Skip to content

CRM

The CRM is a built-in replacement for Directus, providing agents and real estate developers with tools to manage units, agents, portal settings, and view leads. It lives in the same Astro project as the buyer portal but under separate route prefixes.

Note: The CRM exists on spike/cf-runtime only. main uses Directus for CRM functions.

Browser URLs use clean paths (e.g. /units, /leads). The /admin/ prefix is a legacy internal detail — the middleware redirects /admin/* requests to clean paths and rewrites clean paths internally.

OrderLabelRouteVisible to
1Wohneinheiten/unitsDeveloper, Agent
2Dokumente/documentsDeveloper, Agent
3Makler/agentsDeveloper
4Portal-Einstellungen/portal-settingsDeveloper

CRM group: Interessenten → /leads (Developer, Agent).

Domains are shown as a read-only card on Portal-Einstellungen (not a separate nav item). /domains redirects to /portal-settings?section=domains.

Root / serves the OTP login page for unauthenticated requests and the dashboard for authenticated sessions.

RoutePageRole
/OTP login (email → 6-digit code)Public
/acceptLegacy magic-link (deprecated)Public
/Dashboard / homeDeveloper, Agent
/unitsUnit listDeveloper, Agent
/units/newCreate unitDeveloper
/units/[id]Unit detail/editDeveloper, Agent
/agentsMakler list + CRM user inviteDeveloper
/maklerSelf-profile editorAgent
/leadsLead viewerDeveloper, Agent
/portal-settingsPortal branding, features, domainsDeveloper
/documentsDocument managementDeveloper, Agent
/domainsRedirect → portal-settings (domains section)Developer

Implementation note: The middleware rewrites clean CRM paths to /admin/* internally (e.g., /units → /admin/units). API routes retain the /api/admin/* prefix (the rewrite excludes /api/*).

EndpointPurpose
POST /api/auth/admin/request-linkRequest magic-link email
POST /api/auth/admin/logoutInvalidate CRM session
GET /api/admin/session/meCurrent user + accessible projects
POST /api/admin/session/active-projectSwitch active project
GET/POST /api/admin/unitsList/create units
PATCH/DELETE /api/admin/units/[id]Update/archive unit
POST /api/admin/units/[id]/images/presignGet presigned upload URL
POST /api/admin/units/[id]/imagesConfirm image upload
GET/POST /api/admin/agentsList/create Makler (+ CRM login via crmRole on POST)
PATCH /api/admin/agents/[id]Update agent
GET/PATCH /api/admin/agents/meSelf-profile (agent)
POST /api/admin/agents/[id]/avatar/presignGet presigned avatar URL
PUT /api/admin/agents/[id]/projectsSet agent project assignments
GET /api/admin/leadsList buyer registrations
GET/PATCH /api/admin/portal-settingsRead/update portal config
POST /api/admin/portal-settings/logoUpload logo to R2
GET /api/admin/documentsList documents
POST /api/admin/documents/presignGet presigned upload URL
GET /api/admin/domainsList portal domains
POST /api/admin/inviteInvite CRM user (role, projectIds, optional organizationId)
POST /api/admin/invite-developerLegacy developer invite (requires projectIds)

Provisioning logic: src/lib/auth/provision-crm-user.ts — every CRM account needs role (developer | agent) and at least one project (agent_project_assignments).

RoleCRM access (current feature set)
DeveloperFull org scope: Interessenten, Wohneinheiten (incl. create), Dokumente, Makler management, Portal-Einstellungen (incl. domains card). All projects in their organization via memberships.
AgentInteressenten, Wohneinheiten (assigned projects), Dokumente; self-profile at /makler. No Makler admin list or portal settings.
OperatorCross-tenant via Operator Panel (Cloudflare Access, no Lucia session).

Sessions are stored in Postgres (admin_sessions), not Worker memory — they survive redeploys until expiry (7 days) or logout. See CRM Auth.

ComponentPurpose
UnitsTable.tsxData table with sort/filter
AgentManager.tsxMakler CRUD; on create also provisions CRM login (crmRole + active project)
AdminProjectSwitcher.tsxProject selector in top bar