Tech Stack
The full technology stack powering Mithaq Branch Teller.
Frontend
Next.js 16.2 · React 19 · Tailwind CSS 4
App Router with server components and TypeScript strict mode. Runs inside Docker on the Synology NAS. Browser calls relative /api/*; Next.js rewrites to teller_api_dev:5200 server-side — NEXT_PUBLIC_API_URL intentionally unset.
API
.NET 9 · Minimal API · EF Core 9
Endpoint-based routing — no controllers. Entity Framework Core manages schema migrations; MigrateAsync runs on startup. Three auth modes: USE_DEV_AUTH=true → USE_KEYCLOAK_AUTH=true → Entra ID (prod). Priority is evaluated in that order.
Database
PostgreSQL 15 · teller_refactor
Three schemas: core (transactional state — Drawers, Vaults, BranchApprovals), admin (config — Branches, ApprovalPolicies, CashLimits), audit (immutable logs — BranchAuditLogs). All monetary columns are numeric(19,6). Per-entity DB separation via TellerDb-{EntityId} connection string — zero code change required.
Auth
Keycloak 26.2 · PKCE · realm: mithaq-bank
Browser uses PKCE authorization code flow — no client secret in the browser. Custom JWT claims: bank_entity, bank_branch_id. Split-brain fix: internal Docker URL used for JWKS fetch; public DDNS URL appears in the token's iss claim. IssuerSigningKeyResolver handles the mapping with a 10-min key cache.
Policy
OPA sidecar · banking.rego · ABAC
Open Policy Agent sidecar gates all POST /transactions/*, /vault/*, /branch/*. Cash limits are data-driven per entity + currency, pushed from PostgreSQL to OPA at startup and after every admin mutation. Hard-deny when OPA is unreachable (fail-closed). Rego policy live-editable via the Admin Portal.
Observability
OpenTelemetry · SigNoz v0.125.1 EE
.NET API exports traces + metrics + logs to SigNoz via gRPC :4317; EF Core SQL queries are visible as spans (SetDbStatementForText=true). Next.js exports via HTTP :4318 (@vercel/otel). Keycloak traces via KC_TRACING_ENABLED. PostgreSQL metrics via otel-collector scraping :5432 every 30s. 3 dashboards, 5 alert rules, syslog container logs from all services.
ESB
.NET 9 mock · port 5100 · CBS adapter layer
Enterprise Service Bus decouples the teller platform from the Core Banking System. The mock simulates CBS responses for full end-to-end testing. EsbClientFactory resolves the correct EsbClient per entity from config (Esb__Entities__{EntityId}__BaseUrl). Pluggable — swap the mock for any real CBS adapter without touching the teller API.
Deployment & Infrastructure
Runtime
All services run in Docker containers managed by Compose on a Synology NAS. Services share a banking-net Docker network. Never start app, API, web, Keycloak, or OPA locally.
E2E Testing
Playwright suite runs on an OCI cloud VM (VM.Standard.E5.Flex, 4 OCPU / 48 GB, Abu Dhabi region). Auto-started, tested against the live Synology deployment, then stopped per run to save cost.
Service Ports
API: 5200 · Web: 3012 (Next.js 3002 inside) · ESB mock: 5100 · Keycloak: 8871 · OPA: 8181 · SigNoz: 3301
Keycloak split-brain
KEYCLOAK_AUTHORITY is the internal Docker URL (for JWKS fetch); Keycloak__Issuer is the external DDNS URL (appears in the token iss claim). Both env vars must remain set — removing either breaks auth.