# Services — CMS Frontend ## Service Architecture Overview The frontend uses **TanStack Query** as the server-state service layer. All API interactions are encapsulated in typed query/mutation hooks. The `ApiClient` handles transport concerns (headers, 401 retry). `AuthContext` manages session state. There is no additional service abstraction layer needed. --- ## Backend Services (Unit 0 changes) ### CORS Service (new) - **Service**: `ServiceCollectionExtensions.AddCorsFrontendPolicy()` - **Purpose**: Register named CORS policy `"FrontendPolicy"` for frontend SPA - **Configuration source**: `IConfiguration` → `Cors:AllowedOrigins` array - **Key settings**: - `WithOrigins(allowedOrigins)` — from config - `AllowAnyHeader()` + `AllowAnyMethod()` - `AllowCredentials()` — required for httpOnly cookie - **dotnet-appsettings pattern**: `AllowedOrigins` stored in `appsettings.json` (production) and `appsettings.Development.json` (dev, e.g. `["http://localhost:5173"]`); `appsettings.local.json` for developer-specific overrides --- ## Frontend Services (TanStack Query hooks) ### Auth Service (`frontend/src/auth/`) **AuthContext** functions as the auth service — it is the single source of truth for authentication state. | Operation | Implementation | Notes | |-----------|----------------|-------| | Login | `POST /api/v1/auth/login` → stores `accessToken` in memory | httpOnly cookie set by server | | Session restore | `POST /api/v1/auth/refresh` on app mount → stores new `accessToken` | Reads cookie automatically | | Logout | `POST /api/v1/auth/revoke` → clears in-memory token | Server clears cookie | | Token refresh (interceptor) | `POST /api/v1/auth/refresh` on 401 → retry original request | Centralised in ApiClient | ### Availability Service (`frontend/src/api/useAvailability.ts`) | Hook | Query key | Endpoint | Stale time | |------|-----------|----------|-----------| | `useAvailabilityStatus()` | `['availability', 'status']` | `GET /api/v1/availability/status` | 30s | ### Users Service (`frontend/src/api/useUsers.ts`) | Hook | Query key | Endpoint | Notes | |------|-----------|----------|-------| | `useUsers()` | `['users']` | `GET /api/v1/users` | Owner/Admin only | | `useInviteUser()` | mutation | `POST /api/v1/users/invite` | Invalidates `['users']` on success | ### Invitation Service (`frontend/src/api/useInvitation.ts`) | Hook | Query key | Endpoint | Notes | |------|-----------|----------|-------| | `useValidateInvitation(token)` | `['invitation', token]` | `GET /api/v1/users/validate-invitation?token=` | Public, no auth | | `useCompleteSetup()` | mutation | `POST /api/v1/users/complete-setup` | Public, no auth | ### Setup Service (`frontend/src/api/useSetup.ts`) | Hook | Query key | Endpoint | Notes | |------|-----------|----------|-------| | `useSetupStatus()` | `['setup', 'status']` | `GET /api/v1/setup/status` | Used by `InitGuard` | | `useCreateOwner()` | mutation | `POST /api/v1/setup/owner` | Used on `/setup` page | --- ## Service Interaction Diagram ```mermaid graph TD App["App Entry\n(main.tsx)"] InitGuard["InitGuard\n(useSetupStatus)"] AuthCtx["AuthContext\n(session state)"] ApiClient["ApiClient\n(fetch + 401 retry)"] Router["TanStack Router\n(ProtectedRoute + RoleGuard)"] subgraph Pages["Page Components"] Login["LoginPage"] Dashboard["DashboardPage\n(useAvailabilityStatus)"] Users["UsersPage\n(useUsers, useInviteUser)"] Invite["InviteCompletePage\n(useValidateInvitation\nuseCompleteSetup)"] Settings["SettingsPage\n(useAvailabilityStatus)"] Profile["ProfilePage"] end App --> InitGuard App --> AuthCtx InitGuard -->|initialized| Router Router --> Login Router --> Dashboard Router --> Users Router --> Invite Router --> Settings Router --> Profile AuthCtx -->|getAccessToken| ApiClient AuthCtx -->|refresh| ApiClient Dashboard --> ApiClient Users --> ApiClient Invite --> ApiClient Settings --> ApiClient style App fill:#CE93D8,stroke:#6A1B9A,color:#000 style InitGuard fill:#FFC107,stroke:#F57F17,color:#000 style AuthCtx fill:#FFC107,stroke:#F57F17,color:#000 style ApiClient fill:#FFC107,stroke:#F57F17,color:#000 style Router fill:#4CAF50,stroke:#2E7D32,color:#fff style Login fill:#2196F3,stroke:#0D47A1,color:#fff style Dashboard fill:#2196F3,stroke:#0D47A1,color:#fff style Users fill:#2196F3,stroke:#0D47A1,color:#fff style Invite fill:#2196F3,stroke:#0D47A1,color:#fff style Settings fill:#2196F3,stroke:#0D47A1,color:#fff style Profile fill:#2196F3,stroke:#0D47A1,color:#fff ```