# 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
