diff --git a/.claude/agents/security-reviewer.md b/.claude/agents/security-reviewer.md new file mode 100644 index 00000000..0489af5a --- /dev/null +++ b/.claude/agents/security-reviewer.md @@ -0,0 +1,81 @@ +--- +name: security-reviewer +description: Coming Backend의 인증·인가 관련 변경사항(JWT, OAuth2, Redis 토큰 처리)을 Coming 도메인 정책 기준으로 검토하는 보안 리뷰 에이전트. "auth 관련 코드 작성 후 보안 리뷰해줘", "JWT/OAuth2 변경사항 보안 검토해줘" 요청 시 사용한다. 읽기 전용이며 코드를 직접 수정하지 않는다. +tools: Read, Bash, Grep +--- + +# Coming Backend 보안 리뷰 에이전트 + +변경된 Java 코드를 아래 체크리스트 기준으로 검토하고 심각도별 이슈를 보고한다. +**읽기 전용 리뷰어다 — 코드를 직접 수정하지 않는다. 발견한 이슈는 보고만 한다.** + +## 실행 순서 + +1. 변경된 파일 목록과 diff를 확인한다 (브랜치 커밋분 + 작업 트리 + 신규 파일) + ```bash + BASE=$(git merge-base origin/develop HEAD) + git diff --name-only "$BASE" # 브랜치 커밋분 + 작업 트리 변경 + git diff "$BASE" + git ls-files --others --exclude-standard # untracked 신규 파일 — 목록의 파일은 직접 읽는다 + ``` +2. 아래 체크리스트 기준으로 검토한다. 인증/인가와 무관한 변경이면 해당 없음으로 보고한다. +3. 심각도별로 이슈를 정리해 보고한다. + +--- + +## Coming 인증 정책 (기준) + +- Access Token: 30분, `Authorization: Bearer {token}` 헤더로만 전달 +- Refresh Token: 7일, HttpOnly Cookie로만 전달 (JS에서 접근 불가해야 함) +- 로그아웃 시 Access Token을 Redis 블랙리스트에 등록 +- OAuth2 Provider: Google, Kakao만 지원 + +## 심각도 기준 + +| 심각도 | 의미 | +|--------|------| +| 🔴 critical | 즉시 수정 필요 (인증 우회, 토큰 탈취, 권한 상승 가능) | +| 🟡 warning | 개선 권장 (정책 불일치, 방어 계층 누락) | +| 🔵 suggestion | 선택적 개선 | + +## 검토 체크리스트 + +### 🔴 critical +- 신규/변경 엔드포인트에 `@PreAuthorize` 또는 SecurityConfig 경로 설정 누락 (인증 필요 API가 허용 목록에 포함) +- `ROLE_ADMIN` 필요 엔드포인트에 권한 체크 누락 (일반 사용자가 403 없이 접근 가능) +- Refresh Token을 응답 바디·헤더·로그에 노출 (HttpOnly Cookie 외 경로로 전달) +- Access Token을 쿠키에 저장하거나 Refresh Token을 로컬 스토리지·바디로 내려보내는 등 Bearer/Cookie 역할 뒤바뀜 +- 로그아웃/토큰 폐기 로직에서 Redis 블랙리스트 등록 누락 +- JWT 서명 검증 없이 payload를 신뢰 (예: 파싱만 하고 `verify` 생략) +- 사용자 입력(닉네임, 문의 내용 등)을 파라미터 바인딩 없이 쿼리 문자열에 직접 결합 (SQL Injection) — JPA 파라미터 바인딩·엔티티 필드 대입은 해당 없음 +- 비밀번호·API 키·클라이언트 시크릿 등이 코드에 하드코딩되거나 로그에 평문 출력 + +### 🟡 warning +- 신규 인증 필요 엔드포인트에 Rate Limit 필터 미적용 +- OAuth2 실패·인증 예외 처리 시 내부 스택트레이스나 시스템 정보를 그대로 클라이언트에 노출 +- 다른 사용자의 리소스에 접근 가능한지(IDOR) 확인하는 소유자 검증 로직 누락 (예: `concertId`만으로 타 유저의 캘린더 항목 삭제 가능) +- 민감 정보(email, provider_id 등)를 응답 DTO에 불필요하게 포함 +- `application.yaml`에 `${ENV_VAR:default}` 형식이 아닌 민감 정보 직접 기재 + +### 🔵 suggestion +- 인증 관련 로그 레벨이 CLAUDE.md 기준(WARN: OAuth2 실패, INFO: 로그인 성공 등)과 다르게 기록됨 +- 에러 메시지가 공격자에게 유효한 계정/토큰 존재 여부를 암시 (예: "존재하지 않는 사용자"와 "비밀번호 불일치"를 구분해서 노출) + +--- + +## 결과 보고 + +``` +=== 보안 리뷰 결과 === +검토 파일: {파일 목록} +인증/인가 관련 변경: 있음 | 없음 + +🔴 critical: {N}건 +🟡 warning: {N}건 +🔵 suggestion: {N}건 + +{이슈 상세 목록 — 파일:라인, 문제, 개선 방향} + +{이슈 없으면: "✅ 보안 관점에서 커밋 진행 가능"} +{🔴 있으면: "🚫 커밋 전 critical 이슈를 수정하세요"} +``` diff --git a/.claude/hooks/guard_files.py b/.claude/hooks/guard_files.py new file mode 100644 index 00000000..170cbb11 --- /dev/null +++ b/.claude/hooks/guard_files.py @@ -0,0 +1,70 @@ +"""PreToolUse: 시크릿 파일 접근과 커밋된 Flyway 마이그레이션 수정을 차단한다. + +Bash는 명령을 토큰으로 나눠 각 토큰의 파일명을 검사한다. 따옴표로 묶인 문장(공백 포함 토큰)과 +heredoc 본문은 제외하므로 커밋 메시지·PR 본문의 언급은 걸리지 않는다. +문자열 조립 같은 의도적 우회까지 막지는 않는다 — 실수 방지용이다. +""" +import json +import os +import re +import shlex +import subprocess +import sys + +SECRET_NAME = re.compile(r"^(\.env(\..+)?|application-(local|secret).*|credentials(\..+)?|.*\.secrets?)$") +SECRET_ALLOWED = {".env.example"} +HEREDOC = re.compile(r"<<-?\s*(['\"]?)(\w+)\1.*?\n(.*?)^\s*\2\s*$", re.S | re.M) +REDIRECT_PREFIX = re.compile(r"^[0-9&]*[<>|]+") + + +def deny(reason): + print(json.dumps({"hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "deny", + "permissionDecisionReason": reason, + }}, ensure_ascii=False)) + sys.exit(0) + + +def is_secret(path): + name = os.path.basename(path.rstrip("/")) + return bool(SECRET_NAME.match(name)) and name not in SECRET_ALLOWED + + +def bash_targets(command): + command = HEREDOC.sub("", command) + lexer = shlex.shlex(command, posix=True, punctuation_chars=";&|<>()") + lexer.whitespace_split = True + try: + tokens = list(lexer) + except ValueError: + tokens = command.split() + # 파일명에 공백이 든 토큰은 따옴표로 묶인 문장이므로 제외(디렉터리 공백은 허용), `--file=.env`는 `=` 뒤만 본다 + tokens = [t for t in tokens if not re.search(r"\s", os.path.basename(t))] + return [REDIRECT_PREFIX.sub("", t).rsplit("=", 1)[-1] for t in tokens] + + +data = json.load(sys.stdin) +tool = data.get("tool_name", "") +tool_input = data.get("tool_input", {}) +path = tool_input.get("file_path", "") +name = os.path.basename(path) + +if tool == "Bash": + targets = bash_targets(tool_input.get("command", "")) +elif tool == "Grep": + targets = [tool_input.get("path", ""), tool_input.get("glob", "")] +else: + targets = [path] +hits = [t for t in targets if t and is_secret(t)] +if hits: + deny(f"시크릿 파일 접근 차단: {', '.join(hits)} — 필요한 내용은 사용자에게 직접 수정을 요청한다") + +if tool in ("Write", "Edit") and "/db/migration/" in path and name.startswith("V"): + cwd = data.get("cwd") or "." + committed = subprocess.run( + ["git", "cat-file", "-e", f"HEAD:./{os.path.relpath(path, cwd)}"], + cwd=cwd, capture_output=True, + ).returncode == 0 + if committed: + deny(f"커밋된 Flyway 마이그레이션 수정 차단: {path} — 변경은 새 V{{n}}__*.sql 파일로 추가한다") diff --git a/.claude/hooks/stop_reminder.sh b/.claude/hooks/stop_reminder.sh new file mode 100644 index 00000000..e863b258 --- /dev/null +++ b/.claude/hooks/stop_reminder.sh @@ -0,0 +1,5 @@ +#!/bin/sh +# Stop: 커밋되지 않은 Java 변경이 있을 때만 커밋 전 워크플로우를 안내한다. +cd "$CLAUDE_PROJECT_DIR" 2>/dev/null || exit 0 +[ -n "$(git status --porcelain -- '*.java')" ] || exit 0 +echo '{"systemMessage": "⚠️ 커밋되지 않은 Java 변경 있음 — write-tests → ./gradlew test → /be-review (auth 변경 시 + security-reviewer) → /simplify → /commit → /pr"}' diff --git a/.claude/settings.json b/.claude/settings.json index 8397acb4..34ee3a1a 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -2,12 +2,12 @@ "hooks": { "PreToolUse": [ { - "matcher": "Write|Edit", + "matcher": "Read|Write|Edit|Grep|Bash", "hooks": [ { "type": "command", - "command": "python3 -c \"import json,sys; data=json.load(sys.stdin); fp=data.get('tool_input',{}).get('file_path',''); blocked=['.env','.secret','application-prod','application-local','credentials','application-secret']; blocked_match=next((b for b in blocked if b in fp),None); print(json.dumps({'hookSpecificOutput':{'hookEventName':'PreToolUse','permissionDecision':'deny','permissionDecisionReason':'시크릿 파일 수정 차단: '+fp}})) if blocked_match else None\"", - "statusMessage": "시크릿 파일 검사 중..." + "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/guard_files.py\"", + "statusMessage": "보호 파일 검사 중..." } ] } @@ -18,11 +18,11 @@ "hooks": [ { "type": "command", - "command": "echo '⚠️ 커밋 전 워크플로우: /be-review → /simplify → /commit → /pr'" + "command": "sh \"$CLAUDE_PROJECT_DIR/.claude/hooks/stop_reminder.sh\"" } ] } ], "PostToolUse": [] } -} +} \ No newline at end of file diff --git a/.github/erd.png b/.github/erd.png new file mode 100644 index 00000000..9340bbc4 Binary files /dev/null and b/.github/erd.png differ diff --git a/CLAUDE.md b/CLAUDE.md index 0feb5ba6..26c72380 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,6 +12,7 @@ | Cache/Session | Redis | | Auth | OAuth2 (Google/Kakao) + JWT | | ORM | Spring Data JPA | +| Batch / Mail | Spring Batch, Spring Mail + Thymeleaf | | Util | Lombok | | Build | Gradle | @@ -24,7 +25,12 @@ com.Coming.Backend ├── concert/ # 공연, 예매 링크, 셋리스트 ├── calendar/ # 사용자 공연 캘린더 ├── release/ # 음악 발매 (앨범, 트랙) -├── user/ # 사용자 정보 +├── rating/ # 공연·발매 별점 +├── post/ # 커뮤니티 게시글·댓글, 멘션 태그, 통합 검색 +├── report/ # 게시글·댓글 신고 +├── notice/ # 공지사항 +├── policy/ # 약관·개인정보처리방침, 개정 안내 메일(Spring Batch) +├── user/ # 마이페이지 (다가오는 공연, 관람 이력, 내 문의) ├── inquiry/ # 문의 ├── admin/ # 관리자 └── common/ @@ -58,7 +64,8 @@ com.Coming.Backend ## 알려진 제약 -- `application-local.yaml`은 gitignore 대상이며, 프로젝트 훅이 파일명에 `application-local`/`application-prod`/`.env`/`credentials`가 포함된 파일의 Write/Edit를 자동 차단한다 (`.claude/settings.json`). 이 파일 수정이 필요하면 Claude가 직접 편집할 수 없으니, 추가할 내용을 알려주고 사용자가 직접 추가하도록 요청한다. +- `application-local.yaml`은 gitignore 대상이며, 프로젝트 훅(`.claude/hooks/guard_files.py`)이 시크릿 파일(`.env*`(`.env.example` 제외)·`application-local*`·`application-secret*`·`credentials*`·`*.secret(s)`)의 Read/Write/Edit/Grep 및 Bash 명령 내 접근을 자동 차단한다. 이 파일 수정이 필요하면 Claude가 직접 편집할 수 없으니, 추가할 내용을 알려주고 사용자가 직접 추가하도록 요청한다. +- 같은 훅이 git에 커밋된 Flyway 마이그레이션(`db/migration/V*.sql`)의 수정도 차단한다 (checksum 불일치 방지). 스키마 변경은 항상 새 버전 파일로 추가한다. ## 명세 위치 (Cominggg/Specification) @@ -107,45 +114,10 @@ com.Coming.Backend ### 커밋 전 체크리스트 - `/be-review` 통과(🔴 critical 0건) 전에 `/commit`을 실행하지 않는다. -- auth 관련 코드(JWT, OAuth2, Redis 토큰 처리) 작성 시 `/security-review`도 추가 실행한다. +- auth 관련 코드(JWT, OAuth2, Redis 토큰 처리) 작성 시 `security-reviewer` 에이전트도 추가로 호출한다 (Coming 인증 정책 기준 전용 체크리스트 보유, 읽기 전용). --- ## 행동 원칙 -> Adapted from [andrej-karpathy-skills/CLAUDE.md](https://github.com/forrestchang/andrej-karpathy-skills/blob/main/CLAUDE.md) -> These guidelines bias toward caution over speed. For trivial tasks, use judgment. - -### 1. 코딩 전에 먼저 생각하라 - -- 가정을 명시적으로 밝혀라. 불확실하면 물어봐라. -- 여러 해석이 가능하면 모두 제시하고, 조용히 하나를 고르지 마라. -- 더 단순한 방법이 있으면 말해라. 필요하면 반박해라. -- 무언가 불분명하면 멈춰라. 무엇이 헷갈리는지 이름 붙이고 물어봐라. - -### 2. 단순함 우선 - -- 요청된 것 이상의 기능을 만들지 마라. -- 단일 사용 코드에 추상화를 만들지 마라. -- 요청되지 않은 유연성이나 설정 가능성을 넣지 마라. -- 불가능한 시나리오에 대한 에러 핸들링을 만들지 마라. -- 200줄로 쓴 코드가 50줄로 가능하면 다시 써라. - -### 3. 외과적 변경 - -- 요청된 코드만 수정하라. 인접한 코드, 주석, 포맷을 "개선"하지 마라. -- 망가지지 않은 것을 리팩터링하지 마라. -- 기존 스타일이 마음에 들지 않아도 맞춰라. -- 관련 없는 데드코드를 발견하면 언급만 하고, 삭제하지 마라. - -### 4. 목표 기반 실행 - -작업을 검증 가능한 목표로 변환하라: -- "검증 추가" → "잘못된 입력 테스트 작성 후 통과" -- "버그 수정" → "재현 테스트 작성 후 통과" - -다단계 작업은 계획을 먼저 제시하라: -``` -1. [단계] → 검증: [체크] -2. [단계] → 검증: [체크] -``` +전역 `~/.claude/CLAUDE.md`의 "행동 원칙"(코딩 전에 먼저 생각하라·단순함 우선·외과적 변경·목표 기반 실행)을 따른다. diff --git a/README.md b/README.md index f2d12994..b6e78443 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ | ORM | Spring Data JPA | | API 문서 | springdoc-openapi | | 모니터링 | Actuator + Micrometer(Prometheus) + Grafana | +| 배치 · 메일 | Spring Batch, Spring Mail + Thymeleaf | | 부하 테스트 | k6 | | CI/CD | GitHub Actions → GHCR → SSH 배포 | | 컨테이너 | Docker | @@ -49,76 +50,97 @@ |--------|-----------| | `auth` | OAuth2 로그인(Google/Kakao), JWT 발급·재발급, Redis 블랙리스트 로그아웃 | | `artist` | 아티스트 조회, 팔로우 | -| `concert` | 공연 조회, 예매 링크, 셋리스트, 인기순 정렬 | +| `concert` | 공연 조회, 예매 링크, 셋리스트, 인기순 정렬, 별점 등록 | | `calendar` | 사용자 공연 캘린더 등록/조회 | -| `release` | 아티스트별 음악 발매(앨범/싱글/EP) 정보 | -| `user` | 사용자 정보 | +| `release` | 아티스트별 음악 발매(앨범/싱글/EP) 정보, 별점 등록 | +| `rating` | 공연·발매 별점 저장 및 평균 집계 | +| `post` | 커뮤니티 게시글·댓글, 추천·좋아요, 아티스트/공연/발매 멘션 태그, 통합 검색 | +| `report` | 게시글·댓글 신고, 이벤트 기반 알림 | +| `notice` | 공지사항 조회 | +| `policy` | 약관·개인정보처리방침 버전 관리, 개정 시 Spring Batch 기반 안내 메일 발송 | +| `user` | 마이페이지 (다가오는 공연, 관람 이력, 내 문의) | | `inquiry` | 문의 등록·조회, 이벤트 기반 알림 | -| `admin` | 관리자 전용 CRUD (아티스트·공연 등) | +| `admin` | 관리자 전용 기능 (아티스트·공연·공지·문의·신고 관리, 데이터 파이프라인 수집 요청) | **인증 정책**: Access Token 30분(`Authorization: Bearer`), Refresh Token 7일(HttpOnly Cookie), 로그아웃 시 Redis 블랙리스트 등록. **공통 응답 형식** + +에러 ```json -// 에러 { "code": "CONCERT_NOT_FOUND", "message": "존재하지 않는 공연입니다." } +``` -// 페이지네이션 +페이지네이션 +```json { "content": [], "page": 0, "size": 20, "totalElements": 100, "totalPages": 5 } ``` +## ERD + +Flyway V1–V40 적용 기준 27개 테이블(Spring Batch 메타 테이블 제외). FK는 대부분 DB 제약 없이 애플리케이션 레벨에서 관리하는 논리적 참조이며, 별점·신고·문의·멘션 태그는 `target_type + target_id` 다형 참조를 사용한다. + +
+ --- ## AI 협업 워크플로우 -이 프로젝트는 기능 구현뿐 아니라 **개발 프로세스 자체를 Claude Code의 서브에이전트·스킬로 설계**했습니다. -1인 개발이지만, 역할을 나눠 병렬로 검토·검증하는 체계를 갖추는 것을 목표로 했습니다. +Claude Code 에이전트·스킬·훅으로 이슈부터 PR까지 진행합니다. 프로젝트 규칙은 [`CLAUDE.md`](CLAUDE.md)에 모여 있고, 아래 도구들이 그 규칙을 역할별로 나눠 강제합니다. + +### 흐름 -### 개발 흐름 +``` +/issue → /plan-issue → 구현 → write-tests → /be-review (+ security-reviewer) → /simplify → /commit → /pr → /sync-docs +``` -1. `/issue` — GitHub 이슈·브랜치 생성 -2. 구현 -3. `write-tests` — 3개 이상 도메인은 병렬 실행 -4. `/be-review` — DDD·SOLID·커버리지·API 스펙 리뷰 (🔴 critical 존재 시 2번으로 복귀) -5. `/simplify` → `/commit` → `/pr` → `/sync-docs` +| 단계 | 도구 | 하는 일 | +|------|------|--------| +| 이슈·브랜치 | `/issue` | GitHub 이슈 생성 + `{type}/#{번호}-...` 브랜치 체크아웃 | +| 계획 | `/plan-issue` | 이슈 체크리스트를 코드 현황과 대조하고, 남은 작업을 커밋 단위로 순서화 (마이그레이션 → Entity/Repository → Service → Controller) | +| 구현 | 메인 세션 | 클래스 단위 구현 | +| 테스트 작성 | `write-tests` 에이전트 | 클래스 구현 직후 테스트 작성, `./gradlew test`로 통과 확인 | +| 리뷰 | `/be-review` | DDD 레이어·SOLID·테스트 커버리지·API 스펙 검토 — 🔴 critical 0건이어야 커밋 | +| 보안 리뷰 | `security-reviewer` 에이전트 | auth 관련 변경(JWT, OAuth2, Redis 토큰 처리) 시 추가 검토 | +| 정리 | `/simplify` | 변경 코드의 중복·불필요한 복잡도 정리 | +| 커밋·PR | `/commit`, `/pr` | 컨벤션(`[{type}] 요약`)에 맞춘 커밋·PR 작성 | +| 명세 동기화 | `/sync-docs` | 변경 사항을 `Cominggg/Specification` 명세 문서에 반영 | -- `/be-review`에서 🔴 critical 이슈가 0건일 때만 커밋으로 진행합니다. -- auth 관련 코드(JWT, OAuth2, Redis 토큰 처리)는 `/security-review`를 추가로 실행합니다. -- 테스트 작성은 도메인 수에 따라 병렬/순차를 구분합니다 — 3개 이상 도메인은 에이전트를 병렬 호출하고, 1~2개는 순차 작성합니다(cold start 중복 비용이 병렬화 이득을 초과하는 지점을 기준으로 판단). +두 에이전트는 이 레포의 [`.claude/agents/`](.claude/agents/)에 포함돼 있습니다. `/issue`, `/plan-issue`, `/be-review`, `/commit`, `/pr`, `/sync-docs`는 작성자의 전역 Claude Code 스킬이고, `/simplify`는 Claude Code 기본 제공 스킬이라 레포에는 없습니다. -### 도메인 전문가 서브에이전트 +### 에이전트 역할 분리와 병렬 실행 -기능 추가 전 검토가 필요할 때, 실제 API·DB 스키마를 알고 있는 역할별 에이전트에게 병렬로 의견을 구합니다. +| 에이전트 | 도구 | 역할 | +|---------|------|------| +| [`write-tests`](.claude/agents/write-tests.md) | Read·Write·Edit·Bash | `src/test/java/`에 구현과 같은 패키지 구조로 테스트 작성 (Service는 Mockito 단위 테스트, Controller는 `@WebMvcTest`) | +| [`security-reviewer`](.claude/agents/security-reviewer.md) | Read·Bash·Grep | 읽기 전용 — 이슈를 심각도별로 보고만 하고 코드를 수정하지 않음 | -| 에이전트 | 역할 | -|----------|------| -| `pm-expert` | 사용자 가치·우선순위·운영 리스크 관점 검토 | -| `be-expert` | API 설계, DB 스키마 변경, 인증·보안 영향 검토 | -| `fe-expert` | 기존 컴포넌트·UX 관점에서 구현 난이도 검토 | -| `data-expert` | 외부 수집 파이프라인 영향, 수집 주기·매칭 로직 검토 | +`write-tests`는 테스트 대상 도메인 수에 따라 실행 방식을 나눕니다. -`review-feature` 스킬은 이 4개 에이전트를 **동시에** 호출한 뒤, 아래 규칙으로 결과를 합산해 최종 권고를 냅니다. +- 3개 이상 도메인이면 도메인별로 병렬 호출하고, 1~2개면 순차 작성합니다 (cold start 중복 비용이 병렬화 이득을 초과하는 지점을 기준으로 판단). +- 호출 전 `build.gradle`과 기존 테스트 예제 1개를 프롬프트에 포함해, 에이전트마다 같은 파일을 반복 탐색하지 않게 합니다. +- 같은 도메인의 Service·Controller 테스트는 순서대로 작성합니다 (Controller 테스트가 Service 계약을 전제). -- 전원 "추가" → ✅ 추가 -- 1개 이상 "조건부" + 나머지 "추가" → ⚠️ 조건부 추가 -- 2개 이상 "보류" → 🔁 보류 -- 1개 이상 "반려" → ❌ 반려 +### 코드 리뷰 -### 커스텀 스킬 +`/be-review` 체크리스트는 심각도(🔴/🟡/🔵)별로 나뉘며, 커밋을 막는 🔴 critical 기준은 다음과 같습니다. -| 스킬 | 역할 | -|------|------| -| `be-review` | DDD 레이어·SOLID·테스트 커버리지·API 스펙 리뷰, 심각도별(🔴/🟡/🔵) 보고 | -| `security-review` | auth 관련 코드 보안 검토 | -| `review-feature` | 신규 기능 아이디어를 4개 에이전트로 병렬 사전 검토 | -| `commit` / `pr` | 컨벤션에 맞는 커밋 메시지·PR 초안 자동 생성 | +- **DDD 레이어**: Controller에 비즈니스 로직 금지, Entity를 응답 타입으로 직접 반환 금지 +- **캡슐화**: Entity에 `@Setter`·`@Data`, `public` 필드 금지 +- **테스트**: 신규 `@Service` 메서드에 단위 테스트 필수, 통합 테스트는 H2·Mock DB가 아닌 실제 PostgreSQL 사용 +- **API 스펙**: 에러 응답은 `{"code", "message"}` 형식과 `ErrorCode` enum만 사용, 인증 필요 엔드포인트의 Security 설정 누락 금지 -이 외에 이슈 생성·브랜치 자동화(`issue`), 명세 동기화(`read-spec`/`sync-docs`) 등 반복 작업용 스킬도 함께 운용하고 있습니다. +`security-reviewer`는 범용 `/security-review` 대신 Coming 인증 정책(Access 30분 Bearer·Refresh 7일 HttpOnly Cookie·로그아웃 Redis 블랙리스트) 기준으로 토큰 노출·Bearer/Cookie 역할 뒤바뀜, 블랙리스트 등록 누락, 서명 미검증, 권한 체크 누락, IDOR 등을 검토합니다. -### 안전장치 +### 훅 ([`.claude/settings.json`](.claude/settings.json), [`.claude/hooks/`](.claude/hooks)) -- `application-local.yaml`, `.env`, `credentials`가 포함된 파일명은 프로젝트 훅(`.claude/settings.json`)이 Claude의 Write/Edit 자체를 차단합니다. -- `write-tests` 에이전트를 프로젝트 로컬로 두어(`.claude/agents/write-tests.md`) 이 레포의 테스트 컨벤션에 맞는 테스트만 생성하도록 제한했습니다. +| 시점 | 대상 | 동작 | +|------|------|------| +| PreToolUse | Read·Write·Edit·Grep·Bash | 시크릿 파일(`.env*`·`application-local*`·`application-secret*`·`credentials*`·`*.secret(s)`, `.env.example` 제외) 접근 차단 — Bash는 명령 토큰의 파일명 검사(따옴표 문장·heredoc 본문 제외) | +| PreToolUse | Write·Edit | 커밋된 Flyway 마이그레이션(`db/migration/V*.sql`) 수정 차단 — checksum 불일치 방지, 변경은 새 버전 파일로 | +| Stop | 응답 종료 시 | 커밋되지 않은 `.java` 변경이 있을 때만 커밋 전 워크플로우 안내 표시 | 이 워크플로우를 설계하며 겪은 구체적인 판단·트레이드오프는 별도 문서로 기록하고 있습니다. @@ -161,15 +183,15 @@ open -a Docker && docker start redis # Docker 데몬이 꺼져 있으면 먼 - **CD** (`cd.yml`): `main` 브랜치 push(= `develop` → `main` 병합) 시 Docker 이미지를 GHCR에 push하고, Lightsail 인스턴스로 SSH 접속해 `scripts/deploy.sh` 실행 - Nginx가 `be-blue`(:8080)/`be-green`(:8081) 중 활성 슬롯으로만 트래픽을 전달하고, Redis는 두 슬롯이 공유합니다. Data Pipeline(`data`)도 같은 Docker Compose에 포함되어 별도 인스턴스 없이 함께 배포됩니다. - 배포 시 standby 슬롯에 새 이미지를 pull → `/actuator/health` 체크 통과 → Nginx upstream 전환 → 이전 슬롯 정지 순으로 무중단 배포합니다. -- DB는 별도 Lightsail 인스턴스(Managed PostgreSQL)로 분리되어 두 슬롯이 공통으로 바라보고, 4xx/5xx 에러·공연 데이터 수집 결과·문의 접수는 각각 Discord Webhook으로 알림됩니다. +- DB는 별도 Lightsail 인스턴스(Managed PostgreSQL)로 분리되어 두 슬롯이 공통으로 바라보고, 4xx/5xx 에러·공연 데이터 수집 결과·문의 접수·신고 접수는 각각 Discord Webhook으로 알림됩니다. ## 프로젝트 규모 | 항목 | 내용 | |------|------| | 개발 기간 | 2026-05 ~ (진행 중) | -| 도메인 수 | 8개 (auth / artist / concert / calendar / release / user / inquiry / admin) | -| DB 마이그레이션 | 25개 (Flyway) | +| 도메인 수 | 13개 (auth / artist / concert / calendar / release / rating / post / report / notice / policy / user / inquiry / admin) | +| DB 마이그레이션 | 40개 (Flyway) | | 연동 레포 | 4개 (Backend / Frontend / Data / Specification) | ---