Structure & stack¶
Directory layout¶
app/
(public)/ Public status page, history, service detail, badge,
legal pages, subscribe/verify, unsubscribe, my data
admin/
login/ Login page (no guard)
(dashboard)/ Protected admin pages with the navigation layout
sla-reports/ SLA reports; route handlers [id]/pdf, [id]/xlsx and adhoc
api/
status/ Public status API (JSON)
health/ Infrastructure health check
outage/ Status change routes for API tokens
incidents/ Create incidents via API token
maintenances/ Create maintenance windows via API token
api-docs/ Swagger UI for the OpenAPI document
badge.svg/ Dynamic SVG status badge
feed.xml/ RSS feed of incidents
components/ Status page UI and admin widgets
components/ui/ shadcn/ui components (Base UI)
instrumentation.ts Startup check when the server starts (SESSION_SECRET)
lib/
actions/ Server Actions (return ActionResult<T>)
api/ JSON response helpers for API routes
audit/ Audit log writer and wrapper
auth/ Passwords, sessions, role guards, API token checks, rate limiting
crypto/ AES-256-GCM helpers for secrets
db/
schema.ts Drizzle schema and enum constants (source of domain types)
queries/ Database access
seed.ts Demo data
domain/ Business logic (status, uptime, SLA, timezones, roles)
email/ E-mail templates, transports, processor
env/ Environment checks at startup
export/ SLA export: context loading, Excel workbook, response, texts en/de
notifications/ Dispatch, queues, schedulers, uptime checker
pdf/ SLA report PDF generation
push/ sms/ webhooks/ Channel-specific transports, payload builders, processors
validations/ Zod schemas
viewer-timezone.ts Visitor time zone toggle (logic and store)
i18n/ next-intl request configuration
messages/ en.json and de.json
scripts/ Worker, migrations, backup/restore, seeding, helper scripts
drizzle/ SQL migrations and drizzle-kit metadata
tests/ Unit tests, integration tests, factories
e2e/ Playwright specs
public/ Static files, openapi.json, service worker for push
Layering rules¶
The code follows a strict direction of dependencies:
- Pages and routes (Server Components, route handlers) read data and render. They call queries or domain functions.
- Server Actions (
lib/actions/) handle forms. They check the role, validate input with Zod, run the query inside a transaction, and callrevalidatePath. - Queries (
lib/db/queries/) bundle the database access. - Domain (
lib/domain/) contains mostly pure functions without I/O, for example uptime calculation, SLA windows, severity ordering and timezone conversion. They are unit-testable without a database.
A few places access the database directly on purpose: applyComponentStatusChange (lib/domain/component-status.ts) opens the transaction for status changes, the e-mail processor (lib/email/processor.ts) writes the delivery status, and /api/health runs a SELECT 1.
A typical form submission looks like this:
Form → Server Action → role gate (getCurrentUser + ROLES_CAN_*)
→ Zod validation → query in transaction → revalidatePath
Public page data flow¶
Page (Server Component) → getStatusPageData() → queries → PostgreSQL
getStatusPageData() bundles all queries needed for the public page, so the page stays a thin rendering layer.
Conventions¶
- Input from forms and APIs is untrusted and always parsed with Zod.
- Logging uses the built-in logger in
lib/log.ts: structured JSON in production, pretty output in development.console.*is only used in error boundaries and CLI scripts. - Audit entries are written with
logAudit, which never throws. A failed audit write must not fail the business action. - Time zones: values are stored as
timestamptz. Display usessettings.timezoneandsettings.display_timezone, and wall-clock input is converted with DST awareness (lib/domain/timezone.ts). On the public portal, visitors can switch to their browser zone. The choice is stored in the browser (localStoragetz-mode); the server always renders in the app zone. - Date formatting without an explicit locale and time zone (bare
toLocale*) is forbidden, because it causes hydration errors. A unit test checks this forapp/,components/andlib/.