Files
HIPCTF2/docs/architecture/overview.md
T

93 lines
4.5 KiB
Markdown

---
type: architecture
title: System Overview
description: High-level layout of the HIPCTF monorepo (NestJS API + Angular SPA) and how they communicate.
tags: [architecture, backend, frontend, overview]
timestamp: 2026-07-21T15:05:00Z
---
# Overview
HIPCTF is delivered as a Node.js npm workspace containing two packages:
| Package | Path | Stack |
|-----------|-------------|----------------------------------|
| Backend | `backend/` | NestJS 10 + TypeORM + better-sqlite3 |
| Frontend | `frontend/` | Angular 17 standalone components |
The backend also serves the built Angular SPA as static files and falls
back to `index.html` for client-side routes, so a single Node process
hosts the whole application.
# Topology
```
Browser ──HTTPS──▶ NestJS process (PORT, default 3000)
├── /api/v1/* ── controllers → services → TypeORM
│ │
│ ▼
│ better-sqlite3
│ (DATABASE_PATH)
├── /api/docs, /api/docs-json ── Swagger UI (OpenAPI 3.1)
├── /uploads/* ── static file server (UPLOAD_DIR)
└── /* ── SPA fallback (FRONTEND_DIST/index.html)
```
# Backend entrypoint flow
`backend/src/main.ts` orchestrates startup in this order:
1. Create the Nest application.
2. Read configuration via `ConfigService` (validated against
`backend/src/config/env.schema.ts`).
3. Call `DatabaseInitService.init()` so migrations + WAL PRAGMA run
before HTTP traffic is accepted.
4. Register global middleware: `helmet`, `cookieParser`, JSON / URL-encoded
parsers, then `CsrfMiddleware` (registered explicitly so it can read
the parsed body).
5. Mount `/uploads` static and Angular static (`FRONTEND_DIST`).
6. Build the OpenAPI 3.1 document via `toOpenApi31()` and expose Swagger
UI at `/api/docs` and JSON at `/api/docs-json`.
7. Install `SpaFallbackMiddleware` so non-`/api`, non-`/uploads`,
extensionless `GET` requests return `index.html`.
8. `app.listen(port)`.
# Frontend entrypoint flow
`frontend/src/main.ts` bootstraps a standalone Angular app:
1. `provideHttpClient(withInterceptors([csrfInterceptor, authInterceptor]))`
attaches the CSRF header on unsafe methods and the Bearer access
token on every request.
2. `provideRouter(APP_ROUTES, withComponentInputBinding())`.
3. `AppComponent.ngOnInit()` calls `BootstrapService.load()` then
`AuthService.restoreSession()`. Bootstrap fetches `GET /api/v1/bootstrap`
and applies the returned theme tokens to CSS custom properties on
`document.documentElement`. Session restore rehydrates the in-memory
auth signals from `sessionStorage` and posts `POST /api/v1/auth/refresh`
with `withCredentials: true` so a hard refresh does not log the user out.
4. Guards (`authGuard`, `adminGuard`) are async and `await`
`BootstrapService.ready()` + `AuthService.waitUntilHydrated()` before
reading state, so refreshing on a deep link never flashes `/bootstrap`
or `/login`.
# Cross-cutting concepts
| Concern | Backend | Frontend |
|----------------|------------------------------------------------------|---------------------------------------------|
| Authentication | `JwtAuthGuard` (global) + `AuthGuard('jwt')` strategy | `AuthService` signal + `authInterceptor`; session is mirrored to `sessionStorage` and rehydrated on boot via `/api/v1/auth/refresh` |
| Authorization | `AdminGuard` + `@Roles('admin')` decorator | `authGuard` for `/`, `adminGuard` for `/admin` (both async, both delegate to pure decision functions: `decideAuthRedirect` / `decideAdminGuard`) |
| CSRF | `CsrfMiddleware` (skips `/api/v1/auth/login` and `/api/v1/auth/register-first-admin`) | `csrfInterceptor` reads `csrf` cookie |
| Errors | `ApiError``GlobalExceptionFilter` returns `{code, message, details, path, timestamp}` | Components display `error?.error?.message` |
| Config | `envSchema` (zod) + `validateEnv` | None (consumed via bootstrap) |
# See also
- [Backend Module Map](/architecture/backend-modules.md)
- [Frontend Structure](/architecture/frontend-structure.md)
- [REST API Overview](/api/rest-overview.md)