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

vocdoni / saas-backend / 17557469823

08 Sep 2025 04:25PM UTC coverage: 58.777% (-0.06%) from 58.841%
17557469823

Pull #213

github

altergui
fix
Pull Request #213: api: standardize parameters ProcessID, CensusID, GroupID, JobID, UserID, BundleID

254 of 345 new or added lines in 22 files covered. (73.62%)

19 existing lines in 7 files now uncovered.

5652 of 9616 relevant lines covered (58.78%)

32.01 hits per line

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

20.33
/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) {
6✔
42
        var req apicommon.CreateProcessBundleRequest
6✔
43
        if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
6✔
44
                errors.ErrMalformedBody.Write(w)
×
45
                return
×
46
        }
×
47

48
        census, err := a.db.Census(req.CensusID)
6✔
49
        if err != nil {
6✔
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())
6✔
60
        if !ok {
6✔
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) {
6✔
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()
6✔
73
        // The cenus root will be the same as the account's public key
6✔
74
        censusRoot, err := a.csp.PubKey()
6✔
75
        if err != nil {
6✔
76
                errors.ErrGenericInternalServerError.Withf("failed to get CSP public key").Write(w)
×
77
                return
×
78
        }
×
79

80
        if len(req.Processes) == 0 {
6✔
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{
×
NEW
99
                        URI:  a.serverURL + "/process/bundle/" + bundleID.String(),
×
100
                        Root: rootHex,
×
101
                })
×
102
                return
×
103
        }
104

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

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

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

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

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

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

142
        var rootHex internal.HexBytes
6✔
143
        if err := rootHex.ParseString(cspPubKey.String()); err != nil {
6✔
144
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
145
                return
×
146
        }
×
147
        apicommon.HTTPWriteJSON(w, apicommon.CreateProcessBundleResponse{
6✔
148
                URI:  a.serverURL + "/process/bundle/" + bundleID.String(),
6✔
149
                Root: rootHex,
6✔
150
        })
6✔
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) {
×
NEW
171
        bundleID, err := apicommon.BundleIDFromRequest(r)
×
NEW
172
        if err != nil {
×
NEW
173
                errors.ErrMalformedURLParam.WithErr(err).Write(w)
×
174
                return
×
175
        }
×
176

177
        var req AddProcessesToBundleRequest
×
178
        if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
×
179
                errors.ErrMalformedBody.Write(w)
×
180
                return
×
181
        }
×
182

183
        // Get the user from the request context
184
        user, ok := apicommon.UserFromContext(r.Context())
×
185
        if !ok {
×
186
                errors.ErrUnauthorized.Write(w)
×
187
                return
×
188
        }
×
189

190
        // Get the existing bundle
191
        bundle, err := a.db.ProcessBundle(bundleID)
×
192
        if err != nil {
×
193
                if err == db.ErrNotFound {
×
194
                        errors.ErrMalformedURLParam.Withf("bundle not found").Write(w)
×
195
                        return
×
196
                }
×
197
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
198
                return
×
199
        }
200

201
        if len(req.Processes) == 0 {
×
202
                apicommon.HTTPWriteJSON(w, apicommon.CreateProcessBundleResponse{
×
NEW
203
                        URI:  "/process/bundle/" + bundleID.String(),
×
204
                        Root: bundle.Census.Published.Root,
×
205
                })
×
206
                return
×
207
        }
×
208

209
        // Check if the user has the necessary permissions for the organization
210
        if !user.HasRoleFor(bundle.OrgAddress, db.ManagerRole) && !user.HasRoleFor(bundle.OrgAddress, db.AdminRole) {
×
211
                errors.ErrUnauthorized.Withf("user is not admin or manager of organization").Write(w)
×
212
                return
×
213
        }
×
214

215
        // Collect all processes to add
216
        var processesToAdd []internal.HexBytes
×
217

×
218
        for _, processReq := range req.Processes {
×
219
                if len(processReq) == 0 {
×
220
                        errors.ErrMalformedBody.Withf("missing process ID").Write(w)
×
221
                        return
×
222
                }
×
223
                processID, err := hex.DecodeString(util.TrimHex(processReq))
×
224
                if err != nil {
×
225
                        errors.ErrMalformedBody.Withf("invalid process ID").Write(w)
×
226
                        return
×
227
                }
×
228

229
                processesToAdd = append(processesToAdd, processID)
×
230
        }
231

232
        // Add processes to the bundle
233
        if err := a.db.AddProcessesToBundle(bundleID, processesToAdd); err != nil {
×
234
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
235
                return
×
236
        }
×
237

238
        apicommon.HTTPWriteJSON(w, apicommon.CreateProcessBundleResponse{
×
NEW
239
                URI:  "/process/bundle/" + bundleID.String(),
×
240
                Root: bundle.Census.Published.Root,
×
241
        })
×
242
}
243

244
// processBundleInfoHandler godoc
245
//
246
//        @Summary                Get process bundle information
247
//        @Description        Retrieve process bundle information by ID. Returns bundle details including the associated census,
248
//        @Description        census root, organization address, and list of processes.
249
//        @Tags                        process
250
//        @Accept                        json
251
//        @Produce                json
252
//        @Param                        bundleId        path                string        true        "Bundle ID"
253
//        @Success                200                        {object}        db.ProcessesBundle
254
//        @Failure                400                        {object}        errors.Error        "Invalid bundle ID"
255
//        @Failure                404                        {object}        errors.Error        "Bundle not found"
256
//        @Failure                500                        {object}        errors.Error        "Internal server error"
257
//        @Router                        /process/bundle/{bundleId} [get]
258
func (a *API) processBundleInfoHandler(w http.ResponseWriter, r *http.Request) {
×
NEW
259
        bundleID, err := apicommon.BundleIDFromRequest(r)
×
NEW
260
        if err != nil {
×
NEW
261
                errors.ErrMalformedURLParam.WithErr(err).Write(w)
×
262
                return
×
263
        }
×
264

265
        bundle, err := a.db.ProcessBundle(bundleID)
×
266
        if err != nil {
×
267
                if err == db.ErrNotFound {
×
268
                        errors.ErrMalformedURLParam.Withf("bundle not found").Write(w)
×
269
                        return
×
270
                }
×
271
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
272
                return
×
273
        }
274

275
        apicommon.HTTPWriteJSON(w, bundle)
×
276
}
277

278
// processBundleParticipantInfoHandler godoc
279
//
280
//        @Summary                Get participant information for a process bundle
281
//        @Description        Retrieve process information for a participant in a process bundle. Returns process details including
282
//        @Description        the census and metadata.
283
//        @Tags                        process
284
//        @Accept                        json
285
//        @Produce                json
286
//        @Param                        bundleId                path                string        true        "Bundle ID"
287
//        @Param                        participantID        path                string        true        "Participant ID"
288
//        @Success                200                                {object}        interface{}
289
//        @Failure                400                                {object}        errors.Error        "Invalid bundle ID or participant ID"
290
//        @Failure                404                                {object}        errors.Error        "Bundle not found"
291
//        @Failure                500                                {object}        errors.Error        "Internal server error"
292
//        @Router                        /process/bundle/{bundleId}/{participantId} [get]
293
func (a *API) processBundleParticipantInfoHandler(w http.ResponseWriter, r *http.Request) {
×
NEW
294
        bundleID, err := apicommon.BundleIDFromRequest(r)
×
NEW
295
        if err != nil {
×
NEW
296
                errors.ErrMalformedURLParam.WithErr(err).Write(w)
×
297
                return
×
298
        }
×
299

NEW
300
        _, err = a.db.ProcessBundle(bundleID)
×
301
        if err != nil {
×
302
                if err == db.ErrNotFound {
×
303
                        errors.ErrMalformedURLParam.Withf("bundle not found").Write(w)
×
304
                        return
×
305
                }
×
306
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
307
                return
×
308
        }
309

310
        participantID := chi.URLParam(r, "participantId")
×
311
        if participantID == "" {
×
312
                errors.ErrMalformedURLParam.Withf("missing participant ID").Write(w)
×
313
                return
×
314
        }
×
315

316
        // TODO
317
        /*        elections := a.csp.Indexer(participantID, bundleIDStr, "")
318
                if len(elections) == 0 {
319
                        httpWriteJSON(w, []twofactor.Election{})
320
                        return
321
                }
322
        */
323

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