|
1 | 1 | # Cupcake |
2 | 2 |
|
3 | | -A minimal server providing a read-only version of [cake](https://github.com/javaBin/cake-redux) for javaBin regions to |
4 | | -be able to find possible speakers for local talks. |
| 3 | +A Kotlin/Ktor backend that aggregates JavaZone conference and session data from [Sleeping Pill](https://sleepingpill.javazone.no) and enriches speaker profiles with Norwegian postal code data from the Bring API. It serves as the API backend for [frosting](https://github.com/javaBin/frosting), enabling javaBin regions to discover potential speakers for local events. |
5 | 4 |
|
6 | | -For frontend - see [frosting](https://github.com/javaBin/frosting) |
| 5 | +## Tech stack |
7 | 6 |
|
8 | | -## Build |
| 7 | +- **Language**: Kotlin (JVM 22) |
| 8 | +- **Framework**: Ktor |
| 9 | +- **Build**: Gradle (Kotlin DSL) |
| 10 | +- **Caching**: Caffeine (conference/session data) + Cache4k (postal codes) |
| 11 | +- **Auth**: OIDC/JWT via configurable discovery endpoint |
| 12 | +- **Metrics**: Micrometer + Prometheus |
9 | 13 |
|
10 | | -Gradle application using ktor. |
| 14 | +## API endpoints |
11 | 15 |
|
12 | | -## Local running |
| 16 | +| Method | Path | Description | |
| 17 | +|--------|------|-------------| |
| 18 | +| `GET` | `/api/conferences` | Lists conferences (filtered by year, sorted by name descending) | |
| 19 | +| `GET` | `/api/conferences/{id}/sessions` | Sessions for a conference, with speaker postal code/city/county data | |
| 20 | +| `GET` | `/api/me` | Authenticated user info from OIDC token | |
13 | 21 |
|
14 | | -You will require env variables: |
| 22 | +Additional endpoints (`/login`, `/refresh`) are handled by the OIDC layer. |
15 | 23 |
|
16 | | - SP_BASE_URL=https://sleepingpill.javazone.no |
17 | | - SP_USER=<username> |
18 | | - SP_PASSWORD=<password> |
19 | | - BRING_API_KEY=<key> |
20 | | - BRING_API_USER=<e-mail> |
21 | | - OIDC_WELL_KNOWN_URL=<OIDC discovery endpoint URL, e.g. https://auth.example.com/realms/myrealm/.well-known/openid-configuration> |
22 | | - OIDC_EXPECTED_AZP=<expected client ID - defaults to "cupcake-client"> |
23 | | - JWT_ENABLED=true/false |
| 24 | +## Build |
24 | 25 |
|
25 | | -The frontend OIDC authority and client ID can be overridden via `NUXT_PUBLIC_OIDC_AUTHORITY` and |
26 | | -`NUXT_PUBLIC_OIDC_CLIENT_ID` (see [frontend README](https://github.com/javaBin/frosting/README.md)). They default to the |
27 | | -development Keycloak realm and client, so no change is needed for local dev against that environment. |
| 26 | +```bash |
| 27 | +./gradlew clean build |
| 28 | +``` |
28 | 29 |
|
29 | | -If you are not running with auth (`JWT_ENABLED=false`) then localhost is fine. |
| 30 | +Code quality checks (Detekt, Kotlinter, JaCoCo coverage) run as part of `check`: |
30 | 31 |
|
31 | | -## Local running with docker compose |
| 32 | +```bash |
| 33 | +./gradlew check |
| 34 | +``` |
32 | 35 |
|
33 | | -You will need to provide the same environment variables as above in a file called `local.env` in the root directory. |
| 36 | +The output artifact is `build/libs/cupcake.jar` (fat JAR with all dependencies bundled). |
34 | 37 |
|
35 | | -Note - this file is also useful when running from Idea with the envfile plugin. |
| 38 | +## Local running |
36 | 39 |
|
37 | | -This file MUST NOT be committed to git (it is in .gitignore). |
| 40 | +Set the following environment variables (a `local.env` file in the root is gitignored and works well with the IntelliJ IDEA EnvFile plugin): |
38 | 41 |
|
39 | | -## Deploy |
| 42 | +| Variable | Description | |
| 43 | +|---|---| |
| 44 | +| `SP_BASE_URL` | Sleeping Pill base URL (e.g. `https://sleepingpill.javazone.no`) | |
| 45 | +| `SP_USER` | Sleeping Pill username | |
| 46 | +| `SP_PASSWORD` | Sleeping Pill password | |
| 47 | +| `BRING_API_KEY` | Bring API key | |
| 48 | +| `BRING_API_USER` | Bring API user (email) | |
| 49 | +| `OIDC_WELL_KNOWN_URL` | OIDC discovery endpoint (e.g. `https://auth.example.com/realms/myrealm/.well-known/openid-configuration`) | |
| 50 | +| `OIDC_EXPECTED_AZP` | Expected OIDC client ID (defaults to `cupcake-client`) | |
| 51 | +| `JWT_ENABLED` | `true` to enforce JWT authentication, `false` to disable | |
40 | 52 |
|
41 | | -Assuming we will build a docker container - add to [backend action](./.github/workflows/backend.yaml) when decided. |
| 53 | +Then run: |
42 | 54 |
|
43 | | -Currently it is setup for the frontend to proxy the backend - anything on `/api/*` |
| 55 | +```bash |
| 56 | +./gradlew run |
| 57 | +``` |
44 | 58 |
|
45 | | -For example - let's say we setup: |
| 59 | +The server starts on port 8080. With `JWT_ENABLED=false`, no auth is required and `localhost` is fine. |
46 | 60 |
|
47 | | - https://cupcake_backend.javazone.no -> backend |
48 | | - https://cupcake.javazone.no -> frontend |
| 61 | +## Docker |
49 | 62 |
|
50 | | -We would then need to set the host in the frontend for non development builds to `https://cupcake_backend.javazone.no` in |
51 | | -the [proxy](https://github.com/javaBin/frosting/server/middleware/proxy.ts) file - but this is set using the CUPCAKE_BACKEND env var. |
| 63 | +Multi-platform images (`linux/amd64`, `linux/arm64`) are published to `ghcr.io/javabin/cupcake`. |
52 | 64 |
|
53 | | -All app configuration for the backend is done via the environment. |
| 65 | +To build locally: |
54 | 66 |
|
55 | | -If deploying with docker - you can place both on the same docker network and use the service name for the env var. |
| 67 | +```bash |
| 68 | +docker build -t cupcake . |
| 69 | +``` |
56 | 70 |
|
57 | | -### JWT |
| 71 | +The image uses a multi-stage build (Eclipse Temurin JDK 22 build stage, JRE runtime stage) and runs on port 8080. |
| 72 | + |
| 73 | +## CI/CD |
| 74 | + |
| 75 | +| Trigger | Workflow | What it does | |
| 76 | +|---|---|---| |
| 77 | +| Push to `main` | `build.yaml` | Runs `check`, builds and pushes multi-platform Docker image, tags as `staging` | |
| 78 | +| Pull request | `pr.yaml` | Runs `check` (tests, linting, analysis) | |
| 79 | +| Tag `v*` | `release.yaml` | Promotes `staging` image to `release` and version tag | |
| 80 | + |
| 81 | +## Deploy |
58 | 82 |
|
59 | | - JWT_ENABLED - true |
| 83 | +Example hostnames: |
60 | 84 |
|
61 | | -### Sleepingpill |
| 85 | +``` |
| 86 | +https://cupcake-backend.java.no → backend (this service) |
| 87 | +https://cupcake.java.no → frontend (frosting) |
| 88 | +``` |
62 | 89 |
|
63 | | -We use the same user and password for dev and deploy here but it must be set in the environment. |
| 90 | +If deploying with Docker Compose or a shared Docker network, or inside kubernetes with access, use the service name as the `CUPCAKE_BACKEND` value. |
64 | 91 |
|
65 | | - SP_USER |
66 | | - SP_PASSWORD |
| 92 | +### Environment variables for deployment |
67 | 93 |
|
68 | | -### Bring |
| 94 | +#### JWT / OIDC (backend) |
69 | 95 |
|
70 | | -We use the same user and password for dev and deploy here but it must be set in the environment. |
| 96 | +| Variable | Value | |
| 97 | +|---|---| |
| 98 | +| `JWT_ENABLED` | `true` | |
| 99 | +| `OIDC_WELL_KNOWN_URL` | OIDC discovery endpoint | |
| 100 | +| `OIDC_EXPECTED_AZP` | Expected client ID (defaults to `cupcake-client`) | |
71 | 101 |
|
72 | | - BRING_API_USER |
73 | | - BRING_API_KEY |
| 102 | +Users must have the `pkom` role assigned in the OIDC provider under the client specified by `OIDC_EXPECTED_AZP`. |
74 | 103 |
|
75 | | -### OIDC |
| 104 | +#### OIDC (frontend — must match backend) |
76 | 105 |
|
77 | | -This provides authentication and access checking. Users must have the `pkom` role assigned |
78 | | -in the OIDC provider under the client specified by `OIDC_EXPECTED_AZP`. |
| 106 | +| Variable | Description | |
| 107 | +|---|---| |
| 108 | +| `NUXT_PUBLIC_OIDC_AUTHORITY` | OIDC authority URL (e.g. `https://auth.example.com/realms/myrealm`) | |
| 109 | +| `NUXT_PUBLIC_OIDC_CLIENT_ID` | OIDC client ID (defaults to `cupcake-client`) | |
79 | 110 |
|
80 | | - OIDC_WELL_KNOWN_URL - the OIDC discovery endpoint (e.g. https://auth.example.com/realms/myrealm/.well-known/openid-configuration) |
81 | | - OIDC_EXPECTED_AZP - the expected client ID (defaults to "cupcake-client") |
| 111 | +See the [frosting README](https://github.com/javaBin/frosting/README.md) for full frontend configuration. |
82 | 112 |
|
83 | | -The backend fetches the JWKS from the discovery document and validates incoming tokens against it. |
| 113 | +#### Sleeping Pill |
84 | 114 |
|
85 | | -The frontend OIDC settings are configured via environment variables and must be kept in sync with |
86 | | -the backend `OIDC_WELL_KNOWN_URL` and `OIDC_EXPECTED_AZP` settings: |
| 115 | +| Variable | Description | |
| 116 | +|---|---| |
| 117 | +| `SP_BASE_URL` | Sleeping Pill base URL | |
| 118 | +| `SP_USER` | Username | |
| 119 | +| `SP_PASSWORD` | Password | |
87 | 120 |
|
88 | | - NUXT_PUBLIC_OIDC_AUTHORITY - the OIDC authority URL (e.g. https://auth.example.com/realms/myrealm) |
89 | | - NUXT_PUBLIC_OIDC_CLIENT_ID - the OIDC client ID (defaults to "cupcake-client") |
| 121 | +#### Bring |
90 | 122 |
|
| 123 | +| Variable | Description | |
| 124 | +|---|---| |
| 125 | +| `BRING_API_USER` | Bring API user (email) | |
| 126 | +| `BRING_API_KEY` | Bring API key | |
0 commit comments