13 KiB
Admin Dashboard / CRM — product specification
This is the contract the project is built against. It is written for the agent that scaffolds the repository, but it doubles as the human-readable brief: every requirement below is meant to be implementable and verifiable.
Values in angle brackets (<App name>, <entities>, <auth provider>) come from the
project-creation dialog and are recorded in the project record; replace them as you read.
1. Goal
An internal back-office / CRM that an operations team could actually run on day one: sign in with
the configured <auth provider>, see a dashboard of KPIs, manage the supplied <entities> through
generated data tables and record forms, search across everything, import and export CSV, and trust
an append-only audit trail for every change. The CRUD surface is generic and driven by entity
metadata — one registry defines the entities and their typed fields, and the list views, forms,
filters and related-record links are generated from it, so adding a field is a metadata change,
not new hand-written screens for every entity.
It must be runnable end-to-end on day one (with seeded demo data and a seeded admin), not a set of screens wired together later.
Non-goals (do not build unless the must-have list says otherwise): a public/customer-facing portal, marketing pages, billing/subscriptions, real-time collaboration, workflow automation/approvals, ML scoring, mobile apps.
2. Roles
| Role | Can do |
|---|---|
| Viewer | Sign in, view the dashboard, browse and search any entity they can read, open records, run exports. Read-only. |
| Editor | Everything a viewer can, plus create, update, soft-delete and restore records in the entities they are granted. |
| Admin | Everything an editor can, plus manage entity metadata and field definitions, manage roles/permissions, delete permanently, and read the full audit trail. |
RBAC is per entity and per action: a role maps to { entity, actions[] } where actions are
read | create | update | delete | restore | export | import. A user may hold different rights on
different entities (e.g. edit customers, read-only deals). Authentication is <auth provider>;
provide a seeded admin account for local development so a reviewer can log in immediately.
3. Information architecture (routes)
Auth:
/login— sign in (and the SSO callback route when<auth provider>isSSO / OIDC)./logout.
App (guarded; the entity routes are generated from the registry):
/— dashboard: KPI cards, recent activity, quick links to each entity./search— global search across the readable entities./:entity— generic list view for one entity: server-side pagination, sorting, filters./:entity/new— generic record form (create)./:entity/:id— record detail: field values, related-record links, change history./:entity/:id/edit— generic record form (edit); destructive actions live here behind confirmation./import— CSV import: upload, column mapping, dry-run preview, commit, per-row errors./audit— audit trail: filter by actor, entity, action and date; immutable entries./settings/entities— entity & field metadata registry (admin only)./settings/roles— roles and per-entity permission matrix (admin only)./settings/saved-views— saved list filters (personal and shared).
Access to /:entity routes is denied (403) when the signed-in role has no read on that entity;
the nav only shows entities the role can read.
4. Data model
Minimum viable entities (add fields the requirements imply; keep them typed and validated):
- EntityDefinition — id, key (route segment), label, labelPlural, icon, isSystem, displayField, sortField, defaultSortDirection, createdAt.
- FieldDefinition — id, entityId, key, label, type (
text|textarea|number|date|boolean|select|relation), isRequired, isUnique, options[], relationEntityId?, isListColumn, listOrder, isFilterable, isSortable, defaultValue, validationRules (min/max/ pattern/maxLength). The pair EntityDefinition + FieldDefinition is the typed registry that drives the generated tables and forms. - Domain entities — the user-supplied
<entities>(e.g.customers,deals,activities,tasks). Each is a real, typed table/collection whose columns follow its FieldDefinitions; it carries the shared audit columns below. Model them with migrations, not a schemaless blob, and seed a few rows each so lists are presentable on first run. - Shared record columns — id, createdAt, createdBy, updatedAt, updatedBy, deletedAt?
(soft delete), deletedBy?.
deletedAt IS NULLmeans active. - Role — id, key (
viewer|editor|adminor custom), name, isSystem. - Permission — id, roleId, entityId (or
*), actions[] (read/create/update/delete/restore/export/import). - User — id, email, name, passwordHash?, externalId? (for SSO), roleIds[], status, createdAt.
- AuditEvent — id, actorId, actorEmail, entityId, recordId, action (
create|update|delete|restore|import), changes (before/after diff of changed fields), occurredAt. Append-only; never updated or deleted. - SavedView — id, ownerId or shared flag, entityId, name, filters, sort, visibleColumns[].
- ImportJob — id, entityId, filename, status (
pending|validating|ready|committed|failed), columnMapping, rowsTotal, rowsValid, rowsFailed, errors[], createdBy, createdAt. Export jobs may reuse it or stream directly without persisting a row.
Keep every field typed and validated; a record value that does not satisfy its FieldDefinition is a 422, not a stored string.
5. Key flows
- Sign in → dashboard. A reviewer logs in with the seeded admin, lands on
/, and sees KPI cards (record counts per entity, recent creates/updates, and at least one invented business KPI per seeded entity) plus recent audit activity. - Browse a list. The user opens
/:entity, sees a server-paginated table of the visible columns, sorts by a sortable column, applies a filter and saves it as a saved view; reloading or sharing the filtered URL reproduces the same view. - Create / edit a record. The user opens the generated record form, fills typed fields, gets
inline validation errors on submit, saves, and is taken to the record — which now appears in the
list and has a new
create/updateentry in its change history. - Related records. From a record with a
relationfield the user follows a link to the related record; the reverse record lists its referrers. - Soft delete → restore. An editor soft-deletes a record: it disappears from the default list, the deletion is audited, and the record is recoverable from the "deleted" filter or the record view. An admin can restore it; permanent delete is admin-only, confirmed, and audited.
- Audit. An admin opens
/audit, filters to one record, and sees who changed what and when — including before/after values for each changed field — for every create, update, delete and restore. - Import / export. A user exports the current filtered list to CSV (streamed) and imports a CSV of new records: mapping columns to fields, seeing a dry-run with per-row errors, then committing only the valid rows — each committed row producing an audit event.
- Admin metadata. An admin adds a field in
/settings/entities, and it appears as a list column and form input for that entity without writing per-entity screen code.
6. Functional requirements
- Generic CRUD from metadata: list views, filters and forms are generated from EntityDefinition/FieldDefinition. No per-entity hand-written tables or forms.
- List views: server-side pagination (bounded page size), sorting, and filters derived from the filterable fields; empty state when a filter matches nothing; total count shown.
- Filters: text contains/equals, number/date ranges, select/boolean equals, and a relation picker; all applied server-side, never by loading the whole table.
- Record forms: one generated form per entity with typed inputs, required/unique/format validation, and inline, field-level errors returned by the server.
- Related records:
relationfields render as links to the related record; the related record shows a back-reference list. - Bulk actions: select rows and apply a bulk action (bulk soft-delete, bulk export, bulk field update where enabled) — each row audited, with a confirmation step.
- Dashboard: KPI cards and recent activity; numbers come from real (seeded) data, not placeholders.
- Global search: one query box that searches across the readable entities and returns grouped, paged results, respecting RBAC.
- CSV import/export: export streams the current filtered/selected set; import supports column mapping, a dry-run preview, per-row errors and a commit of valid rows only.
- Audit trail: every create/update/delete/restore writes an immutable AuditEvent with actor,
timestamp and the changed fields; the record view and
/auditexpose them. - Soft delete + restore: deletes set
deletedAt; restore clears it; both are authorized and audited. Permanent delete is admin-only and confirmed. - Saved views: persist filters/sort/columns per user and optionally shared.
- Admin: manage entity/field metadata and the role→permission matrix; no destructive action without confirmation.
- Seed data: a seeded admin (viewer + editor + admin usable immediately), the supplied entities with a few rows each, and a saved view or two, so the dashboard is presentable on first run.
7. Non-functional requirements
- Authorization: enforce every entity+action permission server-side per request — never only in the UI; a direct API call with insufficient rights must fail, not merely hide a button.
- Bounded queries: every list query is paginated and bounded with a server-enforced maximum page size; no unbounded scans of a table, ever (list, export and search included).
- Audit atomicity: the audit write is committed atomically with the mutation it records — in the same transaction. A change without an audit event, or an audit event without the change, must not be observable.
- Output escaping: escape every value rendered in the UI (field values, names, error text) so a stored string can never execute as markup.
- Streaming exports: large CSV exports stream to the client; they must not buffer the whole result set in memory. Imports validate in bounded batches.
- Destructive actions: require explicit confirmation and are recoverable wherever soft delete applies; permanent delete is admin-only.
- Resilience: every screen has explicit loading, empty and error states; validation errors are shown per field, not as one generic message.
- Observability: structured logs for imports, exports, permission denials and auth failures; a health endpoint.
8. Acceptance criteria (definition of done)
- Install, dev server, lint, typecheck, tests and production build all pass.
- A seeded admin can log in immediately and sees a dashboard with real (seeded) KPIs.
- Every supplied
<entity>has a working list view and record form generating from metadata — CRUD works end-to-end with typed fields and server-returned validation errors. - RBAC is enforced server-side per entity and action: viewer is read-only, editor can mutate its entities, admin manages metadata/roles, and a denied direct API call returns 403.
- Filters, sorting and pagination are applied server-side (
LIMIT/OFFSET, not client slicing) and every list query is bounded. - Every create, update, delete and restore writes an immutable audit record with actor, timestamp and changed fields, committed atomically with the mutation.
- Soft delete hides a record and restore brings it back; both are audited; permanent delete is admin-only and confirmed.
- Global search and CSV export respect RBAC; export streams and import supports mapping + dry-run with per-row errors.
.env.exampledocuments every secret; README quickstart (install, run, test, env) is accurate.- CI runs install + lint + typecheck + tests + build.
9. Suggested build order
Follow this order and finish (and verify) a layer before starting the next:
- Scaffold the chosen stack (with
<database>), install dependencies, get the dev server and the empty shell running, and commit the skeleton. - Auth & roles — sign in with
<auth provider>, the role→permission model, server-side enforcement, and the seeded admin. - Entity metadata + generic table — the EntityDefinition/FieldDefinition registry, migrations
for the supplied
<entities>and their seed rows, and the generated, server-paginated list view. - Generic record form — typed inputs, validation, relation links, bulk actions.
- Audit trail — atomic audit writes for every mutation, the record change history and
/audit. - Dashboard + global search — KPI cards, recent activity, cross-entity search.
- Import / export — streaming CSV export, import with mapping, dry-run and per-row errors.
- Quality — tests for the flows above, CI, README,
.env.example, accessibility pass.