General.utils.ts109 lines · main
1import type {
2 OrganizationRole,
3 OrganizationRolesResponse,
4} from '@/data/organization-members/organization-roles-query'
5import type { OrganizationMember } from '@/data/organizations/organization-members-query'
6
7export interface ProjectAccessMember {
8 id: string
9 displayName?: string
10 email: string
11 role?: string
12}
13
14interface SummarizeProjectAccessParams {
15 organizationMembers: OrganizationMember[]
16 roles: OrganizationRolesResponse | undefined
17 projectRef?: string
18 hasLimitedVisibility: boolean
19 currentUserId?: string
20 maxVisibleMembers?: number
21}
22
23export interface ProjectAccessSummary {
24 projectMembers: ProjectAccessMember[]
25 visibleMembers: ProjectAccessMember[]
26 hiddenMembersCount: number
27 projectMemberCount: number
28 organizationMemberCount: number
29 shouldShowOrgComparison: boolean
30 hasOrganizationWideAccess: boolean
31}
32
33const getRoleDisplayName = (roleName: string | undefined) => {
34 if (!roleName) return undefined
35 return roleName.split('_')[0]
36}
37
38const roleAppliesToProject = (role: OrganizationRole | undefined, projectRef?: string) => {
39 if (!role) return false
40 if (role.projects.length === 0) return true
41 if (!projectRef) return false
42 return role.projects.some((project) => project.ref === projectRef)
43}
44
45export const summarizeProjectAccess = ({
46 organizationMembers,
47 roles,
48 projectRef,
49 hasLimitedVisibility,
50 currentUserId,
51 maxVisibleMembers = 12,
52}: SummarizeProjectAccessParams): ProjectAccessSummary => {
53 const allRoles = [...(roles?.org_scoped_roles ?? []), ...(roles?.project_scoped_roles ?? [])]
54 const rolesById = new Map(allRoles.map((role) => [role.id, role]))
55
56 const normalizedMembers = organizationMembers.filter(
57 (member) => !member.invited_id && typeof member.gotrue_id === 'string' && !!member.primary_email
58 )
59
60 const membersWithProjectAccess = normalizedMembers
61 .filter((member) =>
62 member.role_ids.some((roleId) => roleAppliesToProject(rolesById.get(roleId), projectRef))
63 )
64 .sort((a, b) => {
65 const isCurrentUserA = !!currentUserId && a.gotrue_id === currentUserId
66 const isCurrentUserB = !!currentUserId && b.gotrue_id === currentUserId
67
68 if (isCurrentUserA && !isCurrentUserB) return -1
69 if (!isCurrentUserA && isCurrentUserB) return 1
70 return (a.primary_email ?? '').localeCompare(b.primary_email ?? '')
71 })
72
73 const projectMembers = membersWithProjectAccess.map((member) => {
74 const matchingRoleNames = member.role_ids
75 .map((roleId) => rolesById.get(roleId))
76 .filter((role) => roleAppliesToProject(role, projectRef))
77 .map((role) => getRoleDisplayName(role?.name))
78 .filter((name): name is string => typeof name === 'string' && name.length > 0)
79
80 const uniqueRoleNames = [...new Set(matchingRoleNames)]
81 const hasDisplayName =
82 typeof member.username === 'string' &&
83 typeof member.primary_email === 'string' &&
84 member.username !== member.primary_email
85
86 return {
87 id: member.gotrue_id as string,
88 displayName: hasDisplayName ? member.username : undefined,
89 email: member.primary_email as string,
90 role: uniqueRoleNames.length > 0 ? uniqueRoleNames.join(', ') : undefined,
91 }
92 })
93
94 const projectMemberCount = projectMembers.length
95 const organizationMemberCount = normalizedMembers.length
96 const shouldShowOrgComparison = !hasLimitedVisibility && organizationMemberCount > 0
97 const hasOrganizationWideAccess =
98 shouldShowOrgComparison && projectMemberCount === organizationMemberCount
99
100 return {
101 projectMembers,
102 visibleMembers: projectMembers.slice(0, maxVisibleMembers),
103 hiddenMembersCount: Math.max(projectMemberCount - maxVisibleMembers, 0),
104 projectMemberCount,
105 organizationMemberCount,
106 shouldShowOrgComparison,
107 hasOrganizationWideAccess,
108 }
109}