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

vocdoni / saas-backend / 28178332752

25 Jun 2026 02:42PM UTC coverage: 62.083% (+0.1%) from 61.957%
28178332752

push

github

web-flow
feat(api)!: address processes by Mongo ObjectID in status/results/metadata (#551)

* feat(api)!: address processes by Mongo ObjectID in status/results/metadata

PUT /process/{processId}/status, GET /process/{processId}/results and
GET /process/{processId}/metadata parsed {processId} as the 64-hex on-chain
election id, while GET/PUT/DELETE /process/{processId} and .../publish parsed
it as the 24-hex Mongo ObjectID. The same path segment meant two different
identifiers, so e.g. GET /process/{onchainID} returned "invalid process ID".

Make all three look the process up by its ObjectID (a.db.Process) and use the
stored process.Address for the Vochain call, matching the rest of the process
API. results/status now require the process to be published (it has an Address).

The 64-hex on-chain id stays only where a voter needs it to sign/verify a vote:
POST /process/{processId}/vote, POST /process/{processId}/sign-info, and the
bundle sign/check bodies.

BREAKING CHANGE: status, results and metadata now take the 24-hex Mongo
ObjectID in {processId}, not the 64-hex on-chain election id.

* feat(api): accept Mongo ObjectID for process sign-info (keep on-chain id)

POST /process/{processId}/sign-info now resolves {processId} as the 24-hex Mongo
ObjectID (a.mainDB.Process) and uses the looked-up process.Address for the CSP
lookup and nullifier - consistent with status/results/metadata.

Exceptionally, and unlike those three, it ALSO still accepts the 64-hex on-chain
election id directly, to avoid breaking voter clients that already hold it (a
valid ObjectID is exactly 24 hex chars, so it never collides with the 64-hex id).

TestProcessReadProxies now asserts both forms return the same consumed address
and nullifier.

32 of 43 new or added lines in 3 files covered. (74.42%)

9816 of 15811 relevant lines covered (62.08%)

44.63 hits per line

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

52.24
/api/process_read.go
1
package api
2

3
import (
4
        "net/http"
5

6
        "github.com/go-chi/chi/v5"
7
        "github.com/vocdoni/saas-backend/account"
8
        "github.com/vocdoni/saas-backend/api/apicommon"
9
        "github.com/vocdoni/saas-backend/db"
10
        "github.com/vocdoni/saas-backend/errors"
11
        "go.mongodb.org/mongo-driver/bson/primitive"
12
        "go.vocdoni.io/dvote/log"
13
)
14

15
// processResultsHandler godoc
16
//
17
//        @Summary                Get the trimmed on-chain election results
18
//        @Description        Fetches the current on-chain state of the election identified by its process id
19
//        @Description        and returns a trimmed view of it: status, vote count, start/end dates, whether the
20
//        @Description        results are final and the tally (if any). Public endpoint: no authentication is
21
//        @Description        required.
22
//        @Tags                        process
23
//        @Produce                json
24
//        @Param                        processId        path                string                                                                true        "24-hex ProcessID"
25
//        @Success                200                        {object}        apicommon.ProcessResultsResponse        "Trimmed on-chain election state"
26
//        @Failure                400                        {object}        errors.Error                                                "Invalid input data"
27
//        @Failure                404                        {object}        errors.Error                                                "Process not found"
28
//        @Failure                500                        {object}        errors.Error                                                "Internal server error"
29
//        @Router                        /process/{processId}/results [get]
30
func (a *API) processResultsHandler(w http.ResponseWriter, r *http.Request) {
2✔
31
        objID, err := primitive.ObjectIDFromHex(chi.URLParam(r, "processId"))
2✔
32
        if err != nil {
2✔
33
                errors.ErrMalformedURLParam.Withf("invalid process id").Write(w)
×
34
                return
×
35
        }
×
36

37
        process, err := a.db.Process(objID)
2✔
38
        if err != nil {
2✔
39
                if err == db.ErrNotFound {
×
40
                        errors.ErrProcessNotFound.Write(w)
×
41
                        return
×
42
                }
×
43
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
44
                return
×
45
        }
46
        // results only exist once the process has been published on chain.
47
        if len(process.Address) == 0 {
2✔
NEW
48
                errors.ErrProcessNotFound.Withf("process not published").Write(w)
×
NEW
49
                return
×
NEW
50
        }
×
51

52
        election, err := a.account.Election(process.Address.Bytes())
2✔
53
        if err != nil {
2✔
54
                errors.ErrVochainRequestFailed.WithErr(err).Write(w)
×
55
                return
×
56
        }
×
57

58
        resp := apicommon.ProcessResultsResponse{
2✔
59
                Status:       election.Status,
2✔
60
                VoteCount:    election.VoteCount,
2✔
61
                StartDate:    election.StartDate,
2✔
62
                EndDate:      election.EndDate,
2✔
63
                FinalResults: election.FinalResults,
2✔
64
        }
2✔
65
        if len(election.Results) > 0 {
4✔
66
                results := make([][]string, len(election.Results))
2✔
67
                for i, question := range election.Results {
4✔
68
                        values := make([]string, len(question))
2✔
69
                        for j, value := range question {
10✔
70
                                values[j] = value.String()
8✔
71
                        }
8✔
72
                        results[i] = values
2✔
73
                }
74
                resp.Results = results
2✔
75
        }
76

77
        apicommon.HTTPWriteJSON(w, &resp)
2✔
78
}
79

80
// processMetadataHandler godoc
81
//
82
//        @Summary                Get the election metadata JSON
83
//        @Description        Rebuilds and returns the raw ElectionMetadata JSON document of the process
84
//        @Description        identified by its process id. The metadata is deterministically derived
85
//        @Description        from the stored ElectionParams, so it is identical to the content-addressed document
86
//        @Description        published on chain. Public endpoint: no authentication is required.
87
//        @Tags                        process
88
//        @Produce                json
89
//        @Param                        processId        path                string                        true        "24-hex ProcessID"
90
//        @Success                200                        {object}        object                        "Election metadata JSON"
91
//        @Failure                400                        {object}        errors.Error        "Invalid input data"
92
//        @Failure                404                        {object}        errors.Error        "Process not found"
93
//        @Failure                500                        {object}        errors.Error        "Internal server error"
94
//        @Router                        /process/{processId}/metadata [get]
95
func (a *API) processMetadataHandler(w http.ResponseWriter, r *http.Request) {
1✔
96
        objID, err := primitive.ObjectIDFromHex(chi.URLParam(r, "processId"))
1✔
97
        if err != nil {
1✔
98
                errors.ErrMalformedURLParam.Withf("invalid process id").Write(w)
×
99
                return
×
100
        }
×
101

102
        process, err := a.db.Process(objID)
1✔
103
        if err != nil {
1✔
104
                if err == db.ErrNotFound {
×
105
                        errors.ErrProcessNotFound.Write(w)
×
106
                        return
×
107
                }
×
108
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
109
                return
×
110
        }
111
        if process.ElectionParams == nil {
1✔
112
                errors.ErrProcessNotFound.Withf("process has no metadata").Write(w)
×
113
                return
×
114
        }
×
115

116
        metaBytes, err := account.BuildElectionMetadata(process.ElectionParams)
1✔
117
        if err != nil {
1✔
118
                errors.ErrGenericInternalServerError.WithErr(err).Write(w)
×
119
                return
×
120
        }
×
121

122
        w.Header().Set("Content-Type", "application/json")
1✔
123
        if _, err := w.Write(metaBytes); err != nil {
1✔
124
                log.Warnw("failed to write metadata response", "error", err)
×
125
        }
×
126
}
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