From 4997f7a6a9aaf137cfb40bacd12b18139f63380c Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Sun, 5 Apr 2026 23:06:41 +0200 Subject: [PATCH] feat: move SaaS app to /platform base path, Logto becomes catch-all MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eliminates all Logto path enumeration in Traefik. Routing is now: - /platform/* → cameleer-saas (SPA + API) - /server/* → server-ui - /* (catch-all) → Logto (sign-in, OIDC, assets, everything) Spring context-path handles backend prefix transparently. No changes needed in controllers, SecurityConfig, or interceptors. Co-Authored-By: Claude Opus 4.6 (1M context) --- docker-compose.yml | 16 ++++++---------- docker/logto-bootstrap.sh | 4 ++-- src/main/resources/application-test.yml | 4 ++++ src/main/resources/application.yml | 4 ++++ ui/src/api/client.ts | 2 +- ui/src/config.ts | 2 +- ui/src/main.tsx | 2 +- ui/vite.config.ts | 1 + 8 files changed, 20 insertions(+), 15 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2c1c188..c4f2865 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -76,7 +76,8 @@ services: start_period: 15s labels: - traefik.enable=true - - traefik.http.routers.logto.rule=PathPrefix(`/oidc`) || PathPrefix(`/interaction`) || PathPrefix(`/assets`) || PathPrefix(`/sign-in`) || PathPrefix(`/register`) || PathPrefix(`/consent`) || PathPrefix(`/single-sign-on`) || PathPrefix(`/social`) || PathPrefix(`/unknown-session`) || PathPrefix(`/api/interaction`) || PathPrefix(`/api/experience`) || PathPrefix(`/api/.well-known`) + - traefik.http.routers.logto.rule=PathPrefix(`/`) + - traefik.http.routers.logto.priority=1 - traefik.http.routers.logto.entrypoints=websecure - traefik.http.routers.logto.tls=true - traefik.http.services.logto.loadbalancer.server.port=3001 @@ -142,15 +143,10 @@ services: CLICKHOUSE_URL: jdbc:clickhouse://clickhouse:8123/cameleer labels: - traefik.enable=true - - traefik.http.routers.api.rule=PathPrefix(`/api`) - - traefik.http.routers.api.entrypoints=websecure - - traefik.http.routers.api.tls=true - - traefik.http.routers.api.service=spa - - traefik.http.routers.spa.rule=PathPrefix(`/`) - - traefik.http.routers.spa.priority=1 - - traefik.http.routers.spa.entrypoints=websecure - - traefik.http.routers.spa.tls=true - - traefik.http.services.spa.loadbalancer.server.port=8080 + - traefik.http.routers.saas.rule=PathPrefix(`/platform`) + - traefik.http.routers.saas.entrypoints=websecure + - traefik.http.routers.saas.tls=true + - traefik.http.services.saas.loadbalancer.server.port=8080 networks: - cameleer diff --git a/docker/logto-bootstrap.sh b/docker/logto-bootstrap.sh index e843e0c..9504460 100644 --- a/docker/logto-bootstrap.sh +++ b/docker/logto-bootstrap.sh @@ -43,8 +43,8 @@ SERVER_UI_PASS="${SERVER_UI_PASS:-admin}" # Redirect URIs (derived from PUBLIC_HOST and PUBLIC_PROTOCOL) HOST="${PUBLIC_HOST:-localhost}" PROTO="${PUBLIC_PROTOCOL:-https}" -SPA_REDIRECT_URIS="[\"${PROTO}://${HOST}/callback\"]" -SPA_POST_LOGOUT_URIS="[\"${PROTO}://${HOST}/login\"]" +SPA_REDIRECT_URIS="[\"${PROTO}://${HOST}/platform/callback\"]" +SPA_POST_LOGOUT_URIS="[\"${PROTO}://${HOST}/platform/login\"]" TRAD_REDIRECT_URIS="[\"http://${HOST}:8081/oidc/callback\"]" TRAD_POST_LOGOUT_URIS="[\"http://${HOST}:8081\"]" diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index bcbe85f..587deb5 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -1,3 +1,7 @@ +server: + servlet: + context-path: /platform + spring: jpa: show-sql: false diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 49b29f1..417038e 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,7 @@ +server: + servlet: + context-path: /platform + spring: application: name: cameleer-saas diff --git a/ui/src/api/client.ts b/ui/src/api/client.ts index e15d0b5..401a99f 100644 --- a/ui/src/api/client.ts +++ b/ui/src/api/client.ts @@ -1,4 +1,4 @@ -const API_BASE = '/api'; +const API_BASE = '/platform/api'; let tokenProvider: (() => Promise) | null = null; diff --git a/ui/src/config.ts b/ui/src/config.ts index 63643d8..0d6c69a 100644 --- a/ui/src/config.ts +++ b/ui/src/config.ts @@ -11,7 +11,7 @@ export async function fetchConfig(): Promise { if (cached) return cached; try { - const response = await fetch('/api/config'); + const response = await fetch('/platform/api/config'); if (response.ok) { cached = await response.json(); return cached!; diff --git a/ui/src/main.tsx b/ui/src/main.tsx index 2797c4f..330fc16 100644 --- a/ui/src/main.tsx +++ b/ui/src/main.tsx @@ -80,7 +80,7 @@ function App() { > - + diff --git a/ui/vite.config.ts b/ui/vite.config.ts index 66aac33..9b53bdb 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -12,6 +12,7 @@ export default defineConfig({ }, }, }, + base: '/platform/', build: { outDir: 'dist', emptyOutDir: true,