ServiceHub
System Architecture · v0.1.1 · NestJS 11 · Next.js 16 · PostgreSQL 18 · Redis · BullMQ · Caddy
Multi-tenant ITSM
Clients
(web)
🧑‍💻EmployeeSubmits & tracks requests
🎧Help Desk AgentTriages, comments, resolves
📊IT Manager / AdminQueues, SLA, reports, admin
HTTPS · JWT bearer + httpOnly refresh cookie · WSS
Edge
🛡️Caddy — TLS edge / reverse proxy
Terminates TLS (HTTPS only). Routes /v1/* & /api/* → API; everything else → Web. Forces HTTP→HTTPS, sets HSTS, sizes large client-header buffers.
HTTPS onlyHTTP→HTTPS redirectHSTSSEC-14
Presentation
Next.js 16 — Web (apps/web)
App Router. Marketing landing + app shell (sidebar + header). 13 screens, light/dark.
TanStack Query v5Tailwind v4shadcn-style
🔌API client + Realtime
Typed fetch → /v1, Bearer + silent refresh; RFC 9457 errors mapped to fields. socket.io client for live updates.
INV-11/12 tokensINV-15 ui-coverage
📦packages/shared
DTOs, enums, ticket state machine, error contract constants — shared by web + API.
REST /v1 (JSON)
API request
pipeline
(apps/api)
⚙️NestJS 11 — every authenticated request flows top → bottom
1Helmet · CORS allowlist · Throttler (rate limit)CSP · nosniff · frame DENY
2JwtAuthGuard → Principal {userId, tenantId, role}@Public bypass
3RolesGuard — RBAC (@Roles)REQUESTER · AGENT · MANAGER · ADMIN
4TenantTxInterceptor — opens tx, SET LOCAL app.tenant_idINV-1 · ADR-01
5ValidationPipe (whitelist) — strips client SLA/audit fieldsSEC-11
6assertResourceAccess — object-level authz (404 to non-owner)INV-2 · SEC-2 / W3
7Domain servicesbusiness logic
Ticketsstate machine · @Version (409)
Commentsinternal vs public
Attachmentsmagic-byte · gated DL
SLAserver-side evaluator
Authargon2 · HIBP · anti-enum
Users / CatalogRBAC admin
Dashboard / Reportsmetrics · PDF/CSV/JSON
Notificationsin-app
Outboxemail + webhook
Auditappend-only
Webhooks (in)verify-before-read
Events Gatewaysocket.io /events
ProblemDetailsFilter — all errors as RFC 9457 application/problem+jsonINV-10 · ADR-07
TypeORM (pooled) · tenant tx · RLS enforced as servicehub_app
Data &
state
🐘PostgreSQL 18
15 tenant-scoped tables, all with tenant_id. Row-Level Security policies + FORCE RLS; app connects as non-owner servicehub_app. Optimistic @VersionColumn; UNIQUE dedup keys.
INV-3 RLSINV-4/5/6/7PITR backups
Redis 7
Auth rate-limit counters (ip,email), cache for SLA/category lookups, and the BullMQ queue backend. socket.io multi-pod adapter.
INV-9 rate-limitBullMQ broker
▲▼
outbox rows (same tx) · repeatable jobs · pub/sub
Async,
realtime &
external
🔁Worker (BullMQ)
Repeatable maintenance tick (30s): drains the email outbox (capped backoff → DLQ) and runs the SLA evaluator per tenant, emitting breach notifications.
ADR-04 outboxADR-05 SLA
📡EventsGateway
socket.io /events. JWT handshake + Origin check; per-room (tenant/ticket) authz. Pushes ticket/comment/SLA events back to clients.
SEC-12 · R9
✉️Resend (external)
Transactional email via the outbox (console stub when unconfigured). Inbound delivery webhooks verified HMAC-before-read & deduped.
INV-8 · SEC-8

🔄 Key data flows

1
Submit ticket (W1) — Browser → Caddy → API. Guards → tenant tx (SET LOCAL) → TicketsService: INSERT ticket + compute SLA + audit event + enqueue confirmation email — all in one transaction → commit → socket emit → 201.
2
Guaranteed email/webhook — Business change writes an outbox row in the same tx (never a synchronous send). The BullMQ worker drains it to Resend with idempotent UNIQUE keys + capped backoff → DLQ.
3
Realtime push — On ticket/comment/SLA changes the API emits to the tenant/ticket room; the socket.io client invalidates the relevant TanStack Query caches.
4
Inbound webhookPOST /v1/webhooks/resend: HMAC verify over the raw body before any DB read, then dedup via webhook_events UNIQUE, then process (idempotent).
5
Authorization isolation (W3) — RLS scopes every query to the tenant; assertResourceAccess adds row-ownership so a requester gets 404 on another user's ticket, sub-resources, and attachments.

🔐 Architectural invariants & boundaries

INV-1Tenant queries only via the tenant-transaction wrapper (SET LOCAL app.tenant_id).
INV-2Object-level authz chokepoint assertResourceAccess — 404 to non-owners.
INV-3RLS enabled + FORCED on all tenant tables; app is a non-owner role.
INV-4/5/6/7UNIQUE constraints: ticket #, email dedup, webhook dedup, user email.
INV-8Webhook signature verified before any DB read.
INV-9Auth rate-limit fires before the password hash compare.
INV-10All errors via the single RFC 9457 problem+json filter.
INV-11/12Design-token discipline; template :root copied verbatim.
INV-13argon2 password hashing (no substitution).
INV-14Attachments served only via the ownership-checked download controller.
INV-15UI coverage: every screen routable, every workflow has an e2e test.
← Gallery