#393 Share The Remaining Authorization Scope Copies In Application Actions - #523
Merged
Merged
Conversation
buildApplicationWhere/buildApplicationScopeWhere/buildReviewablePositionWhere replace the file-private buildBaseWhere and the two hand-rolled manager clauses in actions/applications.ts; the 'listable' | 'reviewable' argument replaces the per-caller withdrawn patches, and the omitted status key on the scope builder makes overwriting the manager scope a type error rather than a "merge, don't overwrite" comment. /applications search params are now zod-parsed instead of cast. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
b-at-neu
commented
Aug 20, 2026
b-at-neu
left a comment
Collaborator
Author
There was a problem hiding this comment.
Code Review — Cycle 1 · approved
0 open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #393
Summary
prisma/actions/applications.tsinto a sharedlib/auth/scopes.ts, alongside the file-privatebuildBaseWherethat used to live inprisma/data/applications.ts.draftvswithdrawndistinction as a'listable' | 'reviewable'argument on the shared builder, instead of each caller patching a post-spreadstatus: { notIn: [...] }.ascasts on/applicationssearch params with a zod schema.Changes
lib/auth/scopes.ts(new) —buildReviewablePositionWhere,buildApplicationScopeWhere(typedOmit<Prisma.ApplicationWhereInput, 'status'>so a caller's status filter can never overwrite the manager scoping), andbuildApplicationWhere(user, 'listable' | 'reviewable').prisma/data/applications.ts— droppedbuildBaseWhereand the two post-spreadwithdrawnpatches;getApplicationForReview,getApplications,getApplicationsTotalusebuildApplicationWhere(user, 'listable');getApplicationStatusCounts,getRecentApplicationsusebuildApplicationWhere(reviewer, 'reviewable');getReviewablePositionsusesbuildReviewablePositionWhere.prisma/actions/applications.ts—updateApplicationStatus/updateApplicationStatuseslose their admin/manager ternaries and the "Merge, don't overwrite" comments in favor of the shared builders; dropped the now-unusedPUBLISHED_POSITION_WHERE/NON_REVIEWABLE_APPLICATION_STATUSESimports.lib/constants.ts/lib/types.ts— addedAPPLICATION_SORT_FIELDS/APPLICATION_SORT_DIRECTIONSas the single source for the sort unions and the page's zod enums.app/(main)/(auth)/applications/page.tsx— asearchParamsSchema(zod) replaces theascasts, thetypeof === 'string'checks, and theVALID_SORT_*arrays; every field is.optional().catch(undefined)so an unparseable or repeated (string[]) value is dropped without discarding the rest or throwing during render.tests/db/authorization.test.ts— new coverage for the action-side position scoping (draft/soft-deleted position, withdrawn application) and the listable/reviewable split on the data side.No behavior change: the scoping semantics, the two actions'
{ error }copy, and the page's happy-path rendering are identical to before.Testing plan
/applicationslists only that position's applications, withdrawn included, drafts excluded; the position filter offers only your positions./applications/<id>→ 404 page.updateApplicationStatus/updateApplicationStatuseson an application belonging to a position they don't manage, or one that's on a draft/soft-deleted position, or one that's withdrawn → denied (throw / skipped, not a data leak)./applications:?status=bogus,?sort=bogus,?sort=name:sideways,?status=applied&status=reviewing(repeated param),?q=(empty), a 5,000-characterq— each loads the page with the bad filter ignored and the good ones applied; no error page.?sort=name:asc,?status=reviewing,?positionId=<id>,?userId=<id>still work, including the per-user deep link from the users table.npm run test(unit + db) green.Automated checks
npm run prettier:check— passnpm run eslint:check— passnpm run tsc:check— passNotes
prisma/actions/question-files.ts(getQuestionFileUrl) is deliberately left alone — it's owner-or-manager and intentionally reaches drafts/draft positions, so folding it into the reviewer scope would be a behavior change, not this ticket.getManagedPositions/isManageralso stay as-is — management surfaces include draft positions, a different predicate despite the resemblance.ENGINEERING.md§3 capability-matrix row naming the shared builder is left to a main-session PR, since.claude/isn't writable by pipeline agents.