33 Commits

Author SHA1 Message Date
WorkClub Automation
eaa163afa4 fix(k8s): stabilize keycloak rollout and align CD deploy manifests
Update Keycloak probe/realm import behavior and authority config so auth services start reliably on the dev cluster, while keeping CD deployment steps aligned with the actual Kubernetes overlay behavior.
2026-03-13 06:25:07 +01:00
WorkClub Automation
672dec5f21 Fix task and shift self-assignment features
Some checks failed
CI Pipeline / Backend Build & Test (pull_request) Successful in 48s
CI Pipeline / Frontend Lint, Test & Build (pull_request) Failing after 28s
CI Pipeline / Infrastructure Validation (pull_request) Successful in 4s
2026-03-09 15:47:57 +01:00
WorkClub Automation
867dc717cc fix(shifts): expose ExternalUserId in ShiftSignupDto to fix frontend signup state
Some checks failed
CI Pipeline / Backend Build & Test (pull_request) Failing after 49s
CI Pipeline / Frontend Lint, Test & Build (pull_request) Successful in 29s
CI Pipeline / Infrastructure Validation (pull_request) Successful in 3s
2026-03-09 14:46:35 +01:00
WorkClub Automation
a8730245b2 fix(backend): resolve shift signup by looking up Member via ExternalUserId
All checks were successful
CI Pipeline / Backend Build & Test (pull_request) Successful in 52s
CI Pipeline / Frontend Lint, Test & Build (pull_request) Successful in 29s
CI Pipeline / Infrastructure Validation (pull_request) Successful in 5s
The signup/cancel endpoints were passing the Keycloak sub claim (external UUID)
directly as MemberId, but ShiftSignup.MemberId references the internal Member.Id.
Now ShiftService resolves ExternalUserId to the internal Member.Id before creating
the signup record. Integration tests updated to seed proper Member entities.
2026-03-09 13:24:50 +01:00
b4b9d23429 next ci test
All checks were successful
CI Pipeline / Backend Build & Test (push) Successful in 1m25s
CI Pipeline / Frontend Lint, Test & Build (push) Successful in 1m3s
CI Pipeline / Infrastructure Validation (push) Successful in 4s
2026-03-08 14:27:08 +01:00
7d9e7d146e simle test to force ci
All checks were successful
CI Pipeline / Backend Build & Test (push) Successful in 1m10s
CI Pipeline / Frontend Lint, Test & Build (push) Successful in 1m0s
CI Pipeline / Infrastructure Validation (push) Successful in 4s
2026-03-08 14:22:56 +01:00
WorkClub Automation
f8f3e0f01e test(harness): stabilize backend+frontend QA test suite (12/12+63/63 unit+integration, 45/45 frontend)
Stabilize test harness across full stack:

Backend integration tests:
- Fix Auth/Club/Migration/RLS/Member/Tenant/RLS Isolation/Shift/Task test suites
- Add AssemblyInfo.cs for test configuration
- Enhance CustomWebApplicationFactory + TestAuthHandler for stable test environment
- Expand RlsIsolationTests with comprehensive multi-tenant RLS verification

Frontend test harness:
- Align vitest.config.ts with backend API changes
- Add bunfig.toml for bun test environment stability
- Enhance api.test.ts with proper test setup integration
- Expand test/setup.ts with fixture initialization

All tests now passing: backend 12/12 unit + 63/63 integration, frontend 45/45
2026-03-06 09:19:32 +01:00
WorkClub Automation
9950185213 fix: stabilize auth-to-tenant flow and correct tenant header mapping
Resolve post-login routing and tenant context issues by proxying frontend API
calls, redirecting authenticated users away from /login, and hardening club
loading with retries/loading guards.

Align tenant identity end-to-end by returning tenantId in /api/clubs/me and
sending X-Tenant-Id from cookie-backed tenantId instead of local clubId,
restoring authorized tasks/shifts data access after club selection.
2026-03-06 08:01:09 +01:00
WorkClub Automation
dbc8964f07 fix: resolve ObjectDisposedException in ClubService.GetMyClubsAsync()
Create fresh NpgsqlConnection per tenant iteration instead of reusing
EF Core's managed connection. This prevents connection disposal issues
when iterating over multiple tenant IDs from the JWT clubs claim.

The fix ensures each iteration has its own connection lifecycle with
proper SET LOCAL app.current_tenant_id for RLS compliance.
2026-03-05 21:46:19 +01:00
WorkClub Automation
ffc4062eba fix: exempt /api/clubs/me from tenant validation
- Add path exemption in TenantValidationMiddleware for /api/clubs/me
- Change authorization policy from RequireMember to RequireViewer
- Fix KEYCLOAK_CLIENT_ID in docker-compose.yml (workclub-app not workclub-api)
- Endpoint now works without X-Tenant-Id header as intended
- Other endpoints still protected by tenant validation

This fixes the chicken-and-egg problem where frontend needs to call
/api/clubs/me to discover available clubs before selecting a tenant.
2026-03-05 21:32:37 +01:00
WorkClub Automation
18be0fb183 fix: exempt /api/clubs/me from tenant validation
- Add path exemption in TenantValidationMiddleware for /api/clubs/me
- Change authorization policy from RequireMember to RequireViewer
- Fix KEYCLOAK_CLIENT_ID in docker-compose.yml (workclub-app)
- Resolves frontend chicken-and-egg problem for club discovery

Verified:
- /api/clubs/me returns 200 OK without X-Tenant-Id header
- /api/tasks still requires X-Tenant-Id (400 Bad Request)
- Other endpoints unaffected
2026-03-05 21:32:34 +01:00
WorkClub Automation
c918f447b2 fix(backend): add TenantDbTransactionInterceptor for RLS with explicit transactions
Implements Option D: wraps auto-commit reads in explicit transactions with SET LOCAL.
Handles transaction lifecycle (create→SET LOCAL→execute→commit/dispose).
Uses IDbTransactionInterceptor for EF-managed SaveChanges transactions.
Critical fix for PostgreSQL RLS requiring transaction-scoped context.
2026-03-05 20:43:03 +01:00
WorkClub Automation
65e80ee334 fix(backend): move RLS initialization before app_admin role assignment
Reorganize SeedDataService to establish RLS policies before granting
app_admin role to prevent permission issues. Remove --no-restore flag
from Dockerfile.dev to ensure proper build.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-05 19:22:29 +01:00
WorkClub Automation
3b7db39cc2 fix(backend): update middleware ordering and interceptors for RLS
Update TenantValidationMiddleware, Program.cs startup sequence,
SaveChangesTenantInterceptor, and TenantProvider to ensure proper
middleware ordering and tenant context initialization before database access.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-05 19:22:21 +01:00
WorkClub Automation
5a4bb16413 fix(backend): resolve tenant context initialization in claims transformation
Set tenant context before querying DB in ClubRoleClaimsTransformation.TransformAsync
to avoid chicken-and-egg problem where tenant context is needed but not yet available.

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-05 19:22:13 +01:00
WorkClub Automation
7859e1b3cf fix(infra): add explicit transaction management to TenantDbConnectionInterceptor for RLS
PostgreSQL SET LOCAL only persists within a transaction scope. Added explicit transaction creation if none exists, ensuring tenant context is properly set before queries execute. Fixes tenant isolation for multi-tenant RLS filtering.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-05 16:30:50 +01:00
WorkClub Automation
8d3ac6e64a Remove transaction check from TenantDbConnectionInterceptor.SetTenantContext
Allow SET LOCAL execution for all database commands by removing the transaction check.
EF Core creates implicit transactions for queries, so SET LOCAL works regardless.

This fixes the issue where read operations without explicit transactions were not getting
tenant context set properly, leading to incorrect RLS filtering and data visibility.
2026-03-05 16:08:09 +01:00
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