- HTML 57.3%
- Java 39.5%
- CSS 3.1%
|
|
||
|---|---|---|
| .github | ||
| .mvn/wrapper | ||
| docs | ||
| src | ||
| .gitattributes | ||
| .gitignore | ||
| CLAUDE.md | ||
| Dockerfile | ||
| mvnw | ||
| mvnw.cmd | ||
| pom.xml | ||
| README.md | ||
AR-8 — CrossFit Training App
Een full-stack CrossFit training tracking app voor AR-8. Gebruikers kunnen trainingen bekijken, scores loggen, personal records bijhouden, events aanmelden en hun level opbouwen via een puntensysteem.
Live: https://ar-8.nl
GitHub: https://github.com/fabian19921/AR8 (privé)
Tech Stack
| Laag | Technologie |
|---|---|
| Backend | Spring Boot 4, Java 21 |
| Database | PostgreSQL |
| Frontend | Thymeleaf + plain JavaScript |
| Auth | JWT (JSON Web Tokens) |
| Foto opslag | Cloudinary |
| Hosting | Render.com |
| Package | com.laverman.STM1D |
Lokaal opstarten
Vereisten
- Java 21
- Maven
- PostgreSQL database
Stappen
# 1. Clone de repo
git clone https://github.com/fabian19921/AR8.git
cd AR8
# 2. Maak een lokale PostgreSQL database aan
createdb ar8_db
# 3. Stel omgevingsvariabelen in (zie hieronder)
# 4. Start de app
mvn spring-boot:run
De app draait op http://localhost:8080
Omgevingsvariabelen
Zet deze in application.properties of als environment variables op Render:
# Database
spring.datasource.url=jdbc:postgresql://localhost:5432/ar8_db
spring.datasource.username=jouw_gebruikersnaam
spring.datasource.password=jouw_wachtwoord
# JWT
jwt.secret=jouw_jwt_secret
# Cloudinary
cloudinary.cloud-name=jouw_cloud_name
cloudinary.api-key=jouw_api_key
cloudinary.api-secret=jouw_api_secret
# Email (wachtwoord reset)
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=jouw_email
spring.mail.password=jouw_app_wachtwoord
Database migraties
Schemawijzigingen gaan via Flyway (src/main/resources/db/migration); de app voert ontbrekende migraties automatisch uit bij het opstarten:
- Nieuwe (lege) database:
V1__baseline.sqlzet het volledige schema neer. - Bestaande database zonder Flyway-historie: wordt eenmalig gebaselined op versie 1 (
spring.flyway.baseline-on-migrate=true), het baseline-script wordt dan overgeslagen.
Een nieuwe schemawijziging is een nieuw versioneerd script (V2__beschrijving.sql, V3__...). Hibernate staat op ddl-auto=validate en controleert bij het opstarten of het schema bij de entities past — handmatige SQL op productie is niet meer nodig.
Functionaliteiten
Gebruikers
- Registreren / inloggen via JWT
- Wachtwoord reset via email
- Profielpagina met foto upload (Cloudinary + Cropper.js)
- Rollen:
USERenADMIN
Trainingen
- Trainingsplan per dag aanmaken (ADMIN)
- Blokken: warming up, kracht, WOD, oefenblok, accessory
- Score typen:
kg,tijd,rondes,reps,meter,geen - RX / Scaled toggle per blokje
- Vinkje voor blokjes zonder score
- Buddy workout systeem (max 4 personen)
- Personal Records automatisch bijhouden, met historie (
pr_history) en verloopgrafiek per oefening - Weekbalk met navigatie
- Vast Mobility-blok onderaan elke dag (los van het trainingsplan, ook op dagen zonder geplande training): atleet logt zelf zijn tijd (mm:ss), invulling vrij (GoWod, Pliability, Flexy, eigen routine)
Puntensysteem (50 levels)
- +1 punt per ingevuld score blokje of vinkje (ook voor Mobility)
- +5 bonus punten als alle blokjes van een dag gedaan zijn (minimaal 3 blokjes); Mobility telt hier niet in mee
- +5 bonus punten als de som van Mobility-tijd in een ISO-week (ma t/m zo) minimaal 105 minuten is (7×15 min, vrij te verdelen over de week)
- +25 punten per event met eindranking ingevuld
- Buddy gebruikers krijgen dezelfde punten als de captain
- 50 levels van Rookie → Always Ready
- Voortgangsbalk op home pagina
- Maandelijks leaderboard (reset elke maand, toont top 5); sta je zelf buiten de top 5, dan toont de app je eigen positie, punten deze maand en het gat tot plek 5
Events
- Events aanmaken met foto (ADMIN)
- Registreren als individueel, buddy of team
- Foto upload per deelnemer met Cropper.js
- Eindranking invullen
- Atleten overzichtspagina per event
WOD Timer
- For Time (telt op, optioneel tijdslimiet)
- EMOM (instelbaar rondes + tijdsduur)
- Interval (werk + rusttijd + rondes)
- 10 seconden aftellen bij start
- Geluidsignalen via Web Audio API
- Scherm blijft aan via Wake Lock API
Overig
- Notities per dag
- Contact pagina
- AR-8 Atleet aanmelding + goedkeuring door admin
Mappenstructuur
src/main/
├── java/com/laverman/STM1D/
│ ├── config/ # CloudinaryConfig, SecurityConfig
│ ├── controller/ # REST controllers
│ ├── dto/ # Request/response DTO's (records met Bean Validation)
│ ├── model/ # JPA entities
│ ├── repository/ # Spring Data repositories
│ └── service/ # Business logic
└── resources/
├── templates/ # Thymeleaf HTML pagina's
│ └── static/ # style.css, images
└── application.properties
Deployment op Render
- Push naar GitHub (
mainbranch) - Render pikt automatisch de nieuwe versie op (auto-deploy aan)
- Flyway voert database-migraties automatisch uit bij het opstarten
- Environment variables staan ingesteld onder Environment in het Render dashboard
- Health-check:
/actuator/health(Actuator, zonder token bereikbaar) — in te stellen als health-check-pad in het Render dashboard; andere Actuator-endpoints zijn niet geëxposed
CI (GitHub Actions)
.github/workflows/ci.yml: draait./mvnw verify(build + alle tests) bij elke pull request en elke push naarmain, tegen een PostgreSQL-servicecontainer. De tests draaien met hetci-profiel (src/test/resources/application-ci.properties, dummy-secrets)..github/workflows/dependency-scan.yml: scant de Maven-dependencies wekelijks (en bij wijzigingen aanpom.xml) op bekende CVE's met Trivy; HIGH/CRITICAL kwetsbaarheden met een beschikbare fix laten de workflow falen. Fixes lopen vrijwel altijd via een patch-bump van de Spring Boot-parent inpom.xml..github/dependabot.yml: wekelijkse update-PR's voor Maven-dependencies en de workflow-actions.
Security
- XSS-bescherming in de frontend: alle dynamische waarden (namen, scores, notificaties, event-/qualifier-velden) worden ge-escaped met de gedeelde helper
esc()uitstatic/js/esc.jsvoordat ze viainnerHTMLgerenderd worden. Nieuwe templates dieinnerHTMLmet dynamische data gebruiken moeten/js/esc.jsincluden enesc()toepassen. - Security headers:
SecurityConfigzet een Content-Security-Policy (o.a.connect-src 'self'tegen token-exfiltratie,frame-ancestors 'none'tegen clickjacking). Externe bronnen die zijn toegestaan:cdnjs.cloudflare.com(cropper.js) enres.cloudinary.com(foto's). - Brute-force-bescherming:
RateLimitService(Bucket4j, in-memory per instance) limiteert/auth/login(10 pogingen per e-mail / 20 per IP per 15 min) en/api/password/forgot(3 per e-mail / 10 per IP per uur); daarboven volgt HTTP 429. - Wachtwoordbeleid: minimaal 10 tekens bij registratie, wijzigen en reset. Per gebruiker is maximaal één reset-token tegelijk actief; een nieuwe aanvraag maakt oude tokens ongeldig.
- Foutafhandeling:
GlobalExceptionHandler(@RestControllerAdvice) vangt onverwachte fouten af — generieke melding naar de client, volledige stacktrace naar de log. Controllers horen geen eigencatch (Exception)met foutdetails richting de client te hebben. - Request-validatie: endpoints nemen geen JPA-entities of losse
Maps als request body aan, maar DTO-records uitdto/met Bean Validation-annotaties en@Validin de controller (tegen mass assignment). Validatiefouten geven een 400 met een Nederlandstalige melding als platte tekst. - Ingelogde gebruiker: controllers parsen het Authorization-header niet zelf — de
JwtAuthFilterverifieert het token en zet eenAuthentication(principal = e-mailadres). Endpoints nemenAuthenticationals parameter en halen de gebruiker op viaCurrentUserService.getUser(authentication). - Rol uit de database: het JWT bevat geen rol-claim; de
JwtAuthFilterlaadt de rol per request uit de database. Een gebruiker degraderen werkt daardoor per direct, en een token van een verwijderde gebruiker geeft geen toegang meer. - Geen persoonsdata in logs:
EmailMaskingConverter(gekoppeld via%maskedMsginlogback-spring.xml) maskeert e-mailadressen in álle logmeldingen naarp***@domein.nl. Logregels hoeven daar zelf niets voor te doen. - Tests hiervoor staan in
src/test/java/com/laverman/STM1D/(o.a.SecurityHeadersTest,RateLimitServiceTest,LoginControllerTest,PasswordResetControllerTest,AdminEndpointSecurityTest). Wie een admin-endpoint toevoegt, hoort inAdminEndpointSecurityTesteen 403-test voor een gewone USER toe te voegen.
Bekende limieten
- Leaderboard berekent punten real-time voor alle gebruikers — bij >100 gebruikers eventueel caching toevoegen
- Geen push notificaties (Garmin API vereist zakelijke goedkeuring)
trainingplan.htmlheeft een bekend responsive probleem op kleine schermen (< 380px)