[feat] #79 - 태그 api#85
Conversation
|
Warning Review limit reached
Next review available in: 26 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
WalkthroughTag 엔티티에 사용자 소유(user_id)와 기본 태그(is_default) 개념을 도입하는 DB 마이그레이션과 엔티티 변경이 이루어졌다. TagController, TagService, TagRepository, DTO, 예외/성공 코드 클래스가 새로 추가되어 태그 생성/조회/삭제 REST API를 구현한다. Changes태그 소유자/기본값 도입 및 API 구현
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant TagController
participant TagService
participant TagRepository
Client->>TagController: POST /api/v1/tags (TagCreateRequest)
TagController->>TagService: createTag(userId, request)
TagService->>TagRepository: existsAccessibleByName(userId, name)
TagRepository-->>TagService: false
TagService->>TagRepository: saveAndFlush(Tag)
TagRepository-->>TagService: Tag
TagService-->>TagController: TagCreateResponse
TagController-->>Client: BaseResponse(CREATED)
sequenceDiagram
participant Client
participant TagController
participant TagService
participant TagRepository
Client->>TagController: DELETE /api/v1/tags/{tagId}
TagController->>TagService: deleteTag(userId, tagId)
TagService->>TagRepository: findById(tagId)
TagRepository-->>TagService: Tag
TagService->>TagService: isDefault, isOwnedBy 검증
TagService->>TagRepository: delete(Tag)
TagService-->>TagController: void
TagController-->>Client: BaseResponse(DELETED)
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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 `@db/migration/2026-07-09__tags_add_owner_and_default.sql`:
- Around line 1-9: The migration only inserts rows into tags but does not apply
the schema changes the tags entity expects, so add the needed ALTER TABLE steps
before the INSERT. Update the tags table to add user_id, is_default, and the
name length/unique constraint required by the tags entity, then keep the seed
INSERT for the default tags so it runs against the corrected schema.
In `@src/main/java/com/Timo/Timo/domain/tag/dto/request/TagCreateRequest.java`:
- Around line 7-9: The TagCreateRequest.name validation is too permissive
compared to the Tag entity, so update the `@Size` constraint on TagCreateRequest
to match the entity’s `@Column`(length = 10) and change the validation message
accordingly. Keep the existing `@NotBlank` rule, and make sure the new max length
and message are consistent in the request DTO so TagCreateRequest rejects
overlong names before DB persistence.
In `@src/main/java/com/Timo/Timo/domain/tag/exception/TagErrorCode.java`:
- Around line 14-15: `TagErrorCode`의 `INVALID_REQUEST`와 `INVALID_TAG_ID`가 같은
code 값인 `TAG_400`을 공유해 클라이언트가 구분할 수 없습니다. `TagErrorCode` enum의 각 상수에 서로 다른 에러
코드를 부여하도록 수정하고, `INVALID_REQUEST`와 `INVALID_TAG_ID`의 의미를 각각 식별 가능한 고유 코드로 분리하세요.
In `@src/main/java/com/Timo/Timo/domain/tag/service/TagService.java`:
- Around line 28-42: The tag name length constraint is inconsistent between
TagCreateRequest and the Tag entity, causing valid DTO input to fail at save
time and be misreported as DUPLICATE_TAG_NAME in TagService.createTag. Update
TagCreateRequest so its `@Size` max matches the Tag entity’s `@Column`(length = 10),
keeping the validation aligned with the database limit and the existing
createTag flow.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 76059ac8-7176-4b20-8941-a2ac2ceae117
⛔ Files ignored due to path filters (1)
src/main/java/com/Timo/Timo/domain/tag/docs/TagControllerDocs.javais excluded by!**/docs/**
📒 Files selected for processing (11)
db/migration/2026-07-09__tags_add_owner_and_default.sqlsrc/main/java/com/Timo/Timo/domain/tag/controller/TagController.javasrc/main/java/com/Timo/Timo/domain/tag/dto/request/TagCreateRequest.javasrc/main/java/com/Timo/Timo/domain/tag/dto/response/TagCreateResponse.javasrc/main/java/com/Timo/Timo/domain/tag/dto/response/TagListResponse.javasrc/main/java/com/Timo/Timo/domain/tag/entity/Tag.javasrc/main/java/com/Timo/Timo/domain/tag/exception/TagErrorCode.javasrc/main/java/com/Timo/Timo/domain/tag/exception/TagExceptionHandler.javasrc/main/java/com/Timo/Timo/domain/tag/exception/TagSuccessCode.javasrc/main/java/com/Timo/Timo/domain/tag/repository/TagRepository.javasrc/main/java/com/Timo/Timo/domain/tag/service/TagService.java
| -- 태그 소유자(user_id)와 기본 태그 여부(is_default) 컬럼 추가. | ||
| -- 운영은 ddl-auto: validate 이므로 배포 전에 반드시 먼저 실행해야 한다. | ||
|
|
||
| -- 모든 사용자가 공유하는 기본 태그. 소유자가 없으므로 user_id는 NULL이다. | ||
| INSERT INTO tags (user_id, name, is_default) | ||
| VALUES (NULL, '일상', b'1'), | ||
| (NULL, '운동', b'1'), | ||
| (NULL, '업무', b'1'), | ||
| (NULL, '과제', b'1'); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: 기존 tags 테이블 생성 마이그레이션 및 name 컬럼 길이 확인
# 기존 tags 테이블 생성 마이그레이션 파일 확인
fd -e sql . db/migration/ | head -20
# tags 테이블 관련 기존 마이그레이션 내용 확인
rg -n -A5 'create table.*tags|CREATE TABLE.*tags|tags' db/migration/ --type sqlRepository: Team-Timo/Timo-Server
Length of output: 658
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== migration files =="
fd -e sql . db/migration | sort
echo
echo "== tags-related SQL occurrences =="
rg -n -A6 -B2 'create table.*tags|CREATE TABLE.*tags|alter table.*tags|tags' db/migration --type sql
echo
echo "== exact migration file =="
nl -ba db/migration/2026-07-09__tags_add_owner_and_default.sqlRepository: Team-Timo/Timo-Server
Length of output: 952
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== search for tags entity / schema annotations =="
rg -n -A4 -B2 'class\s+Tag|interface\s+Tag|`@Table`\(name\s*=\s*"tags"|name\s*=\s*"tags"|`@Column`\(.*length\s*=\s*10|`@Column`\(.*length\s*=\s*20' . --glob '!**/target/**' --glob '!**/build/**' --glob '!**/dist/**'
echo
echo "== search for schema docs / ddl-auto mention =="
rg -n -A3 -B2 'ddl-auto|schema|tags' . --glob '!**/target/**' --glob '!**/build/**' --glob '!**/dist/**'Repository: Team-Timo/Timo-Server
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
root = Path('db/migration')
for p in sorted(root.glob('*.sql')):
print(p)
PYRepository: Team-Timo/Timo-Server
Length of output: 215
INSERT 전에 ALTER TABLE를 추가하세요.
현재 파일에는 스키마 변경이 없고 tags 엔티티는 user_id, is_default, name(10), (user_id, name) 유니크 제약을 전제로 합니다. 이대로 배포하면 INSERT INTO tags (user_id, name, is_default)가 실패합니다.
🤖 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 `@db/migration/2026-07-09__tags_add_owner_and_default.sql` around lines 1 - 9,
The migration only inserts rows into tags but does not apply the schema changes
the tags entity expects, so add the needed ALTER TABLE steps before the INSERT.
Update the tags table to add user_id, is_default, and the name length/unique
constraint required by the tags entity, then keep the seed INSERT for the
default tags so it runs against the corrected schema.
| INVALID_REQUEST(HttpStatus.BAD_REQUEST, "TAG_400", "태그 이름은 필수입니다."), | ||
| INVALID_TAG_ID(HttpStatus.BAD_REQUEST, "TAG_400", "유효하지 않은 태그 ID입니다."), |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
INVALID_REQUEST와 INVALID_TAG_ID가 동일한 에러 코드 TAG_400을 사용합니다.
두 상수는 서로 다른 메시지를 가지지만 code 값이 "TAG_400"으로 동일하여, 클라이언트 측에서 에러 코드로 두 케이스를 구분할 수 없습니다. 로그 분석이나 클라이언트 에러 처리에 혼란을 줄 수 있습니다.
♻️ 제안하는 수정안
- INVALID_REQUEST(HttpStatus.BAD_REQUEST, "TAG_400", "태그 이름은 필수입니다."),
- INVALID_TAG_ID(HttpStatus.BAD_REQUEST, "TAG_400", "유효하지 않은 태그 ID입니다."),
+ INVALID_REQUEST(HttpStatus.BAD_REQUEST, "TAG_400_001", "태그 이름은 필수입니다."),
+ INVALID_TAG_ID(HttpStatus.BAD_REQUEST, "TAG_400_002", "유효하지 않은 태그 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.
| INVALID_REQUEST(HttpStatus.BAD_REQUEST, "TAG_400", "태그 이름은 필수입니다."), | |
| INVALID_TAG_ID(HttpStatus.BAD_REQUEST, "TAG_400", "유효하지 않은 태그 ID입니다."), | |
| INVALID_REQUEST(HttpStatus.BAD_REQUEST, "TAG_400_001", "태그 이름은 필수입니다."), | |
| INVALID_TAG_ID(HttpStatus.BAD_REQUEST, "TAG_400_002", "유효하지 않은 태그 ID입니다."), |
🤖 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/main/java/com/Timo/Timo/domain/tag/exception/TagErrorCode.java` around
lines 14 - 15, `TagErrorCode`의 `INVALID_REQUEST`와 `INVALID_TAG_ID`가 같은 code 값인
`TAG_400`을 공유해 클라이언트가 구분할 수 없습니다. `TagErrorCode` enum의 각 상수에 서로 다른 에러 코드를 부여하도록
수정하고, `INVALID_REQUEST`와 `INVALID_TAG_ID`의 의미를 각각 식별 가능한 고유 코드로 분리하세요.
| public TagCreateResponse createTag(Long userId, TagCreateRequest request) { | ||
| User user = userRepository.findById(userId) | ||
| .orElseThrow(() -> new CustomException(UserErrorCode.USER_NOT_FOUND)); | ||
|
|
||
| if (tagRepository.existsAccessibleByName(userId, request.name())) { | ||
| throw new CustomException(TagErrorCode.DUPLICATE_TAG_NAME); | ||
| } | ||
|
|
||
| try { | ||
| Tag tag = tagRepository.saveAndFlush(Tag.ofUser(user, request.name())); | ||
| return TagCreateResponse.from(tag); | ||
| } catch (DataIntegrityViolationException exception) { | ||
| throw new CustomException(TagErrorCode.DUPLICATE_TAG_NAME); | ||
| } | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win
태그명 글자수 제한이 DTO와 Entity 간에 불일치합니다.
TagCreateRequest의 @Size(max = 20)와 Tag 엔티티의 @Column(length = 10)이 일치하지 않습니다. 11~20자 사이의 태그명은 DTO 검증을 통과하지만 DB 저장 시 DataIntegrityViolationException이 발생하여 DUPLICATE_TAG_NAME 에러로 잘못 매핑됩니다. PR 커밋 메시지에 "태그명 글자수 10개 제한 변경"이 명시되어 있으므로, DTO의 max 값을 10으로 수정해야 합니다.
🤖 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/main/java/com/Timo/Timo/domain/tag/service/TagService.java` around lines
28 - 42, The tag name length constraint is inconsistent between TagCreateRequest
and the Tag entity, causing valid DTO input to fail at save time and be
misreported as DUPLICATE_TAG_NAME in TagService.createTag. Update
TagCreateRequest so its `@Size` max matches the Tag entity’s `@Column`(length = 10),
keeping the validation aligned with the database limit and the existing
createTag flow.
|
|
||
| @Query(""" | ||
| select t from Tag t | ||
| where t.isDefault = true or t.user.id = :userId |
There was a problem hiding this comment.
기본 태그와 사용자 태그를 같은 조회 조건으로 묶어서 가져와서 깔끔쓰한듯 보입니다 굿굿
| throw new CustomException(TagErrorCode.TAG_NOT_FOUND); | ||
| } | ||
|
|
||
| tagRepository.delete(tag); |
There was a problem hiding this comment.
p5) 갑자기 궁금해진 부분인데 삭제하려는 태그가 이미 TODO에서 사용 중인 경우에는 어떻게 처리되나요?
현재 플로우에서는 태그 사용 여부를 확인하지 않고 tagRepository.delete(tag)만 호출해서 기존 TODO의 tagId는 그대로 남아있는걸로 이해했는데 이후 조회 시 해당 태그를 찾지 못해 null로 내려가면 태그가 없는 TODO로 처리되는 건가용??
관련 이슈 🛠
작업 내용 요약 ✏️
태그의 생성, 삭제, 조회 api
주요 변경 사항 🛠️
스크린샷 📷
삭제




기본 태그 삭제 불가
생성
조회
리뷰 요구사항 📢
db에 미리 기본 태그들 넣어두어야합니다. 마이그레이션 파일로 첨부해두었으니 확인 부탁드립니다.
Summary by CodeRabbit
New Features
Bug Fixes