# 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 ```bash # 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