Public Center-Website API — Gap Analysis & Priorities
Status: Analysis against the code in apps/center-website (mainly lib/server-data-loader.ts) and apps/dashboard/src/app/api. Goal: external frontend (e.g., v0 + Vercel) can retrieve all relevant reads via https://dashboard.cockpit-os.de (or configured DASHBOARD_API_URL) — without Prisma on the client app and without data duplication.
Legend
| Symbol | Meaning |
|---|---|
| CORS * | Access-Control-Allow-Origin: * (and typical OPTIONS) on GET responses |
| No CORS | JSON is delivered, browser cross-origin requests from e.g., *.vercel.app fail without a proxy |
| Auth | Endpoint expects a session / not for anonymous public access |
| Prisma CW | Center website currently loads directly from the DB or via its own /api routes of the same app (not the dashboard URL) |
Note: Server-side rendering on Vercel (only fetch from Node) requires no CORS. As soon as the colleague uses client components or browser fetch, CORS + OPTIONS are necessary on the dashboard.
Source of Truth: server-data-loader.ts
| Data / Feature | Current Path in Loader | Dashboard Equivalent (if available) |
|---|---|---|
| Shop Categories | Dashboard GET …/website-categories (Dogfooding; logic in @mall-os/database) | GET /api/categories (Dashboard list); Center website /api/categories = thin BFF |
| Page Content (incl. opening hours) | Dashboard GET …/page-content (Dogfooding) | GET CORS * |
| Services | Dashboard GET …/services?publicWebsite=true (Dogfooding) | Without flag: only isActive |
| Offices | Dashboard GET /api/offices?centerId=&status= (Dogfooding) | Local /api/offices optional for BFF |
| Homepage Tiles | Dashboard …/homepage-tiles | ✅ |
| Category Themes | Dashboard GET …/category-themes-for-website (Dogfooding; @mall-os/database) | …/category-themes = raw for cockpit UI |
| Office Themes | Dashboard GET …/office-themes (Dogfooding; isActive, only offices Active) | ✅ |
| Gastronomy Themes | Dashboard …/gastronomy-themes | ✅ |
| Shops | Dashboard GET …/shops?publicWebsite=true&status=… (Dogfooding; limit up to 5000) | Without publicWebsite: broader status filter (not inactive) |
| Current Events (News, Events, Offers, Jobs) | Dashboard GET …/aktuelles-bundle (Dogfooding; max 500 per type) | Individual routes …/news etc. for other clients |
| Construction Diary | Dashboard GET …/news?constructionDiary=true (Dogfooding in loader) | ✅ P1; local route /api/construction-diary remains in repo, SSR does not use it |
| Individual Offer | Dashboard GET …/offers?offerId= (Dogfooding in loader) | ✅ P1; local route /api/offers/[id] remains in repo, SSR does not use it |
| Center Plan / Floors | Dashboard wayfinding/centerplan + wayfinding/floors (Dogfooding in loader; media resolved as before in loader) | Center website /api/centers/centerplan = BFF (slug, combined response) |
Related Files (Center Website):
apps/center-website/lib/server-data-loader.ts— Main loaderapps/center-website/app/api/categories/route.tsapps/center-website/app/api/shops/route.tsapps/center-website/app/api/aktuelles/route.ts(Client/BFF)apps/center-website/app/api/construction-diary/route.tsapps/center-website/app/api/offers/[id]/route.tsapps/center-website/app/api/centers/centerplan/route.tsapps/center-website/middleware.ts— includingGET …/api/centers/by-domain
P0 — Status (Technical Foundation)
Implemented (CORS * + OPTIONS for Public Reads)
Shared module: apps/dashboard/src/lib/public-api-cors.ts.
| Area | File |
|---|---|
| Categories GET | apps/dashboard/src/app/api/categories/route.ts |
| Page Content GET | apps/dashboard/src/app/api/centers/[centerId]/page-content/route.ts (POST remains without broad CORS — protection against cross-origin write access) |
| Website Shop Categories GET | …/centers/[centerId]/website-categories |
| Category Themes (Website Payload) GET | …/centers/[centerId]/category-themes-for-website |
| Gastronomy/Category-(Admin)-/Office-Themes GET | …/gastronomy-themes, …/category-themes, …/office-themes |
| Center Plan GET | apps/dashboard/src/app/api/wayfinding/centerplan/route.ts |
| Floors GET | apps/dashboard/src/app/api/wayfinding/floors/route.ts |
| Routing POST + Touchscreen GET | apps/dashboard/src/app/api/wayfinding/routing/route.ts |
| Entrances GET | apps/dashboard/src/app/api/centers/[centerId]/entrances/route.ts |
| DOOH Public (Playlists / Player for v0) | …/centers/[centerId]/dooh/public/playlists, …/playlist, …/active, …/local-hero — see API Contract — DOOH |
Public Config Bundle (Whitelist, No website-config)
| Route | File |
|---|---|
GET /api/centers/[centerId]/public-visitor-surface | apps/dashboard/src/app/api/centers/[centerId]/public-visitor-surface/route.ts |
Delivers only explicitly allowed fields (branding, SEO, chatbot UI, center plan / wayfinding hints, apiHints). No API keys, no companionConfig, no analyticsPagePassword, no complete templateContent / themeOverrides.
Still Auth Required: GET /api/centers/[centerId]/website-config — for dashboard editors; external sites use public-visitor-surface + by-slug / theme-config as needed.
Chatbot (Visitor)
POST /api/ai/visitor-chatbot already had CORS * — conversation runs server-side in the dashboard (OpenAI key in the dashboard environment). Body: centerId and/or WordPress apiKey (see route documentation).
Already with CORS * (Reference): among others, …/[centerId]/shops, news, events, offers, jobs, services, homepage-tiles, hotpicks, by-slug/[slug], by-slug/[slug]/theme-config, by-domain, offices, visitor-chatbot.
P1 —Parity (Status)
| Topic | Implementation |
|---|---|
| Construction Diary | GET …/centers/[centerId]/news?constructionDiary=true (+ optional status) — including linkedShops, date range like website |
| Individual Offer | GET …/centers/[centerId]/offers?offerId={uuid} — websiteOfferPublicFilter, not To-Go-exclusive |
| Current Bundle | GET …/centers/[centerId]/aktuelles-bundle — CORS *, limits parameterizable (max 500 per type) |
| Hot Picks | GET …/centers/[centerId]/hotpicks — public, max. 50 active entries; path is also data.apiHints.hotPicksGet on public-visitor-surface |
| OpenAPI Snapshots | cockpit-docs/static/openapi/agencyos-integration.yaml + public-wayfinding-read.yaml (public wayfinding GETs); Markdown contract remains authoritative in case of discrepancies |
P2 — Quality, Dogfooding, Operations
| Topic | File / Location | Recommendation |
|---|---|---|
| Loader only via HTTP | apps/center-website/lib/server-data-loader.ts | Done: all previously mentioned reads including shop categories + category themes (website payload). Optional: OpenAPI |
| Rate Limiting / Abuse | public-visitor-rate-limit.ts + Redis (rate-limit.ts) | Done for: POST /api/ai/visitor-chatbot, POST /api/wayfinding/routing (per IP, 429 + CORS). Edge middleware remains generally; new expensive public POSTs checked accordingly. Env: COCKPIT_DISABLE_PUBLIC_VISITOR_RATE_LIMIT=1 |
| Media URLs | Loader resolveMediaUrl | External frontend: document the same URL resolution or dashboard delivers consistent absolute URLs |
AgencyOS /context vs. Floorplan | …/agencyos/v1/centers/{id}/context | include=floors_summary: compact floor without mapSvg body (length/hybrid heuristic, mapping counter). Full SVG: GET …/wayfinding/floors or MCP cockpit_public_wayfinding_floors. OpenAPI: /openapi/agencyos-integration.yaml (FloorsSummaryItem), /openapi/public-wayfinding-read.yaml. |
| AgencyOS Writing Website UI | Homepage tiles, page content, media library list | Done: …/homepage-tiles, …/page-content, GET …/media; MCP cockpit_homepage_tiles, cockpit_page_content, cockpit_list_media. Search including jobs/offices: cockpit_search_content. |
| SEO / Revalidate | apps/center-website/app/api/revalidate/route.ts | AgencyOS content_push automatically triggers revalidation (CENTER_WEBSITE_URLS + REVALIDATION_SECRET). External v0/Vercel: same route + secret + URL in CENTER_WEBSITE_URLS; MCP cockpit_revalidate_website. |
Short Checklist for Product/Tech Decision
- P0: Public read configuration (without session) + CORS on all endpoints that the browser accesses from a different origin.
- P1: Construction diary + offer detail + optionally aggregate + written API contract.
- P2: Switch center website to the same URLs and secure operations.
Related Packages / Configuration
packages/dashboard-api/src/index.ts—getDashboardApiUrl()(Env:DASHBOARD_API_URL,NEXT_PUBLIC_DASHBOARD_URL,NEXT_PUBLIC_API_URL)
Related Documentation
- Public Center-Website API — Contract (Read) — URLs, parameters, example JSON, v0 prompt
Nutzungsstatistik: Seitenaufrufe werden anonymisiert erfasst. Im Umami-Dashboard nach diesem Pfad filtern: /en/developer-guide/public-center-website-api-gap-analyse