• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vocdoni / saas-backend / 17437769030

03 Sep 2025 03:07PM UTC coverage: 59.109% (+0.2%) from 58.882%
17437769030

push

github

altergui
api: add bulk create group with all members functionality

- Enhanced CreateOrganizationMemberGroupRequest to support 'includeAllMembers' flag
- Added GetAllOrgMemberIDs method in database layer for efficient member ID retrieval
- Updated createOrganizationMemberGroupHandler to handle both specific member groups and include-all-members groups
- Maintains backward compatibility with existing specific member group creation
- Added comprehensive tests covering all scenarios including authorization checks and edge cases
- Improves scalability by avoiding the need to fetch and transmit large member ID lists from frontend

This addresses the scalability issue where creating groups with all members required fetching all member IDs first, which becomes inefficient with large memberbases (100K+ members).

46 of 59 new or added lines in 2 files covered. (77.97%)

27 existing lines in 3 files now uncovered.

5733 of 9699 relevant lines covered (59.11%)

31.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

70.95
/api/organization_groups.go
1
package api
2

3
import (
4
        "encoding/json"
5
        "net/http"
6
        "strconv"
7

8
        "github.com/go-chi/chi/v5"
9
        "github.com/vocdoni/saas-backend/api/apicommon"
10
        "github.com/vocdoni/saas-backend/db"
11
        "github.com/vocdoni/saas-backend/errors"
12
        "go.vocdoni.io/dvote/log"
13
)
14

15
// organizationMemberGroupsHandler godoc
16
//
17
//        @Summary                Get organization member groups
18
//        @Description        Get the list of groups and their info of the organization
19
//        @Description        Does not return the members of the groups, only the groups themselves.
20
//        @Description        Needs admin or manager role
21
//        @Tags                        organizations
22
//        @Accept                        json
23
//        @Produce                json
24
//        @Security                BearerAuth
25
//        @Param                        address                path                string        true        "Organization address"
26
//        @Param                        page                query                integer        false        "Page number (default: 1)"
27
//        @Param                        pageSize        query                integer        false        "Number of items per page (default: 10)"
28
//        @Success                200                        {object}        apicommon.OrganizationMemberGroupsResponse
29
//        @Failure                400                        {object}        errors.Error        "Invalid input data"
30
//        @Failure                401                        {object}        errors.Error        "Unauthorized"
31
//        @Failure                404                        {object}        errors.Error        "Organization not found"
32
//        @Failure                500                        {object}        errors.Error        "Internal server error"
33
//        @Router                        /organizations/{address}/groups [get]
34
func (a *API) organizationMemberGroupsHandler(w http.ResponseWriter, r *http.Request) {
5✔
35
        // get the user from the request context
5✔
36
        user, ok := apicommon.UserFromContext(r.Context())
5✔
37
        if !ok {
5✔
38
                errors.ErrUnauthorized.Write(w)
×
39
                return
×
40
        }
×
41
        // get the organization info from the request context
42
        org, _, ok := a.organizationFromRequest(r)
5✔
43
        if !ok {
6✔
44
                errors.ErrNoOrganizationProvided.Write(w)
1✔
45
                return
1✔
46
        }
1✔
47
        if !user.HasRoleFor(org.Address, db.AdminRole) && !user.HasRoleFor(org.Address, db.ManagerRole) {
5✔
48
                // if the user is not admin or manager of the organization, return an error
1✔
49
                errors.ErrUnauthorized.Withf("user is not admin of organization").Write(w)
1✔
50
                return
1✔
51
        }
1✔
52

53
        // Parse pagination parameters from query string
54
        page := 1      // Default page number
3✔
55
        pageSize := 10 // Default page size
3✔
56

3✔
57
        if pageStr := r.URL.Query().Get("page"); pageStr != "" {
3✔
58
                if pageVal, err := strconv.Atoi(pageStr); err == nil && pageVal > 0 {
×
59
                        page = pageVal
×
60
                }
×
61
        }
62

63
        if pageSizeStr := r.URL.Query().Get("pageSize"); pageSizeStr != "" {
3✔
64
                if pageSizeVal, err := strconv.Atoi(pageSizeStr); err == nil && pageSizeVal > 0 {
×
65
                        pageSize = pageSizeVal
×
66
                }
×
67
        }
68

69
        // send the organization back to the user
70
        pages, groups, err := a.db.OrganizationMemberGroups(org.Address, page, pageSize)
3✔
71
        if err != nil {
3✔
72
                errors.ErrGenericInternalServerError.Withf("could not get organization members: %v", err).Write(w)
×
73
                return
×
74
        }
×
75
        memberGroups := apicommon.OrganizationMemberGroupsResponse{
3✔
76
                TotalPages:  pages,
3✔
77
                CurrentPage: page,
3✔
78
                Groups:      make([]*apicommon.OrganizationMemberGroupInfo, 0, len(groups)),
3✔
79
        }
3✔
80
        for _, group := range groups {
9✔
81
                memberGroups.Groups = append(memberGroups.Groups, &apicommon.OrganizationMemberGroupInfo{
6✔
82
                        ID:           group.ID.Hex(),
6✔
83
                        Title:        group.Title,
6✔
84
                        Description:  group.Description,
6✔
85
                        CreatedAt:    group.CreatedAt,
6✔
86
                        UpdatedAt:    group.UpdatedAt,
6✔
87
                        CensusIDs:    group.CensusIDs,
6✔
88
                        MembersCount: len(group.MemberIDs),
6✔
89
                })
6✔
90
        }
6✔
91
        apicommon.HTTPWriteJSON(w, memberGroups)
3✔
92
}
93

94
// organizationMemberGroupHandler godoc
95
//
96
//        @Summary                Get the information of an organization member group
97
//        @Description        Get the information of an organization member group by its ID
98
//        @Description        Needs admin or manager role
99
//        @Tags                        organizations
100
//        @Accept                        json
101
//        @Produce                json
102
//        @Security                BearerAuth
103
//        @Param                        address        path                string        true        "Organization address"
104
//        @Param                        groupID        path                string        true        "Group ID"
105
//        @Success                200                {object}        apicommon.OrganizationMemberGroupInfo
106
//        @Failure                400                {object}        errors.Error        "Invalid input data"
107
//        @Failure                401                {object}        errors.Error        "Unauthorized"
108
//        @Failure                404                {object}        errors.Error        "Organization or group not found"
109
//        @Failure                500                {object}        errors.Error        "Internal server error"
110
//        @Router                        /organizations/{address}/groups/{groupID} [get]
111
func (a *API) organizationMemberGroupHandler(w http.ResponseWriter, r *http.Request) {
9✔
112
        // get the group ID from the request path
9✔
113
        groupID := chi.URLParam(r, "groupID")
9✔
114
        if groupID == "" {
9✔
115
                errors.ErrInvalidData.Withf("group ID is required").Write(w)
×
116
                return
×
117
        }
×
118
        // get the user from the request context
119
        user, ok := apicommon.UserFromContext(r.Context())
9✔
120
        if !ok {
9✔
121
                errors.ErrUnauthorized.Write(w)
×
122
                return
×
123
        }
×
124
        // get the organization info from the request context
125
        org, _, ok := a.organizationFromRequest(r)
9✔
126
        if !ok {
9✔
127
                errors.ErrNoOrganizationProvided.Write(w)
×
128
                return
×
129
        }
×
130
        if !user.HasRoleFor(org.Address, db.AdminRole) && !user.HasRoleFor(org.Address, db.ManagerRole) {
9✔
131
                // if the user is not admin or manager of the organization, return an error
×
132
                errors.ErrUnauthorized.Withf("user is not admin of organization").Write(w)
×
133
                return
×
134
        }
×
135

136
        group, err := a.db.OrganizationMemberGroup(groupID, org.Address)
9✔
137
        if err != nil {
11✔
138
                if err == db.ErrNotFound {
3✔
139
                        errors.ErrInvalidData.Withf("group not found").Write(w)
1✔
140
                        return
1✔
141
                }
1✔
142
                errors.ErrGenericInternalServerError.Withf("could not get organization member group: %v", err).Write(w)
1✔
143
                return
1✔
144
        }
145
        apicommon.HTTPWriteJSON(w, &apicommon.OrganizationMemberGroupInfo{
7✔
146
                ID:          group.ID.Hex(),
7✔
147
                Title:       group.Title,
7✔
148
                Description: group.Description,
7✔
149
                MemberIDs:   group.MemberIDs,
7✔
150
                CensusIDs:   group.CensusIDs,
7✔
151
                CreatedAt:   group.CreatedAt,
7✔
152
                UpdatedAt:   group.UpdatedAt,
7✔
153
        })
7✔
154
}
155

156
// createOrganizationMemberGroupHandler godoc
157
//
158
//        @Summary                Create an organization member group
159
//        @Description        Create an organization member group with the given members or all members
160
//        @Description        Needs admin or manager role
161
//        @Tags                        organizations
162
//        @Accept                        json
163
//        @Produce                json
164
//        @Security                BearerAuth
165
//        @Param                        address        path                string                                                                                        true        "Organization address"
166
//        @Param                        group        body                apicommon.CreateOrganizationMemberGroupRequest        true        "Group info to create"
167
//        @Success                200                {object}        apicommon.OrganizationMemberGroupInfo
168
//        @Failure                400                {object}        errors.Error        "Invalid input data"
169
//        @Failure                401                {object}        errors.Error        "Unauthorized"
170
//        @Failure                404                {object}        errors.Error        "Organization not found"
171
//        @Failure                500                {object}        errors.Error        "Internal server error"
172
//        @Router                        /organizations/{address}/groups [post]
173
func (a *API) createOrganizationMemberGroupHandler(w http.ResponseWriter, r *http.Request) {
16✔
174
        // get the user from the request context
16✔
175
        user, ok := apicommon.UserFromContext(r.Context())
16✔
176
        if !ok {
16✔
177
                errors.ErrUnauthorized.Write(w)
×
178
                return
×
179
        }
×
180
        // get the organization info from the request context
181
        org, _, ok := a.organizationFromRequest(r)
16✔
182
        if !ok {
17✔
183
                errors.ErrNoOrganizationProvided.Write(w)
1✔
184
                return
1✔
185
        }
1✔
186
        if !user.HasRoleFor(org.Address, db.AdminRole) && !user.HasRoleFor(org.Address, db.ManagerRole) {
17✔
187
                // if the user is not admin or manager of the organization, return an error
2✔
188
                errors.ErrUnauthorized.Withf("user is not admin of organization").Write(w)
2✔
189
                return
2✔
190
        }
2✔
191

192
        var toCreate apicommon.CreateOrganizationMemberGroupRequest
13✔
193
        if err := json.NewDecoder(r.Body).Decode(&toCreate); err != nil {
13✔
194
                errors.ErrMalformedBody.Write(w)
×
195
                return
×
196
        }
×
197

198
        var memberIDs []string
13✔
199
        var err error
13✔
200

13✔
201
        // Check if we should include all members
13✔
202
        if toCreate.IncludeAllMembers {
16✔
203
                // Get all member IDs from the database
3✔
204
                memberIDs, err = a.db.GetAllOrgMemberIDs(org.Address)
3✔
205
                if err != nil {
3✔
NEW
206
                        errors.ErrGenericInternalServerError.Withf("could not get all org member IDs: %v", err).Write(w)
×
NEW
207
                        return
×
NEW
208
                }
×
209
                log.Infow("creating group with all organization members",
3✔
210
                        "org", org.Address.Hex(),
3✔
211
                        "count", len(memberIDs),
3✔
212
                        "user", user.Email,
3✔
213
                        "title", toCreate.Title)
3✔
214
        } else {
10✔
215
                // Use the provided member IDs
10✔
216
                memberIDs = toCreate.MemberIDs
10✔
217
        }
10✔
218

219
        newMemberGroup := &db.OrganizationMemberGroup{
13✔
220
                Title:       toCreate.Title,
13✔
221
                Description: toCreate.Description,
13✔
222
                MemberIDs:   memberIDs,
13✔
223
                OrgAddress:  org.Address,
13✔
224
        }
13✔
225

13✔
226
        groupID, err := a.db.CreateOrganizationMemberGroup(newMemberGroup)
13✔
227
        if err != nil {
15✔
228
                if err == db.ErrNotFound {
2✔
229
                        errors.ErrInvalidData.Withf("organization not found").Write(w)
×
230
                        return
×
231
                }
×
232
                errors.ErrGenericInternalServerError.Withf("could not create organization member group: %v", err).Write(w)
2✔
233
                return
2✔
234
        }
235
        apicommon.HTTPWriteJSON(w, &apicommon.OrganizationMemberGroupInfo{
11✔
236
                ID: groupID,
11✔
237
        })
11✔
238
}
239

240
// updateOrganizationMemberGroupHandler godoc
241
//
242
//        @Summary                Update an organization member group
243
//        @Description        Update an organization member group changing the info, and adding or removing members
244
//        @Description        Needs admin or manager role
245
//        @Tags                        organizations
246
//        @Accept                        json
247
//        @Produce                json
248
//        @Security                BearerAuth
249
//        @Param                        address        path                string                                                                                        true        "Organization address"
250
//        @Param                        groupID        path                string                                                                                        true        "Group ID"
251
//        @Param                        group        body                apicommon.UpdateOrganizationMemberGroupsRequest        true        "Group info to update"
252
//        @Success                200                {string}        string                                                                                        "OK"
253
//        @Failure                400                {object}        errors.Error                                                                        "Invalid input data"
254
//        @Failure                401                {object}        errors.Error                                                                        "Unauthorized"
255
//        @Failure                404                {object}        errors.Error                                                                        "Organization or group not found"
256
//        @Failure                500                {object}        errors.Error                                                                        "Internal server error"
257
//        @Router                        /organizations/{address}/groups/{groupID} [put]
258
func (a *API) updateOrganizationMemberGroupHandler(w http.ResponseWriter, r *http.Request) {
5✔
259
        // get the group ID from the request path
5✔
260
        groupID := chi.URLParam(r, "groupID")
5✔
261
        if groupID == "" {
5✔
262
                errors.ErrInvalidData.Withf("group ID is required").Write(w)
×
263
                return
×
264
        }
×
265
        // get the user from the request context
266
        user, ok := apicommon.UserFromContext(r.Context())
5✔
267
        if !ok {
5✔
268
                errors.ErrUnauthorized.Write(w)
×
269
                return
×
270
        }
×
271
        // get the organization info from the request context
272
        org, _, ok := a.organizationFromRequest(r)
5✔
273
        if !ok {
5✔
274
                errors.ErrNoOrganizationProvided.Write(w)
×
275
                return
×
276
        }
×
277
        if !user.HasRoleFor(org.Address, db.AdminRole) && !user.HasRoleFor(org.Address, db.ManagerRole) {
6✔
278
                // if the user is not admin or manager of the organization, return an error
1✔
279
                errors.ErrUnauthorized.Withf("user is not admin of organization").Write(w)
1✔
280
                return
1✔
281
        }
1✔
282

283
        var toUpdate apicommon.UpdateOrganizationMemberGroupsRequest
4✔
284
        if err := json.NewDecoder(r.Body).Decode(&toUpdate); err != nil {
4✔
285
                errors.ErrMalformedBody.Write(w)
×
286
                return
×
287
        }
×
288

289
        err := a.db.UpdateOrganizationMemberGroup(
4✔
290
                groupID,
4✔
291
                org.Address,
4✔
292
                toUpdate.Title,
4✔
293
                toUpdate.Description,
4✔
294
                toUpdate.AddMembers,
4✔
295
                toUpdate.RemoveMembers,
4✔
296
        )
4✔
297
        if err != nil {
5✔
298
                if err == db.ErrNotFound {
1✔
299
                        errors.ErrInvalidData.Withf("group not found").Write(w)
×
300
                        return
×
301
                }
×
302
                errors.ErrGenericInternalServerError.Withf("could not update organization member group: %v", err).Write(w)
1✔
303
                return
1✔
304
        }
305
        apicommon.HTTPWriteOK(w)
3✔
306
}
307

308
// deleteOrganizationMemberGroupHandler godoc
309
//
310
//        @Summary                Delete an organization member group
311
//        @Description        Delete an organization member group by its ID
312
//        @Tags                        organizations
313
//        @Accept                        json
314
//        @Produce                json
315
//        @Security                BearerAuth
316
//        @Param                        address        path                string                        true        "Organization address"
317
//        @Param                        groupID        path                string                        true        "Group ID"
318
//        @Success                200                {string}        string                        "OK"
319
//        @Failure                400                {object}        errors.Error        "Invalid input data"
320
//        @Failure                401                {object}        errors.Error        "Unauthorized"
321
//        @Failure                404                {object}        errors.Error        "Organization or group not found"
322
//        @Failure                500                {object}        errors.Error        "Internal server error"
323
//        @Router                        /organizations/{address}/groups/{groupID} [delete]
324
func (a *API) deleteOrganizationMemberGroupHandler(w http.ResponseWriter, r *http.Request) {
5✔
325
        // get the member ID from the request path
5✔
326
        groupID := chi.URLParam(r, "groupID")
5✔
327
        if groupID == "" {
5✔
328
                errors.ErrInvalidData.Withf("group ID is required").Write(w)
×
329
                return
×
330
        }
×
331
        // get the user from the request context
332
        user, ok := apicommon.UserFromContext(r.Context())
5✔
333
        if !ok {
5✔
334
                errors.ErrUnauthorized.Write(w)
×
335
                return
×
336
        }
×
337
        // get the organization info from the request context
338
        org, _, ok := a.organizationFromRequest(r)
5✔
339
        if !ok {
5✔
340
                errors.ErrNoOrganizationProvided.Write(w)
×
341
                return
×
342
        }
×
343
        if !user.HasRoleFor(org.Address, db.AdminRole) && !user.HasRoleFor(org.Address, db.ManagerRole) {
6✔
344
                // if the user is not admin or manager of the organization, return an error
1✔
345
                errors.ErrUnauthorized.Withf("user is not admin of organization").Write(w)
1✔
346
                return
1✔
347
        }
1✔
348
        if err := a.db.DeleteOrganizationMemberGroup(groupID, org.Address); err != nil {
5✔
349
                if err == db.ErrNotFound {
1✔
350
                        errors.ErrInvalidData.Withf("group not found").Write(w)
×
351
                        return
×
352
                }
×
353
                errors.ErrGenericInternalServerError.Withf("could not delete organization member group: %v", err).Write(w)
1✔
354
                return
1✔
355
        }
356
        apicommon.HTTPWriteOK(w)
3✔
357
}
358

359
// listOrganizationMemberGroupsHandler godoc
360
//
361
//        @Summary                Get the list of members with details of an organization member group
362
//        @Description        Get the list of members with details of an organization member group
363
//        @Description        Needs admin or manager role
364
//        @Tags                        organizations
365
//        @Accept                        json
366
//        @Produce                json
367
//        @Security                BearerAuth
368
//        @Param                        address                path                string        true        "Organization address"
369
//        @Param                        groupID                path                string        true        "Group ID"
370
//        @Param                        page                query                int                false        "Page number for pagination"
371
//        @Param                        pageSize        query                int                false        "Number of items per page"
372
//        @Success                200                        {object}        apicommon.ListOrganizationMemberGroupResponse
373
//        @Failure                400                        {object}        errors.Error        "Invalid input data"
374
//        @Failure                401                        {object}        errors.Error        "Unauthorized"
375
//        @Failure                404                        {object}        errors.Error        "Organization or group not found"
376
//        @Failure                500                        {object}        errors.Error        "Internal server error"
377
//        @Router                        /organizations/{address}/groups/{groupID}/members [get]
378
func (a *API) listOrganizationMemberGroupsHandler(w http.ResponseWriter, r *http.Request) {
4✔
379
        // get the group ID from the request path
4✔
380
        groupID := chi.URLParam(r, "groupID")
4✔
381
        if groupID == "" {
4✔
382
                errors.ErrInvalidData.Withf("group ID is required").Write(w)
×
383
                return
×
384
        }
×
385
        // get the user from the request context
386
        user, ok := apicommon.UserFromContext(r.Context())
4✔
387
        if !ok {
4✔
388
                errors.ErrUnauthorized.Write(w)
×
389
                return
×
390
        }
×
391
        // get the organization info from the request context
392
        org, _, ok := a.organizationFromRequest(r)
4✔
393
        if !ok {
4✔
394
                errors.ErrNoOrganizationProvided.Write(w)
×
395
                return
×
396
        }
×
397
        if !user.HasRoleFor(org.Address, db.AdminRole) && !user.HasRoleFor(org.Address, db.ManagerRole) {
5✔
398
                // if the user is not admin or manager of the organization, return an error
1✔
399
                errors.ErrUnauthorized.Withf("user is not admin of organization").Write(w)
1✔
400
                return
1✔
401
        }
1✔
402

403
        // Parse pagination parameters from query string
404
        page := 1      // Default page number
3✔
405
        pageSize := 10 // Default page size
3✔
406

3✔
407
        if pageStr := r.URL.Query().Get("page"); pageStr != "" {
4✔
408
                if pageVal, err := strconv.Atoi(pageStr); err == nil && pageVal > 0 {
2✔
409
                        page = pageVal
1✔
410
                }
1✔
411
        }
412

413
        if pageSizeStr := r.URL.Query().Get("pageSize"); pageSizeStr != "" {
4✔
414
                if pageSizeVal, err := strconv.Atoi(pageSizeStr); err == nil && pageSizeVal > 0 {
2✔
415
                        pageSize = pageSizeVal
1✔
416
                }
1✔
417
        }
418

419
        totalPages, members, err := a.db.ListOrganizationMemberGroup(groupID, org.Address, int64(page), int64(pageSize))
3✔
420
        if err != nil {
4✔
421
                if err == db.ErrNotFound {
1✔
422
                        errors.ErrInvalidData.Withf("group not found").Write(w)
×
423
                        return
×
424
                }
×
425
                errors.ErrGenericInternalServerError.Withf("could not get organization member group members: %v", err).Write(w)
1✔
426
                return
1✔
427
        }
428
        if totalPages == 0 {
2✔
429
                // If no members are found, return an empty response
×
430
                apicommon.HTTPWriteJSON(w, &apicommon.ListOrganizationMemberGroupResponse{
×
431
                        TotalPages:  totalPages,
×
432
                        CurrentPage: 0,
×
433
                        Members:     []apicommon.OrgMember{},
×
434
                })
×
435
        }
×
436
        // convert the members to the response format
437
        membersResponse := make([]apicommon.OrgMember, 0, len(members))
2✔
438
        for _, m := range members {
6✔
439
                membersResponse = append(membersResponse, apicommon.OrgMemberFromDb(*m))
4✔
440
        }
4✔
441

442
        apicommon.HTTPWriteJSON(w, &apicommon.ListOrganizationMemberGroupResponse{
2✔
443
                TotalPages:  totalPages,
2✔
444
                CurrentPage: page,
2✔
445
                Members:     membersResponse,
2✔
446
        })
2✔
447
}
448

449
// organizationMemberGroupValidateHandler godoc
450
//
451
//        @Summary                Validate organization group members data
452
//        @Description        Checks the AuthFields for duplicates or empty fields and the TwoFaFields for empty ones.
453
//        @Tags                        organizations
454
//        @Accept                        json
455
//        @Produce                json
456
//        @Security                BearerAuth
457
//        @Param                        address        path                string                                                                        true        "Organization address"
458
//        @Param                        groupID        path                string                                                                        true        "Group ID"
459
//        @Param                        members        body                apicommon.ValidateMemberGroupRequest        true        "Members validation request"
460
//        @Success                200                {string}        string                                                                        "OK"
461
//        @Failure                400                {object}        errors.Error                                                        "Invalid input data"
462
//        @Failure                401                {object}        errors.Error                                                        "Unauthorized"
463
//        @Failure                404                {object}        errors.Error                                                        "Organization or group not found"
464
//        @Failure                500                {object}        errors.Error                                                        "Internal server error"
465
//
466
//        @Router                        /organizations/{address}/groups/{groupID}/validate [post]
467
func (a *API) organizationMemberGroupValidateHandler(w http.ResponseWriter, r *http.Request) {
9✔
468
        // get the group ID from the request path
9✔
469
        groupID := chi.URLParam(r, "groupID")
9✔
470
        if groupID == "" {
9✔
471
                errors.ErrInvalidData.Withf("group ID is required").Write(w)
×
472
                return
×
473
        }
×
474
        // get the user from the request context
475
        user, ok := apicommon.UserFromContext(r.Context())
9✔
476
        if !ok {
9✔
477
                errors.ErrUnauthorized.Write(w)
×
478
                return
×
479
        }
×
480
        // get the organization info from the request context
481
        org, _, ok := a.organizationFromRequest(r)
9✔
482
        if !ok {
9✔
483
                errors.ErrNoOrganizationProvided.Write(w)
×
484
                return
×
485
        }
×
486
        if !user.HasRoleFor(org.Address, db.AdminRole) && !user.HasRoleFor(org.Address, db.ManagerRole) {
10✔
487
                // if the user is not admin or manager of the organization, return an error
1✔
488
                errors.ErrUnauthorized.Withf("user is not admin of organization").Write(w)
1✔
489
                return
1✔
490
        }
1✔
491

492
        var membersRequest apicommon.ValidateMemberGroupRequest
8✔
493
        if err := json.NewDecoder(r.Body).Decode(&membersRequest); err != nil {
8✔
494
                errors.ErrMalformedBody.Write(w)
×
495
                return
×
496
        }
×
497

498
        if len(membersRequest.AuthFields) == 0 && len(membersRequest.TwoFaFields) == 0 {
9✔
499
                errors.ErrInvalidData.Withf("missing both AuthFields and TwoFaFields").Write(w)
1✔
500
                return
1✔
501
        }
1✔
502

503
        // check the org members to veriy tha the OrgMemberAuthFields can be used for authentication
504
        aggregationResults, err := a.db.CheckGroupMembersFields(
7✔
505
                org.Address,
7✔
506
                groupID,
7✔
507
                membersRequest.AuthFields,
7✔
508
                membersRequest.TwoFaFields,
7✔
509
        )
7✔
510
        if err != nil {
8✔
511
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
1✔
512
                return
1✔
513
        }
1✔
514
        if len(aggregationResults.Duplicates) > 0 || len(aggregationResults.MissingData) > 0 {
9✔
515
                // if there are incorrect members, return an error with the IDs of the incorrect members
3✔
516
                errors.ErrInvalidData.WithData(aggregationResults).Write(w)
3✔
517
                return
3✔
518
        }
3✔
519

520
        apicommon.HTTPWriteOK(w)
3✔
521
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc