Authorize Status Changes
“Only the project owner can move issues between columns.” This rule is different from the earlier ones because the authorization lookup has to read two tables. We are modifying an issue, but whether you are allowed to modify it depends on the project the issue belongs to. That is a cross-table check, and it is why assertProjectOwner is exported as a helper.
Import the Guard From projects.ts
Open convex/issues.ts and add the following import at the top:
import { assertProjectOwner } from "./projects";
Tighten the updateStatus Mutation
Update the updateStatus mutation handler to call assertProjectOwner before allowing the status change:
export const updateStatus = mutation({
args: {
id: v.id("issues"),
status: v.union(
v.literal("todo"),
v.literal("in-progress"),
v.literal("done"),
),
},
handler: async (ctx, args) => {
const issue = await ctx.db.get(args.id);
if (issue === null) {
throw new ConvexError("Issue not found");
}
// Only the owner of the issue's parent project may move it between
// columns. Issue creators do not have this right — once an issue is
// filed, triage is the project owner's responsibility.
await assertProjectOwner(ctx, issue.projectId);
await ctx.db.patch(args.id, { status: args.status });
},
});
The handler does three things, in order:
- Load the issue (
ctx.db.get(args.id)). If it does not exist, throw aConvexError. - Call
assertProjectOwnerwith the project’s ID (issue.projectId), which we just learned from step 1. The guard loads the project, confirms the current user owns it, and throws if not. - If both of those pass, run the
patch.
Notice that we have to load the issue ourselves first. assertProjectOwner needs a project ID, and the only way to get a project ID from an issue ID is to load the issue. So there are two database reads here, and we cannot avoid either one, but both are single-document lookups and they are cheap.
Checkpoint: Commit your progress.
git add .
git commit -m "tracker-13: Authorize status changes (project owner only)"
git push