Skip to main content

Project Overview — Renault Club Bulgaria Rewrite

What is this?

Renault Club Bulgaria (RCB) is an online platform for the largest Renault fan club in Bulgaria. This is a full rewrite of the legacy Spring Boot 2.1 / Java 11 / Thymeleaf / MySQL monolith into a modern, maintainable architecture.


Architecture

Backend — Maven Multi-Module

rcb/                        ← root POM (BOM imports, plugins)
├── api/ ← OpenAPI YAML specs + generated Java interfaces
├── core/ ← Business logic (services, mappers, domain, security config)
├── rest/ ← HTTP layer (controllers, exception advice, Spring Boot entry point)
├── persistence/ ← JPA entities, repositories, Liquibase changesets
└── aggregate-report/ ← JaCoCo aggregated coverage report

Module dependency rules (enforced by ArchUnit):

rest     → core, api, persistence
core → api, persistence
api → (no internal deps)
persistence → (no internal deps)

Frontend — React SPA

frontend/
├── src/app/ ← Router, providers (Query, Keycloak, Theme)
├── src/features/ ← One folder per domain (events, news, etc.)
├── src/shared/ ← Shared components, hooks, utils
├── src/theme/ ← MUI theme tokens
└── src/keycloak-theme/ ← Keycloakify custom auth UI

Tech Stack

ConcernChoiceReason
LanguageJava 21Virtual threads, records, pattern matching
FrameworkSpring Boot 3.5.xLTS, native support, Spring Security 6
AuthKeycloak 26.xSSO, MFA, brute-force protection, email verification
API ContractOpenAPI 3.1 (YAML-first)Generated interfaces prevent drift
DBPostgreSQL 16Full text search, JSONB, production-grade
MigrationsLiquibaseVersioned, repeatable, CI-safe
DTO MappingMapStructCompile-time, type-safe, no reflection
SecretsJASYPTEncrypted ENC(...) values in YAML
HTTP ClientsSpring Cloud OpenFeignDeclarative, testable
SchedulingShedLockDistributed locks on scheduled tasks
Error FormatRFC 7807 Problem+JSONStandard, machine-readable errors
FrontendReact 19 + TypeScript strictCurrent LTS
UIMUI v6Consistent, accessible, RCB-branded
StateTanStack Query v5 + Zustand v5Server state + client state separation
BuildViteFast HMR
Testing (BE)JUnit 5, Testcontainers, ArchUnitReal DB, module boundary validation
Testing (FE)Vitest + PlaywrightUnit + E2E
Local StorageMinIOS3-compatible; replaces Cloudinary on local profile
Local EmailMailpitSMTP trap + web UI; replaces SendGrid on local profile
InfrastructureDocker Compose + Traefik v3Hetzner VPS, single-server
CI/CDGitHub Actions (free tier) + GHCRNo paid features
ObservabilityMicrometer + OpenTelemetry + PrometheusStandard stack

Domain Model

Core Entities

EntityKey FieldsRelationships
UserEntitykeycloakSubject, email, firstName, lastName, phoneNumber, lastLoginOneToOne→MembershipCardEntity, ManyToMany→RoleEntity, ManyToMany→EventEntity
EventEntityname, description, organizer, location, date, fee, statusManyToMany↔User, OneToMany→Sponsor, OneToMany→Application
NewsEntitytitle, subTitle, description, source, views, issuedOn, categoryManyToOne→User (author)
CampaignEntitytitle, description, startDate, endDateOneToMany→Sponsor, OneToMany→ClubProduct
MembershipCardEntitytype (NORMAL/SILVER/GOLDEN), validFrom, validUntil; computed isActive()FK on users.membership_card_id
PartnerEntityname, description, logoUrl, website, typeOneToMany→Advertisement
CarEntitymodel, manufactureYear, registrationNumber, horsePower, engineVolumeFK on users.car_id
GalleryEntityname, imageUrl, author
CommentEntitybody (TEXT), targetType (NEWS|EVENT), targetId, deletedManyToOne→UserEntity (author), self-ref (max depth 2)

Enums

EnumValues
ApplicationStatusPENDING, ACCEPTED, REJECTED
EventStatusUPCOMING, ACTIVE, FINISHED, CANCELLED
MembershipCardTypeNORMAL, SILVER, GOLDEN
PartnerTypeDIAMOND, PLATED, GOLDEN, SILVER, BRONZE, ORGANIZATIONAL, MEDIA
CommentTargetTypeNEWS, EVENT
CheckStatusVALID, EXPIRING_SOON, INVALID, UNAVAILABLE

Security Model

Roles (Keycloak realm roles): USER, MODERATOR, ADMIN, ROOT_ADMIN

RolePermissions
USER+Apply to events, view own profile/car/membership, browse public content
MODERATOR+Create/edit events and news
ADMIN+Issue membership cards, manage partners/campaigns, lock/unlock users
ROOT_ADMINDelete users, change any user's role

JWT flow: Keycloak issues JWT → JwtAuthenticationConverter maps realm_access.roles@PreAuthorize guards all endpoints


Database Schema Standards

All tables comply with ADR-001:

  • Primary keys: UUID (gen_random_uuid())
  • Timestamps: TIMESTAMP WITH TIME ZONE (TIMESTAMPTZ)
  • Audit columns: created_at, updated_at, created_by, updated_by on every table
  • FK indexes: every foreign key column has a B-Tree index
  • Soft-delete: deleted_at, deleted_by on user-facing tables

Deployment

Production runs on a Hetzner VPS (Linux) using:

  • Docker Compose — all services in containers
  • Traefik v3 — reverse proxy + automatic Let's Encrypt TLS
  • GitHub Actions — CI (build + test) + CD (SSH deploy via appleboy/ssh-action)
  • GHCR — container registry (GitHub free tier)

See BE Setup Guide for local development instructions.