Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
95ff146
refactor: CLAUDE.md 컨벤션을 .claude/rules로 분리
pooreumjung Jul 1, 2026
65765cc
refactor: review 커맨드를 backend-review로 이름 변경
pooreumjung Jul 1, 2026
9b335f4
docs: 코드 작성 전 되묻기 및 이슈 단위 작업 규칙 추가
pooreumjung Jul 1, 2026
0c9fc52
feat: 이슈/PR 생성 시 담당자·라벨·리뷰어 자동 지정
pooreumjung Jul 1, 2026
48ac01f
fix: rules paths 스코프 보완 (코드 리뷰 반영)
pooreumjung Jul 1, 2026
b7eaa13
Merge pull request #384 from EAT-SSU/feat/#383-add-claude-rules-renam…
pooreumjung Jul 1, 2026
1cb487a
feat: PR 생성 시 테스트 자동 실행하는 CI 워크플로우 추가
pooreumjung Jul 4, 2026
7a7ea73
refactor: JWT 테스트 시크릿을 하드코딩 대신 실행 시점 랜덤 생성으로 변경
pooreumjung Jul 4, 2026
4f6af9f
fix: Ratings 생성 시 평점 값이 null이면 NPE 발생하던 문제 수정
pooreumjung Jul 4, 2026
b148510
fix: 코드 리뷰 반영 - mainRating null 검증 추가 및 평점 범위 1~5로 통일
pooreumjung Jul 4, 2026
00a0a45
Merge pull request #390 from EAT-SSU/fix/#387-handle-null-ratings
pooreumjung Jul 4, 2026
20e1ff7
test: 닉네임 시작/끝 숫자 허용 스펙에 맞게 NicknameValidatorTest 기대값 수정
pooreumjung Jul 4, 2026
ddbfc67
style: 코드 리뷰 반영 - 테스트 메서드명 영문화 및 given/when/then 주석 추가
pooreumjung Jul 4, 2026
5bf02c7
Merge pull request #391 from EAT-SSU/fix/#388-validate-nickname-edges
pooreumjung Jul 5, 2026
7cd80e8
Merge remote-tracking branch 'origin/develop' into feat/#385-add-pr-t…
pooreumjung Jul 5, 2026
26e69e5
Merge pull request #389 from EAT-SSU/feat/#385-add-pr-test-ci-workflow
pooreumjung Jul 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
9 changes: 9 additions & 0 deletions .claude/commands/feature-issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,12 @@ Rules:
- Do not invent implementation details that are not implied by the request.
- Prefer concrete acceptance criteria over vague descriptions.
- If there is no attachment/reference, write `- 없음`.

When the user explicitly asks to create the issue (not just draft it), run:

```bash
gh issue create --title "feature: <summary>" --body "<final markdown body>" --assignee @me --label feat
```

- Always include `--assignee @me` so the issue is assigned to the current user.
- Always include `--label feat` to match this template's convention.
9 changes: 9 additions & 0 deletions .claude/commands/fix-issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,12 @@ Rules:
- If the issue is operational, include the relevant environment such as prod, dev, EC2, Docker, or CloudWatch.
- Do not claim a root cause unless it is directly supported by the given context.
- If there is no attachment/reference, write `- 없음`.

When the user explicitly asks to create the issue (not just draft it), run:

```bash
gh issue create --title "fix: <summary>" --body "<final markdown body>" --assignee @me --label fix
```

- Always include `--assignee @me` so the issue is assigned to the current user.
- Always include `--label fix` to match this template's convention.
22 changes: 22 additions & 0 deletions .claude/commands/pr.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,25 @@ Rules:
- For the checklist, mark an item checked only if the evidence is clear from the context or command results.
- If tests were not run, leave the test checkbox unchecked and mention that in reviewer notes.
- Do not include unrelated refactoring in the summary.

When the user explicitly asks to create the PR (not just draft it), run:

```bash
gh pr create --title "<type>: <summary>" --body "<final markdown body>" --assignee @me --reviewer eunseo9311,sjinssun --label <label>
```

- Always include `--assignee @me` so the PR is assigned to the current user.
- Always include `--reviewer eunseo9311,sjinssun`.
- Determine `<label>` from the current branch's type prefix (`feat/`, `fix/`, `refactor/`, `docs/`, `test/`, `chore/`, `style/`) using this mapping:

| Branch type | Label |
| --- | --- |
| feat | feat |
| fix | fix |
| refactor | refactoring |
| docs | docs |
| test | test |
| chore | chore |
| style | (no label — omit `--label`) |

If the branch type has no matching label in the table, omit `--label` entirely rather than guessing.
13 changes: 13 additions & 0 deletions .claude/rules/commenting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
paths:
- "src/main/java/**/*.java"
- "src/test/java/**/*.java"
---

# Commenting Convention

- Comments should be written in a single line whenever possible.
- Each comment must include the author's name and the date.
- Use IntelliJ's default `FIX ME` comment format.
- Use this for parts of the code where there may be a potential issue but immediate exception handling is unnecessary.
- Also use this for parts that are not yet finalized due to unclear business requirements.
13 changes: 13 additions & 0 deletions .claude/rules/persistence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
paths:
- "**/persistence/**/*.java"
- "**/repository/**/*.java"
- "**/entity/**/*.java"
- "src/main/resources/db/migration/**"
---

# Persistence & Migration Convention

- Complex queries use the `*RepositoryCustom` interface + `*RepositoryImpl` (QueryDSL) pattern.
- Schema is managed with Flyway migration files at `src/main/resources/db/migration/V{n}__{설명}.sql`.
- `ddl-auto` is set to `none`, so any schema change must be accompanied by a new migration file.
10 changes: 10 additions & 0 deletions .claude/rules/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
paths:
- "src/test/**/*.java"
---

# Test Code Convention

- Test method names must be written in **English**.
- If the code is related to test, write comments for "given, when, then".
- Tests run with `@SpringBootTest` connected to a real DB. Do not mock the DB.
67 changes: 67 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: EAT-SSU Server PR 테스트 게이트

on:
pull_request:
branches: [ "develop", "main" ]

permissions:
contents: read

jobs:
test:
name: Test
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_DATABASE: eatssu_test
MYSQL_ROOT_PASSWORD: test
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h localhost"
--health-interval=10s
--health-timeout=5s
--health-retries=10
env:
EATSSU_DB_URL_TEST: jdbc:mysql://localhost:3306/eatssu_test
EATSSU_DB_USERNAME: root
EATSSU_DB_PASSWORD: test
EATSSU_AWS_ACCESS_KEY_DEV: dummy-access-key
EATSSU_AWS_SECRET_KEY_DEV: dummy-secret-key
EATSSU_SLACK_TOKEN: dummy-slack-token
steps:
- uses: actions/checkout@v3

- name: JDK 17 설치
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'

- name: Gradle 캐싱
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: gradlew 실행 권한 부여
run: chmod +x gradlew

- name: 테스트용 JWT 시크릿 생성
run: echo "EATSSU_JWT_SECRET_TEST=$(openssl rand -base64 64 | tr -d '\n')" >> "$GITHUB_ENV"

- name: 테스트 실행
run: ./gradlew test --no-daemon

- name: 테스트 리포트 업로드
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: build/reports/tests/test
29 changes: 5 additions & 24 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,11 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- Suggest based on logical reasons.
- For every request, follow this order before writing any code:
1. Analyze the process (현재 구조/흐름 파악)
2. Suggest a direction (어떤 방향으로 접근할지 제시)
3. Present trade-offs (각 방향의 장단점 제시)
4. Do NOT write code unless the user explicitly asks with a direct command such as "개발해줘", "작성해줘", "구현해줘".

## Commenting Convention

- Comments should be written in a single line whenever possible.
- Each comment must include the author's name and the date.
- Use IntelliJ's default `FIX ME` comment format.
- Use this for parts of the code where there may be a potential issue but immediate exception handling is unnecessary.
- Also use this for parts that are not yet finalized due to unclear business requirements.

## Test Code Convention

- Test method names must be written in **English**.
- But if the codes is related to test, write the comment for "given, when, then".
2. If anything is unclear or requires a decision only the user can make, ask clarifying questions before proposing a direction
3. Suggest a direction based on the analysis (and any answers received)
4. Present trade-offs for each direction
5. Do NOT write code unless the user explicitly asks with a direct command such as "개발해줘", "작성해줘", "구현해줘".
- When implementing from a GitHub issue with multiple feature items, process one item at a time: complete the full analyze → clarify → direction → trade-off → explicit approval cycle for a single item before moving to the next.

## Build & Run Commands

Expand Down Expand Up @@ -84,18 +73,10 @@ src/main/java/ssu/eatssu/

**다국어(i18n)**: `Localizable` 인터페이스의 `getLocalizedValue(language, ko, en, ja, vi)`를 구현하여 언어별 필드를 반환한다.

**복잡한 쿼리**: `*RepositoryCustom` 인터페이스 + `*RepositoryImpl`(QueryDSL) 패턴을 사용한다.

**DB 마이그레이션**: Flyway를 사용하며, `src/main/resources/db/migration/V{n}__{설명}.sql` 형식으로 관리한다. `ddl-auto: none`이므로 스키마 변경 시 반드시 마이그레이션 파일을 추가해야 한다.

### API 버전 관리

일부 Controller/Service는 V1/V2로 버전이 분리되어 있다(예: `ReviewController`/`ReviewControllerV2`, `ReviewService`/`ReviewServiceV2`). 새 기능은 V2 이상에 추가하거나, 필요 시 새 버전을 생성한다.

### 테스트

테스트는 `@SpringBootTest`로 실제 DB에 연결하여 실행한다. 목(Mock) DB를 사용하지 않는다.

### 배포

GitHub Actions(`deploy.yml`)로 Docker 이미지를 빌드하여 EC2에 SSH 배포한다. `prod`/`dev` 브랜치에 따라 배포 환경이 분기된다.
Expand Down
15 changes: 12 additions & 3 deletions src/main/java/ssu/eatssu/domain/rating/entity/Ratings.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,23 @@ public class Ratings {
private Integer tasteRating;

private Ratings(Integer mainRating, Integer amountRating, Integer tasteRating) {
Assert.isTrue(mainRating >= 0 && mainRating <= 5, "mainRating must be between 0 and 5");
Assert.isTrue(amountRating >= 0 && amountRating <= 5, "amountRating must be between 0 and 5");
Assert.isTrue(tasteRating >= 0 && tasteRating <= 5, "tasteRating must be between 0 and 5");
Assert.notNull(mainRating, "mainRating must not be null");
validateRange(mainRating, "mainRating");
Comment on lines +24 to +25

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.

medium

도메인 엔티티에서 JWT 라이브러리의 내부 유틸리티 클래스인 io.jsonwebtoken.lang.Assert를 사용하는 것은 도메인 레이어가 외부 보안 라이브러리에 강하게 결합되는 설계적 문제가 있습니다. 표준 Java 예외(IllegalArgumentException)를 직접 던지거나 java.util.Objects를 사용하여 검증하는 것이 좋습니다.

Suggested change
Assert.notNull(mainRating, "mainRating must not be null");
validateRange(mainRating, "mainRating");
if (mainRating == null) {
throw new IllegalArgumentException("mainRating must not be null");
}
validateRange(mainRating, "mainRating");
References
  1. 도메인 엔티티는 외부 라이브러리(특히 JWT 관련 라이브러리)에 의존하지 않고 순수하게 유지되어야 합니다.

// FIXME(pooreumjung, 2026-07-04): amountRating/tasteRating을 선택값으로 둘지는 비즈니스 요구사항 확정 필요
validateRange(amountRating, "amountRating");
validateRange(tasteRating, "tasteRating");
this.mainRating = mainRating;
this.amountRating = amountRating;
this.tasteRating = tasteRating;
}

private static void validateRange(Integer rating, String fieldName) {
if (rating == null) {
return;
}
Assert.isTrue(rating >= 1 && rating <= 5, fieldName + " must be between 1 and 5");
}
Comment on lines +34 to +39

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.

medium

동일하게 validateRange 메서드에서도 io.jsonwebtoken.lang.Assert 대신 표준 Java 예외를 던지도록 변경하여 외부 라이브러리 의존성을 제거하는 것이 좋습니다.

    private static void validateRange(Integer rating, String fieldName) {
        if (rating == null) {
            return;
        }
        if (rating < 1 || rating > 5) {
            throw new IllegalArgumentException(fieldName + " must be between 1 and 5");
        }
    }
References
  1. 도메인 엔티티는 외부 라이브러리(특히 JWT 관련 라이브러리)에 의존하지 않고 순수하게 유지되어야 합니다.


public static Ratings of(Integer mainRating, Integer amountRating, Integer tasteRating) {
return new Ratings(mainRating, amountRating, tasteRating);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ class NicknameValidatorTest {
}

@ParameterizedTest
@ValueSource(strings = {"유효한 닉네임","available nic","가능한-닉네임","유-효-한-닉-네-임","유 효 한 닉 1 2 a","ㅇㅅㅎㅇㅌ"})
void 유효한_이름을_입력하면_예외가_발생하지_않는다(String input) {
@ValueSource(strings = {"유효한 닉네임","available nic","가능한-닉네임","유-효-한-닉-네-임","유 효 한 닉 1 2 a","ㅇㅅㅎㅇㅌ","0이름","0test","이름0","test0"})
void doesNotThrowWhenNicknameIsValid(String input) {
// given: parameterized valid nickname input

// when & then
assertDoesNotThrow(()-> nicknameValidator.validateNickname(input));
}
Expand Down Expand Up @@ -91,8 +93,10 @@ class NicknameValidatorTest {
}

@ParameterizedTest
@ValueSource(strings = {"0이름","0test","-test"," test"})
void 한글_또는_영어로_시작하지_않는_닉네임인_경우_예외가_발생한다(String input) {
@ValueSource(strings = {"-test"," test"})
void throwsExceptionWhenNicknameStartsWithInvalidCharacter(String input) {
// given: parameterized nickname starting with a symbol (숫자는 시작 문자로 허용됨)

// when & then
assertThatThrownBy(()->nicknameValidator.validateNickname(input))
.isInstanceOf(BaseException.class)
Expand All @@ -101,8 +105,10 @@ class NicknameValidatorTest {
}

@ParameterizedTest
@ValueSource(strings = {"이름0","test0","test-","test "})
void 한글_또는_영어로_끝나지_않는_닉네임인_경우_예외가_발생한다(String input) {
@ValueSource(strings = {"test-","test "})
void throwsExceptionWhenNicknameEndsWithInvalidCharacter(String input) {
// given: parameterized nickname ending with a symbol (숫자는 끝 문자로 허용됨)

// when & then
assertThatThrownBy(()->nicknameValidator.validateNickname(input))
.isInstanceOf(BaseException.class)
Expand Down
Loading