Gamma is a membership and identity management system built for Chalmers IT student division. It serves as the central directory for members, groups, OAuth2 clients, and API keys.
- Language: Java 25
- Framework: Spring Boot 4.0.6
- Build: Gradle 9.5.1 (wrapper)
- Database: PostgreSQL 16+ with Flyway migrations
- Cache/Sessions: Redis 7+
- UI: Thymeleaf + HTMX + Pico CSS
- Auth: Spring Security, OAuth2 Authorization Server, API keys
- JDK 25 (Temurin recommended)
- Docker (for Testcontainers dev services and E2E tests)
- Node.js 20+ and pnpm (for E2E tests)
In bash:
# Starts Spring Boot with auto-started PostgreSQL and Redis via Testcontainers
DEV_SERVICES_ENABLED=true PRODUCTION=false ./gradlew bootRunIn PowerShell:
$env:DEV_SERVICES_ENABLED="true"; $env:PRODUCTION="false"; ./gradlew bootRunThe app starts on http://localhost:8081. On first run, an admin user is auto-created (credentials logged to stdout).
export DB_HOST=localhost DB_PORT=5432 DB_NAME=gamma \
DB_USER=gamma DB_PASSWORD=secret \
REDIS_HOST=localhost REDIS_PORT=6379 \
PRODUCTION=false
./gradlew bootRunmake build-image # tags as gamma-app:test
# or
./gradlew bootBuildImage && docker image tag app:latest gamma-app:testInstall the test dependencies and Chromium before the first run (and reinstall Chromium after updating Playwright):
(cd e2e && pnpm install && pnpm exec playwright install --with-deps chromium)Then, from the repository root:
make e2e # builds image then runs tests
# or with a specific version from GHCR
make test-e2e GAMMA_VERSION=1.2.3When developing E2E tests, after the setup above and with gamma-app:test already built or pulled:
cd e2e && pnpm testAll configuration is environment-variable-driven via application.yml.
| Variable | Default | Description |
|---|---|---|
SERVER_PORT |
8081 |
HTTP port |
BASE_URL |
http://localhost:8081 |
Public base URL (used for redirect URIs, etc.) |
DB_HOST |
db |
PostgreSQL host |
DB_PORT |
5432 |
PostgreSQL port |
DB_NAME |
postgres |
Database name |
DB_USER |
postgres |
Database user |
DB_PASSWORD |
postgres |
Database password |
REDIS_HOST |
redis |
Redis host |
REDIS_PORT |
6379 |
Redis port |
REDIS_PASSWORD |
(empty) | Redis password |
PRODUCTION |
true |
Production mode (disables Thymeleaf caching, dev services, etc.) |
DEV_SERVICES_ENABLED |
false |
Auto-start PostgreSQL + Redis via Testcontainers |
IS_MOCKING |
false |
Load mock data on startup |
MOCK_DATA_RESOURCE |
classpath:/mock/mock.json |
Path to mock data JSON |
ADMIN_SETUP |
true |
Auto-create admin user on first run |
UPLOAD_FOLDER |
./uploads/ |
File upload directory |
GOTIFY_KEY |
123abc |
Gotify pre-shared key |
GOTIFY_BASE_URL |
http://gotify:80 |
Gotify service URL |
SESSION_TIMEOUT |
43200 |
Session timeout in seconds |
ROOT_DEBUG_LEVEL |
INFO |
Root log level |
SERVER_SSL_ENABLED |
(varies) | Enable HTTPS |
Gamma issues scoped API keys for service-to-service access. On first startup with mock data, example keys are created and logged.
| Key Type | Purpose |
|---|---|
INFO |
Read public info (/api/info/v1): groups, super groups, members |
ACCOUNT_SCAFFOLD |
Google account sync data (/api/account-scaffold/v1) |
ALLOW_LIST |
Registration allow-list management (/api/allow-list/v1) |
CLIENT |
OAuth2 client-level API access |
When developing a service that consumes Gamma APIs, obtain the appropriate key type and pass its ID and token via the Authorization header using the pre-shared scheme:
GET /api/info/v1/groups
Authorization: pre-shared <api_key_id>:<api_key_token>Google Java Format is enforced by Spotless. Run before committing:
./gradlew spotlessApplyFlyway migrations live in app/src/main/resources/db/migration/. Migration files follow the V<version>__<description>.sql naming convention.
- Domain records are immutable Java records with
@RecordBuilderfor builders. - JPA entities live in
adapter/secondary/jpa/and are mapped to/from domain records via entity converters.
To validate entity mappings against the Flyway migrations, start the application with development services:
DEV_SERVICES_ENABLED=true PRODUCTION=false ./gradlew bootRunWait for successful application startup. Flyway applies the migrations to the development database, then Hibernate validates the entity mappings (ddl-auto: validate). ./gradlew build compiles and checks the project but does not start the application or validate the database schema.
- Create a domain service in the appropriate
app/sub-package (e.g.app/user/). - Create an adapter in
adapter/primary/api/(@RestControllerwith request/response DTOs). - Wire in the facade and use
AccessGuardfor authorization. - Add E2E tests in
e2e/tests/api/.
- Create a Thymeleaf controller in
adapter/primary/web/. - Create templates in
app/src/main/resources/templates/. - Use HTMX for dynamic partial updates.
Gamma follows Domain-Driven Design with a hexagonal (ports & adapters) architecture.
src/main/java/it/chalmers/gamma/
├── GammaApplication.java # Spring Boot entry point
├── BootstrapRunner.java # Startup bootstrapper (admin setup, mock data)
├── ScheduledTasks.java # Periodic cleanup tasks
├── adapter/
│ ├── primary/ # Inbound ports (driving adapters)
│ │ ├── api/ # REST controllers
│ │ ├── web/ # Thymeleaf MVC controllers
│ │ └── images/ # Image serving controller
│ └── secondary/ # Outbound ports (driven adapters)
│ ├── jpa/ # JPA persistence (entities, repositories, converters)
│ ├── redis/ # Redis-backed OAuth2 authorization store
│ ├── image/ # Local filesystem image service
│ └── mail/ # Gotify-based notification service
├── app/ # Domain + application services
│ ├── Facade.java # Base facade with AccessGuard support
│ ├── <domain>/ # Each bounded context has:
│ │ ├── domain/ # Pure domain records and interfaces
│ │ └── <Domain>Facade.java # Application service orchestrating domain + adapters
│ ├── user/ # User management
│ ├── group/ # Group management
│ ├── supergroup/ # Super group management
│ ├── post/ # Post/role management
│ ├── apikey/ # API key management
│ ├── client/ # OAuth2 client management
│ ├── oauth2/ # OAuth2 business logic
│ ├── authentication/ # Authentication domain
│ ├── admin/ # Admin operations
│ ├── image/ # Image domain
│ ├── mail/ # Mail domain
│ ├── throttling/ # Rate-limit management
│ ├── validation/ # Shared validation
│ └── common/ # Shared value objects / utilities
├── bootstrap/ # Seed data bootstrapping
└── security/ # Spring Security config, filters, JWK
- Domain records: Business objects are immutable Java records with
@RecordBuilder. - Facades: Each domain has a Facade that orchestrates domain logic and adapters, with
AccessGuardfor authorization. - AccessGuard: Composable authorization checks (
isAdmin(),isMe(),isSignedIn(),isApi(), etc.). - Separated entity model: JPA entities are distinct from domain records; entity converters handle mapping.
- AbstractEntity hierarchy:
ImmutableEntity/MutableEntitybase classes with automaticcreated_attimestamps.
There are currently no Java unit/integration tests. Spotless formatting checks run via ./gradlew check.
The E2E suite lives in e2e/ and uses Playwright with Testcontainers to orchestrate PostgreSQL, Redis, Gotify, and Gamma Docker containers.
Complete the E2E setup, including installing Chromium and building or pulling gamma-app:test, before running these commands from the repository root.
# Run all E2E tests
cd e2e && pnpm test
# Run with UI mode
cd e2e && pnpm test:ui
# Checks
cd e2e && pnpm run format:check && pnpm run typecheck && pnpm run lintTest files follow the **/*.it.ts naming convention:
tests/api/: API-level integration teststests/ui/: Browser-level UI tests
- GitHub: https://github.com/cthit/gamma
- Docker images:
ghcr.io/cthit/gamma
Zero-Clause BSD (0BSD). See LICENSE.