Skip to content

Conversation

fehranbit
Copy link
Member

@fehranbit fehranbit commented Oct 11, 2025

The UserRole enum has been renamed to ProjectRole in @prisma/client. This change updates all references to the old UserRole enum within src/actions/collaboration.ts to use the new ProjectRole identifier.

Specifically, this includes:

  • Updating the import statement.
  • Replacing z.nativeEnum(UserRole) with z.nativeEnum(ProjectRole).
  • Adjusting requiredRoles arrays and any inline role variables.

Review Notes:
Please verify that all instances of UserRole have been correctly updated to ProjectRole and that the application builds without TypeScript errors.

Summary by CodeRabbit

  • New Features
    • Inviting a member now validates the user and successfully adds them to the project.
  • Bug Fixes
    • Permissions updated so both Users and Admins can view project members.
    • Admin checks aligned across member invite, removal, and role updates.
  • Refactor
    • Role terminology updated across the app: “Member” replaced with “User.”
    • Invite Member dialog defaults to “User” role and shows updated role options.
    • Members table actions and role changes aligned with new role names.

@coderabbitai
Copy link

coderabbitai bot commented Oct 11, 2025

Walkthrough

Replaced UserRole with Role from the generated Prisma client across actions, libs, and UI. Updated permission checks and requiredRoles usage. Added concrete inviteMember logic to verify users and create project memberships. Adjusted membership semantics from MEMBER→USER. Updated UI components to use Role values and defaults.

Changes

Cohort / File(s) Summary of Changes
Actions: Collaboration
src/actions/collaboration.ts
Switched to Role enum in schemas and authorization; inviteMember now verifies invited user and creates a projectMember; requiredRoles updated (ADMIN/USER).
Libs: Permissions and Action Wrapper
src/lib/action.ts, src/lib/permissions.ts
Converted function signatures and checks from UserRole to Role; updated hasProjectPermission, isProjectAdmin, isProjectMember (MEMBER → USER).
UI: Project Member Management
src/components/models/projects/InviteMemberDialog.tsx, src/components/models/projects/ProjectMembersTable.tsx
Replaced UserRole with Role in imports, props, and handlers; updated default role to Role.USER; updated select options and role values passed to actions.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant D as InviteMemberDialog (UI)
  participant A as inviteMember (action)
  participant P as protectedAction
  participant R as Permissions
  participant DB as Prisma Client

  U->>D: Submit invite (email, role=Role.USER)
  D->>A: invoke inviteMember(input)
  A->>P: protectedAction({ projectId, requiredRoles:[Role.ADMIN] })
  P->>R: hasProjectPermission(user.role, [Role.ADMIN])
  R-->>P: boolean (allow/deny)
  alt authorized
    A->>DB: findUserByEmail(email)
    alt user found
      A->>DB: create projectMember(projectId, userId, role)
      DB-->>A: projectMember
      A-->>D: success
    else user not found
      A-->>D: error (user not found)
    end
  else unauthorized
    P-->>D: error (forbidden)
  end
Loading
sequenceDiagram
  autonumber
  participant C as getProjectMembers (action)
  participant P as protectedAction
  participant R as Permissions
  participant DB as Prisma Client

  C->>P: protectedAction({ requiredRoles:[Role.USER, Role.ADMIN] })
  P->>R: hasProjectPermission(user.role, [USER, ADMIN])
  R-->>P: allow/deny
  alt allowed
    C->>DB: query members by projectId
    DB-->>C: members list
    C-->>Caller: members list
  else denied
    P-->>Caller: error (forbidden)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

I twitch my ears at roles that roll,
From MEMBER to USER, we’ve set the goal.
Admins invite with careful might,
Permissions checked in Prisma light.
Hop, hop—new members join the ring,
Projects hum—what joy they bring! 🐇✨

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly and concisely summarizes the primary change of the pull request, namely renaming the UserRole enum to ProjectRole, without extraneous detail or ambiguity, making it easy for reviewers to understand the main refactoring at a glance.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/update-user-role-enum

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.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/lib/action.ts (1)

67-98: Authorization bypass: requiredRoles read from parsedInput instead of metadata.

All protected actions pass requiredRoles via the 2nd argument, but middleware checks parsedInput, so it skips auth and allows every call. Read from metadata (and derive projectId from metadata or input).

Apply this diff:

-export const protectedAction = authActionClient.use(async ({ next, ctx, parsedInput }) => {
-  const { projectId, requiredRoles } = parsedInput as { projectId: string; requiredRoles: Role[] };
+export const protectedAction = authActionClient.use(async ({ next, ctx, parsedInput, metadata }) => {
+  // Metadata supplied by the action definition (2nd arg)
+  const meta = metadata as
+    | { requiredRoles?: Role[]; projectId?: string | ((input: unknown) => string) }
+    | undefined;
+  const requiredRoles = meta?.requiredRoles;
+  const projectIdFromMeta = meta?.projectId;
+  const projectId =
+    typeof projectIdFromMeta === 'function'
+      ? projectIdFromMeta(parsedInput)
+      : projectIdFromMeta ?? (parsedInput as any)?.projectId;
 
-  if (!projectId || !requiredRoles) {
+  if (!projectId || !requiredRoles?.length) {
     // If projectId or requiredRoles are not provided, it's not a project-protected action
     return next({
       ctx: {
         ...ctx,
       }
     });
   }
 
   const projectMember = await prisma.projectMember.findUnique({
     where: {
       projectId_userId: {
         projectId,
         userId: ctx.user.id,
       },
     },
   });
 
   if (!projectMember || !hasProjectPermission(projectMember.role, requiredRoles)) {
     throw new Error('Not authorized to perform this action on this project.');
   }
 
   return next({
     ctx: {
       ...ctx,
       projectMember,
     }
   });
 });

Optionally, widen metadata validation to include requiredRoles:

 export function defineMetadataSchema() {
-  return zod.object({
-    actionName: zod.string()
-  });
+  return zod.object({
+    actionName: zod.string().optional(),
+    requiredRoles: zod.array(zod.nativeEnum(Role)).optional(),
+    // projectId may be a function; skip validation here
+  });
 }
src/components/models/projects/InviteMemberDialog.tsx (1)

20-27: Fix form typing and schema mismatch (email missing in schema).

  • useForm uses a value as a type. Use a proper inferred type.
  • The action schema currently lacks email; resolver will reject/mismatch. Align with schema after adding email.

Apply this diff here after exporting a type from actions:

-import { inviteMember, inviteMemberSchema } from "@/actions/collaboration";
+import { inviteMember, inviteMemberSchema } from "@/actions/collaboration";
+import type { InviteMemberInput } from "@/actions/collaboration";
 
-  const form = useForm<inviteMemberSchema>({
+  const form = useForm<InviteMemberInput>({
     resolver: zodResolver(inviteMemberSchema),
     defaultValues: {
       projectId,
       email: "",
       role: Role.USER,
     },
   });

Follow-up diff in actions to export the type is included in that file’s comment.

🧹 Nitpick comments (3)
src/actions/collaboration.ts (3)

36-47: Rename unused context param to satisfy linter.

user isn’t used. Rename to _user.

-export const removeMember = protectedAction(removeMemberSchema, { projectId: (input) => input.projectId, requiredRoles: [Role.ADMIN] }, async ({ projectId, memberId }, { user }) => {
+export const removeMember = protectedAction(
+  removeMemberSchema,
+  { projectId: (input) => input.projectId, requiredRoles: [Role.ADMIN] },
+  async ({ projectId, memberId }, { user: _user }) => {

55-69: Rename unused context param to satisfy linter.

-export const updateMemberRole = protectedAction(updateMemberRoleSchema, { projectId: (input) => input.projectId, requiredRoles: [Role.ADMIN] }, async ({ projectId, memberId, role }, { user }) => {
+export const updateMemberRole = protectedAction(
+  updateMemberRoleSchema,
+  { projectId: (input) => input.projectId, requiredRoles: [Role.ADMIN] },
+  async ({ projectId, memberId, role }, { user: _user }) => {

75-84: Broadened read access to USER and ADMIN—LGTM. Also fix unused arg.

-export const getProjectMembers = protectedAction(getProjectMembersSchema, { projectId: (input) => input.projectId, requiredRoles: [Role.USER, Role.ADMIN] }, async ({ projectId }, { user }) => {
+export const getProjectMembers = protectedAction(
+  getProjectMembersSchema,
+  { projectId: (input) => input.projectId, requiredRoles: [Role.USER, Role.ADMIN] },
+  async ({ projectId }, { user: _user }) => {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bef48ae and fa4f995.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • src/actions/collaboration.ts (4 hunks)
  • src/components/models/projects/InviteMemberDialog.tsx (3 hunks)
  • src/components/models/projects/ProjectMembersTable.tsx (4 hunks)
  • src/lib/action.ts (2 hunks)
  • src/lib/permissions.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/actions/collaboration.ts (1)
src/lib/action.ts (1)
  • protectedAction (67-98)
🪛 GitHub Check: lint-and-build
src/actions/collaboration.ts

[warning] 11-11:
'user' is defined but never used. Allowed unused args must match /^_/u


[warning] 36-36:
'user' is defined but never used. Allowed unused args must match /^_/u


[warning] 55-55:
'user' is defined but never used. Allowed unused args must match /^_/u


[warning] 75-75:
'user' is defined but never used. Allowed unused args must match /^_/u

🔇 Additional comments (6)
src/lib/action.ts (1)

6-6: Import swap to Role looks fine.

Enum import aligns with the rest of the PR.

src/components/models/projects/InviteMemberDialog.tsx (2)

4-4: Import aligned with new enum.

Switch to Role is consistent.


25-26: Default and options updated to Role—LGTM.

Role.USER default and options mapped from Role satisfy the new enum.

Also applies to: 54-55

src/components/models/projects/ProjectMembersTable.tsx (1)

3-3: Enum migration to Role is consistent and correct.

  • Types and calls updated to Role.
  • Passing member.user.id matches server expecting userId in unique key.

Also applies to: 18-19, 50-58, 86-91

src/lib/permissions.ts (1)

1-13: Permissions updated to Role with correct semantics.

  • hasProjectPermission via includes is fine.
  • isProjectMember allowing USER or ADMIN matches new roles.
src/actions/collaboration.ts (1)

49-56: Schema change to Role is correct. Also fix unused arg in handler below.

Ensure updateMemberRole callers pass valid Role values (UI now does).

Comment on lines +4 to 5
import { Role } from "@/generated/prisma/client";

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Verify migration completeness (UserRole → Role/ProjectRole) and build health.

Run a quick repo scan for any leftover UserRole/ProjectRole references and imports from @prisma/client, and spot type issues like useForm generic misuse.

Also applies to: 11-12, 36-37, 55-56, 75-76


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Check for leftover UserRole/ProjectRole usages"
rg -nP -C2 '\b(UserRole|ProjectRole)\b'

echo
echo "2) Check for Role imports from @prisma/client (should be from generated path now)"
rg -nP -C0 'import\s*{[^}]*\b(Role|UserRole|ProjectRole)\b[^}]*}\s*from\s*[\"\']@prisma/client[\"\']'

echo
echo "3) Check for useForm generic mis-uses referencing schema values"
rg -nP -C3 'useForm<\s*inviteMemberSchema\s*>' --type=ts --type=tsx

echo
echo "4) Spot actions that destructure unused { user }"
rg -nP -C2 'async\s*\([^)]*\{\s*user\s*\}\s*\)' src/actions

echo
echo "5) Sanity-check that all action calls pass requiredRoles in options"
rg -nP -n 'protectedAction\([^,]+,\s*\{[^}]*requiredRoles' src/actions

echo
echo "6) Grep for any remaining z.nativeEnum\(UserRole\)"
rg -nP -C2 'z\.nativeEnum\(\s*UserRole\s*\)'

Length of output: 49


🏁 Script executed:

#!/bin/bash

echo "1) Check for leftover UserRole/ProjectRole usages"
rg -nP -C2 '\b(UserRole|ProjectRole)\b' || true

echo
echo "2) Check for Role imports from @prisma/client (should be from generated path now)"
rg -nP -C0 "import\s*{[^}]*\b(Role|UserRole|ProjectRole)\b[^}]*}\s*from\s*['\"]@prisma/client['\"]" || true

echo
echo "3) Check for useForm generic mis-uses referencing schema values"
rg -nP -C3 'useForm<\s*inviteMemberSchema\s*>' --type=ts --type=tsx || true

echo
echo "4) Spot actions that destructure unused { user }"
rg -nP -C2 'async\s*\([^)]*\{\s*user\s*\}\s*\)' src/actions || true

echo
echo "5) Sanity-check that all action calls pass requiredRoles in options"
rg -nP -n 'protectedAction\([^,]+,\s*\{[^}]*requiredRoles' src/actions || true

echo
echo "6) Grep for any remaining z.nativeEnum\(UserRole\)"
rg -nP -C2 'z\.nativeEnum\(\s*UserRole\s*\)' || true

Length of output: 3035


🏁 Script executed:

#!/bin/bash

echo "Re-check for useForm generic mis-uses referencing schema values without --type flags"
rg -nP 'useForm<\s*inviteMemberSchema\s*>' -g '*.ts' -g '*.tsx' || true

Length of output: 186


Migration complete; finalize cleanup

  • No leftover UserRole/ProjectRole imports or references; all protectedAction calls include requiredRoles.
  • Drop unused { user } destructuring in src/actions/collaboration.ts (lines 11–12, 36–37, 55–56, 75–76) or utilize user.
  • In src/components/models/projects/InviteMemberDialog.tsx:20, replace useForm<inviteMemberSchema> with useForm<z.infer<typeof inviteMemberSchema>>().
🤖 Prompt for AI Agents
In src/actions/collaboration.ts around lines 11–12, 36–37, 55–56 and 75–76 there
are unused destructured { user } variables; remove the unused { user } entries
from those function parameter lists (or, if intended, use the user variable in
the function body). Ensure each protectedAction call keeps requiredRoles intact
and run TypeScript compile to confirm no unused-variable errors remain.

Comment on lines 6 to 9
export const inviteMemberSchema = z.object({
projectId: z.string(),
email: z.string().email(),
role: z.nativeEnum(UserRole),
role: z.nativeEnum(Role),
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add email to schema and export inferred input type.

Handler and UI require email, but schema omits it, causing type/validation errors.

Apply this diff:

 export const inviteMemberSchema = z.object({
   projectId: z.string(),
+  email: z.string().email(),
   role: z.nativeEnum(Role),
 });
 
-export const inviteMember = protectedAction(inviteMemberSchema, { projectId: (input) => input.projectId, requiredRoles: [Role.ADMIN] }, async ({ projectId, email, role }, { user }) => {
+export type InviteMemberInput = z.infer<typeof inviteMemberSchema>;
+
+export const inviteMember = protectedAction(
+  inviteMemberSchema,
+  { projectId: (input) => input.projectId, requiredRoles: [Role.ADMIN] },
+  async ({ projectId, email, role }, { user: _user }) => {
   // TODO: Implement actual invitation logic (e.g., send email, create invitation token)
   // For now, directly add the user if they exist
   const existingUser = await prisma.user.findUnique({ where: { email } });
 
   if (!existingUser) {
     throw new Error("User not found");
   }
 
-  await prisma.projectMember.create({
+  // Consider handling duplicates gracefully; upsert can avoid unique constraint errors
+  await prisma.projectMember.create({
     data: {
       projectId,
       userId: existingUser.id,
       role,
     },
   });
 
   return { success: true, message: `User ${email} invited as ${role}` };
-});
+});

Also applies to: 11-29

🤖 Prompt for AI Agents
In src/actions/collaboration.ts around lines 6-9 (and also affecting lines
11-29), the inviteMemberSchema is missing the required email field and the
inferred input type is not exported; update the schema to include email:
z.string().email() (or z.string().email().nonempty() per project conventions),
export the schema if not already exported, and add an exported type alias like
export type InviteMemberInput = z.infer<typeof inviteMemberSchema> so handlers
and UI can import the validated/typed input.

@Stereco-btc Stereco-btc merged commit b8e376e into main Oct 11, 2025
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants