Commit Graph

16 Commits

Author SHA1 Message Date
WorkClub Automation
b813043195 fix(auth): add JWT audience claim and disable issuer validation
- Added Keycloak audience protocol mapper to workclub-app client
  - Maps 'workclub-api' to aud claim in access tokens
- Disabled issuer validation in API for local dev
  - External clients use localhost:8080, internal use keycloak:8080
  - Prevents validation mismatch in Docker network environment

This resolves 401 Unauthorized errors on all authenticated endpoints.

Ref: .sisyphus/evidence/final-f3-manual-qa.md lines 418-444
2026-03-05 14:12:53 +01:00
WorkClub Automation
def0331b21 fix(backend): update API package version to 10.0.3
Ultraworked with Sisyphus (https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-05 11:22:15 +01:00
WorkClub Automation
1a5d5e8651 style(backend): apply dotnet format whitespace normalization
- Applied dotnet format to 24 files in backend/
- Corrects spacing, indentation, and formatting consistency
- No functional changes to code logic

Ultraworked with Sisyphus <https://github.com/code-yeongyu/oh-my-opencode>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-05 11:07:19 +01:00
WorkClub Automation
6124557f11 infra(docker): add Dockerfiles for backend and frontend
Implements Tasks 23 & 24: Backend and Frontend Dockerfiles

Backend Dockerfiles:
- Dockerfile.dev: Development with dotnet watch hot reload
  - Base: sdk:10.0, installs dotnet-ef tool
  - Layer caching: csproj files copied before source
  - Entry: dotnet watch run with --no-restore
- Dockerfile: Production multi-stage build
  - Build stage: sdk:10.0, restore + build + publish
  - Runtime stage: aspnet:10.0-alpine (~110MB)
  - Health check: /health/live endpoint
  - Non-root: USER app (built-in)

Frontend Dockerfiles:
- Dockerfile.dev: Development with Bun hot reload
  - Base: node:22-alpine, installs Bun globally
  - Layer caching: package.json + bun.lock before source
  - Command: bun run dev
- Dockerfile: Production standalone 3-stage build
  - Deps stage: Install with --frozen-lockfile
  - Build stage: bun run build → standalone output
  - Runner stage: node:22-alpine with non-root nextjs user
  - Copies: .next/standalone, .next/static, public
  - Health check: Node.js HTTP GET to port 3000
  - Entry: node server.js (~240MB)

All Dockerfiles use layer caching optimization and security best practices.

Note: Docker build verification skipped (Docker daemon not running).
2026-03-03 20:59:20 +01:00
WorkClub Automation
db880b3480 feat(clubs): add Club and Member API endpoints with auto-sync
Implement Task 16: Club + Member API endpoints with MemberSyncService

Services:
- ClubService: GetMyClubsAsync (user's clubs), GetCurrentClubAsync (tenant club)
- MemberService: GetMembersAsync (list), GetMemberByIdAsync, GetCurrentMemberAsync
- MemberSyncService: Auto-creates Member records from JWT on first request

Middleware:
- MemberSyncMiddleware: Runs after auth, calls MemberSyncService

Endpoints:
- GET /api/clubs/me (list user's clubs)
- GET /api/clubs/current (current tenant's club)
- GET /api/members (list members, RLS filtered)
- GET /api/members/{id} (member detail)
- GET /api/members/me (current user's membership)

Tests: 14 integration tests (6 club + 8 member)
- Club filtering by user membership
- Multi-tenant isolation via RLS
- Member auto-sync on first request
- Cross-tenant access blocked
- Role-based authorization

Build: 0 errors, all tests compile
Pattern: TypedResults, RequireAuthorization policies, TDD approach
2026-03-03 19:41:01 +01:00
WorkClub Automation
0ef1d0bbd4 feat(shifts): add Shift CRUD API with sign-up/cancel and capacity management
- ShiftService with 7 methods: list, detail, create, update, delete, signup, cancel
- 5 DTOs: ShiftListDto, ShiftDetailDto, CreateShiftRequest, UpdateShiftRequest, ShiftSignupDto
- Minimal API endpoints: GET /api/shifts, GET /api/shifts/{id}, POST, PUT, DELETE, POST /signup, DELETE /signup
- Capacity validation: sign-up rejected when full → 409 Conflict
- Past shift blocking: cannot sign up for past shifts → 422 Unprocessable
- Duplicate signup prevention: check existing before create → 409 Conflict
- Concurrency: 2-attempt retry loop for last-slot race conditions
- Authorization: POST/PUT (Manager+), DELETE (Admin), signup/cancel (Member+)
- Test infrastructure: Added X-Test-UserId header support for member ID injection
- 13 TDD integration tests: CRUD, sign-up, capacity, past shift, concurrency
- Build: 0 errors (6 BouncyCastle warnings expected)

Task 15 complete. Wave 3: 3/5 tasks done.
2026-03-03 19:30:23 +01:00
WorkClub Automation
8550dd213a feat(tasks): add Task CRUD API with 5-state workflow
- TaskService with CRUD operations + state machine enforcement
- 5 DTOs: TaskListDto, TaskDetailDto, CreateTaskRequest, UpdateTaskRequest
- Minimal API endpoints: GET /api/tasks, GET /api/tasks/{id}, POST, PATCH, DELETE
- State machine: Open→Assigned→InProgress→Review→Done (invalid transitions → 422)
- Concurrency: DbUpdateConcurrencyException → 409 Conflict
- Authorization: POST (Manager+), DELETE (Admin), GET/PATCH (Member+)
- RLS: Automatic tenant filtering via TenantDbConnectionInterceptor
- 10 TDD integration tests: CRUD, state transitions, concurrency, role enforcement
- Build: 0 errors (6 BouncyCastle warnings expected)

Task 14 complete. Wave 3: 2/5 tasks done.
2026-03-03 19:19:21 +01:00
WorkClub Automation
cff101168c test(rls): add multi-tenant isolation integration tests
- 6 comprehensive RLS tests: complete isolation, no context, insert protection, concurrent requests, cross-tenant spoof, interceptor verification
- Uses Testcontainers PostgreSQL + Dapper for raw SQL validation
- Parallel safety test: 50 concurrent requests with ConcurrentBag
- Build passes: 0 errors (6 expected BouncyCastle warnings)
- Evidence: task-13-rls-isolation.txt (21KB), task-13-concurrent-safety.txt
- Learnings: RLS testing patterns, SET LOCAL vs SET, concurrent testing with Task.WhenAll

Task 13 complete. Wave 3: 1/5 tasks done.
2026-03-03 19:11:01 +01:00
WorkClub Automation
3a82933fd5 fix(backend): simplify Finbuckle namespace imports and register DB interceptors
- Use consolidated Finbuckle.MultiTenant namespace instead of separate imports
- Switch TenantProvider to use untyped IMultiTenantContextAccessor (Finbuckle 9.x pattern)
- Register TenantDbConnectionInterceptor and SaveChangesTenantInterceptor as singletons
- Add interceptors to DbContext configuration for RLS tenant context support
- Update evidence files for Task 7 and Task 8 verification
2026-03-03 18:52:35 +01:00
WorkClub Automation
c9cb629ddb fix(backend): use explicit Finbuckle.MultiTenant namespace imports 2026-03-03 14:32:55 +01:00
WorkClub Automation
eff6fded72 chore(backend): clean up redundant Finbuckle package and add ITenantProvider interface 2026-03-03 14:32:43 +01:00
WorkClub Automation
28964c6767 feat(backend): add PostgreSQL schema, RLS policies, and multi-tenant middleware
- Add EF Core migrations for initial schema (clubs, members, work_items, shifts, shift_signups)
- Implement RLS policies with SET LOCAL for tenant isolation
- Add Finbuckle multi-tenant middleware with ClaimStrategy + HeaderStrategy fallback
- Create TenantValidationMiddleware to enforce JWT claims match X-Tenant-Id header
- Add tenant-aware DB interceptors (SaveChangesTenantInterceptor, TenantDbConnectionInterceptor)
- Configure AppDbContext with tenant scoping and RLS support
- Add test infrastructure: CustomWebApplicationFactory, TestAuthHandler, DatabaseFixture
- Write TDD integration tests for multi-tenant isolation and RLS enforcement
- Add health check null safety for connection string

Tasks: 7 (PostgreSQL schema + migrations + RLS), 8 (Finbuckle multi-tenancy + validation), 12 (test infrastructure)
2026-03-03 14:32:21 +01:00
WorkClub Automation
b9edbb8a65 feat(auth): add Keycloak JWT authentication and role-based authorization
- Configure JWT Bearer authentication with Keycloak realm integration
- Create ClubRoleClaimsTransformation to parse 'clubs' claim and add ASP.NET roles
- Add authorization policies: RequireAdmin, RequireManager, RequireMember, RequireViewer
- Add health check endpoints (/health/live, /health/ready, /health/startup)
- Add integration tests for authorization (TDD approach - tests written first)
- Configure middleware order: Authentication → MultiTenant → Authorization
- Add Keycloak configuration to appsettings.Development.json
- Add AspNetCore.HealthChecks.NpgSql v9.0.0 package

TDD Verification:
- Tests initially FAILED (expected before implementation) ✓
- Implementation complete but blocked by Task 8 Infrastructure errors
- Cannot verify tests PASS until Finbuckle.MultiTenant types resolve

Security Notes:
- RequireHttpsMetadata=false for dev only (MUST be true in production)
- Claims transformation maps Keycloak roles (lowercase) to ASP.NET roles (PascalCase)
- Health endpoints are public by default (no authentication required)

Blockers:
- Infrastructure project has Finbuckle.MultiTenant type resolution errors from Task 8
- Tests cannot execute until TenantProvider compilation errors are fixed
2026-03-03 14:27:30 +01:00
Sisyphus CI
b7854e9571 feat(seed): add development seed data script
- Create SeedDataService in Infrastructure/Seed with idempotent seeding
- Seed 2 clubs: Sunrise Tennis Club, Valley Cycling Club
- Seed 7 member records (5 unique Keycloak test users)
- Seed 8 work items covering all status states
- Seed 5 shifts with date variety (past, today, future)
- Seed shift signups for realistic partial capacity
- Register SeedDataService in Program.cs with development-only guard
- Use deterministic GUID generation from club names
- Ensure all tenant IDs match for RLS compliance
- Track in learnings.md and evidence files for Task 22 QA
2026-03-03 14:23:50 +01:00
WorkClub Automation
ba024c45be feat(domain): add core entities — Club, Member, WorkItem, Shift with state machine
- Create domain entities in WorkClub.Domain/Entities: Club, Member, WorkItem, Shift, ShiftSignup
- Implement enums: SportType, ClubRole, WorkItemStatus
- Add ITenantEntity interface for multi-tenancy support
- Implement state machine validation on WorkItem with C# 14 switch expressions
- Valid transitions: Open→Assigned→InProgress→Review→Done, Review→InProgress (rework)
- All invalid transitions throw InvalidOperationException
- TDD approach: Write tests first, 12/12 passing
- Use required properties with explicit Guid/Guid? for foreign keys
- DateTimeOffset for timestamps (timezone-aware, multi-tenant friendly)
- RowVersion byte[] for optimistic concurrency control
- No navigation properties yet (deferred to EF Core task)
- No domain events or validation attributes (YAGNI for MVP)
2026-03-03 14:09:25 +01:00
Sisyphus Executor
c7dd3299d7 chore(scaffold): initialize git repo and monorepo with .NET solution 2026-03-03 14:02:37 +01:00