Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
99f727b
`test: AdminHelpControllerDocsTest 테스트 URI 및 필드명 수정`
Whale0928 Dec 31, 2025
71c91b5
docs: 회원가입 및 Help API 문서 추가 및 수정
Whale0928 Dec 31, 2025
3243e53
feat: 이미지 업로드 로그 테이블 추가
Whale0928 Dec 31, 2025
e00eecc
feat: 이미지 업로드 로그 관리 기능 추가
Whale0928 Jan 5, 2026
ef9a2b2
refactor: Request DTO에서 Entity 의존성 제거
Whale0928 Jan 5, 2026
d0f82dc
refactor: 아키텍처 규칙 위반 수정
Whale0928 Jan 5, 2026
5dbd511
refactor: ImageUploadLog를 이벤트 기반 ResourceLog로 변경
Whale0928 Jan 6, 2026
d48647a
refactor: ImageUpload 테스트 통합 및 Mock 테스트 제거
Whale0928 Jan 6, 2026
d6588bb
test: PreSigned URL API 통합 테스트 추가
Whale0928 Jan 6, 2026
85d3a47
refactor: ImageUpload 통합 테스트 MockMvcTester로 변경 및 ResourceLog 검증 추가
Whale0928 Jan 6, 2026
c3e173d
feat: 이미지 리소스 활성화 이벤트 기반 ResourceLog 로깅 구현
Whale0928 Jan 9, 2026
aa9b0fd
test: 이미지 리소스 ACTIVATED 이벤트 통합 테스트 추가
Whale0928 Jan 9, 2026
08aacc9
docs: CLAUDE.md 테스트 패턴 및 서브모듈 가이드 추가
Whale0928 Jan 9, 2026
a74703c
chore: version update
Whale0928 Jan 11, 2026
cfcad1b
Merge remote-tracking branch 'origin/main'
Whale0928 Jan 11, 2026
173d4fc
chore: version update
Whale0928 Jan 11, 2026
085a52a
chore: version update
Whale0928 Jan 11, 2026
a996df5
feat(alcohols): TastingTag 추출 기능 스켈레톤 구조 추가
claude Jan 11, 2026
b0e1950
chore: apply code formatting [skip ci]
github-actions[bot] Jan 11, 2026
65fa875
feat(alcohols): Aho-Corasick 기반 TastingTag 추출 로직 구현
claude Jan 12, 2026
ef236e1
refactor: aho-corasick 버전 관리를 toml로 이동
claude Jan 12, 2026
1df1b41
test(alcohols): TastingTagService 단위 테스트 추가
claude Jan 12, 2026
f7b977e
chore: apply code formatting [skip ci]
github-actions[bot] Jan 12, 2026
753dd32
test(alcohols): TastingTagServiceTest ParameterizedTest로 리팩토링
claude Jan 12, 2026
09e8e6b
chore: apply code formatting [skip ci]
github-actions[bot] Jan 12, 2026
dc3a89e
fix: 아키텍처 규칙 준수를 위한 수정
claude Jan 12, 2026
663fc6a
feat(alcohols): TastingTag 추출 API 엔드포인트 추가
claude Jan 12, 2026
6102a8d
fix: 컨트롤러 메서드 명명 규칙 준수
claude Jan 12, 2026
ff216c8
test(alcohols): TastingTag 통합 테스트 및 REST Docs 추가
claude Jan 12, 2026
6194e34
chore: apply code formatting [skip ci]
github-actions[bot] Jan 12, 2026
33b560d
fix(alcohols): TastingTag 통합 테스트 Trie 초기화 문제 수정
Whale0928 Jan 13, 2026
f28affa
fix(alcohols): TastingTag REST Docs 필드 타입 수정 및 HTTP 테스트 파일 추가
Whale0928 Jan 13, 2026
a433ea2
docs(alcohols): TastingTag API 문서 추가
Whale0928 Jan 13, 2026
b2ce6f7
refactor: best review selection query and payload structure
hgkim-openerd Jan 13, 2026
8d4fc24
chore: bump bottlenote-product-api version to 1.0.3
hgkim-openerd Jan 13, 2026
92fd516
feat(alcohols): 조회수 기반 인기 위스키 API 추가
hgkim-openerd Jan 13, 2026
06e0958
chore: apply code formatting [skip ci]
github-actions[bot] Jan 13, 2026
2628582
chore: 테스트 파일 삭제
Whale0928 Jan 14, 2026
f43600b
chore: 인기 주류 관련 테스트 및 데이터 삭제
Whale0928 Jan 14, 2026
eeee046
docs(alcohols): 인기 위스키 조회 API 문서 추가
Whale0928 Jan 14, 2026
00fd5dc
docs(blocks): 유저 차단 API 문서에 요청 및 응답 파라미터 섹션 추가
Whale0928 Jan 14, 2026
11ae0e9
fix(batch): 일일 데이터 리포트 환경 기반 스킵 로직으로 변경
hgkim-openerd Jan 14, 2026
4cf208b
fix(resource): 리뷰 수정 시 이미지 리소스 ACTIVATED 로그 중복 저장 방지
Whale0928 Jan 14, 2026
2999102
fix(test): 리뷰 수정 통합 테스트에 필수 status 필드 추가
Whale0928 Jan 14, 2026
80e7cfe
refactor: 이미지 리소스 저장 방식
Whale0928 Jan 14, 2026
01c5ebd
feat(tag): 태그 추출 시 부분 매칭 허용
claude Jan 15, 2026
eaafe58
chore: apply code formatting [skip ci]
github-actions[bot] Jan 15, 2026
57e4642
feat: 위스키 탐색 및 테이스팅 태그 요청 추가
Whale0928 Jan 15, 2026
7a060c0
feat(resource): 이미지 리소스 무효화/삭제 이벤트 인프라 추가
Whale0928 Jan 15, 2026
502b48b
feat(image): 이미지 교체/삭제 시 INVALIDATED 상태 전환 로직 구현
Whale0928 Jan 15, 2026
3a0c5a6
fix: Docker 캐시 설정 registry 기반으로 변경
Whale0928 Jan 15, 2026
11ee970
Merge remote-tracking branch 'origin/main'
Whale0928 Jan 15, 2026
d158945
chore: 캐시 정리 워크플로우 추가
Whale0928 Jan 15, 2026
2ff7032
feat(security): 악성 봇/스캐너 경로 차단 기능 추가
Whale0928 Jan 16, 2026
bc2f039
feat(admin-security): admin-api에 악성 봇/스캐너 경로 차단 적용
Whale0928 Jan 16, 2026
80669cb
docs: batch 모듈 분리 계획 문서 작성
hgkim-openerd Jan 16, 2026
ab7d50f
docs: batch 모듈 분리 계획 문서 보강
hgkim-openerd Jan 16, 2026
a786a09
docs: DB 커넥션 풀 설계 내용 추가
hgkim-openerd Jan 16, 2026
7d905cb
docs(batch): BestReviewReader 무한 루프 버그 문서화
Whale0928 Jan 16, 2026
a6a3f16
fix: 예외 로그 메시지 출력 형식 개선
Whale0928 Jan 16, 2026
ea4c21b
refactor: 배치 및 Quartz 관련 설정 및 의존성 제거
Whale0928 Jan 16, 2026
5d3ffef
feat: 배치 모듈 초기 설정 추가
Whale0928 Jan 16, 2026
3f450be
refactor: 배치 모듈 리팩토링 및 불필요한 리소스 제거
Whale0928 Jan 16, 2026
c9e0993
chore: .gitignore에 spy.log 추가
Whale0928 Jan 16, 2026
eeb1000
feat: Admin API 이미지 업로드 PreSigned URL 기능 추가
Whale0928 Jan 18, 2026
2977045
test: Admin 이미지 업로드 통합 테스트 시나리오 확장 및 CLAUDE.md 체크리스트 추가
Whale0928 Jan 18, 2026
88c179b
docs: Admin 이미지 업로드 API 문서화 추가
Whale0928 Jan 18, 2026
e0b2ec2
feat: Admin 술 단건 조회 및 참조 데이터 API 추가
claude Jan 19, 2026
967fce1
docs: Admin API RestDocs 테스트 및 문서 추가
claude Jan 19, 2026
24215c7
chore: apply code formatting [skip ci]
github-actions[bot] Jan 19, 2026
88c734f
feat: Admin 참조 데이터 API에 Pagination 적용
claude Jan 19, 2026
2d3ae91
chore: apply code formatting [skip ci]
github-actions[bot] Jan 19, 2026
699c6f5
fix: InMemoryTastingTagRepository에 누락된 findAllTastingTags 메서드 추가
claude Jan 19, 2026
c26126c
chore: apply code formatting [skip ci]
github-actions[bot] Jan 19, 2026
207e6e2
fix: CI 빌드 오류 수정
claude Jan 19, 2026
e90e1e0
chore: apply code formatting [skip ci]
github-actions[bot] Jan 19, 2026
92d5db5
refactor: product-api 구조에 맞게 의존성 수정
claude Jan 19, 2026
c4882a5
chore: apply code formatting [skip ci]
github-actions[bot] Jan 19, 2026
81010be
refactor: auth 패키지명 persentaton → presentation 수정
Whale0928 Jan 19, 2026
3387920
refactor: alcohols 패키지명 수정 및 Service 레이어 적용
Whale0928 Jan 19, 2026
2cf048e
fix: InMemoryAlcoholQueryRepository에 누락된 메서드 추가
Whale0928 Jan 19, 2026
09fbc3b
docs: 위스키 검색 API 성능 개선 계획 문서 추가
Whale0928 Jan 19, 2026
fa70b26
docs: 마이그레이션 관련 문서 정리
Whale0928 Jan 20, 2026
4f66c99
docs: Admin API HTTP 스펙 문서 초안 추가
Whale0928 Jan 20, 2026
e768e96
feat: Admin Alcohol CUD API 및 카테고리 레퍼런스 API 구현
Whale0928 Jan 20, 2026
cf8f5ba
fix: product-api InMemoryAlcoholQueryRepository에 findAllCategoryPairs…
Whale0928 Jan 20, 2026
cdcd163
chore: 서브모듈 버전 업데이트 (deleted_at 컬럼 추가)
Whale0928 Jan 20, 2026
c3faa16
docs: Add popular whiskey API plan and admin API enum documentation
hgkim-openerd Jan 21, 2026
2de65eb
refactor: batch 모듈 독립 실행 및 관련 설정 분리
Whale0928 Jan 22, 2026
0d674be
feat: TestDataSetupHelper 및 TestData record 추가
Whale0928 Jan 22, 2026
86364a6
refactor: Picks/Rating/Likes 통합 테스트 @Sql 제거
Whale0928 Jan 22, 2026
f26f742
refactor: User 통합 테스트 @Sql 제거
Whale0928 Jan 22, 2026
4d23075
refactor: UserHistory 통합 테스트 @Sql 제거
Whale0928 Jan 22, 2026
3aa9e24
refactor: Review/Reply 통합 테스트 @Sql 제거
Whale0928 Jan 22, 2026
c2403f2
chore: init-script SQL 파일 삭제
Whale0928 Jan 22, 2026
67da49a
docs: MockMvc to MockMvcTester 마이그레이션 계획 문서 추가
Whale0928 Jan 22, 2026
d627737
deps: version sync
hgkim-openerd Jan 22, 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
4 changes: 2 additions & 2 deletions .github/actions/docker-build-push/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ runs:
GIT_BRANCH=${{ github.ref_name }}
BUILD_TIME=${{ steps.build-time.outputs.time }}
${{ inputs.build-args }}
cache-from: type=gha,scope=${{ inputs.cache-scope }}
cache-to: type=gha,scope=${{ inputs.cache-scope }},mode=max
cache-from: type=registry,ref=${{ inputs.registry-url }}/${{ inputs.image-name }}:cache
cache-to: type=registry,ref=${{ inputs.registry-url }}/${{ inputs.image-name }}:cache,mode=max

- name: Install Cosign
if: inputs.sign-image == 'true'
Expand Down
114 changes: 114 additions & 0 deletions .github/workflows/cache-cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: cache cleanup

on:
schedule:
- cron: '0 0 * * *' # 매일 자정 (UTC)
workflow_dispatch:
inputs:
threshold_gb:
description: '삭제 기준 용량 (GB)'
required: false
default: '5'
dry_run:
description: '테스트 모드 (삭제 안 함)'
required: false
type: boolean
default: false

jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Check cache usage
id: check
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
USAGE=$(gh api repos/${{ github.repository }}/actions/cache/usage --jq '.active_caches_size_in_bytes')
USAGE_GB=$(echo "scale=2; $USAGE / 1024 / 1024 / 1024" | bc)
THRESHOLD=${{ inputs.threshold_gb || '5' }}

echo "현재 캐시 사용량: ${USAGE_GB}GB / 10GB"
echo "삭제 기준: ${THRESHOLD}GB"
echo "usage_gb=$USAGE_GB" >> $GITHUB_OUTPUT
echo "threshold=$THRESHOLD" >> $GITHUB_OUTPUT

if (( $(echo "$USAGE_GB > $THRESHOLD" | bc -l) )); then
echo "should_clean=true" >> $GITHUB_OUTPUT
echo "기준 초과 - 정리 필요"
else
echo "should_clean=false" >> $GITHUB_OUTPUT
echo "기준 이하 - 정리 불필요"
fi

- name: List caches before cleanup
if: steps.check.outputs.should_clean == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "=== 캐시 목록 (크기순) ==="
gh api repos/${{ github.repository }}/actions/caches --paginate \
--jq '.actions_caches | sort_by(-.size_in_bytes) | .[:20][] | "\(.key[0:50]) | \(.ref[0:25]) | \(.size_in_bytes / 1024 / 1024 | floor)MB"'

- name: Delete PR branch caches
if: steps.check.outputs.should_clean == 'true' && inputs.dry_run != true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "=== PR 브랜치 캐시 삭제 ==="
gh api repos/${{ github.repository }}/actions/caches --paginate \
--jq '.actions_caches[] | select(.ref | startswith("refs/pull/")) | .id' | \
while read id; do
if [ -n "$id" ]; then
gh api -X DELETE repos/${{ github.repository }}/actions/caches/$id && echo "Deleted: $id"
fi
done

- name: Check usage after PR cleanup
if: steps.check.outputs.should_clean == 'true' && inputs.dry_run != true
id: after_pr
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
sleep 3
USAGE=$(gh api repos/${{ github.repository }}/actions/cache/usage --jq '.active_caches_size_in_bytes')
USAGE_GB=$(echo "scale=2; $USAGE / 1024 / 1024 / 1024" | bc)
THRESHOLD=${{ inputs.threshold_gb || '5' }}

echo "PR 정리 후 사용량: ${USAGE_GB}GB"

if (( $(echo "$USAGE_GB > $THRESHOLD" | bc -l) )); then
echo "still_over=true" >> $GITHUB_OUTPUT
else
echo "still_over=false" >> $GITHUB_OUTPUT
fi

- name: Delete old main branch caches (if still over)
if: steps.after_pr.outputs.still_over == 'true' && inputs.dry_run != true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "=== main 브랜치 오래된 캐시 삭제 (7일 이상) ==="
CUTOFF=$(date -d '7 days ago' -Iseconds)

gh api repos/${{ github.repository }}/actions/caches --paginate \
--jq ".actions_caches[] | select(.ref == \"refs/heads/main\" and .last_accessed_at < \"$CUTOFF\") | .id" | \
while read id; do
if [ -n "$id" ]; then
gh api -X DELETE repos/${{ github.repository }}/actions/caches/$id && echo "Deleted old cache: $id"
fi
done

- name: Final usage report
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
sleep 3
USAGE=$(gh api repos/${{ github.repository }}/actions/cache/usage --jq '.active_caches_size_in_bytes')
USAGE_GB=$(echo "scale=2; $USAGE / 1024 / 1024 / 1024" | bc)
COUNT=$(gh api repos/${{ github.repository }}/actions/caches --jq '.total_count')

echo "=============================="
echo "최종 캐시 사용량: ${USAGE_GB}GB / 10GB"
echo "캐시 개수: ${COUNT}개"
echo "=============================="
2 changes: 2 additions & 0 deletions .github/workflows/github-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ on:
branches: [ "main" ]
paths:
- 'bottlenote-*/src/docs/**'
- 'bottlenote-*/src/test/**/docs/**'
- 'bottlenote-*/build.gradle*'
- 'gradle/libs.versions.toml'
- 'docs/**'
- '.github/workflows/github-pages.yml'
workflow_dispatch:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,6 @@ cosign.key
cosign.pub

*.env.*

# Log files
spy.log
200 changes: 197 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,43 @@ flowchart TD
### bottlenote-admin-api
- **역할**: 관리자용 API 서버 모듈 (Kotlin)
- **특징**:
- bottlenote-mono 모듈 의존
- 관리자 전용 REST API
- bottlenote-mono 모듈 의존 (비즈니스 로직은 mono에서 제공)
- 관리자 전용 REST API (프레젠테이션 계층만 담당)
- context-path: `/admin/api/v1`
- Spring REST Docs 기반 API 문서화
- JWT 기반 어드민 전용 인증 체계
- 루트 어드민 자동 초기화 (`ApplicationReadyEvent`)

**패키지 구조**:
```
app/
├── Application.kt # 진입점
├── bottlenote/{domain}/
│ ├── presentation/ # 컨트롤러
│ └── config/ # 설정 클래스
└── global/
├── common/ # 공통 유틸
└── security/ # 보안 설정
```

**Kotlin 코딩 컨벤션**:
- `data class`: 요청/응답 DTO, 설정 클래스
- `object`: 테스트 헬퍼 (싱글톤)
- `val` 불변성 선호, `lateinit var`는 DI 주입용으로만 사용
- 생성자 주입: 클래스 선언부에 파라미터로 명시
- Named parameters 활용으로 가독성 향상

**테스트 구조**:
- `@Tag("admin_integration")`: 통합 테스트 태그
- `IntegrationTestSupport`: 테스트 베이스 클래스 (TestContainers, MockMvcTester)
- `app/docs/`: RestDocs 테스트 (`@WebMvcTest`)
- `app/integration/`: 통합 테스트
- `app/helper/`: 테스트 헬퍼 (`object` 싱글톤)

**인증 체계**:
- `AdminJwtAuthenticationFilter`, `AdminJwtAuthenticationManager` 사용
- `SecurityContextUtil.getAdminUserIdByContext()`: 현재 어드민 ID 조회
- RBAC 역할: `ROOT_ADMIN`, `PARTNER`, `COMMUNITY_MANAGER`

### bottlenote-batch
- **역할**: 배치 처리 모듈
Expand All @@ -84,13 +118,134 @@ flowchart TD
## 빌드 및 실행

```bash
# 서브모듈 초기화 (최초 클론 후 필수)
git submodule update --init --recursive

./gradlew build # 전체 빌드
./gradlew test # 기본 테스트 (integration, data-jpa-test 제외)
./gradlew unit_test # 단위 테스트 (@Tag("unit"))
./gradlew integration_test # 통합 테스트 (@Tag("integration"))
./gradlew check_rule_test # 아키텍처 규칙 테스트 (@Tag("rule"))
./gradlew asciidoctor # API 문서 생성
./gradlew bootRun # 애플리케이션 실행

# admin-api 모듈 전용
./gradlew :bottlenote-admin-api:build # admin-api 빌드
./gradlew :bottlenote-admin-api:test # admin-api 테스트 실행
./gradlew :bottlenote-admin-api:asciidoctor # admin-api 문서 생성
./gradlew :bottlenote-admin-api:bootRun # admin-api 실행
```

### 서브모듈

- **git.environment-variables**: 환경 설정 및 초기화 스크립트 포함
- `storage/mysql/init/*.sql`: TestContainers용 DB 초기화 스크립트
- 통합 테스트 실행 전 서브모듈 초기화 필수

## Admin API 구현 규칙

### Admin API 구현 체크리스트

새로운 Admin API를 구현할 때 다음 체크리스트를 따르세요:

#### 1. 요구사항 분석 및 설계
- [ ] product-api에 동일/유사 기능이 있는지 확인
- [ ] mono 모듈의 기존 서비스/도메인 로직 재사용 가능 여부 확인
- [ ] 신규 서비스 메서드가 필요한 경우 mono 모듈에 추가 계획
- [ ] API 엔드포인트 설계 (HTTP Method, URL 패턴)

#### 2. mono 모듈 수정 (필요 시)
- [ ] 서비스 클래스에 admin 전용 메서드 추가 (예: `xxxForAdmin`)
- [ ] 인증 방식 분리: admin은 `adminId`를 파라미터로 받도록 설계
- [ ] 공통 로직 추출 및 리팩토링 (`private` 메서드 분리)
- [ ] 기존 테스트 영향 확인 및 수정

#### 3. admin-api 컨트롤러 구현
- [ ] 패키지: `app.bottlenote.{domain}.presentation`
- [ ] 클래스명: `Admin{도메인명}Controller`
- [ ] `@RestController`, `@RequestMapping("/{리소스}")` 설정
- [ ] 인증이 필요한 API: `SecurityContextUtil.getAdminUserIdByContext()` 호출
- [ ] 응답: `GlobalResponse.ok(response)` 래핑

#### 4. 테스트 작성
- [ ] 통합 테스트: `app/integration/{domain}/Admin{도메인명}IntegrationTest.kt`
- `IntegrationTestSupport` 상속
- `@Tag("admin_integration")` 태그
- 인증 성공/실패 케이스
- 주요 비즈니스 시나리오
- [ ] RestDocs 테스트 (API 문서화 필요 시): `app/docs/{domain}/Admin{도메인명}ControllerDocsTest.kt`

#### 5. 검증 및 완료
- [ ] 컴파일 확인: `./gradlew :bottlenote-admin-api:compileKotlin`
- [ ] 테스트 실행: `./gradlew :bottlenote-admin-api:admin_integration_test`
- [ ] API 문서 생성: `./gradlew :bottlenote-admin-api:asciidoctor` (RestDocs 테스트 작성 시)

### 컨트롤러 작성 규칙

1. **패키지 위치**: `app.bottlenote.{domain}.presentation`
2. **클래스명**: `Admin{도메인명}Controller`
3. **매핑**: `@RequestMapping("/{복수형 리소스}")` (예: `/helps`, `/alcohols`)
4. **응답 타입**: `ResponseEntity<*>` 또는 `ResponseEntity<GlobalResponse>`
5. **응답 래핑**: `GlobalResponse.ok(response)` 사용

### API 엔드포인트 설계

| HTTP Method | 용도 | URL 패턴 | 예시 |
|-------------|------|----------|------|
| GET | 목록 조회 | `/{resources}` | `GET /helps` |
| GET | 단건 조회 | `/{resources}/{id}` | `GET /helps/1` |
| POST | 생성/액션 | `/{resources}` 또는 `/{resources}/{id}/{action}` | `POST /helps/1/answer` |
| PUT | 전체 수정 | `/{resources}/{id}` | `PUT /helps/1` |
| PATCH | 부분 수정 | `/{resources}/{id}` | `PATCH /helps/1` |
| DELETE | 삭제 | `/{resources}/{id}` | `DELETE /helps/1` |

### 요청/응답 처리

1. **목록 조회 요청**: `@ModelAttribute` + Request DTO
2. **단건 조회**: `@PathVariable`
3. **생성/수정 요청**: `@RequestBody @Valid` + Request DTO
4. **페이징 응답**: `PageResponse.of(content, cursorPageable)`

### 인증이 필요한 API

```kotlin
val adminId = SecurityContextUtil.getAdminUserIdByContext()
.orElseThrow { UserException(UserExceptionCode.REQUIRED_USER_ID) }
```

### 서비스 의존

- 컨트롤러는 mono 모듈의 서비스를 직접 주입받아 사용
- 비즈니스 로직은 mono 모듈에 구현
- admin-api는 프레젠테이션 계층만 담당

### 테스트 작성 규칙

**통합 테스트** (`app/integration/{domain}/`):
- `IntegrationTestSupport` 상속
- `@Tag("admin_integration")` 태그
- `@Nested` 클래스로 API별 그룹화
- `mockMvcTester`로 API 호출 및 검증

**RestDocs 테스트** (`app/docs/{domain}/`):
- `@WebMvcTest(controllers = [...], excludeAutoConfiguration = [SecurityAutoConfiguration::class])`
- `@MockitoBean`으로 서비스 목킹
- `document()` 메서드로 API 문서 스니펫 생성

### 헬퍼 클래스

- **위치**: `app/helper/{domain}/`
- **형태**: `object` 싱글톤
- **용도**: 테스트 데이터 생성
- **네이밍**: `{도메인명}Helper`

```kotlin
object AlcoholsHelper {
fun createAdminAlcoholItem(
id: Long = 1L,
korName: String = "테스트 위스키"
): AdminAlcoholItem = ...
}
```

## 코드 작성 규칙
Expand Down Expand Up @@ -157,7 +312,7 @@ flowchart TD

- `@Tag("unit")`: 단위 테스트, `@Tag("integration")`: 통합 테스트, `@Tag("rule")`: 아키텍처 규칙
- 클래스명: `{기능명}ServiceTest`, 메서드명: `{기능명}할_수_있다`
- `@DisplayName`: 한글로 테스트 목적 명시
- `@DisplayName`: 한글로 테스트 목적 명시 (형식: `~할 때 ~한다`)

### 테스트 구조

Expand All @@ -166,6 +321,45 @@ flowchart TD
- TestContainers 사용 (실제 DB 환경)
- 테스트 데이터: `src/test/resources/init-script/` 디렉토리

### 단위 테스트 패턴

- **Fake/Stub 패턴 선호**: Mock 대신 InMemory 구현체 사용
- **네이밍**: `InMemory{도메인명}Repository`, `Fake{서비스명}`
- **위치**: `{도메인}.fixture` 패키지
- **이벤트 테스트**: `FakeApplicationEventPublisher`로 발행된 이벤트 검증

```java
// 예시: InMemory 레포지토리
public class InMemoryReviewRepository implements ReviewRepository {
private final Map<Long, Review> database = new HashMap<>();
// 도메인 레포지토리 인터페이스 구현
}
```

### 통합 테스트 패턴

- **베이스 클래스**: `IntegrationTestSupport` 상속
- **API 테스트**: `MockMvcTester` 사용, `extractData()` 메서드로 응답 추출
- **비동기 대기**: `Awaitility`로 이벤트 처리 대기
- **테스트 데이터 생성**: `{도메인명}TestFactory` 사용 (`@Autowired`)

```java
// 예시: 비동기 이벤트 대기
Awaitility.await()
.atMost(3, TimeUnit.SECONDS)
.untilAsserted(() -> {
List<ResourceLog> logs = repository.findByUserId(userId);
assertEquals(2, logs.size());
});
```

### 이벤트 기반 아키텍처

- **이벤트 발행**: `ApplicationEventPublisher.publishEvent()`
- **이벤트 수신**: `@TransactionalEventListener` + `@Async` 조합
- **트랜잭션 분리**: `@Transactional(propagation = Propagation.REQUIRES_NEW)`
- **이벤트 클래스**: `{도메인명}{동작}Event` record로 정의

## 데이터베이스 설계

### JPA 엔티티
Expand Down
20 changes: 20 additions & 0 deletions Dockerfile-batch
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM eclipse-temurin:21-jre
WORKDIR /app

ARG GIT_COMMIT=unknown
ARG GIT_BRANCH=unknown
ARG BUILD_TIME=unknown

ENV GIT_COMMIT=${GIT_COMMIT}
ENV GIT_BRANCH=${GIT_BRANCH}
ENV BUILD_TIME=${BUILD_TIME}
ENV TZ=Asia/Seoul

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN mkdir -p config

COPY bottlenote-batch/build/libs/bottlenote-batch.jar /app.jar

ENV SPRING_PROFILES_ACTIVE=default

ENTRYPOINT ["java", "-jar", "/app.jar"]
Loading
Loading