fix: 게시판 쓰기 실패가 잡히지 않아 크래시로 이어지던 문제
등록·수정·답변·삭제 네 곳 모두 Repository 호출을 try/catch 없이 부르고 있었다. 첨부 업로드만 감싸여 있었다. 백엔드가 거절하면 `BackendRequestError`가 Server Action 밖으로 그대로 튀어 프레임워크의 오류 처리로 넘어가고, 화면에는 "Cannot read properties of undefined (reading 'stack')" 같은 엉뚱한 오류가 뜬다 — 사용자는 무엇이 잘못됐는지 알 수 없다. 네 곳을 `runWrite`로 감싸 실패를 폼 상태로 바꾼다. 백엔드가 준 문구를 그대로 보여주고, 그것이 없을 때만 일반 문구로 떨어진다 — 다른 화면(관리자·아이템)이 이미 쓰는 방식이다. Co-Authored-By: Claude Opus 5
@3149cbe3cbdf72ee3939423bc1728648cbe37aff
--- app/(protected)/(basic)/boards/_actions.ts
+++ app/(protected)/(basic)/boards/_actions.ts
... | ... | @@ -52,6 +52,23 @@ |
| 52 | 52 |
const OVERSIZE_MESSAGE = `첨부파일은 ${Math.floor(
|
| 53 | 53 |
MAX_ATTACHMENT_BYTES / (1024 * 1024) |
| 54 | 54 |
)}MB 이하만 올릴 수 있습니다.`; |
| 55 |
+const SAVE_FAILED_MESSAGE = '저장하지 못했습니다. 잠시 후 다시 시도해 주세요.'; |
|
| 56 |
+const DELETE_FAILED_MESSAGE = '삭제하지 못했습니다. 잠시 후 다시 시도해 주세요.'; |
|
| 57 |
+ |
|
| 58 |
+async function runWrite( |
|
| 59 |
+ write: () => Promise<void>, |
|
| 60 |
+ fallback: string |
|
| 61 |
+): Promise<BoardPostFormState | null> {
|
|
| 62 |
+ try {
|
|
| 63 |
+ await write(); |
|
| 64 |
+ return null; |
|
| 65 |
+ } catch (error) {
|
|
| 66 |
+ return {
|
|
| 67 |
+ status: 'error', |
|
| 68 |
+ message: error instanceof Error ? error.message : fallback, |
|
| 69 |
+ }; |
|
| 70 |
+ } |
|
| 71 |
+} |
|
| 55 | 72 |
|
| 56 | 73 |
function readString(formData: FormData, key: string): string {
|
| 57 | 74 |
const value = formData.get(key); |
... | ... | @@ -136,7 +153,13 @@ |
| 136 | 153 |
return { status: 'error', errors: validation.errors };
|
| 137 | 154 |
} |
| 138 | 155 |
|
| 139 |
- await createBoardPost(boardType, validation.values); |
|
| 156 |
+ const failure = await runWrite( |
|
| 157 |
+ () => createBoardPost(boardType, validation.values), |
|
| 158 |
+ SAVE_FAILED_MESSAGE |
|
| 159 |
+ ); |
|
| 160 |
+ if (failure) {
|
|
| 161 |
+ return failure; |
|
| 162 |
+ } |
|
| 140 | 163 |
|
| 141 | 164 |
revalidatePath(BOARD_PATHS[boardType]); |
| 142 | 165 |
return { status: 'success' };
|
... | ... | @@ -173,7 +196,13 @@ |
| 173 | 196 |
return { status: 'error', errors: validation.errors };
|
| 174 | 197 |
} |
| 175 | 198 |
|
| 176 |
- await updateBoardPost(boardType, bbsId, validation.values); |
|
| 199 |
+ const failure = await runWrite( |
|
| 200 |
+ () => updateBoardPost(boardType, bbsId, validation.values), |
|
| 201 |
+ SAVE_FAILED_MESSAGE |
|
| 202 |
+ ); |
|
| 203 |
+ if (failure) {
|
|
| 204 |
+ return failure; |
|
| 205 |
+ } |
|
| 177 | 206 |
|
| 178 | 207 |
revalidatePath(BOARD_PATHS[boardType]); |
| 179 | 208 |
return { status: 'success' };
|
... | ... | @@ -213,7 +242,13 @@ |
| 213 | 242 |
return { status: 'error', errors: validation.errors };
|
| 214 | 243 |
} |
| 215 | 244 |
|
| 216 |
- await saveInquiryAnswer(bbsId, validation.values); |
|
| 245 |
+ const failure = await runWrite( |
|
| 246 |
+ () => saveInquiryAnswer(bbsId, validation.values), |
|
| 247 |
+ SAVE_FAILED_MESSAGE |
|
| 248 |
+ ); |
|
| 249 |
+ if (failure) {
|
|
| 250 |
+ return failure; |
|
| 251 |
+ } |
|
| 217 | 252 |
|
| 218 | 253 |
revalidatePath(BOARD_PATHS.inquiry); |
| 219 | 254 |
return { status: 'success' };
|
... | ... | @@ -235,7 +270,13 @@ |
| 235 | 270 |
return { status: 'error', message: INVALID_REQUEST_MESSAGE };
|
| 236 | 271 |
} |
| 237 | 272 |
|
| 238 |
- await deleteBoardPost(bbsId); |
|
| 273 |
+ const failure = await runWrite( |
|
| 274 |
+ () => deleteBoardPost(bbsId), |
|
| 275 |
+ DELETE_FAILED_MESSAGE |
|
| 276 |
+ ); |
|
| 277 |
+ if (failure) {
|
|
| 278 |
+ return failure; |
|
| 279 |
+ } |
|
| 239 | 280 |
|
| 240 | 281 |
revalidatePath(BOARD_PATHS[boardType]); |
| 241 | 282 |
return { status: 'success' };
|
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?