Przegląd platformy
Platforma DealerCRM to ekosystem aplikacji dla dealerów i importerów samochodowych. Centralnym elementem jest DealerID — Identity Provider (OAuth2 / OIDC), z którego wszystkie pozostałe aplikacje korzystają jako jedynego źródła tożsamości.
Aplikacje platformy
Dział zatytułowany „Aplikacje platformy”| App | Status | Repo | Rola |
|---|---|---|---|
| DealerID | ✅ żyje | apps/dealerid (Symfony PHP) | IDP. Tożsamości, OAuth2 server, ekran zgody, dashboard /account/apps. Nic poza tym. |
| DealerCRM | ✅ żyje | apps/crm-api + apps/crm-frontend | CRM dla dealerów. Wystawiany jako N Applications (gezet.localhost, gezet-renault.localhost, staff.localhost …) — każda z własnym brandingiem i filtrem danych. Pierwsza apka — referencja integracji. |
| DealerBMS | 🟡 planowany | apps/bms-* (TBD) | Backoffice management (procesy serwisowe / magazynowe). Będzie konsumować DealerID + model application. |
| DealerService | 🟡 planowany | apps/service-* (TBD) | Aplikacja dla serwisów (przyjęcia, naprawy, fakturowanie). |
| DealerAnalytics | 🟡 planowany | apps/analytics-* (TBD) | Hurtownia / BI / raportowanie cross-app. |
| Portal Klienta | 🟡 planowany | apps/portal-* (TBD) | Portal end-user dla klientów dealera (ich umowy, historia, kontakt z opiekunem). |
Termin „Application” w CRM: to instancja aplikacji CRM osiągalna pod konkretnym hostem —
gezet.localhost(kind=dealer_main),gezet-renault.localhost(kind=importer_network),renault.localhost(kind=importer_main),staff.localhost(kind=staff). Nie mylić z “aplikacją partnerską” / “klientem OAuth2” rejestrowanym w DealerID (oauth2_client) — patrz Rejestracja aplikacji i Multi-tenancy: Aplikacje.
Kluczowa zasada: każda kolejna aplikacja platformy musi używać DealerID jako jedynego dostawcy tożsamości. Bez własnych baz haseł, bez własnego SSO. To jest twarda decyzja architektoniczna i nie podlega negocjacjom — patrz Rejestracja aplikacji.
Diagram — komponenty i przepływy
Dział zatytułowany „Diagram — komponenty i przepływy”graph TB
subgraph IDP["🪪 DealerID — IDP"]
DID_AUTH["/authorize<br/>/token<br/>/userinfo"]
DID_ACCOUNT["/account/apps<br/>(dashboard usera)"]
DID_DB[("app_user<br/>oauth2_client<br/>oauth2_access_token")]
end
subgraph CRM["📋 DealerCRM Applications (referencyjna apka)"]
CRM_BFF["crm-frontend<br/>(Next.js BFF + UI)<br/>host → Application"]
CRM_API["crm-api<br/>(Symfony, modular monolith)<br/>resolve Application z Host header"]
CRM_DB[("iam_user, organization,<br/>application (host → org, kind,<br/>importer_id), lead, ...")]
APP_INSTANCES["Application instances:<br/>• gezet.localhost (dealer_main)<br/>• gezet-renault.localhost (importer_network)<br/>• renault.localhost (importer_main)<br/>• staff.localhost (staff)"]
end
subgraph FUTURE["🟡 Planowane apki"]
BMS["DealerBMS"]
SVC["DealerService"]
ANALYTICS["DealerAnalytics"]
PORTAL["Portal Klienta"]
end
USER([👤 User<br/>pracownik dealera<br/>lub staff Grupy Dealer])
USER -->|"1. login<br/>(form / Google / Microsoft)"| DID_AUTH
USER -->|"2. wybiera Application"| DID_ACCOUNT
USER -->|"3. /authorize → callback<br/>na host wybranej Application"| CRM_BFF
CRM_BFF -->|"4. code → token"| DID_AUTH
CRM_BFF -->|"5. /api/* (Bearer)<br/>+ forward Host header"| CRM_API
CRM_API -->|"6. JWT verify (RS256)<br/>+ /userinfo"| DID_AUTH
CRM_API --> APP_INSTANCES
APP_INSTANCES --> CRM_DB
DID_AUTH --> DID_DB
DID_ACCOUNT -.->|"server-to-server<br/>(scoped JWT, app.directory.read)"| CRM_API
DID_ACCOUNT -.-> BMS
DID_ACCOUNT -.-> SVC
DID_ACCOUNT -.-> ANALYTICS
DID_ACCOUNT -.-> PORTAL
BMS -.-> DID_AUTH
SVC -.-> DID_AUTH
ANALYTICS -.-> DID_AUTH
PORTAL -.-> DID_AUTH
classDef done fill:#dcfce7,stroke:#16a34a,color:#14532d;
classDef wip fill:#fef9c3,stroke:#ca8a04,color:#713f12;
class IDP,CRM done;
class FUTURE,BMS,SVC,ANALYTICS,PORTAL wip;
Relacja host → application → organization
Dział zatytułowany „Relacja host → application → organization”flowchart LR
H1["gezet.localhost"] --> A1["Application<br/>kind=dealer_main"]
H2["gezet-renault.localhost"] --> A2["Application<br/>kind=importer_network<br/>importer_id=Renault"]
H3["renault.localhost"] --> A3["Application<br/>kind=importer_main"]
H4["staff.localhost"] --> A4["Application<br/>kind=staff"]
A1 -->|organization_id| O1["Gezet (dealer)"]
A2 -->|organization_id| O1
A2 -.->|importer_id<br/>(branding source)| O2["Renault Polska (importer)"]
A3 -->|organization_id| O2
A4 -->|organization_id| O3["DealerCRM Platform Org"]
O1 -->|branding_skin_slug| S1["default"]
O2 -->|branding_skin_slug| S2["renault"]
O3 -->|branding_skin_slug| S3["staff"]
Co utrzymuje zespół DealerID
Dział zatytułowany „Co utrzymuje zespół DealerID”- Serwer OAuth2 (
league/oauth2-server-bundle) — endpointy/authorize,/token,/userinfo. - Logowanie: form_login z hasłem (argon2id), SSO Google Workspace, SSO Microsoft Entra ID.
- Ekran zgody — pokazywany dla third-party klientów. First-party klienci (flaga
oauth2_client.is_first_party) mają auto-approve. - Auto-provisioning internal staff — gdy domena emaila pasuje do
app.sso.internal_domains(grupadealer.pl), Google SSO tworzy konto zis_internal_staff = TRUE. Patrz SSO Google + auto-provisioning. - Dashboard
/account/apps— agreguje karty aplikacji wołając każdyoauth2_client.app_directory_urlserver-to-server.
Co utrzymuje zespół partnerski (czyli ty)
Dział zatytułowany „Co utrzymuje zespół partnerski (czyli ty)”- Implementacja OAuth2 client w swojej apce (PKCE obowiązkowe). Patrz Flow OAuth2.
- Walidacja Bearer JWT w swoim API (RS256, publiczny klucz DealerID).
- Endpoint
/api/dealerid/user-apps(opcjonalnie — jeśli chcesz, by user widział twoją apkę na dashboardzie DealerID). Patrz App Directory — implementacja. - Tabela membership w twojej apce — nie polegaj na DealerID, że user „należy do twojej apki”. DealerID trzyma tylko tożsamości. Czy ktoś ma dostęp do BMS — to decyzja BMS.
Niezmienniki
Dział zatytułowany „Niezmienniki”- DealerID nie ma wiedzy o tym, kto jest userem w CRM/BMS. Pyta tylko: „kto to jest?” (email + name + is_internal_staff).
- Każda apka platformy ma własną tabelę membership. Bycie zarejestrowanym w DealerID ≠ dostęp do CRM.
- Każda apka musi sama provisionować swojego usera przy pierwszym loginie (lazy provision na podstawie emaila z claimu
sub). - Pracownicy Grupy Dealer logują się wyłącznie przez Google Workspace. Hasło + Microsoft są zablokowane dla
is_internal_staff.