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

vocdoni / saas-backend / 28377766778

29 Jun 2026 02:02PM UTC coverage: 62.725% (+0.3%) from 62.475%
28377766778

Pull #563

github

altergui
fix(subscriptions): handle SET_PROCESS_CENSUS payload; clarify limit zero-semantics

Second Copilot round:

- HasTxPermission grouped SET_PROCESS_CENSUS with NEW_PROCESS and read the tx
  via GetNewProcess(). A SET_PROCESS_CENSUS tx carries a SetProcess payload, so
  GetNewProcess() is nil and dereferencing newProcess.Process panicked (the
  prior round's move of the census check out of the managed guard made this
  reachable for all orgs). Split the case: NEW_PROCESS keeps its checks behind a
  nil-guard; SET_PROCESS_CENSUS reads GetSetProcess() and bounds GetCensusSize()
  against the governing plan's MaxCensus. Adds unit coverage for both the
  in-bounds (allowed) and over-cap (rejected) managed census-update paths.

- apicommon.IntegratorLimits doc: 0 is not uniformly 'unlimited'. Only MaxVotes
  treats 0 as unlimited; MaxManagedProcesses/MaxSMS/MaxEmails are hard caps where
  0 means no allowance. Clarified, and noted the distinct 'unknown' 0 when an
  override-enabled integrator has no plan.
Pull Request #563: feat(integrator): expose pooled votes/SMS/email usage and caps on /integrator

70 of 91 new or added lines in 4 files covered. (76.92%)

342 existing lines in 11 files now uncovered.

10258 of 16354 relevant lines covered (62.72%)

44.87 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 · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc