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

vocdoni / saas-backend / 16301338771

15 Jul 2025 06:29PM UTC coverage: 56.793% (+0.7%) from 56.082%
16301338771

Pull #165

github

emmdim
refactor:  census creation

- Removed the PublishedCensus type and added it as Census parameter.
- Introduced new OrgMemberAuthFields defining the data options for member authentication.
- Added the `CheckOrgMemberAuthFields` function that checks a set of members and given auth fields empties an
d duplicates
- Add the option to create a census through the api based on a given group
Pull Request #165: Implements group based census creation

250 of 399 new or added lines in 9 files covered. (62.66%)

4 existing lines in 3 files now uncovered.

5104 of 8987 relevant lines covered (56.79%)

25.32 hits per line

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

18.78
/api/process_bundles.go
1
package api
2

3
import (
4
        "encoding/hex"
5
        "encoding/json"
6
        "net/http"
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
        "github.com/vocdoni/saas-backend/internal"
13
        "go.vocdoni.io/dvote/util"
14
)
15

16
// Using types from apicommon package
17

18
// AddProcessesToBundleRequest represents the request body for adding processes to an existing bundle.
19
// It contains an array of process IDs to add to the bundle.
20
type AddProcessesToBundleRequest struct {
21
        Processes []string `json:"processes"` // Array of process creation requests to add
22
}
23

24
// createProcessBundleHandler godoc
25
//
26
//        @Summary                Create a new process bundle
27
//        @Description        Create a new process bundle with the specified census and optional list of processes. Requires
28
//        @Description        Manager/Admin role for the organization that owns the census. The census root will be the same as the
29
//        @Description        account's public key.
30
//        @Tags                        process
31
//        @Accept                        json
32
//        @Produce                json
33
//        @Security                BearerAuth
34
//        @Param                        request        body                apicommon.CreateProcessBundleRequest        true        "Process bundle creation information"
35
//        @Success                200                {object}        apicommon.CreateProcessBundleResponse
36
//        @Failure                400                {object}        errors.Error        "Invalid input data"
37
//        @Failure                401                {object}        errors.Error        "Unauthorized"
38
//        @Failure                404                {object}        errors.Error        "Census not found"
39
//        @Failure                500                {object}        errors.Error        "Internal server error"
40
//        @Router                        /process/bundle [post]
41
func (a *API) createProcessBundleHandler(w http.ResponseWriter, r *http.Request) {
1✔
42
        var req apicommon.CreateProcessBundleRequest
1✔
43
        if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
1✔
44
                errors.ErrMalformedBody.Write(w)
×
45
                return
×
46
        }
×
47

48
        census, err := a.db.Census(req.CensusID)
1✔
49
        if err != nil {
1✔
50
                if err == db.ErrNotFound {
×
51
                        errors.ErrMalformedURLParam.Withf("census not found").Write(w)
×
52
                        return
×
53
                }
×
54
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
55
                return
×
56
        }
57

58
        // Get the user from the request context
59
        user, ok := apicommon.UserFromContext(r.Context())
1✔
60
        if !ok {
1✔
61
                errors.ErrUnauthorized.Write(w)
×
62
                return
×
63
        }
×
64

65
        // Check if the user has the necessary permissions for the organization
66
        if !user.HasRoleFor(census.OrgAddress, db.ManagerRole) && !user.HasRoleFor(census.OrgAddress, db.AdminRole) {
1✔
67
                errors.ErrUnauthorized.Withf("user is not admin or manager of organization").Write(w)
×
68
                return
×
69
        }
×
70

71
        // generate a new bundle ID
72
        bundleID := a.db.NewBundleID()
1✔
73
        // The cenus root will be the same as the account's public key
1✔
74
        censusRoot, err := a.csp.PubKey()
1✔
75
        if err != nil {
1✔
76
                errors.ErrGenericInternalServerError.Withf("failed to get CSP public key").Write(w)
×
77
                return
×
78
        }
×
79

80
        if len(req.Processes) == 0 {
1✔
81
                // Create the process bundle
×
82
                bundle := &db.ProcessesBundle{
×
83
                        ID:         bundleID,
×
84
                        OrgAddress: census.OrgAddress,
×
85
                        Census:     *census,
×
86
                }
×
87
                _, err = a.db.SetProcessBundle(bundle)
×
88
                if err != nil {
×
89
                        errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
90
                        return
×
91
                }
×
92

93
                var rootHex internal.HexBytes
×
94
                if err := rootHex.ParseString(censusRoot.String()); err != nil {
×
95
                        errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
96
                        return
×
97
                }
×
98
                apicommon.HTTPWriteJSON(w, apicommon.CreateProcessBundleResponse{
×
99
                        URI:  a.serverURL + "/process/bundle/" + bundleID.Hex(),
×
100
                        Root: rootHex,
×
101
                })
×
102
                return
×
103
        }
104

105
        // Collect all processes
106
        var processes []internal.HexBytes
1✔
107

1✔
108
        for _, processReq := range req.Processes {
2✔
109
                if len(processReq) == 0 {
1✔
110
                        errors.ErrMalformedBody.Withf("missing process ID").Write(w)
×
111
                        return
×
112
                }
×
113
                processID, err := hex.DecodeString(util.TrimHex(processReq))
1✔
114
                if err != nil {
1✔
115
                        errors.ErrMalformedBody.Withf("invalid process ID").Write(w)
×
116
                        return
×
117
                }
×
118

119
                processes = append(processes, processID)
1✔
120
        }
121

122
        // Create the process bundle
123
        cspPubKey, err := a.csp.PubKey()
1✔
124
        if err != nil {
1✔
125
                errors.ErrGenericInternalServerError.Withf("failed to get CSP public key").Write(w)
×
126
                return
×
127
        }
×
128

129
        bundle := &db.ProcessesBundle{
1✔
130
                ID:         bundleID,
1✔
131
                Processes:  processes,
1✔
132
                OrgAddress: census.OrgAddress,
1✔
133
                Census:     *census,
1✔
134
        }
1✔
135

1✔
136
        _, err = a.db.SetProcessBundle(bundle)
1✔
137
        if err != nil {
1✔
138
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
139
                return
×
140
        }
×
141

142
        var rootHex internal.HexBytes
1✔
143
        if err := rootHex.ParseString(cspPubKey.String()); err != nil {
1✔
144
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
145
                return
×
146
        }
×
147
        apicommon.HTTPWriteJSON(w, apicommon.CreateProcessBundleResponse{
1✔
148
                URI:  a.serverURL + "/process/bundle/" + bundleID.Hex(),
1✔
149
                Root: rootHex,
1✔
150
        })
1✔
151
}
152

153
// updateProcessBundleHandler godoc
154
//
155
//        @Summary                Add processes to an existing bundle
156
//        @Description        Add additional processes to an existing bundle. Requires Manager/Admin role for the organization
157
//        @Description        that owns the bundle.
158
//        @Tags                        process
159
//        @Accept                        json
160
//        @Produce                json
161
//        @Security                BearerAuth
162
//        @Param                        bundleId        path                string                                                true        "Bundle ID"
163
//        @Param                        request                body                AddProcessesToBundleRequest        true        "Processes to add"
164
//        @Success                200                        {object}        apicommon.CreateProcessBundleResponse
165
//        @Failure                400                        {object}        errors.Error        "Invalid input data"
166
//        @Failure                401                        {object}        errors.Error        "Unauthorized"
167
//        @Failure                404                        {object}        errors.Error        "Bundle or census not found"
168
//        @Failure                500                        {object}        errors.Error        "Internal server error"
169
//        @Router                        /process/bundle/{bundleId} [put]
170
func (a *API) updateProcessBundleHandler(w http.ResponseWriter, r *http.Request) {
×
171
        bundleIDStr := chi.URLParam(r, "bundleId")
×
172
        if bundleIDStr == "" {
×
173
                errors.ErrMalformedURLParam.Withf("missing bundle ID").Write(w)
×
174
                return
×
175
        }
×
176

177
        var bundleID internal.HexBytes
×
178
        if err := bundleID.ParseString(bundleIDStr); err != nil {
×
179
                errors.ErrMalformedURLParam.Withf("invalid bundle ID").Write(w)
×
180
                return
×
181
        }
×
182

183
        var req AddProcessesToBundleRequest
×
184
        if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
×
185
                errors.ErrMalformedBody.Write(w)
×
186
                return
×
187
        }
×
188

189
        // Get the user from the request context
190
        user, ok := apicommon.UserFromContext(r.Context())
×
191
        if !ok {
×
192
                errors.ErrUnauthorized.Write(w)
×
193
                return
×
194
        }
×
195

196
        // Get the existing bundle
197
        bundle, err := a.db.ProcessBundle(bundleID)
×
198
        if err != nil {
×
199
                if err == db.ErrNotFound {
×
200
                        errors.ErrMalformedURLParam.Withf("bundle not found").Write(w)
×
201
                        return
×
202
                }
×
203
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
204
                return
×
205
        }
206

207
        if len(req.Processes) == 0 {
×
208
                apicommon.HTTPWriteJSON(w, apicommon.CreateProcessBundleResponse{
×
209
                        URI:  "/process/bundle/" + bundleIDStr,
×
NEW
210
                        Root: bundle.Census.Published.Root,
×
211
                })
×
212
                return
×
UNCOV
213
        }
×
214

215
        // Check if the user has the necessary permissions for the organization
216
        if !user.HasRoleFor(bundle.OrgAddress, db.ManagerRole) && !user.HasRoleFor(bundle.OrgAddress, db.AdminRole) {
×
217
                errors.ErrUnauthorized.Withf("user is not admin or manager of organization").Write(w)
×
218
                return
×
219
        }
×
220

221
        // Collect all processes to add
222
        var processesToAdd []internal.HexBytes
×
223

×
224
        for _, processReq := range req.Processes {
×
225
                if len(processReq) == 0 {
×
226
                        errors.ErrMalformedBody.Withf("missing process ID").Write(w)
×
227
                        return
×
228
                }
×
229
                processID, err := hex.DecodeString(util.TrimHex(processReq))
×
230
                if err != nil {
×
231
                        errors.ErrMalformedBody.Withf("invalid process ID").Write(w)
×
232
                        return
×
233
                }
×
234

235
                processesToAdd = append(processesToAdd, processID)
×
236
        }
237

238
        // Add processes to the bundle
239
        if err := a.db.AddProcessesToBundle(bundleID, processesToAdd); err != nil {
×
240
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
241
                return
×
242
        }
×
243

244
        apicommon.HTTPWriteJSON(w, apicommon.CreateProcessBundleResponse{
×
245
                URI:  "/process/bundle/" + bundleIDStr,
×
NEW
246
                Root: bundle.Census.Published.Root,
×
247
        })
×
248
}
249

250
// processBundleInfoHandler godoc
251
//
252
//        @Summary                Get process bundle information
253
//        @Description        Retrieve process bundle information by ID. Returns bundle details including the associated census,
254
//        @Description        census root, organization address, and list of processes.
255
//        @Tags                        process
256
//        @Accept                        json
257
//        @Produce                json
258
//        @Param                        bundleId        path                string        true        "Bundle ID"
259
//        @Success                200                        {object}        db.ProcessesBundle
260
//        @Failure                400                        {object}        errors.Error        "Invalid bundle ID"
261
//        @Failure                404                        {object}        errors.Error        "Bundle not found"
262
//        @Failure                500                        {object}        errors.Error        "Internal server error"
263
//        @Router                        /process/bundle/{bundleId} [get]
264
func (a *API) processBundleInfoHandler(w http.ResponseWriter, r *http.Request) {
×
265
        bundleIDStr := chi.URLParam(r, "bundleId")
×
266
        if bundleIDStr == "" {
×
267
                errors.ErrMalformedURLParam.Withf("missing bundle ID").Write(w)
×
268
                return
×
269
        }
×
270

271
        var bundleID internal.HexBytes
×
272
        if err := bundleID.ParseString(bundleIDStr); err != nil {
×
273
                errors.ErrMalformedURLParam.Withf("invalid bundle ID").Write(w)
×
274
                return
×
275
        }
×
276

277
        bundle, err := a.db.ProcessBundle(bundleID)
×
278
        if err != nil {
×
279
                if err == db.ErrNotFound {
×
280
                        errors.ErrMalformedURLParam.Withf("bundle not found").Write(w)
×
281
                        return
×
282
                }
×
283
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
284
                return
×
285
        }
286

287
        apicommon.HTTPWriteJSON(w, bundle)
×
288
}
289

290
// processBundleParticipantInfoHandler godoc
291
//
292
//        @Summary                Get participant information for a process bundle
293
//        @Description        Retrieve process information for a participant in a process bundle. Returns process details including
294
//        @Description        the census and metadata.
295
//        @Tags                        process
296
//        @Accept                        json
297
//        @Produce                json
298
//        @Param                        bundleId                path                string        true        "Bundle ID"
299
//        @Param                        participantID        path                string        true        "Participant ID"
300
//        @Success                200                                {object}        interface{}
301
//        @Failure                400                                {object}        errors.Error        "Invalid bundle ID or participant ID"
302
//        @Failure                404                                {object}        errors.Error        "Bundle not found"
303
//        @Failure                500                                {object}        errors.Error        "Internal server error"
304
//        @Router                        /process/bundle/{bundleId}/{participantId} [get]
305
func (a *API) processBundleParticipantInfoHandler(w http.ResponseWriter, r *http.Request) {
×
306
        bundleIDStr := chi.URLParam(r, "bundleId")
×
307
        if bundleIDStr == "" {
×
308
                errors.ErrMalformedURLParam.Withf("missing bundle ID").Write(w)
×
309
                return
×
310
        }
×
311

312
        var bundleID internal.HexBytes
×
313
        if err := bundleID.ParseString(bundleIDStr); err != nil {
×
314
                errors.ErrMalformedURLParam.Withf("invalid bundle ID").Write(w)
×
315
                return
×
316
        }
×
317

318
        _, err := a.db.ProcessBundle(bundleID)
×
319
        if err != nil {
×
320
                if err == db.ErrNotFound {
×
321
                        errors.ErrMalformedURLParam.Withf("bundle not found").Write(w)
×
322
                        return
×
323
                }
×
324
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
325
                return
×
326
        }
327

328
        participantID := chi.URLParam(r, "participantId")
×
329
        if participantID == "" {
×
330
                errors.ErrMalformedURLParam.Withf("missing participant ID").Write(w)
×
331
                return
×
332
        }
×
333

334
        // TODO
335
        /*        elections := a.csp.Indexer(participantID, bundleIDStr, "")
336
                if len(elections) == 0 {
337
                        httpWriteJSON(w, []twofactor.Election{})
338
                        return
339
                }
340
        */
341

342
        apicommon.HTTPWriteJSON(w, nil)
×
343
}
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

© 2025 Coveralls, Inc