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

vocdoni / saas-backend / 16522415145

25 Jul 2025 12:49PM UTC coverage: 57.876% (+0.06%) from 57.815%
16522415145

Pull #198

github

emmdim
db: Fixes bug in calculating duplicate members during data validation
Pull Request #198: all: removes censusType from CreateCensus requests. Updates setCensus…

34 of 41 new or added lines in 4 files covered. (82.93%)

4 existing lines in 1 file now uncovered.

5236 of 9047 relevant lines covered (57.88%)

26.85 hits per line

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

76.69
/db/census.go
1
// Package db provides database operations for the Vocdoni SaaS backend,
2
// handling storage and retrieval of censuses, organizations, users, and
3
// other data structures required for the voting platform.
4
package db
5

6
import (
7
        "context"
8
        "fmt"
9
        "time"
10

11
        "github.com/ethereum/go-ethereum/common"
12
        "github.com/vocdoni/saas-backend/internal"
13
        "go.mongodb.org/mongo-driver/bson"
14
        "go.mongodb.org/mongo-driver/bson/primitive"
15
        "go.mongodb.org/mongo-driver/mongo/options"
16
        "go.vocdoni.io/dvote/log"
17
)
18

19
// SetCensus creates a new census for an organization
20
// Returns the hex representation of the census
21
func (ms *MongoStorage) SetCensus(census *Census) (string, error) {
29✔
22
        // create a context with a timeout
29✔
23
        ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
29✔
24
        defer cancel()
29✔
25

29✔
26
        if census.OrgAddress.Cmp(common.Address{}) == 0 {
30✔
27
                return "", ErrInvalidData
1✔
28
        }
1✔
29
        // check that the org exists
30
        _, err := ms.Organization(census.OrgAddress)
28✔
31
        if err != nil {
30✔
32
                if err == ErrNotFound {
4✔
33
                        return "", ErrInvalidData
2✔
34
                }
2✔
35
                return "", fmt.Errorf("organization not found: %w", err)
×
36
        }
37

38
        if census.ID != primitive.NilObjectID {
30✔
39
                // if the census exists, update it with the new data
4✔
40
                census.UpdatedAt = time.Now()
4✔
41
        } else {
26✔
42
                // if the census doesn't exist, create its id
22✔
43
                census.ID = primitive.NewObjectID()
22✔
44
                census.CreatedAt = time.Now()
22✔
45
        }
22✔
46
        census.Type = census.TwoFaFields.GetCensusType()
26✔
47

26✔
48
        updateDoc, err := dynamicUpdateDocument(census, nil)
26✔
49
        if err != nil {
26✔
50
                return "", err
×
51
        }
×
52
        ms.keysLock.Lock()
26✔
53
        defer ms.keysLock.Unlock()
26✔
54
        filter := bson.M{"_id": census.ID}
26✔
55
        opts := options.Update().SetUpsert(true)
26✔
56
        _, err = ms.censuses.UpdateOne(ctx, filter, updateDoc, opts)
26✔
57
        if err != nil {
26✔
58
                return "", err
×
59
        }
×
60

61
        return census.ID.Hex(), nil
26✔
62
}
63

64
// SetPublished census updates the PublishedCensus field of a census
65
func (ms *MongoStorage) SetPublishedCensus(censusID, uri string, root internal.HexBytes) (string, error) {
×
66
        if len(censusID) == 0 || len(uri) == 0 || len(root) == 0 {
×
67
                return "", ErrInvalidData
×
68
        }
×
69

70
        censusOID, err := primitive.ObjectIDFromHex(censusID)
×
71
        if err != nil {
×
72
                return "", ErrInvalidData
×
73
        }
×
74
        census := &Census{
×
75
                ID: censusOID,
×
76
                Published: PublishedCensus{
×
77
                        Root: root,
×
78
                        URI:  uri,
×
79
                },
×
80
        }
×
81

×
82
        return ms.SetCensus(census)
×
83
}
84

85
// SetGroupCensus creates a new census for an organization
86
// Returns the hex representation of the census
87
func (ms *MongoStorage) SetGroupCensus(
88
        census *Census,
89
        groupID string,
90
        participantIDs []primitive.ObjectID,
91
) (string, error) {
12✔
92
        if groupID == "" {
13✔
93
                return ms.SetCensus(census)
1✔
94
        }
1✔
95

96
        // create a context with a timeout
97
        ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
11✔
98
        defer cancel()
11✔
99

11✔
100
        if census.OrgAddress.Cmp(common.Address{}) == 0 {
12✔
101
                return "", ErrInvalidData
1✔
102
        }
1✔
103

104
        ms.keysLock.Lock()
10✔
105
        defer ms.keysLock.Unlock()
10✔
106
        // check that the org exists
10✔
107
        _, err := ms.Organization(census.OrgAddress)
10✔
108
        if err != nil {
11✔
109
                if err == ErrNotFound {
2✔
110
                        return "", ErrInvalidData
1✔
111
                }
1✔
112
                return "", fmt.Errorf("error retrieving organization: %w", err)
×
113
        }
114

115
        // check that the group exists
116
        group, err := ms.OrganizationMemberGroup(groupID, census.OrgAddress)
9✔
117
        if err != nil {
12✔
118
                if err == ErrNotFound {
5✔
119
                        return "", ErrInvalidData
2✔
120
                }
2✔
121
                return "", fmt.Errorf("error retrieving organization group: %w", err)
1✔
122
        }
123
        census.GroupID = group.ID
6✔
124

6✔
125
        if census.ID != primitive.NilObjectID {
7✔
126
                // if the census exists, update it with the new data
1✔
127
                census.UpdatedAt = time.Now()
1✔
128
        } else {
6✔
129
                // if the census doesn't exist, create its id
5✔
130
                census.ID = primitive.NewObjectID()
5✔
131
                census.CreatedAt = time.Now()
5✔
132
        }
5✔
133
        census.Type = census.TwoFaFields.GetCensusType()
6✔
134

6✔
135
        updateDoc, err := dynamicUpdateDocument(census, nil)
6✔
136
        if err != nil {
6✔
137
                return "", err
×
138
        }
×
139
        filter := bson.M{"_id": census.ID}
6✔
140
        opts := options.Update().SetUpsert(true)
6✔
141
        _, err = ms.censuses.UpdateOne(ctx, filter, updateDoc, opts)
6✔
142
        if err != nil {
6✔
143
                return "", err
×
144
        }
×
145

146
        // update the group with the census ID
147
        if err := ms.addOrganizationMemberGroupCensus(ctx, group.ID.Hex(), census.OrgAddress, census.ID.Hex()); err != nil {
6✔
NEW
148
                return "", fmt.Errorf("error updating group with census ID: %w", err)
×
NEW
149
        }
×
150

151
        // set the participants for the census
152
        if len(participantIDs) > 0 {
10✔
153
                if err := ms.setBulkCensusParticipant(ctx, census.ID.Hex(), participantIDs); err != nil {
4✔
154
                        return "", fmt.Errorf("error setting census participants: %w", err)
×
155
                }
×
156
        }
157
        return census.ID.Hex(), nil
6✔
158
}
159

160
// DeleteCensus removes a census and all its members
161
func (ms *MongoStorage) DelCensus(censusID string) error {
4✔
162
        objID, err := primitive.ObjectIDFromHex(censusID)
4✔
163
        if err != nil {
6✔
164
                return ErrInvalidData
2✔
165
        }
2✔
166

167
        ms.keysLock.Lock()
2✔
168
        defer ms.keysLock.Unlock()
2✔
169
        // create a context with a timeout
2✔
170
        ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
2✔
171
        defer cancel()
2✔
172

2✔
173
        // delete the census from the database using the ID
2✔
174
        filter := bson.M{"_id": objID}
2✔
175
        _, err = ms.censuses.DeleteOne(ctx, filter)
2✔
176
        return err
2✔
177
}
178

179
// Census retrieves a census from the DB based on its ID
180
func (ms *MongoStorage) Census(censusID string) (*Census, error) {
76✔
181
        objID, err := primitive.ObjectIDFromHex(censusID)
76✔
182
        if err != nil {
78✔
183
                return nil, ErrInvalidData
2✔
184
        }
2✔
185

186
        // create a context with a timeout
187
        ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
74✔
188
        defer cancel()
74✔
189

74✔
190
        census := &Census{}
74✔
191
        err = ms.censuses.FindOne(ctx, bson.M{"_id": objID}).Decode(census)
74✔
192
        if err != nil {
78✔
193
                return nil, fmt.Errorf("failed to get census: %w", err)
4✔
194
        }
4✔
195

196
        return census, nil
70✔
197
}
198

199
// CensusesByOrg retrieves all the censuses for an organization based on its
200
// address. It checks that the organization exists and returns an error if it
201
// doesn't. If the organization exists, it returns the censuses.
202
func (ms *MongoStorage) CensusesByOrg(orgAddress common.Address) ([]*Census, error) {
6✔
203
        ms.keysLock.RLock()
6✔
204
        defer ms.keysLock.RUnlock()
6✔
205
        // create a context with a timeout
6✔
206
        ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
6✔
207
        defer cancel()
6✔
208

6✔
209
        if _, err := ms.fetchOrganizationFromDB(ctx, orgAddress); err != nil {
8✔
210
                if err == ErrNotFound {
4✔
211
                        return nil, ErrInvalidData
2✔
212
                }
2✔
213
                return nil, fmt.Errorf("organization not found: %w", err)
×
214
        }
215
        // find the censuses in the database
216
        censuses := []*Census{}
4✔
217
        cursor, err := ms.censuses.Find(ctx, bson.M{"orgAddress": orgAddress})
4✔
218
        if err != nil {
4✔
219
                return nil, err
×
220
        }
×
221
        defer func() {
8✔
222
                if err := cursor.Close(ctx); err != nil {
4✔
223
                        log.Warnw("error closing cursor", "error", err)
×
224
                }
×
225
        }()
226
        if err := cursor.All(ctx, &censuses); err != nil {
4✔
227
                return nil, err
×
228
        }
×
229
        return censuses, nil
4✔
230
}
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