Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
c0af401
[WTH-137] weeth server cicd 구축 (#4)
hyxklee Feb 17, 2026
f9836b8
[WTH-140] Deploy Job 분리 (#5)
hyxklee Feb 17, 2026
828596a
hotfix: 레디스 설정 추가
hyxklee Feb 17, 2026
57b2cc1
[WTH-143] 응답 코드 및 예외 예시 오류 수정 (#7)
hyxklee Feb 18, 2026
734e2ae
[WTH-139] file 도메인 코틀린 마이그레이션 (#6)
hyxklee Feb 18, 2026
05340f5
[WTH-144] comment 도메인 코틀린 마이그레이션 (#8)
hyxklee Feb 18, 2026
1188e59
[WTH-146] penalty 도메인 코틀린 마이그레이션 (#10)
soo0711 Feb 19, 2026
ee9cf0a
[WTH-142] attendance 도메인 코틀린 마이그레이션 (#9)
soo0711 Feb 19, 2026
868dee9
[WTH-145] board 도메인 마이그레이션 (#11)
hyxklee Feb 19, 2026
3b2288a
[WTH-157] global 도메인 마이그레이션 (#13)
hyxklee Feb 20, 2026
8c80d06
[WTH-149] account 도메인 코틀린 마이그레이션 (#12)
soo0711 Feb 20, 2026
b1bd576
[WTH-159] user 도메인 마이그레이션 (#14)
hyxklee Feb 27, 2026
16183ca
[WTH-148] schedule 도메인 코틀린 마이그레이션 (#15)
soo0711 Feb 27, 2026
b164e9b
[WTH-161] 마이그레이션 후 정리 (#16)
hyxklee Mar 2, 2026
bfaeb0d
[WTH-160] user 도메인 리팩토링 (#18)
hyxklee Mar 3, 2026
219bee8
[WTH-150] board 도메인 리팩토링 (#20)
hyxklee Mar 4, 2026
4da8426
[WTH-174] weeth server 클로드 코드 커스텀 업데이트 (#19)
hyxklee Mar 4, 2026
9dfe46a
[WTH-169] QR 출석체크 기능 구현 (#17)
soo0711 Mar 4, 2026
6d790c9
[WTH-176] club 도메인 엔티티 추가 작업 (#21)
hyxklee Mar 6, 2026
5c2314e
[WTH-181] club 도메인 추가 작업 2 (#22)
hyxklee Mar 7, 2026
cb9986f
[WTH-183] club 도메인 추가 작업 3 (#23)
hyxklee Mar 11, 2026
dac0a80
[WTH-180] 대시보드 api 구현 (#24)
soo0711 Mar 13, 2026
5d3d4f4
[WTH-189] club 추가 마무리 작업 (#25)
hyxklee Mar 16, 2026
76eee80
[WTH-197] 동아리 정보 공개 api 구현 (#27)
hyxklee Mar 18, 2026
e2d0673
[WTH-198] 동아리 개설 가입시 기수 입력 및 초기화 (#28)
hyxklee Mar 19, 2026
af1b842
[WTH-203] 동아리 개설/가입 수 제한 (#30)
hyxklee Mar 19, 2026
29c3d20
[WTH-210] 학교 학과 open api (#29)
soo0711 Mar 19, 2026
1ce5373
[WTH-208] 사용자 프로필 기능 추가 (#26)
soo0711 Mar 23, 2026
9c55e9a
[WTH-201] 동아리 입력 출력값 검증 (#31)
hyxklee Mar 23, 2026
db775ae
[WTH-204] LEAD 권한 이양 API 구현 (#33)
hyxklee Mar 23, 2026
9af4bb2
[WTH-196] 인증 정책 수정, 이미지 관련 필드 수정 (#32)
hyxklee Mar 23, 2026
23a4334
[WTH-200] 동아리 정책 수정 (#34)
hyxklee Mar 23, 2026
34721db
[WTH-202] 출석 정책 및 구조 정리 (#36)
soo0711 Mar 23, 2026
e522a4b
[WTH-205] 내 동아리 활동 정보 조회 api 추가 (#35)
hyxklee Mar 23, 2026
c7842f8
[WTH-206] 게시글 좋아요 api (#38)
soo0711 Mar 25, 2026
fe269a7
[WTH-207] 게시판 기본 기능 수정 (#37)
soo0711 Mar 25, 2026
3b71d36
[WTH-221] 액세스 토큰 리프레시 토큰 쿠키 반환 (#39)
hyxklee Mar 25, 2026
ac5faec
[WTH-220] 동아리 이미지도 File을 타도록 수정 (#40)
hyxklee Mar 25, 2026
97b3894
HOTFIX: cors 주소 추가
hyxklee Mar 25, 2026
723ec7c
HOTFIX: PATCH 매핑으로 수정
hyxklee Mar 25, 2026
a75545f
[WTH-228] 공지사항 삭제 가능 (#41)
soo0711 Mar 27, 2026
1ebc7dd
[WTH-229] 유저 기수 수정 (#43)
soo0711 Mar 29, 2026
784ac94
[WTH-226] 세션 반복 생성 (#42)
hyxklee Mar 30, 2026
646e267
[WTH-236] 랜딩 문의하기 api 구현 (#45)
soo0711 Mar 31, 2026
60f941c
[WTH-237] 애플 로그인 수정 및 로그인 시 유저 정보 반환 (#44)
hyxklee Apr 1, 2026
6c025b2
HOTFIX: CORS 경로 변경
hyxklee Apr 1, 2026
8030bdc
fix: 도메인 설정이 변경된 경우 바로 반영 되도록 로직 수정 (#46)
hyxklee Apr 1, 2026
d94f5c7
[WTH-251] post comment 작성자 타입을 club member로 변경 (#48)
soo0711 Apr 7, 2026
bfcd07d
[WTH-247] 유저 필드 nullable 처리 (#47)
hyxklee Apr 8, 2026
9539325
HOTIFX: 랜딩 CORS 추가
hyxklee Apr 9, 2026
6851b00
[WTH-252] 출석 요약 조회 api에 세션 id가 없어 출석이 불가능한 문제 (#49)
hyxklee Apr 9, 2026
471e734
[WTH-260] 동아리 생성시 club id 동아리 이름 즉시 반환 (#50)
hyxklee Apr 15, 2026
ffe9ce0
[WTH-278] 파일 수정 오류 해결 (#52)
hyxklee Apr 19, 2026
af48530
[WTH-309] 문의 내용 NotBlank 검증 제거 (#53)
soo0711 Apr 19, 2026
ecd5685
[WTH-282] 댓글 좋아요 백엔드 qa (#54)
soo0711 Apr 20, 2026
9f4b213
[WTH-294] qr 만료시간 실시간 전달 (#51)
soo0711 Apr 20, 2026
e25a054
[WTH-310] 대시보드 최근 게시글에 좋아요 여부 필드 추가 (#55)
soo0711 Apr 21, 2026
b9c3889
[WTH-105] Weeth Server 모니터링 설정 (#56)
hyxklee Apr 24, 2026
756bfb2
HOTFIX: 어드민 유저 이름 환경 변수 설정
hyxklee Apr 24, 2026
f005306
HOTFIX: 모니터링 핫픽스 추가 (Log 수집 안됨, Lettuce 수집 안됨 해결)
hyxklee Apr 25, 2026
305b3e0
HOTFIX: 빌드 오류 해소
hyxklee Apr 25, 2026
ec2ee84
HOTFIX: 불필요 이미지 정리 코드 추가
hyxklee Apr 25, 2026
252abab
[WTH-327] 어드민 게시판 관리 수정 (#57)
hyxklee Apr 25, 2026
45c8e74
[WTH-326] 게시판 관련 API에 boardId 필드 추가 (#58)
soo0711 Apr 27, 2026
db4bfd3
[WTH-350] 게시판 권한 추가 (#60)
hyxklee Apr 28, 2026
1904da8
[WTH-347] sse is empty 처리 (#59)
soo0711 Apr 28, 2026
5471b1d
[WTH-349] 모니터링 인프라 개선 (#61)
hyxklee Apr 28, 2026
157fa15
[WTH-356] 리더 추방이 불가능하도록 수정 (#62)
hyxklee Apr 29, 2026
a5cce26
[WTH] 오늘의 일정 단건 응답으로 수정 (#64)
soo0711 Apr 29, 2026
82cbce1
[WTH-358] 추방된 경우 동아리 리스트 미표기 (#65)
hyxklee Apr 30, 2026
89ea8b5
[WTH-361] 운영서버 모니터링 설정 (#66)
hyxklee Apr 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
16 changes: 16 additions & 0 deletions .claude/hooks/ktlint-format.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash
# PostToolUse hook: .kt 파일 수정 시 ktlint 자동 포맷

FILE_PATH=$(cat | jq -r '.tool_input.file_path // empty')

if [ -z "$FILE_PATH" ]; then
exit 0
fi

# .kt 파일만 처리
if [[ "$FILE_PATH" != *.kt ]]; then
exit 0
fi

cd "$CLAUDE_PROJECT_DIR" || exit 0
./gradlew ktlintFormat 2>&1 >&2
110 changes: 78 additions & 32 deletions .claude/rules/api-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ class UserController(
}
```

## Club-scoped API

Club resources use `/api/v4/clubs/{clubId}/...`. `clubId` is Base62 TSID — use two annotations together:

```kotlin
@TsidParam // Swagger (type: string)
@TsidPathVariable clubId: Long // decodes Base62 → Long at runtime
```

## Required Annotations

| Annotation | Purpose |
Expand Down Expand Up @@ -61,9 +70,9 @@ enum class UserResponseCode(
override val status: HttpStatus,
override val message: String
) : ResponseCodeInterface {
GET_MY_INFO(1100, HttpStatus.OK, "내 정보 조회에 성공했습니다."),
GET_USER_INFO(1101, HttpStatus.OK, "다른 사용자 정보 조회에 성공했습니다."),
UPDATE_PROFILE_IMAGE(1102, HttpStatus.OK, "프로필 이미지 수정에 성공했습니다.")
USER_FIND_ALL_SUCCESS(10900, HttpStatus.OK, "모든 회원 정보를 성공적으로 조회했습니다."),
USER_FIND_BY_ID_SUCCESS(10907, HttpStatus.OK, "회원 정보가 성공적으로 조회되었습니다."),
USER_UPDATE_SUCCESS(10908, HttpStatus.OK, "회원 정보가 성공적으로 수정되었습니다."),
}
```

Expand All @@ -72,44 +81,70 @@ enum class UserResponseCode(
- `CommonResponse.success(USER_FIND_BY_ID_SUCCESS, data)`
- `CommonResponse.success(USER_UPDATE_SUCCESS)`

## Domain Success Codes
## Code Format

| | Mean | Value |
|---|-----------------|----------------------------------------------------------------------------|
| X | Category | 1=Success, 2=Domain Error, 3=Infra/Server Error, 4=Client/Validation Error |
| DD | Domain ID | 01~99 |
| NN | In Domain Count | 00~99 |

## Domain ID

| DD | Domain | Success Range | Domain Error Range | Infra Error Range |
|----|------------|---------------|--------------------|-------------------|
| 01 | account | 10100~ | 20100~ | — |
| 02 | attendance | 10200~ | 20200~ | — |
| 03 | session | 10300~ | 20300~ | — |
| 04 | board | 10400~ | 20400~ | — |
| 05 | comment | 10500~ | 20500~ | — |
| 06 | file | 10600~ | 20600~ | 30600~ |
| 07 | penalty | 10700~ | 20700~ | — |
| 08 | schedule | 10800~ | 20800~ | — |
| 09 | user | 10900~ | 20900~ | — |
| 10 | cardinal | 11000~ | 21000~ | — |
| 11 | club | 11100~ | 21100~ | — |
| 12 | dashboard | 11200~ | 21200~ | — |
| 13 | university | 11300~ | — | 31300~ |
| 90 | jwt/auth | — | 29000~ | — |
| 99 | common | — | — | 39900~ |

Current project uses domain-specific success enums under `src/main/java/com/weeth/domain/*/presentation/*ResponseCode.java`.
## Domain Success Codes

| Domain | ResponseCode Enum | Code Range | Location |
|--------|------------------|------------|----------|
| Account | `AccountResponseCode` | `11xx` | `domain/account/presentation/` |
| Attendance | `AttendanceResponseCode` | `12xx` | `domain/attendance/presentation/` |
| Board | `BoardResponseCode` | `13xx` | `domain/board/presentation/` |
| Comment | `CommentResponseCode` | `140xx` | `domain/comment/presentation/` |
| File | `FileResponseCode` | `15xx` | `domain/file/presentation/` |
| Penalty | `PenaltyResponseCode` | `160xx` | `domain/penalty/presentation/` |
| Schedule | `ScheduleResponseCode` | `17xx` | `domain/schedule/presentation/` |
| User | `UserResponseCode` | `18xx` | `domain/user/presentation/` |
| Account | `AccountResponseCode` | `101xx` | `domain/account/presentation/` |
| Attendance | `AttendanceResponseCode` | `102xx` | `domain/attendance/presentation/` |
| Session | `SessionResponseCode` | `103xx` | `domain/session/presentation/` |
| Board | `BoardResponseCode` | `104xx` | `domain/board/presentation/` |
| Comment | `CommentResponseCode` | `105xx` | `domain/comment/presentation/` |
| File | `FileResponseCode` | `106xx` | `domain/file/presentation/` |
| Penalty | `PenaltyResponseCode` | `107xx` | `domain/penalty/presentation/` |
| Schedule | `ScheduleResponseCode` | `108xx` | `domain/schedule/presentation/` |
| User | `UserResponseCode` | `109xx` | `domain/user/presentation/` |
| Cardinal | `CardinalResponseCode` | `110xx` | `domain/cardinal/presentation/` |
| Club | `ClubResponseCode` | `111xx` | `domain/club/presentation/` |
| Dashboard | `DashboardResponseCode` | `112xx` | `domain/dashboard/presentation/` |
| University | `UniversityResponseCode` | `113xx` | `domain/university/presentation/` |

## Domain Error Codes

Domain-specific error enums under `src/main/java/com/weeth/domain/*/application/exception/*ErrorCode.java`.

| Domain | ErrorCode Enum | Code Range | Location |
|--------|---------------|------------|----------|
| Account | `AccountErrorCode` | `21xx` | `domain/account/application/exception/` |
| Attendance | `AttendanceErrorCode` | `22xx` | `domain/attendance/application/exception/` |
| Board | `BoardErrorCode`, `NoticeErrorCode`, `PostErrorCode` | `23xx` | `domain/board/application/exception/` |
| Comment | `CommentErrorCode` | `240x` | `domain/comment/application/exception/` |
| Penalty | `PenaltyErrorCode` | `260x` | `domain/penalty/application/exception/` |
| Schedule | `EventErrorCode`, `MeetingErrorCode` | `27xx` | `domain/schedule/application/exception/` |
| User | `UserErrorCode` | `28xx` | `domain/user/application/exception/` |
| JWT (Global) | `JwtErrorCode` | `29xx` | `global/auth/jwt/exception/` |

## Code Numbering

| Range | Category |
|-------|----------|
| 1XXX | Success responses |
| 2XXX | Domain-specific errors |
| 3XXX | Server errors |
| 4XXX | Client errors |
| Account | `AccountErrorCode` | `201xx` | `domain/account/application/exception/` |
| Attendance | `AttendanceErrorCode` | `202xx` | `domain/attendance/application/exception/` |
| Session | `SessionErrorCode` | `203xx` | `domain/session/application/exception/` |
| Board | `BoardErrorCode` | `204xx` | `domain/board/application/exception/` |
| Comment | `CommentErrorCode` | `205xx` | `domain/comment/application/exception/` |
| File | `FileErrorCode` | `206xx` (domain), `306xx` (infra) | `domain/file/application/exception/` |
| Penalty | `PenaltyErrorCode` | `207xx` | `domain/penalty/application/exception/` |
| Schedule | `EventErrorCode` | `208xx` | `domain/schedule/application/exception/` |
| User | `UserErrorCode` | `209xx` | `domain/user/application/exception/` |
| Cardinal | `CardinalErrorCode` | `210xx` | `domain/cardinal/application/exception/` |
| Club | `ClubErrorCode` | `211xx` | `domain/club/application/exception/` |
| Dashboard | `DashboardErrorCode` | `212xx` | `domain/dashboard/application/exception/` |
| University | `UniversityErrorCode` | `313xx` (infra) | `domain/university/application/exception/` |
| JWT (Global) | `JwtErrorCode` | `290xx` | `global/auth/jwt/application/exception/` |

## HTTP Methods

Expand All @@ -132,6 +167,17 @@ DELETE /users/{userId} # Delete user
POST /users/{userId}/activate # Action on resource
```

### Admin Endpoints

`admin` prefix comes **before** `clubs/{clubId}`: `/api/v4/admin/clubs/{clubId}/{resource}`

```
/api/v4/clubs/{clubId}/boards # user-facing
/api/v4/admin/clubs/{clubId}/boards # admin
```

Enables a single SecurityConfig rule: `.requestMatchers("/api/v4/admin/**").hasRole("ADMIN")`

## Query & Path Parameters

- Query params for filtering: `?page=0&size=10&status=ACTIVE`
Expand Down
52 changes: 46 additions & 6 deletions .claude/rules/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Package Structure

```text
src/main/kotlin/weeth/
src/main/kotlin/com/weeth/
├── domain/{domain-name}/
│ ├── application/
│ │ ├── dto/request/, dto/response/
Expand All @@ -26,7 +26,8 @@ src/main/kotlin/weeth/
└── global/
├── auth/
├── config/
└── common/
├── common/
└── logging/
```

## Layer Dependencies
Expand Down Expand Up @@ -79,11 +80,50 @@ presentation → application → domain (owns Port)

## Entity (Rich Domain Model)

- **Factory method**: `companion object` with `create()` / `of()` including validation
- **State changes**: named methods (`publish()`, `softDelete()`) — no public setters
- **Validation**: `require` for argument checks, `check` for state preconditions
- **Business decisions**: `isEditableBy()`, `canPublish()` belong to Entity

### Constructor Pattern

Primary constructor takes **business creation params only** (non-property) — JPA-managed fields (`id`, `isDeleted`) belong in the body with `private set` and default values.

```kotlin
@Entity
class Post(
title: String,
content: String,
user: User,
board: Board,
) : BaseEntity() {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0L
private set

var title: String = title
private set
// ...

companion object {
fun create(title: String, content: String, user: User, board: Board): Post {
require(title.isNotBlank()) { "제목은 비어 있을 수 없습니다" }
return Post(title = title, content = content, user = user, board = board)
}
}
}
```

| Concern | Location |
|---------|----------|
| JPA-managed fields (`id`, `isDeleted`) | Body, `private set`, default value |
| Business creation params | Primary constructor (non-property) |
| Validation | `create()` / named mutation methods — not constructor |

- **Factory method** (`companion object`): use when the entity has creation logic or validation. Expresses domain intent.
- **Simple entities** (e.g., `Board`): public constructor is fine; no factory method needed if creation is trivial.

## Value Object (VO)

- **Location**: `domain/vo/`
Expand Down Expand Up @@ -154,8 +194,8 @@ class User(

## Port-Adapter Pattern

- **Port** (`domain/port/`): interface in domain language, no tech names → `FileStorage`, `PushNotificationSender`
- **Adapter** (`infrastructure/`): implementation with tech prefix → `S3FileStorage`, `FcmPushNotificationSender`
- **Port** (`domain/port/`): interface in domain language → `FileStoragePort`, `PushNotificationSenderPort`
- **Adapter** (`infrastructure/`): implementation with tech prefix → `S3FileStorageAdapter`, `FcmPushNotificationSenderAdapter`
- UseCase depends on Port interface only → swappable, testable

## Core Principles
Expand All @@ -164,4 +204,4 @@ class User(
2. **UseCase = orchestration**: coordinates flow; "how" is decided by Entity
3. **No meaningless services**: Repository wrappers are eliminated; Domain Service only for multi-entity logic
4. **Port-Adapter**: domain owns Port interfaces; infrastructure implements them
5. **Incremental migration**: migrate Java → Kotlin preserving existing structure
5. **Kotlin-first**: Java → Kotlin migration complete; all new code in Kotlin
11 changes: 11 additions & 0 deletions .claude/rules/code-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ companion object {
}
```

## Comments

- Do NOT comment on self-explanatory code
- Add comments in these cases:
- **Core business logic**: Domain rules, policy decisions — explain "why", not "what"
- **Collaboration aid**: Intent or background that other developers need to understand the code
- **Non-obvious implementation**: Performance optimizations, workarounds, external system constraints
- **Architecture decisions**: Reason for choosing a specific pattern or structure (e.g., `// NOTE: Kept in Java for Lombok @SuperBuilder compatibility`)
- Use KDoc (`/** */`) for public APIs, Port interfaces, and external contracts
- Use inline comments (`//`) for implementation intent within methods

## Null Handling

```kotlin
Expand Down
36 changes: 19 additions & 17 deletions .claude/rules/exception-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
RuntimeException
└── BaseException (abstract)
├── UserNotFoundException
├── OrderNotFoundException
├── BoardNotFoundException
└── ... (domain-specific exceptions)
```

Expand Down Expand Up @@ -40,31 +40,33 @@ enum class UserErrorCode(
override val message: String
) : ErrorCodeInterface {
@ExplainError("사용자 ID로 조회했으나 해당 사용자가 존재하지 않을 때 발생합니다.")
USER_NOT_FOUND(2100, HttpStatus.NOT_FOUND, "존재하지 않는 사용자입니다."),
@ExplainError("사용자 설정을 조회했으나 설정 정보가 존재하지 않을 때 발생합니다.")
USER_SETTING_NOT_FOUND(2101, HttpStatus.NOT_FOUND, "존재하지 않는 사용자 설정입니다."),
@ExplainError("이미 탈퇴 처리된 사용자 계정에 접근을 시도할 때 발생합니다.")
USER_ALREADY_LEAVE(2102, HttpStatus.BAD_REQUEST, "이미 탈퇴한 사용자입니다."),
USER_NOT_FOUND(20900, HttpStatus.NOT_FOUND, "존재하지 않는 유저입니다."),

@ExplainError("가입 승인 대기 중인 사용자가 접근을 시도할 때 발생합니다.")
USER_INACTIVE(20901, HttpStatus.FORBIDDEN, "가입 승인이 허가되지 않은 계정입니다."),

@ExplainError("이미 가입된 이메일로 회원가입을 시도할 때 발생합니다.")
USER_EXISTS(20902, HttpStatus.BAD_REQUEST, "이미 가입된 사용자입니다."),
}
```

## Common Error Codes
## Common Error Codes (pattern example, not yet implemented)

Follow the pattern below when introducing a common error code enum. Currently, `CommonExceptionHandler` uses `CommonResponse.createFailure()` directly.

```kotlin
enum class CommonErrorCode(
override val code: Int,
override val status: HttpStatus,
override val message: String
) : ErrorCodeInterface {
// 3XXX: Server errors
INTERNAL_SERVER_ERROR(3001, HttpStatus.INTERNAL_SERVER_ERROR, "Internal server error"),
JSON_PROCESSING_ERROR(3002, HttpStatus.INTERNAL_SERVER_ERROR, "JSON processing error"),
// 3DDNN: Infra/Server errors (DD=99 for common)
INTERNAL_SERVER_ERROR(39901, HttpStatus.INTERNAL_SERVER_ERROR, "Internal server error"),
JSON_PROCESSING_ERROR(39902, HttpStatus.INTERNAL_SERVER_ERROR, "JSON processing error"),

// 4XXX: Client errors
INVALID_ARGUMENT(4001, HttpStatus.BAD_REQUEST, "Invalid argument"),
RESOURCE_NOT_FOUND(4003, HttpStatus.NOT_FOUND, "Resource not found"),
// 4DDNN: Client/Validation errors (DD=99 for common)
INVALID_ARGUMENT(49901, HttpStatus.BAD_REQUEST, "Invalid argument"),
RESOURCE_NOT_FOUND(49903, HttpStatus.NOT_FOUND, "Resource not found"),
}
```

Expand Down Expand Up @@ -115,8 +117,8 @@ enum class UserErrorCode(
override val status: HttpStatus,
override val message: String
) : ErrorCodeInterface {
@ExplainError("Raised when no user exists for the given user ID.")
USER_NOT_FOUND(2100, HttpStatus.NOT_FOUND, "존재하지 않는 사용자입니다."),
@ExplainError("사용자 ID로 조회했으나 해당 사용자가 존재하지 않을 때 발생합니다.")
USER_NOT_FOUND(20900, HttpStatus.NOT_FOUND, "존재하지 않는 유저입니다."),
}
```

Expand Down
18 changes: 8 additions & 10 deletions .claude/rules/mapper-dto.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,15 @@

## Mapper Pattern

AS-IS (Java): MapStruct 사용 → TO-BE (Kotlin): 수동 Mapper 패턴으로 마이그레이션
Manual `@Component` Mapper pattern (no MapStruct).

```kotlin
@Component
class UserMapper(
private val profileMapper: ProfileMapper
) {
class UserMapper {
fun toResponse(user: User) = UserResponse(
id = user.id,
name = user.name,
email = user.email,
profile = profileMapper.toResponse(user.profile)
)

fun toEntity(request: CreateUserRequest) = User(
Expand Down Expand Up @@ -82,7 +79,9 @@ data class UserResponse(
- Use non-nullable types for required fields
- Use nullable types with default `null` for optional fields

## List Response with Pagination
## List Response with Pagination (pattern example)

Follow the pattern below when introducing a pagination response DTO.

```kotlin
data class UserListResponse(
Expand Down Expand Up @@ -114,12 +113,11 @@ data class PageResponse(

## Mapper Dependencies

Mappers can inject other mappers:
Mappers can inject other mappers when needed:

```kotlin
@Component
class UserMapper(
private val profileMapper: ProfileMapper,
private val addressMapper: AddressMapper
class PostMapper(
private val commentMapper: CommentMapper
)
```
Loading
Loading