mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-07-27 22:45:03 +00:00
refactor(projects): move authz persistence into a repositories layer
Reorganises the authz lib along DDD lines by separating the persistence boundary from the domain: src/lib/projects/authz.ts domain — pure decisions src/app/repositories/projects/authzRepository.ts persistence — Drizzle The repository sits alongside src/app/db/ and mirrors the existing db/schema/projects/ layout, so schema and repository stay symmetrical as Ara Projects grows more of both. Dependencies point one way: the repository imports the domain's fact types and returns them, so the domain never learns a table shape and continues to import no database client. The 34 guard tests still run with no connection. No behaviour change — the queries and guards are untouched. Note this is a new pattern for the codebase: the prevailing convention is a colocated queries.ts/server.ts per lib folder, which 88 files still follow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f358b874df
commit
1ce8a65faf
2 changed files with 20 additions and 13 deletions
|
|
@ -1,8 +1,17 @@
|
|||
/**
|
||||
* DB-backed fact loaders feeding the pure guards in `./authz` (issue #408).
|
||||
* Ara Projects authorization repository (issue #408).
|
||||
*
|
||||
* Kept separate from the guards so those stay unit-testable without a database.
|
||||
* Route handlers load facts here, then decide with `authz`.
|
||||
* The persistence boundary for authorization: it owns the Drizzle queries and
|
||||
* returns domain-shaped facts (`ProjectAuthzFacts`, `WorkOrderAuthzFacts`)
|
||||
* rather than raw rows, so the domain layer never sees a table shape. The
|
||||
* decisions themselves live in `@/lib/projects/authz`, which imports no
|
||||
* database client and unit-tests without a connection.
|
||||
*
|
||||
* const [user, project] = await Promise.all([
|
||||
* loadAuthzUser(userId),
|
||||
* loadProjectAuthzFacts(projectId),
|
||||
* ]);
|
||||
* if (!canViewProject(user, project)) return forbidden();
|
||||
*
|
||||
* Every loader is a single round trip; none probes per-row inside a list.
|
||||
*/
|
||||
|
|
@ -20,7 +29,7 @@ import type {
|
|||
AuthzUser,
|
||||
ProjectAuthzFacts,
|
||||
WorkOrderAuthzFacts,
|
||||
} from "./authz";
|
||||
} from "@/lib/projects/authz";
|
||||
|
||||
/**
|
||||
* The organisations a user belongs to, via `team_members → team → org_id`.
|
||||
|
|
@ -3,16 +3,14 @@
|
|||
* Ara Projects route handler (issue #408). No route may re-implement any of
|
||||
* this inline.
|
||||
*
|
||||
* This module is deliberately **pure**: it imports no database client, so the
|
||||
* guards unit-test without mocking a connection pool. The DB-backed resolvers
|
||||
* that produce its inputs live next door in `./authzQueries` — a route handler
|
||||
* loads facts there, then decides here.
|
||||
* This is the domain layer: deliberately **pure**, importing no database
|
||||
* client, so the guards unit-test without mocking a connection pool. It owns
|
||||
* the vocabulary (`ProjectRole`, `ProjectAuthzFacts`) and the decisions.
|
||||
*
|
||||
* const [user, project] = await Promise.all([
|
||||
* loadAuthzUser(userId),
|
||||
* loadProjectAuthzFacts(projectId),
|
||||
* ]);
|
||||
* if (!canViewProject(user, project)) return forbidden();
|
||||
* Persistence lives behind the repository at
|
||||
* `@/app/repositories/projects/authzRepository`, which loads those facts. A
|
||||
* route handler loads there, then decides here — and the dependency only ever
|
||||
* points that way, so the domain never learns a table shape.
|
||||
*
|
||||
* Scoping note: Ara Projects are **organisation**-scoped. The existing
|
||||
* `team_portfolio_permissions` machinery is portfolio-scoped and plays no part
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue