Palette Platform is an end-to-end, multi-platform color discovery and collection platform. It provides a production-grade REST API built with Kotlin and Spring Boot, a modern responsive web client with Vue 3 and TypeScript, and native mobile apps powered by Kotlin Multiplatform (KMP) sharing networking, business logic, and offline caching across Android (Jetpack Compose) and iOS (SwiftUI).
The platform is designed around a clean modular monolith backend and client apps:
identity: User registration, authentication, JWT access tokens, opaque refresh tokens with rotation and SHA-256 database hashing, logout, and current user retrieval.palette: Palette creation, editing, deletion, detail lookup, random palette discovery, and feed listing (newest, popular, tag filtering, name search, hex color filtering, pagination).favorite: Idempotent palette favoriting/unfavoriting, personal collections, atomic count updates preventing negative likes.moderation: Admin review queue (pending palettes), publish, reject, archive operations with an audit trail (moderation_logs).mobile (KMP): Shared Kotlin Multiplatform core (mobile/shared/) powering both native Android (Jetpack Compose) and native iOS (SwiftUI) applications with zero business logic duplication.web: Responsive Vue 3 + TypeScript SPA mirroring mobile design specifications with dark/light themes.shared: RFC 9457 ProblemDetail error handling, rate limiting filter, OpenAPI / Swagger documentation, Actuator health and metrics probes.
- Backend: Kotlin 2.0.21 on Java 17+, Spring Boot 3.4.3, Spring Data JPA, Hibernate, PostgreSQL, Flyway, Spring Security, JJWT
- Mobile (KMP): Kotlin Multiplatform (KMP), Ktor Client, Kotlinx Coroutines, Kotlinx Serialization, Android Jetpack Compose, iOS SwiftUI
- Web: Vue 3 (Composition API), TypeScript, Vite, Pinia, Vue Router
- Documentation: SpringDoc OpenAPI 3 / Swagger UI
- Testing: JUnit 5, MockK, MockMvc, Testcontainers PostgreSQL, Vitest, Playwright, XCTest
- Quality: Detekt static analysis, ESLint, Prettier
- Containers & CI/CD: Docker, Docker Compose, GitHub Actions, GitHub Container Registry (GHCR)
palette-platform/
├── backend/ Kotlin + Spring Boot REST API
├── web/ Vue.js 3 + TypeScript Client (Vite, Pinia)
├── mobile/ Mobile clients & Kotlin Multiplatform (KMP)
│ ├── shared/ Shared KMP Core (Ktor, domain models, use cases, offline cache)
│ ├── androidApp/ Android native client (Jetpack Compose, Material 3)
│ └── iosApp/ iOS native client (SwiftUI)
├── infrastructure/ Docker and deployment configuration
├── docs/ Product, mobile, and engineering documentation
└── .github/workflows/ Path-scoped CI and Release pipelines
| Variable | Default | Description |
|---|---|---|
PORT |
8080 |
Server HTTP listening port |
DB_URL |
jdbc:postgresql://localhost:5432/palette |
PostgreSQL JDBC connection URL |
DB_USERNAME |
palette |
Database username |
DB_PASSWORD |
palette |
Database password |
JWT_SECRET |
Development default string (32+ bytes) | Secret key for signing HMAC-SHA256 JWT tokens |
JWT_ACCESS_TOKEN_EXPIRATION_MINUTES |
15 |
Expiration lifetime for access tokens in minutes |
JWT_REFRESH_TOKEN_EXPIRATION_DAYS |
7 |
Expiration lifetime for refresh tokens in days |
Requirements: Java 17+ (JDK 17) and Docker (or local PostgreSQL).
- Start PostgreSQL:
docker compose -f infrastructure/docker/compose.yml up -d postgres- Start Spring Boot backend:
cd backend
./gradlew bootRun- Access Health and Swagger UI:
- Health probe:
curl http://localhost:8080/actuator/health - OpenAPI Specification:
http://localhost:8080/v3/api-docs - Swagger UI:
http://localhost:8080/swagger-ui.html
docker compose -f infrastructure/docker/compose.yml --profile full up --buildRun unit and web tests:
cd backend
./gradlew testRun static analysis with Detekt:
cd backend
./gradlew detektBuild executable JAR:
cd backend
./gradlew bootJarRun Testcontainers PostgreSQL integration tests:
cd backend
TESTCONTAINERS_ENABLED=true ./gradlew testBase path: /api/v1
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "designer@example.com",
"password": "Password123!",
"displayName": "ColorCrafter"
}'Response:
{
"accessToken": "eyJhbGciOi...",
"refreshToken": "xP93nK...",
"tokenType": "Bearer",
"user": {
"id": "c3f8e6c4-...",
"email": "designer@example.com",
"displayName": "ColorCrafter",
"role": "USER",
"createdAt": "2026-09-16T19:00:00Z"
}
}curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "designer@example.com",
"password": "Password123!"
}'curl -X POST http://localhost:8080/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refreshToken": "xP93nK..."
}'Palettes require exactly 4 valid, uppercase, non-duplicate HEX colors:
curl -X POST http://localhost:8080/api/v1/palettes \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"name": "Nordic Autumn",
"colors": ["#2E3440", "#4C566A", "#D8DEE9", "#ECEFF4"],
"tags": ["nordic", "cool", "minimal"],
"publish": true
}'Publicly query newest or popular palettes with optional search query, tag, or exact HEX color:
curl "http://localhost:8080/api/v1/palettes?sort=popular&tag=minimal&page=0&size=20"# Favorite
curl -X POST http://localhost:8080/api/v1/palettes/<PALETTE_ID>/favorite \
-H "Authorization: Bearer <ACCESS_TOKEN>"
# Unfavorite
curl -X DELETE http://localhost:8080/api/v1/palettes/<PALETTE_ID>/favorite \
-H "Authorization: Bearer <ACCESS_TOKEN>"# List pending palettes
curl http://localhost:8080/api/v1/admin/palettes/pending \
-H "Authorization: Bearer <ADMIN_TOKEN>"
# Publish pending palette
curl -X POST http://localhost:8080/api/v1/admin/palettes/<PALETTE_ID>/publish \
-H "Authorization: Bearer <ADMIN_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"reason": "Meets quality standards"}'- Port in use: Change
PORTin environment variables or command line (-DPORT=8081). - Database connection error: Verify Docker is running and PostgreSQL is ready (
pg_isready -U palette -d palette). - Authentication failure: Ensure the Authorization header starts with
Bearer. - Invalid palette submission: Ensure all four colors are distinct 6-character hex strings with
#prefix.