-
Notifications
You must be signed in to change notification settings - Fork 77
feat: manage project members #284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThis update introduces project member management features, including the ability for project owners to remove members via the frontend UI. Supporting changes span backend access control, new API endpoints, frontend stores, composables, and UI enhancements. Additional improvements include persistent state for several Pinia stores and animated transitions for snackbars. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Frontend
participant Backend
participant AccessControlService
participant ProjectUsersService
User->>Frontend: Click "Remove member"
Frontend->>Frontend: Show confirmation dialog
User->>Frontend: Confirm removal
Frontend->>Backend: DELETE /projects/:projectId/users/:projectUserId
Backend->>ProjectUsersService: remove(projectUserId)
ProjectUsersService->>AccessControlService: revokePermissions(userId, projectId)
ProjectUsersService->>DB: Remove ProjectUser, update User.project=null
Backend-->>Frontend: Success/Failure
Frontend->>Frontend: Show snackbar with result
Assessment against linked issues
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
npm error Exit handler never called! 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (4)
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🧹 Nitpick comments (15)
packages/frontend/src/components/common/base/BaseSnackbar.vue (2)
13-13
: Consider retaining helpful comments for maintainability.The removed comment explained the countdown step calculation logic, which could be valuable for future maintenance. Consider keeping such explanatory comments, especially for mathematical calculations.
21-21
: Consider retaining helpful comments for maintainability.The removed comment documented the interval update frequency (100ms), which is useful context for understanding the countdown behavior.
packages/frontend/src/main.ts (1)
43-43
: Consider selective retry configuration for different query types.The global
retry: false
setting improves error handling control but disables retries for all queries. Consider if some queries (like fetching project data) might benefit from retries while authentication-related queries should fail fast.You could configure retry behavior per query type:
// In individual services useQuery({ queryKey: ['projects'], queryFn: fetchProjects, retry: 3, // Override global setting for this query })packages/frontend/src/composables/useTryCatch.ts (2)
6-11
: Consider making error messages configurable.The error message is hardcoded to a generic message. Consider allowing custom error messages for better user experience.
-function showErrorSnackbar() { +function showErrorSnackbar(message?: string) { showSnackbar({ - message: 'Something went wrong, please try again.', + message: message || 'Something went wrong, please try again.', color: 'error', }); }
13-19
: Enhance function handling for better flexibility.The current implementation has limitations: it doesn't handle return values, async functions, or provide error details for debugging.
Consider this more flexible implementation:
-function tryCatch(fn: () => unknown) { +function tryCatch<T>(fn: () => T, errorMessage?: string): T | undefined { try { - fn(); + return fn(); } catch (error) { + console.error('tryCatch error:', error); - showErrorSnackbar(); + showErrorSnackbar(errorMessage); + return undefined; } } + +async function tryCatchAsync<T>(fn: () => Promise<T>, errorMessage?: string): Promise<T | undefined> { + try { + return await fn(); + } catch (error) { + console.error('tryCatchAsync error:', error); + showErrorSnackbar(errorMessage); + return undefined; + } +}packages/shared/src/lib/auth/index.ts (1)
34-43
: Well-designed AccessControl type with good flexibility.The type definition properly models access control permissions with:
- Clear permission levels using existing enums
- Flexible resource targeting via optional properties
- Proper metadata tracking (id, createdAt)
- Integration with existing shared types
However, consider adding validation to ensure at least one resource is specified:
export type AccessControl = { id: number; permissionLevel: PermissionLevel; user: User; project?: Project; workspace?: Workspace; space?: Space; list?: List; createdAt: string; -}; +} & ( + | { project: Project; workspace?: never; space?: never; list?: never } + | { workspace: Workspace; project?: never; space?: never; list?: never } + | { space: Space; project?: never; workspace?: never; list?: never } + | { list: List; project?: never; workspace?: never; space?: never } +);This ensures exactly one resource type is specified while maintaining type safety.
packages/frontend/src/services/useAccessControlService.ts (1)
14-19
: Consider enhancing query configuration for better caching behavior.The service implementation is correct and follows established patterns. However, consider these improvements:
Query key specificity: The current key
['accessControl']
is global. Consider making it user-specific like['accessControl', { userId }]
if multiple users' permissions might be cached.Stale time configuration: Adding a
staleTime
property would improve performance by reducing unnecessary refetches, similar to other services in the codebase.function useGetUserPermissions() { return useQuery({ - queryKey: ['accessControl'], + queryKey: ['accessControl', 'me'], queryFn: getUserPermissions, + staleTime: 5 * 60 * 1000, // 5 minutes, consistent with other services }); }packages/frontend/src/services/useProjectUsersService.ts (1)
38-48
: Clarify parameter naming for better code readability.The delete function parameter
projectUserId
could be misleading. Based on the RESTful endpoint pattern/projects/${projectId}/users/${projectUserId}
, this appears to be the ProjectUser entity ID, but the name suggests it's a User ID.Consider renaming for clarity:
async function deleteProjectUser({ projectId, - projectUserId, + userId, // or projectUserEntityId if it's the ProjectUser.id }: { projectId: number; - projectUserId: number; + userId: number; // or projectUserEntityId: number }): Promise<void> { - return sendRequest(`/projects/${projectId}/users/${projectUserId}`, { + return sendRequest(`/projects/${projectId}/users/${userId}`, { method: 'DELETE', }); }packages/frontend/src/components/common/base/BaseSnackbarWrapper.vue (2)
11-11
: Consider extractingclosingSnackbars
to the snackbar store for better state management.Since this closing state is integral to the snackbar lifecycle, it would be more appropriate to manage it within the snackbar store itself rather than as local component state. This would centralize all snackbar-related state management.
37-40
: Extract inline styles to a computed property for better maintainability.The inline style object could be extracted to a computed property, especially if you plan to add more dynamic styles in the future.
+const getSnackbarStyle = (index: number) => ({ + bottom: `${snackbarOffsets[index]}px`, + transition: 'all ease 0.2s', +}); <base-snackbar v-if="!closingSnackbars.has(snackbar.id)" :color="snackbar.options.color" :timeout="snackbar.options.timeout ?? 4000" - :style="{ - bottom: `${snackbarOffsets[index]}px`, - transition: 'all ease 0.2s', - }" + :style="getSnackbarStyle(index)" @close="handleSnackbarClose(snackbar)" >packages/frontend/src/stores/access.control.ts (1)
31-36
: Consider returningPermissionLevel.NONE
instead ofnull
for consistency.Since
PermissionLevel.NONE
exists in the enum, returning it instead ofnull
would provide better type consistency and avoid null checks in consuming code.function getPermissionLevel( type: 'project' | 'workspace' | 'space' | 'list', id: number -): PermissionLevel | null { - return permissionsMap.value[type]?.[id]?.permissionLevel ?? null; +): PermissionLevel { + return permissionsMap.value[type]?.[id]?.permissionLevel ?? PermissionLevel.NONE; }packages/frontend/src/components/common/settings/ProjectMembersSettings.vue (1)
40-50
: Consider adding more member management actions to the menu.The menu currently only has a delete action. You might want to add actions like changing roles, viewing member details, etc.
Do you want me to suggest additional menu items that could enhance the member management functionality?
packages/backend/src/app/common/auth/services/access.control.service.ts (3)
186-199
: Sequential recursion may slow bulk grants
for ... of
combined withawait
runs the DB write serially for every child item; granting hundreds of descendants could become noticeably slow.- for (const child of children) { - for (const item of child.items) { - await this.grantPermission(user, child.type, item.id, permissionLevel); - } - } + // Execute grants in parallel on each recursion layer + for (const child of children) { + await Promise.all( + child.items.map((item) => + this.grantPermission(user, child.type, item.id, permissionLevel), + ), + ); + }Using
Promise.all
preserves the recursive structure while significantly improving throughput.
217-226
: Same performance caveat for revocationsRevocation mirrors the grant logic and inherits the same serial-execution penalty. Consider parallelising with
Promise.all
, analogous to the previous suggestion.
264-273
: Eager-loading all relations could be heavy
findAllForUser
fetches every access-control row together withproject
,workspace
,space
, andlist
relations. In large organisations this may:• Load thousands of rows into memory.
• Cause N+1 queries if relations aren’t properly joined.Consider adding pagination or allowing the caller to specify which relations it needs:
- async findAllForUser(userId: number) { - return this.accessControlRepository.find({ - where: { user: { id: userId } }, - relations: ["project", "workspace", "space", "list"], - }); - } + async findAllForUser( + userId: number, + relations: ("project" | "workspace" | "space" | "list")[] = [], + ) { + return this.accessControlRepository.find({ + where: { user: { id: userId } }, + relations, + }); + }This keeps the method flexible and avoids unnecessary load on the DB.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (20)
packages/backend/src/app/common/auth/auth.module.ts
(3 hunks)packages/backend/src/app/common/auth/controllers/access.control.controller.ts
(1 hunks)packages/backend/src/app/common/auth/controllers/auth.controller.ts
(1 hunks)packages/backend/src/app/common/auth/services/access.control.service.ts
(4 hunks)packages/backend/src/app/common/auth/services/auth.service.ts
(2 hunks)packages/backend/src/app/common/projects/project-users/project.user.entity.ts
(1 hunks)packages/backend/src/app/common/projects/project-users/project.users.service.ts
(2 hunks)packages/frontend/src/components/common/base/BaseSnackbar.vue
(1 hunks)packages/frontend/src/components/common/base/BaseSnackbarWrapper.vue
(1 hunks)packages/frontend/src/components/common/settings/ProjectMembersSettings.vue
(2 hunks)packages/frontend/src/composables/useTryCatch.ts
(1 hunks)packages/frontend/src/main.ts
(1 hunks)packages/frontend/src/services/useAccessControlService.ts
(1 hunks)packages/frontend/src/services/useProjectUsersService.ts
(2 hunks)packages/frontend/src/stores/access.control.ts
(1 hunks)packages/frontend/src/stores/field.query.ts
(1 hunks)packages/frontend/src/stores/notification.ts
(1 hunks)packages/frontend/src/stores/query.ts
(1 hunks)packages/frontend/src/styles/styles.scss
(1 hunks)packages/shared/src/lib/auth/index.ts
(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (12)
packages/backend/src/app/common/projects/project-users/project.users.service.ts (1)
packages/shared/src/lib/users/index.ts (1)
User
(3-13)
packages/frontend/src/stores/field.query.ts (4)
packages/frontend/src/stores/state.ts (1)
getCurrentList
(63-67)packages/shared/src/lib/lists/lists.ts (1)
List
(4-15)packages/shared/src/lib/card-types/index.ts (1)
CardType
(8-20)packages/frontend/src/composables/useFields.ts (1)
useFields
(5-188)
packages/backend/src/app/common/projects/project-users/project.user.entity.ts (1)
packages/shared/src/lib/projects/index.ts (1)
Project
(3-9)
packages/frontend/src/stores/notification.ts (2)
packages/frontend/src/stores/auth.ts (1)
useAuthStore
(8-174)packages/frontend/src/services/useNotificationService.ts (1)
useNotificationService
(6-56)
packages/backend/src/app/common/auth/controllers/access.control.controller.ts (2)
packages/backend/src/app/common/auth/decorators/current.user.decorator.ts (1)
CurrentUser
(4-15)packages/shared/src/lib/users/index.ts (1)
User
(3-13)
packages/frontend/src/services/useProjectUsersService.ts (1)
packages/frontend/src/composables/useHttp.ts (1)
useHttp
(70-98)
packages/shared/src/lib/auth/index.ts (5)
packages/shared/src/lib/users/index.ts (1)
User
(3-13)packages/shared/src/lib/projects/index.ts (1)
Project
(3-9)packages/shared/src/lib/workspaces/index.ts (1)
Workspace
(4-16)packages/shared/src/lib/spaces/index.ts (1)
Space
(3-10)packages/shared/src/lib/lists/lists.ts (1)
List
(4-15)
packages/frontend/src/stores/query.ts (9)
packages/frontend/src/stores/auth.ts (2)
useAuthStore
(8-174)isAuthenticated
(90-92)packages/frontend/src/stores/state.ts (2)
useStateStore
(12-126)getCurrentList
(63-67)packages/frontend/src/services/useViewsService.ts (1)
useViewsService
(11-119)packages/frontend/src/services/useProjectUsersService.ts (1)
useProjectUsersService
(6-66)packages/frontend/src/services/useWorkspacesService.ts (1)
useWorkspacesService
(6-111)packages/frontend/src/services/useSpacesService.ts (1)
useSpacesService
(11-119)packages/frontend/src/services/useListsService.ts (1)
useListsService
(6-132)packages/shared/src/lib/card-types/index.ts (1)
CardType
(8-20)packages/shared/src/lib/lists/lists.ts (2)
List
(4-15)ListStage
(17-24)
packages/frontend/src/services/useAccessControlService.ts (2)
packages/frontend/src/composables/useHttp.ts (1)
useHttp
(70-98)packages/shared/src/lib/auth/index.ts (1)
AccessControl
(34-43)
packages/frontend/src/composables/useTryCatch.ts (1)
packages/frontend/src/stores/snackbar.ts (2)
useSnackbarStore
(16-46)showSnackbar
(28-34)
packages/frontend/src/stores/access.control.ts (1)
packages/frontend/src/services/useAccessControlService.ts (1)
useAccessControlService
(7-24)
packages/backend/src/app/common/auth/services/access.control.service.ts (4)
packages/shared/src/lib/auth/index.ts (1)
AccessControlResourceType
(28-32)packages/shared/src/lib/workspaces/index.ts (1)
Workspace
(4-16)packages/shared/src/lib/spaces/index.ts (1)
Space
(3-10)packages/shared/src/lib/lists/lists.ts (1)
List
(4-15)
🔇 Additional comments (16)
packages/frontend/src/stores/notification.ts (1)
4-26
: LGTM! Consistent persistence enhancement.Adding
{ persist: true }
to the notification store aligns with the pattern established in other stores likeuseAuthStore
and provides a better user experience by persisting notification state across browser sessions.packages/frontend/src/styles/styles.scss (1)
102-110
: LGTM! Proper Vue transition implementation.The CSS transition classes are correctly structured for Vue 3's transition system, providing smooth fade-in/fade-out effects for UI components like snackbars. The 0.5s duration provides good visual feedback without being too slow.
packages/backend/src/app/common/auth/services/auth.service.ts (1)
197-197
: Verify the impact of role change across the system.Similar to the registerWithInvite method, changing the default role from "admin" to "editor" for users joining invitations improves security. Ensure consistency across the access control system.
packages/backend/src/app/common/auth/controllers/auth.controller.ts (1)
2-8
: Import path refactoring looks correct.The relative import paths are properly structured and align with the directory reorganization mentioned in the AI summary. This change improves code maintainability by using relative imports within the auth module.
packages/backend/src/app/common/projects/project-users/project.user.entity.ts (1)
16-19
:❓ Verification inconclusive
Verify performance impact of eager loading both relations.
Adding
eager: true
to the project relation creates consistency with the user relation, but now both relations are eagerly loaded. This could lead to performance issues with complex queries or large datasets.Please verify that this doesn't cause performance issues, especially in queries that fetch multiple ProjectUser entities:
🏁 Script executed:
#!/bin/bash # Description: Check for ProjectUser queries that might be affected by double eager loading # Search for ProjectUser repository usage and queries ast-grep --pattern 'find$_($$$)' | grep -A 5 -B 5 ProjectUser rg -A 10 'createQueryBuilder.*ProjectUser|find.*ProjectUser' --type tsLength of output: 5832
Verify performance impact of double eager loading
Eager-loading both the
project
anduser
relations means everyProjectUser
lookup will JOIN two tables. This may degrade performance on large result sets or complex filters.Affected code paths:
packages/backend/src/app/common/lists/list-groups/list.groups.service.ts
•await this.listGroupsRepository.manager.find(ProjectUser, { … })
packages/backend/src/app/common/projects/project-users/project.users.service.ts
•findAll
usingthis.projectUsersRepository.find()
•findOne
usingthis.projectUsersRepository.findOne()
Recommendations:
- Benchmark these queries with representative data volumes.
- Introduce pagination or select-only-needed columns.
- Consider switching to lazy loading or explicit joins for heavy queries.
packages/backend/src/app/common/auth/auth.module.ts (1)
4-4
: Controller reorganization improves module structure.The move to a
controllers
subdirectory and addition ofAccessControlController
properly organizes the authentication module for the new project member management features.Also applies to: 20-20, 47-47
packages/frontend/src/stores/field.query.ts (1)
6-32
: LGTM! Store persistence and naming improvements.The changes properly update the store to use the standard two-argument
defineStore
format with explicit naming and add persistence functionality. This follows the same pattern applied to other stores in the codebase and will improve both debugging capabilities and user experience by maintaining state across sessions.packages/backend/src/app/common/projects/project-users/project.users.service.ts (2)
102-115
: Good permission management integration.The update method properly saves the entity and maintains permission consistency through the AccessControlService. This ensures that role changes are reflected in the access control system.
128-135
:✅ Verification successful
Verify user update operation consistency.
The direct user update via repository manager could potentially conflict with other user operations or leave inconsistent state if the user is concurrently being modified elsewhere.
🏁 Script executed:
#!/bin/bash # Description: Check if there are other places where user.project is being modified # to ensure consistency across the codebase # Search for other locations where user.project field is being updated rg -A 5 -B 5 "project.*=.*null|project.*null" --type ts rg -A 5 -B 5 "\.project\s*=" --type tsLength of output: 4752
No conflicting backend updates to User.project detected
- Backend:
packages/backend/src/app/common/projects/project-users/project.users.service.ts
(lines 128–135)Our search found no other server‐side writes to
User.project
—only this update and client‐side state changes—so the direct repository update here won’t conflict with concurrent user operations.packages/frontend/src/stores/query.ts (1)
17-19
: LGTM! Persistence enhancement follows established patterns.The update to enable store persistence is correctly implemented using Pinia's two-argument form with the
{ persist: true }
option. This aligns with similar persistence enhancements made to other stores in the frontend and ensures a consistent user experience by retaining query state across sessions.Also applies to: 88-89
packages/backend/src/app/common/auth/controllers/access.control.controller.ts (1)
1-20
: LGTM! Well-structured controller with proper authentication.The controller implementation follows NestJS best practices:
- Security: JWT authentication guard ensures only authenticated users can access permissions
- Structure: Proper dependency injection and controller decoration
- Endpoint design: RESTful
/me
endpoint for user-specific data- Type safety: Correct use of User entity type with CurrentUser decorator
The authentication flow is secure as the user ID is extracted from the JWT token rather than being passed as a parameter, preventing unauthorized access to other users' permissions.
packages/frontend/src/services/useProjectUsersService.ts (1)
50-59
: LGTM! Proper mutation implementation with cache invalidation.The mutation hook is well-implemented:
- Cache consistency: Correctly invalidates the project users query on successful deletion
- Error handling: Relies on Vue Query's built-in error handling, which is appropriate
- Type safety: Proper TypeScript parameter typing
The query invalidation ensures the UI stays synchronized with the backend state after user removal.
packages/frontend/src/components/common/base/BaseSnackbarWrapper.vue (1)
31-58
: LGTM! The animation implementation is well-structured.The implementation correctly uses
<Teleport>
to render snackbars in the overlay container and<Transition>
for smooth animations. The two-phase close handling (marking as closing, then removing after animation) ensures proper cleanup.packages/frontend/src/components/common/settings/ProjectMembersSettings.vue (3)
109-124
: LGTM! Access control integration is well-implemented.The conditional rendering of the context menu based on owner permissions is correctly implemented, ensuring that only project owners can see the remove member option.
52-60
: Consider checking permissions before showing error for owner removal.Since only owners can see the remove option, this check might be redundant. However, it's good defensive programming to keep it.
62-76
: Good implementation of confirmation dialog and error handling.The deletion flow properly uses a confirmation dialog and the
tryCatch
composable for error handling, providing a good user experience.
Closes #167
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Style
Chores