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

mendersoftware / mender-server / 1622978334

13 Jan 2025 03:51PM UTC coverage: 72.802% (-3.8%) from 76.608%
1622978334

Pull #300

gitlab-ci

alfrunes
fix: Deployment device count should not exceed max devices

Added a condition to skip deployments when the device count reaches max
devices.

Changelog: Title
Ticket: MEN-7847
Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #300: fix: Deployment device count should not exceed max devices

4251 of 6164 branches covered (68.96%)

Branch coverage included in aggregate %.

0 of 18 new or added lines in 1 file covered. (0.0%)

2544 existing lines in 83 files now uncovered.

42741 of 58384 relevant lines covered (73.21%)

21.49 hits per line

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

85.71
/backend/services/useradm/store/mongo/datastore_mongo.go
1
// Copyright 2023 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
package mongo
16

17
import (
18
        "context"
19
        "crypto/tls"
20
        "strings"
21
        "time"
22

23
        "github.com/pkg/errors"
24
        "go.mongodb.org/mongo-driver/bson"
25
        "go.mongodb.org/mongo-driver/mongo"
26
        mopts "go.mongodb.org/mongo-driver/mongo/options"
27
        "golang.org/x/crypto/bcrypt"
28

29
        "github.com/mendersoftware/mender-server/pkg/mongo/codec"
30
        "github.com/mendersoftware/mender-server/pkg/mongo/oid"
31
        mstore "github.com/mendersoftware/mender-server/pkg/store/v2"
32

33
        "github.com/mendersoftware/mender-server/services/useradm/jwt"
34
        "github.com/mendersoftware/mender-server/services/useradm/model"
35
        "github.com/mendersoftware/mender-server/services/useradm/store"
36
)
37

38
const (
39
        DbUsersColl        = "users"
40
        DbTokensColl       = "tokens"
41
        DbSettingsColl     = "settings"
42
        DbUserSettingsColl = "user_settings"
43

44
        DbUserEmail       = "email"
45
        DbUserPass        = "password"
46
        DbUserLoginTs     = "login_ts"
47
        DbTokenSubject    = "sub"
48
        DbTokenExpiresAt  = "exp"
49
        DbTokenExpireTime = "exp.time"
50
        DbTokenIssuedAt   = "iat"
51
        DbTokenTenant     = "tenant"
52
        DbTokenUser       = "user"
53
        DbTokenIssuer     = "iss"
54
        DbTokenScope      = "scp"
55
        DbTokenAudience   = "aud"
56
        DbTokenNotBefore  = "nbf"
57
        DbTokenLastUsed   = "last_used"
58
        DbTokenName       = "name"
59
        DbID              = "_id"
60

61
        DbTokenIssuedAtTime = DbTokenIssuedAt + ".time"
62

63
        DbUniqueEmailIndexName           = "email_1"
64
        DbUniqueTokenNameIndexName       = "token_name_1"
65
        DbTokenSubjectIndexName          = "token_subject_1"
66
        brokenDbTokenExpirationIndexName = "token_expiration"
67

68
        DbTenantUniqueTokenNameIndexName = "tenant_1_subject_1_name_1"
69
        DbTenantTokenSubjectIndexName    = "tenant_1_subject_1"
70

71
        DbSettingsEtag            = "etag"
72
        DbSettingsTenantIndexName = "tenant"
73
        DbSettingsUserID          = "user_id"
74
)
75

76
type DataStoreMongoConfig struct {
77
        // MGO connection string
78
        ConnectionString string
79

80
        // SSL support
81
        SSL           bool
82
        SSLSkipVerify bool
83

84
        // Overwrites credentials provided in connection string if provided
85
        Username string
86
        Password string
87
}
88

89
type DataStoreMongo struct {
90
        client      *mongo.Client
91
        automigrate bool
92
        multitenant bool
93
}
94

95
func GetDataStoreMongo(config DataStoreMongoConfig) (*DataStoreMongo, error) {
2✔
96
        d, err := NewDataStoreMongo(config)
2✔
97
        if err != nil {
2✔
98
                return nil, errors.Wrap(err, "database connection failed")
×
99
        }
×
100
        return d, nil
2✔
101
}
102

103
func NewDataStoreMongoWithClient(client *mongo.Client) (*DataStoreMongo, error) {
3✔
104

3✔
105
        db := &DataStoreMongo{
3✔
106
                client: client,
3✔
107
        }
3✔
108

3✔
109
        return db, nil
3✔
110
}
3✔
111

112
func NewDataStoreMongo(config DataStoreMongoConfig) (*DataStoreMongo, error) {
2✔
113
        var err error
2✔
114
        var mongoURL string
2✔
115

2✔
116
        clientOptions := mopts.Client().
2✔
117
                SetRegistry(codec.NewRegistry())
2✔
118
        if !strings.Contains(config.ConnectionString, "://") {
2✔
119
                mongoURL = "mongodb://" + config.ConnectionString
×
120
        } else {
2✔
121
                mongoURL = config.ConnectionString
2✔
122

2✔
123
        }
2✔
124
        clientOptions.ApplyURI(mongoURL)
2✔
125

2✔
126
        if config.Username != "" {
2✔
127
                credentials := mopts.Credential{
×
128
                        Username: config.Username,
×
129
                }
×
130
                if config.Password != "" {
×
131
                        credentials.Password = config.Password
×
132
                        credentials.PasswordSet = true
×
133
                }
×
134
                clientOptions.SetAuth(credentials)
×
135
        }
136

137
        if config.SSL {
2✔
138
                tlsConfig := &tls.Config{
×
139
                        InsecureSkipVerify: config.SSLSkipVerify,
×
140
                }
×
141
                clientOptions.SetTLSConfig(tlsConfig)
×
142
        }
×
143

144
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
2✔
145
        defer cancel()
2✔
146
        c, err := mongo.Connect(ctx, clientOptions)
2✔
147
        if err != nil {
2✔
148
                return nil, err
×
149
        }
×
150

151
        // Validate connection
152
        if err = c.Ping(ctx, nil); err != nil {
2✔
153
                return nil, err
×
154
        }
×
155

156
        db, err := NewDataStoreMongoWithClient(c)
2✔
157
        if err != nil {
2✔
158
                return nil, err
×
159
        }
×
160

161
        return db, nil
2✔
162
}
163

164
func (db *DataStoreMongo) Ping(ctx context.Context) error {
1✔
165
        res := db.client.Database(DbName).RunCommand(ctx, bson.M{"ping": 1})
1✔
166
        return res.Err()
1✔
167
}
1✔
168

169
func (db *DataStoreMongo) CreateUser(ctx context.Context, u *model.User) error {
3✔
170
        now := time.Now().UTC()
3✔
171

3✔
172
        u.CreatedTs = &now
3✔
173
        u.UpdatedTs = &now
3✔
174

3✔
175
        _, err := db.client.
3✔
176
                Database(mstore.DbFromContext(ctx, DbName)).
3✔
177
                Collection(DbUsersColl).
3✔
178
                InsertOne(ctx, mstore.WithTenantID(ctx, u))
3✔
179

3✔
180
        if err != nil {
4✔
181
                if strings.Contains(err.Error(), "duplicate key error") {
2✔
182
                        return store.ErrDuplicateEmail
1✔
183
                }
1✔
184

185
                return errors.Wrap(err, "failed to insert user")
×
186
        }
187

188
        return nil
3✔
189
}
190

191
func isDuplicateKeyError(err error) bool {
2✔
192
        const errCodeDupKey = 11000
2✔
193
        switch errType := err.(type) {
2✔
UNCOV
194
        case mongo.WriteException:
×
UNCOV
195
                if len(errType.WriteErrors) > 0 {
×
UNCOV
196
                        for _, we := range errType.WriteErrors {
×
UNCOV
197
                                if we.Code == errCodeDupKey {
×
UNCOV
198
                                        return true
×
UNCOV
199
                                }
×
200
                        }
201
                }
202
        case mongo.CommandError:
1✔
203
                if errType.Code == errCodeDupKey {
2✔
204
                        return true
1✔
205
                }
1✔
206
        }
207
        return false
2✔
208
}
209

210
// UpdateUser updates the user of a given ID.
211
// NOTE: This function supports using ETag matching using UserUpdate,
212
// but defaults to wildcard matching
213
func (db *DataStoreMongo) UpdateUser(
214
        ctx context.Context,
215
        id string,
216
        u *model.UserUpdate,
217
) (*model.User, error) {
2✔
218
        var updatedUser = new(model.User)
2✔
219
        //compute/set password hash
2✔
220
        if u.Password != "" {
4✔
221
                hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
2✔
222
                if err != nil {
2✔
223
                        return nil, errors.Wrap(err,
×
224
                                "failed to generate password hash")
×
225
                }
×
226
                u.Password = string(hash)
2✔
227
        }
228

229
        now := time.Now().UTC()
2✔
230
        u.UpdatedTs = &now
2✔
231

2✔
232
        collUsers := db.client.
2✔
233
                Database(mstore.DbFromContext(ctx, DbName)).
2✔
234
                Collection(DbUsersColl)
2✔
235

2✔
236
        f := bson.M{"_id": id}
2✔
237
        if u.ETag != nil {
3✔
238
                if *u.ETag == model.ETagNil {
1✔
239
                        f["etag"] = bson.D{{Key: "$exists", Value: false}}
×
240
                } else {
1✔
241
                        f["etag"] = *u.ETag
1✔
242
                }
1✔
243
                if u.ETagUpdate == nil {
1✔
244
                        next := *u.ETag
×
245
                        next.Increment()
×
246
                        u.ETagUpdate = &next
×
247
                }
×
248
        }
249
        up := bson.M{"$set": u}
2✔
250
        fuOpts := mopts.FindOneAndUpdate().
2✔
251
                SetReturnDocument(mopts.Before)
2✔
252
        err := collUsers.FindOneAndUpdate(ctx, mstore.WithTenantID(ctx, f), up, fuOpts).
2✔
253
                Decode(updatedUser)
2✔
254

2✔
255
        switch {
2✔
256
        case err == mongo.ErrNoDocuments:
1✔
257
                return nil, store.ErrUserNotFound
1✔
258
        case isDuplicateKeyError(err):
1✔
259
                return nil, store.ErrDuplicateEmail
1✔
260
        case err != nil:
×
261
                return nil, errors.Wrap(err, "store: failed to update user")
×
262
        }
263

264
        return updatedUser, nil
2✔
265
}
266

267
func (db *DataStoreMongo) UpdateLoginTs(ctx context.Context, id string) error {
1✔
268
        collUsrs := db.client.
1✔
269
                Database(mstore.DbFromContext(ctx, DbName)).
1✔
270
                Collection(DbUsersColl)
1✔
271

1✔
272
        _, err := collUsrs.UpdateOne(ctx,
1✔
273
                mstore.WithTenantID(ctx, bson.D{{Key: "_id", Value: id}}),
1✔
274
                bson.D{{Key: "$set", Value: bson.D{
1✔
275
                        {Key: DbUserLoginTs, Value: time.Now()}},
1✔
276
                }},
1✔
277
        )
1✔
278
        return err
1✔
279
}
1✔
280

281
func (db *DataStoreMongo) GetUserByEmail(
282
        ctx context.Context,
283
        email model.Email,
284
) (*model.User, error) {
2✔
285
        var user model.User
2✔
286

2✔
287
        err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
2✔
288
                Collection(DbUsersColl).
2✔
289
                FindOne(ctx, mstore.WithTenantID(ctx, bson.M{DbUserEmail: email})).
2✔
290
                Decode(&user)
2✔
291

2✔
292
        if err != nil {
3✔
293
                if err == mongo.ErrNoDocuments {
2✔
294
                        return nil, nil
1✔
295
                } else {
1✔
296
                        return nil, errors.Wrap(err, "failed to fetch user")
×
297
                }
×
298
        }
299

300
        return &user, nil
2✔
301
}
302

303
func (db *DataStoreMongo) GetUserById(ctx context.Context, id string) (*model.User, error) {
1✔
304
        user, err := db.GetUserAndPasswordById(ctx, id)
1✔
305
        if user != nil {
2✔
306
                user.Password = ""
1✔
307
        }
1✔
308
        return user, err
1✔
309
}
310

311
func (db *DataStoreMongo) GetUserAndPasswordById(
312
        ctx context.Context,
313
        id string,
314
) (*model.User, error) {
1✔
315
        var user model.User
1✔
316

1✔
317
        err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
1✔
318
                Collection(DbUsersColl).
1✔
319
                FindOne(ctx, mstore.WithTenantID(ctx, bson.M{"_id": id})).
1✔
320
                Decode(&user)
1✔
321

1✔
322
        if err != nil {
2✔
323
                if err == mongo.ErrNoDocuments {
2✔
324
                        return nil, nil
1✔
325
                } else {
1✔
326
                        return nil, errors.Wrap(err, "failed to fetch user")
×
327
                }
×
328
        }
329

330
        return &user, nil
1✔
331
}
332

333
func (db *DataStoreMongo) GetTokenById(ctx context.Context, id oid.ObjectID) (*jwt.Token, error) {
1✔
334
        var token jwt.Token
1✔
335

1✔
336
        err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
1✔
337
                Collection(DbTokensColl).
1✔
338
                FindOne(ctx, mstore.WithTenantID(ctx, bson.M{"_id": id})).
1✔
339
                Decode(&token)
1✔
340

1✔
341
        if err != nil {
2✔
342
                if err == mongo.ErrNoDocuments {
2✔
343
                        return nil, nil
1✔
344
                } else {
1✔
345
                        return nil, errors.Wrap(err, "failed to fetch token")
×
346
                }
×
347
        }
348

349
        return &token, nil
1✔
350
}
351

352
func (db *DataStoreMongo) GetUsers(
353
        ctx context.Context,
354
        fltr model.UserFilter,
355
) ([]model.User, error) {
1✔
356
        findOpts := mopts.Find().
1✔
357
                SetProjection(bson.M{DbUserPass: 0})
1✔
358

1✔
359
        collUsers := db.client.
1✔
360
                Database(mstore.DbFromContext(ctx, DbName)).
1✔
361
                Collection(DbUsersColl)
1✔
362

1✔
363
        var mgoFltr = bson.D{}
1✔
364
        if fltr.ID != nil {
2✔
365
                mgoFltr = append(mgoFltr, bson.E{Key: "_id", Value: bson.D{{
1✔
366
                        Key: "$in", Value: fltr.ID,
1✔
367
                }}})
1✔
368
        }
1✔
369
        if fltr.Email != nil {
2✔
370
                mgoFltr = append(mgoFltr, bson.E{Key: "email", Value: bson.D{{
1✔
371
                        Key: "$in", Value: fltr.Email,
1✔
372
                }}})
1✔
373
        }
1✔
374
        if fltr.CreatedAfter != nil {
2✔
375
                mgoFltr = append(mgoFltr, bson.E{
1✔
376
                        Key: "created_ts", Value: bson.D{{
1✔
377
                                Key: "$gt", Value: *fltr.CreatedAfter,
1✔
378
                        }},
1✔
379
                })
1✔
380
        }
1✔
381
        if fltr.CreatedBefore != nil {
2✔
382
                mgoFltr = append(mgoFltr, bson.E{
1✔
383
                        Key: "created_ts", Value: bson.D{{
1✔
384
                                Key: "$lt", Value: *fltr.CreatedBefore,
1✔
385
                        }},
1✔
386
                })
1✔
387
        }
1✔
388
        if fltr.UpdatedAfter != nil {
2✔
389
                mgoFltr = append(mgoFltr, bson.E{
1✔
390
                        Key: "updated_ts", Value: bson.D{{
1✔
391
                                Key: "$gt", Value: *fltr.UpdatedAfter,
1✔
392
                        }},
1✔
393
                })
1✔
394
        }
1✔
395
        if fltr.UpdatedBefore != nil {
2✔
396
                mgoFltr = append(mgoFltr, bson.E{
1✔
397
                        Key: "updated_ts", Value: bson.D{{
1✔
398
                                Key: "$lt", Value: *fltr.UpdatedBefore,
1✔
399
                        }},
1✔
400
                })
1✔
401
        }
1✔
402
        cur, err := collUsers.Find(ctx, mstore.WithTenantID(ctx, mgoFltr), findOpts)
1✔
403
        if err != nil {
2✔
404
                return nil, errors.Wrap(err, "store: failed to fetch users")
1✔
405
        }
1✔
406

407
        users := []model.User{}
1✔
408
        err = cur.All(ctx, &users)
1✔
409
        switch err {
1✔
410
        case nil, mongo.ErrNoDocuments:
1✔
411
                return users, nil
1✔
412
        default:
×
413
                return nil, errors.Wrap(err, "store: failed to decode users")
×
414
        }
415
}
416

417
func (db *DataStoreMongo) DeleteUser(ctx context.Context, id string) error {
1✔
418
        _, err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
1✔
419
                Collection(DbUsersColl).
1✔
420
                DeleteOne(ctx, mstore.WithTenantID(ctx, bson.M{"_id": id}))
1✔
421

1✔
422
        if err != nil {
1✔
423
                return err
×
424
        }
×
425

426
        return nil
1✔
427
}
428

429
func (db *DataStoreMongo) SaveToken(ctx context.Context, token *jwt.Token) error {
1✔
430
        _, err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
1✔
431
                Collection(DbTokensColl).
1✔
432
                InsertOne(ctx, mstore.WithTenantID(ctx, token))
1✔
433

1✔
434
        if isDuplicateKeyError(err) {
1✔
UNCOV
435
                return store.ErrDuplicateTokenName
×
436
        } else if err != nil {
1✔
437
                return errors.Wrap(err, "failed to store token")
×
438
        }
×
439
        return err
1✔
440
}
441

442
func (db *DataStoreMongo) EnsureSessionTokensLimit(ctx context.Context, userID oid.ObjectID,
443
        tokensLimit int) error {
1✔
444
        opts := &mopts.FindOptions{}
1✔
445
        opts.SetLimit(int64(tokensLimit))
1✔
446
        opts.SetSkip(int64(tokensLimit) - 1)
1✔
447
        opts.SetSort(bson.M{
1✔
448
                DbTokenIssuedAtTime: -1,
1✔
449
        })
1✔
450

1✔
451
        cur, err := db.client.Database(mstore.DbFromContext(ctx, DbName)).
1✔
452
                Collection(DbTokensColl).
1✔
453
                Find(ctx, mstore.WithTenantID(ctx, bson.M{
1✔
454
                        DbTokenSubject: userID,
1✔
455
                        DbTokenName:    bson.M{"$exists": false},
1✔
456
                }), opts)
1✔
457
        if err != nil {
1✔
458
                return err
×
459
        }
×
460

461
        tokens := []jwt.Token{}
1✔
462
        err = cur.All(ctx, &tokens)
1✔
463
        if err == mongo.ErrNoDocuments || len(tokens) == 0 {
1✔
UNCOV
464
                return nil
×
465
        } else if err != nil {
1✔
466
                return err
×
467
        }
×
468

469
        _, err = db.client.
1✔
470
                Database(mstore.DbFromContext(ctx, DbName)).
1✔
471
                Collection(DbTokensColl).
1✔
472
                DeleteMany(ctx, mstore.WithTenantID(ctx, bson.M{
1✔
473
                        DbTokenSubject: userID,
1✔
474
                        DbTokenName:    bson.M{"$exists": false},
1✔
475
                        DbTokenIssuedAtTime: bson.M{
1✔
476
                                "$lt": tokens[0].IssuedAt.Time,
1✔
477
                        },
1✔
478
                }))
1✔
479

1✔
480
        return err
1✔
481
}
482

483
// WithMultitenant enables multitenant support and returns a new datastore based
484
// on current one
485
func (db *DataStoreMongo) WithMultitenant() *DataStoreMongo {
1✔
486
        return &DataStoreMongo{
1✔
487
                client:      db.client,
1✔
488
                automigrate: db.automigrate,
1✔
489
                multitenant: true,
1✔
490
        }
1✔
491
}
1✔
492

493
// WithAutomigrate enables automatic migration and returns a new datastore based
494
// on current one
495
func (db *DataStoreMongo) WithAutomigrate() *DataStoreMongo {
3✔
496
        return &DataStoreMongo{
3✔
497
                client:      db.client,
3✔
498
                automigrate: true,
3✔
499
                multitenant: db.multitenant,
3✔
500
        }
3✔
501
}
3✔
502

503
func (db *DataStoreMongo) DeleteToken(ctx context.Context, userID, tokenID oid.ObjectID) error {
1✔
504
        _, err := db.client.
1✔
505
                Database(mstore.DbFromContext(ctx, DbName)).
1✔
506
                Collection(DbTokensColl).
1✔
507
                DeleteOne(ctx, mstore.WithTenantID(ctx, bson.M{DbID: tokenID, DbTokenSubject: userID}))
1✔
508
        return err
1✔
509
}
1✔
510

511
// deletes all tenant's tokens (identity in context)
512
func (db *DataStoreMongo) DeleteTokens(ctx context.Context) error {
1✔
513
        d, err := db.client.
1✔
514
                Database(mstore.DbFromContext(ctx, DbName)).
1✔
515
                Collection(DbTokensColl).
1✔
516
                DeleteMany(ctx, mstore.WithTenantID(ctx, bson.M{}))
1✔
517

1✔
518
        if err != nil {
1✔
519
                return err
×
520
        }
×
521

522
        if d.DeletedCount == 0 {
2✔
523
                return store.ErrTokenNotFound
1✔
524
        }
1✔
525

526
        return err
1✔
527
}
528

529
// deletes all user's tokens
530
func (db *DataStoreMongo) DeleteTokensByUserId(ctx context.Context, userId string) error {
2✔
531
        return db.DeleteTokensByUserIdExceptCurrentOne(ctx, userId, oid.ObjectID{})
2✔
532
}
2✔
533

534
// deletes all user's tokens except the current one
535
func (db *DataStoreMongo) DeleteTokensByUserIdExceptCurrentOne(
536
        ctx context.Context,
537
        userId string,
538
        tokenID oid.ObjectID,
539
) error {
2✔
540
        c := db.client.
2✔
541
                Database(mstore.DbFromContext(ctx, DbName)).
2✔
542
                Collection(DbTokensColl)
2✔
543

2✔
544
        id := oid.FromString(userId)
2✔
545
        filter := bson.M{
2✔
546
                "sub": id,
2✔
547
        }
2✔
548

2✔
549
        if tokenID != (oid.ObjectID{}) {
3✔
550
                filter["_id"] = bson.M{
1✔
551
                        "$ne": tokenID,
1✔
552
                }
1✔
553
        }
1✔
554

555
        _, err := c.DeleteMany(ctx, mstore.WithTenantID(ctx, filter))
2✔
556
        if err != nil {
2✔
557
                return errors.Wrap(err, "failed to remove tokens")
×
558
        }
×
559

560
        return nil
2✔
561
}
562

563
func (db *DataStoreMongo) SaveSettings(ctx context.Context, s *model.Settings, etag string) error {
1✔
564
        c := db.client.Database(mstore.DbFromContext(ctx, DbName)).
1✔
565
                Collection(DbSettingsColl)
1✔
566

1✔
567
        o := &mopts.ReplaceOptions{}
1✔
568
        o.SetUpsert(true)
1✔
569

1✔
570
        filters := bson.M{}
1✔
571
        if etag != "" {
2✔
572
                filters[DbSettingsEtag] = etag
1✔
573
        }
1✔
574
        _, err := c.ReplaceOne(ctx,
1✔
575
                mstore.WithTenantID(ctx, filters),
1✔
576
                mstore.WithTenantID(ctx, s),
1✔
577
                o,
1✔
578
        )
1✔
579
        if err != nil && !mongo.IsDuplicateKeyError(err) {
1✔
580
                return errors.Wrapf(err, "failed to store settings %v", s)
×
581
        } else if mongo.IsDuplicateKeyError(err) && etag != "" {
2✔
582
                return store.ErrETagMismatch
1✔
583
        }
1✔
584

585
        return err
1✔
586
}
587

588
func (db *DataStoreMongo) SaveUserSettings(ctx context.Context, userID string,
589
        s *model.Settings, etag string) error {
1✔
590
        c := db.client.Database(mstore.DbFromContext(ctx, DbName)).
1✔
591
                Collection(DbUserSettingsColl)
1✔
592

1✔
593
        o := &mopts.ReplaceOptions{}
1✔
594
        o.SetUpsert(true)
1✔
595

1✔
596
        filters := bson.M{
1✔
597
                DbSettingsUserID: userID,
1✔
598
        }
1✔
599
        if etag != "" {
2✔
600
                filters[DbSettingsEtag] = etag
1✔
601
        }
1✔
602
        settings := mstore.WithTenantID(ctx, s)
1✔
603
        settings = append(settings, bson.E{
1✔
604
                Key:   DbSettingsUserID,
1✔
605
                Value: userID,
1✔
606
        })
1✔
607
        _, err := c.ReplaceOne(ctx,
1✔
608
                mstore.WithTenantID(ctx, filters),
1✔
609
                settings,
1✔
610
                o,
1✔
611
        )
1✔
612
        if err != nil && !mongo.IsDuplicateKeyError(err) {
1✔
613
                return errors.Wrapf(err, "failed to store settings %v", s)
×
614
        } else if mongo.IsDuplicateKeyError(err) && etag != "" {
2✔
615
                return store.ErrETagMismatch
1✔
616
        }
1✔
617

618
        return err
1✔
619
}
620

621
func (db *DataStoreMongo) GetSettings(ctx context.Context) (*model.Settings, error) {
1✔
622
        c := db.client.Database(mstore.DbFromContext(ctx, DbName)).
1✔
623
                Collection(DbSettingsColl)
1✔
624

1✔
625
        var settings *model.Settings
1✔
626
        err := c.FindOne(ctx, mstore.WithTenantID(ctx, bson.M{})).Decode(&settings)
1✔
627

1✔
628
        switch err {
1✔
629
        case nil:
1✔
630
                return settings, nil
1✔
631
        case mongo.ErrNoDocuments:
1✔
632
                return nil, nil
1✔
633
        default:
×
634
                return nil, errors.Wrapf(err, "failed to get settings")
×
635
        }
636
}
637

638
func (db *DataStoreMongo) GetUserSettings(ctx context.Context,
639
        userID string) (*model.Settings, error) {
1✔
640
        c := db.client.Database(mstore.DbFromContext(ctx, DbName)).
1✔
641
                Collection(DbUserSettingsColl)
1✔
642

1✔
643
        filters := bson.M{
1✔
644
                DbSettingsUserID: userID,
1✔
645
        }
1✔
646
        var settings *model.Settings
1✔
647
        err := c.FindOne(ctx, mstore.WithTenantID(ctx, filters)).Decode(&settings)
1✔
648

1✔
649
        switch err {
1✔
650
        case nil:
1✔
651
                return settings, nil
1✔
652
        case mongo.ErrNoDocuments:
1✔
653
                return nil, nil
1✔
654
        default:
×
655
                return nil, errors.Wrapf(err, "failed to get settings")
×
656
        }
657
}
658

659
func (db *DataStoreMongo) GetPersonalAccessTokens(
660
        ctx context.Context,
661
        userID string,
662
) ([]model.PersonalAccessToken, error) {
1✔
663
        findOpts := mopts.Find().
1✔
664
                SetProjection(
1✔
665
                        bson.M{
1✔
666
                                DbID:             1,
1✔
667
                                DbTokenName:      1,
1✔
668
                                DbTokenExpiresAt: 1,
1✔
669
                                DbTokenLastUsed:  1,
1✔
670
                                DbTokenIssuedAt:  1,
1✔
671
                        },
1✔
672
                )
1✔
673

1✔
674
        collTokens := db.client.
1✔
675
                Database(mstore.DbFromContext(ctx, DbName)).
1✔
676
                Collection(DbTokensColl)
1✔
677

1✔
678
        var mgoFltr = bson.M{
1✔
679
                DbTokenSubject: oid.FromString(userID),
1✔
680
                DbTokenName:    bson.M{"$exists": true},
1✔
681
        }
1✔
682
        cur, err := collTokens.Find(ctx, mstore.WithTenantID(ctx, mgoFltr), findOpts)
1✔
683
        if err != nil {
1✔
684
                return nil, errors.Wrap(err, "store: failed to fetch tokens")
×
685
        }
×
686

687
        tokens := []model.PersonalAccessToken{}
1✔
688
        err = cur.All(ctx, &tokens)
1✔
689
        switch err {
1✔
690
        case nil, mongo.ErrNoDocuments:
1✔
691
                return tokens, nil
1✔
692
        default:
×
693
                return nil, errors.Wrap(err, "store: failed to decode tokens")
×
694
        }
695
}
696

697
func (db *DataStoreMongo) UpdateTokenLastUsed(ctx context.Context, id oid.ObjectID) error {
1✔
698
        collTokens := db.client.
1✔
699
                Database(mstore.DbFromContext(ctx, DbName)).
1✔
700
                Collection(DbTokensColl)
1✔
701

1✔
702
        _, err := collTokens.UpdateOne(ctx,
1✔
703
                mstore.WithTenantID(ctx, bson.D{{Key: DbID, Value: id}}),
1✔
704
                bson.D{{Key: "$set", Value: bson.D{
1✔
705
                        {Key: DbTokenLastUsed, Value: time.Now()}},
1✔
706
                }},
1✔
707
        )
1✔
708

1✔
709
        return err
1✔
710
}
1✔
711

712
func (db *DataStoreMongo) CountPersonalAccessTokens(
713
        ctx context.Context,
714
        userID string,
715
) (int64, error) {
1✔
716
        collTokens := db.client.
1✔
717
                Database(mstore.DbFromContext(ctx, DbName)).
1✔
718
                Collection(DbTokensColl)
1✔
719

1✔
720
        var mgoFltr = bson.M{
1✔
721
                DbTokenSubject: oid.FromString(userID),
1✔
722
                DbTokenName:    bson.M{"$exists": true},
1✔
723
        }
1✔
724
        count, err := collTokens.CountDocuments(ctx, mstore.WithTenantID(ctx, mgoFltr))
1✔
725
        if err != nil {
1✔
726
                return -1, errors.Wrap(err, "store: failed to count tokens")
×
727
        }
×
728
        return count, nil
1✔
729
}
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