feat: Sprint 13 — Production Hardening (security fixes, CI gate, rate limiting, tests)
Deploy to TrueNAS / deploy (push) Failing after 12s

This commit is contained in:
Patrick Plate
2026-06-18 16:08:05 +02:00
parent 279487067e
commit f9a87efb7a
17 changed files with 1962 additions and 107 deletions
+57 -81
View File
@@ -1,111 +1,87 @@
# 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.
Full-stack management platform for German cannabis cultivation associations (Anbauvereinigungen) under the CanG/KCanG regulatory framework.
## 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) |
| Layer | Technology |
|-------|-----------|
| **Frontend** | Next.js 15, React 19, TypeScript, Tailwind CSS 4, shadcn/ui |
| **Backend** | Spring Boot 3.5, Java 17, Spring Security (JWT + session) |
| **Database** | PostgreSQL 16, Flyway migrations |
| **Infrastructure** | Docker Compose, Gitea Actions CI/CD, TrueNAS deployment |
## 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
├── cannamanage-api/ # Spring Boot REST API (entry point)
├── cannamanage-service/ # Business logic layer
├── cannamanage-domain/ # JPA entities, enums, value objects
├── cannamanage-frontend/ # Next.js frontend (pnpm)
├── deploy/ # Deployment scripts & nginx config
── docker-compose.yml # Local development stack
└── .gitea/workflows/ # CI/CD pipeline
```
## Modules
## Local Development
### 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
### Prerequisites
### cannamanage-service
- `ComplianceService` — CanG §19 quota enforcement (25 unit tests)
- Repositories for all entities
- Java 17+
- Maven 3.9+
- Node.js 22+ with pnpm 10+
- Docker & Docker Compose
### 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
### Backend
```bash
# Start PostgreSQL
docker compose up -d
docker compose up -d db
# 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
# Run Spring Boot
mvn spring-boot:run -f cannamanage-api/pom.xml -Dspring-boot.run.profiles=local
```
## Testing
### Frontend
- **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
```bash
cd cannamanage-frontend
pnpm install
pnpm dev
```
Integration tests use `@SpringBootTest(webEnvironment = RANDOM_PORT)` with H2 and Spring's `RestClient`.
The frontend runs on http://localhost:3000, backend on http://localhost:8080.
## Security Model
### Full Stack (Docker)
- **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
```bash
docker compose up --build
```
## Sprint History
## Deployment
| 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 |
Push to `main` triggers the Gitea Actions CI pipeline which:
1. Runs backend tests (`mvn test`)
2. Runs frontend lint (`pnpm lint`)
3. Builds Docker images
4. Deploys to TrueNAS via Docker Compose
5. Verifies backend health + frontend availability
Manual deploy:
```bash
cd deploy && ./deploy.sh
```
## Environment Variables
| Variable | Purpose | Default |
|----------|---------|---------|
| `CANNAMANAGE_SECURITY_JWT_SECRET` | JWT signing key (base64, 256-bit) | — (required) |
| `CORS_ORIGINS` | Allowed CORS origins (comma-separated) | `http://localhost:3000` |
| `SMTP_HOST` / `SMTP_PORT` | Mail server for invites | `localhost:1025` |
| `SCHEDULERS_ENABLED` | Enable background jobs | `true` |
## License
Private — Patrick Plate
Proprietary — Patrick Plate