Skip to content

feat: #279 다구 관리 + 차록 연동#283

Open
FLYLIKEB wants to merge 2 commits into
mainfrom
feature/issue-279-teaware-management
Open

feat: #279 다구 관리 + 차록 연동#283
FLYLIKEB wants to merge 2 commits into
mainfrom
feature/issue-279-teaware-management

Conversation

@FLYLIKEB
Copy link
Copy Markdown
Owner

@FLYLIKEB FLYLIKEB commented Mar 18, 2026

Summary

  • Closes feat: 다구(茶具) 관리 및 차록 연동 기능 #279
  • Teaware 모듈 신규: entity(카테고리 enum, capacity, material, memo, isPinned), DTO, Service, Controller, Module
  • TypeORM migration: CreateTeaware + AddTeawareIdToNotes (FK, ON DELETE SET NULL)
  • CRUD + pin 토글 API, JWT 인증 (클래스 레벨 가드)
  • E2E 테스트 7건: CRUD/pin/401/cross-user 격리(다른 사용자 404)
  • 프론트엔드: Teaware 타입, teawareApi, TeawareSection 컴포넌트
  • Settings 다구 관리 UI, NewNote 다구 선택(핀 자동 선택), NoteDetail 다구 표시
  • CreateNoteDto.teawareId @ISINT @ispositive 수정, memo @maxlength(500)

Test plan

  • npm run build ✅ (frontend)
  • cd backend && npm run build
  • code-reviewer 에이전트 검토 완료 (HIGH 이슈 수정 완료)

🤖 Generated with Claude Code

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능
    • 다구 관리 기능 추가: 사용하는 다구(차 도구)를 등록, 편집, 삭제할 수 있습니다
    • 즐겨 찾는 다구를 핀 고정하여 빠르게 접근 가능
    • 새 기록 작성 시 사용한 다구를 연결하여 관리
    • 기록 상세 페이지에서 사용한 다구 정보 표시
    • 설정 페이지에서 보유한 다구 목록 한눈에 확인

FLYLIKEB and others added 2 commits March 18, 2026 17:57
- Teaware 엔티티/DTO/서비스/컨트롤러/모듈 신규 생성
- TypeORM migration: CreateTeaware, AddTeawareIdToNotes
- CRUD + pin 토글 API (JWT 인증)
- E2E 테스트 6건 추가 (POST/GET/PATCH/DELETE/pin/401)
- 프론트엔드: Teaware 타입, teawareApi, TeawareSection 컴포넌트
- Settings 다구 관리 UI, NewNote 다구 선택, NoteDetail 다구 표시

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Note.teaware ManyToOne에 onDelete: 'SET NULL' 추가 (migration과 일치)
- CreateTeawareDto.memo에 @maxlength(500) 추가
- CreateNoteDto.teawareId @IsNumber → @ISINT @ispositive로 수정
- TeawareService.remove 반환 타입 Promise<Teaware> → Promise<void>
- 다른 사용자 다구 접근 시 404 E2E 격리 테스트 추가
- NewNote.tsx 핀 표시 이모지 → [핀] 텍스트로 변경

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cha-log Ready Ready Preview, Comment Mar 18, 2026 9:14am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 18, 2026

Walkthrough

이 PR은 다구(차 도구) 관리 기능을 추가합니다. 백엔드에 Teaware 엔티티, CRUD 서비스 및 REST 컨트롤러를 구현하고, 프론트엔드에 관리 UI 컴포넌트와 API를 추가합니다. Note 엔티티에 teawareId 관계를 통합하며, 포괄적인 E2E 테스트를 포함합니다.

Changes

Cohort / File(s) Summary
Database Migrations
backend/migrations/1814000000000-CreateTeaware.ts, backend/migrations/1814000000001-AddTeawareIdToNotes.ts
Teaware 테이블 생성 및 Note 테이블에 teawareId 외래키 추가. Teaware는 userId FK, 카테고리 열거형, 용량/소재/메모 필드 포함.
Teaware Entity & DTOs
backend/src/teaware/entities/teaware.entity.ts, backend/src/teaware/dto/create-teaware.dto.ts, backend/src/teaware/dto/update-teaware.dto.ts
Teaware 엔티티 정의 (TeawareCategory 열거형 포함), 검증 규칙이 있는 생성/수정 DTO. 필드: 이름, 카테고리, 용량, 소재, 메모, isPinned.
Teaware Service & Controller
backend/src/teaware/teaware.service.ts, backend/src/teaware/teaware.controller.ts
CRUD 및 togglePin 작업, 사용자별 범위 지정. 컨트롤러는 JWT 보호된 REST 엔드포인트 제공.
Teaware Module & App Setup
backend/src/teaware/teaware.module.ts, backend/src/app.module.ts
TeawareModule 등록 및 앱 모듈에 통합.
Notes Integration
backend/src/notes/entities/note.entity.ts, backend/src/notes/dto/create-note.dto.ts, src/hooks/useNoteForm.ts, src/lib/api/notes.api.ts
Note 엔티티에 teawareId 및 teaware 관계 추가. CreateNoteDto에 teawareId 필드 추가. useNoteForm 훅 확장하여 teaware 상태 관리 및 prefetch 로직 포함.
Frontend API & Types
src/lib/api/teaware.api.ts, src/lib/api/index.ts, src/types/index.ts
Teaware API 모듈 (getAll, getOne, create, update, remove, togglePin). API 내보내기 및 Teaware 타입 정의.
Frontend Components & Pages
src/components/teaware/TeawareSection.tsx, src/pages/NewNote.tsx, src/pages/NoteDetail.tsx, src/pages/Settings.tsx
TeawareSection 컴포넌트 (목록, 추가, 삭제, 핀 토글). NewNote에 teaware 선택 UI 추가. NoteDetail에서 사용된 teaware 표시. Settings에 TeawareSection 렌더링.
Testing
backend/test/suites/teaware.e2e-spec.ts, backend/test/app.e2e-spec.ts, backend/test/setup/test-setup.ts
포괄적인 E2E 테스트 (인증, CRUD, 교차 사용자 접근 제어, togglePin). 테스트 설정 정리 업데이트.

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자 (Client)
    participant Browser as Browser
    participant Server as NestJS Server
    participant DB as Database

    User->>Browser: 설정에서 다구 추가 버튼 클릭
    Browser->>Browser: TeawareSection 렌더링<br/>fetchTeawares() 호출
    Browser->>Server: GET /teaware (JWT)
    Server->>DB: SELECT * FROM teaware WHERE userId = ?
    DB-->>Server: 다구 목록
    Server-->>Browser: [Teaware[]]
    Browser->>Browser: 상태 업데이트, 목록 표시

    User->>Browser: "추가" 버튼 클릭
    Browser->>Browser: Dialog 열기, 폼 초기화
    User->>Browser: 폼 입력 (이름, 카테고리, 용량 등)
    User->>Browser: "추가" 버튼 클릭
    Browser->>Server: POST /teaware<br/>{name, category, capacity, ...}
    Server->>DB: INSERT INTO teaware (userId, name, ...)<br/>VALUES (?, ?, ...)
    DB-->>Server: 생성된 Teaware
    Server-->>Browser: {id, name, category, ...}
    Browser->>Browser: 목록에 추가, Dialog 닫기<br/>성공 Toast 표시
Loading
sequenceDiagram
    participant User as 사용자 (Client)
    participant Browser as Browser
    participant Server as NestJS Server
    participant DB as Database

    User->>Browser: 차록 작성 페이지 열기
    Browser->>Browser: useNoteForm() 초기화
    Browser->>Server: GET /teaware (JWT)
    Server->>DB: SELECT * FROM teaware<br/>WHERE userId = ? ORDER BY isPinned DESC
    DB-->>Server: 핀된 다구 포함 목록
    Server-->>Browser: [Teaware[]]
    Browser->>Browser: teawares 상태 저장<br/>핀된 다구 자동 선택

    User->>Browser: 다구 선택 변경 (Dropdown)
    Browser->>Browser: teawareId 상태 업데이트
    User->>Browser: 차록 저장 버튼 클릭
    Browser->>Server: POST /notes<br/>{content, drinkDate, teawareId, ...}
    Server->>DB: INSERT INTO notes<br/>(userId, content, teawareId, ...)<br/>VALUES (?, ?, ?, ...)
    DB-->>Server: 생성된 Note
    Server-->>Browser: {id, content, teaware: {...}, ...}
    Browser->>Browser: 차록 상세 페이지 이동<br/>teaware 정보 표시
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: 백엔드 구축 및 프론트엔드 연동 #9: Notes 및 백엔드 모듈이 도입된 PR로, 이 PR에서 동일한 notes DTO/엔티티 파일에 Teaware 엔티티 및 teawareId 필드/관계를 추가하므로 직접적으로 관련.
  • Feature/UUID to int migration #15: 두 PR 모두 notes 데이터 모델을 수정 — 이 PR은 teawareId 관계를 추가하고 해당 PR은 ID 타입을 UUID에서 int로 변경하므로 충돌 가능성.
  • feat: Implement rating schema versioning system (Issue #71) #89: 두 PR 모두 notes 데이터 모델을 수정 (create-note.dto.ts, note.entity.ts) — 이 PR은 teawareId/teaware 관계를 추가하고 해당 PR은 평가를 스키마/축 모델로 재구조화하므로 동일 핵심 파일 수정.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목 'feat: #279 다구 관리 + 차록 연동'은 이 PR의 주요 변경사항(다구 관리 기능 추가 및 차록 연동)을 명확하고 간결하게 요약하고 있습니다.
Linked Issues check ✅ Passed PR은 #279의 모든 핵심 요구사항을 충족합니다: (1) 다구 등록/관리 CRUD [entity, DTO, service, controller], (2) 카테고리별 핀 설정 [isPinned, togglePin], (3) 차록 연동 [Note.teaware 관계, NewNote 자동선택, NoteDetail 표시].
Out of Scope Changes check ✅ Passed 모든 변경사항이 다구 관리 및 차록 연동 기능의 범위 내입니다. 추가된 파일은 모두 다구 관련 모듈, 타입, API, UI 컴포넌트이며, 기존 파일 수정도 다구 기능 통합에만 관련되어 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/issue-279-teaware-management
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@FLYLIKEB
Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 18, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (7)
src/types/index.ts (1)

111-116: category 타입을 문자열 유니온으로 좁히는 것을 권장합니다.

현재 string은 오타/잘못된 값이 컴파일 단계에서 걸러지지 않습니다. 가능한 카테고리 집합이 정해져 있다면 유니온 타입으로 제한해 타입 안정성을 높이는 편이 좋습니다.

♻️ 제안 예시
+export type TeawareCategory = '자사호' | '개완' | '공도배' | '잔';

 export interface Teaware {
   id: number;
   userId: number;
   name: string;
-  category: string;
+  category: TeawareCategory;
   capacity?: number | null;
   material?: string | null;
   memo?: string | null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/types/index.ts` around lines 111 - 116, The Teaware interface’s category
is currently a plain string which allows typos; define a specific union type
(e.g. export type TeawareCategory = 'X' | 'Y' | 'Z' or whatever the real allowed
values are) and change Teaware.category to that union (or to TeawareCategory |
null if nullable), then update all usages that expect a string (constructors,
validators, DB mappers, tests, and any deserialization code) to use/validate the
new union type and adjust runtime parsing/validation where input can be
arbitrary.
backend/test/suites/teaware.e2e-spec.ts (2)

8-8: 미사용 변수 teawareId

Line 8에서 선언된 teawareId는 Line 50에서 할당만 되고 이후 사용되지 않습니다. 각 테스트가 독립적으로 데이터를 생성하므로 제거해도 됩니다.

♻️ 수정 제안
 describe('/teaware - 다구 관리 API', () => {
   let context: TestContext;
   let testUser: TestUser;
-  let teawareId: number;

그리고 Line 50도 제거:

     expect(response.body.isPinned).toBe(false);
-    teawareId = response.body.id;
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/test/suites/teaware.e2e-spec.ts` at line 8, Remove the unused
variable teawareId: delete the declaration of teawareId and the assignment to
teawareId found later in the spec (they're unused after assignment); keep each
test creating its own data independently (e.g., leave creation helpers intact)
so removing teawareId doesn't change test behavior.

15-19: beforeEach에서 FK 체크 비활성화 주의

SET FOREIGN_KEY_CHECKS = 0은 테스트 격리에 유용하지만, 실제 FK 제약 조건 관련 버그를 감출 수 있습니다. 현재 구조에서는 notes 테이블과의 관계가 있으므로, notesteawareId도 함께 정리하는 것이 더 안전할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/test/suites/teaware.e2e-spec.ts` around lines 15 - 19, The test
beforeEach currently disables FK checks and only deletes from the teaware table
(in the beforeEach block using context.dataSource.query), which can hide
FK-related bugs; update the setup to properly clear dependent rows by deleting
related rows in the notes table (e.g., remove rows where notes.teawareId
references teaware) before deleting teaware, or use a safe cascade/truncate
approach within the beforeEach to ensure both notes and teaware are cleared
while keeping FK checks enabled.
backend/src/teaware/teaware.controller.ts (1)

74-81: parseInt로 이미 숫자인 userId 파싱

JWT 전략에서 req.user.userId는 이미 숫자 타입입니다 (jwt.strategy.ts Line 30-37 참조). parseInt는 불필요하며, 직접 타입 검사가 더 명확합니다.

♻️ 타입 안전한 파싱 제안
   private parseUserId(req: any): number {
-    const parsed = parseInt(req.user?.userId, 10);
-    if (Number.isNaN(parsed)) {
+    const userId = req.user?.userId;
+    if (typeof userId !== 'number' || !Number.isFinite(userId)) {
       throw new BadRequestException('인증 정보가 올바르지 않습니다.');
     }
-    return parsed;
+    return userId;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/teaware/teaware.controller.ts` around lines 74 - 81, The
parseUserId method currently calls parseInt on req.user?.userId which is
unnecessary since jwt.strategy populates userId as a number; update parseUserId
to perform a direct type and validity check instead: verify req.user?.userId
exists and typeof req.user.userId === 'number' and
Number.isFinite(req.user.userId'), throw the existing BadRequestException('인증
정보가 올바르지 않습니다.') on failure, and return the numeric userId otherwise
(referencing parseUserId and req.user.userId to locate the code).
src/components/teaware/TeawareSection.tsx (1)

267-275: 메모 입력 maxLength 누락

이름(maxLength={100})과 재질(maxLength={100})에는 길이 제한이 있지만, 메모에는 없습니다. 백엔드 DTO는 @MaxLength(500)을 적용하고 있어, 프론트엔드에서도 일치시키는 것이 좋습니다.

♻️ 수정 제안
             <div className="space-y-1.5">
               <Label htmlFor="teaware-memo">메모</Label>
               <Input
                 id="teaware-memo"
                 value={form.memo}
                 onChange={(e) => setForm((prev) => ({ ...prev, memo: e.target.value }))}
                 placeholder="간단한 메모"
+                maxLength={500}
               />
             </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/teaware/TeawareSection.tsx` around lines 267 - 275, The memo
Input in TeawareSection is missing a maxLength and should match the backend DTO
(`@MaxLength`(500)); update the <Input id="teaware-memo"> used with form.memo to
include maxLength={500} and keep the existing placeholder and onChange handler
(setForm) unchanged so the UI enforces the same 500-character limit as the
server.
backend/src/teaware/dto/create-teaware.dto.ts (2)

13-15: name 필드에 빈 문자열 검증 누락

@IsString()@MaxLength(100)만으로는 빈 문자열("")이 통과됩니다. @IsNotEmpty() 데코레이터를 추가하여 빈 이름을 방지하는 것이 좋습니다.

♻️ 수정 제안
 import {
   IsString,
   IsEnum,
   IsOptional,
   IsNumber,
   MaxLength,
   Min,
   Max,
+  IsNotEmpty,
 } from 'class-validator';

 export class CreateTeawareDto {
   `@IsString`()
+  `@IsNotEmpty`()
   `@MaxLength`(100)
   name: string;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/teaware/dto/create-teaware.dto.ts` around lines 13 - 15, The name
property in CreateTeawareDto is only validated with `@IsString`() and
`@MaxLength`(100) so an empty string passes; add the `@IsNotEmpty`() decorator to
the name field and import IsNotEmpty from class-validator to ensure "" is
rejected (update the CreateTeawareDto class property `name: string` and the
import list accordingly).

20-24: 용량 검증 범위 확인

@Min(1) 설정으로 최소 1ml부터 허용되는데, 0ml 용량이 필요한 경우가 있을 수 있습니다(예: 차판처럼 용량 개념이 없는 다구). 현재 설계 의도가 맞다면 무시하셔도 됩니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/teaware/dto/create-teaware.dto.ts` around lines 20 - 24, The
capacity field in CreateTeawareDto uses `@Min`(1) which disallows 0ml values
needed for items without capacity; change the validator to allow zero by
replacing `@Min`(1) with `@Min`(0) (or explicitly document/handle null for
capacity?: number | null) so capacity can be 0 while keeping `@Max`(9999.9) and
other decorators intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/teaware/TeawareSection.tsx`:
- Around line 250-255: The capacity input in TeawareSection uses min={0} which
conflicts with the backend DTO `@Min`(1); update the input to enforce min={1} and
ensure the onChange stores a numeric value (e.g., parseInt/Number) into
form.capacity via setForm so the client-side validation matches the backend
(refer to TeawareSection, the capacity input element, form.capacity and the
setForm updater).
- Around line 124-125: Newly created Teaware is appended to state with
setTeawares([...prev, created]) but server orders by isPinned DESC, createdAt
DESC so the local list becomes unsorted; fix by either (A) after
teawareApi.create(payload) call fetchTeawares() to replace local state with the
server-sorted list, or (B) insert the created item into the correct position
locally using the same comparator (isPinned DESC, createdAt DESC) before calling
setTeawares; locate this logic around teawareApi.create and setTeawares in
TeawareSection (or use the existing fetchTeawares helper) and implement one of
these two approaches so the UI order matches the backend.

In `@src/hooks/useNoteForm.ts`:
- Around line 193-195: The current logic in useNoteForm (around setTeawareId
usage with normalizedNote.teawareId and the payload preparation around line 374)
fails to clear an existing teaware selection because it only sets state when
teawareId != null and converts null to undefined when sending, so the server
never receives a deliberate un-link. Update the initializer that reads
normalizedNote.teawareId to explicitly call setTeawareId(null) when
normalizedNote.teawareId === null (not only when != null), and update the
payload construction (the function that builds the submission payload / submit
handler) to send teawareId: null when clearing instead of omitting it or
converting it to undefined so the server records the removal.

In `@src/lib/api/teaware.api.ts`:
- Line 20: The remove method in src/lib/api/teaware.api.ts is typed to return
Teaware but the backend DELETE /teaware/:id returns { message: string } (see
teaware.controller.ts Line 63); update the generic on apiClient.delete used in
the remove function to reflect the actual response shape ({ message: string })
so the remove method's return type matches the backend.

---

Nitpick comments:
In `@backend/src/teaware/dto/create-teaware.dto.ts`:
- Around line 13-15: The name property in CreateTeawareDto is only validated
with `@IsString`() and `@MaxLength`(100) so an empty string passes; add the
`@IsNotEmpty`() decorator to the name field and import IsNotEmpty from
class-validator to ensure "" is rejected (update the CreateTeawareDto class
property `name: string` and the import list accordingly).
- Around line 20-24: The capacity field in CreateTeawareDto uses `@Min`(1) which
disallows 0ml values needed for items without capacity; change the validator to
allow zero by replacing `@Min`(1) with `@Min`(0) (or explicitly document/handle null
for capacity?: number | null) so capacity can be 0 while keeping `@Max`(9999.9)
and other decorators intact.

In `@backend/src/teaware/teaware.controller.ts`:
- Around line 74-81: The parseUserId method currently calls parseInt on
req.user?.userId which is unnecessary since jwt.strategy populates userId as a
number; update parseUserId to perform a direct type and validity check instead:
verify req.user?.userId exists and typeof req.user.userId === 'number' and
Number.isFinite(req.user.userId'), throw the existing BadRequestException('인증
정보가 올바르지 않습니다.') on failure, and return the numeric userId otherwise
(referencing parseUserId and req.user.userId to locate the code).

In `@backend/test/suites/teaware.e2e-spec.ts`:
- Line 8: Remove the unused variable teawareId: delete the declaration of
teawareId and the assignment to teawareId found later in the spec (they're
unused after assignment); keep each test creating its own data independently
(e.g., leave creation helpers intact) so removing teawareId doesn't change test
behavior.
- Around line 15-19: The test beforeEach currently disables FK checks and only
deletes from the teaware table (in the beforeEach block using
context.dataSource.query), which can hide FK-related bugs; update the setup to
properly clear dependent rows by deleting related rows in the notes table (e.g.,
remove rows where notes.teawareId references teaware) before deleting teaware,
or use a safe cascade/truncate approach within the beforeEach to ensure both
notes and teaware are cleared while keeping FK checks enabled.

In `@src/components/teaware/TeawareSection.tsx`:
- Around line 267-275: The memo Input in TeawareSection is missing a maxLength
and should match the backend DTO (`@MaxLength`(500)); update the <Input
id="teaware-memo"> used with form.memo to include maxLength={500} and keep the
existing placeholder and onChange handler (setForm) unchanged so the UI enforces
the same 500-character limit as the server.

In `@src/types/index.ts`:
- Around line 111-116: The Teaware interface’s category is currently a plain
string which allows typos; define a specific union type (e.g. export type
TeawareCategory = 'X' | 'Y' | 'Z' or whatever the real allowed values are) and
change Teaware.category to that union (or to TeawareCategory | null if
nullable), then update all usages that expect a string (constructors,
validators, DB mappers, tests, and any deserialization code) to use/validate the
new union type and adjust runtime parsing/validation where input can be
arbitrary.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 63a43bd0-3f47-4eea-8e6c-d9d341c6c9f4

📥 Commits

Reviewing files that changed from the base of the PR and between c223d2b and 415c154.

📒 Files selected for processing (23)
  • backend/migrations/1814000000000-CreateTeaware.ts
  • backend/migrations/1814000000001-AddTeawareIdToNotes.ts
  • backend/src/app.module.ts
  • backend/src/notes/dto/create-note.dto.ts
  • backend/src/notes/entities/note.entity.ts
  • backend/src/teaware/dto/create-teaware.dto.ts
  • backend/src/teaware/dto/update-teaware.dto.ts
  • backend/src/teaware/entities/teaware.entity.ts
  • backend/src/teaware/teaware.controller.ts
  • backend/src/teaware/teaware.module.ts
  • backend/src/teaware/teaware.service.ts
  • backend/test/app.e2e-spec.ts
  • backend/test/setup/test-setup.ts
  • backend/test/suites/teaware.e2e-spec.ts
  • src/components/teaware/TeawareSection.tsx
  • src/hooks/useNoteForm.ts
  • src/lib/api/index.ts
  • src/lib/api/notes.api.ts
  • src/lib/api/teaware.api.ts
  • src/pages/NewNote.tsx
  • src/pages/NoteDetail.tsx
  • src/pages/Settings.tsx
  • src/types/index.ts

Comment on lines +124 to +125
const created = await teawareApi.create(payload);
setTeawares((prev) => [...prev, created as Teaware]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

새 다구 추가 시 목록 정렬 불일치

새로 생성된 다구가 배열 끝에 추가되지만, 백엔드는 isPinned DESC, createdAt DESC로 정렬합니다. 새 항목은 isPinned: false이므로 목록 중간에 위치해야 합니다.

🔧 정렬 유지 제안
       const created = await teawareApi.create(payload);
-      setTeawares((prev) => [...prev, created as Teaware]);
+      setTeawares((prev) => {
+        const updated = [...prev, created as Teaware];
+        return updated.sort((a, b) => {
+          if (a.isPinned !== b.isPinned) return b.isPinned ? 1 : -1;
+          return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
+        });
+      });

또는 간단히 fetchTeawares()를 호출하여 서버에서 정렬된 목록을 다시 가져올 수 있습니다.

📝 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.

Suggested change
const created = await teawareApi.create(payload);
setTeawares((prev) => [...prev, created as Teaware]);
const created = await teawareApi.create(payload);
setTeawares((prev) => {
const updated = [...prev, created as Teaware];
return updated.sort((a, b) => {
if (a.isPinned !== b.isPinned) return b.isPinned ? 1 : -1;
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
});
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/teaware/TeawareSection.tsx` around lines 124 - 125, Newly
created Teaware is appended to state with setTeawares([...prev, created]) but
server orders by isPinned DESC, createdAt DESC so the local list becomes
unsorted; fix by either (A) after teawareApi.create(payload) call
fetchTeawares() to replace local state with the server-sorted list, or (B)
insert the created item into the correct position locally using the same
comparator (isPinned DESC, createdAt DESC) before calling setTeawares; locate
this logic around teawareApi.create and setTeawares in TeawareSection (or use
the existing fetchTeawares helper) and implement one of these two approaches so
the UI order matches the backend.

Comment on lines +250 to +255
type="number"
min={0}
value={form.capacity}
onChange={(e) => setForm((prev) => ({ ...prev, capacity: e.target.value }))}
placeholder="예: 120"
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

용량 입력 최소값 불일치

min={0}으로 설정되어 있지만, 백엔드 DTO는 @Min(1)입니다. 사용자가 0을 입력하면 서버에서 검증 오류가 발생합니다.

🔧 수정 제안
               <Input
                 id="teaware-capacity"
                 type="number"
-                min={0}
+                min={1}
                 value={form.capacity}
📝 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.

Suggested change
type="number"
min={0}
value={form.capacity}
onChange={(e) => setForm((prev) => ({ ...prev, capacity: e.target.value }))}
placeholder="예: 120"
/>
type="number"
min={1}
value={form.capacity}
onChange={(e) => setForm((prev) => ({ ...prev, capacity: e.target.value }))}
placeholder="예: 120"
/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/teaware/TeawareSection.tsx` around lines 250 - 255, The
capacity input in TeawareSection uses min={0} which conflicts with the backend
DTO `@Min`(1); update the input to enforce min={1} and ensure the onChange stores
a numeric value (e.g., parseInt/Number) into form.capacity via setForm so the
client-side validation matches the backend (refer to TeawareSection, the
capacity input element, form.capacity and the setForm updater).

Comment thread src/hooks/useNoteForm.ts
Comment on lines +193 to +195
if (normalizedNote.teawareId != null) {
setTeawareId(normalizedNote.teawareId);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

수정 모드에서 다구 해제(null) 의도가 유실될 수 있습니다.

Line 193~195에서 teawareId가 null인 노트를 불러올 때 상태를 명시적으로 null로 덮어쓰지 않아 이전 값이 남을 수 있고, Line 374에서 nullundefined로 바꿔 전송해 다구 연결 해제가 서버에 반영되지 않을 수 있습니다.

🐛 제안 수정안
-        if (normalizedNote.teawareId != null) {
-          setTeawareId(normalizedNote.teawareId);
-        }
+        setTeawareId(normalizedNote.teawareId ?? null);
-          teawareId: teawareId ?? undefined,
+          teawareId: teawareId ?? null,

Also applies to: 374-374

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useNoteForm.ts` around lines 193 - 195, The current logic in
useNoteForm (around setTeawareId usage with normalizedNote.teawareId and the
payload preparation around line 374) fails to clear an existing teaware
selection because it only sets state when teawareId != null and converts null to
undefined when sending, so the server never receives a deliberate un-link.
Update the initializer that reads normalizedNote.teawareId to explicitly call
setTeawareId(null) when normalizedNote.teawareId === null (not only when !=
null), and update the payload construction (the function that builds the
submission payload / submit handler) to send teawareId: null when clearing
instead of omitting it or converting it to undefined so the server records the
removal.

create: (data: CreateTeawareRequest) => apiClient.post<Teaware>('/teaware', data),
update: (id: number, data: UpdateTeawareRequest) =>
apiClient.patch<Teaware>(`/teaware/${id}`, data),
remove: (id: number) => apiClient.delete<Teaware>(`/teaware/${id}`),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

remove 메서드 반환 타입 불일치

백엔드 DELETE /teaware/:id{ message: string } 형태를 반환하지만 (teaware.controller.ts Line 63), 여기서는 Teaware로 타입 지정되어 있습니다. 현재 반환값을 사용하지 않아 문제는 없지만, 타입 정확성을 위해 수정을 권장합니다.

🔧 수정 제안
-  remove: (id: number) => apiClient.delete<Teaware>(`/teaware/${id}`),
+  remove: (id: number) => apiClient.delete<{ message: string }>(`/teaware/${id}`),
📝 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.

Suggested change
remove: (id: number) => apiClient.delete<Teaware>(`/teaware/${id}`),
remove: (id: number) => apiClient.delete<{ message: string }>(`/teaware/${id}`),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/api/teaware.api.ts` at line 20, The remove method in
src/lib/api/teaware.api.ts is typed to return Teaware but the backend DELETE
/teaware/:id returns { message: string } (see teaware.controller.ts Line 63);
update the generic on apiClient.delete used in the remove function to reflect
the actual response shape ({ message: string }) so the remove method's return
type matches the backend.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 다구(茶具) 관리 및 차록 연동 기능

1 participant