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

vocdoni / saas-backend / 18900867018

29 Oct 2025 07:54AM UTC coverage: 60.228% (+0.1%) from 60.11%
18900867018

Pull #84

github

altergui
fixup SetProcess
Pull Request #84: api: support creating (and listing) draft processes

124 of 203 new or added lines in 3 files covered. (61.08%)

332 existing lines in 11 files now uncovered.

6277 of 10422 relevant lines covered (60.23%)

36.41 hits per line

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

51.98
/api/process.go
1
package api
2

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

8
        "github.com/ethereum/go-ethereum/common"
9
        "github.com/go-chi/chi/v5"
10
        "github.com/vocdoni/saas-backend/api/apicommon"
11
        "github.com/vocdoni/saas-backend/db"
12
        "github.com/vocdoni/saas-backend/errors"
13
        "go.mongodb.org/mongo-driver/bson/primitive"
14
)
15

16
// createProcessHandler godoc
17
//
18
//        @Summary                Create a new voting process
19
//        @Description        Create a new voting process. Requires Manager/Admin role.
20
//        @Tags                        process
21
//        @Accept                        json
22
//        @Produce                json
23
//        @Security                BearerAuth
24
//        @Param                        request        body                apicommon.CreateProcessRequest        true        "Process creation information"
25
//        @Success                200                {object}        primitive.ObjectID                                "Process ID"
26
//        @Failure                400                {object}        errors.Error                                        "Invalid input data"
27
//        @Failure                401                {object}        errors.Error                                        "Unauthorized"
28
//        @Failure                404                {object}        errors.Error                                        "Published census not found"
29
//        @Failure                409                {object}        errors.Error                                        "Process already exists"
30
//        @Failure                500                {object}        errors.Error                                        "Internal server error"
31
//        @Router                        /process [post]
32
func (a *API) createProcessHandler(w http.ResponseWriter, r *http.Request) {
5✔
33
        // parse the process info from the request body
5✔
34
        processInfo := &apicommon.CreateProcessRequest{}
5✔
35
        if err := json.NewDecoder(r.Body).Decode(&processInfo); err != nil {
5✔
36
                errors.ErrMalformedBody.Write(w)
×
37
                return
×
38
        }
×
39

40
        if processInfo.CensusID == nil {
6✔
41
                errors.ErrMalformedBody.Withf("missing census ID").Write(w)
1✔
42
                return
1✔
43
        }
1✔
44

45
        // get the user from the request context
46
        user, ok := apicommon.UserFromContext(r.Context())
4✔
47
        if !ok {
4✔
48
                errors.ErrUnauthorized.Write(w)
×
49
                return
×
50
        }
×
51

52
        if processInfo.Draft && len(processInfo.OrgAddress) == 0 {
4✔
NEW
53
                errors.ErrMalformedBody.Withf("draft processes must provide an org address").Write(w)
×
NEW
54
                return
×
NEW
55
        }
×
56

57
        var orgAddress common.Address
4✔
58
        var census *db.Census
4✔
59
        if processInfo.CensusID != nil {
8✔
60
                var err error
4✔
61
                census, err = a.db.Census(processInfo.CensusID.String())
4✔
62
                if err != nil {
5✔
63
                        if err == db.ErrNotFound {
1✔
NEW
64
                                errors.ErrMalformedURLParam.Withf("invalid census provided").Write(w)
×
NEW
65
                                return
×
NEW
66
                        }
×
67
                        errors.ErrGenericInternalServerError.WithErr(err).Write(w)
1✔
68
                        return
1✔
69
                }
70
                orgAddress = census.OrgAddress
3✔
NEW
71
        } else if len(processInfo.OrgAddress) > 0 {
×
NEW
72
                orgAddress = processInfo.OrgAddress
×
NEW
73
        } else {
×
NEW
74
                errors.ErrMalformedBody.Withf("either census ID or organization address must be provided").Write(w)
×
UNCOV
75
                return
×
UNCOV
76
        }
×
77

78
        // check the user has the necessary permissions
79
        if !user.HasRoleFor(orgAddress, db.ManagerRole) && !user.HasRoleFor(orgAddress, db.AdminRole) {
3✔
80
                errors.ErrUnauthorized.Withf("user is not admin of organization").Write(w)
×
81
                return
×
82
        }
×
83

84
        // Create or update the process
85
        process := &db.Process{
3✔
86
                Census:     *census,
3✔
87
                Metadata:   processInfo.Metadata,
3✔
88
                OrgAddress: orgAddress,
3✔
89
                Draft:      processInfo.Draft,
3✔
90
        }
3✔
91
        if len(processInfo.Address) > 0 {
3✔
92
                process.Address = processInfo.Address
×
93
        }
×
94

95
        processID, err := a.db.SetProcess(process)
3✔
96
        if err != nil {
3✔
97
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
98
                return
×
99
        }
×
100

101
        apicommon.HTTPWriteJSON(w, processID)
3✔
102
}
103

104
// updateProcessHandler godoc
105
//
106
//        @Summary                Update an existing voting process
107
//        @Description        Update an existing voting process. Requires Manager/Admin role.
108
//        @Tags                        process
109
//        @Accept                        json
110
//        @Produce                json
111
//        @Security                BearerAuth
112
//        @Param                        processId        path                string                                                        true        "Process ID"
113
//        @Param                        request                body                apicommon.CreateProcessRequest        true        "Process update information"
114
//        @Success                200                        {string}        string                                                        "OK"
115
//        @Failure                400                        {object}        errors.Error                                        "Invalid input data"
116
//        @Failure                401                        {object}        errors.Error                                        "Unauthorized"
117
//        @Failure                404                        {object}        errors.Error                                        "Process not found"
118
//        @Failure                500                        {object}        errors.Error                                        "Internal server error"
119
//        @Router                        /process/{processId} [put]
120
func (a *API) updateProcessHandler(w http.ResponseWriter, r *http.Request) {
1✔
121
        processID := chi.URLParam(r, "processId")
1✔
122
        if len(processID) == 0 {
1✔
NEW
123
                errors.ErrMalformedURLParam.Withf("missing process ID").Write(w)
×
NEW
124
                return
×
NEW
125
        }
×
126
        parsedID, err := primitive.ObjectIDFromHex(processID)
1✔
127
        if err != nil {
1✔
NEW
128
                errors.ErrMalformedURLParam.Withf("invalid process ID").Write(w)
×
NEW
129
                return
×
NEW
130
        }
×
131

132
        // parse the process info from the request body
133
        processInfo := &apicommon.UpdateProcessRequest{}
1✔
134
        if err := json.NewDecoder(r.Body).Decode(&processInfo); err != nil {
1✔
NEW
135
                errors.ErrMalformedBody.Write(w)
×
NEW
136
                return
×
NEW
137
        }
×
138

139
        // get the user from the request context
140
        user, ok := apicommon.UserFromContext(r.Context())
1✔
141
        if !ok {
1✔
NEW
142
                errors.ErrUnauthorized.Write(w)
×
NEW
143
                return
×
NEW
144
        }
×
145

146
        existingProcess, err := a.db.Process(parsedID)
1✔
147
        if err != nil {
1✔
NEW
148
                if err == db.ErrNotFound {
×
NEW
149
                        errors.ErrMalformedURLParam.Withf("process not found").Write(w)
×
NEW
150
                        return
×
NEW
151
                }
×
NEW
152
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
NEW
153
                return
×
154
        }
155

156
        // check the user has the necessary permissions
157
        if !user.HasRoleFor(existingProcess.OrgAddress, db.ManagerRole) &&
1✔
158
                !user.HasRoleFor(existingProcess.OrgAddress, db.AdminRole) {
1✔
NEW
159
                errors.ErrUnauthorized.Withf("user is not admin of organization").Write(w)
×
NEW
160
                return
×
NEW
161
        }
×
162

163
        var census *db.Census
1✔
164
        if processInfo.CensusID != nil {
2✔
165
                census, err = a.db.Census(processInfo.CensusID.String())
1✔
166
                if err != nil {
1✔
NEW
167
                        if err == db.ErrNotFound {
×
NEW
168
                                errors.ErrMalformedURLParam.Withf("census not found").Write(w)
×
NEW
169
                                return
×
NEW
170
                        }
×
NEW
171
                        errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
NEW
172
                        return
×
173
                }
174
        }
175

176
        if processInfo.Draft != nil {
2✔
177
                existingProcess.Draft = *processInfo.Draft
1✔
178
        }
1✔
179

180
        if len(processInfo.Metadata) > 0 {
2✔
181
                existingProcess.Metadata = processInfo.Metadata
1✔
182
        }
1✔
183

184
        if len(processInfo.Address) > 0 {
1✔
NEW
185
                existingProcess.Address = processInfo.Address
×
NEW
186
        }
×
187

188
        if len(processInfo.CensusID) > 0 {
2✔
189
                existingProcess.Census = *census
1✔
190
        }
1✔
191

192
        _, err = a.db.SetProcess(existingProcess)
1✔
193
        if err != nil {
1✔
NEW
194
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
NEW
195
                return
×
NEW
196
        }
×
197

198
        apicommon.HTTPWriteJSON(w, "Process updated successfully")
1✔
199
}
200

201
// processInfoHandler godoc
202
//
203
//        @Summary                Get process information
204
//        @Description        Retrieve voting process information by ID. Returns process details including census and metadata.
205
//        @Tags                        process
206
//        @Accept                        json
207
//        @Produce                json
208
//        @Param                        processId        path                string        true        "Process ID"
209
//        @Success                200                        {object}        db.Process
210
//        @Failure                400                        {object}        errors.Error        "Invalid process ID"
211
//        @Failure                404                        {object}        errors.Error        "Process not found"
212
//        @Failure                500                        {object}        errors.Error        "Internal server error"
213
//        @Router                        /process/{processId} [get]
214
func (a *API) processInfoHandler(w http.ResponseWriter, r *http.Request) {
4✔
215
        processID := chi.URLParam(r, "processId")
4✔
216
        if len(processID) == 0 {
4✔
217
                errors.ErrMalformedURLParam.Withf("missing process ID").Write(w)
×
218
                return
×
219
        }
×
220
        parsedID, err := primitive.ObjectIDFromHex(processID)
4✔
221
        if err != nil {
5✔
222
                errors.ErrMalformedURLParam.Withf("invalid process ID").Write(w)
1✔
223
                return
1✔
224
        }
1✔
225

226
        process, err := a.db.Process(parsedID)
3✔
227
        if err != nil {
3✔
228
                if err == db.ErrNotFound {
×
229
                        errors.ErrMalformedURLParam.Withf("process not found").Write(w)
×
230
                        return
×
231
                }
×
232
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
233
                return
×
234
        }
235

236
        apicommon.HTTPWriteJSON(w, process)
3✔
237
}
238

239
// organizationListProcessDraftsHandler godoc
240
//
241
//        @Summary                Get paginated list of process drafts
242
//        @Description        Returns a list of voting process drafts.
243
//        @Tags                        process
244
//        @Accept                        json
245
//        @Produce                json
246
//        @Success                200        {object}        apicommon.ListOrganizationProcesses
247
//        @Failure                404        {object}        errors.Error        "Process not found"
248
//        @Failure                500        {object}        errors.Error        "Internal server error"
249
//        @Router                        /organizations/{address}/processes/drafts [get]
250
func (a *API) organizationListProcessDraftsHandler(w http.ResponseWriter, r *http.Request) {
2✔
251
        // get the organization info from the request context
2✔
252
        org, _, ok := a.organizationFromRequest(r)
2✔
253
        if !ok {
2✔
NEW
254
                errors.ErrNoOrganizationProvided.Write(w)
×
NEW
255
                return
×
NEW
256
        }
×
257
        // get the user from the request context
258
        user, ok := apicommon.UserFromContext(r.Context())
2✔
259
        if !ok {
2✔
NEW
260
                errors.ErrUnauthorized.Write(w)
×
NEW
261
                return
×
NEW
262
        }
×
263
        // check the user has the necessary permissions
264
        if !user.HasRoleFor(org.Address, db.ManagerRole) && !user.HasRoleFor(org.Address, db.AdminRole) {
2✔
NEW
265
                errors.ErrUnauthorized.Withf("user is not admin of organization").Write(w)
×
NEW
266
                return
×
NEW
267
        }
×
268

269
        // Parse pagination parameters from query string
270
        page := 1      // Default page number
2✔
271
        pageSize := 10 // Default page size
2✔
272

2✔
273
        if pageStr := r.URL.Query().Get("page"); pageStr != "" {
2✔
NEW
274
                if pageVal, err := strconv.Atoi(pageStr); err == nil && pageVal > 0 {
×
NEW
275
                        page = pageVal
×
NEW
276
                }
×
277
        }
278

279
        if pageSizeStr := r.URL.Query().Get("pageSize"); pageSizeStr != "" {
2✔
NEW
280
                if pageSizeVal, err := strconv.Atoi(pageSizeStr); err == nil && pageSizeVal >= 0 {
×
NEW
281
                        pageSize = pageSizeVal
×
NEW
282
                }
×
283
        }
284

285
        // retrieve the orgMembers with pagination
286
        pages, processes, err := a.db.ListProcesses(org.Address, page, pageSize, true)
2✔
287
        if err != nil {
2✔
NEW
288
                errors.ErrGenericInternalServerError.Withf("could not get processes: %v", err).Write(w)
×
NEW
289
                return
×
NEW
290
        }
×
291

292
        apicommon.HTTPWriteJSON(w, &apicommon.ListOrganizationProcesses{
2✔
293
                TotalPages:  pages,
2✔
294
                CurrentPage: page,
2✔
295
                Processes:   processes,
2✔
296
        })
2✔
297
}
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