Files
slp-modular-cms/aidlc-docs/features/cms-frontend/construction/unit-2/functional-design/domain-entities.md
T

8.1 KiB

Domain Entities — Unit 2: Authentication Pages

User (from backend, extended in Unit 2)

Type Definition

interface User {
  id: string;
  name: string;
  email: string;
  role: "Owner" | "Admin" | "User";
  createdAt: string; // ISO timestamp
}

Lifecycle in Unit 2

  • Created during setup: POST /Setup creates first Owner
  • Created during invitation: POST /Invitation/complete creates User
  • Returned by: Login, Refresh, Setup, Invitation completion
  • Stored in: AuthContext (in-memory, cleared on logout)

Unit 2 Attributes Used

  • id — for authorization checks and API calls
  • name — displayed in user menu (Unit 3+)
  • email — shown read-only in InviteCompletePage
  • role — used by RoleGuard to determine route access

SetupStatus

Type Definition

interface SetupStatus {
  initialized: boolean;
  createdAt?: string; // ISO timestamp, present only if initialized = true
}

Lifecycle in Unit 2

  • Fetched by: InitGuard on app mount
  • Stored in: React Query cache (staleTime: Infinity)
  • Used by: InitGuard to determine redirect logic
  • Returned by: GET /Setup/status

States

  • initialized: false — system requires setup; redirect to /setup
  • initialized: true — system ready; allow normal flow

InvitationToken

Type Definition

interface InvitationToken {
  token: string; // from query param: ?token=xxx
  valid: boolean;
  email: string; // present only if valid = true
  error?: string; // present only if valid = false
}

Lifecycle in Unit 2

  • Source: URL query param (/invite/complete?token=xxx)
  • Validated by: GET /Invitation/validate?token=xxx on page mount
  • Stored in: Local component state (InviteCompletePage)
  • Used for: Form submission to POST /Invitation/complete

States

  • Token extracted from URL
  • Validation in progress (loading state)
  • Validation succeeded (valid: true, show form)
  • Validation failed (valid: false, show error)
  • Submission in progress
  • Submission succeeded (redirect to login)
  • Submission failed (show banner error)

PasswordCredential

Type Definition

interface PasswordCredential {
  password: string;
  confirmPassword?: string; // present in setup/invite forms, absent in login
}

Validation

  • Both fields validated by Zod schema (src/lib/schemas/auth.ts)
  • password: 8+ chars, 1 uppercase, 1 digit, 1 special char (backend-aligned)
  • confirmPassword: must equal password

Usage

  • SetupPage: Both password and confirmPassword required
  • InviteCompletePage: Both password and confirmPassword required
  • LoginPage: Only password (no confirm, already implemented in Unit 1)

FormError

Type Definition

interface FormError {
  field?: string; // field name if field-specific
  message: string;
  code?: string; // backend error code
}

Lifecycle

  • Extracted from backend response or validation failure
  • Displayed inline (per field) or in banner (form-level)
  • Cleared on field blur (inline) or form reset (banner)

Examples

  • { field: "password", message: "At least 8 characters" } — inline
  • { message: "Email already in use" } — banner
  • { message: "Network error. Please try again." } — banner

RoleGuard Context

Type Definition

interface RoleGuardContext {
  userRole: "Owner" | "Admin" | "User" | null;
  requiredRole: "Owner" | "Admin" | "User" | ("Owner" | "Admin")[];
  hasAccess: boolean;
  accessDeniedMessage: string;
}

Lifecycle

  • Evaluated on route load (via TanStack Router beforeLoad)
  • userRole read from AuthContext
  • requiredRole defined per route (hardcoded in route definition)
  • hasAccess calculated as: userRole in requiredRole(s)
  • accessDeniedMessage shown inline if hasAccess = false

Example Routes

// /users — Owner or Admin
{ requiredRole: ["Owner", "Admin"], message: "Owner or Admin access required" }

// /settings — Owner only
{ requiredRole: "Owner", message: "Owner access required" }

// /cms — Owner only
{ requiredRole: "Owner", message: "Owner access required" }

AuthContext State (Unit 2 Extends Unit 1)

Extended State

interface AuthContextState {
  // From Unit 1
  user: User | null;
  accessToken: string | null;
  expiresAt: number | null;
  isAuthenticated: boolean;

  // New in Unit 2
  userRole: "Owner" | "Admin" | "User" | null;
}

Usage in Unit 2

  • useAuth() returns the state + methods
  • user.role checked by RoleGuard for access control
  • userRole derived from user.role for convenience

Language Preference

Type Definition

interface LanguagePreference {
  code: "en" | "nl"; // ISO 639-1
  label: string; // "English" | "Nederlands"
}

Lifecycle

  • Collected in SetupPage form
  • Sent to backend in POST /Setup
  • Stored in user profile (backend)
  • Retrieved on login (future: Unit 3+, stored in AuthContext)
  • Used by i18next to set app locale

Available Locales

  • en: English (fallback, eager load)
  • nl: Nederlands (lazy load)

API Response Types

Setup Response

interface SetupResponse {
  message: string;
  user: User; // newly created Owner
}

Invitation Validation Response

interface InvitationValidationResponse {
  valid: boolean;
  email?: string; // present if valid
  error?: string; // present if invalid
}

Invitation Completion Response

interface InvitationCompletionResponse {
  message: string;
  user: User; // newly created User
}

Error Response (ProblemDetails)

interface ProblemDetails {
  type: string; // e.g., "https://example.com/errors/validation-error"
  title: string; // e.g., "Bad Request"
  status: number; // HTTP status
  detail: string; // user-facing message
  instance?: string;
  extensions?: Record<string, unknown>;
}

Relationships & Dependencies

Entity Relationships

graph LR
    User["User<br/>(created by Setup<br/>or Invitation)"]
    Auth["AuthContext<br/>(stores User)"]
    Setup["SetupStatus<br/>(session cache)"]
    Guard["InitGuard<br/>(checks status)"]
    Token["InvitationToken<br/>(email tied)"]
    Role["RoleGuard<br/>(reads role)"]
    Cred["PasswordCredential<br/>(Zod schema)"]
    
    User -->|stored in| Auth
    Auth -->|provides role to| Role
    Setup -->|controls| Guard
    Token -->|creates| User
    Cred -->|validates| User
    Role -->|controls access| Auth
    
    classDef entity fill:#4CAF50,stroke:#2E7D32,color:#fff,stroke-width:2px
    classDef context fill:#2196F3,stroke:#0D47A1,color:#fff,stroke-width:2px
    classDef guard fill:#FF9800,stroke:#E65100,color:#fff,stroke-width:2px
    classDef validation fill:#9C27B0,stroke:#4A148C,color:#fff,stroke-width:2px
    
    class User,Setup,Token entity
    class Auth,Role context
    class Guard guard
    class Cred validation

Text alternative: Entity relationship diagram showing User as central entity (green), created by Setup or Invitation, stored in AuthContext (blue). SetupStatus controls InitGuard (orange) for initialization checks. InvitationToken creates User and is tied to email. PasswordCredential (purple) validates User credentials. RoleGuard reads User.role from AuthContext to control route access. Arrows show dependencies and data flow between entities.

Data Flow

  1. App loads → InitGuard fetches SetupStatus
  2. If not initialized → redirect to SetupPage
  3. User completes setup → creates Owner User, redirects to LoginPage
  4. OR: User clicks invitation link → InviteCompletePage validates token
  5. User completes invitation → creates User, redirects to LoginPage
  6. LoginPage → creates session via AuthContext.login()
  7. AuthContext stores User (with role) in memory
  8. RoleGuard reads User.role to control route access

Constraint Notes

  • All entities align with backend schema (no frontend-only fields)
  • Timestamps are ISO 8601 strings (backend provides)
  • Role is enum (fixed set: Owner, Admin, User)
  • Email is unique key (backend enforces)
  • Passwords never stored in frontend state (only accessToken in memory)