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
| Rolle | Nutzen |
|---|---|
| Center Manager, Mieter, Facility | Einreichung über MallCrew (v0) |
| KAM / Redaktion | Inbox, Kommentare, Aufgaben im Cockpit |
| Entwickler / v0 | Proxy unter /api/*, siehe unten |
| Agenten | AgencyOS + MCP (cockpit_list_media_intake, …) |
Wo
| Komponente | Pfad / URL |
|---|---|
| Prod MallCrew (v0/Vercel) | https://crew.cockpit-os.de |
| Monorepo | smg-mall-cms (lokal z. B. ~/Documents/Web/smg-mall-cms Kopie 2) |
| Prod Cockpit | https://dashboard.cockpit-os.de |
| Partner-API (Code) | apps/dashboard/src/app/api/partner/v1/ |
| Proxy-Referenz | apps/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-Checkliste | docs/PARTNER-MANAGER-HANDOFF.md |
| Smoke-Test | scripts/smoke-partner-v1-api.mjs → pnpm smoke:partner-v1 |
Architektur
Entscheidungen (verbindlich)
- Center-Liste — in Login-Response (
user.centerAssignments), kein separater Centers-Endpunkt für Partner. - Shops — lazy pro Center:
GET /api/partner/v1/centers/{centerId}/shops. - Browser → nie direkt Cockpit — v0/MallCrew ruft nur same-origin
/api/*auf; Next.js-Proxy setztAuthorization: Bearerserverseitig. - Session — HttpOnly-Cookie
partner_sessionin der Manager-App (7 Tage), keinlocalStoragefü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.
Gast ohne Login (Token-Link)
Token-Präfix mi_ — Proxy setzt Header X-Intake-Token Richtung Cockpit:
GET/POST /api/partner/v1/intake/submitPOST /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_managercm_assistantfacility_managerfacility_techniciantenant
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
| Methode | Pfad | Zweck |
|---|---|---|
| POST | /auth/login | Login |
| GET | /auth/me | Session |
| POST | /media/upload?centerId= | Multipart-Upload → Bunny + MediaFile |
| GET | /app/help | Hilfe — FAQ, Support, Quick-Links |
| GET | /centers/{centerId}/profile | Profil — User, Center, Links, Berechtigungen |
| GET | /centers/{centerId}/reports/summary?period= | Berichte — today | week | month (Center-Team, nicht Mieter) |
| GET | /centers/{centerId}/shops | Shop-Dropdown (limit, offset, q) |
| GET | /centers/{centerId}/shops/{shopId} | Shop-Detail (read-only; Mieter nur eigener Shop) |
| GET | /centers/{centerId}/events | Events-Liste (q, upcoming, limit, offset) |
| GET | /centers/{centerId}/events/{eventId} | Event-Detail |
| GET | /centers/{centerId}/offers | Angebote-Liste (q, active, limit, offset) |
| GET | /centers/{centerId}/offers/{offerId} | Angebot-Detail |
| GET | /centers/{centerId}/media-intake | Liste (status, limit, offset) |
| POST | /centers/{centerId}/media-intake | Einreichung oder Entwurf (saveAsDraft: true → status: 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}/comments | Kommentar-Thread |
| POST | /ai/improve-text | KI-Textverbesserung — Body: centerId, text, context?, contributionType? |
| POST | /ai/chat | KI-Assistent — Body: centerId, query, optional confirmExecution, intent (Bestätigung) |
| GET | /centers/{centerId}/tasks | Partner-Aufgaben (aktiv) |
| POST | /centers/{centerId}/tasks/{taskId}/ack | Aufgabe bestätigen |
| GET | /centers/{centerId}/today | Tagesübersicht — Besucher (Footfall), Parken, Issues, Events, Beiträge |
| GET | /centers/{centerId}/issues | Störungen/Issues (status, priority, limit) |
| GET | /centers/{centerId}/issues/{issueId} | Störung Detail inkl. Kommentare |
| PATCH | /centers/{centerId}/issues/{issueId}/status | Status setzen — in_progress, resolved, closed (+ optional comment, resolution, mediaFileId) |
| POST | /centers/{centerId}/issues | Störung melden — Body: title, description?, location?, priority?, category? |
| GET | /centers/{centerId}/facility/overview | Wartungs-KPIs (Issues + Gebäudesysteme) — nicht für Mieter |
| GET | /centers/{centerId}/facility/systems | Anlagen-Ü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}/status | Abholung/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/submit | Gast: Metadaten / Einreichung |
| POST | /intake/upload | Gast: 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/login | POST …/auth/login + Cookie |
GET /api/auth/me | GET …/auth/me |
POST /api/auth/logout | Cookie löschen |
POST /api/media/upload?centerId= | Streaming (duplex: 'half') |
GET /api/app/help | Hilfe / FAQ |
GET /api/centers/{id}/profile | Profil & Links |
GET /api/centers/{id}/reports/summary | Berichte (Center-Team) |
GET /api/centers/{id}/shops | GET …/shops |
GET /api/centers/{id}/shops/{shopId} | Shop-Detail (read-only) |
GET /api/centers/{id}/events | Events-Liste |
GET /api/centers/{id}/events/{eventId} | Event-Detail |
GET /api/centers/{id}/offers | Angebote-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}/tasks | GET …/tasks |
POST /api/centers/{id}/tasks/{taskId}/ack | POST …/tasks/{id}/ack |
GET /api/centers/{id}/today | Tagesübersicht (Analytics/Today) |
GET/POST /api/centers/{id}/issues | Störungen melden / liste (Facility) |
GET /api/centers/{id}/issues/{issueId} | Störung Detail |
PATCH /api/centers/{id}/issues/{issueId}/status | Störung bearbeiten / abschließen |
GET /api/centers/{id}/facility/overview | Wartungs-KPIs |
GET /api/centers/{id}/facility/systems | Gebäudetechnik / Anlagen |
GET /api/centers/{id}/reservations?pickupCode= | Abholcode suchen (R&C) |
PATCH /api/centers/{id}/reservations/{id}/status | Abgeholt / bezahlt markieren |
GET /api/centers/{id}/reserve-campaigns/report | Kampagnen-Statistik (Mieter) |
POST /api/ai/improve-text | POST …/ai/improve-text |
POST /api/ai/chat | KI-Assistent (Antwort + bestätigte Aktionen) |
GET/POST /api/intake/submit, POST /api/intake/upload | Gast-Flow |
AgencyOS & MCP
Redaktion/Agenten nutzen AgencyOS (Bearer API-Key), nicht Partner-Login:
| AgencyOS | Beschreibung |
|---|---|
GET/POST /api/agencyos/v1/centers/{centerId}/media-intake | Liste / anlegen |
PATCH …/media-intake/{submissionId} | Status / Notiz |
GET/POST /api/agencyos/v1/centers/{centerId}/partner-tasks | Aufgaben |
MCP: cockpit_list_media_intake, cockpit_create_media_intake, cockpit_update_media_intake_status, cockpit_create_partner_task
Details: AgencyOS-Integration
So testen
- Migrationen (additive, kein Datenverlust):
pnpm db:migrate:media-intakepnpm db:migrate:mallcrew-extensions
- Test-User im Cockpit: Einstellungen → Benutzer — Partner-Rolle + Center-Zuordnung + Aktiv.
- Smoke gegen Prod:
COCKPIT_API_URL=https://dashboard.cockpit-os.de \PARTNER_EMAIL=… PARTNER_PASSWORD=… \pnpm smoke:partner-v1
- E2E: Login MallCrew → Center → Upload → Submit → Cockpit-Reiter Medien-Einreichungen → Eintrag sichtbar.
- 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)
| Variable | Zweck |
|---|---|
DATABASE_URL | Postgres |
BUNNY_STORAGE_*, BUNNY_CDN_URL | Medien-Upload |
COCKPIT_PARTNER_API_TOKEN_SECRET | Partner-Bearer-Signatur (Fallback: NEXTAUTH_SECRET) |
PARTNER_AI_GATEWAY_URL / PARTNER_AI_GATEWAY_API_KEY | Optional — KI „Text verbessern" |
NEXT_PUBLIC_PARTNER_INTAKE_APP_URL | https://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)
| Variable | Wert |
|---|---|
COCKPIT_PARTNER_API_BASE | https://dashboard.cockpit-os.de |
SESSION_SECRET | openssl rand -base64 32 — nur Manager-App |
Alias in Referenz-App: COCKPIT_API_URL.
Nicht in v0 committen: Bunny-Keys, COCKPIT_PARTNER_API_TOKEN_SECRET, Passwörter.
Datenbank
| Migration | Inhalt |
|---|---|
20260828130000_add_media_intake_SAFE.sql | media_intake_submissions, media_intake_submit_links |
20260828160000_add_mallcrew_extensions_SAFE.sql | contributionType, conditions, Kommentare, Aufgaben |
Schema-Referenz: db_structure.md im Repo-Root.
Siehe auch
- Partner-Medien-Einreichungen (Redaktion)
- Cockpit steuert Frontends
- Handoff:
docs/PARTNER-MANAGER-HANDOFF.md - Changelog: Plattform (0.1.212 / 0.1.213)
Nutzungsstatistik: Seitenaufrufe werden anonymisiert erfasst. Im Umami-Dashboard nach diesem Pfad filtern: /en/developer-guide/partner-api-v1