wip league admin tools

This commit is contained in:
2025-12-28 12:04:12 +01:00
parent 5dc8c2399c
commit 6edf12fda8
401 changed files with 15365 additions and 6047 deletions

View File

@@ -120,6 +120,20 @@ Rules:
3. API Layer
API Services / Controllers (Thin Orchestration)
The API layer is a transport boundary. It MUST delegate business logic to `./core`:
• orchestrate auth + authorization checks (actor/session/roles)
• collect/validate transport input (DTOs at the boundary)
• execute a Core use case (entities/value objects live here)
• map Result → DTO / ViewModel via a Presenter (presenter owns mapping)
Rules:
• Controllers stay thin: no business rules, no domain validation, no decision-making
• API services orchestrate: auth + use case execution + presenter mapping
• Domain objects never cross the API boundary un-mapped
Presenter
@Injectable()
@@ -287,6 +301,12 @@ Rules:
Do / Dont (Boundary Examples)
✅ DO: Keep pages/components consuming ViewModels returned by website services (DTOs stop at the service boundary), e.g. [LeagueAdminSchedulePage()](apps/website/app/leagues/[id]/schedule/admin/page.tsx:12).
✅ DO: Keep controllers/services thin and delegating, e.g. [LeagueController.createLeagueSeasonScheduleRace()](apps/api/src/domain/league/LeagueController.ts:291).
❌ DONT: Put business rules in the API layer; rules belong in `./core` use cases/entities/value objects, e.g. [CreateLeagueSeasonScheduleRaceUseCase.execute()](core/racing/application/use-cases/CreateLeagueSeasonScheduleRaceUseCase.ts:38).
6. Optional Extensions
Custom Output Ports

View File

@@ -1,4 +1,4 @@
+# View Models
# View Models
## Definition
@@ -59,6 +59,7 @@ that logic belongs in the Core, not here.
## Creation Rules
- View Models are created from API DTOs
- DTOs never reach pages/components; map DTO → ViewModel in website services
- UI components must never construct View Models themselves
- Construction happens in services or presentation layers
- The UI only consumes View Models, never DTOs

View File

@@ -0,0 +1,78 @@
# League Actor Model & Permissions (Canonical)
This document defines the canonical backend actor model and the permission rules for **league admin/owner** operations.
It is the source of truth for Subtask 0A in [`plans/league-admin-mvp-plan.md`](plans/league-admin-mvp-plan.md:1).
---
## Session identity (source of truth)
### What the authenticated session contains
- The API authentication layer attaches `request.user.userId` based on the session cookie (`gp_session`).
- See [`AuthenticationGuard.canActivate()`](apps/api/src/domain/auth/AuthenticationGuard.ts:16).
- The backend uses an async request context (`AsyncLocalStorage`) to make the current request available to services.
- See [`requestContextMiddleware()`](adapters/http/RequestContext.ts:24).
- Wired globally for the API via [`AppModule.configure()`](apps/api/src/app.module.ts:49).
### Mapping: `userId` → `driverId`
Current canonical mapping (for MVP):
- The “actor” is derived from session, and `driverId === userId`.
- This is implemented by [`getActorFromRequestContext()`](apps/api/src/domain/auth/getActorFromRequestContext.ts:12).
Rationale:
- The current system uses the session user identity as the same identifier used by racing/league membership repositories (e.g. seeded admin user is `driver-1` in session).
- If/when we introduce a real user ↔ driver relationship (1:N), this function becomes the single authoritative mapping point.
---
## Canonical actor model
The APIs canonical “actor” is:
```ts
type Actor = { userId: string; driverId: string };
```
Returned by [`getActorFromRequestContext()`](apps/api/src/domain/auth/getActorFromRequestContext.ts:12).
Rules:
- All auth/permissions decisions use the actor derived from the authenticated session.
- Controllers and services must never use request-body “performer/admin IDs” for authorization decisions.
---
## League permissions: admin/owner
### Meaning of “league admin/owner”
A driver is authorized as a league admin if:
- They have an **active** membership in the league, and
- Their membership role is either `owner` or `admin`.
Authoritative check:
- Implemented in the core use case [`GetLeagueAdminPermissionsUseCase.execute()`](core/racing/application/use-cases/GetLeagueAdminPermissionsUseCase.ts:39) by loading membership and validating `status` + `role`.
### How it is validated server-side
Canonical enforcement entrypoint (API layer):
- [`requireLeagueAdminOrOwner()`](apps/api/src/domain/league/LeagueAuthorization.ts:15)
This helper:
- Derives the actor from session via [`getActorFromRequestContext()`](apps/api/src/domain/auth/getActorFromRequestContext.ts:12)
- Invokes the core use case with `performerDriverId: actor.driverId`
---
## Contract rule (non-negotiable)
**No league write operation may accept performer/admin IDs for auth decisions.**
Concretely:
- Request DTOs may still temporarily contain IDs for “target entities” (e.g. `targetDriverId`), but never the acting user/admin/performer ID.
- Any endpoint/service that needs “who is performing this” MUST obtain it from session-derived actor, not from request payload, params, or hardcoded values.
Tests:
- Actor derives from session, not payload: [`ActorFromSession`](apps/api/src/domain/auth/ActorFromSession.test.ts:17).
- Permission helper uses session-derived actor consistently: [`ActorFromSession`](apps/api/src/domain/auth/ActorFromSession.test.ts:30).
- Example application in a league write-like operation (`joinLeague`) ignores payload driverId and uses session actor: [`LeagueService`](apps/api/src/domain/league/LeagueService.test.ts:1).
---