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

vocdoni / saas-backend / 28376740396

29 Jun 2026 01:46PM UTC coverage: 62.732% (+0.3%) from 62.475%
28376740396

Pull #563

github

altergui
fix(integrator): keep per-process census bound for managed orgs; surface plan errors

Addresses review on the census-counter removal:

- HasTxPermission: the per-process "MaxCensusSize <= plan.MaxCensus" check was
  inside "if !managed(org)", so dropping the aggregate ManagedCensusSize quota
  let a managed org publish a process with an unbounded declared census size.
  Move the per-process bound out so it applies to every org (governed by the
  integrator's plan for managed orgs); only the process-*count* check stays
  managed-exempt, since ReserveManagedPublish covers that. Adds a regression
  test: a managed publish over the integrator plan's MaxCensus is rejected.

- integratorInfoHandler: a real plan-lookup error was swallowed and reported as
  0 (unlimited/unknown) caps. Tolerate only the no-plan case (PlanID == "") and
  return 500 on an actual lookup failure.
Pull Request #563: feat(integrator): expose pooled votes/SMS/email usage and caps on /integrator

62 of 77 new or added lines in 4 files covered. (80.52%)

342 existing lines in 11 files now uncovered.

10251 of 16341 relevant lines covered (62.73%)

44.9 hits per line

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

71.56
/api/organization_groups.go
1
package api
2

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

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

14
// organizationMemberGroupsHandler godoc
15
//
16
//        @Summary                Get organization member groups
17
//        @Description        Get the list of groups and their info of the organization
18
//        @Description        Does not return the members of the groups, only the groups themselves.
19
//        @Description        Needs admin or manager role
20
//        @Tags                        organizations
21
//        @Accept                        json
22
//        @Produce                json
23
//        @Security                BearerAuth
24
//        @Param                        address        path                string        true        "Organization address"
25
//        @Param                        page        query                integer        false        "Page number (default: 1)"
26
//        @Param                        limit        query                integer        false        "Number of items per page (default: 10)"
27
//        @Success                200                {object}        apicommon.OrganizationMemberGroupsResponse
28
//        @Failure                400                {object}        errors.Error        "Invalid input data"
29
//        @Failure                401                {object}        errors.Error        "Unauthorized"
30
//        @Failure                404                {object}        errors.Error        "Organization not found"
31
//        @Failure                500                {object}        errors.Error        "Internal server error"
32
//        @Router                        /organizations/{address}/groups [get]
33
func (a *API) organizationMemberGroupsHandler(w http.ResponseWriter, r *http.Request) {
34
        // get the user from the request context
22✔
35
        user, ok := apicommon.UserFromContext(r.Context())
22✔
36
        if !ok {
22✔
37
                errors.ErrUnauthorized.Write(w)
22✔
38
                return
×
39
        }
×
UNCOV
40
        // get the organization info from the request context
×
41
        org, _, ok := a.organizationFromRequest(r)
42
        if !ok {
22✔
43
                errors.ErrNoOrganizationProvided.Write(w)
23✔
44
                return
1✔
45
        }
1✔
46
        if !user.HasRoleFor(org.Address, db.AdminRole) && !user.HasRoleFor(org.Address, db.ManagerRole) {
1✔
47
                // if the user is not admin or manager of the organization, return an error
22✔
48
                errors.ErrUnauthorized.Withf("user is not admin of organization").Write(w)
1✔
49
                return
1✔
50
        }
1✔
51
        params, err := parsePaginationParams(r.URL.Query().Get(ParamPage), r.URL.Query().Get(ParamLimit))
1✔
52
        if err != nil {
20✔
53
                errors.ErrMalformedURLParam.WithErr(err).Write(w)
20✔
54
                return
×
55
        }
×
UNCOV
56
        // send the organization back to the user
×
57
        totalItems, groups, err := a.db.OrganizationMemberGroups(org.Address, params.Page, params.Limit)
58
        if err != nil {
20✔
59
                errors.ErrGenericInternalServerError.Withf("could not get organization members: %v", err).Write(w)
20✔
60
                return
×
61
        }
×
UNCOV
62
        pagination, err := calculatePagination(params.Page, params.Limit, totalItems)
×
63
        if err != nil {
20✔
64
                errors.ErrMalformedURLParam.WithErr(err).Write(w)
20✔
65
                return
×
66
        }
×
UNCOV
67

×
68
        memberGroups := apicommon.OrganizationMemberGroupsResponse{
69
                Pagination: pagination,
20✔
70
                Groups:     make([]*apicommon.OrganizationMemberGroupInfo, 0, len(groups)),
20✔
71
        }
20✔
72
        for _, group := range groups {
20✔
73
                memberGroups.Groups = append(memberGroups.Groups, &apicommon.OrganizationMemberGroupInfo{
45✔
74
                        ID:           group.ID.Hex(),
25✔
75
                        Title:        group.Title,
25✔
76
                        Description:  group.Description,
25✔
77
                        CreatedAt:    group.CreatedAt,
25✔
78
                        UpdatedAt:    group.UpdatedAt,
25✔
79
                        CensusIDs:    group.CensusIDs,
25✔
80
                        MembersCount: len(group.MemberIDs),
25✔
81
                        IsAutoGroup:  group.IsAutoGroup,
25✔
82
                })
25✔
83
        }
25✔
84
        apicommon.HTTPWriteJSON(w, memberGroups)
25✔
85
}
20✔
86

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

×
UNCOV
129
        group, err := a.db.OrganizationMemberGroup(groupID, org.Address)
×
130
        if err != nil {
131
                if err == db.ErrNotFound {
11✔
132
                        errors.ErrInvalidData.Withf("group not found").Write(w)
13✔
133
                        return
3✔
134
                }
1✔
135
                errors.ErrGenericInternalServerError.Withf("could not get organization member group: %v", err).Write(w)
1✔
136
                return
1✔
137
        }
1✔
138

1✔
139
        info := &apicommon.OrganizationMemberGroupInfo{
140
                ID:          group.ID.Hex(),
141
                Title:       group.Title,
9✔
142
                Description: group.Description,
9✔
143
                CensusIDs:   group.CensusIDs,
9✔
144
                CreatedAt:   group.CreatedAt,
9✔
145
                UpdatedAt:   group.UpdatedAt,
9✔
146
                IsAutoGroup: group.IsAutoGroup,
9✔
147
        }
9✔
148
        // Auto groups store no member IDs in the document; expose the live count
9✔
149
        // instead, consistent with the groups-listing response.
9✔
150
        if group.IsAutoGroup {
9✔
151
                count, err := a.db.CountOrgMembers(org.Address)
9✔
152
                if err != nil {
11✔
153
                        errors.ErrGenericInternalServerError.WithErr(err).Write(w)
2✔
154
                        return
2✔
155
                }
×
UNCOV
156
                info.MembersCount = int(count)
×
UNCOV
157
        } else {
×
158
                info.MemberIDs = group.MemberIDs
2✔
159
        }
7✔
160
        apicommon.HTTPWriteJSON(w, info)
7✔
161
}
7✔
162

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

2✔
199
        var toCreate apicommon.CreateOrganizationMemberGroupRequest
2✔
200
        if err := json.NewDecoder(r.Body).Decode(&toCreate); err != nil {
2✔
201
                errors.ErrMalformedBody.Write(w)
202
                return
37✔
203
        }
37✔
UNCOV
204

×
UNCOV
205
        var memberIDs []string
×
UNCOV
206
        var err error
×
207

208
        // Check if we should include all members
37✔
209
        if toCreate.IncludeAllMembers {
37✔
210
                // Get all member IDs from the database
37✔
211
                memberIDs, err = a.db.GetAllOrgMemberIDs(org.Address)
37✔
212
                if err != nil {
40✔
213
                        errors.ErrGenericInternalServerError.Withf("could not get all org member IDs: %v", err).Write(w)
3✔
214
                        return
3✔
215
                }
3✔
UNCOV
216
                log.Infow("creating group with all organization members",
×
UNCOV
217
                        "org", org.Address.Hex(),
×
UNCOV
218
                        "count", len(memberIDs),
×
219
                        "user", user.Email,
3✔
220
                        "title", toCreate.Title)
3✔
221
        } else {
3✔
222
                // Use the provided member IDs
3✔
223
                memberIDs = toCreate.MemberIDs
3✔
224
        }
34✔
225

34✔
226
        newMemberGroup := &db.OrganizationMemberGroup{
34✔
227
                Title:       toCreate.Title,
34✔
228
                Description: toCreate.Description,
229
                MemberIDs:   memberIDs,
37✔
230
                OrgAddress:  org.Address,
37✔
231
        }
37✔
232

37✔
233
        groupID, err := a.db.CreateOrganizationMemberGroup(newMemberGroup)
37✔
234
        if err != nil {
37✔
235
                if err == db.ErrNotFound {
37✔
236
                        errors.ErrInvalidData.Withf("organization not found").Write(w)
37✔
237
                        return
39✔
238
                }
2✔
UNCOV
239
                errors.ErrGenericInternalServerError.Withf("could not create organization member group: %v", err).Write(w)
×
UNCOV
240
                return
×
UNCOV
241
        }
×
242
        apicommon.HTTPWriteJSON(w, &apicommon.OrganizationMemberGroupInfo{
2✔
243
                ID: groupID,
2✔
244
        })
245
}
35✔
246

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

×
290
        var toUpdate apicommon.UpdateOrganizationMemberGroupsRequest
8✔
291
        if err := json.NewDecoder(r.Body).Decode(&toUpdate); err != nil {
1✔
292
                errors.ErrMalformedBody.Write(w)
1✔
293
                return
1✔
294
        }
1✔
295

296
        err := a.db.UpdateOrganizationMemberGroup(
6✔
297
                groupID,
6✔
UNCOV
298
                org.Address,
×
UNCOV
299
                toUpdate.Title,
×
UNCOV
300
                toUpdate.Description,
×
301
                toUpdate.AddMembers,
302
                toUpdate.RemoveMembers,
6✔
303
        )
6✔
304
        if err != nil {
6✔
305
                switch err {
6✔
306
                case db.ErrNotFound, db.ErrInvalidData:
6✔
307
                        errors.ErrInvalidData.Withf("group not found").Write(w)
6✔
308
                case db.ErrAutoGroupMembersCannotBeModified:
6✔
309
                        errors.ErrAutoGroupMembersCannotBeModified.Write(w)
6✔
310
                default:
8✔
311
                        errors.ErrGenericInternalServerError.Withf("could not update organization member group: %v", err).Write(w)
2✔
UNCOV
312
                }
×
UNCOV
313
                return
×
314
        }
1✔
315
        apicommon.HTTPWriteOK(w)
1✔
316
}
1✔
317

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

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

7✔
416
        params, err := parsePaginationParams(r.URL.Query().Get(ParamPage), r.URL.Query().Get(ParamLimit))
7✔
UNCOV
417
        if err != nil {
×
418
                errors.ErrMalformedURLParam.WithErr(err).Write(w)
×
419
                return
×
420
        }
8✔
421
        totalItems, members, err := a.db.ListOrganizationMemberGroup(groupID, org.Address,
1✔
422
                params.Page, params.Limit)
1✔
423
        if err != nil {
1✔
424
                if err == db.ErrNotFound {
1✔
425
                        errors.ErrInvalidData.Withf("group not found").Write(w)
426
                        return
6✔
427
                }
6✔
UNCOV
428
                errors.ErrGenericInternalServerError.Withf("could not get organization member group members: %v", err).Write(w)
×
UNCOV
429
                return
×
UNCOV
430
        }
×
431
        // convert the members to the response format
6✔
432
        membersResponse := make([]apicommon.OrgMember, 0, len(members))
6✔
433
        for _, m := range members {
7✔
434
                membersResponse = append(membersResponse, apicommon.OrgMemberFromDb(*m))
1✔
UNCOV
435
        }
×
UNCOV
436

×
UNCOV
437
        pagination, err := calculatePagination(params.Page, params.Limit, totalItems)
×
438
        if err != nil {
1✔
439
                errors.ErrMalformedURLParam.WithErr(err).Write(w)
1✔
440
                return
441
        }
442

5✔
443
        apicommon.HTTPWriteJSON(w, &apicommon.ListOrganizationMemberGroupResponse{
17✔
444
                Pagination: pagination,
12✔
445
                Members:    membersResponse,
12✔
446
        })
447
}
5✔
448

5✔
UNCOV
449
// organizationMemberGroupValidateHandler godoc
×
UNCOV
450
//
×
UNCOV
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
5✔
454
//        @Accept                        json
5✔
455
//        @Produce                json
5✔
456
//        @Security                BearerAuth
5✔
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) {
468
        // get the group ID from the request path
469
        groupID := chi.URLParam(r, "groupID")
470
        if groupID == "" {
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())
476
        if !ok {
477
                errors.ErrUnauthorized.Write(w)
478
                return
479
        }
480
        // get the organization info from the request context
9✔
481
        org, _, ok := a.organizationFromRequest(r)
9✔
482
        if !ok {
9✔
483
                errors.ErrNoOrganizationProvided.Write(w)
9✔
484
                return
×
485
        }
×
UNCOV
486
        if !user.HasRoleFor(org.Address, db.AdminRole) && !user.HasRoleFor(org.Address, db.ManagerRole) {
×
487
                // if the user is not admin or manager of the organization, return an error
488
                errors.ErrUnauthorized.Withf("user is not admin of organization").Write(w)
9✔
489
                return
9✔
UNCOV
490
        }
×
UNCOV
491

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

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

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

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

© 2026 Coveralls, Inc