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

vocdoni / saas-backend / 17432512706

03 Sep 2025 11:50AM UTC coverage: 58.81% (+0.4%) from 58.396%
17432512706

push

github

emmdim
api: new endpoint /organizations/{address}/jobs

94 of 115 new or added lines in 4 files covered. (81.74%)

184 existing lines in 4 files now uncovered.

5654 of 9614 relevant lines covered (58.81%)

28.96 hits per line

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

25.87
/api/organizations.go
1
package api
2

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

9
        "github.com/ethereum/go-ethereum/common"
10
        "github.com/vocdoni/saas-backend/account"
11
        "github.com/vocdoni/saas-backend/api/apicommon"
12
        "github.com/vocdoni/saas-backend/db"
13
        "github.com/vocdoni/saas-backend/errors"
14
        "github.com/vocdoni/saas-backend/internal"
15
        "github.com/vocdoni/saas-backend/notifications/mailtemplates"
16
        "github.com/vocdoni/saas-backend/subscriptions"
17
        "go.vocdoni.io/dvote/log"
18
)
19

20
// createOrganizationHandler godoc
21
//
22
//        @Summary                Create a new organization
23
//        @Description        Create a new organization. If the organization is a suborganization, the parent organization must be
24
//        @Description        specified in the request body, and the user must be an admin of the parent. If the parent organization
25
//        @Description        is already a suborganization, an error is returned.
26
//        @Tags                        organizations
27
//        @Accept                        json
28
//        @Produce                json
29
//        @Security                BearerAuth
30
//        @Param                        request        body                apicommon.OrganizationInfo        true        "Organization information"
31
//        @Success                200                {object}        apicommon.OrganizationInfo
32
//        @Failure                400                {object}        errors.Error        "Invalid input data"
33
//        @Failure                401                {object}        errors.Error        "Unauthorized"
34
//        @Failure                404                {object}        errors.Error        "Parent organization not found"
35
//        @Failure                500                {object}        errors.Error        "Internal server error"
36
//        @Router                        /organizations [post]
37
func (a *API) createOrganizationHandler(w http.ResponseWriter, r *http.Request) {
17✔
38
        // get the user from the request context
17✔
39
        user, ok := apicommon.UserFromContext(r.Context())
17✔
40
        if !ok {
17✔
41
                errors.ErrUnauthorized.Write(w)
×
42
                return
×
43
        }
×
44
        // get the organization info from the request body
45
        orgInfo := &apicommon.OrganizationInfo{}
17✔
46
        if err := json.NewDecoder(r.Body).Decode(orgInfo); err != nil {
17✔
47
                errors.ErrMalformedBody.Write(w)
×
48
                return
×
49
        }
×
50
        // create the organization signer to store the address and the nonce
51
        // TODO: replace email with something else such as user ID
52
        signer, nonce, err := account.NewSigner(a.secret, user.Email)
17✔
53
        if err != nil {
17✔
54
                errors.ErrGenericInternalServerError.Withf("could not create organization signer: %v", err).Write(w)
×
55
                return
×
56
        }
×
57
        // check if the organization type is valid
58
        if !db.IsOrganizationTypeValid(orgInfo.Type) {
17✔
59
                errors.ErrMalformedBody.Withf("invalid organization type").Write(w)
×
60
                return
×
61
        }
×
62
        // find default plan
63
        defaultPlan, err := a.db.DefaultPlan()
17✔
64
        if err != nil || defaultPlan == nil {
17✔
65
                errors.ErrNoDefaultPlan.WithErr((err)).Write(w)
×
66
                return
×
67
        }
×
68
        parentOrg := common.Address{}
17✔
69
        var dbParentOrg *db.Organization
17✔
70
        if orgInfo.Parent != nil {
17✔
71
                // check if the org has permission to create suborganizations
×
72
                hasPermission, err := a.subscriptions.HasDBPermission(user.Email, orgInfo.Parent.Address, subscriptions.CreateSubOrg)
×
73
                if !hasPermission || err != nil {
×
74
                        errors.ErrUnauthorized.Withf("user does not have permission to create suborganizations: %v", err).Write(w)
×
75
                        return
×
76
                }
×
77

78
                dbParentOrg, err = a.db.Organization(orgInfo.Parent.Address)
×
79
                if err != nil {
×
80
                        if err == db.ErrNotFound {
×
81
                                errors.ErrOrganizationNotFound.Withf("parent organization not found").Write(w)
×
82
                                return
×
83
                        }
×
84
                        errors.ErrGenericInternalServerError.Withf("could not get parent organization: %v", err).Write(w)
×
85
                        return
×
86
                }
87
                if len(dbParentOrg.Parent) > 0 {
×
88
                        errors.ErrMalformedBody.Withf("parent organization is already a suborganization").Write(w)
×
89
                        return
×
90
                }
×
91
                isAdmin, err := a.db.UserHasRoleInOrg(user.Email, dbParentOrg.Address, db.AdminRole)
×
92
                if err != nil {
×
93
                        errors.ErrGenericInternalServerError.
×
94
                                Withf("could not check if user is admin of parent organization: %v", err).
×
95
                                Write(w)
×
96
                        return
×
97
                }
×
98
                if !isAdmin {
×
99
                        errors.ErrUnauthorized.Withf("user is not admin of parent organization").Write(w)
×
100
                        return
×
101
                }
×
102
                parentOrg = orgInfo.Parent.Address
×
103
                // update the parent organization counter
×
104
                if err := a.db.IncrementOrganizationSubOrgsCounter(parentOrg); err != nil {
×
105
                        errors.ErrGenericInternalServerError.Withf("increment suborgs: %v", err).Write(w)
×
106
                        return
×
107
                }
×
108
        }
109
        // create the organization
110
        dbOrg := &db.Organization{
17✔
111
                Address:         signer.Address(),
17✔
112
                Website:         orgInfo.Website,
17✔
113
                Creator:         user.Email,
17✔
114
                CreatedAt:       time.Now(),
17✔
115
                Nonce:           nonce,
17✔
116
                Type:            db.OrganizationType(orgInfo.Type),
17✔
117
                Size:            orgInfo.Size,
17✔
118
                Color:           orgInfo.Color,
17✔
119
                Country:         orgInfo.Country,
17✔
120
                Subdomain:       orgInfo.Subdomain,
17✔
121
                Timezone:        orgInfo.Timezone,
17✔
122
                Active:          true,
17✔
123
                Communications:  orgInfo.Communications,
17✔
124
                TokensPurchased: 0,
17✔
125
                TokensRemaining: 0,
17✔
126
                Parent:          parentOrg,
17✔
127
                Subscription: db.OrganizationSubscription{
17✔
128
                        PlanID:        defaultPlan.ID,
17✔
129
                        StartDate:     time.Now(),
17✔
130
                        Active:        true,
17✔
131
                        MaxCensusSize: defaultPlan.Organization.MaxCensus,
17✔
132
                },
17✔
133
        }
17✔
134
        if err := a.db.SetOrganization(dbOrg); err != nil {
17✔
135
                if orgInfo.Parent != nil {
×
136
                        if err := a.db.DecrementOrganizationSubOrgsCounter(parentOrg); err != nil {
×
137
                                log.Errorf("decrement suborgs: %v", err)
×
138
                        }
×
139
                }
140
                if err == db.ErrAlreadyExists {
×
141
                        errors.ErrInvalidOrganizationData.WithErr(err).Write(w)
×
142
                        return
×
143
                }
×
144
                errors.ErrGenericInternalServerError.Write(w)
×
145
                return
×
146
        }
147
        // send the organization back to the user
148
        apicommon.HTTPWriteJSON(w, apicommon.OrganizationFromDB(dbOrg, dbParentOrg))
17✔
149
}
150

151
// organizationInfoHandler godoc
152
//
153
//        @Summary                Get organization information
154
//        @Description        Get information about an organization
155
//        @Tags                        organizations
156
//        @Accept                        json
157
//        @Produce                json
158
//        @Param                        address        path                string        true        "Organization address"
159
//        @Success                200                {object}        apicommon.OrganizationInfo
160
//        @Failure                400                {object}        errors.Error        "Invalid input data"
161
//        @Failure                404                {object}        errors.Error        "Organization not found"
162
//        @Failure                500                {object}        errors.Error        "Internal server error"
163
//        @Router                        /organizations/{address} [get]
164
func (a *API) organizationInfoHandler(w http.ResponseWriter, r *http.Request) {
10✔
165
        // get the organization info from the request context
10✔
166
        org, parent, ok := a.organizationFromRequest(r)
10✔
167
        if !ok {
10✔
168
                errors.ErrNoOrganizationProvided.Write(w)
×
169
                return
×
170
        }
×
171
        // send the organization back to the user
172
        apicommon.HTTPWriteJSON(w, apicommon.OrganizationFromDB(org, parent))
10✔
173
}
174

175
// updateOrganizationHandler godoc
176
//
177
//        @Summary                Update organization information
178
//        @Description        Update the information of an organization.
179
//        @Description        Only the admin of the organization can update the information.
180
//        @Description        Only certain fields can be updated, and they will be updated only if they are not empty.
181
//        @Tags                        organizations
182
//        @Accept                        json
183
//        @Produce                json
184
//        @Security                BearerAuth
185
//        @Param                        address        path                string                                                true        "Organization address"
186
//        @Param                        request        body                apicommon.OrganizationInfo        true        "Organization information to update"
187
//        @Success                200                {string}        string                                                "OK"
188
//        @Failure                400                {object}        errors.Error                                "Invalid input data"
189
//        @Failure                401                {object}        errors.Error                                "Unauthorized"
190
//        @Failure                404                {object}        errors.Error                                "Organization not found"
191
//        @Failure                500                {object}        errors.Error                                "Internal server error"
192
//        @Router                        /organizations/{address} [put]
193
func (a *API) updateOrganizationHandler(w http.ResponseWriter, r *http.Request) {
×
194
        // get the user from the request context
×
195
        user, ok := apicommon.UserFromContext(r.Context())
×
196
        if !ok {
×
197
                errors.ErrUnauthorized.Write(w)
×
198
                return
×
199
        }
×
200
        // get the organization info from the request context
201
        org, _, ok := a.organizationFromRequest(r)
×
202
        if !ok {
×
203
                errors.ErrNoOrganizationProvided.Write(w)
×
204
                return
×
205
        }
×
206
        if !user.HasRoleFor(org.Address, db.AdminRole) {
×
207
                errors.ErrUnauthorized.Withf("user is not admin of organization").Write(w)
×
208
                return
×
209
        }
×
210
        // get the organization info from the request body
211
        newOrgInfo := &apicommon.OrganizationInfo{}
×
212
        if err := json.NewDecoder(r.Body).Decode(newOrgInfo); err != nil {
×
213
                errors.ErrMalformedBody.Write(w)
×
214
                return
×
215
        }
×
216
        // update just the fields that can be updated and are not empty
217
        updateOrg := false
×
218
        if newOrgInfo.Website != "" {
×
219
                org.Website = newOrgInfo.Website
×
220
                updateOrg = true
×
221
        }
×
222
        if newOrgInfo.Size != "" {
×
223
                org.Size = newOrgInfo.Size
×
224
                updateOrg = true
×
225
        }
×
226
        if newOrgInfo.Color != "" {
×
227
                org.Color = newOrgInfo.Color
×
228
                updateOrg = true
×
229
        }
×
230
        if newOrgInfo.Subdomain != "" {
×
231
                org.Subdomain = newOrgInfo.Subdomain
×
232
                updateOrg = true
×
233
        }
×
234
        if newOrgInfo.Country != "" {
×
235
                org.Country = newOrgInfo.Country
×
236
                updateOrg = true
×
237
        }
×
238
        if newOrgInfo.Timezone != "" {
×
239
                org.Timezone = newOrgInfo.Timezone
×
240
                updateOrg = true
×
241
        }
×
242
        if newOrgInfo.Active != org.Active {
×
243
                org.Active = newOrgInfo.Active
×
244
                updateOrg = true
×
245
        }
×
246
        // update the organization if any field was changed
247
        if updateOrg {
×
248
                if err := a.db.SetOrganization(org); err != nil {
×
249
                        errors.ErrGenericInternalServerError.Withf("could not update organization: %v", err).Write(w)
×
250
                        return
×
251
                }
×
252
        }
253
        apicommon.HTTPWriteOK(w)
×
254
}
255

256
// organizationsTypesHandler godoc
257
//
258
//        @Summary                Get available organization types
259
//        @Description        Get the list of available organization types that can be assigned to an organization
260
//        @Tags                        organizations
261
//        @Accept                        json
262
//        @Produce                json
263
//        @Success                200        {object}        apicommon.OrganizationTypeList
264
//        @Router                        /organizations/types [get]
265
func (*API) organizationsTypesHandler(w http.ResponseWriter, _ *http.Request) {
×
266
        organizationTypes := []*apicommon.OrganizationType{}
×
267
        for orgType, name := range db.OrganizationTypesNames {
×
268
                organizationTypes = append(organizationTypes, &apicommon.OrganizationType{
×
269
                        Type: string(orgType),
×
270
                        Name: name,
×
271
                })
×
272
        }
×
273
        apicommon.HTTPWriteJSON(w, &apicommon.OrganizationTypeList{Types: organizationTypes})
×
274
}
275

276
// organizationSubscriptionHandler godoc
277
//
278
//        @Summary                Get organization subscription
279
//        @Description        Get the subscription information for an organization
280
//        @Tags                        organizations
281
//        @Accept                        json
282
//        @Produce                json
283
//        @Security                BearerAuth
284
//        @Param                        address        path                string        true        "Organization address"
285
//        @Success                200                {object}        apicommon.OrganizationSubscriptionInfo
286
//        @Failure                400                {object}        errors.Error        "Invalid input data"
287
//        @Failure                401                {object}        errors.Error        "Unauthorized"
288
//        @Failure                404                {object}        errors.Error        "Organization not found or no subscription"
289
//        @Failure                500                {object}        errors.Error        "Internal server error"
290
//        @Router                        /organizations/{address}/subscription [get]
291
func (a *API) organizationSubscriptionHandler(w http.ResponseWriter, r *http.Request) {
×
292
        // get the user from the request context
×
293
        user, ok := apicommon.UserFromContext(r.Context())
×
294
        if !ok {
×
295
                errors.ErrUnauthorized.Write(w)
×
296
                return
×
297
        }
×
298
        // get the organization info from the request context
299
        org, _, ok := a.organizationFromRequest(r)
×
300
        if !ok {
×
301
                errors.ErrNoOrganizationProvided.Write(w)
×
302
                return
×
303
        }
×
304
        if !user.HasRoleFor(org.Address, db.AdminRole) {
×
305
                errors.ErrUnauthorized.Withf("user is not admin of organization").Write(w)
×
306
                return
×
307
        }
×
308
        if org.Subscription == (db.OrganizationSubscription{}) {
×
309
                errors.ErrNoOrganizationSubscription.Write(w)
×
310
                return
×
311
        }
×
312
        // get the subscription from the database
313
        plan, err := a.db.Plan(org.Subscription.PlanID)
×
314
        if err != nil {
×
315
                errors.ErrGenericInternalServerError.Withf("could not get subscription: %v", err).Write(w)
×
316
                return
×
317
        }
×
318
        info := &apicommon.OrganizationSubscriptionInfo{
×
319
                SubcriptionDetails: apicommon.SubscriptionDetailsFromDB(&org.Subscription),
×
320
                Usage:              apicommon.SubscriptionUsageFromDB(&org.Counters),
×
321
                Plan:               apicommon.SubscriptionPlanFromDB(plan),
×
322
        }
×
323
        apicommon.HTTPWriteJSON(w, info)
×
324
}
325

326
// organizationCensusesHandler godoc
327
//
328
//        @Summary                Get organization censuses
329
//        @Description        Get the list of censuses for an organization
330
//        @Tags                        organizations
331
//        @Accept                        json
332
//        @Produce                json
333
//        @Security                BearerAuth
334
//        @Param                        address        path                string        true        "Organization address"
335
//        @Success                200                {object}        apicommon.OrganizationCensuses
336
//        @Failure                400                {object}        errors.Error        "Invalid input data"
337
//        @Failure                401                {object}        errors.Error        "Unauthorized"
338
//        @Failure                404                {object}        errors.Error        "Organization not found"
339
//        @Failure                500                {object}        errors.Error        "Internal server error"
340
//        @Router                        /organizations/{address}/censuses [get]
341
func (a *API) organizationCensusesHandler(w http.ResponseWriter, r *http.Request) {
×
342
        // get the user from the request context
×
343
        user, ok := apicommon.UserFromContext(r.Context())
×
344
        if !ok {
×
345
                errors.ErrUnauthorized.Write(w)
×
346
                return
×
347
        }
×
348
        // get the organization info from the request context
349
        org, _, ok := a.organizationFromRequest(r)
×
350
        if !ok {
×
351
                errors.ErrNoOrganizationProvided.Write(w)
×
352
                return
×
353
        }
×
354
        if !user.HasRoleFor(org.Address, db.AdminRole) {
×
355
                errors.ErrUnauthorized.Withf("user is not admin of organization").Write(w)
×
356
                return
×
357
        }
×
358
        // get the censuses from the database
359
        censuses, err := a.db.CensusesByOrg(org.Address)
×
360
        if err != nil {
×
361
                if err == db.ErrNotFound {
×
362
                        errors.ErrOrganizationNotFound.Write(w)
×
363
                        return
×
364
                }
×
365
                errors.ErrGenericInternalServerError.Withf("could not get censuses: %v", err).Write(w)
×
366
                return
×
367
        }
368
        // decode the censuses from the database
369
        result := apicommon.OrganizationCensuses{
×
370
                Censuses: []apicommon.OrganizationCensus{},
×
371
        }
×
372
        for _, census := range censuses {
×
373
                result.Censuses = append(result.Censuses, apicommon.OrganizationCensusFromDB(census))
×
374
        }
×
375
        apicommon.HTTPWriteJSON(w, result)
×
376
}
377

378
// organizationCreateTicket godoc
379
//
380
//        @Summary                Create a new ticket for an organization
381
//        @Description        Create a new ticket for an organization. The user must have some role in the organization (any role).
382
//        @Tags                        organizations
383
//        @Accept                        json
384
//        @Produce                json
385
//        @Security                BearerAuth
386
//        @Param                        address        path                string                                                                                true        "Organization address"
387
//        @Param                        request        body                apicommon.CreateOrganizationTicketRequest        true        "Ticket request information"
388
//        @Success                200                {string}        string                                                                                "OK"
389
//        @Failure                400                {object}        errors.Error                                                                "Invalid input data"
390
//        @Failure                401                {object}        errors.Error                                                                "Unauthorized"
391
//        @Failure                404                {object}        errors.Error                                                                "Organization not found"
392
//        @Failure                500                {object}        errors.Error                                                                "Internal server error"
393
//        @Router                        /organizations/{address}/ticket [post]
394
func (a *API) organizationCreateTicket(w http.ResponseWriter, r *http.Request) {
×
395
        // get the user from the request context
×
396
        user, ok := apicommon.UserFromContext(r.Context())
×
397
        if !ok {
×
398
                errors.ErrUnauthorized.Write(w)
×
399
                return
×
400
        }
×
401
        // get the organization info from the request context
402
        org, _, ok := a.organizationFromRequest(r)
×
403
        if !ok {
×
404
                errors.ErrNoOrganizationProvided.Write(w)
×
405
                return
×
406
        }
×
407
        // check if the new user already has a role in the organization
408
        if hasAnyRole, err := a.db.UserHasAnyRoleInOrg(user.Email, org.Address); err != nil {
×
409
                errors.ErrInvalidUserData.WithErr(err).Write(w)
×
410
                return
×
411
        } else if !hasAnyRole {
×
412
                errors.ErrUnauthorized.Withf("user has no role in the organization").Write(w)
×
413
                return
×
414
        }
×
415

416
        // get the ticket request from the request body
417
        ticketReq := &apicommon.CreateOrganizationTicketRequest{}
×
418
        if err := json.NewDecoder(r.Body).Decode(ticketReq); err != nil {
×
419
                errors.ErrMalformedBody.Write(w)
×
420
                return
×
421
        }
×
422
        // validate the ticket request
423
        if ticketReq.Title == "" || ticketReq.Description == "" {
×
424
                errors.ErrMalformedBody.With("title and description are required").Write(w)
×
425
                return
×
426
        }
×
427

428
        if !internal.ValidEmail(user.Email) {
×
429
                errors.ErrEmailMalformed.With("invalid user email address").Write(w)
×
430
                return
×
431
        }
×
432
        notification, err := mailtemplates.SupportNotification.ExecTemplate(
×
433
                struct {
×
434
                        Type         string
×
435
                        Organization common.Address
×
436
                        Title        string
×
437
                        Description  string
×
438
                        Email        string
×
439
                }{ticketReq.TicketType, org.Address, ticketReq.Title, ticketReq.Description, user.Email},
×
440
        )
×
441
        if err != nil {
×
442
                log.Warnw("could not execute support notification template", "error", err)
×
443
                errors.ErrGenericInternalServerError.Write(w)
×
444
                return
×
445
        }
×
446

447
        notification.ToAddress = apicommon.SupportEmail
×
448
        notification.ReplyTo = user.Email
×
449

×
450
        // send an email to the support destination
×
451
        if err := a.mail.SendNotification(r.Context(), notification); err != nil {
×
452
                log.Warnw("could not send ticket notification email", "error", err)
×
453
                errors.ErrGenericInternalServerError.Write(w)
×
454
                return
×
455
        }
×
456
        apicommon.HTTPWriteOK(w)
×
457
}
458

459
// organizationJobsHandler godoc
460
//
461
//        @Summary                Get organization jobs
462
//        @Description        Get the list of import jobs for an organization with pagination support
463
//        @Tags                        organizations
464
//        @Accept                        json
465
//        @Produce                json
466
//        @Security                BearerAuth
467
//        @Param                        address                path                string        true        "Organization address"
468
//        @Param                        page                query                integer        false        "Page number (default: 1)"
469
//        @Param                        pageSize        query                integer        false        "Number of items per page (default: 10)"
470
//        @Param                        type                query                string        false        "Filter by job type (org_members or census_participants)"
471
//        @Success                200                        {object}        apicommon.JobsResponse
472
//        @Failure                400                        {object}        errors.Error        "Invalid input"
473
//        @Failure                401                        {object}        errors.Error        "Unauthorized"
474
//        @Failure                404                        {object}        errors.Error        "Organization not found"
475
//        @Failure                500                        {object}        errors.Error        "Internal server error"
476
//        @Router                        /organizations/{address}/jobs [get]
477
func (a *API) organizationJobsHandler(w http.ResponseWriter, r *http.Request) {
15✔
478
        // get the user from the request context
15✔
479
        user, ok := apicommon.UserFromContext(r.Context())
15✔
480
        if !ok {
15✔
NEW
481
                errors.ErrUnauthorized.Write(w)
×
NEW
482
                return
×
NEW
483
        }
×
484
        // get the organization info from the request context
485
        org, _, ok := a.organizationFromRequest(r)
15✔
486
        if !ok {
17✔
487
                errors.ErrNoOrganizationProvided.Write(w)
2✔
488
                return
2✔
489
        }
2✔
490
        // check the user has the necessary permissions
491
        if !user.HasRoleFor(org.Address, db.ManagerRole) && !user.HasRoleFor(org.Address, db.AdminRole) {
13✔
NEW
492
                errors.ErrUnauthorized.Withf("user is not admin or manager of organization").Write(w)
×
NEW
493
                return
×
NEW
494
        }
×
495

496
        // Parse pagination parameters from query string
497
        page := 1      // Default page number
13✔
498
        pageSize := 10 // Default page size
13✔
499

13✔
500
        if pageStr := r.URL.Query().Get("page"); pageStr != "" {
17✔
501
                if pageVal, err := strconv.Atoi(pageStr); err == nil && pageVal > 0 {
8✔
502
                        page = pageVal
4✔
503
                }
4✔
504
        }
505

506
        if pageSizeStr := r.URL.Query().Get("pageSize"); pageSizeStr != "" {
17✔
507
                if pageSizeVal, err := strconv.Atoi(pageSizeStr); err == nil && pageSizeVal > 0 && pageSizeVal <= 100 {
8✔
508
                        pageSize = pageSizeVal
4✔
509
                }
4✔
510
        }
511

512
        // Parse job type filter
513
        var jobType *db.JobType
13✔
514
        if typeStr := r.URL.Query().Get("type"); typeStr != "" {
19✔
515
                switch typeStr {
6✔
516
                case string(db.JobTypeOrgMembers):
2✔
517
                        t := db.JobTypeOrgMembers
2✔
518
                        jobType = &t
2✔
519
                case string(db.JobTypeCensusParticipants):
2✔
520
                        t := db.JobTypeCensusParticipants
2✔
521
                        jobType = &t
2✔
522
                default:
2✔
523
                        errors.ErrMalformedURLParam.Withf("invalid job type: %s", typeStr).Write(w)
2✔
524
                        return
2✔
525
                }
526
        }
527

528
        // retrieve the jobs with pagination
529
        totalPages, jobs, err := a.db.Jobs(org.Address, page, pageSize, jobType)
11✔
530
        if err != nil {
11✔
NEW
531
                errors.ErrGenericInternalServerError.Withf("could not get jobs: %v", err).Write(w)
×
NEW
532
                return
×
NEW
533
        }
×
534

535
        // convert the jobs to the response format
536
        jobsResponse := make([]apicommon.JobInfo, 0, len(jobs))
11✔
537
        for _, job := range jobs {
21✔
538
                jobsResponse = append(jobsResponse, apicommon.JobFromDB(&job))
10✔
539
        }
10✔
540

541
        apicommon.HTTPWriteJSON(w, &apicommon.JobsResponse{
11✔
542
                TotalPages:  totalPages,
11✔
543
                CurrentPage: page,
11✔
544
                Jobs:        jobsResponse,
11✔
545
        })
11✔
546
}
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