Skip to content

Commit 87e25d3

Browse files
authored
Refect/be/120 : 이제 테스트 실행하도록 변경 (#120) (#121)
* refect/be : 이제 배포 시 테스트 코드 실행 * refect/be : 이제 배포 시 테스트 코드 실행
1 parent 0a74a68 commit 87e25d3

File tree

2 files changed

+254
-2
lines changed

2 files changed

+254
-2
lines changed

CLAUDE.md

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Korean Travel Guide Backend - A Spring Boot Kotlin application providing OAuth authentication, AI chatbot, user-to-user chat, and rating systems for connecting tourists with guides in Korea.
8+
9+
**Architecture**: Domain-Driven Design (DDD)
10+
**Tech Stack**: Spring Boot 3.4.1, Kotlin 1.9.25, Spring AI 1.1.0-M2, PostgreSQL/H2
11+
12+
## Essential Commands
13+
14+
### Build & Run
15+
```bash
16+
# Run the application
17+
./gradlew bootRun
18+
19+
# Build without tests
20+
./gradlew build -x test
21+
22+
# Run all tests
23+
./gradlew test
24+
25+
# Run specific test
26+
./gradlew test --tests "WeatherServiceTest"
27+
```
28+
29+
### Code Quality
30+
```bash
31+
# Check Kotlin code style (MUST pass before commit)
32+
./gradlew ktlintCheck
33+
34+
# Auto-format Kotlin code
35+
./gradlew ktlintFormat
36+
37+
# Setup git hooks (first time only)
38+
./setup-git-hooks.sh # Linux/Mac
39+
setup-git-hooks.bat # Windows
40+
```
41+
42+
### Development
43+
```bash
44+
# Access Swagger UI
45+
open http://localhost:8080/swagger-ui.html
46+
47+
# Access H2 Console (dev database)
48+
open http://localhost:8080/h2-console
49+
# JDBC URL: jdbc:h2:mem:testdb
50+
# Username: sa
51+
# Password: (empty)
52+
53+
# Health check
54+
curl http://localhost:8080/actuator/health
55+
```
56+
57+
### Redis (Optional - for caching)
58+
```bash
59+
# Start Redis with Docker
60+
docker run -d --name redis -p 6379:6379 redis:alpine
61+
62+
# Stop Redis
63+
docker stop redis
64+
65+
# The app runs without Redis (session.store-type: none in dev)
66+
```
67+
68+
## Architecture & Domain Structure
69+
70+
The codebase follows Domain-Driven Design with clear separation:
71+
72+
### Domain Packages
73+
```
74+
com/back/koreaTravelGuide/
75+
├── common/ # Shared infrastructure
76+
│ ├── config/ # App, Security, Redis, AI configs
77+
│ ├── security/ # OAuth2, JWT, filters
78+
│ ├── exception/ # Global exception handler
79+
│ └── ApiResponse.kt # Standard API response wrapper
80+
├── domain/
81+
│ ├── user/ # User management (GUEST, GUIDE, ADMIN)
82+
│ ├── auth/ # OAuth login endpoints
83+
│ ├── ai/
84+
│ │ ├── aiChat/ # AI chatbot sessions (Spring AI + OpenRouter)
85+
│ │ ├── weather/ # Weather API integration (KMA)
86+
│ │ └── tour/ # Tourism API integration
87+
│ ├── userChat/ # WebSocket chat between Guest-Guide
88+
│ │ ├── chatroom/ # Chat room management
89+
│ │ └── chatmessage/ # Message persistence
90+
│ └── rate/ # Rating system for AI sessions & guides
91+
```
92+
93+
### Key Architectural Patterns
94+
95+
1. **Each domain is self-contained** with its own entity, repository, service, controller, and DTOs
96+
2. **Common utilities live in `common/`** - never duplicate config or security logic
97+
3. **AI Chat uses Spring AI** with function calling for weather/tour tools
98+
4. **User Chat uses WebSocket** (STOMP) for real-time messaging
99+
5. **Global exception handling** via `GlobalExceptionHandler.kt` - just throw exceptions, they're caught automatically
100+
101+
### Critical Configuration Files
102+
103+
- **build.gradle.kts**: Contains BuildConfig plugin that generates constants from YAML files (area-codes.yml, prompts.yml, etc.)
104+
- **application.yml**: Dev config with H2, Redis optional, OAuth2 providers
105+
- **SecurityConfig.kt**: Currently allows all requests for dev (MUST restrict for production)
106+
- **AiConfig.kt**: Spring AI ChatClient with OpenRouter (uses OPENROUTER_API_KEY env var)
107+
108+
## Working with Spring AI
109+
110+
The AI chatbot uses Spring AI 1.1.0-M2 with **function calling** for weather and tour data:
111+
112+
```kotlin
113+
// Tools are automatically called by AI when needed
114+
// Located in: domain/ai/weather & domain/ai/tour
115+
@Tool(description = "Get weather forecast")
116+
fun getWeatherForecast(city: String): WeatherResponse
117+
118+
@Tool(description = "Get tourist spots")
119+
fun getTourSpots(area: String): TourResponse
120+
```
121+
122+
**Important**: System prompts are managed in `src/main/resources/prompts.yml` and compiled into BuildConfig at build time.
123+
124+
## Testing Strategy
125+
126+
Tests are in `src/test/kotlin` mirroring the main structure:
127+
128+
- **Unit tests**: Mock services, test business logic
129+
- **Integration tests**: Use `@SpringBootTest` with H2 in-memory database
130+
- **Controller tests**: Use `@WebMvcTest` with MockMvc
131+
- **Example**: `WeatherApiClientTest.kt` - uses MockMvc with mocked service layer
132+
133+
Always run `./gradlew ktlintCheck` before committing - it's enforced by git hooks.
134+
135+
## Security & Authentication
136+
137+
### OAuth2 Flow
138+
1. User initiates OAuth (Google/Kakao/Naver)
139+
2. `CustomOAuth2UserService` processes OAuth response
140+
3. `CustomOAuth2LoginSuccessHandler` creates JWT tokens
141+
4. JWT stored in HTTP-only cookie
142+
5. `JwtAuthenticationFilter` validates requests
143+
144+
### JWT Configuration
145+
- Access token: 60 minutes (configurable via `JWT_ACCESS_TOKEN_EXPIRATION_MINUTES`)
146+
- Refresh token: 7 days (configurable via `JWT_REFRESH_TOKEN_EXPIRATION_DAYS`)
147+
- Secret key: MUST set `CUSTOM__JWT__SECRET_KEY` in production
148+
149+
**Dev Mode**: All endpoints are currently permitAll() - restrict before deployment!
150+
151+
## Database Schema
152+
153+
- **Development**: H2 in-memory (jdbc:h2:mem:testdb), resets on restart
154+
- **Production**: PostgreSQL (configured in application-prod.yml)
155+
- **JPA Strategy**: `ddl-auto: create-drop` in dev (wipes DB on restart)
156+
157+
Main entities:
158+
- `User` - OAuth users with roles (GUEST/GUIDE/ADMIN)
159+
- `AiChatSession` / `AiChatMessage` - AI conversation history
160+
- `ChatRoom` / `ChatMessage` - User-to-user messaging
161+
- `Rate` - Ratings for AI sessions or guides
162+
163+
## Environment Variables
164+
165+
Required `.env` file (copy from .env.example):
166+
```bash
167+
# AI (Required)
168+
OPENROUTER_API_KEY=sk-or-v1-...
169+
OPENROUTER_MODEL=anthropic/claude-3.5-sonnet
170+
171+
# OAuth (Required for auth)
172+
GOOGLE_CLIENT_ID=...
173+
GOOGLE_CLIENT_SECRET=...
174+
KAKAO_CLIENT_ID=...
175+
KAKAO_CLIENT_SECRET=...
176+
NAVER_CLIENT_ID=...
177+
NAVER_CLIENT_SECRET=...
178+
179+
# APIs (Required for AI tools)
180+
WEATHER__API__KEY=...
181+
TOUR_API_KEY=...
182+
183+
# JWT (Required for production)
184+
CUSTOM__JWT__SECRET_KEY=...
185+
186+
# Redis (Optional - caching)
187+
REDIS_HOST=localhost
188+
REDIS_PORT=6379
189+
REDIS_PASSWORD=
190+
```
191+
192+
## API Response Standards
193+
194+
All controllers return `ApiResponse<T>`:
195+
```kotlin
196+
// Success
197+
ApiResponse(msg = "Success", data = userDto)
198+
// Returns: {"msg": "Success", "data": {...}}
199+
200+
// Error (handled automatically)
201+
throw IllegalArgumentException("Invalid input")
202+
// Returns: {"msg": "Invalid input", "data": null}
203+
```
204+
205+
Common exceptions mapped to HTTP status:
206+
- `IllegalArgumentException` → 400 Bad Request
207+
- `NoSuchElementException` → 404 Not Found
208+
- `AccessDeniedException` → 403 Forbidden
209+
- Others → 500 Internal Server Error
210+
211+
## Development Workflow
212+
213+
1. **Create branch**: `{type}/{scope}/{issue-number}` (e.g., `feat/be/42`)
214+
2. **Make changes**: Follow DDD structure - put code in correct domain
215+
3. **Format code**: `./gradlew ktlintFormat`
216+
4. **Test**: `./gradlew test`
217+
5. **Commit**: `{type}(scope): summary` (e.g., `feat(be): Add weather caching`)
218+
6. **PR title**: `{type}(scope): summary (#{issue})` (e.g., `feat(be): Add weather caching (#42)`)
219+
220+
## Common Issues & Solutions
221+
222+
### Build fails with "BuildConfig not found"
223+
- Run `./gradlew build` to generate BuildConfig.kt from YAML files
224+
- BuildConfig is gitignored - regenerates on every build
225+
226+
### Redis connection errors
227+
- Redis is optional in dev (session.store-type: none)
228+
- Start Redis: `docker run -d -p 6379:6379 --name redis redis:alpine`
229+
- Check: `docker logs redis`
230+
231+
### ktlint failures
232+
- Auto-fix: `./gradlew ktlintFormat`
233+
- Pre-commit hook enforces this - setup via `./setup-git-hooks.sh`
234+
235+
### Spring AI errors
236+
- Verify `OPENROUTER_API_KEY` in .env
237+
- Check model name matches OpenRouter API (currently: anthropic/claude-3.5-sonnet)
238+
- Logs show AI requests: `logging.level.org.springframework.ai: DEBUG`
239+
240+
### OAuth login fails
241+
- Ensure all OAuth credentials in .env
242+
- Check redirect URIs match OAuth provider settings
243+
- Dev: `http://localhost:8080/login/oauth2/code/{provider}`
244+
245+
## Important Notes
246+
247+
- **Never commit .env** - it's gitignored, contains secrets
248+
- **ktlint is enforced** - setup git hooks on first clone
249+
- **H2 data is ephemeral** - resets on every restart in dev
250+
- **Global config in common/** - don't duplicate security/config in domains
251+
- **BuildConfig is generated** - don't edit manually, modify YAML sources
252+
- **Redis is optional in dev** - required for production caching/sessions

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ RUN gradle dependencies --no-daemon
1717
COPY .env .
1818
COPY src src
1919

20-
# 애플리케이션 빌드 (테스트 및 ktlint 스킵)
21-
RUN gradle build -x test -x ktlintTestSourceSetCheck -x ktlintMainSourceSetCheck -x ktlintKotlinScriptCheck --no-daemon
20+
# 애플리케이션 빌드 (ktlint 스킵, 테스트 실행)
21+
RUN gradle build -x ktlintTestSourceSetCheck -x ktlintMainSourceSetCheck -x ktlintKotlinScriptCheck --no-daemon
2222

2323
# 두 번째 스테이지: 실행 스테이지
2424
FROM container-registry.oracle.com/graalvm/jdk:21

0 commit comments

Comments
 (0)