Cloud Tower (CDA App) — Migration Assessment onto the CTN Stack
Scope note. This document assesses re-platforming the existing Cloud Tower / CDA app — the implementation of the 2026 use case “Visibility Achterland Containerlogistiek” — from its current Supabase backend onto the CTN preferred stack (Quarkus, PostgreSQL, Keycloak). It is a pre-scoping assessment, not a committed plan; effort figures are order-of-magnitude pending detailed estimation by the implementing team. The app currently lives in a separate (private) repository,
Basic-Data-Infrastructure/cloud-tower-beginnings.
Why this matters for CTN
Section titled “Why this matters for CTN”The Cloud Tower app already realises the 2026 use case — combining Portbase data with inland-terminal data into a dashboard for import containers moving over barge into the hinterland. It was originally generated on Supabase (PostgREST data API, Supabase Auth, Deno Edge Functions, Supabase Realtime, ~53 Row-Level-Security policies). Porting it onto the CTN stack converges the use-case dashboard with the target architecture and lets it consume the Association Register (ASR) and endpoint registration as a first-class data consumer.
Position in the CTN context
Section titled “Position in the CTN context”| CTN concept (see System Scope and Context) | Cloud Tower mapping |
|---|---|
| Use case “Visibility Achterland Containerlogistiek” | This app — the import-container hinterland dashboard |
| Data consumer | The app — it consumes Portbase (CargoController) and inland-terminal (ITV / Van Berkel) data |
| Data custodians / providers | Poort8 CargoController function (Portbase), Van Berkel ITV (via iSHARE) |
| Association Register (ASR) | Participant onboarding, authentication of authorised representatives, M2M-credential issuance — the app’s external calls should obtain credentials this way instead of hardcoded keys |
| Endpoint registration | Where the app registers as a consumer, obtains OAuth client credentials, and discovers provider endpoints |
| Keycloak (IAM) | Replaces Supabase Auth for human login |
| Local authorisation (VAD-only, 2026) | The app enforces authorisation locally — exactly where the current RLS logic must move (into the Quarkus app layer). No central PDP/OPA in 2026 scope. |
Assessment summary
Section titled “Assessment summary”Porting is feasible and medium-sized — a backend re-platform, not a rewrite. The database is already PostgreSQL, and the heavy business logic (demurrage/detention calculation, Excel import/deduplication, release tri-state) lives in the React frontend, so both move almost for free. Effort concentrates in three areas:
- Data API (🔴 largest). Replace the auto-generated Supabase PostgREST API with hand-written Quarkus REST endpoints (
quarkus-rest+ Hibernate ORM/Panache) and rewrite the frontend data hooks to call them. Frontend data access is centralised in a handful of hooks/pages, which bounds the surface. - Security / authorisation (🔴 highest risk). ~53 Postgres RLS policies are silently bypassed the moment a Quarkus service connects with a privileged role. They must be re-expressed in the app layer (JAX-RS
@RolesAllowed+ service-layer visibility/ownership checks), with parity tests asserting each former RLS rule before cutover. This matches the CTN 2026 “local authorisation at the data provider” model. - Realtime (🔴). Supabase
postgres_changeschannels (live per-row sync spinners, live navigation counts) → Quarkus SSE/WebSocket backed by PostgresLISTEN/NOTIFY(or CDC).
Several remaining concerns are net simplifications on the CTN stack:
- The custom branded-auth-email function is deleted — Keycloak renders branded auth emails via themes (the repo’s
keycloak-theme/module can be reused). - Human login moves to Keycloak OIDC (
quarkus-oidc), backed by eHerkenning per the CTN model; OTP / magic-link are native Keycloak features. - Roles (
user_roles+ ahas_role()DB function) become Keycloak realm/client roles carried in the token. - External-API credentials (today hardcoded Portbase key + iSHARE client credentials) become M2M credentials issued via the ASR, with endpoints discovered via endpoint registration — removing hardcoded secrets and aligning with CTN.
- The sync edge functions (
portbase-sync,itv-sync,scheduled-sync, queue/trigger functions) become Quarkus@Scheduledjobs + MicroProfile REST clients; logic ports cleanly (Deno-TS → Java is mechanical). - Email decouples from the current third-party (Lovable) email API to a standard provider via
quarkus-mailer. - Telemetry moves from a hardcoded Azure App Insights connection string to native OpenTelemetry/Micrometer.
Recommended approach
Section titled “Recommended approach”A strangler-fig migration keeps the dashboard shippable throughout:
- Phase 0 — Stand up the Quarkus service alongside Supabase; Liquibase owns a copy of the schema (TDR-0001); read-only
containersendpoints; validate Keycloak login against a CTN realm. - Phase 1 — Auth cutover to Keycloak/OIDC; migrate roles into Keycloak. (Lowest-risk visible change; unblocks the rest.)
- Phase 2 — Move reads (dashboards, counts, tracking) to Quarkus REST + SSE; re-implement visibility/role checks with RLS-parity tests.
- Phase 3 — Move writes and the sync jobs; switch external calls to ASR-issued M2M credentials; retire the third-party email path.
- Phase 4 — Decommission Supabase; final data cutover.
Order of magnitude: a few weeks for an experienced Quarkus team, gated by Phases 2–3 (data API + RLS parity + realtime). The repo’s asr-service provides a working Quarkus + Keycloak template to clone conventions from.
Key risks
Section titled “Key risks”- 🔴 Invisible RLS. Most access control is currently enforced in the database, not in code. Migrating to a privileged Quarkus DB connection removes that protection unless re-implemented and parity-tested first. This is the item most likely to introduce a security gap if rushed.
- 🔴 Realtime is a feature, not polish. Per-row sync feedback and live counts depend on it; the SSE +
LISTEN/NOTIFYreplacement needs deliberate design (reconnection, fan-out, stream auth). - 🟡
auth.uid()coupling. Migrations/policies reference Supabase’sauthschema; these must be translated to app-supplied identity during the Liquibase port. - 🟡 Two identity models. Human representatives (eHerkenning via Keycloak) vs. machine sync (M2M via ASR) — keep them distinct in the token/authorisation design.
- 🟡 iSHARE specifics carry over. The inland-terminal sync already tolerates iSHARE
403 "No Policy"; preserve that when re-issuing the call via ASR-managed credentials.
Open questions for the architecture board
Section titled “Open questions for the architecture board”- Confirm the CTN model for this app: data-consumer registration + M2M credentials via the ASR for the Portbase / inland-terminal (iSHARE) calls.
- Confirm Keycloak handles human login (eHerkenning-backed) for the dashboard’s operator/admin roles.
- Confirm Liquibase ownership of the ported schema (TDR-0001).
- Decide timing relative to the 2026 ASR / endpoint-registration deliverables.
References
Section titled “References”- CTN System Scope and Context
- ADR-00002 — Preferred stack: Quarkus / PostgreSQL / Keycloak
- Solution Strategy · Building Blocks · Deployment
- Full technical inventory (per-file change list, backend building blocks) lives in the app repo at
docs/MIGRATION-Quarkus-Keycloak.md(Basic-Data-Infrastructure/cloud-tower-beginnings, private).
In samenwerking met




