Patrick Plate 4be9c4cf2c
Deploy to Production / test (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
fix(frontend): resolve app-wide 'Oops' crash + PWA middleware interception
Root cause (found via Playwright browser probe — curl could not detect client-side
hydration errors):

1. ROOT-LAYOUT INTL CRASH (the 'Oops' on every page incl /login):
   app/layout.tsx renders global client components (PwaInstallPrompt → useTranslations,
   Toaster, Sonner) as siblings of {children} inside <Providers>, but only each
   route-group layout wrapped its own children in NextIntlClientProvider. So those
   global components mounted with NO intl context → 'No intl context found' → React
   hydration crash → global-error 'Oops'. Fix: wrap the root body in
   NextIntlClientProvider via getMessages() (RootLayout now async). Nested providers
   stay valid (next-intl supports nesting).

2. PWA MIDDLEWARE INTERCEPTION (manifest.json syntax error + stale cache):
   middleware matcher did not exclude /manifest.json or /sw.js, so unauthenticated
   browsers got 307→/login (HTML) for both. Browser parsed HTML as JSON
   ('manifest.json:1 Syntax error') and an HTML/old service worker kept serving
   stale bundles ('website hasn't changed' after redeploys). Fix: exclude
   manifest.json, sw.js, icons, offline from the matcher.

3. SERVICE-WORKER STALE CACHE: bump CACHE_NAME v1→v2 so the activate handler purges
   old cached bundles from clients that loaded the broken build.

Also adds scripts/debug/dashboard-probe.mjs — a Playwright probe that logs in and
captures real client-side console/network errors + screenshot.
2026-06-13 10:36:09 +02:00

CannaManage

Multi-tenant cannabis club management platform for German Anbauvereinigungen (cultivation associations) under CanG §19.

Overview

CannaManage handles member management, distribution tracking, and legal compliance for cannabis cultivation clubs in Germany. It enforces the strict quotas mandated by the Cannabis Act (CanG) — including monthly limits (50g adult / 30g under-21), daily limits (25g), and THC restrictions for minors.

Tech Stack

Component Technology
Runtime Java 21 (Temurin)
Framework Spring Boot 4.0.6
Security Spring Security 7.0 + JWT (JJWT 0.12.6)
ORM Hibernate 7 / JPA
Database PostgreSQL (prod), H2 (test)
Migrations Flyway 10
API Docs SpringDoc OpenAPI 2.8.6
Build Maven (multi-module)
Container Docker Compose (Postgres + app)

Project Structure

cannamanage/
├── cannamanage-domain/      # JPA entities, enums, TenantContext
├── cannamanage-service/     # Business logic, repositories, ComplianceService
├── cannamanage-api/         # Spring Boot app, controllers, security, DTOs
├── docs/
│   └── sprint-2/           # Sprint planning docs
└── docker-compose.yml       # Local dev environment

Modules

cannamanage-domain

JPA entities with multi-tenant isolation via @Filter("tenantFilter"):

  • Member — club members with age tracking
  • Distribution — cannabis distribution records
  • MonthlyQuota — per-member monthly usage tracking
  • Batch / Strain / StockMovement — inventory management
  • Club — association registration
  • User — authentication accounts

cannamanage-service

  • ComplianceService — CanG §19 quota enforcement (25 unit tests)
  • Repositories for all entities

cannamanage-api

  • Auth — JWT login + refresh token rotation (SHA-256 hashed)
  • Members — CRUD for association members
  • Distributions — compliance-gated distribution recording
  • Stock — batch and inventory management
  • Compliance — quota status API
  • Multi-tenant isolation via TenantFilterAspect (Hibernate @Filter activation)

API Endpoints

Method Path Auth Description
POST /api/v1/auth/login Public Login with email + password
POST /api/v1/auth/refresh Public Refresh token rotation
GET /api/v1/compliance/quota/{memberId} ADMIN, MEMBER Monthly quota status
GET/POST/PUT /api/v1/members/** ADMIN, MEMBER Member CRUD
POST /api/v1/distributions/** ADMIN, MEMBER Record distributions
GET/POST /api/v1/stock/** ADMIN Stock management

Swagger UI: http://localhost:8080/swagger-ui.html

Running Locally

# Start PostgreSQL
docker compose up -d

# Run the app
JAVA_HOME=/path/to/jdk-21 ./mvnw spring-boot:run -pl cannamanage-api

# Run all tests (H2 in-memory)
JAVA_HOME=/path/to/jdk-21 ./mvnw clean verify

Testing

  • 37 tests total — all green
  • 25 unit tests (ComplianceServiceTest) — quota enforcement logic
  • 7 integration tests (AuthControllerIntegrationTest) — full HTTP auth flow
  • 5 integration tests (ComplianceControllerIntegrationTest) — quota API with JWT

Integration tests use @SpringBootTest(webEnvironment = RANDOM_PORT) with H2 and Spring's RestClient.

Security Model

  • Stateless JWT — no session, no UserDetailsService
  • Roles: ADMIN (full access), MEMBER (self-service), STAFF (Sprint 3)
  • Multi-tenancy: Hibernate @Filter activated per-request via AOP aspect
  • Refresh tokens: SHA-256 hashed (Spring Security 7 enforces BCrypt 72-byte limit)
  • Token rotation on refresh — old tokens invalidated

Sprint History

Sprint Focus Status
1 Domain entities, ComplianceService, 25 tests Done
2 REST API, Spring Security, JWT, OpenAPI, integration tests Done
3 Member portal, STAFF role, real-time notifications 📋 Planned

License

Private — Patrick Plate

S
Description
CannaManage — B2B SaaS for German Cannabis Social Clubs (Anbauvereinigungen)
Readme 7.8 MiB
Languages
Java 61%
TypeScript 38.1%
JavaScript 0.4%
CSS 0.3%
Shell 0.2%