# 2.2 User Service (Port 8082) - Detailed Implementation Plan ## Summary Implement the User Service as the source of truth for player profile and compliance data (registration, profile updates, email verification status, GDPR export/deletion), aligned with the implementation decisions already used in `backend/services/question-bank-service`. This service will run on Fiber, persist in PostgreSQL, expose health/readiness/metrics, and use shared packages for auth, validation, error mapping, logging, tracing, and service bootstrapping. ## Decisions Reused from 2.1 Question Bank Service 1. Use the same service composition style as `question-bank-service/cmd/main.go`: - `internal/infra/config.FromEnv()` for service-specific env parsing. - Shared logger/metrics/tracer initialization from `backend/shared`. - Repository initialization + `EnsureSchema(ctx)` at startup. - `/health`, `/ready`, and `/metrics` endpoints. - Route registration through `internal/interfaces/http/RegisterRoutes`. 2. Use the same auth approach: - Public routes can remain open when Zitadel env is missing (local development fallback). - Admin routes use Zitadel JWT middleware when configured. 3. Use the same persistence style: - PostgreSQL with `pgxpool` and SQL DDL in repository `EnsureSchema`, matching current pragmatic pattern. 4. Follow the same testing pyramid: - Domain/application unit tests. - HTTP integration tests with in-memory doubles. - Optional DB-backed integration tests gated by environment. ## Objectives 1. Provide player registration and profile management APIs. 2. Keep local profile data synchronized with authenticated identity claims from Zitadel. 3. Support GDPR rights: data export and account deletion. 4. Enforce RBAC on admin endpoints and ownership checks on user-scoped endpoints. 5. Deliver production-ready observability and baseline test coverage. ## API Endpoints - `POST /users/register` - `GET /users/:id` - `PUT /users/:id` - `DELETE /users/:id` - `POST /users/verify-email` - `GET /admin/users` - `POST /admin/users/:id/export` ## Domain Model Aggregate: - `User` Value objects: - `Email` (normalized lowercase, RFC-like format validation) - `DisplayName` (2-50 chars, allowed chars policy aligned with project guidelines) - `ConsentRecord` (terms/privacy/version + timestamp + source) Repository contract (`UserRepository`): - `Create(ctx, user)` - `GetByID(ctx, id)` - `GetByEmail(ctx, email)` - `UpdateProfile(ctx, id, patch)` - `MarkEmailVerified(ctx, id, verifiedAt)` - `SoftDelete(ctx, id, deletedAt)` - `List(ctx, pagination, filters)` - `ExportBundle(ctx, id)` ## Data Model (PostgreSQL) Primary table: `users` - `id UUID PRIMARY KEY` - `zitadel_user_id VARCHAR(128) UNIQUE NULL` - `email VARCHAR(320) UNIQUE NOT NULL` - `email_verified BOOLEAN NOT NULL DEFAULT FALSE` - `display_name VARCHAR(50) NOT NULL` - `consent_version VARCHAR(32) NOT NULL` - `consent_given_at TIMESTAMPTZ NOT NULL` - `consent_source VARCHAR(32) NOT NULL DEFAULT 'web'` - `created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` - `updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` - `deleted_at TIMESTAMPTZ NULL` Indexes: - `UNIQUE(email)` - `UNIQUE(zitadel_user_id) WHERE zitadel_user_id IS NOT NULL` - `INDEX(deleted_at)` - `INDEX(created_at DESC)` Audit table: `user_audit_log` - Track admin exports and delete actions for compliance/auditability. - Fields: `id`, `actor_user_id`, `target_user_id`, `action`, `metadata_json`, `created_at`. ## Authorization and Identity Rules 1. `GET/PUT/DELETE /users/:id`: - Allowed for the same authenticated user (`sub` mapped to local user) or `admin` role. 2. `POST /users/register`: - Requires authenticated token. - If user already exists by `zitadel_user_id` or email, return idempotent success (`200`) with existing profile. 3. `POST /users/verify-email`: - Requires authenticated token. - Resolve email verification from trusted source (token claim and/or Zitadel userinfo). - Never accept arbitrary email-verified flag from client body. 4. Admin routes (`/admin/*`): - Require admin role + MFA via shared Zitadel middleware behavior. ## Endpoint Behavior 1. `POST /users/register` - Input: `display_name`, `consent_version`, `consent_source`. - Identity fields (`email`, `zitadel_user_id`) come from token / Zitadel client, not request body. - Output: canonical user profile. 2. `GET /users/:id` - Return sanitized profile (no internal audit metadata). - `404` for missing or soft-deleted user. 3. `PUT /users/:id` - Allow updates to `display_name` and consent refresh fields. - Reject immutable field updates (`email`, `id`, `zitadel_user_id`). 4. `DELETE /users/:id` (GDPR) - Soft-delete local profile. - Trigger best-effort anonymization policy for user-facing displays. - Write compliance audit event. 5. `POST /users/verify-email` - Refresh verification status from Zitadel and persist local `email_verified=true` with timestamp/audit event. 6. `GET /admin/users` - Paginated listing with filters: `email`, `display_name`, `created_after`, `created_before`, `include_deleted`. 7. `POST /admin/users/:id/export` - Produce structured JSON export bundle (profile + consents + audit entries). - Return payload inline for now (later storage handoff can be added). ## Package and File Layout Target structure (mirrors 2.1 service organization): - `backend/services/user-service/cmd/main.go` - `backend/services/user-service/internal/infra/config/config.go` - `backend/services/user-service/internal/domain/user/` - `backend/services/user-service/internal/application/user/` - `backend/services/user-service/internal/infra/persistence/ent/` - `backend/services/user-service/internal/interfaces/http/` - `backend/services/user-service/tests/` ## Implementation Work Breakdown ### Workstream A - Bootstrap and Configuration 1. Add `internal/infra/config/config.go` with: - `USER_SERVICE_PORT` (default `8082`) - `USER_ADMIN_LIST_DEFAULT_LIMIT` (default `50`) - `USER_ADMIN_LIST_MAX_LIMIT` (default `200`) - shared `POSTGRES_*`, `TRACING_*`, `METRICS_*`, `LOG_LEVEL`, `ZITADEL_*` 2. Update `cmd/main.go` to match question-bank startup pattern: - logger/metrics/tracer creation - postgres client + schema ensure - readiness check endpoint - metrics endpoint - route registration with optional admin middleware ### Workstream B - Domain and Application Layer 1. Define domain entity, value objects, domain errors. 2. Define application DTOs for request/response mapping. 3. Implement application service methods: - `Register` - `GetProfile` - `UpdateProfile` - `DeleteUser` - `VerifyEmail` - `AdminListUsers` - `AdminExportUser` 4. Reuse shared error codes and transport error mapping style from question-bank handlers. ### Workstream C - Persistence Layer 1. Implement `Client` and `UserRepository` under `internal/infra/persistence/ent`. 2. Implement `EnsureSchema(ctx)` for `users` and `user_audit_log` tables/indexes. 3. Add SQL queries for pagination/filtering and soft-delete aware reads. 4. Add mappers between SQL rows and domain entity. ### Workstream D - HTTP Interface 1. Add request/response models with validation tags. 2. Implement handler methods with consistent JSON envelope and status codes. 3. Register routes in `routes.go` with admin group and middleware support. 4. Enforce ownership checks using user id from auth context. ### Workstream E - Compliance and Audit 1. Implement export builder (deterministic JSON schema). 2. Implement delete flow with audit logging and redaction policy hooks. 3. Ensure admin export/delete actions are auditable. ### Workstream F - Testing 1. Unit tests: - email/display-name/consent validation - idempotent registration logic - ownership and admin authorization guard behavior 2. HTTP integration tests (in-memory repo + test middleware): - register/get/update/delete happy paths - unauthorized/forbidden cases - admin list/export auth behavior - `/metrics` availability 3. Repository integration tests (optional env-gated): - schema creation - unique constraints - pagination/filter correctness ## Error Handling Contract Use project shared domain errors and consistent mappings: - `400`: validation failures - `401`: missing/invalid token - `403`: non-owner/non-admin access - `404`: user not found - `409`: duplicate email or identity conflict - `500`: unexpected errors ## Observability 1. Structured logs: - include `service=user-service`, request id, actor id (when available), and route. - never log raw tokens or PII-heavy payloads. 2. Metrics: - request count/latency/status via shared HTTP metrics. - dedicated counters for `user_registration_total`, `user_deletion_total`, `gdpr_export_total`. 3. Tracing: - ensure startup/shutdown tracer lifecycle identical to question-bank. - instrument service/repository boundaries for registration/export/delete paths. ## Security and Privacy Controls 1. Input validation and sanitization on all mutable fields. 2. No trust of client-supplied identity attributes when token context exists. 3. Soft-delete as default deletion strategy; avoid hard delete in initial implementation. 4. Minimize data in responses and logs (data minimization principle). 5. Audit every admin export and delete action. ## Delivery Sequence (3-4 Days) 1. Day 1: bootstrap/config + domain/application skeleton + schema DDL. 2. Day 2: repository + register/get/update endpoints + auth/ownership checks. 3. Day 3: delete/verify-email/admin list/export + observability wiring. 4. Day 4: tests, bug fixes, and readiness verification. ## Verification Commands From `backend/services/user-service`: ```bash go test ./... go vet ./... ``` From `backend` (optional full workspace check): ```bash go test ./... ``` ## Definition of Done 1. All endpoints implemented and route-protected per spec. 2. PostgreSQL schema auto-created on startup and validated in tests. 3. Health, readiness, and metrics endpoints functional. 4. AuthN/AuthZ and ownership checks covered by tests. 5. GDPR export/delete flows implemented with audit records. 6. `go test ./...` and `go vet ./...` pass for `user-service`.