Skip to main content

Partner-API v1 (Manager-App / MallCrew)

Was

Partner-API v1 ist die authentifizierte REST-Schicht im Cockpit-Dashboard für die Partner-Manager-App (UI in v0 auf Vercel, Produktname MallCrew). Partner und Mieter reichen Medien mit Metadaten ein; KAM/Redaktion bearbeitet die Warteschlange im Cockpit-Reiter Medien-Einreichungen.

CockpitOS = Source of Truth — Layout und Branding in v0; Inhalt, Status, Kommentare und Aufgaben im Cockpit.

Warum

Center-Ansprechpartner sollen ohne Dashboard-Zugang strukturiert Fotos/Videos einreichen. Die Manager-App darf keine Bunny-Keys oder Cockpit-Secrets im Browser halten — deshalb Proxy-Routes in der v0-App und Bearer-Token nur serverseitig.

Wer ist betroffen

RolleNutzen
Center Manager, Mieter, FacilityEinreichung über MallCrew (v0)
KAM / RedaktionInbox, Kommentare, Aufgaben im Cockpit
Entwickler / v0Proxy unter /api/*, siehe unten
AgentenAgencyOS + MCP (cockpit_list_media_intake, …)

Wo

KomponentePfad / URL
Prod MallCrew (v0/Vercel)https://crew.cockpit-os.de
Monoreposmg-mall-cms (lokal z. B. ~/Documents/Web/smg-mall-cms Kopie 2)
Prod Cockpithttps://dashboard.cockpit-os.de
Partner-API (Code)apps/dashboard/src/app/api/partner/v1/
Proxy-Referenzapps/partner-manager/ (Next.js, Port 3010 dev)
v0-Prompt (Dropzone)docs/v0-partner-media-intake-prompt.md
v0-Prompt (MallCrew Branding)docs/v0-mallcrew-prompt.md
Handoff-Checklistedocs/PARTNER-MANAGER-HANDOFF.md
Smoke-Testscripts/smoke-partner-v1-api.mjspnpm smoke:partner-v1

Architektur

Entscheidungen (verbindlich)

  1. Center-Liste — in Login-Response (user.centerAssignments), kein separater Centers-Endpunkt für Partner.
  2. Shops — lazy pro Center: GET /api/partner/v1/centers/{centerId}/shops.
  3. Browser → nie direkt Cockpit — v0/MallCrew ruft nur same-origin /api/* auf; Next.js-Proxy setzt Authorization: Bearer serverseitig.
  4. SessionHttpOnly-Cookie partner_session in der Manager-App (7 Tage), kein localStorage für Tokens.

Upload-Flow

MallCrew (v0) POST /api/media/upload?centerId=
→ Proxy POST /api/partner/v1/media/upload?centerId=
→ Bunny centers/{centerId}/media-intake/
→ MediaFile (Postgres)
Einreichung mit mediaItems[] (Referenzen, kein Re-Upload)

Limits: Bild max. 10 MB, Video max. 100 MB.

Token-Präfix mi_ — Proxy setzt Header X-Intake-Token Richtung Cockpit:

  • GET/POST /api/partner/v1/intake/submit
  • POST /api/partner/v1/intake/upload

Authentifizierung

Login

POST /api/partner/v1/auth/login

Body: { "email", "password" }

Response (200): { success, partnerToken, user: { id, name, email, centerAssignments: [{ centerId, centerName, role }] } }

Manager-App: Proxy speichert partnerToken im HttpOnly-Cookie; Client erhält kein Token im JSON.

Berechtigte Rollen

Nur Rollen aus canAccessCenterManagerApp (apps/dashboard/src/lib/permissions.ts):

  • center_manager
  • cm_assistant
  • facility_manager
  • facility_technician
  • tenant

Zusätzlich: Status Aktiv, mindestens eine Center-Zuordnung.

403 für z. B. Super Admin, Center Admin, Content Editor — Meldung: „Keine Berechtigung für Partner-App“.

Session prüfen

GET /api/partner/v1/auth/me — Header Authorization: Bearer {partnerToken}

Response (Auszug):

{
"user": {
"id": "…",
"email": "…",
"name": "…",
"role": "center_manager",
"centerAssignments": [
{ "centerId": "…", "centerName": "…", "centerRole": "center_manager" }
]
}
}

role entspricht dem Login-Feld (toCenterManagerAppRole) — für MallCrew-Personas und Navigation nutzen.

Routen (Partner-v1)

Basis: {DASHBOARD_ORIGIN}/api/partner/v1

MethodePfadZweck
POST/auth/loginLogin
GET/auth/meSession
POST/media/upload?centerId=Multipart-Upload → Bunny + MediaFile
GET/app/helpHilfe — FAQ, Support, Quick-Links
GET/centers/{centerId}/profileProfil — User, Center, Links, Berechtigungen
GET/centers/{centerId}/reports/summary?period=Berichte — today | week | month (Center-Team, nicht Mieter)
GET/centers/{centerId}/shopsShop-Dropdown (limit, offset, q)
GET/centers/{centerId}/shops/{shopId}Shop-Detail (read-only; Mieter nur eigener Shop)
GET/centers/{centerId}/eventsEvents-Liste (q, upcoming, limit, offset)
GET/centers/{centerId}/events/{eventId}Event-Detail
GET/centers/{centerId}/offersAngebote-Liste (q, active, limit, offset)
GET/centers/{centerId}/offers/{offerId}Angebot-Detail
GET/centers/{centerId}/media-intakeListe (status, limit, offset)
POST/centers/{centerId}/media-intakeEinreichung oder Entwurf (saveAsDraft: truestatus: draft)
GET/centers/{centerId}/media-intake/{submissionId}Detail inkl. Kommentare (Mieter-sichtbar)
PATCH/centers/{centerId}/media-intake/{submissionId}Entwurf bearbeiten; submit: true → Einreichung
DELETE/centers/{centerId}/media-intake/{submissionId}Entwurf löschen
GET/POST…/media-intake/{submissionId}/commentsKommentar-Thread
POST/ai/improve-textKI-Textverbesserung — Body: centerId, text, context?, contributionType?
POST/ai/chatKI-Assistent — Body: centerId, query, optional confirmExecution, intent (Bestätigung)
GET/centers/{centerId}/tasksPartner-Aufgaben (aktiv)
POST/centers/{centerId}/tasks/{taskId}/ackAufgabe bestätigen
GET/centers/{centerId}/todayTagesübersicht — Besucher (Footfall), Parken, Issues, Events, Beiträge
GET/centers/{centerId}/issuesStörungen/Issues (status, priority, limit)
GET/centers/{centerId}/issues/{issueId}Störung Detail inkl. Kommentare
PATCH/centers/{centerId}/issues/{issueId}/statusStatus setzen — in_progress, resolved, closed (+ optional comment, resolution, mediaFileId)
POST/centers/{centerId}/issuesStörung melden — Body: title, description?, location?, priority?, category?
GET/centers/{centerId}/facility/overviewWartungs-KPIs (Issues + Gebäudesysteme) — nicht für Mieter
GET/centers/{centerId}/facility/systemsAnlagen-Übersicht — nicht für Mieter
GET/centers/{centerId}/reservations?pickupCode=Reserve & Collect: Abholcode suchen (auth + Shop-Scope für Mieter)
PATCH/centers/{centerId}/reservations/{id}/statusAbholung/Bezahlung — Body: { status: picked_up | paid | ready_for_pickup }
GET/centers/{centerId}/reserve-campaigns/report?shopId=Kampagnen-Auswertung (Mieter: eigener Shop per E-Mail-Match)
GET/POST/intake/submitGast: Metadaten / Einreichung
POST/intake/uploadGast: Upload

Beitragstyp (contributionType)

opening | promotion | event | hiring | renovation | other

Status (status)

draft | new | in_review | accepted | rejected | published


Proxy-Routes (MallCrew / v0)

Pflicht in der Vercel-App — Spiegel unter apps/partner-manager/src/app/api/:

MallCrew (same-origin)Cockpit Partner-v1
POST /api/auth/loginPOST …/auth/login + Cookie
GET /api/auth/meGET …/auth/me
POST /api/auth/logoutCookie löschen
POST /api/media/upload?centerId=Streaming (duplex: 'half')
GET /api/app/helpHilfe / FAQ
GET /api/centers/{id}/profileProfil & Links
GET /api/centers/{id}/reports/summaryBerichte (Center-Team)
GET /api/centers/{id}/shopsGET …/shops
GET /api/centers/{id}/shops/{shopId}Shop-Detail (read-only)
GET /api/centers/{id}/eventsEvents-Liste
GET /api/centers/{id}/events/{eventId}Event-Detail
GET /api/centers/{id}/offersAngebote-Liste
GET /api/centers/{id}/offers/{offerId}Angebot-Detail
GET/POST /api/centers/{id}/media-intake
GET/PATCH/DELETE /api/centers/{id}/media-intake/{id}
GET/POST …/media-intake/{id}/comments
GET /api/centers/{id}/tasksGET …/tasks
POST /api/centers/{id}/tasks/{taskId}/ackPOST …/tasks/{id}/ack
GET /api/centers/{id}/todayTagesübersicht (Analytics/Today)
GET/POST /api/centers/{id}/issuesStörungen melden / liste (Facility)
GET /api/centers/{id}/issues/{issueId}Störung Detail
PATCH /api/centers/{id}/issues/{issueId}/statusStörung bearbeiten / abschließen
GET /api/centers/{id}/facility/overviewWartungs-KPIs
GET /api/centers/{id}/facility/systemsGebäudetechnik / Anlagen
GET /api/centers/{id}/reservations?pickupCode=Abholcode suchen (R&C)
PATCH /api/centers/{id}/reservations/{id}/statusAbgeholt / bezahlt markieren
GET /api/centers/{id}/reserve-campaigns/reportKampagnen-Statistik (Mieter)
POST /api/ai/improve-textPOST …/ai/improve-text
POST /api/ai/chatKI-Assistent (Antwort + bestätigte Aktionen)
GET/POST /api/intake/submit, POST /api/intake/uploadGast-Flow

AgencyOS & MCP

Redaktion/Agenten nutzen AgencyOS (Bearer API-Key), nicht Partner-Login:

AgencyOSBeschreibung
GET/POST /api/agencyos/v1/centers/{centerId}/media-intakeListe / anlegen
PATCH …/media-intake/{submissionId}Status / Notiz
GET/POST /api/agencyos/v1/centers/{centerId}/partner-tasksAufgaben

MCP: cockpit_list_media_intake, cockpit_create_media_intake, cockpit_update_media_intake_status, cockpit_create_partner_task

Details: AgencyOS-Integration


So testen

  1. Migrationen (additive, kein Datenverlust):
    pnpm db:migrate:media-intake
    pnpm db:migrate:mallcrew-extensions
  2. Test-User im Cockpit: Einstellungen → Benutzer — Partner-Rolle + Center-Zuordnung + Aktiv.
  3. Smoke gegen Prod:
    COCKPIT_API_URL=https://dashboard.cockpit-os.de \
    PARTNER_EMAIL=… PARTNER_PASSWORD=… \
    pnpm smoke:partner-v1
  4. E2E: Login MallCrew → Center → Upload → Submit → Cockpit-Reiter Medien-Einreichungen → Eintrag sichtbar.
  5. Login-Curl (ohne Manager-App):
    curl -s -X POST https://dashboard.cockpit-os.de/api/partner/v1/auth/login \
    -H 'Content-Type: application/json' \
    -d '{"email":"…","password":"…"}'

Betrieb

Cockpit (Render)

VariableZweck
DATABASE_URLPostgres
BUNNY_STORAGE_*, BUNNY_CDN_URLMedien-Upload
COCKPIT_PARTNER_API_TOKEN_SECRETPartner-Bearer-Signatur (Fallback: NEXTAUTH_SECRET)
PARTNER_AI_GATEWAY_URL / PARTNER_AI_GATEWAY_API_KEYOptional — KI „Text verbessern"
NEXT_PUBLIC_PARTNER_INTAKE_APP_URLhttps://crew.cockpit-os.de — Einreich-Links (mi_)

Migrationen laufen im Render-Build über packages/database/scripts/db-push-render.sh.

MallCrew / v0 (Vercel, server-only)

VariableWert
COCKPIT_PARTNER_API_BASEhttps://dashboard.cockpit-os.de
SESSION_SECRETopenssl rand -base64 32nur Manager-App

Alias in Referenz-App: COCKPIT_API_URL.

Nicht in v0 committen: Bunny-Keys, COCKPIT_PARTNER_API_TOKEN_SECRET, Passwörter.


Datenbank

MigrationInhalt
20260828130000_add_media_intake_SAFE.sqlmedia_intake_submissions, media_intake_submit_links
20260828160000_add_mallcrew_extensions_SAFE.sqlcontributionType, conditions, Kommentare, Aufgaben

Schema-Referenz: db_structure.md im Repo-Root.


Siehe auch

Nutzungsstatistik: Seitenaufrufe werden anonymisiert erfasst. Im Umami-Dashboard nach diesem Pfad filtern: /en/developer-guide/partner-api-v1