3.1.3 Board repositories — `boardRepository` / `boardColumnRepository` / `boardColumnStatusRepository`
Estimate: 16m · Depends on: 3.1.1
The data-access leaf layer for boards — three repositories, each a set of single-Prisma-op methods, write methods requiring tx: Prisma.TransactionClient (CLAUDE.md). Named by primary entity, not call site.
boardRepository — findByProject(projectId, workspaceId, tx?) → Board[] (ordered by createdAt; v1 returns the single default board), findDefaultForProject(projectId, workspaceId, tx?) → Board | null (the project's board for the v1 single-board case), findById(boardId, workspaceId, tx?) → Board | null, create(data, tx) → Board.
boardColumnRepository — findByBoard(boardId, workspaceId, tx?) → BoardColumn[] (ordered by position asc), findById(columnId, workspaceId, tx?) → BoardColumn | null, create(data, tx) → BoardColumn, update(columnId, data, tx) → BoardColumn (for the WIP/rename writes a later story uses). Batched findByBoards(boardIds[], workspaceId, tx?) for no-N+1 reads, mirroring workflowsRepository.findStatusesByProjects.
boardColumnStatusRepository — findByBoard(boardId, workspaceId, tx?) → BoardColumnStatus[] (the full mapping for the projection to bucket statuses → columns), create(data, tx) → BoardColumnStatus, deleteByColumn(columnId, tx) and deleteByStatus(boardId, statusId, tx) (re-map writes a later admin story uses). findByColumn(columnId, workspaceId, tx?) for a single column’s statuses.
Rules: each method is ONE Prisma call (no composition — that’s the service); reads used only by read paths may use the db singleton; writes require tx; every method takes workspaceId and scopes by it at the app layer (finding #26) on top of RLS. No DTO mapping here (services map). No business logic.
Acceptance criteria
- Three repositories exported as
export const <name>Repository = { ... }, each method a single Prisma op. - All write methods (
create/update/delete*) take a requiredtx: Prisma.TransactionClient; pure reads may usedb. - Every method takes + filters by
workspaceId(app-layer gate atop RLS, finding #26). - Batched
findByBoardsexists for the projection’s no-N+1 read; no repository method calls another repository. - Vitest (real Postgres) covers the reads + a representative write under a transaction, asserting workspace scoping.
Context refs
lib/repositories/workflowsRepository.ts— the exact shape (single-op, required-tx writes, batched reads) this mirrorslib/repositories/workItemRepository.ts— read-path / filter conventionsmotir-core/CLAUDE.md— Repository layer rules (single op, required tx, entity-named, leaves)