feat(projects): projects table + owner_project FK; ProjectId/Project types

First commit of §7 multi-repo ownership. A 'project' is a repo-root that
declaratively manages a slice of the shared group tree; each group node is
owned by exactly one project (single-owner-per-node). This lands the registry
+ shared types and wires the inert 0047 `owner_project` seam — no behavior
change yet (claim logic is the next commit).

- migration 0066: `projects` table (UUID pk, unique slug, name, created_by →
  admin_users ON DELETE SET NULL); `groups.owner_project` gains its FK →
  projects(id) ON DELETE SET NULL (un-claim, never cascade-destroy a tree) +
  an index.
- shared: `ProjectId` (id_type! macro) + `Project` struct; `Group` gains
  `owner_project: Option<ProjectId>`.
- group_repo: GROUP_COLS + the ancestors recursive-CTE term now carry
  `owner_project`, so `ancestors()` surfaces ownership for the nearest-claimed
  fold; GroupRow + From updated.
- schema snapshot re-blessed; /version schema assertion 65 → 66.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-06 20:01:57 +02:00
parent f1730a1ba0
commit ba97c35aaf
8 changed files with 106 additions and 9 deletions

View File

@@ -105,8 +105,8 @@ impl PostgresGroupRepository {
}
}
const GROUP_COLS: &str =
"id, parent_id, slug, name, description, structure_version, created_at, updated_at";
const GROUP_COLS: &str = "id, parent_id, slug, name, description, structure_version, created_at, \
updated_at, owner_project";
#[async_trait]
impl GroupRepository for PostgresGroupRepository {
@@ -157,7 +157,8 @@ impl GroupRepository for PostgresGroupRepository {
SELECT {GROUP_COLS}, 0 AS depth FROM groups WHERE id = $1
UNION ALL
SELECT g.id, g.parent_id, g.slug, g.name, g.description, \
g.structure_version, g.created_at, g.updated_at, c.depth + 1 \
g.structure_version, g.created_at, g.updated_at, g.owner_project, \
c.depth + 1 \
FROM groups g JOIN chain c ON g.id = c.parent_id \
WHERE c.depth < 64
)
@@ -349,6 +350,7 @@ struct GroupRow {
structure_version: i64,
created_at: chrono::DateTime<chrono::Utc>,
updated_at: chrono::DateTime<chrono::Utc>,
owner_project: Option<Uuid>,
}
impl From<GroupRow> for Group {
@@ -360,6 +362,7 @@ impl From<GroupRow> for Group {
name: r.name,
description: r.description,
structure_version: r.structure_version,
owner_project: r.owner_project.map(Into::into),
created_at: r.created_at,
updated_at: r.updated_at,
}