feat: 감정일기 수정 , 조회 api 추가 - #113
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthrough일기 CRUD 전체 흐름을 구현한다. Prisma 서비스 계층(createDiary/updateDiaryById/deleteDiaryById/getDiaryById), API 엔드포인트(POST/GET/PATCH/DELETE), 클라이언트 API 함수 및 React Query 훅(useCreateDiary/useUpdateDiary/useDeleteDiary)을 추가하고, 일기 상세 페이지를 서버 컴포넌트로 전환하며, 수정 전용 페이지와 수정/삭제 액션 컴포넌트를 신규 추가한다. 캘린더 페이지 헤더 추가 및 네비게이션 경로 조정도 포함된다. Changes일기 CRUD 및 상세 페이지 전환
Sequence Diagram(s)sequenceDiagram
participant User
participant DiaryDetailPage as /diary/[id]<br/>(server)
participant DiaryActions as DiaryActions<br/>(client)
participant DiaryAPI as /api/diary/[diaryId]
participant DiaryEditPage as /diary/[id]/edit<br/>(server)
participant DiaryCreateForm as DiaryCreate<br/>(mode=edit)
rect rgba(70, 130, 180, 0.5)
note over User, DiaryDetailPage: 일기 상세 조회
User->>DiaryDetailPage: 페이지 접근
DiaryDetailPage->>DiaryDetailPage: getServerSession + getDiaryById
DiaryDetailPage-->>User: DiaryDetailHeader + DiaryActions 렌더
end
rect rgba(60, 179, 113, 0.5)
note over User, DiaryCreateForm: 일기 수정 흐름
User->>DiaryActions: 수정 버튼 클릭
DiaryActions->>DiaryEditPage: router.push(/diary/[id]/edit)
DiaryEditPage->>DiaryEditPage: getServerSession + getDiaryById
DiaryEditPage-->>DiaryCreateForm: mode="edit" + diary 데이터
User->>DiaryCreateForm: 폼 수정 및 제출
DiaryCreateForm->>DiaryAPI: PATCH { title, content, emotionType }
DiaryAPI-->>DiaryCreateForm: 200 { success }
DiaryCreateForm-->>User: /diary/[id]로 이동
end
rect rgba(220, 80, 80, 0.5)
note over User, DiaryAPI: 일기 삭제 흐름
User->>DiaryActions: 삭제 버튼 클릭
DiaryActions->>DiaryActions: 삭제 확인 모달 표시
User->>DiaryActions: 삭제 확인 버튼
DiaryActions->>DiaryAPI: DELETE /api/diary/[diaryId]
DiaryAPI-->>DiaryActions: 200 { success }
DiaryActions-->>User: /calendar로 이동
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/app/api/disputes/route.ts (2)
86-129:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
completed=true필터 제거로 완료 목록 API 계약이 깨졌습니다.이 변경 이후
completed=true가 더 이상 완료 상태 필터링을 수행하지 않아, 완료 사건 목록 조회가 의도와 다르게 동작합니다. 현재 클라이언트는 여전히completed=true로 요청하고 있어(완료 목록 훅/레코드 목록 경로), 결과 데이터 정합성이 깨집니다.completed분기를 복원하거나, 최소한 API/클라이언트를 동일 파라미터 체계로 동시에 맞춰야 합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/api/disputes/route.ts` around lines 86 - 129, The `completed` query parameter filtering has been removed from the where clause construction, causing the API contract to break since clients still send `completed=true` requests for completed dispute lists. Restore the handling for the completed parameter in the where clause by adding a conditional that filters for completed statuses (such as DisputeStatus.JUDGED, DisputeStatus.CLOSED, DisputeStatus.EXPIRED, etc.) when the completed parameter is true, similar to how the active and rawStatus parameters are currently handled in the spread operator pattern.
83-84:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
page/limit숫자 파싱 실패 시NaN이 그대로 전파됩니다.
parseInt가 실패하면NaN이 되고, 현재 보정식으로는 정상 숫자로 복구되지 않습니다. 이 값이skip/take로 들어가면 조회가 500으로 실패할 수 있습니다.제안 수정안
- const page = Math.max(1, parseInt(searchParams.get('page') ?? '1', 10)) - const limit = Math.min(50, Math.max(1, parseInt(searchParams.get('limit') ?? '20', 10))) + const parsedPage = Number.parseInt(searchParams.get('page') ?? '1', 10) + const parsedLimit = Number.parseInt(searchParams.get('limit') ?? '20', 10) + const page = Number.isFinite(parsedPage) ? Math.max(1, parsedPage) : 1 + const limit = Number.isFinite(parsedLimit) ? Math.min(50, Math.max(1, parsedLimit)) : 20🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/api/disputes/route.ts` around lines 83 - 84, The parseInt operations for page and limit parameters can return NaN when parsing fails, and the current Math.max/Math.min operations do not properly handle NaN values since Math operations with NaN return NaN. Add explicit validation using isNaN after each parseInt call to ensure the parsed values are valid numbers, and provide appropriate fallback values (1 for page and 20 for limit) when parsing fails or results in NaN. This will prevent NaN from being passed to the skip and take query parameters which would cause database query failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/`(page)/diary/create/page.tsx:
- Around line 37-38: The current implementation on line 37 uses new
Date().toISOString().slice(0, 10) which calculates the date in UTC time,
potentially causing a one-day offset from the user's local date. This affects
how the diary entry is stored and retrieved in calendar operations. Replace this
UTC-based date calculation with one that uses the user's local timezone by
extracting the year, month, and day from a Date object using local time methods
(getFullYear, getMonth, getDate) and formatting them as YYYY-MM-DD, ensuring the
diaryDate sent to createMutation.mutate reflects the user's actual local date.
In `@src/app/api/diary/`[diaryId]/route.ts:
- Around line 75-107: The PATCH handler (and DELETE handler as noted) lacks
exception handling for potential failures during request.json() parsing and
service operations. Wrap the entire body of the PATCH function in a try-catch
block to handle two error scenarios: catch SyntaxError when request.json() fails
and return a 400 VALIDATION_ERROR response, and catch any other errors that
occur during updateDiaryById execution and return a 500 INTERNAL_SERVER_ERROR
response. Apply the same try-catch pattern to the DELETE handler to ensure
consistent error handling and guarantee all errors return properly formatted
ApiResponse objects.
In `@src/app/api/diary/route.ts`:
- Line 108: The catch block at line 108 in the GET handler is silently consuming
errors without any logging, making it impossible to debug failures in
production. Add error logging inside this catch block that captures the actual
error details and includes the route prefix (e.g., "GET /api/diary") to enable
operational traceability and incident diagnosis. This aligns with the logging
MVP requirements for API error logs and DB failure logs.
- Around line 11-23: The POST handler lacks exception handling around the
request.json() call and body destructuring at lines 21-22, which can throw
uncaught exceptions that don't follow the common ApiResponse format. Wrap the
body parsing (request.json()) and destructuring (extracting title, content,
emotionType, diaryDate) in a try-catch block, then return appropriate
ApiResponse formatted error responses for both JSON parsing failures and
validation errors, ensuring all error paths return consistent ApiResponse
structure with proper status codes.
- Around line 24-32: The validation check for diaryDate in the POST route (the
if statement checking !content and !diaryDate) only verifies that the field
exists, but does not validate the date format. Add format validation for
diaryDate to ensure it matches the YYYY-MM-DD format and passes a canonical
check (matching the validation logic used in the GET endpoint). If the diaryDate
format is invalid, return a 400 response with a VALIDATION_ERROR code in the
same manner as the existing validation error response, ensuring that malformed
date inputs are caught at the validation layer rather than escalating to 500
errors in the service layer.
In `@src/domains/diary/diary.service.ts`:
- Around line 24-40: The updateDiaryById function uses a check-then-act pattern
that is not atomic and loses failure cause information. Instead of performing a
separate findUnique query and then updating by id alone, consolidate all
conditions (id, userId, and deletedAt: null) into a single atomic update
operation in the prisma.emotionDiary.update call. Check the count of affected
records from the update result to determine success. Replace the boolean return
type with an enum that distinguishes between three outcomes: success, not found
(404), and unauthorized (403). Apply the same atomic update pattern and enum
return type to the delete operation as well (mentioned to also apply at lines
49-67).
In `@src/types/diary.ts`:
- Line 59: The emotionType field in the DiaryDetail type is currently defined as
string | null, which allows any string value to be passed through the edit flow
without validation. Change the type of the emotionType property in the
DiaryDetail interface from string | null to EmotionType | null to enforce that
only valid emotion type values can be assigned at compile time. This will
prevent invalid emotion strings from being retained or resent during the diary
edit operations referenced in the create page.
---
Outside diff comments:
In `@src/app/api/disputes/route.ts`:
- Around line 86-129: The `completed` query parameter filtering has been removed
from the where clause construction, causing the API contract to break since
clients still send `completed=true` requests for completed dispute lists.
Restore the handling for the completed parameter in the where clause by adding a
conditional that filters for completed statuses (such as DisputeStatus.JUDGED,
DisputeStatus.CLOSED, DisputeStatus.EXPIRED, etc.) when the completed parameter
is true, similar to how the active and rawStatus parameters are currently
handled in the spread operator pattern.
- Around line 83-84: The parseInt operations for page and limit parameters can
return NaN when parsing fails, and the current Math.max/Math.min operations do
not properly handle NaN values since Math operations with NaN return NaN. Add
explicit validation using isNaN after each parseInt call to ensure the parsed
values are valid numbers, and provide appropriate fallback values (1 for page
and 20 for limit) when parsing fails or results in NaN. This will prevent NaN
from being passed to the skip and take query parameters which would cause
database query failures.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 39c725d0-77f5-412c-9b46-e81b29280cea
📒 Files selected for processing (23)
src/app/(page)/calendar/page.module.scsssrc/app/(page)/calendar/page.tsxsrc/app/(page)/diary/[id]/DiaryActions.module.scsssrc/app/(page)/diary/[id]/DiaryActions.tsxsrc/app/(page)/diary/[id]/DiaryDetailHeader.tsxsrc/app/(page)/diary/[id]/edit/page.tsxsrc/app/(page)/diary/[id]/page.module.scsssrc/app/(page)/diary/[id]/page.tsxsrc/app/(page)/diary/create/page.module.scsssrc/app/(page)/diary/create/page.tsxsrc/app/api/calendar/route.tssrc/app/api/diary/[diaryId]/route.tssrc/app/api/diary/route.tssrc/app/api/disputes/route.tssrc/components/calendar/RecordList.module.scsssrc/components/diary/EmotionDiaryList.module.scsssrc/components/layout/BottomNavigation.tsxsrc/domains/diary/diary.api.tssrc/domains/diary/diary.hooks.tssrc/domains/diary/diary.service.tssrc/domains/dispute/dispute.api.tssrc/domains/dispute/dispute.hooks.tssrc/types/diary.ts
…ature/calendar-daily-record-api
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/components/layout/Header.tsx (1)
32-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win접근성 향상을 위해 Link에 aria-label 추가를 고려하세요.
로고 이미지를 Link로 감싼 구현은 정상적으로 동작하지만, 스크린 리더 사용자를 위해 Link 컴포넌트에 명시적인
aria-label을 추가하면 링크의 목적을 더 명확하게 전달할 수 있습니다.♿ 접근성 개선 제안
- <Link href="/"> + <Link href="/" aria-label="홈으로 이동"> <Image src="/images/common/logo.svg" alt="말해부엉"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/layout/Header.tsx` around lines 32 - 40, The Link component wrapping the logo image lacks an aria-label attribute, which reduces accessibility for screen reader users. Add an aria-label prop to the Link component (which has href="/") with a descriptive label that communicates the purpose of the link, such as navigating to the home page. This will help screen reader users understand that clicking the logo returns them to the homepage.src/domains/diary/diary.service.ts (1)
30-30: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win
emotionType입력 타입을 도메인 유니언으로 좁혀 주세요.
src/types/diary.ts의 허용값 계약이 있는데 여기서는string으로 열려 있어 잘못된 감정값이 서비스 경계까지 들어올 수 있습니다.수정 예시
- data: { title: string; content: string; emotionType: string }, + data: { title: string; content: string; emotionType: EmotionType },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/domains/diary/diary.service.ts` at line 30, The emotionType field in the data parameter of the diary service method is currently typed as a generic string, which allows invalid emotion values. Import the emotion type union from src/types/diary.ts and replace the emotionType string type with the proper domain union type to ensure only valid emotion values are accepted at the service boundary.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/api/diary/route.ts`:
- Around line 66-67: In the catch block of the diary creation route (the POST
endpoint in src/app/api/diary/route.ts), replace the direct console.error(error)
call that logs the raw error object with a safer logging approach. Instead of
logging the entire error object which may contain sensitive diary data or
personal information, construct a log message that includes only the route
prefix (like the API endpoint path) and non-sensitive metadata about the error
(such as error type or code). This prevents sensitive data like diary original
text or personal information from being exposed in logs while still providing
useful debugging information.
- Around line 56-57: The emotionType parameter received from the client is
passed directly to the createDiary function without validation against the
EmotionType contract, which can result in invalid values being stored or causing
runtime errors. Before calling createDiary in the try block, validate that the
emotionType value is one of the allowed EmotionType values (not just applying a
default fallback). If the provided emotionType is invalid, either return an
appropriate error response to the client or validate it against a whitelist of
valid emotion types to ensure only valid values are persisted to the database.
---
Nitpick comments:
In `@src/components/layout/Header.tsx`:
- Around line 32-40: The Link component wrapping the logo image lacks an
aria-label attribute, which reduces accessibility for screen reader users. Add
an aria-label prop to the Link component (which has href="/") with a descriptive
label that communicates the purpose of the link, such as navigating to the home
page. This will help screen reader users understand that clicking the logo
returns them to the homepage.
In `@src/domains/diary/diary.service.ts`:
- Line 30: The emotionType field in the data parameter of the diary service
method is currently typed as a generic string, which allows invalid emotion
values. Import the emotion type union from src/types/diary.ts and replace the
emotionType string type with the proper domain union type to ensure only valid
emotion values are accepted at the service boundary.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 6a9a72b8-bb4d-4ed7-bcfb-748f4aff34e1
📒 Files selected for processing (5)
src/app/api/diary/[diaryId]/route.tssrc/app/api/diary/route.tssrc/components/layout/Header.tsxsrc/domains/diary/diary.service.tssrc/types/diary.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/types/diary.ts
- src/app/api/diary/[diaryId]/route.ts
| try { | ||
| const diaryId = await createDiary(userId, { title: title ?? '', content, emotionType: emotionType ?? 'neutral', diaryDate }); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
emotionType 허용값을 검증한 뒤 저장해 주세요.
현재는 클라이언트 문자열을 그대로 전달하므로 EmotionType 계약 밖의 값이 500으로 승격되거나 잘못 저장될 수 있습니다.
수정 예시
-import type { DiaryItem } from '`@/types/diary`';
+import type { DiaryItem, EmotionType } from '`@/types/diary`';
+
+const EMOTION_TYPES: readonly EmotionType[] = ['happy', 'sad', 'angry', 'annoyed', 'neutral'];
+const isEmotionType = (value: unknown): value is EmotionType =>
+ typeof value === 'string' && EMOTION_TYPES.includes(value as EmotionType);+ const normalizedEmotionType = emotionType ?? 'neutral';
+ if (!isEmotionType(normalizedEmotionType)) {
+ return NextResponse.json<ApiResponse>(
+ { success: false, error: { code: 'VALIDATION_ERROR', message: 'emotionType 값이 올바르지 않습니다.' } },
+ { status: 400 },
+ );
+ }
+
try {
- const diaryId = await createDiary(userId, { title: title ?? '', content, emotionType: emotionType ?? 'neutral', diaryDate });
+ const diaryId = await createDiary(userId, { title: title ?? '', content, emotionType: normalizedEmotionType, diaryDate });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/api/diary/route.ts` around lines 56 - 57, The emotionType parameter
received from the client is passed directly to the createDiary function without
validation against the EmotionType contract, which can result in invalid values
being stored or causing runtime errors. Before calling createDiary in the try
block, validate that the emotionType value is one of the allowed EmotionType
values (not just applying a default fallback). If the provided emotionType is
invalid, either return an appropriate error response to the client or validate
it against a whitelist of valid emotion types to ensure only valid values are
persisted to the database.
| } catch (error) { | ||
| console.error(error); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
POST 오류 로그에서 raw error 객체를 그대로 남기지 마세요.
일기 생성 경로는 민감한 원문을 다루므로 예외 객체 전체 대신 route prefix와 비민감 메타데이터만 남기는 편이 안전합니다.
수정 예시
} catch (error) {
- console.error(error);
+ console.error('[POST /api/diary]', {
+ name: error instanceof Error ? error.name : 'UnknownError',
+ type: typeof error,
+ });As per coding guidelines, src/app/api/**/*.{ts,tsx}는 Do not store unnecessary case original text, diary original text, or personal information in logs를 요구합니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } catch (error) { | |
| console.error(error); | |
| } catch (error) { | |
| console.error('[POST /api/diary]', { | |
| name: error instanceof Error ? error.name : 'UnknownError', | |
| type: typeof error, | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/api/diary/route.ts` around lines 66 - 67, In the catch block of the
diary creation route (the POST endpoint in src/app/api/diary/route.ts), replace
the direct console.error(error) call that logs the raw error object with a safer
logging approach. Instead of logging the entire error object which may contain
sensitive diary data or personal information, construct a log message that
includes only the route prefix (like the API endpoint path) and non-sensitive
metadata about the error (such as error type or code). This prevents sensitive
data like diary original text or personal information from being exposed in logs
while still providing useful debugging information.
Source: Coding guidelines
Summary by CodeRabbit
릴리스 노트
신기능
개선사항