203 lines
13 KiB
Markdown
203 lines
13 KiB
Markdown
# 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>` is `SSO / 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 NULL` means active.
|
|
- **Role** — id, key (`viewer` | `editor` | `admin` or 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
|
|
|
|
1. **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.
|
|
2. **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.
|
|
3. **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`/`update` entry in its change history.
|
|
4. **Related records.** From a record with a `relation` field the user follows a link to the related
|
|
record; the reverse record lists its referrers.
|
|
5. **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.
|
|
6. **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.
|
|
7. **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.
|
|
8. **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:** `relation` fields 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 `/audit` expose 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.example` documents 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:
|
|
|
|
1. **Scaffold** the chosen stack (with `<database>`), install dependencies, get the dev server and
|
|
the empty shell running, and commit the skeleton.
|
|
2. **Auth & roles** — sign in with `<auth provider>`, the role→permission model, server-side
|
|
enforcement, and the seeded admin.
|
|
3. **Entity metadata + generic table** — the EntityDefinition/FieldDefinition registry, migrations
|
|
for the supplied `<entities>` and their seed rows, and the generated, server-paginated list view.
|
|
4. **Generic record form** — typed inputs, validation, relation links, bulk actions.
|
|
5. **Audit trail** — atomic audit writes for every mutation, the record change history and `/audit`.
|
|
6. **Dashboard + global search** — KPI cards, recent activity, cross-entity search.
|
|
7. **Import / export** — streaming CSV export, import with mapping, dry-run and per-row errors.
|
|
8. **Quality** — tests for the flows above, CI, README, `.env.example`, accessibility pass.
|