This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Cupcake is a Kotlin/Ktor REST API backend for javaBin regional organizers to discover potential speakers for local events. It aggregates JavaZone conference/session data from Sleeping Pill and enriches speaker profiles with Norwegian postal code information from the Bring API. The frontend (Frosting) lives in a separate repository.
All commands run from the repository root (where gradlew lives).
./gradlew run # Run locally (port 8080)
./gradlew clean build # Build fat JAR → build/libs/cupcake.jar
./gradlew check # Lint (detekt + kotlinter) + tests + coverage
./gradlew test # Run all tests
./gradlew test --tests "ClassName" # Run a single test class
./gradlew test --tests "*.ClassName.test*" # Run tests matching a pattern
./gradlew lintKotlin # Kotlinter check only
./gradlew detekt # Detekt check onlyCreate a local.env file in the root (gitignored) for local development. Required vars:
| Variable | Description |
|---|---|
SP_BASE_URL |
Sleeping Pill base URL |
SP_USER / SP_PASSWORD |
Sleeping Pill credentials |
BRING_API_USER / BRING_API_KEY |
Bring API credentials (email + key) |
OIDC_WELL_KNOWN_URL |
OIDC discovery endpoint |
JWT_ENABLED |
Set to false to disable auth locally |
Optional: SP_CACHE_TTL_SECONDS (default 3600), PORT (default 8080).
plugins/— Ktor plugin configuration:Routing.kt,Security.kt,Monitoring.kt,HTTP.kt,Serialization.ktclients/— HTTP clients for Sleeping Pill and Bring APIsleepingpill/— Domain models (Conference,Session) andSleepingPillServicewith Caffeine async cachebring/—BringServicewith Cache4k postal code cache (24h TTL, refreshed hourly)api/—ApiErrorsealed class andRespond.ktextension functionsconfig/— Configuration data classes (loaded fromapplication.confvia HOCON)
All require JWT authentication with helter Cognito group membership (unless JWT_ENABLED=false):
GET /api/conferences— Conferences filtered by year, sorted by name descendingGET /api/conferences/{id}/sessions— Sessions enriched with speaker postal code/city/countyGET /api/me— Authenticated user info from Cognito UserInfo endpoint + token claimsGET /metrics-micrometer— Prometheus metrics (unauthenticated)
Error handling: Arrow's Either<ApiError, T> throughout. ApiError is a sealed class; RoutingContext.respond(error: ApiError) in Respond.kt maps errors to HTTP responses.
Caching: Sleeping Pill data uses Caffeine async cache (configurable TTL). Postal codes use Cache4k (24h TTL). Setting TTL ≤ 0 disables caching.
Security: JWT validated via Auth0 JWK provider against Cognito's JWKS. Requires helter group in cognito:groups claim. /api/me calls the Cognito UserInfo endpoint with the Bearer token to retrieve email and name; hasPkomRole is derived from cognito:groups containing pkom.
Serialization: kotlinx.serialization. Custom serializer for HttpStatusCode.
Tests use Kotest (FunSpec) with Ktor's test host. Key utilities in TestExtensions.kt:
buildClient()/buildMockEngine()— mocked HTTP client with fixture responsesserializedTestApplication()— test app with serialization and no-op authloadFixture()— loads JSON fromsrc/test/resources/
Fixtures live in src/test/resources/ (e.g., conferences.json, sessions.json, postal_codes.json).