MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-426

6.13.5 Public-project categories / tags — the tag model + per-project tagging + the tag-facet read (browsable by topic)

Done
Description

Estimate: 50m · Depends on: 6.13.2

The category / tag model that makes the square browsable by topic — the GitHub-Topics / GitLab-Topics axis. A public project carries tags; the square filters to a tag (6.13.3) and lists the categories sorted by project count.

  • The tag model + join: a ProjectTag (a normalized tag — { id, slug, label }, bounded set) + a ProjectTagAssignment join to project, BOTH FKs modelled as Prisma @relation on both sides (the CLAUDE.md FK-as-@relation rule — no raw-SQL-only FK), unique on (projectId, tagId). Tags are reusable across projects (a shared topic vocabulary, the GitHub-Topics shape), not free-text per project duplicated.
  • Per-project tagging: the project admin assigns tags (a service + route, project-admin-gated — reuse the 6.4 project-admin check); bounded (a sane cap on tags per project) + the tags are validated against the bounded vocabulary. (Whether the admin can MINT a new tag vs only pick from a curated set is decided here — default to a curated/normalized vocabulary so the categories stay a clean browse axis, not a long tail of near-dupes.)
  • The tag-facet read: a read returning the categories with their PUBLIC-project counts (the GitLab "topics sorted by number of associated projects" view) — counting ONLY public projects (it respects the 6.13.2 access_level = public filter, so a tag’s count is its public-project count, and a tag with only non-public projects does not inflate the square). This read feeds the 6.13.3 category filter + the categories-browse panel.

Stay 4-layer: the tag model + assignment in prisma/schema.prisma + a single-op repository; the tagging service (admin-gated) + the tag-facet read service own their logic; no raw Prisma in routes.

Acceptance criteria

  • The migration adds ProjectTag + the ProjectTagAssignment join (every FK an @relation on both sides, unique on (projectId, tagId)); prisma migrate dev reports no drift.
  • A project admin assigns tags from the bounded vocabulary (project-admin-gated, capped per project); tags are reusable across projects (a shared topic, not per-project free-text dupes).
  • The tag-facet read returns categories with their PUBLIC-project counts (respecting the 6.13.2 public-only filter); it feeds the 6.13.3 category filter + the categories-browse view.
  • 4-layer respected (the tag model in a single-op repository, the tagging + facet logic in services, no raw Prisma in routes).

Context refs

  • 6.13.2 (the access_level = public filter the tag counts respect) + 6.13.3 (the category filter this model feeds).
  • scripts/plan-seed/data/story-6.4.ts — the project-admin check the tagging gate reuses.
  • GitHub Topics (https://github.com/explore) + GitLab project topics (https://docs.gitlab.com/user/project/project_topics/) — the reusable topic vocabulary + the topics-sorted-by-project-count view mirrored.
  • motir-core/CLAUDE.md § 4-layer + § migration FK-as-@relation rule.