Commit Graph

11 Commits

Author SHA1 Message Date
Julien Gautier 383e565b93 chore(prisma): point root postinstall to BFF schema
CI / check (pull_request) Has been cancelled
CI / perf (pull_request) Has been cancelled
CI / a11y (pull_request) Has been cancelled
CI / commits (pull_request) Has been cancelled
CI / scan (pull_request) Has been cancelled
`@prisma/client`'s postinstall hook runs `prisma generate` from the
workspace root, where it doesn't find a schema in the default
locations and warns. The hook then exits without generating the
typed client — fine today (the schema has no models yet, no code
calls into the client at runtime), but a trap waiting for the first
real model to be added.

Declare `package.json#prisma.schema` so the postinstall hook locates
the schema at apps/portal-bff/prisma/schema.prisma and generates the
typed client unconditionally on every `pnpm install`.

Note: Prisma 6.19 emits a deprecation warning ("migrate to
prisma.config.ts"). Migrating that format will be folded into the
future Prisma 7 upgrade (which itself depends on nestjs-prisma
catching up to Prisma 7 and will get its own ADR).
2026-05-04 15:27:37 +02:00
julien b7adf2e308 fix(ci): post-runner-image cleanup (#7)
CI / commits (push) Has been skipped
CI / scan (push) Failing after 6m19s
CI / check (push) Successful in 11m43s
CI / perf (push) Successful in 12m0s
CI / a11y (push) Successful in 10m8s
## Summary
Three follow-up fixes uncovered by the first end-to-end CI run on the self-hosted act_runner image (smoke-test PR).

- **`check` job** — replace `nrwl/nx-set-shas@v4` (GitHub-API only, 404 on Gitea) with a manual shell step that derives `NX_BASE`/`NX_HEAD` from local git history (merge-base on `pull_request`, `HEAD~1` on `push`).
- **`perf` job** — pin to `catthehacker/ubuntu:full-22.04` so Lighthouse CI finds a real Chrome. The default act image (`act-22.04`) is the minimal variant, ships without browsers.
- **`package.json`** — declare `pnpm.onlyBuiltDependencies` to silence the "Ignored build scripts" warning and approve only the post-install hooks we actually rely on (Nx, Prisma, esbuild, swc, native watchers/resolvers).

No ADR change — these are purely operational adjustments.

## Test plan
- [ ] CI run on this PR: `check` and `perf` jobs both green.
- [ ] No "Ignored build scripts" warning in `pnpm install` step output.
- [ ] After merge, `push` event on `main` re-runs `check`, `scan`, `perf`, `a11y` cleanly (no `nx-set-shas` 404).

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #7
2026-05-04 14:55:14 +02:00
julien 25093db0b9 fix(ci): declare packageManager for pnpm/action-setup (#6)
CI / commits (push) Has been skipped
CI / check (push) Failing after 10s
CI / perf (push) Failing after 5m23s
CI / scan (push) Failing after 6m41s
CI / a11y (push) Successful in 9m51s
## Summary

- Add `"packageManager": "pnpm@10.33.2"` to root `package.json`.

## Motivation

The smoke-test CI run got past `actions/checkout@v4` (runner image fix from PR #4 working), but failed on `pnpm/action-setup@v3` with:

```
Error: No pnpm version is specified.
Please specify it by one of the following ways:
  - in the GitHub Action config with the key "version"
  - in the package.json with the key "packageManager"
```

The action looks at `package.json`'s `packageManager` field by default. Setting it there (rather than hardcoding `version:` in every workflow YAML) is the single-source-of-truth approach: corepack reads the same field locally, so dev environments and CI converge automatically.

## Implementation notes

- Pinned to `pnpm@10.33.2` (the exact version the workspace was bootstrapped with). Future bumps go through a dedicated `chore(deps): bump pnpm to X.Y.Z` PR that updates this field — keeps CI and local dev in lockstep.
- `corepack` will pick up this declaration on the next `corepack enable` or pnpm invocation. Devs already on 10.33.2 see no change; devs on a different version are nudged onto the canonical one.
- Workflow YAMLs (`.gitea/workflows/ci.yml`, `security-scheduled.yml`) untouched — `pnpm/action-setup@v3` reads `packageManager` directly.

## Verification

- [ ] Next CI run succeeds at `pnpm/action-setup@v3` (= the step that fails on smoke-test PR currently). Confirmed by re-pushing to the smoke-test branch (or any other PR) after this one merges.
- [x] `pnpm install` locally still works with the declaration (no change for someone already on 10.33.2).

## Related

- Follow-up to PR #4 (runner image fix). Together they unblock the CI gates.
- Smoke-test PR can be closed once a real PR (e.g. the upcoming "Local infra recipe") demonstrates green CI end to end.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #6
2026-05-04 12:36:38 +02:00
julien a0f6e594ba fix(portal-bff): downgrade Prisma to 6.x for nestjs-prisma compatibility (#3)
CI / commits (push) Has been skipped
CI / check (push) Failing after 4s
CI / scan (push) Failing after 6s
CI / a11y (push) Failing after 3s
CI / perf (push) Failing after 4s
## Summary

- Pin `prisma` and `@prisma/client` to `^6` (resolved 6.19.3) instead of the previous `^7`.
- Switch the generator in `apps/portal-bff/prisma/schema.prisma` from `prisma-client` to `prisma-client-js` (Prisma 6's default).
- Add the explicit `url = env("DATABASE_URL")` in the `datasource db` block (required by Prisma 6; was implicit in Prisma 7 via `prisma.config.ts`).
- Remove `apps/portal-bff/prisma.config.ts` (Prisma 7-only feature).
- Drop the now-irrelevant `/generated/prisma` entry from `apps/portal-bff/.gitignore`.

## Motivation

Two distinct Prisma 7 breaking changes surfaced as runtime errors in `pnpm nx serve portal-bff`:

1. **Generator output path:** Prisma 7's default `prisma-client` generator writes to a custom output dir declared in the schema. `@prisma/client/default.js`'s runtime stub still resolves `.prisma/client/default` in a sibling `node_modules/.prisma/client/`, which only the legacy `prisma-client-js` generator populates. Result: `ESM loader error: Cannot find module '.prisma/client/default'`.

2. **PrismaClientOptions API:** In Prisma 7, `PrismaClientOptions` no longer exposes `datasourceUrl` nor `datasources`. The connection must come through a driver adapter (e.g. `@prisma/adapter-pg`). `nestjs-prisma@0.27.0` calls `super(undefined)` when no `prismaServiceOptions` is passed, which Prisma 7 rejects with `PrismaClientInitializationError: PrismaClient needs to be constructed with a non-empty, valid PrismaClientOptions`.

Both issues are downstream of Prisma 7's "adapter-first" architecture being incompatible with `nestjs-prisma`'s still-Prisma-6-shaped wrapper. Working around each issue separately would lead into bespoke wiring (custom PrismaService, manual `@prisma/adapter-pg` install, hand-rolled DI). That's bricolage on a foundational layer.

Per CLAUDE.md ("default to stable, recognized, battle-tested choices; cutting-edge alternatives only when the trade-off is captured in an ADR"), the cheapest, cleanest fix is to use Prisma 6 — still actively maintained, fully aligned with `nestjs-prisma`'s design, supported by every tutorial and example in the wider ecosystem.

## Implementation notes

- ADR-0006 ("Persistence — PostgreSQL with Prisma") specifies "Prisma" without pinning a version. The version choice is a tactical detail. No ADR amendment.
- `apps/portal-bff/.env.example` is unchanged — the `DATABASE_URL` variable name is the same in Prisma 6 and 7.
- `prisma.config.ts` removed: it was a Prisma 7 file that Prisma 6 ignores; keeping it would only confuse a future contributor.
- The Prisma 7 `prisma-client` generator left a residual output dir at `apps/portal-bff/generated/` from the previous setup; removed locally and excluded from gitignore (no longer needed).
- Re-evaluate when `nestjs-prisma` releases an update aligned with Prisma 7's adapter model. The path back is symmetric: pin Prisma to `^7`, restore the schema's `prisma-client` generator, install `@prisma/adapter-pg`, and update `app.module.ts` to instantiate the adapter.

## Verification

- [x] `pnpm exec prisma generate` populates `node_modules/.../@prisma+client@6.19.3/.prisma/client/default.js` (no more 7.x in the active resolution).
- [x] `pnpm nx build portal-bff` green.
- [X] `pnpm nx serve portal-bff` boots cleanly (no `PrismaClientInitializationError`) — to be confirmed locally before merge. Connection to a real Postgres is out of scope of this PR; if Postgres is not running, an `ECONNREFUSED` is expected and unrelated.
- [ ] `pnpm ci:check` — runs in CI on PR open.

## Related

- [ADR-0006 — Persistence: PostgreSQL with Prisma](docs/decisions/0006-persistence-postgresql-prisma.md). Generator and version are tactical; no amendment.
- Future: revisit Prisma version when `nestjs-prisma` ships an update with first-class driver-adapter support.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #3
2026-05-04 10:46:12 +02:00
Julien Gautier be7187d5f2 chore: scaffold Gitea Actions pipelines per ADR-0015 / ADR-0017
CI / commits (push) Has been skipped
CI / check (push) Failing after 36s
CI / a11y (push) Failing after 2s
CI / perf (push) Failing after 38s
CI / scan (push) Failing after 2m15s
Wire the CI/CD pipeline scaffolding. Implements the level-2 (Gitea
Actions) part of ADR-0015 with the thin-YAML pattern, plus the perf
gate from ADR-0017. The level-1 decisions (gates list, branch model,
secrets policy) are unchanged.

Files:
- .nvmrc pins Node 24 (latest LTS major) for actions/setup-node.
- package.json gains four ci:* scripts that the workflows call:
  - ci:check  - 'nx affected -t format:check lint test build'
  - ci:audit  - 'pnpm audit --audit-level=moderate'
  - ci:commits- 'commitlint --from $COMMIT_LINT_FROM --to HEAD'
  - ci:perf   - 'nx build portal-shell --configuration=production
                 && lhci autorun --config=./lighthouserc.js'
  All four runnable locally; CI workflows are thin wrappers.
- @lhci/cli added as a dev dependency for ci:perf.
- lighthouserc.js encodes the Core Web Vitals thresholds from
  ADR-0017 (LCP <= 2500ms, CLS <= 0.1, TBT <= 200ms, server
  response <= 800ms, Performance >= 0.9). v1 measures only the
  static-served portal-shell bundle (Nx Welcome placeholder); the
  critical-routes list expands as real screens land.
- .gitea/workflows/ci.yml runs five jobs on PR + push to main:
  check, scan (audit + Trivy + gitleaks), commits (PR-only), perf,
  a11y. The a11y job is a placeholder that no-ops with a clear
  message; it wires up for real with the first Playwright e2e suite
  (ADR-0016). All gates are blocking - branch protection on main
  will require all five jobs green.
- .gitea/workflows/security-scheduled.yml runs weekly (Mon 04:00
  UTC) for full-tree Trivy + gitleaks (no severity filter, no
  skip-dirs - broader than per-PR) plus a Lighthouse run against
  the prod URL when vars.LHCI_PROD_URL is set.

Slight deviation from ADR-0015 §'Level 2': Trivy and gitleaks are
binaries (Go) and don't have clean npm wrappers, so they are
invoked through their official Gitea-Actions-compatible actions
inside the YAML rather than via 'pnpm ci:scan'. The high-level
decision (gates: audit + secret-scan + dep-scan) is unchanged; the
script/action boundary is shifted by one tool. Documented here for
traceability; no ADR amendment needed.

Operational TODOs (not blocking the scaffold):
- pnpm audit currently reports 6 moderate transitive vulnerabilities
  (ajv, brace-expansion, yaml, @hono/node-server, follow-redirects,
  uuid) in deep deps of Nx/Angular plugins. CI will fail on this
  gate until upstream updates land or Renovate bumps; expected and
  documented.
- act_runner self-hosted instances are not yet registered against
  the Gitea organisation; the workflows reference [self-hosted,
  on-prem] runner labels per ADR-0015. CI will not actually execute
  until the runners are up - that's an infra task.
- Branch protection rules on main (require all five jobs green) are
  configured in Gitea UI, not in this commit.
- Lighthouse-prod scheduled job runs only when vars.LHCI_PROD_URL
  is set - skipped silently otherwise. To be configured once a prod
  environment exists.
2026-04-30 19:48:20 +02:00
Julien Gautier bd8eefb44a chore: configure Tailwind 4 and align lib tsconfigs
Wire Tailwind CSS 4 in the portal-shell app per ADR-0016 (the future
host of spartan-ng components in libs/shared/ui will read from the
Tailwind tokens via the shared-tokens lib).

- pnpm add -D tailwindcss @tailwindcss/postcss postcss
- apps/portal-shell/postcss.config.js declares @tailwindcss/postcss
- apps/portal-shell/src/styles.scss renamed to styles.css and now
  contains a single @import 'tailwindcss' directive. Plain CSS for the
  global file avoids the Sass @import deprecation warning that fires
  when Tailwind directives sit inside SCSS. Component-level styles can
  still use SCSS.
- apps/portal-shell/project.json styles entry updated accordingly.

Side fix: align libs/shared/tokens and libs/shared/util tsconfigs from
module: commonjs to module: esnext. The Nx @nx/js:library --bundler=
tsc generator emits commonjs by default, but tsconfig.base.json
specifies moduleResolution: bundler, which TS only allows alongside
esnext or es2015+ modules. Without the alignment, those libs failed to
build (TS5095). All apps and libs now build green.

spartan-ng wiring is intentionally NOT in this commit. spartan-ng is
currently at 0.0.1-alpha.681 - clearly pre-1.0, which trips the
project rule against pre-1.0 dependencies. ADR-0016 chose spartan-ng
with the copy-paste mitigation, but the alpha state warrants an
explicit go/no-go decision before committing the workspace to it.
2026-04-30 17:50:35 +02:00
Julien Gautier 3308fd6071 chore: configure local quality gates per ADR-0007
Wire Husky 9 + lint-staged + commitlint with @commitlint/config-
conventional - the local pair of git hooks defined in ADR-0007.

- package.json declares the prepare script ('husky') so the hooks
  install on every fresh pnpm install (no manual step).
- .husky/pre-commit runs 'pnpm exec lint-staged' which formats staged
  files via prettier (lint and tests stay in CI per ADR-0015 to keep
  commits fast).
- .husky/commit-msg runs 'pnpm exec commitlint --edit "$1"' to enforce
  Conventional Commits at commit time. Same check is repeated in CI
  for defense in depth.
- commitlint.config.cjs at the workspace root extends @commitlint
  /config-conventional.
- lint-staged config in package.json restricts to ts/tsx/js/jsx/html/
  scss/css/md/json/yml/yaml under prettier --write.

Verified: a 'feat: ...' commit message is accepted; a message without
a Conventional Commits type is rejected.
2026-04-30 17:43:49 +02:00
Julien Gautier 8de19320c5 chore: generate shared and feature libs with module boundaries per ADR-0003
Generate the four phase-4 libraries:

- libs/shared/tokens (project name shared-tokens) - plain TS lib via
  @nx/js:library; will host the a11y design tokens (palette,
  contrast tiers, spacing, motion) once Tailwind lands in phase 5;
  consumable by both apps; tagged scope:shared, type:shared.
- libs/shared/util (shared-util) - plain TS lib for cross-cutting
  utility code; tagged scope:shared, type:shared.
- libs/shared/ui (shared-ui) - Angular standalone library that will
  host the spartan-ng components copy-pasted in phase 5; Angular-only
  so tagged scope:portal-shell, type:shared. unitTestRunner=
  vitest-analog because vitest-angular requires a buildable lib.
- libs/feature/auth (feature-auth) - placeholder Angular standalone
  feature lib to demonstrate the type:feature pattern; tagged
  scope:portal-shell, type:feature.

@nx/enforce-module-boundaries depConstraints replaced (root
eslint.config.mjs) with the rules from ADR-0003:

  scope:portal-shell -> scope:portal-shell, scope:shared
  scope:portal-bff   -> scope:portal-bff,   scope:shared
  scope:shared       -> scope:shared
  type:app           -> type:feature, type:shared
  type:feature       -> type:feature, type:shared
  type:shared        -> type:shared

This forbids portal-shell from importing portal-bff code (and vice
versa) and prevents shared libs from depending on feature libs.

Project names follow the convention of ADR-0003 (feature-<name> /
shared-<scope>) by passing --name explicitly to the generator; the Nx
22 default takes only the last directory segment.

Sanity check: pnpm nx run-many -t lint and -t test pass for the 8
projects (4 apps/e2e + 4 libs).

Side effects from the generators: tsconfig.base.json paths populated
with the lib import aliases; nx.json gains vite/playwright plugin
entries; .gitignore picks up vitest.config.*.timestamp* (Vitest temp
files); package.json gains @analogjs/vitest-angular and related
devDeps; pnpm-lock.yaml regenerated. Two eslint-disable comments in
portal-bff-e2e support files were trimmed by lint --fix - those files
already lint clean without the directive.
2026-04-30 17:37:29 +02:00
Julien Gautier 2b0e20bd85 chore: wire PostgreSQL + Prisma per ADR-0006
Add Prisma 7 + nestjs-prisma. The schema lives at
apps/portal-bff/prisma/schema.prisma with provider postgresql; the new
prisma-client generator (Prisma 7 default) outputs the typed client to
apps/portal-bff/generated/prisma/ which is gitignored.

apps/portal-bff/src/app/app.module.ts imports PrismaModule.forRoot
({ isGlobal: true }) so PrismaService is injectable across the BFF
without per-module imports.

apps/portal-bff/.env.example documents DATABASE_URL with a local-dev
default, plus a forward list of env vars introduced by upcoming phases
and ADRs (auth, sessions, MFA, observability, audit, downstream APIs)
- catalog reference, not implementation. The actual .env stays
gitignored at both repo root and app levels.

prisma.config.ts (Prisma 7's TypeScript config) is committed; it loads
DATABASE_URL via dotenv. Schema and migrations paths are pinned to
prisma/ relative to the bff app.

PostgreSQL provisioning, RLS policies for the dual-audience design,
the dedicated audit schema with role grants (audit_owner / audit_writer
/ audit_reader / audit_archiver per ADR-0013), and column-level
encryption for L3-scoped data are out of scope of this commit -
they belong with the future on-prem infrastructure ADR.
2026-04-30 17:28:54 +02:00
Julien Gautier bea5e1954f chore: generate portal-shell and portal-bff apps per ADR-0004 / ADR-0005
Add the @nx/angular, @nx/nest, @nx/vite, @nx/eslint plugins, then
generate the two apps. Adjust the empty-template tsconfig.base.json
to be Angular-compatible (drop project references and customConditions
that the empty-template defaults to but Angular doesn't support; keep
the strict-TS extensions from ADR-0004).

apps/portal-shell (Angular 21):
- standalone APIs, routing, SCSS, esbuild
- vitest-angular as unitTestRunner, playwright for e2e
- strict mode
- tags scope:portal-shell, type:app
- app.config.ts wired with provideZonelessChangeDetection() per
  ADR-0004 (Angular 21 + Nx 22 generates without zone.js by default)

apps/portal-bff (NestJS 11):
- Express adapter (default per ADR-0005)
- Jest as unitTestRunner
- tags scope:portal-bff, type:app
- main.ts wired with a global ValidationPipe configured
  whitelist + forbidNonWhitelisted + transform per ADR-0005
- Phase-2 security additions (helmet, CORS, sessions, CSRF, rate
  limit, auth guards, error filter) deferred to their respective
  ADRs - placeholder comment in main.ts

Workspace dependencies: class-validator + class-transformer added
(required by NestJS ValidationPipe at runtime). Nx-generated
.gitignore additions (.angular, __screenshots__) merged into ours.
.vscode/extensions.json and launch.json added by Nx are kept (do not
override our existing settings.json).
2026-04-30 16:12:42 +02:00
Julien Gautier 64ea72b321 chore: bootstrap Nx workspace per ADR-0002
Initialize the Nx 22 monorepo via copy-from-scratch (the
create-nx-workspace 'apps' preset is now mapped to nrwl/empty-template
in Nx 22). Workspace files extracted from a scratch generation and
adapted to the project:

- package.json: name 'apf-portal' (renamed from @org/source default)
- tsconfig.base.json: customConditions aligned to apf-portal; strict TS
  extended per ADR-0004 with noUncheckedIndexedAccess,
  exactOptionalPropertyTypes, noPropertyAccessFromIndexSignature
- nx.json: nxCloudId stripped (on-prem, no Nx Cloud SaaS)
- .prettierrc: project convention (singleQuote, semi, printWidth 100)
- pnpm-workspace.yaml: apps/* and libs/** for the integrated layout
- pnpm-lock.yaml committed

Plugins for Angular, NestJS, Vite, ESLint will be added in the next
phase when apps are generated. The auto-generated CLAUDE.md / AGENTS.md
/ .claude/ / .github/ from the bootstrap were intentionally not
carried over (we keep our own CLAUDE.md and use Gitea, not GitHub).
2026-04-30 16:07:11 +02:00