|
| 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 |
0 commit comments