ListMembers lists all members of a project (both users and groups)
(ctx context.Context, orgID uuid.UUID, projectID uuid.UUID, paginationOpts *pagination.OffsetPaginationOpts)
| 125 | |
| 126 | // ListMembers lists all members of a project (both users and groups) |
| 127 | func (r *ProjectRepo) ListMembers(ctx context.Context, orgID uuid.UUID, projectID uuid.UUID, paginationOpts *pagination.OffsetPaginationOpts) ([]*biz.ProjectMembership, int, error) { |
| 128 | ctx, span := otelx.Start(ctx, projectRepoTracer, "ProjectRepo.ListMembers") |
| 129 | defer span.End() |
| 130 | |
| 131 | // Check if the project exists and belongs to the organization |
| 132 | existingProject, err := r.FindProjectByOrgIDAndID(ctx, orgID, projectID) |
| 133 | if err != nil { |
| 134 | return nil, 0, fmt.Errorf("failed to find project: %w", err) |
| 135 | } |
| 136 | if existingProject == nil { |
| 137 | return nil, 0, biz.NewErrNotFound("project") |
| 138 | } |
| 139 | |
| 140 | // Build the query with base conditions for all membership types |
| 141 | query := r.data.DB.Membership.Query(). |
| 142 | Where( |
| 143 | membership.ResourceTypeEQ(authz.ResourceTypeProject), |
| 144 | membership.ResourceID(projectID), |
| 145 | ).WithParent() |
| 146 | |
| 147 | // Get total count before applying pagination |
| 148 | totalCount, err := query.Count(ctx) |
| 149 | if err != nil { |
| 150 | return nil, 0, fmt.Errorf("failed to count project members: %w", err) |
| 151 | } |
| 152 | |
| 153 | // Apply pagination |
| 154 | if paginationOpts != nil { |
| 155 | query = query. |
| 156 | Order(ent.Desc(membership.FieldCreatedAt)). |
| 157 | Limit(paginationOpts.Limit()). |
| 158 | Offset(paginationOpts.Offset()) |
| 159 | } |
| 160 | |
| 161 | // Execute the query |
| 162 | memberships, err := query.All(ctx) |
| 163 | if err != nil { |
| 164 | return nil, 0, fmt.Errorf("failed to list project members: %w", err) |
| 165 | } |
| 166 | |
| 167 | // Convert to biz.ProjectMembership objects |
| 168 | result := make([]*biz.ProjectMembership, 0, len(memberships)) |
| 169 | for _, m := range memberships { |
| 170 | var mems *biz.ProjectMembership |
| 171 | |
| 172 | switch m.MembershipType { |
| 173 | case authz.MembershipTypeUser: |
| 174 | u, uErr := r.data.DB.User.Get(ctx, m.MemberID) |
| 175 | if uErr != nil { |
| 176 | // Skip orphaned rows whose member_id no longer points at a real user. |
| 177 | // memberships.member_id is polymorphic with no FK, so deletes that bypass |
| 178 | // the app-level cascade can leave dangling rows here. |
| 179 | if ent.IsNotFound(uErr) { |
| 180 | r.log.Warnf("orphaned project membership %s references missing user %s, skipping", m.ID, m.MemberID) |
| 181 | totalCount-- |
| 182 | continue |
| 183 | } |
| 184 | return nil, 0, fmt.Errorf("failed to find user: %w", uErr) |
nothing calls this directly
no test coverage detected