한국어로 응답하고 작업해주세요 (Please respond and work in Korean).
DataGSM is a Spring Boot REST API service providing school information (students, clubs, meals, schedules) for Gwangju Software Meister High School. The system uses Google OAuth2 authentication with JWT token and API key management.
- Backend: Kotlin, Spring Boot 4.0, Spring Security, Spring Data JPA
- Database: MySQL (main data), Redis (caching, sessions)
- Query & Integration: QueryDSL for complex queries, OpenFeign for external APIs
- Serialization: Jackson 3.0 for JSON processing
- Testing: Kotest + MockK + JUnit 5 (Given-When-Then style)
datagsm-server/
├── datagsm-common/ # Shared library (Entity, DTO, Repository, Config, Health API)
├── datagsm-oauth-authorization/ # OAuth2 authentication, account lifecycle (signup, password reset)
├── datagsm-oauth-userinfo/ # OAuth2 UserInfo endpoint (external clients)
├── datagsm-openapi/ # Public read-only API (students, clubs, NEIS)
└── datagsm-web/ # Web service API (user features, admin features, Excel)
Each module follows: controller/, service/, repository/, entity/, dto/
Note: /v1/health endpoint is provided by HealthController in datagsm-common/global/controller/ and is shared across all modules.
- Build:
./gradlew build - Test:
./gradlew test - Format:
./gradlew ktlintFormat - Run:
./gradlew :<module>:bootRun
- Prefer
valovervar. Usevaronly when reassignment is strictly required. - Always use constructor injection — never
@Autowiredfield injection. - Use Kotlin null-safety features (
?.,?:) instead of!!. - Do NOT add excessive comments — only where logic is not self-evident.
- Jackson: always use
@field:target — never@param:(e.g.,@field:JsonProperty("user_name")) - Swagger/OpenAPI:
- Request DTOs (
*ReqDto): use@param:Schema - Response DTOs (
*ResDto): use@field:Schema
- Request DTOs (
- 1–2 query params: use
@RequestParam; 3+ or with validation: use@ModelAttribute+ DTO @RequestBodyvariable:reqDto;@ModelAttributequery:queryReq@Transactionalmust be at method level only — never class level- Read operations:
@Transactional(readOnly = true)/ Write operations:@Transactional - Use
CommonApiResponsewrapper for all API responses
- English only — verb-led sentences
- SLF4J
{}placeholder only — no Kotlin string interpolation, no colon separators - Correct:
logger().info("Deleted {} expired API keys", deletedCount) - Wrong:
logger().error("에러 발생: $message")orlogger().error("Failed: {}", msg)
- Use
ExpectedExceptiondirectly — do NOT subclass it - Message: Korean (합쇼체) + period, no dynamic data (IDs, names, variables)
- Correct:
ExpectedException("학생을 찾을 수 없습니다.", HttpStatus.NOT_FOUND) - Wrong:
ExpectedException("학생 ID: $id 없음", HttpStatus.NOT_FOUND)
Format: type(scope): 설명
- Types:
add/update/fix/refactor/ci/cd/docs/test/merge - Scope: domain name (
auth,student,club,application, etc.) — NOT module names - Cross-cutting only:
global,ci/cd, or module names (web,openapi,oauth) - Description: Korean, no period
- Avoid N+1 problems — use Fetch Join or
@EntityGraph - Use
@Transactional(readOnly = true)for read operations
- Write Kotest tests for business logic
- Use Kotest
DescribeSpecwithdescribe/context/itblocks - Use MockK for mocking; Given-When-Then structure inside
itblocks - Test names in Korean:
describe("클래스명 클래스의"),describe("메서드명 메서드는")
- This project uses Java 25 for Gradle builds
- Always check
.gitignoreand.geminiignorewhen suggesting file changes - When analyzing code, consider the multi-module structure