[Story-1029] Forums catalog and forum_type cut

Story 1029Epic None31 files · +1942/−692

PR #254 · pramaan-io/pramaan-functions · merged 2026-06-04

How it flows

sequenceDiagram
  autonumber
  participant mig as Migration
  participant api as Forum API
  participant mat as Matter / Proceeding
  mig->>mig: create business.forums + RLS
  mig->>api: schemas live
  api->>api: create / list / update / archive
  api->>mat: nullable restrictive FKs
  mat-->>api: delete blocked if referenced
  mat->>mat: forumId copied from matter default

app-api.json · +879 — new methods

Methodin → outwhat it does
_enable_forum_rls() → None
_disable_forum_rls() → None
upgrade() → None

What changed, file by file

Alembic migration that creates the business.forums table, turns on row-level security per firm, and adds nullable restrictive foreign keys from matters and proceedings.

new methodsignature
_enable_forum_rls() → None
_disable_forum_rls() → None
upgrade() → None
downgrade() → None
New fields: revision : strdown_revision : str | Sequence[str] | Nonebranch_labels : str | Sequence[str] | Nonedepends_on : str | Sequence[str] | None

Forum domain service that lists, creates, updates, and soft-deletes forums, scoped strictly to the current firm via the session context.

new methodsignature
_current_firm_id(session) → UUID
_current_user_id(session) → UUID | None
_view(forum) → ForumView
_get_forum(session, firm_id, forum_id, include_deleted) → Forum
list_forums(session, include_archived) → ForumListResponse
create_forum(session, req) → ForumView
update_forum(session, forum_id, req) → ForumView
delete_forum(session, forum_id) → None

Forum SQLModel entity carrying id, firm_id, name, availability lifecycle, and full audit columns for creation, archival, and soft deletion.

new classsignature
Forum(class)
New fields: id : UUIDfirm_id : UUIDname : stravailability : strcreated_by_user_id : UUID | Nonearchived_at : datetime | Nonearchived_by_user_id : UUID | Nonedeleted_at : datetime | Nonedeleted_by_user_id : UUID | None

Pydantic DTOs for forum create, update, view, list, and delete-conflict responses, replacing the legacy forum_summary module entirely.

new classsignature
ForumAvailability(class)
ForumCreateRequest(class)
ForumUpdateRequest(class)
ForumView(class)
ForumListResponse(class)
ForumDeleteConflictResponse(class)
New fields: name : stravailability : ForumAvailabilityid : UUIDarchived_at : datetime | Nonearchived_by_user_id : UUID | Nonecreated_by_user_id : UUID | Nonematter_count : intproceeding_count : int

FastAPI router exposing CRUD /app/forums endpoints, replacing the legacy /app/forums/summary with archive, unarchive, and guarded delete.

new methodsignature
list_forums(db, include_archived) → ForumListResponse
create_forum(req, db) → ForumView
update_forum(forum_id, req, db) → ForumView
delete_forum(forum_id, db) → Response

Adds a forum validator so a proceeding can only reference a forum that belongs to the same firm as the matter it sits under.

new methodsignature
_validate_forum(session, firm_id, forum_id) → None

Matter DTOs drop the legacy forum_type string and now expose default_forum_id as a proper foreign key field on the response shape.

New fields: default_forum_id : UUID | Noneforum_type : str

Matter service gains a forum validator that confirms a matter's default forum belongs to the calling firm before persisting.

new methodsignature
_validate_forum(session, firm_id, forum_id) → None

OpenAPI generation helper extended with origin-extension attachment, supporting the schema diffs this PR introduces.

new methodsignature
add_origin_extensions(payload, origin) → None

Proceeding entity gains a nullable forum_id column linking each proceeding to the new forums catalog instead of a string label.

New fields: forum_id : UUID | None

Matter entity replaces the forum_type string with a default_forum_id foreign key into the new business.forums catalog.

New fields: default_forum_id : UUID | None

Proceeding DTO exposes forum_id as the operative forum venue, replacing the old forum_type echo on the wire.

New fields: forum_id : UUID | None

Failure cases the PR closes

Soft delete refuses if matter or proceeding count > zero
Row-level security blocks cross-firm forum reads at the database
Legacy forum_type is gone from DTOs, filters, seeds, constants, OpenAPI
Proceedings inherit the matter's default forum on create
Targeted pytest on app-forums and app-matters pass clean
mypy noise exists but lives outside this story's surface

Files changed (31)

File±
alembic/env.py+1
alembic/versions/7f2a91c4d6b8_0070_forums_catalog.py ★+190
docs/walkthroughs/story-1029-walkthrough.html+72
openapi/admin-api.json+68 −34
openapi/app-api.json ★+879 −275
scripts/generate_openapi.py+19
src/app_api/router_matters.py+1 −3
src/domains/forum/__init__.py+5 −1
src/domains/forum/dtos/__init__.py+14 −6
src/domains/forum/dtos/forum.py+50
src/domains/forum/dtos/forum_summary.py+0 −44
src/domains/forum/entity.py+98
src/domains/forum/router.py+47 −8
src/domains/forum/service.py ★+172 −39
src/domains/matter/dtos/matter.py+38 −8
src/domains/matter/entity.py+6 −9
src/domains/matter/service.py+35 −8
src/domains/matter_type/seeds/__init__.py+0 −2
src/domains/matter_type/seeds/system_matter_types.py+1 −19
src/domains/proceeding/dtos/proceeding.py+6
src/domains/proceeding/entity.py+8
src/domains/proceeding/service.py+42
src/shared/constants/__init__.py+0 −4
src/shared/constants/firm_status.py+0 −25
src/shared/db/entities.py+2
tests/contract/generate_fixtures.py+36 −1
tests/contract/seed.py+8
tests/integration/test_app_forums.py+114 −183
tests/integration/test_app_matters.py+27 −9
tests/integration/test_rls_isolation.py+1 −12
tests/unit/test_matter_dtos.py+2 −2