# 01 — Architecture Reference

> Complete monorepo layout, data flow, and structural conventions.

## Monorepo Layout

```
project-root/
├── hub/                          # Main application
│   ├── frontend/                 # React SPA
│   │   ├── src/
│   │   │   ├── api/              # API client hooks (one file per domain)
│   │   │   ├── components/       # UI components (grouped by domain)
│   │   │   │   ├── common/       # Shared: SearchInput, Modal, DataTable, etc.
│   │   │   │   ├── dashboard/    # Dashboard-specific: StatsGrid, GroupInsights
│   │   │   │   ├── groups/       # Groups domain: GroupTable, GroupModal, etc.
│   │   │   │   ├── users/        # Users domain
│   │   │   │   ├── mailboxes/    # Mailboxes domain
│   │   │   │   ├── distributionLists/  # DL domain
│   │   │   │   ├── layout/       # Shell: Sidebar, Header, MainLayout
│   │   │   │   ├── training/     # Training guide: InteractiveDemo, SectionMockUI
│   │   │   │   ├── auth/         # Auth components: AdminRoute (admin-only guard)
│   │   │   │   └── system/       # System: ArchitectureDiagram, HealthPanel, CostBreakdown, ResourceInventory
│   │   │   ├── hooks/            # Custom React hooks
│   │   │   │   ├── useAccessibleTenants.ts  # Filters tenants by user's scoped access
│   │   │   │   ├── useDebounce.ts           # Debounced value hook
│   │   │   │   ├── usePermissions.ts        # Derives persona + permission flags
│   │   │   │   └── useReadOnly.ts           # Read-only mode hook
│   │   │   ├── lib/              # Utilities: config, logger, sanitize, utils
│   │   │   │   └── recommendations.ts  # Tenant health analysis + cross-brand aggregation
│   │   │   ├── pages/            # Route-level page components (15 pages)
│   │   │   │                     # Dashboard, Groups, Users, Mailboxes,
│   │   │   │                     # DistributionLists, AuditLog, TrainingGuide,
│   │   │   │                     # PersonaMatrix, SystemArchitecture, SystemHealth,
│   │   │   │                     # Settings, UserManagement, HealthInsights,
│   │   │   │                     # LoggedOut, NotFound
│   │   │   ├── providers/        # React context providers
│   │   │   ├── types/            # TypeScript type definitions
│   │   │   ├── __tests__/        # Vitest unit tests
│   │   │   ├── App.tsx           # Route definitions
│   │   │   ├── main.tsx          # Entry point + provider tree
│   │   │   └── index.css         # Tailwind + CSS custom properties
│   │   ├── e2e/                  # Playwright E2E tests
│   │   ├── staticwebapp.config.json  # SWA routing, auth, headers
│   │   ├── tailwind.config.ts    # Tailwind theme + brand colors
│   │   ├── vite.config.ts        # Vite + proxy config + vendor chunk splitting
│   │   ├── tsconfig.json         # Strict TS config
│   │   └── package.json
│   │
│   ├── api/                      # Azure Functions backend
│   │   ├── src/
│   │   │   ├── functions/        # 21 files: 19 HTTP + 1 timer + 1 pre-invocation hook (50 HTTP routes)
│   │   │   ├── lib/
│   │   │   │   ├── auth/         # SWA auth extraction
│   │   │   │   ├── middleware/   # RBAC, CSRF, rate limiter
│   │   │   │   ├── graph/        # Microsoft Graph client wrappers
│   │   │   │   ├── healthChecks/ # Deep health monitoring
│   │   │   │   ├── auditLogger.ts   # Dual-layer: App Insights + in-memory ring buffer
│   │   │   │   ├── auditQuery.ts    # Kusto query builder for App Insights audit data
│   │   │   │   ├── blobPersistence.ts  # Azure Blob Storage persistence (user store)
│   │   │   │   ├── logger.ts
│   │   │   │   ├── readOnlyMode.ts
│   │   │   │   ├── telemetry.ts     # App Insights init (33% sampling, audit bypasses)
│   │   │   │   ├── userStore.ts     # User/RBAC store with Blob Storage persistence + env var bootstrap
│   │   │   │   └── writeFailureTelemetry.ts  # hub.write_failure event tracking
│   │   │   └── tests/            # Vitest unit tests
│   │   ├── host.json             # Functions host config
│   │   ├── tsconfig.json
│   │   └── package.json
│   │
│   ├── config/                   # Shared config (tenant definitions)
│   ├── deploy/                   # Deployment scripts + runbooks
│   ├── infrastructure/           # Azure resource docs (app registration)
│   ├── scripts/                  # Utility scripts
│   ├── reports/                  # Generated reports
│   └── swa-cli.config.json       # SWA CLI project config (ports, commands)
│
├── .github/
│   ├── agents/                   # GitHub Copilot agent definitions
│   │   └── AZURE-SWA.agent.md    # SWA expert agent config
│   └── workflows/                # CI/CD pipelines
│       ├── azure-static-web-apps.yml  # Production deploy (main)
│       ├── ci-dev.yml                  # Dev branch CI
│       └── deploy-pages.yml            # GitHub Pages deploy
│
├── docs/                         # Project documentation
│   ├── site/                     # GitHub Pages executive overview (static HTML)
│   │   └── index.html            # Live stats: 1,142 users, 300+ groups, 384 sites
│   ├── ARCHITECTURE.md
│   ├── TESTING-GUIDE.md
│   └── prd/                      # Product requirement docs
│
└── reference/                    # ← This folder (replication guide)
```

## Data Flow

```
Browser (React SPA)
    │
    ├── GET /.auth/me ──────────────────────→ SWA Built-in Auth
    │                                          │
    │   ┌──────────────────────────────────────┘
    │   │ Returns: { clientPrincipal: { userDetails, userRoles } }
    │   │ On Free tier: userRoles = ['anonymous', 'authenticated'] only
    │   │ On Standard tier: rolesSource adds custom persona role
    │   ▼
    ├── AuthProvider stores user in React Context
    │
    ├── GET /api/user-context ──────────────→ Returns RBAC context
    │   │  { email, displayName, globalPersona, tenants[], isMultiTenant }
    │   │  This is the RBAC source of truth on Free tier
    │   ▼
    │   useUserContext() caches in React Query
    │   usePermissions() derives permission flags from globalPersona
    │   useAccessibleTenants() filters tenants by user access
    │
    ├── GET/POST /api/{resource} ───────────→ Azure Functions (Node 20)
    │   (x-ms-client-principal auto-injected)    │
    │                                            ├── startup.ts pre-invocation hook
    │                                            │   └── ensureInitialized() (blob → env vars)
    │                                            ├── swaAuth.ts extracts user
    │                                            ├── rbac.ts checks permissions
    │                                            ├── graph/ calls Microsoft Graph
    │                                            ├── auditLogger.ts logs writes
    │                                            └── Returns JSON response
    │
    ├── TanStack React Query caches responses
    │   (staleTime: 30s, retry: 1)
    │
    └── UI renders with role-aware components
        (buttons shown/hidden per persona)

Sign-out flow:
    AuthProvider.logout()
        → /.auth/logout?post_logout_redirect_uri=/logged-out
            → SWA clears session cookie, redirects to /logged-out
                → LoggedOut.tsx redirects to AAD /oauth2/v2.0/logout
                    → AAD clears SSO session, shows "signed out" page

rolesSource flow (Standard tier only — not active on Free tier):
    SWA ──POST /api/auth/roles──→ authRoles.ts
                                    │
                                    ├── getAppUser(email) from blob-backed user store
                                    └── returns { roles: [persona] }
                                         └── SWA adds to session alongside
                                             built-in roles (authenticated, anonymous)
    ⚠️  Free tier does not support rolesSource. RBAC is resolved server-side
        via resolvePersona() (ROLE_ASSIGNMENTS → GLOBAL_ADMIN_USERS → viewer)
        and exposed through GET /api/user-context.
```

## Key Conventions

### 1. One File Per Domain in API Hooks (`src/api/`)
```
groups.ts          → useGroups(), useGroup(), useCreateGroup(), etc.
users.ts           → useUsers(), useUser()
distributionLists.ts → useDistributionLists()
tenants.ts         → useTenants()
audit.ts           → useAuditLog()
```

Each file exports React Query hooks that wrap axios calls through a shared `client.ts`.

> **Dynamic tenant queries (v3.10.1):** When a component needs to fetch data for a dynamic list of tenants (e.g., Health Insights), use `useQueries()` from TanStack React Query v5 instead of calling hooks in a loop. Export a compat fetch function (e.g., `fetchGroupsByTenantCompat(tenantId)`) from the API hook file and call it within `useQueries({ queries: tenantIds.map(...) })`. This avoids violating React's Rules of Hooks.

> **User filtering (v3.5):** The `users.ts` endpoint applies `MEMBER_USERS_FILTER` (`userType eq 'Member'`) and `hasExchangeLicense()` to return only mailbox-eligible members, not raw directory listings.

### 2. One File Per HTTP Endpoint in Functions (`api/src/functions/`)

There are **21 function files** — 19 HTTP handlers registering **50 HTTP routes**, plus 1 timer-triggered function and 1 pre-invocation hook:

```
appUsers.ts           → GET/POST/PUT/DELETE /api/app-users (hub user management)
audit.ts              → GET /api/audit
authRoles.ts          → POST /api/auth/roles (SWA rolesSource — assigns custom roles on login)
config.ts             → GET /api/config
deepHealth.ts         → GET /api/deep-health
deletedGroups.ts      → GET/POST /api/deleted-groups (soft-delete recovery)
distributionListMembers.ts → GET/POST/DELETE /api/distribution-lists/{id}/members
distributionLists.ts  → GET/POST/DELETE /api/distribution-lists
groupMembers.ts       → GET/POST/DELETE /api/groups/{id}/members
groupOwners.ts        → GET/POST/DELETE /api/groups/{id}/owners
groups.ts             → GET /api/groups, GET /api/groups/{id}
health.ts             → GET /api/health
mailboxes.ts          → GET /api/mailboxes
mailboxForwarding.ts  → GET/PUT /api/mailboxes/{id}/forwarding
mailboxSettings.ts    → GET/PUT /api/mailboxes/{id}/settings
tenants.ts            → GET /api/tenants
userContext.ts        → GET /api/user-context
userGroups.ts         → GET /api/users/{id}/groups
users.ts              → GET /api/users (filtered to mailbox-eligible members)
auditMaintenance.ts   → Timer (daily 02:00 UTC) — App Insights ingestion monitoring
startup.ts            → Pre-invocation hook — ensures user store initialized before every function call
```

> **rolesSource (v3.9 — Standard tier only):** `authRoles.ts` is not called by browsers or API clients — SWA calls it internally on every authentication. It looks up the user in the blob-backed user store and returns their persona as a custom SWA role. **Requires Standard plan.** On Free tier, remove `rolesSource` from `staticwebapp.config.json` — RBAC falls back to `resolvePersona()` (ROLE_ASSIGNMENTS → GLOBAL_ADMIN_USERS → viewer).

> **Pre-invocation hook (v3.10):** `startup.ts` registers an `app.hook.preInvocation` handler that calls `ensureInitialized()` from the user store. This guarantees env vars (`TENANT_USER_ACCESS`, `ROLE_ASSIGNMENTS`, `GLOBAL_ADMIN_USERS`) are populated from blob storage before any function handler executes — fixing cold-start 403 errors where auth middleware would read empty env vars.

> **Timer function (v3.6):** `auditMaintenance.ts` is not an HTTP endpoint — it runs on a CRON schedule (`0 0 2 * * *`) to monitor App Insights data ingestion against the 5 GB free-tier cap. It warns at 80% usage and errors when exceeded.

### 3. Domain-Grouped Components (`src/components/{domain}/`)
- Each domain folder contains components specific to that page
- `common/` holds cross-cutting components (modals, tables, search)
- `layout/` holds the shell (sidebar, header, main layout)

### 4. Pages Are Thin (`src/pages/`)
- Pages compose domain components + API hooks
- Pages handle loading/error states
- Pages wire up the Header with title/subtitle
- Business logic lives in hooks and components, NOT pages

### 5. Provider Stack (order matters)
```tsx
<React.StrictMode>
  <BrowserRouter>
    <QueryClientProvider client={queryClient}>
      <ThemeProvider>
        <AuthProvider>
          <ReadOnlyProvider>
            <NotificationProvider>
              <AuthGate>
                <App />
              </AuthGate>
            </NotificationProvider>
          </ReadOnlyProvider>
        </AuthProvider>
      </ThemeProvider>
    </QueryClientProvider>
  </BrowserRouter>
</React.StrictMode>
```

### 6. Type Safety Level
Both frontend and API use maximum TypeScript strictness:
```jsonc
{
  "strict": true,
  "noImplicitAny": true,
  "strictNullChecks": true,
  "exactOptionalPropertyTypes": true,
  "noUnusedLocals": true,
  "noUnusedParameters": true,
  "noUncheckedIndexedAccess": true  // API only — catches arr[i] as T | undefined
}
```

### 7. Path Aliases
Frontend uses `@/` alias via Vite + tsconfig:
```typescript
import { useGroups } from '@/api/groups'
import { cn } from '@/lib/utils'
```

### 8. Environment Variables
```bash
# Frontend (.env or SWA app settings)
VITE_API_URL=/api          # Defaults to /api (relative) for SWA

# API (SWA app settings)
TENANT_CONFIGS=<json>      # Multi-tenant configuration (array of tenant objects)
GRAPH_CLIENT_ID=<guid>     # Azure AD app registration
GRAPH_CLIENT_SECRET=<secret>
ROLE_ASSIGNMENTS=<json>    # Email → role mapping
CSRF_SECRET=<64-char-hex>  # HMAC signing secret for stateless CSRF tokens
READ_ONLY_MODE=false       # Emergency read-only switch
USER_STORE_CONNECTION=<connection-string>  # Azure Blob Storage for user store persistence
```
