-
Notifications
You must be signed in to change notification settings - Fork 0
develp branch 작업내용 머지 : develop -> main #238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9faa907
8aef1d6
5cd1df0
f18efc4
f7933a8
0c37602
bba8dc8
1dd76b2
58a6077
d4eaa41
f5b01df
81b5d52
3dd456d
c8c7851
3f5f3ed
7c12d98
cae0452
1fcb71f
596494b
5bd68a3
e0710f2
b151dd6
03812ff
26c6173
1c27ed1
d05d8ca
77af4b7
5ba4424
126ca23
3300b4e
73935a6
54f0955
8881134
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import { apiClient } from '../index'; | ||
|
|
||
| export interface PresignedUrlRequest { | ||
| extension: string; | ||
| size: number; | ||
| } | ||
|
|
||
| export interface PresignedUrlResponse { | ||
| isSuccess: boolean; | ||
| code: number; | ||
| message: string; | ||
| data?: { | ||
| presignedUrls: Array<{ | ||
| presignedUrl: string; | ||
| fileUrl: string; | ||
| }>; | ||
| }; | ||
| } | ||
|
|
||
| export const getPresignedUrl = async ( | ||
| requests: PresignedUrlRequest[] | ||
| ): Promise<PresignedUrlResponse> => { | ||
| const { data } = await apiClient.post<PresignedUrlResponse>( | ||
| '/feeds/images/presigned-url', | ||
| requests | ||
| ); | ||
|
|
||
| return data; | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| export const uploadFileToS3 = async ( | ||
| presignedUrl: string, | ||
| file: File | ||
| ): Promise<boolean> => { | ||
| try { | ||
| const response = await fetch(presignedUrl, { | ||
| method: 'PUT', | ||
| body: file, | ||
| headers: { | ||
| 'Content-Type': file.type, | ||
| }, | ||
| }); | ||
|
|
||
| return response.ok; | ||
| } catch (error) { | ||
| console.error('S3 업로드 실패:', error); | ||
| return false; | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { apiClient } from '../index'; | ||
| import type { UpdateRecordRequest, UpdateRecordData, ApiResponse } from '@/types/record'; | ||
|
|
||
| // API 응답 타입 | ||
| export type UpdateRecordResponse = ApiResponse<UpdateRecordData>; | ||
|
|
||
| // 기록 수정 API 함수 | ||
| export const updateRecord = async ( | ||
| roomId: number, | ||
| recordId: number, | ||
| recordData: UpdateRecordRequest, | ||
| ): Promise<UpdateRecordResponse> => { | ||
| try { | ||
| const response = await apiClient.patch<UpdateRecordResponse>( | ||
| `/rooms/${roomId}/records/${recordId}`, | ||
| recordData, | ||
| ); | ||
| return response.data; | ||
| } catch (error) { | ||
| console.error('기록 수정 API 오류:', error); | ||
| throw error; | ||
| } | ||
| }; | ||
|
|
||
| /* | ||
| 사용 예시: | ||
| const recordData: UpdateRecordRequest = { | ||
| content: "수정된 기록 내용입니다~~" | ||
| }; | ||
|
|
||
| try { | ||
| const result = await updateRecord(1, 123, recordData); | ||
| if (result.isSuccess) { | ||
| console.log("수정된 방 ID:", result.data.roomId); | ||
| // 성공 처리 로직 | ||
| } else { | ||
| console.error("기록 수정 실패:", result.message); | ||
| // 실패 처리 로직 | ||
| } | ||
| } catch (error) { | ||
| console.error("API 호출 오류:", error); | ||
| // 에러 처리 로직 | ||
| } | ||
| */ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { apiClient } from '../index'; | ||
| import type { UpdateVoteRequest, UpdateVoteData, ApiResponse } from '@/types/record'; | ||
|
|
||
| // API 응답 타입 | ||
| export type UpdateVoteResponse = ApiResponse<UpdateVoteData>; | ||
|
|
||
| // 투표 수정 API 함수 | ||
| export const updateVote = async ( | ||
| roomId: number, | ||
| voteId: number, | ||
| voteData: UpdateVoteRequest, | ||
| ): Promise<UpdateVoteResponse> => { | ||
| try { | ||
| const response = await apiClient.patch<UpdateVoteResponse>( | ||
| `/rooms/${roomId}/votes/${voteId}`, | ||
| voteData, | ||
| ); | ||
| return response.data; | ||
| } catch (error) { | ||
| console.error('투표 수정 API 오류:', error); | ||
| throw error; | ||
| } | ||
| }; | ||
|
|
||
| /* | ||
| 사용 예시: | ||
| const voteData: UpdateVoteRequest = { | ||
| content: "수정된 투표 내용입니다~" | ||
| }; | ||
|
|
||
| try { | ||
| const result = await updateVote(1, 123, voteData); | ||
| if (result.isSuccess) { | ||
| console.log("수정된 방 ID:", result.data.roomId); | ||
| // 성공 처리 로직 | ||
| } else { | ||
| console.error("투표 수정 실패:", result.message); | ||
| // 실패 처리 로직 | ||
| } | ||
| } catch (error) { | ||
| console.error("API 호출 오류:", error); | ||
| // 에러 처리 로직 | ||
| } | ||
| */ |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,28 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import { apiClient } from '../index'; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // 오늘의 한마디 삭제 응답 타입 | ||||||||||||||||||||||||||||||||||||||||||||||
| export interface DeleteDailyGreetingResponse { | ||||||||||||||||||||||||||||||||||||||||||||||
| isSuccess: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||
| code: number; | ||||||||||||||||||||||||||||||||||||||||||||||
| message: string; | ||||||||||||||||||||||||||||||||||||||||||||||
| data: { | ||||||||||||||||||||||||||||||||||||||||||||||
| roomId: number; | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // 오늘의 한마디 삭제 API 함수 | ||||||||||||||||||||||||||||||||||||||||||||||
| export const deleteDailyGreeting = async ( | ||||||||||||||||||||||||||||||||||||||||||||||
| roomId: number, | ||||||||||||||||||||||||||||||||||||||||||||||
| attendanceCheckId: number, | ||||||||||||||||||||||||||||||||||||||||||||||
| ): Promise<DeleteDailyGreetingResponse> => { | ||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||
| const response = await apiClient.delete<DeleteDailyGreetingResponse>( | ||||||||||||||||||||||||||||||||||||||||||||||
| `/rooms/${roomId}/daily-greeting/${attendanceCheckId}`, | ||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+18
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainDELETE 204(No Content) 응답 시 타입-런타임 불일치로 실패 처리될 위험 서버가 204를 반환하면 response.data가 비거나 없음으로 내려올 수 있어, 호출부에서 한 가지 완화안(호출부 변경 없이 204 허용): - const response = await apiClient.delete<DeleteDailyGreetingResponse>(
- `/rooms/${roomId}/daily-greeting/${attendanceCheckId}`,
- );
-
- return response.data;
+ const response = await apiClient.delete(
+ `/rooms/${roomId}/daily-greeting/${attendanceCheckId}`,
+ );
+ // 2xx는 성공으로 정규화
+ if (response.status >= 200 && response.status < 300) {
+ return (response.data as DeleteDailyGreetingResponse) ?? {
+ isSuccess: true,
+ code: response.status,
+ message: '',
+ data: { roomId },
+ };
+ }
+ return response.data as DeleteDailyGreetingResponse;🏁 Script executed: #!/bin/bash
# 삭제 API 응답 바디 유무/스키마 사용처 점검
rg -n -C2 --type ts "DeleteDailyGreetingResponse|deleteDailyGreeting|daily-greeting"Length of output: 2800 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "===== src/api/rooms/deleteDailyGreeting.ts ====="
sed -n '1,200p' src/api/rooms/deleteDailyGreeting.ts || true
echo
echo "===== src/components/today-words/MessageList/MessageList.tsx (lines ~90-130) ====="
sed -n '90,130p' src/components/today-words/MessageList/MessageList.tsx || trueLength of output: 2020 DELETE 204(No Content) 대응 — deleteDailyGreeting에서 2xx 응답을 성공으로 정규화하세요. 현재 deleteDailyGreeting는 response.data를 바로 반환하므로 서버가 204를 반환하면 호출부의 result.isSuccess 검사에서 실패로 처리됩니다. API 함수에서 2xx 응답을 성공으로 정규화하도록 수정하세요. - const response = await apiClient.delete<DeleteDailyGreetingResponse>(
- `/rooms/${roomId}/daily-greeting/${attendanceCheckId}`,
- );
-
- return response.data;
+ const response = await apiClient.delete(
+ `/rooms/${roomId}/daily-greeting/${attendanceCheckId}`,
+ );
+ // 2xx는 성공으로 정규화
+ if (response.status >= 200 && response.status < 300) {
+ return (response.data as DeleteDailyGreetingResponse) ?? {
+ isSuccess: true,
+ code: response.status,
+ message: '',
+ data: { roomId },
+ };
+ }
+ return response.data as DeleteDailyGreetingResponse;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| console.error('오늘의 한마디 삭제 API 오류:', error); | ||||||||||||||||||||||||||||||||||||||||||||||
| throw error; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
대외 호출: 타임아웃/재시도/에러 메시지 보강 필요
현재 무한 대기 가능성과 일시적 5xx에 취약합니다. 업로드 UX/복구력을 위해 최소 타임아웃과 제한적 재시도를 권장합니다.
export const uploadFileToS3 = async ( presignedUrl: string, file: File ): Promise<boolean> => { try { - const response = await fetch(presignedUrl, { - method: 'PUT', - body: file, - headers: { - 'Content-Type': file.type, - }, - }); + // 15s 타임아웃 + 최대 2회 재시도(총 3회) + const maxAttempts = 3; + const timeoutMs = 15000; + let lastError: unknown = null; + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), timeoutMs); + try { + const response = await fetch(presignedUrl, { + method: 'PUT', + body: file, + headers: { 'Content-Type': file.type }, + signal: controller.signal, + }); + clearTimeout(timer); + if (response.ok) return true; + // 5xx만 재시도, 그 외는 즉시 실패 + if (response.status < 500 || response.status >= 600) { + const msg = await response.text().catch(() => ''); + console.error(`S3 업로드 실패: ${response.status} ${msg}`); + return false; + } + } catch (e) { + clearTimeout(timer); + lastError = e; + // AbortError 등은 재시도 + } + // 지수 백오프(최대 ~1s) + await new Promise(r => setTimeout(r, Math.min(1000, 100 * 2 ** (attempt - 1)))); + } + console.error('S3 업로드 반복 실패:', lastError); + return false; - return response.ok; } catch (error) { console.error('S3 업로드 실패:', error); return false; } };🤖 Prompt for AI Agents