keycloak init

This commit is contained in:
MaKarin
2026-03-21 16:00:27 +03:00
parent 33521016d3
commit 8d6875f4b0
50 changed files with 2242 additions and 252 deletions

View File

@@ -1,12 +1,14 @@
# Backend Generation Process
Backend generation follows a pipeline aligned with runtime and validation docs:
Backend generation follows a pipeline aligned with runtime, auth, and validation docs:
DSL
CLI scaffolding
code generation
backend code generation
auth generation
runtime infrastructure
@@ -18,7 +20,18 @@ seed
validation
Follow **backend/runtime-rules.md**, **backend/prisma-rules.md**, **backend/prisma-service.md**, **backend/database-runtime.md**, **backend/seed-rules.md**, and **backend/service-rules.md**.
Follow:
- `backend/architecture.md`
- `backend/runtime-rules.md`
- `backend/prisma-rules.md`
- `backend/prisma-service.md`
- `backend/database-runtime.md`
- `backend/seed-rules.md`
- `backend/service-rules.md`
- `auth/keycloak-architecture.md`
- `auth/backend-auth-rules.md`
- `auth/keycloak-realm-template-rules.md`
---
@@ -27,10 +40,12 @@ Follow **backend/runtime-rules.md**, **backend/prisma-rules.md**, **backend/pris
Read DSL inputs and extract:
- entities
- attributes (including primary key attribute name per entity)
- attributes, including the actual primary key attribute per entity
- enums
- foreign keys
The generator must treat auth as default runtime infrastructure, not as a DSL feature toggle.
---
# Step 2 — CLI scaffolding
@@ -38,50 +53,118 @@ Read DSL inputs and extract:
Use official CLIs before generating backend code:
- NestJS project scaffold in `server/` (see `generation/scaffolding-rules.md`)
- Install backend dependencies (`@prisma/client`, `prisma`, `@nestjs/config`, and seed runner when needed)
- install backend dependencies:
- `@prisma/client`
- `prisma`
- `@nestjs/config`
- `jose`
- seed runner when needed by the chosen package tooling
The generator must **not** use deprecated Keycloak-specific Node adapters such as `keycloak-connect`.
---
# Step 3 — Code generation
# Step 3 — Core backend code generation
Generate backend source artifacts:
1. **Prisma schema** (`server/prisma/schema.prisma`) from domain DSL:
1. **Prisma schema** (`server/prisma/schema.prisma`) from the domain DSL:
- attributes
- primary keys
- relations
- enums
2. **NestJS modules** per entity:
- module
- controller (path params use actual PK name: `:id`, `:code`, etc.)
- service (must sanitize update payload before Prisma — see **backend/service-rules.md**)
- controller
- service
3. **DTO files**:
- `create-entity.dto.ts`
- `update-entity.dto.ts`
- `entity.response.dto.ts` (or equivalent)
4. **PrismaService**:
- `OnModuleInit` + `await this.$connect()`
- no `beforeExit` hook
5. **Service update methods**: Sanitize update payload before passing to Prisma (remove `id`, primary key, and readonly attributes from `data`). Do not pass the raw request body as `data` to `prisma.*.update()`.
- implement `OnModuleInit`
- call `await this.$connect()` in `onModuleInit()`
- do not use a `beforeExit` hook
5. **Service update methods**:
- sanitize update payload before Prisma
- remove `id`, the entity primary key, and readonly attributes from `data`
- do not pass the raw request body directly to `prisma.*.update()`
Use mapping rules from `backend/prisma-rules.md`:
- DSL `decimal` -> DTO `string`
- DSL `date` -> DTO `string` (ISO)
---
# Step 4 — Runtime infrastructure
# Step 4 — Backend auth generation
Generate runtime config files:
Generate auth infrastructure as part of the normal backend output. Auth must not be deferred to a manual post-step.
Required generated auth artifacts:
- `server/src/auth/auth.module.ts`
- JWT auth guard
- roles guard
- `@Public()` decorator
- `@Roles()` decorator
- typed authenticated principal interface
- typed auth-aware config validation in `server/src/config/`
JWT verification rules:
- verify JWTs with issuer, audience, and JWKS
- use a standards-based library such as `jose`
- resolve JWKS in this exact order:
1. explicit `KEYCLOAK_JWKS_URL`
2. OIDC discovery
3. `${issuer}/protocol/openid-connect/certs`
RBAC rules:
- extract authorization roles only from `realm_access.roles`
- do not infer roles from other claims
- apply CRUD defaults by HTTP method:
- `GET` -> `viewer`, `editor`, `admin`
- `POST`, `PATCH`, `PUT` -> `editor`, `admin`
- `DELETE` -> `admin`
- mark `/health` as public with `@Public()`
Controller generation rules:
- generated CRUD controllers must receive auth decorators by default
- path params must still use the actual entity primary key name (`:id`, `:code`, etc.)
- public routes must be explicit rather than implicit
---
# Step 5 — Runtime infrastructure
Generate backend runtime config files:
- `server/.env`
- `server/.env.example`
- `server/src/config/*` typed validation helpers
- `server/package.json` lifecycle:
- `"postinstall": "prisma generate"`
- `server/package.json` Prisma seed:
- `"prisma": { "seed": "ts-node prisma/seed.ts" }` (or `tsx` variant by project standard)
- `"prisma": { "seed": "ts-node prisma/seed.ts" }` (or `tsx` equivalent if that is the chosen project standard)
Commands that must be supported/documented:
The generated backend env contract must include:
- `PORT`
- `DATABASE_URL`
- `CORS_ALLOWED_ORIGINS`
- `KEYCLOAK_ISSUER_URL`
- `KEYCLOAK_AUDIENCE`
- `KEYCLOAK_JWKS_URL` (optional)
Fail-fast config rule:
- backend startup must fail fast when required auth or database env vars are missing
- the generator must not silently fall back to production auth values in code
Commands that must be supported and documented:
- `npx prisma generate`
- `npx prisma migrate dev`
@@ -89,9 +172,9 @@ Commands that must be supported/documented:
---
# Step 5 — Database runtime
# Step 6 — Database runtime
Generator must create `docker-compose.yml` at the **project root** with PostgreSQL.
Create `docker-compose.yml` at the **project root** with PostgreSQL only.
Minimum required compose characteristics:
@@ -99,13 +182,15 @@ Minimum required compose characteristics:
- `image: postgres:16`
- `ports: ["5432:5432"]`
Credentials/database in compose must match `DATABASE_URL`.
Credentials and database name in compose must match `DATABASE_URL`.
Keycloak must remain an external runtime dependency and must not be added to `docker-compose.yml` in this repository.
---
# Step 6 — Migration
# Step 7 — Migration
Apply schema to development database:
Apply schema to the development database:
```bash
cd server
@@ -114,7 +199,7 @@ npx prisma migrate dev
---
# Step 7 — Seed
# Step 8 — Seed
Run development seed:
@@ -127,12 +212,14 @@ Seed file location: `server/prisma/seed.ts`.
---
# Step 8 — Validation
# Step 9 — Validation
Run runtime and contract checks from `generation/post-generation-validation.md`, including:
Run runtime, auth, and contract checks from `generation/post-generation-validation.md`, including:
- docker-compose exists and DB container starts
- Prisma lifecycle commands succeed
- seed runs
- `/health` responds
- React Admin receives `id` in every record
- `/health` responds without auth
- protected routes reject unauthenticated requests with `401`
- authenticated users with insufficient role receive `403`
- React Admin-compatible API responses include `id` for every record

View File

@@ -1,6 +1,6 @@
# Developer Workflow
This document describes the **developer workflow** for running a generated fullstack application locally. The generator must produce a project that supports this workflow so the app is **fully runnable** after generation.
This document describes the **developer workflow** for running a generated fullstack application locally. The generator must produce a project that supports this workflow so the app is **fully runnable** after generation, including authentication.
---
@@ -9,12 +9,34 @@ This document describes the **developer workflow** for running a generated fulls
- **Node.js** (LTS, e.g. 18+)
- **npm**
- **Docker** and **Docker Compose** (for the development database)
- **External Keycloak server** reachable by the generated frontend and backend
The generated project must not require the developer to invent auth wiring manually after generation.
---
# Workflow Steps
## 1. Start the database
## 1. Prepare Keycloak and env files
From the project root, the generated project must include:
- root-level generated Keycloak realm import artifact
- repository default example filename: `toir-realm.json`
- `server/.env.example`
- `client/.env.example`
Required workflow:
1. Copy the generated env examples to real env files as needed.
2. Fill all required backend and frontend auth variables.
3. Import or verify the generated Keycloak realm import artifact in the external Keycloak server before starting the app.
The generator must document that auth config is fail-fast. Missing required auth env vars must stop startup instead of silently falling back to production values in code.
---
## 2. Start the database
From the **project root**:
@@ -24,7 +46,7 @@ docker compose up -d
This starts the PostgreSQL container defined in `docker-compose.yml`. Wait a few seconds for the database to accept connections.
Verify (optional):
Verify if needed:
```bash
docker compose ps
@@ -32,7 +54,7 @@ docker compose ps
---
## 2. Backend setup and start
## 3. Backend setup and start
From the **server** directory:
@@ -45,13 +67,15 @@ npx prisma db seed
npm run start
```
- `npm install` installs dependencies and runs `postinstall` (for example `prisma generate`).
- `npx prisma generate` explicitly generates Prisma client.
- `npx prisma migrate dev` creates/applies migrations.
- `npx prisma db seed` inserts minimal development data.
- `npm run start` starts NestJS backend.
- `npm install` installs dependencies and runs `postinstall` when configured.
- `npx prisma generate` explicitly generates Prisma client.
- `npx prisma migrate dev` creates/applies migrations.
- `npx prisma db seed` inserts minimal development data.
- `npm run start` starts the NestJS backend.
The API should be available at the configured port (e.g. `http://localhost:3000`). Verify with:
The API should be available at the configured port (for example `http://localhost:3000`).
Verify:
```bash
curl http://localhost:3000/health
@@ -59,9 +83,15 @@ curl http://localhost:3000/health
Expected: `{ "status": "ok" }` (or equivalent).
Generated backend behavior must also ensure:
- protected CRUD routes require authentication by default
- insufficient roles result in `403`
- `/health` remains public
---
## 3. Frontend setup and start
## 4. Frontend setup and start
In a **separate terminal**, from the **project root**:
@@ -71,10 +101,15 @@ npm install
npm run dev
```
- `npm install` installs frontend dependencies.
- `npm run dev` starts the Vite dev server (e.g. `http://localhost:5173`).
- `npm install` installs frontend dependencies including `keycloak-js`.
- `npm run dev` starts the Vite dev server (for example `http://localhost:5173`).
Open the Vite URL in a browser; the React Admin app should load and use the backend API.
Open the frontend URL in a browser. The generated React Admin app must:
- initialize Keycloak before render
- use redirect-based login only
- authenticate against the configured Keycloak realm/client
- call the backend with bearer tokens through the shared request seam
---
@@ -82,8 +117,19 @@ Open the Vite URL in a browser; the React Admin app should load and use the back
| Step | Command / location |
|------|---------------------|
| Prepare Keycloak + env | Fill `server/.env` and `client/.env`; import or verify the generated realm import artifact |
| Start database | From root: `docker compose up -d` |
| Backend setup/start | `cd server && npm install && npx prisma generate && npx prisma migrate dev && npx prisma db seed && npm run start` |
| Frontend setup/start | `cd client && npm install && npm run dev` |
The generator must produce all required artifacts (docker-compose, env, schema, migrations, seed, health endpoint) so that this workflow succeeds and the development environment is fully runnable.
The generator must produce all required artifacts so that this workflow succeeds:
- docker-compose
- env examples
- schema
- migrations
- seed
- health endpoint
- frontend auth integration
- backend auth infrastructure
- root-level Keycloak realm import artifact

View File

@@ -1,36 +1,138 @@
# Frontend Generation Process
Frontend generation uses the DSL and API specification.
Frontend generation must produce the React Admin CRUD application **and** the default Keycloak integration required by the runtime architecture.
Follow:
- `frontend/architecture.md`
- `frontend/react-admin-rules.md`
- `auth/keycloak-architecture.md`
- `auth/frontend-auth-rules.md`
- `auth/keycloak-realm-template-rules.md`
---
# Step 1 — Parse DSL
Extract entities and attributes.
Extract:
- entities
- attributes
- relation fields required for reference inputs and reference displays
The generator must treat auth as default frontend infrastructure rather than as an optional feature.
---
# Step 2 — Generate React Admin Resources
# Step 2 — Generate frontend runtime structure
Generate the base frontend structure required by the proven runtime:
- `client/src/main.tsx`
- `client/src/App.tsx`
- `client/src/dataProvider.ts`
- `client/src/config/env.ts`
- `client/src/auth/keycloak.ts`
- `client/src/auth/authProvider.ts`
- `client/.env.example`
The generated frontend must:
- initialize Keycloak before rendering the SPA
- register React Admin with a mandatory `authProvider`
- enforce authenticated operation rather than anonymous operation
- use environment-driven runtime config and fail fast when required auth vars are missing
---
# Step 3 — Generate React Admin resources
For each entity create:
EntityList.tsx
EntityCreate.tsx
EntityEdit.tsx
EntityShow.tsx
- `EntityList.tsx`
- `EntityCreate.tsx`
- `EntityEdit.tsx`
- `EntityShow.tsx`
Resource generation must remain compatible with the auth-aware shared request seam.
---
# Step 3 — Map Fields
# Step 4 — Map fields
Map DSL attributes to React Admin components.
Map DSL attributes to React Admin components according to existing field rules and relation semantics.
Reference/resource lookups must continue to flow through the same shared authenticated request layer used by the main CRUD resources.
---
# Step 4Register Resources
# Step 5Generate auth-aware API layer
Register resources in App.tsx.
Generate a shared `dataProvider.ts` that:
Example:
- reads the API base URL from `VITE_API_URL`
- attaches `Authorization: Bearer <access_token>` to every backend request
- uses the same request seam for all React Admin operations, including:
- list
- get one
- get many
- get many reference
- create
- update
- delete
- handles token refresh before protected requests
- keeps token refresh concurrency-safe by sharing one in-flight refresh operation
- does not store access tokens or refresh tokens in `localStorage` or `sessionStorage`
<Resource name="equipment" ... />
The generator must not create multiple competing HTTP clients for authenticated and unauthenticated traffic. The shared request seam is the single bearer injection point.
---
# Step 6 — Generate frontend auth flow
Generate Keycloak frontend integration with these required rules:
- use `keycloak-js`
- redirect-based login only
- no custom in-app username/password login form
- Authorization Code + PKCE with `S256`
- initialize Keycloak before React render
- provide a React Admin `authProvider`
- distinguish auth failures from authorization failures:
- `401` -> force logout / re-authentication
- `403` -> do not re-authenticate; surface access denied / permission error
The generator must not silently fall back to production auth settings in code. Example values belong in `.env.example`, but runtime config must fail fast if required values are absent.
---
# Step 7 — Register resources and auth
Register resources in `App.tsx` and wire them into an authenticated `Admin` root.
Generated `App.tsx` must:
- register the shared `dataProvider`
- register the mandatory `authProvider`
- configure the app so it does not operate anonymously once auth is enabled
Example shape:
```tsx
<Admin dataProvider={dataProvider} authProvider={authProvider}>
<Resource name="equipment" ... />
</Admin>
```
---
# Step 8 — Frontend runtime config
Generate frontend env examples and config access for:
- `VITE_API_URL`
- `VITE_KEYCLOAK_URL`
- `VITE_KEYCLOAK_REALM`
- `VITE_KEYCLOAK_CLIENT_ID`
`client/.env.example` must use filled example values that match the documented Keycloak and local dev topology, while runtime code must fail fast if any required variable is missing.

View File

@@ -1,160 +1,236 @@
# Post-Generation Validation
After generating the backend or fullstack application, run these checks to ensure the project will run without runtime errors.
After generating the backend or fullstack application, run these checks to ensure the project will start cleanly and that the default auth model has been generated correctly.
---
# Validation Checklist
## 1. Environment file
## 1. Frontend and backend env files
- [ ] **`.env` exists** in the backend root (e.g. `server/.env`).
- [ ] **`.env.example` exists** with at least `DATABASE_URL` and a placeholder value.
- [ ] **`DATABASE_URL`** is present in `.env` (or documented in `.env.example` so the user can copy and set it).
- [ ] `server/.env.example` exists and documents:
- `PORT`
- `DATABASE_URL`
- `CORS_ALLOWED_ORIGINS`
- `KEYCLOAK_ISSUER_URL`
- `KEYCLOAK_AUDIENCE`
- optional `KEYCLOAK_JWKS_URL`
- [ ] `client/.env.example` exists and documents:
- `VITE_API_URL`
- `VITE_KEYCLOAK_URL`
- `VITE_KEYCLOAK_REALM`
- `VITE_KEYCLOAK_CLIENT_ID`
- [ ] Runtime code fails fast when required auth or database env vars are missing.
- [ ] Runtime code does not silently fall back to production auth settings.
**Failure symptom:** `Environment variable not found: DATABASE_URL` at startup.
**Failure symptoms:** startup succeeds with undefined auth config, or fails later with opaque auth/runtime errors.
---
## 2. PrismaService implementation
## 2. Keycloak realm artifact
- [ ] A **PrismaService** (or equivalent) class exists and extends `PrismaClient`.
- [ ] It implements **`OnModuleInit`** and calls **`await this.$connect()`** in `onModuleInit()`.
- [ ] It does **not** use **`this.$on('beforeExit', ...)`** or any `beforeExit` hook.
- [ ] A root-level generated Keycloak realm import artifact exists.
- [ ] If the repository default filename `toir-realm.json` is not used, the project-specific equivalent is documented consistently across bootstrap and workflow docs.
- [ ] The generated realm artifact is self-contained and reproducible.
- [ ] The generated realm artifact parameterizes realm name, frontend client ID, backend audience/client ID, production URLs, and artifact filename consistently with the generated project auth config.
- [ ] It defines realm roles:
- `admin`
- `editor`
- `viewer`
- [ ] It defines a frontend SPA client consistent with the generated frontend Keycloak client ID.
- [ ] It defines a backend audience/resource client consistent with the generated backend audience/client ID.
- [ ] It defines explicit audience delivery, such as an `api-audience` client scope.
- [ ] It does not rely on undeclared built-in client scopes being present after import.
- [ ] It explicitly addresses delivery of:
- `sub`
- `aud`
- `realm_access.roles`
**Failure symptom:** Deprecation/runtime errors in Prisma 5 when using `beforeExit`.
**Failure symptoms:** access tokens are missing required claims, or realm import succeeds but generated apps still cannot authenticate/authorize reliably.
---
## 3. Frontend auth files and behavior
- [ ] Generated frontend includes:
- `client/src/config/env.ts`
- `client/src/auth/keycloak.ts`
- `client/src/auth/authProvider.ts`
- `client/src/main.tsx`
- `client/src/App.tsx`
- `client/src/dataProvider.ts`
- [ ] `keycloak-js` is installed.
- [ ] Keycloak is initialized before the SPA renders.
- [ ] Login is redirect-based only.
- [ ] No custom in-app username/password login form is generated.
- [ ] `Authorization Code + PKCE (S256)` is encoded in the frontend auth flow.
- [ ] `client/src/dataProvider.ts` or the documented shared request seam injects `Authorization: Bearer <access_token>` into all API requests.
- [ ] Token refresh is concurrency-safe:
- one shared in-flight refresh operation
- no parallel refresh stampede
- [ ] Generated auth code does not persist access tokens or refresh tokens in `localStorage` or `sessionStorage`.
- [ ] React Admin auth semantics distinguish:
- `401` -> force logout / re-authentication
- `403` -> do not re-authenticate; surface access denied / permission error
**Failure symptoms:** app renders before auth is ready, reference calls miss auth headers, refresh storms occur, or `403` incorrectly forces logout.
---
## 4. Backend auth files and behavior
- [ ] Generated backend includes:
- `server/src/auth/auth.module.ts`
- JWT guard
- roles guard
- `@Public()` decorator
- `@Roles()` decorator
- typed authenticated principal interface
- typed config validation in `server/src/config/`
- [ ] `jose` is installed.
- [ ] JWT verification uses issuer + audience + JWKS.
- [ ] JWKS resolution follows this exact priority:
1. `KEYCLOAK_JWKS_URL`
2. OIDC discovery
3. `${issuer}/protocol/openid-connect/certs`
- [ ] Authorization roles are extracted only from `realm_access.roles`.
- [ ] Deprecated Keycloak-specific Node adapters are not used.
**Failure symptoms:** invalid tokens are accepted, valid tokens are rejected due to bad JWKS resolution, or RBAC depends on unstable/non-standard claims.
---
## 5. CRUD protection and RBAC defaults
- [ ] `/health` is public.
- [ ] Each generated CRUD controller method other than explicit public routes is protected by the generated auth/RBAC infrastructure.
- [ ] CRUD RBAC defaults are present:
- `GET` -> `viewer | editor | admin`
- `POST`, `PATCH`, `PUT` -> `editor | admin`
- `DELETE` -> `admin`
- [ ] Unauthenticated request to a protected route returns `401`.
- [ ] Authenticated user with insufficient role receives `403`.
**Failure symptoms:** anonymous CRUD access remains open, or insufficient-role users are not denied properly.
---
## 6. PrismaService implementation
- [ ] A `PrismaService` (or equivalent) class exists and extends `PrismaClient`.
- [ ] It implements `OnModuleInit` and calls `await this.$connect()` in `onModuleInit()`.
- [ ] It does not use `this.$on('beforeExit', ...)` or any `beforeExit` hook.
**Failure symptom:** deprecation/runtime errors in Prisma 5 when using `beforeExit`.
**Reference:** `backend/prisma-service.md`
---
## 3. Prisma client lifecycle
## 7. Prisma client lifecycle
- [ ] **`package.json`** includes a script that runs Prisma client generation:
- Either **`"postinstall": "prisma generate"`** (or `npx prisma generate`),
- Or clear documentation to run **`npx prisma generate`** after install.
- [ ] After schema generation or change, **`npx prisma generate`** has been run (or will run via postinstall).
- [ ] `package.json` includes a script that runs Prisma client generation:
- either `"postinstall": "prisma generate"` (or `npx prisma generate`)
- or clear documentation to run `npx prisma generate` after install
- [ ] After schema generation or change, `npx prisma generate` has been run or will run via `postinstall`.
**Failure symptom:** `Cannot find module '@prisma/client'` or missing types at build/run.
**Failure symptom:** `Cannot find module '@prisma/client'` or missing generated types.
---
## 4. Database migration
## 8. Database migration
- [ ] **Migration workflow** is documented (e.g. in README or generation docs).
- [ ] Instruction to run **`npx prisma migrate dev`** (or `prisma migrate deploy` for production) after first generation or schema change.
- [ ] Generation pipeline (see `generation/backend-generation.md`) includes or documents the migration step.
- [ ] Migration workflow is documented.
- [ ] Instruction to run `npx prisma migrate dev` exists after first generation or schema change.
- [ ] Generation pipeline includes or documents the migration step.
**Failure symptom:** Tables do not exist; Prisma errors on first query.
**Failure symptom:** tables do not exist; Prisma errors on first query.
---
## 5. REST route parameters
## 9. REST route parameters
- [ ] For each entity, path parameters use the **correct primary key name** from the DSL.
- [ ] **Entity with PK `id` (uuid):** routes use **`/:id`** (e.g. `GET /equipment/:id`, `PATCH /equipment/:id`).
- [ ] **Entity with non-`id` primary key (e.g. `code`):** routes use **`/:code`** (or the actual PK attribute name), e.g. `GET /equipment-types/:code`, **not** `GET /equipment-types/:id`.
- [ ] For each entity, path parameters use the correct primary key name from the DSL.
- [ ] Entity with PK `id` uses `/:id`.
- [ ] Entity with non-`id` primary key (for example `code`) uses the actual PK name such as `/:code`, not `/:id`.
**Example:**
**Failure symptom:** controller expects one param name while the route defines another, leading to broken CRUD behavior.
| Entity | Primary key | Correct path | Incorrect path |
| ------------- | ----------- | ---------------------- | -------------------- |
| Equipment | id | /equipment/:id | — |
| EquipmentType | code | /equipment-types/:code | /equipment-types/:id |
| RepairOrder | id | /repair-orders/:id | — |
**Failure symptom:** Controller expects `params.id` but route is defined with `:code`; or vice versa; 404 or wrong resource updated.
**Reference:** `backend/architecture.md` — API path rules for non-id primary keys.
**Reference:** `backend/architecture.md`
---
## 6. DTO type mapping (serialization)
## 10. DTO type mapping and React Admin ID compatibility
- [ ] **DSL `decimal`** → In DTO/API response, use **`string`** (or a type that serializes to string), not Prisma `Decimal`, to avoid JSON serialization issues.
- [ ] **DSL `date`** → In DTO/API response, use **`string`** (ISO 8601) or ensure DateTime is serialized to string, so React Admin and JSON consumers receive a string.
- [ ] DSL `decimal` maps to DTO/API `string`.
- [ ] DSL `date` maps to DTO/API `string` (ISO) or equivalent string serialization.
- [ ] Every API response object contains a field named `id`.
- [ ] If the entity primary key is not named `id`, the response maps the primary key to `id`.
**Reference:** `backend/prisma-rules.md` — DTO type mapping table.
**Failure symptoms:** serialization issues for decimals/dates, or React Admin cannot identify records.
---
## 7. React Admin ID field in API responses
## 11. Update payload sanitization
- [ ] **Every API response object** (list items and single-resource GET) contains a field named **`id`**.
- [ ] If the entity primary key is **not** named `id`, the response must **map** the primary key to `id`: e.g. `id: record.code` for EquipmentType, so the payload includes both `id` and `code` (or at least `id` with the PK value).
- [ ] The `id` field value must be the **primary key value** (string or uuid as appropriate).
- [ ] Update endpoints do not pass `id` or the primary key in Prisma `data`.
- [ ] Generated update methods remove `id`, the entity primary key, and readonly attributes before calling `prisma.*.update()`.
**Failure symptom:** React Admin fails to identify records, breaks cache/references, or throws when expecting `record.id`.
**Reference:** `frontend/react-admin-rules.md` — React Admin ID Field Requirement.
**Failure symptom:** Prisma throws because immutable or invalid fields are passed in update `data`.
---
## 8. Update payload sanitization (service layer)
## 12. Database runtime
- [ ] **Update endpoint must not pass `id` (or primary key) in Prisma `data`.** The service must sanitize the incoming DTO before calling `prisma.*.update({ where, data })`: remove `id`, remove the entity primary key field (e.g. `code`), and remove any readonly attributes. Only updatable fields should be passed as `data`.
- [ ] Generated update methods follow the pattern from **backend/service-rules.md** (e.g. `const { id, code, ...data } = dto` then pass `data` to Prisma).
- [ ] `docker-compose.yml` exists at the project root.
- [ ] It defines a PostgreSQL service with image `postgres:16`, port `5432`, and credentials matching `DATABASE_URL`.
- [ ] `docker compose up -d` starts the database successfully.
- [ ] `docker-compose.yml` does not add Keycloak in this repository.
**Failure symptom:** Prisma throws when `data` contains `id` or another field that is not on the model or not writable (e.g. entity with PK `code` receives body with `id` from React Admin).
**Reference:** `backend/service-rules.md`
**Failure symptom:** Prisma cannot reach the development database or repo topology drifts from the documented external-Keycloak model.
---
## 9. Database runtime (docker-compose)
## 13. Migrations, seed, and health endpoint
- [ ] **`docker-compose.yml` exists** at the project root (or documented location).
- [ ] It defines a **PostgreSQL** service with image (e.g. `postgres:16`), port `5432`, and credentials/DB name matching `DATABASE_URL` in `server/.env`.
- [ ] **Database container starts:** `docker compose up -d` runs without error and the container is reachable on the configured port.
- [ ] `npx prisma migrate dev` runs successfully from `server/`.
- [ ] Seed script exists at `server/prisma/seed.ts` (or equivalent).
- [ ] `npx prisma db seed` runs without error.
- [ ] Backend exposes `GET /health`.
- [ ] `GET /health` returns HTTP 200 with a status payload.
**Failure symptom:** `PrismaClientInitializationError P1001: Can't reach database server at localhost:5432`.
**Reference:** `backend/database-runtime.md`
---
## 10. Migrations and seed
- [ ] **`npx prisma migrate dev`** runs successfully from `server/` when the database is up (schema is applied or created).
- [ ] **Seed script** exists at `server/prisma/seed.ts` (or equivalent) and creates minimal sample data (e.g. one EquipmentType, one Equipment, one RepairOrder).
- [ ] **`npx prisma db seed`** runs without error (package.json has `prisma.seed` configured and seed runner installed).
**Reference:** `backend/seed-rules.md`, `generation/runtime-bootstrap.md`
---
## 11. Health endpoint
- [ ] Backend exposes **GET /health** (or equivalent health route).
- [ ] **API responds to /health:** With backend running, `GET http://localhost:<port>/health` returns HTTP 200 and a body such as `{ "status": "ok" }`.
**Reference:** `backend/architecture.md` — Health Endpoint
**Failure symptom:** development bootstrap cannot complete end-to-end.
---
# Summary Table
| Check | Required artifact / rule |
| ------------------- | ---------------------------------------------- |
| .env | File exists with DATABASE_URL |
| DATABASE_URL | Present in .env or .env.example |
| PrismaService | OnModuleInit + $connect(); no beforeExit |
| prisma generate | postinstall script or documented step |
| Migration | Documented step: prisma migrate dev |
| REST path params | Use entity PK name (:id or :code, etc.) |
| Decimal/Date in DTO | Map to string for serialization |
| API response `id` | Every record has `id`; if PK ≠ id, map PK → id |
| Update payload | Service strips `id`, PK, readonly from data before Prisma |
| docker-compose.yml | Exists at project root; PostgreSQL service |
| Database container | Starts with `docker compose up -d` |
| prisma migrate dev | Runs successfully from server/ |
| Seed script | Exists; prisma db seed runs |
| GET /health | Backend responds with 200 and status payload |
| Check | Required artifact / rule |
| --- | --- |
| Frontend env | `client/.env.example` with required Vite auth vars |
| Backend env | `server/.env.example` with DB, CORS, and Keycloak vars |
| Fail-fast config | Startup fails when required auth env is missing |
| Realm artifact | Root generated realm import artifact with self-contained auth setup |
| Frontend auth | `keycloak.ts`, `authProvider.ts`, authenticated `dataProvider.ts` |
| Backend auth | `AuthModule`, guards, decorators, typed principal |
| JWKS strategy | explicit URL -> discovery -> certs fallback |
| Role source | `realm_access.roles` only |
| CRUD RBAC | GET viewer/editor/admin; write editor/admin; delete admin |
| `/health` | Public and returns 200 |
| Protected route unauthenticated | Returns `401` |
| Protected route insufficient role | Returns `403` |
| Token storage | No `localStorage` / `sessionStorage` persistence |
| Token refresh | Concurrency-safe single in-flight refresh |
| Prisma lifecycle | `OnModuleInit` + `$connect()`, no `beforeExit` |
| Update sanitization | Strip `id` / PK / readonly before Prisma update |
| React Admin `id` | Every record includes `id` |
| Database runtime | PostgreSQL compose exists and starts |
---
# Integration with generation pipeline
1. **Backend generation** (see `generation/backend-generation.md`) should produce artifacts that satisfy the above by default.
2. After generation, run this checklist manually or via a script that parses generated code and config.
3. If any check fails, the AI context or generator should be updated so that future runs pass.
1. Backend and frontend generation must produce artifacts that satisfy the above by default.
2. Runtime bootstrap must include Keycloak realm import/verification before app startup.
3. After generation, run this checklist manually or via an automated script.
4. If any check fails, update the generator context so future runs pass without manual repair.

View File

@@ -1,14 +1,42 @@
# Runtime Bootstrap
After project generation, the following commands must work in order so that the application runs without manual database provisioning or ad-hoc steps.
After project generation, the following commands and prerequisites must work in order so that the application runs without ad-hoc auth or database setup.
The generator must produce a **runnable development environment**: backend, frontend, and database must all be startable via a documented sequence.
The generator must produce a **runnable development environment** consisting of:
- external Keycloak identity provider
- backend API
- frontend SPA
- PostgreSQL database
The generator must also produce the runtime artifacts required to bootstrap auth from zero, including a root-level Keycloak realm import artifact. The repository default example filename is `toir-realm.json`, but future generations must allow a project-specific equivalent.
---
# Bootstrap Sequence
## 1. Start the database
## 1. Prepare Keycloak
Before starting the application, ensure an external Keycloak server is reachable and import or verify the generated realm artifact:
- root-level generated Keycloak realm import artifact
- repository default example filename: `toir-realm.json`
The runtime instructions must require importing or verifying this realm before frontend/backend startup.
Keycloak bootstrap expectations:
- realm import is self-contained
- frontend client exists
- backend audience client exists
- realm roles exist
- issued access tokens reliably contain `sub`, `aud`, and `realm_access.roles`
The generator must not rely on undeclared built-in client scopes magically existing after realm import.
---
## 2. Start the database
From the **project root**:
@@ -16,11 +44,11 @@ From the **project root**:
docker compose up -d
```
This starts the PostgreSQL container. The backend will connect to it using `DATABASE_URL` from `server/.env`.
This starts the PostgreSQL container. The backend connects to it using `DATABASE_URL` from `server/.env`.
---
## 2. Backend setup and start
## 3. Backend setup and start
```bash
cd server
@@ -31,15 +59,22 @@ npx prisma db seed
npm run start
```
- `npm install` installs dependencies and runs `postinstall` (e.g. `prisma generate`) if configured.
- `npx prisma generate` ensures Prisma client is generated explicitly after install/schema changes.
- `npx prisma migrate dev` creates/applies migrations and ensures the database schema exists.
- `npx prisma db seed` — populates minimal development data for immediate UI/API usage.
- `npm run start` starts the NestJS server (default port e.g. 3000).
- `npm install` installs dependencies and runs `postinstall` if configured.
- `npx prisma generate` ensures Prisma client is generated explicitly after install or schema changes.
- `npx prisma migrate dev` creates/applies migrations.
- `npx prisma db seed` inserts minimal development data.
- `npm run start` starts the NestJS server.
Backend startup requirements:
- required database and auth env vars must be present
- startup must fail fast if required env vars are missing
- CORS must support the SPA -> API bearer-token model
- `/health` must remain public
---
## 3. Frontend setup and start
## 4. Frontend setup and start
In a separate terminal, from the **project root**:
@@ -49,16 +84,37 @@ npm install
npm run dev
```
- `npm run dev` starts the Vite dev server (e.g. http://localhost:5173).
- `npm install` installs frontend dependencies including `keycloak-js`.
- `npm run dev` starts the Vite dev server.
Frontend startup requirements:
- required Vite auth vars must be present
- startup must fail fast if required auth vars are missing
- Keycloak must initialize before the SPA is rendered
- login must use redirect-based Keycloak authentication only
---
# Success Criteria
After running the above:
After completing the bootstrap sequence:
- Database container is running; Prisma can connect.
- Backend responds (e.g. `GET /health` returns `{ "status": "ok" }`).
- Frontend loads and can call the backend API.
- Keycloak is reachable and the generated realm has been imported or verified.
- Database container is running and Prisma can connect.
- Backend responds on `/health` without auth.
- Protected backend routes require a valid bearer token.
- Frontend loads, redirects through Keycloak login when needed, and uses bearer auth for all API calls.
The generator is responsible for producing all artifacts (docker-compose, schema, migrations, seed, env, health endpoint) so that this sequence succeeds without additional manual setup.
The generator is responsible for producing all artifacts and instructions needed for this sequence:
- `docker-compose.yml`
- schema
- migrations
- seed
- backend/frontend env examples
- health endpoint
- auth infrastructure
- root-level Keycloak realm import artifact
Docker scope must remain limited to PostgreSQL. Keycloak remains an external dependency in this repository.

View File

@@ -2,7 +2,9 @@
The generator must use **official CLI tools** to create base project structures.
The AI must **not** manually generate the entire project skeleton (e.g. by writing all config files and folder structure by hand). Using the CLI reduces errors and ensures compatibility with current tool versions.
The AI must **not** manually generate the entire project skeleton by hand. CLI scaffolding reduces drift and keeps the generated project aligned with current NestJS and Vite conventions.
Auth is part of the default generated runtime. Scaffolding must therefore install the required frontend and backend auth dependencies during the normal project bootstrap path.
---
@@ -19,9 +21,9 @@ npx @nestjs/cli@10.3.2 new server --package-manager npm --skip-git
## Rules
- **Project directory** must be `server`.
- **TypeScript** must be used (default for Nest CLI).
- **npm** must be the package manager (`--package-manager npm`).
- **Git** initialization must be skipped (`--skip-git`).
- **TypeScript** must be used.
- **npm** must be the package manager.
- **Git** initialization must be skipped.
## After scaffolding — install required dependencies
@@ -29,10 +31,16 @@ Run from the `server` directory:
```bash
npm install @prisma/client
npm install prisma --save-dev
npm install @nestjs/config
npm install jose
npm install prisma --save-dev
```
## Backend auth dependency rules
- `jose` must be installed by default because JWT verification is part of the default generated backend.
- The generator must **not** install deprecated Keycloak-specific Node adapters such as `keycloak-connect`.
---
# Frontend Scaffolding
@@ -48,7 +56,7 @@ npm create vite@5.2.0 client -- --template react-ts
## Rules
- **Project directory** must be `client`.
- **React + TypeScript** template must be used (`--template react-ts`).
- **React + TypeScript** template must be used.
## After scaffolding — install required dependencies
@@ -58,8 +66,14 @@ Run from the `client` directory:
npm install react-admin
npm install ra-data-simple-rest
npm install @mui/material @emotion/react @emotion/styled
npm install keycloak-js
```
## Frontend auth dependency rules
- `keycloak-js` must be installed by default because redirect-based Keycloak login is part of the default generated frontend.
- The generated frontend must use `keycloak-js` for Authorization Code + PKCE and must not generate a custom in-app username/password login form.
---
# Scaffolding Strategy
@@ -67,12 +81,12 @@ npm install @mui/material @emotion/react @emotion/styled
Generation pipeline order:
1. **Parse DSL** — Read domain, DTO, API, and UI DSL files.
2. **Run CLI scaffolding** — Create `server` with NestJS CLI and `client` with Vite CLI; install dependencies as above.
3. **Code generation** — Generate Prisma schema, NestJS modules/DTOs/PrismaService, and React Admin resources.
4. **Runtime infrastructure** — Generate `.env`, `.env.example`, package lifecycle scripts, and runtime config files.
2. **Run CLI scaffolding** — Create `server` with NestJS CLI and `client` with Vite CLI; install runtime and auth dependencies listed above.
3. **Code generation** — Generate Prisma schema, NestJS modules/DTOs/PrismaService/auth infrastructure, and React Admin resources/auth integration.
4. **Runtime infrastructure** — Generate backend/frontend `.env.example`, runtime config files, lifecycle scripts, and a root-level Keycloak realm import artifact (repository default example filename: `toir-realm.json`).
5. **Database runtime** — Generate `docker-compose.yml` in project root with PostgreSQL service (`postgres`, image `postgres:16`, port `5432:5432`).
6. **Migration** — Apply schema with `npx prisma migrate dev`.
7. **Seed** — Populate minimal development data with `npx prisma db seed`.
8. **Validation** — Run checks from `generation/post-generation-validation.md`.
8. **Validation** — Run checks from `generation/post-generation-validation.md`, including auth validation and realm-template validation.
Scaffolding (steps 12) must be done with the CLI; steps 38 are generated from the DSL and project docs.
Scaffolding (steps 12) must be done with the CLI. Steps 38 must be generated from the DSL and the project context documents, including the auth-specific context in `auth/*.md`.

View File

@@ -1,8 +1,40 @@
# Update Strategy
When DSL changes:
When the DSL changes, regeneration must preserve the default auth-enabled runtime rather than falling back to CRUD-only output.
1. Regenerate prisma.schema
2. Run prisma migrate dev
3. Regenerate Nest modules
4. Regenerate React Admin resources
## Required regeneration sequence
1. Regenerate `prisma/schema.prisma`.
2. Run `npx prisma migrate dev`.
3. Regenerate NestJS entity modules, DTOs, controllers, and services.
4. Regenerate backend auth infrastructure:
- `AuthModule`
- guards
- decorators
- typed authenticated principal
- typed config validation
- CRUD RBAC decorations
5. Regenerate React Admin resources.
6. Regenerate frontend auth infrastructure:
- `src/config/env.ts`
- `src/auth/keycloak.ts`
- `src/auth/authProvider.ts`
- authenticated `dataProvider.ts`
- `App.tsx` auth wiring
- `main.tsx` init-before-render flow
7. Regenerate backend and frontend `.env.example` files so the auth env contract stays in sync.
8. Regenerate the root-level Keycloak realm import artifact. The repository default example filename is `toir-realm.json`, but the generator must allow a project-specific equivalent.
9. Re-run post-generation validation, including:
- auth dependency checks
- fail-fast env checks
- `/health` public check
- unauthenticated protected route -> `401`
- insufficient role -> `403`
- realm-template validation
## Guardrails
- Regeneration must not strip auth back out of the project.
- Auth remains outside the DSL grammar, but it is part of the default generated runtime.
- If a DSL change affects entities or routes, the generator must re-apply the default CRUD RBAC rules automatically.
- If a DSL change affects runtime topology or naming, the generator must keep backend/frontend env examples, CORS rules, and the generated realm import artifact aligned with the generated app.