Skip to content

[#390] Blade/issues backend#416

Merged
DVidal1205 merged 15 commits intomainfrom
blade/issues-backend
Mar 10, 2026
Merged

[#390] Blade/issues backend#416
DVidal1205 merged 15 commits intomainfrom
blade/issues-backend

Conversation

@DGoel1602
Copy link
Contributor

@DGoel1602 DGoel1602 commented Mar 10, 2026

Why

We need to be able to store and query issues for the new feature!

What

Adds schemas and the full router to make, delete, get, update schemas with proper authentication and considerations for future features. Schema includes main issues table and 2 join tables joining issues with both assignees and the teams that are allowed to see the issue (issue is only assigned to one team, but can be visible by multiple).

Also moves all relations in the database to a seperate file for cleaner navigation for future DB schema changes. (We should probably move these to more than 3 files anyway but it's fine for now)

Closes: #390
Closes: #391

Test Plan

Checklist

  • Database: No schema changes, OR I have contacted the Development Lead to run db:push before merging
  • Environment Variables: No environment variables changed, OR I have contacted the Development Lead to modify them on Coolify BEFORE merging.

Lots of schema changes please db push!

Summary by CodeRabbit

  • New Features

    • Full issue management API (create, read, update, delete).
    • Issue status workflow: BACKLOG, PLANNING, IN_PROGRESS, FINISHED.
    • Team visibility controls and user assignment for issues.
    • New permissions: Read Issues and Edit Issues.
  • Chores

    • Database schema and relation metadata extended to support issues, visibility and assignment join tables.
    • Public constants and exports updated to include issue-related values.

@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an Issue domain: DB enums/tables and relations; new constants and permissions; DB client includes relations; a TRPC issues router (create/get/getAll/update/delete) with permission and visibility logic; and the router is exposed from the API root.

Changes

Cohort / File(s) Summary
API: router integration
packages/api/src/root.ts, packages/api/src/routers/issues.ts
Exports and wires issuesRouter into the app router. Adds TRPC procedures createIssue, getIssue, getAllIssues, updateIssue, deleteIssue with permission checks and visibility/junction-table handling.
Database: schemas (tables & enums)
packages/db/src/schemas/knight-hacks.ts
Adds issue_status enum and Issue table (fields, FKs, parent FK), IssueSchema, and join tables IssuesToTeamsVisibility, IssuesToUsersAssignment with indices and cascade rules.
Database: relations mapping
packages/db/src/schemas/relations.ts
New centralized relation declarations for User, Role, Permission, Issue, join tables, Account/Member/Session relations; exposes relation metadata previously defined elsewhere.
Database: auth schema cleanup
packages/db/src/schemas/auth.ts
Removes exported relation declarations (relations relocated to relations.ts).
Database: client wiring
packages/db/src/client.ts
Imports relations, adds RelationsSchema to DatabaseSchema, and includes relations in the drizzle schema spread.
Constants & permissions
packages/consts/src/issue.ts, packages/consts/src/index.ts, packages/consts/src/permissions.ts
Adds ISSUE_STATUS constant and re-exports it; adds READ_ISSUES and EDIT_ISSUES entries to PERMISSION_DATA.
Manifest / package
manifest_file, package.json
Manifest and package metadata updated to reflect new files/exports.

Sequence Diagram

sequenceDiagram
    actor Client
    participant API as "API / Issues Router"
    participant Auth as "Auth / Permissions"
    participant DB as "Database"

    Client->>API: createIssue(payload)
    API->>Auth: verify EDIT_ISSUES
    Auth-->>API: allowed / denied
    alt allowed
        API->>DB: INSERT Issue
        DB-->>API: Issue created
        API->>DB: INSERT IssuesToTeamsVisibility / IssuesToUsersAssignment
        DB-->>API: junctions created
        API-->>Client: return Issue
    else denied
        API-->>Client: Unauthorized error
    end

    Client->>API: getIssue(id)
    API->>Auth: verify READ_ISSUES
    Auth-->>API: allowed / denied
    alt allowed
        API->>DB: SELECT Issue + apply visibility filters (officer/team/assignee/creator)
        DB-->>API: Issue + relations
        API-->>Client: return Issue
    else denied
        API-->>Client: Unauthorized error
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested labels

Major, Constants

Suggested reviewers

  • DVidal1205
  • must108
🚥 Pre-merge checks | ✅ 7 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (7 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title follows the required format with issue number in brackets and a concise description, staying well under the 72-character limit.
Linked Issues check ✅ Passed The PR successfully implements all core requirements from issues #390 and #391: complete Issue schema with all specified fields, many-to-many relationships for assignees and visibility, sub-issue support, full CRUD router with filtering, and new permissions (READ_ISSUES, EDIT_ISSUES).
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue requirements; the reorganization of relations into a separate file is a supporting improvement aligned with the stated objective of cleaner navigation.
No Hardcoded Secrets ✅ Passed Grep searches for common secret patterns across modified files returned no results; manual inspection confirms only legitimate TypeScript code without credentials.
Validated Env Access ✅ Passed Zero instances of direct process.env found in modified files; all dependencies properly imported from configured modules (@forge/consts, @forge/db, @forge/utils).
No Typescript Escape Hatches ✅ Passed No instances of any types, @ts-ignore, @ts-expect-error, or non-null assertions found across all modified files. Code consistently demonstrates strict TypeScript practices.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch blade/issues-backend

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@DGoel1602 DGoel1602 added Database Change modifies code in the DB package API Change modifies code in the global API/tRPC package labels Mar 10, 2026
@DGoel1602 DGoel1602 marked this pull request as ready for review March 10, 2026 05:27
@Spyderma9 Spyderma9 force-pushed the blade/issues-backend branch from 7468125 to 888314e Compare March 10, 2026 05:32
@DGoel1602 DGoel1602 force-pushed the blade/issues-backend branch from 942219a to 6207046 Compare March 10, 2026 05:35
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/api/src/routers/issues.ts`:
- Around line 57-72: The insert flow currently calls
db.insert(Issue).values(...).returning() and then separately calls
insertJunctions(issue.id, teamVisibilityIds, assigneeIds), which can leave the
DB in an inconsistent state if a later statement fails; change this to run the
insert and all related junction operations inside a single database transaction
(use your DB client's transaction API) so that the creation of Issue and the
calls performed by insertJunctions are committed or rolled back together; locate
the code using symbols Issue, db.insert(...).returning(), and insertJunctions
and wrap those calls in a transaction callback, moving any subsequent awaits
(including the TRPCError check) into the transaction scope and ensuring
insertJunctions uses the transactional connection/context.
- Around line 86-107: The visibility logic for non-officers only checks
IssuesToTeamsVisibility and omits the issue's owning team, so members of the
owning team may be denied; update the non-officer branch that computes
visibilityFilter (the code using db.query.Permissions.findMany,
visibilityFilter, exists(...) and IssuesToTeamsVisibility) to also allow access
when Issue.team is one of the user's roles — e.g. when userRoles is non-empty,
combine the exists(...) predicate with an OR that checks Issue.team is in
userRoles (use the existing inArray helper against Issue.team), or alternatively
ensure the owning team is seeded into IssuesToTeamsVisibility; adjust the
visibilityFilter expression accordingly so Issue.team is considered in the
authorization check.
- Around line 194-197: The query currently hydrates full auth rows via
teamVisibility.with.team: true and userAssignments.with.user: true, exposing
sensitive auth fields; change these to project only the public fields needed by
the client (e.g., team: { select: { id: true, name: true, slug: true } } and
user: { select: { id: true, name: true, username: true, avatarUrl: true } }) and
apply the same change for the other occurrence around lines 258-260 so assignee
and visible-team objects return only non-sensitive properties.
- Around line 19-22: CreateIssueInputSchema currently reuses IssueSchema and
leaks DB constraints; replace it with an explicit input schema (update
CreateIssueInputSchema) that omits any client-provided creator field and
enforces the same validations used in updateIssue: require name and description
with .min(1), validate links elements with .url(), validate status with
z.enum(ISSUE.ISSUE_STATUS), and require team (teamId or team field as per Issue
shape); keep assigneeIds and teamVisibilityIds as optional
z.array(z.string().uuid()). Also update the createIssue logic to perform the
multi-table writes (insert issue, then visibility/assignment inserts) inside a
single DB transaction so the operations are atomic.

In `@packages/db/src/schemas/knight-hacks.ts`:
- Around line 579-611: The junction tables IssuesToTeamsVisibility and
IssuesToUsersAssignment must declare referential delete behavior: update the
issueId foreign-key definitions (in IssuesToTeamsVisibility and
IssuesToUsersAssignment) that reference Issue.id to include .onDelete("CASCADE")
so rows are removed automatically when an Issue is deleted; leave or explicitly
set the teamId/userId foreign keys (references to Roles.id and User.id) to the
intended behavior if different (e.g., .onDelete("RESTRICT") or
.onDelete("CASCADE")) to make the constraint explicit.
- Around line 548-575: The Issue table lacks secondary indexes for the fields
used in read paths (team, creator, status, date, parent) causing full table
scans; add indexes in the same module where Issue is defined by creating indexes
on Issue.team, Issue.creator, Issue.status, Issue.date (and a composite index if
you expect common multi-column filters like (team, status) or (team, date)), and
add an index on Issue.parent (in addition to the existing foreign key) using
Drizzle’s index creation helpers so queries in
packages/api/src/routers/issues.ts that filter by team, creator, status, date,
or parent use those indexes for efficient lookups.
- Around line 29-30: This change added the new enums/tables (issue_status via
issueStatus, issue, issues_to_teams_visibility, issues_to_users_assignment) but
used db:push; update drizzle to generate migrations by adding an out property in
drizzle.config.ts (e.g., out: "./drizzle") and run drizzle-kit generate
postgresql to create and check in SQL migration files; in those migrations add
secondary indexes on issue(status), issue(team), issue(creator), issue(date) and
issue(parent) to support common queries; and modify the table
definitions/migrations to set explicit onDelete behavior (onDelete: "CASCADE" or
"RESTRICT") for the junction table foreign keys in issues_to_teams_visibility
and issues_to_users_assignment and for the self-referential parent FK on issue
to ensure referential integrity rather than relying on application ordering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 5b27364f-2d97-4846-9406-d36d43220918

📥 Commits

Reviewing files that changed from the base of the PR and between c936e2a and 7468125.

📒 Files selected for processing (9)
  • packages/api/src/root.ts
  • packages/api/src/routers/issues.ts
  • packages/consts/src/index.ts
  • packages/consts/src/issue.ts
  • packages/consts/src/permissions.ts
  • packages/db/src/client.ts
  • packages/db/src/schemas/auth.ts
  • packages/db/src/schemas/knight-hacks.ts
  • packages/db/src/schemas/relations.ts
💤 Files with no reviewable changes (1)
  • packages/db/src/schemas/auth.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/db/src/schemas/relations.ts (1)

41-44: Consider adding direct FK relations for eager-loading.

IssueRelations only wires the junction tables. Adding one relations for team, creator, event, and parent would enable Drizzle's with clause to load them in a single query:

💡 Optional enhancement
 export const IssueRelations = relations(Issue, ({ many, one }) => ({
   teamVisibility: many(IssuesToTeamsVisibility),
   userAssignments: many(IssuesToUsersAssignment),
+  team: one(Roles, { fields: [Issue.team], references: [Roles.id] }),
+  creator: one(User, { fields: [Issue.creator], references: [User.id] }),
+  event: one(Event, { fields: [Issue.event], references: [Event.id] }),
+  parentIssue: one(Issue, { fields: [Issue.parent], references: [Issue.id] }),
 }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/db/src/schemas/relations.ts` around lines 41 - 44, IssueRelations
currently only exposes junction-table many relations (IssuesToTeamsVisibility,
IssuesToUsersAssignment); add direct one relations so Drizzle's .with
eager-loading can pull related rows in one query. Update the relations(Issue,
...) call to include one mappings (e.g., team: one(Team), creator: one(User),
event: one(Event), parent: one(Issue)) alongside the existing many(...) entries
and ensure the corresponding FK columns on the Issue model exist and match those
relation targets.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/db/src/schemas/relations.ts`:
- Around line 41-44: IssueRelations currently only exposes junction-table many
relations (IssuesToTeamsVisibility, IssuesToUsersAssignment); add direct one
relations so Drizzle's .with eager-loading can pull related rows in one query.
Update the relations(Issue, ...) call to include one mappings (e.g., team:
one(Team), creator: one(User), event: one(Event), parent: one(Issue)) alongside
the existing many(...) entries and ensure the corresponding FK columns on the
Issue model exist and match those relation targets.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 32f4d1f9-589b-4123-8389-4cd17271b240

📥 Commits

Reviewing files that changed from the base of the PR and between 7468125 and 6207046.

📒 Files selected for processing (9)
  • packages/api/src/root.ts
  • packages/api/src/routers/issues.ts
  • packages/consts/src/index.ts
  • packages/consts/src/issue.ts
  • packages/consts/src/permissions.ts
  • packages/db/src/client.ts
  • packages/db/src/schemas/auth.ts
  • packages/db/src/schemas/knight-hacks.ts
  • packages/db/src/schemas/relations.ts
💤 Files with no reviewable changes (1)
  • packages/db/src/schemas/auth.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/consts/src/permissions.ts
  • packages/api/src/root.ts
  • packages/api/src/routers/issues.ts
  • packages/consts/src/issue.ts
  • packages/db/src/client.ts
  • packages/consts/src/index.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/db/src/schemas/knight-hacks.ts (1)

557-574: Consider adding explicit onDelete behavior to the Issue table's foreign keys.

The FKs for event, team, creator, and parentReference currently use the default NO ACTION. While this is safe (prevents orphaned records), being explicit makes the referential integrity contract clear to future maintainers.

Suggested behavior based on typical use cases:

  • event: onDelete: "set null" (issue survives if event is deleted)
  • team / creator: onDelete: "restrict" (explicit: require reassignment before deletion)
  • parentReference: onDelete: "set null" or "restrict" depending on whether child issues should become top-level or block parent deletion
💡 Example for the event FK
-    event: t.uuid().references(() => Event.id),
+    event: t.uuid().references(() => Event.id, { onDelete: "set null" }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/db/src/schemas/knight-hacks.ts` around lines 557 - 574, Update the
Issue table foreign key definitions to explicitly set onDelete behavior: for the
column and foreign key symbols event (t.uuid().references(() => Event.id)) set
onDelete: "set null"; for team (t.uuid().notNull().references(() => Roles.id))
and creator (t.uuid().notNull().references(() => User.id)) set onDelete:
"restrict"; and for parentReference (foreignKey({ columns: [table.parent],
foreignColumns: [table.id], name: "issue_parent_fk" })) set onDelete to either
"set null" or "restrict" per chosen policy; modify the
references()/foreignKey(...) calls accordingly to include the onDelete option so
the schema expresses these explicit referential actions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/db/src/schemas/knight-hacks.ts`:
- Around line 557-574: Update the Issue table foreign key definitions to
explicitly set onDelete behavior: for the column and foreign key symbols event
(t.uuid().references(() => Event.id)) set onDelete: "set null"; for team
(t.uuid().notNull().references(() => Roles.id)) and creator
(t.uuid().notNull().references(() => User.id)) set onDelete: "restrict"; and for
parentReference (foreignKey({ columns: [table.parent], foreignColumns:
[table.id], name: "issue_parent_fk" })) set onDelete to either "set null" or
"restrict" per chosen policy; modify the references()/foreignKey(...) calls
accordingly to include the onDelete option so the schema expresses these
explicit referential actions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 54bd41b1-62c7-46b9-8bac-507bd42a7230

📥 Commits

Reviewing files that changed from the base of the PR and between 6207046 and f96272a.

📒 Files selected for processing (2)
  • packages/api/src/routers/issues.ts
  • packages/db/src/schemas/knight-hacks.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/api/src/routers/issues.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/db/src/schemas/knight-hacks.ts (1)

559-566: Consider adding explicit onDelete behavior on team and creator foreign keys.

These FKs currently default to NO ACTION, meaning you cannot delete a Role or User if they have associated issues. This might be intentional (forces reassignment before deletion), but it's inconsistent with the rest of this file where similar references use explicit onDelete: "cascade".

Since both columns are notNull, your options are:

  • cascade — Deleting the team/user deletes their issues (matches codebase pattern)
  • restrict — Explicitly prevents deletion if issues exist (current implicit behavior, made explicit)
♻️ Suggested change (if cascade is desired)
     team: t
       .uuid()
       .notNull()
-      .references(() => Roles.id),
+      .references(() => Roles.id, { onDelete: "cascade" }),
     creator: t
       .uuid()
       .notNull()
-      .references(() => User.id),
+      .references(() => User.id, { onDelete: "cascade" }),

As per coding guidelines: "Foreign key constraints and referential integrity" — explicit behavior prevents surprises when deleting parent records.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/db/src/schemas/knight-hacks.ts` around lines 559 - 566, The foreign
keys for team and creator (references to Roles.id and User.id in knight-hacks
schema) lack explicit onDelete behavior; update both references to include an
explicit onDelete (preferably onDelete: "cascade" to match the rest of the file)
so deletions of a Role or User will cascade to their issues, e.g. modify the
team references(() => Roles.id) and creator references(() => User.id)
declarations to include the onDelete option.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/db/src/schemas/knight-hacks.ts`:
- Around line 559-566: The foreign keys for team and creator (references to
Roles.id and User.id in knight-hacks schema) lack explicit onDelete behavior;
update both references to include an explicit onDelete (preferably onDelete:
"cascade" to match the rest of the file) so deletions of a Role or User will
cascade to their issues, e.g. modify the team references(() => Roles.id) and
creator references(() => User.id) declarations to include the onDelete option.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: fd44f227-f577-48f7-808a-0a175b26f780

📥 Commits

Reviewing files that changed from the base of the PR and between f96272a and fb251e4.

📒 Files selected for processing (2)
  • packages/api/src/routers/issues.ts
  • packages/db/src/schemas/knight-hacks.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/api/src/routers/issues.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/db/src/schemas/knight-hacks.ts`:
- Around line 559-566: The current foreign keys for the Issue columns team and
creator reference Roles.id and User.id with onDelete: "cascade", which causes
Issues to be deleted when a Role or User is removed; change these constraints to
avoid destructive cascades—update the team foreign key (identifier: team) to use
onDelete: "restrict" (prevent role deletion while issues reference it) and
update the creator foreign key (identifier: creator) to either use onDelete:
"restrict" as well or make creator nullable and use onDelete: "set null" if you
want issues to survive account cleanup; adjust the column nullability
accordingly if you choose the set-null approach and run migrations to apply the
constraint changes.
- Around line 567-579: The current single nullable parent column (parent) with
its foreign key (parentReference / issue_parent_fk) only supports one-to-many
sub-issues; instead add a self-join table to model many-to-many links. Create a
new table (e.g., IssuesToSubIssues via createTable) with columns issue_id and
sub_issue_id that reference Issue.id (onDelete cascade) and a composite primary
key on [issue_id, sub_issue_id]; remove or deprecate the parent FK/indexes
(parent, parentReference, parentIdx, issue_parent_fk) and update any code that
writes/reads parent to use the new join table. Ensure migrations are written to
migrate existing parent values into the join table and to drop the old
column/index once consumers are updated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 0e45cd01-8fb7-4993-a08a-64ade84e035c

📥 Commits

Reviewing files that changed from the base of the PR and between fb251e4 and b15d5a5.

📒 Files selected for processing (1)
  • packages/db/src/schemas/knight-hacks.ts

Copy link
Contributor

@DVidal1205 DVidal1205 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me

@DVidal1205 DVidal1205 added this pull request to the merge queue Mar 10, 2026
Merged via the queue into main with commit 881ae73 Mar 10, 2026
8 checks passed
@DVidal1205 DVidal1205 deleted the blade/issues-backend branch March 10, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API Change modifies code in the global API/tRPC package Database Change modifies code in the DB package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[SPRINT-01] Create Issue router [SPRINT-01] Create Issue Schemas

3 participants