From b94d37813e7dcbf83fdf37f3044288c2dc6b1166 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Fri, 13 Mar 2026 14:03:40 +0100 Subject: [PATCH 01/38] Migrate messaging DB package to mongo-driver v2 --- .gitignore | 1 + go.mod | 3 ++- go.sum | 6 +++-- mongodb-driver-update.md | 27 +++++++++++++++++++ pkg/db/messaging/db.go | 10 +++---- pkg/db/messaging/email-templates.go | 36 +++++++++++++------------- pkg/db/messaging/outgoing-emails.go | 11 ++++---- pkg/db/messaging/scheduledEmail.go | 30 ++++++++++----------- pkg/db/messaging/sent-emails.go | 27 +++++++++---------- pkg/db/messaging/sent-sms.go | 26 +++++++++---------- pkg/db/messaging/sms-templates.go | 34 ++++++++++++------------ pkg/db/utils.go | 4 +-- pkg/messaging/types/emailTemplate.go | 4 +-- pkg/messaging/types/outgoing_email.go | 26 +++++++++---------- pkg/messaging/types/scheduledEmails.go | 4 +-- pkg/messaging/types/sms.go | 14 +++++----- 16 files changed, 144 insertions(+), 119 deletions(-) create mode 100644 mongodb-driver-update.md diff --git a/.gitignore b/.gitignore index 6b1d5594..180a0dc5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ logs test/run-management-api.sh test/run-smtp-bridge.sh test/run-participant-api.sh +test/run-all.sh test/participant-api.yaml test/smtp-bridge.yaml *-api-routes.txt diff --git a/go.mod b/go.mod index 1fd747ee..ffb22bd9 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/gin-gonic/gin v1.10.1 github.com/golang-jwt/jwt/v5 v5.3.0 go.mongodb.org/mongo-driver v1.17.4 + go.mongodb.org/mongo-driver/v2 v2.5.0 ) require ( @@ -39,7 +40,7 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect - github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/scram v1.2.0 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect golang.org/x/arch v0.20.0 // indirect diff --git a/go.sum b/go.sum index 9ea268ed..908d6c0d 100644 --- a/go.sum +++ b/go.sum @@ -77,8 +77,8 @@ github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= -github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= +github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= @@ -86,6 +86,8 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfS github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw= go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= +go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md new file mode 100644 index 00000000..718d6e52 --- /dev/null +++ b/mongodb-driver-update.md @@ -0,0 +1,27 @@ +# Update mongodb driver + +- The bson/primitive package has been merged into the bson package —> changed any instance of primitive.ObjectID to bson.ObjectId. + +## DB + +- context.Context parameter has been removed from mongo.Connect() because the deployment connector doesn’t accept a context, meaning that the context passed to mongo.Connect() in previous versions didn't serve a purpose. +- Simplfied DropOne and DropAll methods by removing the server response +- Index Model: The old `IndexOptionsBuilder` type was removed and `IndexModel.Options.Name` is no longer accessible as a field. Required steps: + - define variable for index names + - capture index names returned by CreateMany + - use the stored names when dropping indexes + - maybe store indexNames as field of messagingDBService struct?!? (e.g. `emailTemplateIndexNames map[string][]`) + +### Messaging + +#### email-templates + +- save email template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (testing? Line 128/129) + +#### scheduled-emails + +- save scheduled-emails: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (testing? Line 128/129) + +#### sms-templates + +- save sms template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (testing? Line 128/129) diff --git a/pkg/db/messaging/db.go b/pkg/db/messaging/db.go index dead742a..dd75ca8a 100644 --- a/pkg/db/messaging/db.go +++ b/pkg/db/messaging/db.go @@ -6,9 +6,9 @@ import ( "time" "github.com/case-framework/case-backend/pkg/db" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) // collection names @@ -30,10 +30,10 @@ type MessagingDBService struct { } func NewMessagingDBService(configs db.DBConfig) (*MessagingDBService, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) + _, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) defer cancel() - dbClient, err := mongo.Connect(ctx, + dbClient, err := mongo.Connect( options.Client().ApplyURI(configs.URI), options.Client().SetMaxConnIdleTime(time.Duration(configs.IdleConnTimeout)*time.Second), options.Client().SetMaxPoolSize(configs.MaxPoolSize), diff --git a/pkg/db/messaging/email-templates.go b/pkg/db/messaging/email-templates.go index 67733751..8ef8d7dc 100644 --- a/pkg/db/messaging/email-templates.go +++ b/pkg/db/messaging/email-templates.go @@ -4,14 +4,15 @@ import ( "fmt" "log/slog" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" messagingTypes "github.com/case-framework/case-backend/pkg/messaging/types" ) +var emailTemplateIndexNames []string + var indexesForEmailTemplatesCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -27,18 +28,17 @@ func (messagingDBService *MessagingDBService) DropIndexForEmailTemplatesCollecti defer cancel() if dropAll { - _, err := messagingDBService.collectionEmailTemplates(instanceID).Indexes().DropAll(ctx) + err := messagingDBService.collectionEmailTemplates(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for email templates", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, index := range indexesForEmailTemplatesCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for email templates collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range emailTemplateIndexNames { + if indexName == "" { + slog.Error("Index name is empty for email templates collection", slog.String("index", fmt.Sprintf("%+v", indexName))) continue } - indexName := *index.Options.Name - _, err := messagingDBService.collectionEmailTemplates(instanceID).Indexes().DropOne(ctx, indexName) + err := messagingDBService.collectionEmailTemplates(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for email templates", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("indexName", indexName)) } @@ -49,10 +49,11 @@ func (messagingDBService *MessagingDBService) DropIndexForEmailTemplatesCollecti func (messagingDBService *MessagingDBService) CreateDefaultIndexesForEmailTemplatesCollection(instanceID string) { ctx, cancel := messagingDBService.getContext() defer cancel() - _, err := messagingDBService.collectionEmailTemplates(instanceID).Indexes().CreateMany(ctx, indexesForEmailTemplatesCollection) + names, err := messagingDBService.collectionEmailTemplates(instanceID).Indexes().CreateMany(ctx, indexesForEmailTemplatesCollection) if err != nil { slog.Error("Error creating index for email templates", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } + emailTemplateIndexNames = names } // find all email templates with study key empty @@ -93,7 +94,7 @@ func (messagingDBService *MessagingDBService) GetEmailTemplateByID(instanceID st ctx, cancel := messagingDBService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(id) + _id, err := bson.ObjectIDFromHex(id) if err != nil { return nil, err } @@ -114,22 +115,21 @@ func (messagingDBService *MessagingDBService) SaveEmailTemplate(instanceID strin defer cancel() if emailTemplate.ID.IsZero() { - emailTemplate.ID = primitive.NewObjectID() + emailTemplate.ID = bson.NewObjectID() // new email template res, err := messagingDBService.collectionEmailTemplates(instanceID).InsertOne(ctx, emailTemplate) if err != nil { return messagingTypes.EmailTemplate{}, err } - emailTemplate.ID = res.InsertedID.(primitive.ObjectID) + emailTemplate.ID = res.InsertedID.(bson.ObjectID) return emailTemplate, nil } // update email template filter := bson.M{"_id": emailTemplate.ID} - upsert := false - after := options.After - opt := options.FindOneAndReplaceOptions{Upsert: &upsert, ReturnDocument: &after} - err := messagingDBService.collectionEmailTemplates(instanceID).FindOneAndReplace(ctx, filter, emailTemplate, &opt).Decode(&emailTemplate) + opt := options.FindOneAndReplace().SetUpsert(false).SetReturnDocument(options.After) + err := messagingDBService.collectionEmailTemplates(instanceID).FindOneAndReplace(ctx, filter, emailTemplate, opt).Decode(&emailTemplate) + if err != nil { return messagingTypes.EmailTemplate{}, err } diff --git a/pkg/db/messaging/outgoing-emails.go b/pkg/db/messaging/outgoing-emails.go index 7cfc82c3..f817bdcd 100644 --- a/pkg/db/messaging/outgoing-emails.go +++ b/pkg/db/messaging/outgoing-emails.go @@ -6,8 +6,7 @@ import ( "time" messagingTypes "github.com/case-framework/case-backend/pkg/messaging/types" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) func (dbService *MessagingDBService) DropIndexForOutgoingEmailsCollection(instanceID string, dropAll bool) { @@ -15,7 +14,7 @@ func (dbService *MessagingDBService) DropIndexForOutgoingEmailsCollection(instan defer cancel() if dropAll { - _, err := dbService.collectionOutgoingEmails(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionOutgoingEmails(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for outgoing emails", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } @@ -36,7 +35,7 @@ func (dbService *MessagingDBService) AddToOutgoingEmails(instanceID string, emai if err != nil { return email, err } - email.ID = res.InsertedID.(primitive.ObjectID) + email.ID = res.InsertedID.(bson.ObjectID) return email, nil } @@ -92,7 +91,7 @@ func (dbService *MessagingDBService) ResetLastSendAttemptForOutgoing(instanceID ctx, cancel := dbService.getContext() defer cancel() - _id, _ := primitive.ObjectIDFromHex(emailID) + _id, _ := bson.ObjectIDFromHex(emailID) filter := bson.M{"_id": _id} update := bson.M{"$set": bson.M{"lastSendAttempt": 0}} res, err := dbService.collectionOutgoingEmails(instanceID).UpdateOne(ctx, filter, update) @@ -109,7 +108,7 @@ func (dbService *MessagingDBService) DeleteOutgoingEmail(instanceID string, id s ctx, cancel := dbService.getContext() defer cancel() - _id, _ := primitive.ObjectIDFromHex(id) + _id, _ := bson.ObjectIDFromHex(id) filter := bson.M{"_id": _id} res, err := dbService.collectionOutgoingEmails(instanceID).DeleteOne(ctx, filter, nil) diff --git a/pkg/db/messaging/scheduledEmail.go b/pkg/db/messaging/scheduledEmail.go index 19b27904..367c76dd 100644 --- a/pkg/db/messaging/scheduledEmail.go +++ b/pkg/db/messaging/scheduledEmail.go @@ -6,9 +6,8 @@ import ( messagingTypes "github.com/case-framework/case-backend/pkg/messaging/types" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) func (dbService *MessagingDBService) DropIndexForEmailSchedulesCollection(instanceID string, dropAll bool) { @@ -16,7 +15,7 @@ func (dbService *MessagingDBService) DropIndexForEmailSchedulesCollection(instan defer cancel() if dropAll { - _, err := dbService.collectionEmailSchedules(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionEmailSchedules(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for email schedules", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } @@ -85,7 +84,7 @@ func (dbService *MessagingDBService) GetScheduledEmailByID(instanceID string, id ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(id) + _id, err := bson.ObjectIDFromHex(id) if err != nil { return nil, err } @@ -106,24 +105,21 @@ func (dbService *MessagingDBService) SaveScheduledEmail(instanceID string, sched if !scheduledEmail.ID.IsZero() { filter := bson.M{"_id": scheduledEmail.ID} - upsert := false - rd := options.After - options := options.FindOneAndReplaceOptions{ - Upsert: &upsert, - ReturnDocument: &rd, - } + options := options.FindOneAndReplace(). + SetUpsert(false). + SetReturnDocument(options.After) elem := messagingTypes.ScheduledEmail{} - err := dbService.collectionEmailSchedules(instanceID).FindOneAndReplace( - ctx, filter, scheduledEmail, &options, - ).Decode(&elem) + err := dbService.collectionEmailSchedules(instanceID). + FindOneAndReplace(ctx, filter, scheduledEmail, options). + Decode(&elem) return elem, err } else { - scheduledEmail.ID = primitive.NewObjectID() + scheduledEmail.ID = bson.NewObjectID() res, err := dbService.collectionEmailSchedules(instanceID).InsertOne(ctx, scheduledEmail) if err != nil { return scheduledEmail, err } - scheduledEmail.ID = res.InsertedID.(primitive.ObjectID) + scheduledEmail.ID = res.InsertedID.(bson.ObjectID) return scheduledEmail, nil } } @@ -133,7 +129,7 @@ func (dbService *MessagingDBService) DeleteScheduledEmail(instanceID string, id ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(id) + _id, err := bson.ObjectIDFromHex(id) if err != nil { return err } diff --git a/pkg/db/messaging/sent-emails.go b/pkg/db/messaging/sent-emails.go index 696a3477..12c341fb 100644 --- a/pkg/db/messaging/sent-emails.go +++ b/pkg/db/messaging/sent-emails.go @@ -6,12 +6,13 @@ import ( "time" messagingTypes "github.com/case-framework/case-backend/pkg/messaging/types" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) +var sentEmailIndexNames []string + var indexesForSentEmailsCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -27,18 +28,17 @@ func (dbService *MessagingDBService) DropIndexForSentEmailsCollection(instanceID defer cancel() if dropAll { - _, err := dbService.collectionSentEmails(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionSentEmails(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for sent emails", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, index := range indexesForSentEmailsCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for sent emails collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range sentEmailIndexNames { + if indexName == "" { + slog.Error("Index name is empty for sent emails collection", slog.String("index", fmt.Sprintf("%+v", indexName))) continue } - indexName := *index.Options.Name - _, err := dbService.collectionSentEmails(instanceID).Indexes().DropOne(ctx, indexName) + err := dbService.collectionSentEmails(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for sent emails", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("indexName", indexName)) } @@ -50,10 +50,11 @@ func (dbService *MessagingDBService) CreateDefaultIndexesForSentEmailsCollection ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionSentEmails(instanceID).Indexes().CreateMany(ctx, indexesForSentEmailsCollection) + names, err := dbService.collectionSentEmails(instanceID).Indexes().CreateMany(ctx, indexesForSentEmailsCollection) if err != nil { slog.Error("Error creating index for sent emails", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } + sentEmailIndexNames = names } func (dbService *MessagingDBService) AddToSentEmails(instanceID string, email messagingTypes.OutgoingEmail) (messagingTypes.OutgoingEmail, error) { @@ -63,11 +64,11 @@ func (dbService *MessagingDBService) AddToSentEmails(instanceID string, email me email.SentAt = time.Now().UTC() email.To = []string{} - email.ID = primitive.NilObjectID + email.ID = bson.NilObjectID res, err := dbService.collectionSentEmails(instanceID).InsertOne(ctx, email) if err != nil { return email, err } - email.ID = res.InsertedID.(primitive.ObjectID) + email.ID = res.InsertedID.(bson.ObjectID) return email, nil } diff --git a/pkg/db/messaging/sent-sms.go b/pkg/db/messaging/sent-sms.go index 9e752004..20b18285 100644 --- a/pkg/db/messaging/sent-sms.go +++ b/pkg/db/messaging/sent-sms.go @@ -1,17 +1,17 @@ package messaging import ( - "fmt" "log/slog" "time" "github.com/case-framework/case-backend/pkg/messaging/types" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) +var sentSMSIndexNames []string + var indexesForSentSMSCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -28,18 +28,17 @@ func (dbService *MessagingDBService) DropIndexForSentSMSCollection(instanceID st defer cancel() if dropAll { - _, err := dbService.collectionSentSMS(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionSentSMS(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for sent SMS", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, index := range indexesForSentSMSCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for sent SMS collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range sentSMSIndexNames { + if indexName == "" { + slog.Error("Index name is empty for sent SMS collection") continue } - indexName := *index.Options.Name - _, err := dbService.collectionSentSMS(instanceID).Indexes().DropOne(ctx, indexName) + err := dbService.collectionSentSMS(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for sent SMS", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("indexName", indexName)) } @@ -51,10 +50,11 @@ func (dbService *MessagingDBService) CreateDefaultIndexesForSentSMSCollection(in ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionSentSMS(instanceID).Indexes().CreateMany(ctx, indexesForSentSMSCollection) + names, err := dbService.collectionSentSMS(instanceID).Indexes().CreateMany(ctx, indexesForSentSMSCollection) if err != nil { slog.Error("Error creating index for sent SMS", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } + sentSMSIndexNames = names } func (dbService *MessagingDBService) AddToSentSMS(instanceID string, sms types.SentSMS) (types.SentSMS, error) { @@ -65,7 +65,7 @@ func (dbService *MessagingDBService) AddToSentSMS(instanceID string, sms types.S if err != nil { return sms, err } - sms.ID = res.InsertedID.(primitive.ObjectID) + sms.ID = res.InsertedID.(bson.ObjectID) return sms, nil } diff --git a/pkg/db/messaging/sms-templates.go b/pkg/db/messaging/sms-templates.go index 841af01d..4cdd9c80 100644 --- a/pkg/db/messaging/sms-templates.go +++ b/pkg/db/messaging/sms-templates.go @@ -1,17 +1,17 @@ package messaging import ( - "fmt" "log/slog" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" messagingTypes "github.com/case-framework/case-backend/pkg/messaging/types" ) +var smsTemplateIndexNames []string + var indexesForSMSTemplatesCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -25,18 +25,17 @@ func (messagingDBService *MessagingDBService) DropIndexForSMSTemplatesCollection ctx, cancel := messagingDBService.getContext() defer cancel() if dropAll { - _, err := messagingDBService.collectionSMSTemplates(instanceID).Indexes().DropAll(ctx) + err := messagingDBService.collectionSMSTemplates(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for SMS templates", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, index := range indexesForSMSTemplatesCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for SMS templates collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range smsTemplateIndexNames { + if indexName == "" { + slog.Error("Index name is empty for SMS templates collection") continue } - indexName := *index.Options.Name - _, err := messagingDBService.collectionSMSTemplates(instanceID).Indexes().DropOne(ctx, indexName) + err := messagingDBService.collectionSMSTemplates(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for SMS templates", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("indexName", indexName)) } @@ -48,10 +47,11 @@ func (messagingDBService *MessagingDBService) CreateDefaultIndexesForSMSTemplate ctx, cancel := messagingDBService.getContext() defer cancel() - _, err := messagingDBService.collectionSMSTemplates(instanceID).Indexes().CreateMany(ctx, indexesForSMSTemplatesCollection) + names, err := messagingDBService.collectionSMSTemplates(instanceID).Indexes().CreateMany(ctx, indexesForSMSTemplatesCollection) if err != nil { slog.Error("Error creating index for SMS templates", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } + smsTemplateIndexNames = names } // save email template (if id is empty, insert, else update) @@ -60,22 +60,20 @@ func (messagingDBService *MessagingDBService) SaveSMSTemplate(instanceID string, defer cancel() if smsTemplate.ID.IsZero() { - smsTemplate.ID = primitive.NewObjectID() + smsTemplate.ID = bson.NewObjectID() // new template res, err := messagingDBService.collectionSMSTemplates(instanceID).InsertOne(ctx, smsTemplate) if err != nil { return messagingTypes.SMSTemplate{}, err } - smsTemplate.ID = res.InsertedID.(primitive.ObjectID) + smsTemplate.ID = res.InsertedID.(bson.ObjectID) return smsTemplate, nil } // update template filter := bson.M{"_id": smsTemplate.ID} - upsert := false - after := options.After - opt := options.FindOneAndReplaceOptions{Upsert: &upsert, ReturnDocument: &after} - err := messagingDBService.collectionSMSTemplates(instanceID).FindOneAndReplace(ctx, filter, smsTemplate, &opt).Decode(&smsTemplate) + opt := options.FindOneAndReplace().SetUpsert(false).SetReturnDocument(options.After) + err := messagingDBService.collectionSMSTemplates(instanceID).FindOneAndReplace(ctx, filter, smsTemplate, opt).Decode(&smsTemplate) if err != nil { return messagingTypes.SMSTemplate{}, err } diff --git a/pkg/db/utils.go b/pkg/db/utils.go index 8bf7e9d2..b93d1e64 100644 --- a/pkg/db/utils.go +++ b/pkg/db/utils.go @@ -4,8 +4,8 @@ import ( "context" "errors" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" ) func ListCollectionIndexes(ctx context.Context, collection *mongo.Collection) ([]bson.M, error) { diff --git a/pkg/messaging/types/emailTemplate.go b/pkg/messaging/types/emailTemplate.go index 29adfdff..82fd8bf4 100644 --- a/pkg/messaging/types/emailTemplate.go +++ b/pkg/messaging/types/emailTemplate.go @@ -1,6 +1,6 @@ package types -import "go.mongodb.org/mongo-driver/bson/primitive" +import "go.mongodb.org/mongo-driver/v2/bson" const ( EMAIL_TYPE_REGISTRATION = "registration" @@ -21,7 +21,7 @@ const ( ) type EmailTemplate struct { - ID primitive.ObjectID `bson:"_id" json:"id,omitempty"` + ID bson.ObjectID `bson:"_id" json:"id,omitempty"` MessageType string `bson:"messageType" json:"messageType"` StudyKey string `bson:"studyKey,omitempty" json:"studyKey"` DefaultLanguage string `bson:"defaultLanguage" json:"defaultLanguage"` diff --git a/pkg/messaging/types/outgoing_email.go b/pkg/messaging/types/outgoing_email.go index 7d55f25b..3015512a 100644 --- a/pkg/messaging/types/outgoing_email.go +++ b/pkg/messaging/types/outgoing_email.go @@ -3,20 +3,20 @@ package types import ( "time" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) type OutgoingEmail struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id"` - MessageType string `bson:"messageType" json:"messageType"` - UserID string `bson:"userId" json:"userId"` - To []string `bson:"to" json:"to"` - Subject string `bson:"subject" json:"subject"` - HeaderOverrides *HeaderOverrides `bson:"headerOverrides" json:"headerOverrides"` - Content string `bson:"content" json:"content"` - AddedAt int64 `bson:"addedAt" json:"addedAt"` - SentAt time.Time `bson:"sentAt" json:"sentAt"` - ExpiresAt int64 `bson:"expiresAt" json:"expiresAt"` - HighPrio bool `bson:"highPrio" json:"highPrio"` - LastSendAttempt int64 `bson:"lastSendAttempt" json:"lastSendAttempt"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id"` + MessageType string `bson:"messageType" json:"messageType"` + UserID string `bson:"userId" json:"userId"` + To []string `bson:"to" json:"to"` + Subject string `bson:"subject" json:"subject"` + HeaderOverrides *HeaderOverrides `bson:"headerOverrides" json:"headerOverrides"` + Content string `bson:"content" json:"content"` + AddedAt int64 `bson:"addedAt" json:"addedAt"` + SentAt time.Time `bson:"sentAt" json:"sentAt"` + ExpiresAt int64 `bson:"expiresAt" json:"expiresAt"` + HighPrio bool `bson:"highPrio" json:"highPrio"` + LastSendAttempt int64 `bson:"lastSendAttempt" json:"lastSendAttempt"` } diff --git a/pkg/messaging/types/scheduledEmails.go b/pkg/messaging/types/scheduledEmails.go index 6f0e8612..a5ae870c 100644 --- a/pkg/messaging/types/scheduledEmails.go +++ b/pkg/messaging/types/scheduledEmails.go @@ -2,11 +2,11 @@ package types import ( study "github.com/case-framework/case-backend/pkg/study/types" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) type ScheduledEmail struct { - ID primitive.ObjectID `bson:"_id" json:"id,omitempty"` + ID bson.ObjectID `bson:"_id" json:"id,omitempty"` Template EmailTemplate `bson:"template" json:"template"` Type string `bson:"type" json:"type"` StudyKey string `bson:"studyKey" json:"studyKey"` diff --git a/pkg/messaging/types/sms.go b/pkg/messaging/types/sms.go index 525a9708..ccbc6fae 100644 --- a/pkg/messaging/types/sms.go +++ b/pkg/messaging/types/sms.go @@ -3,19 +3,19 @@ package types import ( "time" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) type SentSMS struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id"` - UserID string `bson:"userID" json:"userID"` - MessageType string `bson:"messageType" json:"messageType"` - SentAt time.Time `bson:"sentAt" json:"sentAt"` - PhoneNumber string `bson:"phoneNumber" json:"phoneNumber"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id"` + UserID string `bson:"userID" json:"userID"` + MessageType string `bson:"messageType" json:"messageType"` + SentAt time.Time `bson:"sentAt" json:"sentAt"` + PhoneNumber string `bson:"phoneNumber" json:"phoneNumber"` } type SMSTemplate struct { - ID primitive.ObjectID `bson:"_id" json:"id,omitempty"` + ID bson.ObjectID `bson:"_id" json:"id,omitempty"` MessageType string `bson:"messageType" json:"messageType"` DefaultLanguage string `bson:"defaultLanguage" json:"defaultLanguage"` From string `bson:"from" json:"from"` From 4a26a41a768642b8f899f126072d168be195fb57 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Mon, 16 Mar 2026 08:09:09 +0100 Subject: [PATCH 02/38] Migrate management-user db package to mongo-driver v2 --- mongodb-driver-update.md | 2 +- pkg/db/management-user/app-roles.go | 52 +++++++++---------- pkg/db/management-user/db.go | 10 ++-- pkg/db/management-user/management-users.go | 36 +++++++------- pkg/db/management-user/permissions.go | 32 ++++++------ pkg/db/management-user/service-users.go | 34 ++++++------- pkg/db/management-user/sessions.go | 30 +++++------ pkg/db/management-user/types.go | 58 +++++++++++----------- 8 files changed, 128 insertions(+), 126 deletions(-) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index 718d6e52..de971034 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -2,7 +2,7 @@ - The bson/primitive package has been merged into the bson package —> changed any instance of primitive.ObjectID to bson.ObjectId. -## DB +## All DB packages - context.Context parameter has been removed from mongo.Connect() because the deployment connector doesn’t accept a context, meaning that the context passed to mongo.Connect() in previous versions didn't serve a purpose. - Simplfied DropOne and DropAll methods by removing the server response diff --git a/pkg/db/management-user/app-roles.go b/pkg/db/management-user/app-roles.go index 0be2c26a..d2bc5d17 100644 --- a/pkg/db/management-user/app-roles.go +++ b/pkg/db/management-user/app-roles.go @@ -2,14 +2,12 @@ package managementuser import ( "errors" - "fmt" "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) // store which users have which roles @@ -22,6 +20,8 @@ func (dbService *ManagementUserDBService) collectionAppRoleTemplates(instanceID return dbService.DBClient.Database(dbService.getDBName(instanceID)).Collection(COLLECTION_NAME_APP_ROLE_TEMPLATES) } +var appRoleIndexNames []string + var indexesForAppRolesCollection = []mongo.IndexModel{ { Keys: bson.D{{Key: "subjectId", Value: 1}}, @@ -46,18 +46,17 @@ func (dbService *ManagementUserDBService) DropIndexForAppRolesCollection(instanc ctx, cancel := dbService.getContext() defer cancel() if dropAll { - _, err := dbService.collectionAppRoles(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionAppRoles(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for app roles", slog.String("error", err.Error())) } } else { - for _, index := range indexesForAppRolesCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for app roles collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range appRoleIndexNames { + if indexName == "" { + slog.Error("Index name is empty for app roles collection") continue } - indexName := *index.Options.Name - _, err := dbService.collectionAppRoles(instanceID).Indexes().DropOne(ctx, indexName) + err := dbService.collectionAppRoles(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for app roles", slog.String("error", err.Error()), slog.String("indexName", indexName)) } @@ -69,12 +68,15 @@ func (dbService *ManagementUserDBService) CreateDefaultIndexesForAppRolesCollect ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionAppRoles(instanceID).Indexes().CreateMany(ctx, indexesForAppRolesCollection) + names, err := dbService.collectionAppRoles(instanceID).Indexes().CreateMany(ctx, indexesForAppRolesCollection) if err != nil { slog.Error("Error creating index for app roles", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } + appRoleIndexNames = names } +var appRoleTemplateIndexNames []string + var indexesForAppRoleTemplatesCollection = []mongo.IndexModel{ { Keys: bson.D{{Key: "appName", Value: 1}, {Key: "role", Value: 1}}, @@ -90,18 +92,17 @@ func (dbService *ManagementUserDBService) DropIndexForAppRoleTemplatesCollection ctx, cancel := dbService.getContext() defer cancel() if dropAll { - _, err := dbService.collectionAppRoleTemplates(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionAppRoleTemplates(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for app role templates", slog.String("error", err.Error())) } } else { - for _, index := range indexesForAppRoleTemplatesCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for app role templates collection: ", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range appRoleTemplateIndexNames { + if indexName == "" { + slog.Error("Index name is empty for app role templates collection") continue } - indexName := *index.Options.Name - _, err := dbService.collectionAppRoleTemplates(instanceID).Indexes().DropOne(ctx, indexName) + err := dbService.collectionAppRoleTemplates(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for app role templates", slog.String("error", err.Error()), slog.String("indexName", indexName)) } @@ -113,10 +114,11 @@ func (dbService *ManagementUserDBService) CreateDefaultIndexesForAppRoleTemplate ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionAppRoleTemplates(instanceID).Indexes().CreateMany(ctx, indexesForAppRoleTemplatesCollection) + names, err := dbService.collectionAppRoleTemplates(instanceID).Indexes().CreateMany(ctx, indexesForAppRoleTemplatesCollection) if err != nil { slog.Error("Error creating index for app role templates", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } + appRoleTemplateIndexNames = names } /// App role templates @@ -141,7 +143,7 @@ func (dbService *ManagementUserDBService) AddAppRoleTemplate( if err != nil { return err } - appRoleTemplate.ID = res.InsertedID.(primitive.ObjectID) + appRoleTemplate.ID = res.InsertedID.(bson.ObjectID) return nil } @@ -174,7 +176,7 @@ func (dbService *ManagementUserDBService) GetAppRoleTemplateByID( ctx, cancel := dbService.getContext() defer cancel() - objID, err := primitive.ObjectIDFromHex(appRoleTemplateID) + objID, err := bson.ObjectIDFromHex(appRoleTemplateID) if err != nil { return AppRoleTemplate{}, err } @@ -198,7 +200,7 @@ func (dbService *ManagementUserDBService) UpdateAppRoleTemplate( ctx, cancel := dbService.getContext() defer cancel() - objID, err := primitive.ObjectIDFromHex(appRoleTemplateID) + objID, err := bson.ObjectIDFromHex(appRoleTemplateID) if err != nil { return err } @@ -228,7 +230,7 @@ func (dbService *ManagementUserDBService) DeleteAppRoleTemplate( ctx, cancel := dbService.getContext() defer cancel() - objID, err := primitive.ObjectIDFromHex(appRoleTemplateID) + objID, err := bson.ObjectIDFromHex(appRoleTemplateID) if err != nil { return err } @@ -272,7 +274,7 @@ func (dbService *ManagementUserDBService) AddAppRoleForSubject( if err != nil { return err } - appRole.ID = res.InsertedID.(primitive.ObjectID) + appRole.ID = res.InsertedID.(bson.ObjectID) return nil } @@ -327,7 +329,7 @@ func (dbService *ManagementUserDBService) DeleteAppRole( ctx, cancel := dbService.getContext() defer cancel() - objID, err := primitive.ObjectIDFromHex(appRoleID) + objID, err := bson.ObjectIDFromHex(appRoleID) if err != nil { return err } diff --git a/pkg/db/management-user/db.go b/pkg/db/management-user/db.go index 826d5330..a59ccec4 100644 --- a/pkg/db/management-user/db.go +++ b/pkg/db/management-user/db.go @@ -6,9 +6,9 @@ import ( "time" "github.com/case-framework/case-backend/pkg/db" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) // collection names @@ -35,10 +35,10 @@ type ManagementUserDBService struct { } func NewManagementUserDBService(configs db.DBConfig) (*ManagementUserDBService, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) + _, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) defer cancel() - dbClient, err := mongo.Connect(ctx, + dbClient, err := mongo.Connect( options.Client().ApplyURI(configs.URI), options.Client().SetMaxConnIdleTime(time.Duration(configs.IdleConnTimeout)*time.Second), options.Client().SetMaxPoolSize(configs.MaxPoolSize), diff --git a/pkg/db/management-user/management-users.go b/pkg/db/management-user/management-users.go index fb6c1b6b..b0a064e8 100644 --- a/pkg/db/management-user/management-users.go +++ b/pkg/db/management-user/management-users.go @@ -1,16 +1,16 @@ package managementuser import ( - "fmt" "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) +var managementUserIndexNames []string + var indexesForManagementUsersCollection = []mongo.IndexModel{ { Keys: bson.D{{Key: "sub", Value: 1}}, @@ -23,18 +23,17 @@ func (dbService *ManagementUserDBService) DropIndexForManagementUsersCollection( defer cancel() if dropAll { - _, err := dbService.collectionManagementUsers(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionManagementUsers(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for management users", slog.String("error", err.Error())) } } else { - for _, index := range indexesForManagementUsersCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for management users collection", slog.String("index", fmt.Sprintf("%+v", index)), slog.String("instanceID", instanceID)) + for _, indexName := range managementUserIndexNames { + if indexName == "" { + slog.Error("Index name is empty for management users collection", slog.String("instanceID", instanceID)) continue } - indexName := *index.Options.Name - _, err := dbService.collectionManagementUsers(instanceID).Indexes().DropOne(ctx, indexName) + err := dbService.collectionManagementUsers(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for management users", slog.String("error", err.Error()), slog.String("indexName", indexName), slog.String("instanceID", instanceID)) } @@ -45,10 +44,11 @@ func (dbService *ManagementUserDBService) DropIndexForManagementUsersCollection( func (dbService *ManagementUserDBService) CreateDefaultIndexesForManagementUsersCollection(instanceID string) { ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionManagementUsers(instanceID).Indexes().CreateMany(ctx, indexesForManagementUsersCollection) + names, err := dbService.collectionManagementUsers(instanceID).Indexes().CreateMany(ctx, indexesForManagementUsersCollection) if err != nil { slog.Error("Error creating index for management users", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } + managementUserIndexNames = names } func (dbService *ManagementUserDBService) CreateUser( @@ -62,7 +62,7 @@ func (dbService *ManagementUserDBService) CreateUser( if err != nil { return nil, err } - newUser.ID = res.InsertedID.(primitive.ObjectID) + newUser.ID = res.InsertedID.(bson.ObjectID) return newUser, nil } @@ -89,7 +89,7 @@ func (dbService *ManagementUserDBService) GetUserByID( ctx, cancel := dbService.getContext() defer cancel() var user ManagementUser - objID, err := primitive.ObjectIDFromHex(id) + objID, err := bson.ObjectIDFromHex(id) if err != nil { return nil, err } @@ -113,7 +113,7 @@ func (dbService *ManagementUserDBService) UpdateUser( ) error { ctx, cancel := dbService.getContext() defer cancel() - objID, err := primitive.ObjectIDFromHex(id) + objID, err := bson.ObjectIDFromHex(id) if err != nil { return err } @@ -149,7 +149,7 @@ func (dbService *ManagementUserDBService) DeleteUser( } // delete the user - objID, err := primitive.ObjectIDFromHex(id) + objID, err := bson.ObjectIDFromHex(id) if err != nil { return err } @@ -204,9 +204,9 @@ func (dbService *ManagementUserDBService) GetUsersByIDs( ctx, cancel := dbService.getContext() defer cancel() - var objIDs []primitive.ObjectID + var objIDs []bson.ObjectID for _, id := range ids { - objID, err := primitive.ObjectIDFromHex(id) + objID, err := bson.ObjectIDFromHex(id) if err != nil { return nil, err } diff --git a/pkg/db/management-user/permissions.go b/pkg/db/management-user/permissions.go index 290b1d8d..287c23ab 100644 --- a/pkg/db/management-user/permissions.go +++ b/pkg/db/management-user/permissions.go @@ -1,15 +1,15 @@ package managementuser import ( - "fmt" "log/slog" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) +var permissionIndexNames []string + var indexesForPermissionsCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -28,18 +28,17 @@ func (dbService *ManagementUserDBService) DropIndexForPermissionsCollection(inst defer cancel() if dropAll { - _, err := dbService.collectionPermissions(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionPermissions(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for permissions: ", slog.String("error", err.Error())) } } else { - for _, index := range indexesForPermissionsCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for permissions collection: ", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range permissionIndexNames { + if indexName == "" { + slog.Error("Index name is empty for permissions collection") continue } - indexName := *index.Options.Name - _, err := dbService.collectionPermissions(instanceID).Indexes().DropOne(ctx, indexName) + err := dbService.collectionPermissions(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for permissions: ", slog.String("error", err.Error()), slog.String("indexName", indexName)) } @@ -50,10 +49,11 @@ func (dbService *ManagementUserDBService) DropIndexForPermissionsCollection(inst func (dbService *ManagementUserDBService) CreateDefaultIndexesForPermissionsCollection(instanceID string) { ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionPermissions(instanceID).Indexes().CreateMany(ctx, indexesForPermissionsCollection) + names, err := dbService.collectionPermissions(instanceID).Indexes().CreateMany(ctx, indexesForPermissionsCollection) if err != nil { slog.Error("Error creating index for permissions: ", slog.String("error", err.Error())) } + permissionIndexNames = names } // Create permission @@ -82,7 +82,7 @@ func (dbService *ManagementUserDBService) CreatePermission( if err != nil { return nil, err } - permission.ID = res.InsertedID.(primitive.ObjectID) + permission.ID = res.InsertedID.(bson.ObjectID) return permission, nil } @@ -94,7 +94,7 @@ func (dbService *ManagementUserDBService) GetPermissionByID( ctx, cancel := dbService.getContext() defer cancel() - objID, err := primitive.ObjectIDFromHex(permissionID) + objID, err := bson.ObjectIDFromHex(permissionID) if err != nil { return nil, err } @@ -195,7 +195,7 @@ func (dbService *ManagementUserDBService) UpdatePermissionLimiter( ctx, cancel := dbService.getContext() defer cancel() - objID, err := primitive.ObjectIDFromHex(permissionID) + objID, err := bson.ObjectIDFromHex(permissionID) if err != nil { return err } @@ -217,7 +217,7 @@ func (dbService *ManagementUserDBService) DeletePermission( ctx, cancel := dbService.getContext() defer cancel() - objID, err := primitive.ObjectIDFromHex(permissionID) + objID, err := bson.ObjectIDFromHex(permissionID) if err != nil { return err } diff --git a/pkg/db/management-user/service-users.go b/pkg/db/management-user/service-users.go index 9c32d97a..3b0d17df 100644 --- a/pkg/db/management-user/service-users.go +++ b/pkg/db/management-user/service-users.go @@ -2,16 +2,16 @@ package managementuser import ( "errors" - "fmt" "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) +var serviceUserAPIKeyIndexNames []string + func (dbService *ManagementUserDBService) collectionServiceUsers(instanceID string) *mongo.Collection { return dbService.DBClient.Database(dbService.getDBName(instanceID)).Collection(COLLECTION_NAME_SERVICE_USERS) } @@ -40,18 +40,17 @@ func (dbService *ManagementUserDBService) DropIndexForServiceUserAPIKeysCollecti defer cancel() if dropAll { - _, err := dbService.collectionServiceUserAPIKeys(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionServiceUserAPIKeys(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for service user API keys: ", slog.String("error", err.Error())) } } else { - for _, index := range indexesForServiceUserAPIKeysCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for service user API keys collection: ", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range serviceUserAPIKeyIndexNames { + if indexName == "" { + slog.Error("Index name is empty for service user API keys collection") continue } - indexName := *index.Options.Name - _, err := dbService.collectionServiceUserAPIKeys(instanceID).Indexes().DropOne(ctx, indexName) + err := dbService.collectionServiceUserAPIKeys(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for service user API keys: ", slog.String("error", err.Error()), slog.String("indexName", indexName)) } @@ -63,10 +62,11 @@ func (dbService *ManagementUserDBService) CreateDefaultIndexesForServiceUserAPIK ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionServiceUserAPIKeys(instanceID).Indexes().CreateMany(ctx, indexesForServiceUserAPIKeysCollection) + names, err := dbService.collectionServiceUserAPIKeys(instanceID).Indexes().CreateMany(ctx, indexesForServiceUserAPIKeysCollection) if err != nil { slog.Error("Error creating index for service user API keys: ", slog.String("error", err.Error())) } + serviceUserAPIKeyIndexNames = names } // CreateServiceUser creates a new service user @@ -92,7 +92,7 @@ func (dbService *ManagementUserDBService) CreateServiceUser(instanceID string, l return nil, err } - serviceUser.ID = result.InsertedID.(primitive.ObjectID) + serviceUser.ID = result.InsertedID.(bson.ObjectID) return serviceUser, nil } @@ -102,7 +102,7 @@ func (dbService *ManagementUserDBService) GetServiceUserByID(instanceID string, ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(id) + _id, err := bson.ObjectIDFromHex(id) if err != nil { return nil, err } @@ -148,7 +148,7 @@ func (dbService *ManagementUserDBService) DeleteServiceUser(instanceID string, i ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(id) + _id, err := bson.ObjectIDFromHex(id) if err != nil { slog.Error("Error converting ID to ObjectID", slog.String("error", err.Error())) return err @@ -180,7 +180,7 @@ func (dbService *ManagementUserDBService) UpdateServiceUser(instanceID string, i ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(id) + _id, err := bson.ObjectIDFromHex(id) if err != nil { slog.Error("Error converting ID to ObjectID", slog.String("error", err.Error())) return err @@ -258,7 +258,7 @@ func (dbService *ManagementUserDBService) DeleteServiceUserAPIKey(instanceID str ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(id) + _id, err := bson.ObjectIDFromHex(id) if err != nil { return err } diff --git a/pkg/db/management-user/sessions.go b/pkg/db/management-user/sessions.go index 94214d07..2798553b 100644 --- a/pkg/db/management-user/sessions.go +++ b/pkg/db/management-user/sessions.go @@ -1,16 +1,16 @@ package managementuser import ( - "fmt" "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) +var sessionIndexNames []string + func (dbService *ManagementUserDBService) collectionSessions(instanceID string) *mongo.Collection { return dbService.DBClient.Database(dbService.getDBName(instanceID)).Collection(COLLECTION_NAME_SESSIONS) } @@ -27,18 +27,17 @@ func (dbService *ManagementUserDBService) DropIndexForSessionsCollection(instanc defer cancel() if dropAll { - _, err := dbService.collectionSessions(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionSessions(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for sessions", slog.String("error", err.Error())) } } else { - for _, index := range indexesForSessionsCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for sessions collection: ", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range sessionIndexNames { + if indexName == "" { + slog.Error("Index name is empty for sessions collection") continue } - indexName := *index.Options.Name - _, err := dbService.collectionSessions(instanceID).Indexes().DropOne(ctx, indexName) + err := dbService.collectionSessions(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for sessions", slog.String("error", err.Error()), slog.String("indexName", indexName)) } @@ -50,10 +49,11 @@ func (dbService *ManagementUserDBService) CreateDefaultIndexesForSessionsCollect ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionSessions(instanceID).Indexes().CreateMany(ctx, indexesForSessionsCollection) + names, err := dbService.collectionSessions(instanceID).Indexes().CreateMany(ctx, indexesForSessionsCollection) if err != nil { slog.Error("Error creating index for sessions: ", slog.String("error", err.Error())) } + sessionIndexNames = names } // Session represents a user session, created when a user logs in @@ -74,7 +74,7 @@ func (dbService *ManagementUserDBService) CreateSession( if err != nil { return nil, err } - session.ID = res.InsertedID.(primitive.ObjectID) + session.ID = res.InsertedID.(bson.ObjectID) return session, nil } @@ -87,7 +87,7 @@ func (dbService *ManagementUserDBService) GetSession( defer cancel() var session Session - objID, err := primitive.ObjectIDFromHex(sessionID) + objID, err := bson.ObjectIDFromHex(sessionID) if err != nil { return nil, err } @@ -106,7 +106,7 @@ func (dbService *ManagementUserDBService) DeleteSession( ctx, cancel := dbService.getContext() defer cancel() - objID, err := primitive.ObjectIDFromHex(sessionID) + objID, err := bson.ObjectIDFromHex(sessionID) if err != nil { return err } diff --git a/pkg/db/management-user/types.go b/pkg/db/management-user/types.go index 58fa968b..ecd22168 100644 --- a/pkg/db/management-user/types.go +++ b/pkg/db/management-user/types.go @@ -3,13 +3,13 @@ package managementuser import ( "time" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) // enum for the subject type type Permission struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + ID bson.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` SubjectID string `json:"subjectId,omitempty" bson:"subjectId,omitempty"` SubjectType string `json:"subjectType,omitempty" bson:"subjectType,omitempty"` ResourceType string `json:"resourceType,omitempty" bson:"resourceType,omitempty"` @@ -25,7 +25,7 @@ type Permission struct { // Action is the action that is allowed e.g., download_responses, upload_survey, etc. type ManagementUser struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + ID bson.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` Sub string `json:"sub,omitempty" bson:"sub,omitempty"` Email string `json:"email,omitempty" bson:"email,omitempty"` Username string `json:"username,omitempty" bson:"username,omitempty"` @@ -37,42 +37,42 @@ type ManagementUser struct { } type Session struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` - UserID string `json:"userId,omitempty" bson:"userId,omitempty"` - RenewToken string `json:"renewToken,omitempty" bson:"renewToken,omitempty"` - CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt,omitempty"` + ID bson.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + UserID string `json:"userId,omitempty" bson:"userId,omitempty"` + RenewToken string `json:"renewToken,omitempty" bson:"renewToken,omitempty"` + CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt,omitempty"` } type ServiceUser struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` - Label string `json:"label,omitempty" bson:"label,omitempty"` - Description string `json:"description,omitempty" bson:"description,omitempty"` - CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt,omitempty"` + ID bson.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + Label string `json:"label,omitempty" bson:"label,omitempty"` + Description string `json:"description,omitempty" bson:"description,omitempty"` + CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt,omitempty"` } type ServiceUserAPIKey struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` - ServiceUserID string `json:"serviceUserId,omitempty" bson:"serviceUserId,omitempty"` - Key string `json:"key,omitempty" bson:"key,omitempty"` - ExpiresAt *time.Time `json:"expiresAt,omitempty" bson:"expiresAt,omitempty"` - CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt,omitempty"` - LastUsedAt time.Time `json:"lastUsedAt,omitempty" bson:"lastUsedAt,omitempty"` + ID bson.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + ServiceUserID string `json:"serviceUserId,omitempty" bson:"serviceUserId,omitempty"` + Key string `json:"key,omitempty" bson:"key,omitempty"` + ExpiresAt *time.Time `json:"expiresAt,omitempty" bson:"expiresAt,omitempty"` + CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt,omitempty"` + LastUsedAt time.Time `json:"lastUsedAt,omitempty" bson:"lastUsedAt,omitempty"` } type AppRole struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` - SubjectID string `json:"subjectId,omitempty" bson:"subjectId,omitempty"` - SubjectType string `json:"subjectType,omitempty" bson:"subjectType,omitempty"` - AppName string `json:"appName,omitempty" bson:"appName,omitempty"` - Role string `json:"role,omitempty" bson:"role,omitempty"` - CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt,omitempty"` + ID bson.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + SubjectID string `json:"subjectId,omitempty" bson:"subjectId,omitempty"` + SubjectType string `json:"subjectType,omitempty" bson:"subjectType,omitempty"` + AppName string `json:"appName,omitempty" bson:"appName,omitempty"` + Role string `json:"role,omitempty" bson:"role,omitempty"` + CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt,omitempty"` } type AppRoleTemplate struct { - ID primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` - AppName string `json:"appName,omitempty" bson:"appName,omitempty"` - Role string `json:"role,omitempty" bson:"role,omitempty"` - RequiredPermissions []Permission `json:"requiredPermissions,omitempty" bson:"requiredPermissions,omitempty"` - CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt,omitempty"` - UpdatedAt time.Time `json:"updatedAt,omitempty" bson:"updatedAt,omitempty"` + ID bson.ObjectID `json:"id,omitempty" bson:"_id,omitempty"` + AppName string `json:"appName,omitempty" bson:"appName,omitempty"` + Role string `json:"role,omitempty" bson:"role,omitempty"` + RequiredPermissions []Permission `json:"requiredPermissions,omitempty" bson:"requiredPermissions,omitempty"` + CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt,omitempty"` + UpdatedAt time.Time `json:"updatedAt,omitempty" bson:"updatedAt,omitempty"` } From beaacbe127af2483ef8160fae5e0df28684e1d64 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Mon, 16 Mar 2026 08:09:55 +0100 Subject: [PATCH 03/38] migrate global-infos db package to mongo-driver v2 --- pkg/db/global-infos/blocked-jwts.go | 23 ++++++++++++----------- pkg/db/global-infos/db.go | 10 +++++----- pkg/db/global-infos/temptoken.go | 23 ++++++++++++----------- 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/pkg/db/global-infos/blocked-jwts.go b/pkg/db/global-infos/blocked-jwts.go index 3226c126..5322c3d6 100644 --- a/pkg/db/global-infos/blocked-jwts.go +++ b/pkg/db/global-infos/blocked-jwts.go @@ -1,15 +1,16 @@ package globalinfos import ( - "fmt" "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) +var blockedJwtIndexNames []string + type BlockedJwt struct { Token string `bson:"token"` ExpiresAt time.Time `bson:"expiresAt"` @@ -35,18 +36,17 @@ func (dbService *GlobalInfosDBService) DropIndexForBlockedJwtsCollection(dropAll defer cancel() if dropAll { - _, err := dbService.collectionBlockedJwts().Indexes().DropAll(ctx) + err := dbService.collectionBlockedJwts().Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for blocked jwts", slog.String("error", err.Error())) } } else { - for _, index := range indexesForBlockedJwtsCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for blocked jwts collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range blockedJwtIndexNames { + if indexName == "" { + slog.Error("Index name is empty for blocked jwts collection") continue } - indexName := *index.Options.Name - _, err := dbService.collectionBlockedJwts().Indexes().DropOne(ctx, indexName) + err := dbService.collectionBlockedJwts().Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for blocked jwts", slog.String("error", err.Error()), slog.String("indexName", indexName)) } @@ -58,10 +58,11 @@ func (dbService *GlobalInfosDBService) CreateDefaultIndexesForBlockedJwtsCollect ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionBlockedJwts().Indexes().CreateMany(ctx, indexesForBlockedJwtsCollection) + names, err := dbService.collectionBlockedJwts().Indexes().CreateMany(ctx, indexesForBlockedJwtsCollection) if err != nil { slog.Error("Error creating index for blocked jwts", slog.String("error", err.Error())) } + blockedJwtIndexNames = names } // AddBlockedJwt adds a JWT token to the blocked list with the specified expiration time diff --git a/pkg/db/global-infos/db.go b/pkg/db/global-infos/db.go index 92d0dc66..0c4218da 100644 --- a/pkg/db/global-infos/db.go +++ b/pkg/db/global-infos/db.go @@ -6,9 +6,9 @@ import ( "time" "github.com/case-framework/case-backend/pkg/db" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) // collection names @@ -26,10 +26,10 @@ type GlobalInfosDBService struct { } func NewGlobalInfosDBService(configs db.DBConfig) (*GlobalInfosDBService, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) + _, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) defer cancel() - dbClient, err := mongo.Connect(ctx, + dbClient, err := mongo.Connect( options.Client().ApplyURI(configs.URI), options.Client().SetMaxConnIdleTime(time.Duration(configs.IdleConnTimeout)*time.Second), options.Client().SetMaxPoolSize(configs.MaxPoolSize), diff --git a/pkg/db/global-infos/temptoken.go b/pkg/db/global-infos/temptoken.go index 458f0bc5..a09243ce 100644 --- a/pkg/db/global-infos/temptoken.go +++ b/pkg/db/global-infos/temptoken.go @@ -2,18 +2,19 @@ package globalinfos import ( "errors" - "fmt" "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" userTypes "github.com/case-framework/case-backend/pkg/user-management/types" umUtils "github.com/case-framework/case-backend/pkg/user-management/utils" ) +var temptokenIndexNames []string + var indexesForTemptokensCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -42,17 +43,16 @@ func (dbService *GlobalInfosDBService) DropIndexForTemptokensCollection(dropAll defer cancel() if dropAll { - if _, err := dbService.collectionTemptokens().Indexes().DropAll(ctx); err != nil { + if err := dbService.collectionTemptokens().Indexes().DropAll(ctx); err != nil { slog.Error("Error dropping indexes for temptokens", slog.String("error", err.Error())) } } else { - for _, index := range indexesForTemptokensCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for temptokens collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range temptokenIndexNames { + if indexName == "" { + slog.Error("Index name is empty for temptokens collection") continue } - indexName := *index.Options.Name - _, err := dbService.collectionTemptokens().Indexes().DropOne(ctx, indexName) + err := dbService.collectionTemptokens().Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for temptokens", slog.String("error", err.Error()), slog.String("indexName", indexName)) } @@ -64,10 +64,11 @@ func (dbService *GlobalInfosDBService) CreateDefaultIndexesForTemptokensCollecti ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionTemptokens().Indexes().CreateMany(ctx, indexesForTemptokensCollection) + names, err := dbService.collectionTemptokens().Indexes().CreateMany(ctx, indexesForTemptokensCollection) if err != nil { slog.Error("Error creating index for temptokens", slog.String("error", err.Error())) } + temptokenIndexNames = names } func (dbService *GlobalInfosDBService) AddTempToken(t userTypes.TempToken) (token string, err error) { From 6e75dcc16bff028a913305eb977b81de9ba02be1 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Mon, 16 Mar 2026 12:42:00 +0100 Subject: [PATCH 04/38] migrate participant-user db package to mongo-driver v2 --- mongodb-driver-update.md | 15 +++++- pkg/db/participant-user/db.go | 8 +-- pkg/db/participant-user/failedOtpAttempts.go | 23 +++++---- pkg/db/participant-user/otps.go | 23 +++++---- pkg/db/participant-user/renew-tokens.go | 23 +++++---- pkg/db/participant-user/user-attributes.go | 36 ++++++------- pkg/db/participant-user/users.go | 53 +++++++++----------- pkg/db/study/confidentialResponses.go | 25 ++++----- pkg/db/study/db.go | 8 +-- pkg/db/study/taskQueue.go | 35 ++++++------- pkg/user-management/types/contact-info.go | 14 +++--- pkg/user-management/types/profile.go | 14 +++--- pkg/user-management/types/user-attributes.go | 12 ++--- pkg/user-management/types/user.go | 10 ++-- pkg/user-management/utils/new-user-init.go | 4 +- 15 files changed, 157 insertions(+), 146 deletions(-) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index de971034..e3a69693 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -20,8 +20,19 @@ #### scheduled-emails -- save scheduled-emails: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (testing? Line 128/129) +- save scheduled-emails: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal #### sms-templates -- save sms template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (testing? Line 128/129) +- save sms template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal + +### participant user + +#### user-attributes + +- The UpdateOptions has been chnaged to UpdateOneOptions to configure UpdateOne operation. + +#### users + +- add user: MongoDB Go Driver v2 no longer allows constructing or modifying option structs directly, so update options must now be created through the new builder API (options.UpdateOne().SetUpsert(true)) instead of setting fields on UpdateOptions manually. +- update user in db: FindOneAndReplaceOptions can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOneAndReplace().SetReturnDocument(options.After)) instead of setting option fields directly. diff --git a/pkg/db/participant-user/db.go b/pkg/db/participant-user/db.go index 73947e35..bf1b8d1c 100644 --- a/pkg/db/participant-user/db.go +++ b/pkg/db/participant-user/db.go @@ -6,9 +6,9 @@ import ( "time" "github.com/case-framework/case-backend/pkg/db" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) // collection names @@ -32,7 +32,7 @@ func NewParticipantUserDBService(configs db.DBConfig) (*ParticipantUserDBService ctx, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) defer cancel() - dbClient, err := mongo.Connect(ctx, + dbClient, err := mongo.Connect( options.Client().ApplyURI(configs.URI), options.Client().SetMaxConnIdleTime(time.Duration(configs.IdleConnTimeout)*time.Second), options.Client().SetMaxPoolSize(configs.MaxPoolSize), diff --git a/pkg/db/participant-user/failedOtpAttempts.go b/pkg/db/participant-user/failedOtpAttempts.go index e02b99b8..209e6867 100644 --- a/pkg/db/participant-user/failedOtpAttempts.go +++ b/pkg/db/participant-user/failedOtpAttempts.go @@ -1,19 +1,20 @@ package participantuser import ( - "fmt" "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) const ( FAILED_OTP_ATTEMP_WINDOW = 60 * 5 ) +var failedOTPAttemptIndexNames []string + type FailedOtpAttempt struct { Timestamp time.Time `json:"timestamp" bson:"timestamp"` UserID string `json:"userId" bson:"userID"` @@ -39,18 +40,17 @@ func (dbService *ParticipantUserDBService) DropIndexForFailedOtpAttemptsCollecti defer cancel() if dropAll { - _, err := dbService.collectionFailedOtpAttempts(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionFailedOtpAttempts(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for FailedOtpAttempts", slog.String("error", err.Error())) } } else { - for _, index := range indexesForFailedOtpAttemptsCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for FailedOtpAttempts collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range failedOTPAttemptIndexNames { + if indexName == "" { + slog.Error("Index name is empty for FailedOtpAttempts collection") continue } - indexName := *index.Options.Name - _, err := dbService.collectionFailedOtpAttempts(instanceID).Indexes().DropOne(ctx, indexName) + err := dbService.collectionFailedOtpAttempts(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for FailedOtpAttempts", slog.String("error", err.Error()), slog.String("indexName", indexName)) } @@ -62,10 +62,11 @@ func (dbService *ParticipantUserDBService) CreateDefaultIndexesForFailedOtpAttem ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionFailedOtpAttempts(instanceID).Indexes().CreateMany(ctx, indexesForFailedOtpAttemptsCollection) + names, err := dbService.collectionFailedOtpAttempts(instanceID).Indexes().CreateMany(ctx, indexesForFailedOtpAttemptsCollection) if err != nil { slog.Error("Error creating index for FailedOtpAttempts", slog.String("error", err.Error())) } + failedOTPAttemptIndexNames = names } func (dbService *ParticipantUserDBService) CountFailedOtpAttempts(instanceID string, userID string) (int64, error) { diff --git a/pkg/db/participant-user/otps.go b/pkg/db/participant-user/otps.go index b2179cf8..e208291b 100644 --- a/pkg/db/participant-user/otps.go +++ b/pkg/db/participant-user/otps.go @@ -2,13 +2,12 @@ package participantuser import ( "errors" - "fmt" "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" userTypes "github.com/case-framework/case-backend/pkg/user-management/types" ) @@ -17,6 +16,8 @@ const ( OTP_TTL = 60 * 15 ) +var otpIndexNames []string + var indexesForOTPsCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -38,18 +39,17 @@ func (dbService *ParticipantUserDBService) DropIndexForOTPsCollection(instanceID defer cancel() if dropAll { - _, err := dbService.collectionOTPs(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionOTPs(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for OTPs", slog.String("error", err.Error())) } } else { - for _, index := range indexesForOTPsCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for OTPs collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range otpIndexNames { + if indexName == "" { + slog.Error("Index name is empty for OTPs collection") continue } - indexName := *index.Options.Name - _, err := dbService.collectionOTPs(instanceID).Indexes().DropOne(ctx, indexName) + err := dbService.collectionOTPs(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for OTPs", slog.String("error", err.Error()), slog.String("indexName", indexName)) } @@ -61,10 +61,11 @@ func (dbService *ParticipantUserDBService) CreateDefaultIndexesForOTPsCollection ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionOTPs(instanceID).Indexes().CreateMany(ctx, indexesForOTPsCollection) + names, err := dbService.collectionOTPs(instanceID).Indexes().CreateMany(ctx, indexesForOTPsCollection) if err != nil { slog.Error("Error creating index for OTPs", slog.String("error", err.Error())) } + otpIndexNames = names } func (dbService *ParticipantUserDBService) CreateOTP(instanceID string, userID string, code string, t userTypes.OTPType, maxOTPCount int64) error { diff --git a/pkg/db/participant-user/renew-tokens.go b/pkg/db/participant-user/renew-tokens.go index f95a303c..f35c43cf 100644 --- a/pkg/db/participant-user/renew-tokens.go +++ b/pkg/db/participant-user/renew-tokens.go @@ -2,13 +2,12 @@ package participantuser import ( "errors" - "fmt" "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" userTypes "github.com/case-framework/case-backend/pkg/user-management/types" ) @@ -18,6 +17,8 @@ const ( RENEW_TOKEN_DEFAULT_LIFETIME = 60 * 60 * 24 * 90 ) +var renewTokenIndexNames []string + var indexesForRenewTokensCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -53,18 +54,17 @@ func (dbService *ParticipantUserDBService) DropIndexForRenewTokensCollection(ins defer cancel() if dropAll { - _, err := dbService.collectionRenewTokens(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionRenewTokens(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for renew tokens", slog.String("error", err.Error())) } } else { - for _, index := range indexesForRenewTokensCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for renew tokens collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range renewTokenIndexNames { + if indexName == "" { + slog.Error("Index name is empty for renew tokens collection") continue } - indexName := *index.Options.Name - _, err := dbService.collectionRenewTokens(instanceID).Indexes().DropOne(ctx, indexName) + err := dbService.collectionRenewTokens(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for renew tokens", slog.String("error", err.Error()), slog.String("indexName", indexName)) } @@ -76,12 +76,13 @@ func (dbService *ParticipantUserDBService) CreateDefaultIndexesForRenewTokensCol ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionRenewTokens(instanceID).Indexes().CreateMany( + names, err := dbService.collectionRenewTokens(instanceID).Indexes().CreateMany( ctx, indexesForRenewTokensCollection, ) if err != nil { slog.Error("Error creating index for renew tokens", slog.String("error", err.Error())) } + renewTokenIndexNames = names } func (dbService *ParticipantUserDBService) CreateRenewToken(instanceID string, userID string, token string, lifeTimeInSec int, sessionID string) error { diff --git a/pkg/db/participant-user/user-attributes.go b/pkg/db/participant-user/user-attributes.go index 9e77136e..e62fd318 100644 --- a/pkg/db/participant-user/user-attributes.go +++ b/pkg/db/participant-user/user-attributes.go @@ -2,18 +2,18 @@ package participantuser import ( "context" - "fmt" "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" userTypes "github.com/case-framework/case-backend/pkg/user-management/types" ) +var participantUserAttributeIndexNames []string + var indexesForParticipantUserAttributesCollection = []mongo.IndexModel{ { Keys: bson.D{{Key: "userId", Value: 1}, {Key: "type", Value: 1}}, @@ -26,18 +26,17 @@ func (dbService *ParticipantUserDBService) DropIndexForParticipantUserAttributes defer cancel() if dropAll { - _, err := dbService.collectionParticipantUserAttributes(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionParticipantUserAttributes(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for participant user attributes", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, index := range indexesForParticipantUserAttributesCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for participant user attributes collection", slog.String("index", fmt.Sprintf("%+v", index)), slog.String("instanceID", instanceID)) + for _, indexName := range participantUserAttributeIndexNames { + if indexName == "" { + slog.Error("Index name is empty for participant user attributes collection", slog.String("instanceID", instanceID)) continue } - indexName := *index.Options.Name - _, err := dbService.collectionParticipantUserAttributes(instanceID).Indexes().DropOne(ctx, indexName) + err := dbService.collectionParticipantUserAttributes(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for participant user attributes", slog.String("error", err.Error()), slog.String("indexName", indexName), slog.String("instanceID", instanceID)) } @@ -49,10 +48,11 @@ func (dbService *ParticipantUserDBService) CreateDefaultIndexesForParticipantUse ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionParticipantUserAttributes(instanceID).Indexes().CreateMany(ctx, indexesForParticipantUserAttributesCollection) + names, err := dbService.collectionParticipantUserAttributes(instanceID).Indexes().CreateMany(ctx, indexesForParticipantUserAttributesCollection) if err != nil { slog.Error("Error creating index for participant user attributes", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } + participantUserAttributeIndexNames = names } // Create or update a user attribute for a user by type @@ -65,7 +65,7 @@ func (dbService *ParticipantUserDBService) SetUserAttribute( ctx, cancel := dbService.getContext() defer cancel() - userIDObj, err := primitive.ObjectIDFromHex(userID) + userIDObj, err := bson.ObjectIDFromHex(userID) if err != nil { return err } @@ -74,7 +74,7 @@ func (dbService *ParticipantUserDBService) SetUserAttribute( ctx, bson.M{"userId": userIDObj, "type": attributeType}, bson.M{"$set": bson.M{"attributes": attributes, "createdAt": time.Now().UTC()}}, - options.Update().SetUpsert(true), + options.UpdateOne().SetUpsert(true), ) return err } @@ -85,7 +85,7 @@ func (dbService *ParticipantUserDBService) DeleteAllUserAttributes( instanceID string, userID string, ) error { - userIDObj, err := primitive.ObjectIDFromHex(userID) + userIDObj, err := bson.ObjectIDFromHex(userID) if err != nil { return err } @@ -99,12 +99,12 @@ func (dbService *ParticipantUserDBService) DeleteUserAttribute(instanceID string ctx, cancel := dbService.getContext() defer cancel() - userIDObj, err := primitive.ObjectIDFromHex(userID) + userIDObj, err := bson.ObjectIDFromHex(userID) if err != nil { return err } - attributeIDObj, err := primitive.ObjectIDFromHex(attributeID) + attributeIDObj, err := bson.ObjectIDFromHex(attributeID) if err != nil { return err } @@ -118,7 +118,7 @@ func (dbService *ParticipantUserDBService) GetAttributesForUser(instanceID strin ctx, cancel := dbService.getContext() defer cancel() - userIDObj, err := primitive.ObjectIDFromHex(userID) + userIDObj, err := bson.ObjectIDFromHex(userID) if err != nil { return nil, err } diff --git a/pkg/db/participant-user/users.go b/pkg/db/participant-user/users.go index eb3ade50..a3d1758d 100644 --- a/pkg/db/participant-user/users.go +++ b/pkg/db/participant-user/users.go @@ -3,18 +3,18 @@ package participantuser import ( "context" "errors" - "fmt" "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" umTypes "github.com/case-framework/case-backend/pkg/user-management/types" ) +var participantUserIndexNames []string + var indexesForParticipantUsersCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -55,18 +55,17 @@ func (dbService *ParticipantUserDBService) DropIndexForParticipantUsersCollectio defer cancel() if dropAll { - _, err := dbService.collectionParticipantUsers(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionParticipantUsers(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for participant users", slog.String("error", err.Error())) } } else { - for _, index := range indexesForParticipantUsersCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for participant users collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range participantUserIndexNames { + if indexName == "" { + slog.Error("Index name is empty for participant users collection") continue } - indexName := *index.Options.Name - _, err := dbService.collectionParticipantUsers(instanceID).Indexes().DropOne(ctx, indexName) + err := dbService.collectionParticipantUsers(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for participant users", slog.String("error", err.Error()), slog.String("indexName", indexName)) } @@ -78,12 +77,13 @@ func (dbService *ParticipantUserDBService) CreateDefaultIndexesForParticipantUse ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionParticipantUsers(instanceID).Indexes().CreateMany( + names, err := dbService.collectionParticipantUsers(instanceID).Indexes().CreateMany( ctx, indexesForParticipantUsersCollection, ) if err != nil { slog.Error("Error creating index for participant users", slog.String("error", err.Error())) } + participantUserIndexNames = names } func (dbService *ParticipantUserDBService) FixFieldNameForContactInfos(instanceID string) error { @@ -106,13 +106,10 @@ func (dbService *ParticipantUserDBService) AddUser(instanceID string, user umTyp defer cancel() filter := bson.M{"account.accountID": user.Account.AccountID} - upsert := true - opts := options.UpdateOptions{ - Upsert: &upsert, - } + opts := options.UpdateOne().SetUpsert(true) res, err := dbService.collectionParticipantUsers(instanceID).UpdateOne(ctx, filter, bson.M{ "$setOnInsert": user, - }, &opts) + }, opts) if err != nil { return } @@ -122,7 +119,7 @@ func (dbService *ParticipantUserDBService) AddUser(instanceID string, user umTyp return } - id = res.UpsertedID.(primitive.ObjectID).Hex() + id = res.UpsertedID.(bson.ObjectID).Hex() return } @@ -130,7 +127,7 @@ func (dbService *ParticipantUserDBService) GetUser(instanceID, objectID string) ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(objectID) + _id, err := bson.ObjectIDFromHex(objectID) if err != nil { return umTypes.User{}, err } @@ -156,7 +153,7 @@ func (dbService *ParticipantUserDBService) GetUserByProfileID(instanceID, profil defer cancel() var user umTypes.User - _profileID, err := primitive.ObjectIDFromHex(profileID) + _profileID, err := bson.ObjectIDFromHex(profileID) if err != nil { return umTypes.User{}, err } @@ -169,7 +166,7 @@ func (dbService *ParticipantUserDBService) SaveFailedLoginAttempt(instanceID str ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(userID) + _id, err := bson.ObjectIDFromHex(userID) if err != nil { return err } @@ -188,7 +185,7 @@ func (dbService *ParticipantUserDBService) SavePasswordResetTrigger(instanceID s ctx, cancel := dbService.getContext() defer cancel() - _id, _ := primitive.ObjectIDFromHex(userID) + _id, _ := bson.ObjectIDFromHex(userID) filter := bson.M{"_id": _id} update := bson.M{"$push": bson.M{"account.passwordResetTriggers": time.Now().Unix()}} _, err := dbService.collectionParticipantUsers(instanceID).UpdateOne(ctx, filter, update) @@ -205,11 +202,9 @@ func (dbService *ParticipantUserDBService) _updateUserInDB(orgID string, user um elem := umTypes.User{} filter := bson.M{"_id": user.ID} - rd := options.After - fro := options.FindOneAndReplaceOptions{ - ReturnDocument: &rd, - } - err := dbService.collectionParticipantUsers(orgID).FindOneAndReplace(ctx, filter, user, &fro).Decode(&elem) + fro := options.FindOneAndReplace(). + SetReturnDocument(options.After) + err := dbService.collectionParticipantUsers(orgID).FindOneAndReplace(ctx, filter, user, fro).Decode(&elem) return elem, err } @@ -232,7 +227,7 @@ func (dbService *ParticipantUserDBService) DeleteUser(instanceID, userID string) ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(userID) + _id, err := bson.ObjectIDFromHex(userID) if err != nil { return err } @@ -256,7 +251,7 @@ func (dbService *ParticipantUserDBService) UpdateUser(instanceID string, userID ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(userID) + _id, err := bson.ObjectIDFromHex(userID) if err != nil { return err } diff --git a/pkg/db/study/confidentialResponses.go b/pkg/db/study/confidentialResponses.go index f6000d64..afa1b689 100644 --- a/pkg/db/study/confidentialResponses.go +++ b/pkg/db/study/confidentialResponses.go @@ -7,10 +7,9 @@ import ( "log/slog" studyTypes "github.com/case-framework/case-backend/pkg/study/types" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) var indexesForConfidentialResponsesCollection = []mongo.IndexModel{ @@ -23,6 +22,8 @@ var indexesForConfidentialResponsesCollection = []mongo.IndexModel{ }, } +var confidentialResponseIndexNames []string + func (dbService *StudyDBService) DropIndexForConfidentialResponsesCollection(instanceID string, studyKey string, dropAll bool) { ctx, cancel := dbService.getContext() defer cancel() @@ -30,18 +31,17 @@ func (dbService *StudyDBService) DropIndexForConfidentialResponsesCollection(ins collection := dbService.collectionConfidentialResponses(instanceID, studyKey) if dropAll { - _, err := collection.Indexes().DropAll(ctx) + err := collection.Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for confidential responses", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } } else { - for _, index := range indexesForConfidentialResponsesCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for confidential responses collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range confidentialResponseIndexNames { + if indexName == "" { + slog.Error("Index name is empty for confidential responses collection", slog.String("index", fmt.Sprintf("%+v", indexName))) continue } - indexName := *index.Options.Name - _, err := collection.Indexes().DropOne(ctx, indexName) + err := collection.Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for confidential responses", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey), slog.String("indexName", indexName)) } @@ -54,10 +54,11 @@ func (dbService *StudyDBService) CreateDefaultIndexesForConfidentialResponsesCol defer cancel() collection := dbService.collectionConfidentialResponses(instanceID, studyKey) - _, err := collection.Indexes().CreateMany(ctx, indexesForConfidentialResponsesCollection) + names, err := collection.Indexes().CreateMany(ctx, indexesForConfidentialResponsesCollection) if err != nil { slog.Error("Error creating index for confidential responses", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } + confidentialResponseIndexNames = names } func (dbService *StudyDBService) AddConfidentialResponse(instanceID string, studyKey string, response studyTypes.SurveyResponse) (string, error) { @@ -67,7 +68,7 @@ func (dbService *StudyDBService) AddConfidentialResponse(instanceID string, stud return "", errors.New("participantID must be defined") } res, err := dbService.collectionConfidentialResponses(instanceID, studyKey).InsertOne(ctx, response) - id := res.InsertedID.(primitive.ObjectID) + id := res.InsertedID.(bson.ObjectID) return id.Hex(), err } diff --git a/pkg/db/study/db.go b/pkg/db/study/db.go index 2d48c105..94b7a465 100644 --- a/pkg/db/study/db.go +++ b/pkg/db/study/db.go @@ -6,9 +6,9 @@ import ( "time" "github.com/case-framework/case-backend/pkg/db" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) // collection names @@ -41,7 +41,7 @@ func NewStudyDBService(configs db.DBConfig) (*StudyDBService, error) { ctx, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) defer cancel() - dbClient, err := mongo.Connect(ctx, + dbClient, err := mongo.Connect( options.Client().ApplyURI(configs.URI), options.Client().SetMaxConnIdleTime(time.Duration(configs.IdleConnTimeout)*time.Second), options.Client().SetMaxPoolSize(configs.MaxPoolSize), diff --git a/pkg/db/study/taskQueue.go b/pkg/db/study/taskQueue.go index 57817ff7..d1c79feb 100644 --- a/pkg/db/study/taskQueue.go +++ b/pkg/db/study/taskQueue.go @@ -5,10 +5,9 @@ import ( "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" studyTypes "github.com/case-framework/case-backend/pkg/study/types" ) @@ -24,23 +23,24 @@ var indexesForTaskQueueCollection = []mongo.IndexModel{ }, } +var taskQueueIndexNames []string + func (dbService *StudyDBService) DropIndexForTaskQueueCollection(instanceID string, dropAll bool) { ctx, cancel := dbService.getContext() defer cancel() if dropAll { - _, err := dbService.collectionTaskQueue(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionTaskQueue(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for task queue", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, index := range indexesForTaskQueueCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for task queue collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range taskQueueIndexNames { + if indexName == "" { + slog.Error("Index name is empty for task queue collection", slog.String("index", fmt.Sprintf("%+v", indexName))) continue } - indexName := *index.Options.Name - _, err := dbService.collectionTaskQueue(instanceID).Indexes().DropOne(ctx, indexName) + err := dbService.collectionTaskQueue(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for task queue", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("indexName", indexName)) } @@ -52,10 +52,11 @@ func (dbService *StudyDBService) CreateDefaultIndexesForTaskQueueCollection(inst ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionTaskQueue(instanceID).Indexes().CreateMany(ctx, indexesForTaskQueueCollection) + names, err := dbService.collectionTaskQueue(instanceID).Indexes().CreateMany(ctx, indexesForTaskQueueCollection) if err != nil { slog.Error("Error creating index for task queue", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } + taskQueueIndexNames = names } // create task @@ -82,7 +83,7 @@ func (dbService *StudyDBService) CreateTask( if err != nil { return task, err } - task.ID = ret.InsertedID.(primitive.ObjectID) + task.ID = ret.InsertedID.(bson.ObjectID) return task, nil } @@ -91,7 +92,7 @@ func (dbService *StudyDBService) GetTaskByID(instanceID string, taskID string) ( ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(taskID) + _id, err := bson.ObjectIDFromHex(taskID) if err != nil { return task, err } @@ -125,7 +126,7 @@ func (dbService *StudyDBService) UpdateTaskTotalCount(instanceID string, taskID ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(taskID) + _id, err := bson.ObjectIDFromHex(taskID) if err != nil { return err } @@ -148,7 +149,7 @@ func (dbService *StudyDBService) UpdateTaskProgress(instanceID string, taskID st ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(taskID) + _id, err := bson.ObjectIDFromHex(taskID) if err != nil { return err } @@ -177,7 +178,7 @@ func (dbService *StudyDBService) UpdateTaskCompleted( ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(taskID) + _id, err := bson.ObjectIDFromHex(taskID) if err != nil { return err } @@ -203,7 +204,7 @@ func (dbService *StudyDBService) DeleteTaskByID(instanceID string, taskID string ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(taskID) + _id, err := bson.ObjectIDFromHex(taskID) if err != nil { return err } diff --git a/pkg/user-management/types/contact-info.go b/pkg/user-management/types/contact-info.go index 8efc7268..db1a8916 100644 --- a/pkg/user-management/types/contact-info.go +++ b/pkg/user-management/types/contact-info.go @@ -1,12 +1,12 @@ package types -import "go.mongodb.org/mongo-driver/bson/primitive" +import "go.mongodb.org/mongo-driver/v2/bson" type ContactInfo struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id"` - Type ContactInfoType `bson:"type" json:"type"` - ConfirmedAt int64 `bson:"confirmedAt" json:"confirmedAt"` - ConfirmationLinkSentAt int64 `bson:"confirmationLinkSentAt" json:"confirmationLinkSentAt"` - Email string `bson:"email,omitempty" json:"email,omitempty"` - Phone string `bson:"phone,omitempty" json:"phone,omitempty"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id"` + Type ContactInfoType `bson:"type" json:"type"` + ConfirmedAt int64 `bson:"confirmedAt" json:"confirmedAt"` + ConfirmationLinkSentAt int64 `bson:"confirmationLinkSentAt" json:"confirmationLinkSentAt"` + Email string `bson:"email,omitempty" json:"email,omitempty"` + Phone string `bson:"phone,omitempty" json:"phone,omitempty"` } diff --git a/pkg/user-management/types/profile.go b/pkg/user-management/types/profile.go index 51fc8300..be3cd18b 100644 --- a/pkg/user-management/types/profile.go +++ b/pkg/user-management/types/profile.go @@ -1,12 +1,12 @@ package types -import "go.mongodb.org/mongo-driver/bson/primitive" +import "go.mongodb.org/mongo-driver/v2/bson" type Profile struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id"` - Alias string `bson:"alias" json:"alias"` - ConsentConfirmedAt int64 `bson:"consentConfirmedAt" json:"consentConfirmedAt"` - CreatedAt int64 `bson:"createdAt" json:"createdAt"` - AvatarID string `bson:"avatarID" json:"avatarID"` - MainProfile bool `bson:"mainProfile" json:"mainProfile"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id"` + Alias string `bson:"alias" json:"alias"` + ConsentConfirmedAt int64 `bson:"consentConfirmedAt" json:"consentConfirmedAt"` + CreatedAt int64 `bson:"createdAt" json:"createdAt"` + AvatarID string `bson:"avatarID" json:"avatarID"` + MainProfile bool `bson:"mainProfile" json:"mainProfile"` } diff --git a/pkg/user-management/types/user-attributes.go b/pkg/user-management/types/user-attributes.go index d38c219e..3b45185d 100644 --- a/pkg/user-management/types/user-attributes.go +++ b/pkg/user-management/types/user-attributes.go @@ -3,13 +3,13 @@ package types import ( "time" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) type UserAttributes struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id"` - UserID primitive.ObjectID `bson:"userId" json:"userId"` - Type string `bson:"type" json:"type"` - Attributes map[string]any `bson:"attributes" json:"attributes"` - CreatedAt time.Time `bson:"createdAt" json:"createdAt"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id"` + UserID bson.ObjectID `bson:"userId" json:"userId"` + Type string `bson:"type" json:"type"` + Attributes map[string]any `bson:"attributes" json:"attributes"` + CreatedAt time.Time `bson:"createdAt" json:"createdAt"` } diff --git a/pkg/user-management/types/user.go b/pkg/user-management/types/user.go index 925890f3..41e54ffb 100644 --- a/pkg/user-management/types/user.go +++ b/pkg/user-management/types/user.go @@ -4,7 +4,7 @@ import ( "errors" "time" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) const ACCOUNT_TYPE_EMAIL = "email" @@ -17,7 +17,7 @@ const ( ) type User struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id"` Account Account `bson:"account" json:"account"` Timestamps Timestamps `bson:"timestamps" json:"timestamps"` @@ -29,7 +29,7 @@ type User struct { // Add a new email address func (u *User) AddNewEmail(addr string, confirmed bool) { contactInfo := ContactInfo{ - ID: primitive.NewObjectID(), + ID: bson.NewObjectID(), Type: CONTACT_INFO_TYPE_EMAIL, ConfirmedAt: 0, Email: addr, @@ -71,7 +71,7 @@ func (u *User) SetPhoneNumber(phone string) { newContactInfos = append(newContactInfos, ci) } contactInfo := ContactInfo{ - ID: primitive.NewObjectID(), + ID: bson.NewObjectID(), Type: CONTACT_INFO_TYPE_PHONE, ConfirmedAt: 0, Phone: phone, @@ -181,7 +181,7 @@ func (u *User) ReplaceContactInfoInContactPreferences(oldId string, newId string // AddProfile generates unique ID and adds profile to the user's array func (u *User) AddProfile(p *Profile) { - p.ID = primitive.NewObjectID() + p.ID = bson.NewObjectID() p.CreatedAt = time.Now().Unix() u.Profiles = append(u.Profiles, *p) } diff --git a/pkg/user-management/utils/new-user-init.go b/pkg/user-management/utils/new-user-init.go index 64a47aad..b10847db 100644 --- a/pkg/user-management/utils/new-user-init.go +++ b/pkg/user-management/utils/new-user-init.go @@ -4,7 +4,7 @@ import ( "time" userTypes "github.com/case-framework/case-backend/pkg/user-management/types" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) func InitNewEmailUser( @@ -22,7 +22,7 @@ func InitNewEmailUser( }, Profiles: []userTypes.Profile{ { - ID: primitive.NewObjectID(), + ID: bson.NewObjectID(), Alias: BlurEmailAddress(email), MainProfile: true, AvatarID: "default", From 2b607fd49e0957b9436783ec3f797a8a77343e43 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Tue, 17 Mar 2026 09:59:19 +0100 Subject: [PATCH 05/38] use contect.Context instead of mongo.SessionContext --- pkg/db/participant-user/otps.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/db/participant-user/otps.go b/pkg/db/participant-user/otps.go index e208291b..65072b05 100644 --- a/pkg/db/participant-user/otps.go +++ b/pkg/db/participant-user/otps.go @@ -1,6 +1,7 @@ package participantuser import ( + "context" "errors" "log/slog" "time" @@ -78,7 +79,7 @@ func (dbService *ParticipantUserDBService) CreateOTP(instanceID string, userID s } defer session.EndSession(ctx) - createOTPIfLimitNotReached := func(sessCtx mongo.SessionContext) error { + createOTPIfLimitNotReached := func(sessCtx context.Context) error { filter := bson.M{"userID": userID} count, err := dbService.collectionOTPs(instanceID).CountDocuments(sessCtx, filter) @@ -99,7 +100,6 @@ func (dbService *ParticipantUserDBService) CreateOTP(instanceID string, userID s _, err = dbService.collectionOTPs(instanceID).InsertOne(sessCtx, otp) return err } - return mongo.WithSession(ctx, session, createOTPIfLimitNotReached) } From 5f34d1bd03b14b0ec8c4df34fa287b84e7b397e9 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Tue, 17 Mar 2026 10:10:25 +0100 Subject: [PATCH 06/38] update imports --- jobs/messaging/scheduled-messages.go | 2 +- jobs/user-management/generate-id-lookup.go | 2 +- jobs/user-management/main.go | 2 +- pkg/db/participant-user/db.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/jobs/messaging/scheduled-messages.go b/jobs/messaging/scheduled-messages.go index 082ab534..53073f0a 100644 --- a/jobs/messaging/scheduled-messages.go +++ b/jobs/messaging/scheduled-messages.go @@ -13,7 +13,7 @@ import ( studyTypes "github.com/case-framework/case-backend/pkg/study/types" umTypes "github.com/case-framework/case-backend/pkg/user-management/types" umUtils "github.com/case-framework/case-backend/pkg/user-management/utils" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" ) func handleScheduledMessages(wg *sync.WaitGroup) { diff --git a/jobs/user-management/generate-id-lookup.go b/jobs/user-management/generate-id-lookup.go index 1cda1ae5..f38e5cf3 100644 --- a/jobs/user-management/generate-id-lookup.go +++ b/jobs/user-management/generate-id-lookup.go @@ -7,7 +7,7 @@ import ( studyService "github.com/case-framework/case-backend/pkg/study" studyTypes "github.com/case-framework/case-backend/pkg/study/types" umTypes "github.com/case-framework/case-backend/pkg/user-management/types" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" ) func generateProfileIDLookup() { diff --git a/jobs/user-management/main.go b/jobs/user-management/main.go index d0a59117..765771b9 100644 --- a/jobs/user-management/main.go +++ b/jobs/user-management/main.go @@ -5,7 +5,7 @@ import ( "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" emailsending "github.com/case-framework/case-backend/pkg/messaging/email-sending" emailTypes "github.com/case-framework/case-backend/pkg/messaging/types" diff --git a/pkg/db/participant-user/db.go b/pkg/db/participant-user/db.go index bf1b8d1c..ba1628ea 100644 --- a/pkg/db/participant-user/db.go +++ b/pkg/db/participant-user/db.go @@ -29,7 +29,7 @@ type ParticipantUserDBService struct { } func NewParticipantUserDBService(configs db.DBConfig) (*ParticipantUserDBService, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) + _, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) defer cancel() dbClient, err := mongo.Connect( From 981027bee45a8a4ea5d4bdd4262909909753d7b6 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 18 Mar 2026 13:03:41 +0100 Subject: [PATCH 07/38] migrate study db package to mongo-driver v2 --- mongodb-driver-update.md | 35 ++++++++++++++++ pkg/db/study/confidential-id-map.go | 23 +++++------ pkg/db/study/confidentialResponses.go | 8 ++-- pkg/db/study/db.go | 2 +- pkg/db/study/participantFileInfos.go | 33 ++++++++-------- pkg/db/study/participants.go | 34 ++++++++-------- pkg/db/study/reports.go | 40 ++++++++----------- pkg/db/study/responses.go | 51 ++++++++++++------------ pkg/db/study/study-code-lists.go | 22 ++++++----- pkg/db/study/study-counters.go | 22 ++++++----- pkg/db/study/study-variables.go | 29 +++++++------- pkg/db/study/studyInfos.go | 29 +++++++------- pkg/db/study/studyRules.go | 37 +++++++++-------- pkg/db/study/surveys.go | 55 +++++++++++--------------- pkg/study/types/participant.go | 4 +- pkg/study/types/participantFileinfo.go | 14 +++---- pkg/study/types/report.go | 16 ++++---- pkg/study/types/survey.go | 18 ++++----- pkg/study/types/surveyResponse.go | 4 +- pkg/study/types/task.go | 14 +++---- 20 files changed, 260 insertions(+), 230 deletions(-) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index e3a69693..63d8ba16 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -11,6 +11,7 @@ - capture index names returned by CreateMany - use the stored names when dropping indexes - maybe store indexNames as field of messagingDBService struct?!? (e.g. `emailTemplateIndexNames map[string][]`) +- removed unused return value for context.WithTimeout() ### Messaging @@ -36,3 +37,37 @@ - add user: MongoDB Go Driver v2 no longer allows constructing or modifying option structs directly, so update options must now be created through the new builder API (options.UpdateOne().SetUpsert(true)) instead of setting fields on UpdateOptions manually. - update user in db: FindOneAndReplaceOptions can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOneAndReplace().SetReturnDocument(options.After)) instead of setting option fields directly. + +#### otps + +- update the callback for mongo.WithSession to use a context.Context implementation, rather than the custom mongo.SessionContext + +TEST: If you want to be extra safe, you can: +Deploy this change to a non‑production environment first. +Run a few test flows that: +exceed the maxOTPCount to ensure the “too many OTP requests” path still works, +run concurrent OTP creations to confirm only the allowed number of documents is written. + +### study + +#### participants + +- configure FindOneAndReplaceOptions through options.FindOneAndReplace().Set... instead of filling the struct fields directly. + +#### confidential responses + +- configure replace options via the options.Replace() builder (for example, options.Replace().SetUpsert(true)) instead of instantiating a ReplaceOptions struct literal and passing its address. + +#### study-rules + +- &options.FindOneOptions{ Sort: sortByPublished } becomes options.FindOne().SetSort(sortByPublished). + +#### reports + +- GetUniqueReportKeysForStudy:`Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop. + +#### surveys + +- GetSurveyKeysForStudy: `Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop. +- GetCurrentSurveyVersion: create FindOneOptions using the options.FindOne() builder and setters (for example, options.FindOne().SetSort(sortByPublishedDesc)) instead of instantiating &options.FindOneOptions{} and mutating its fields. +- GetSurveyVersions: create FindOptions using the options.Find() builder and its setters (for example, options.Find().SetProjection(...).SetSort(...)) instead of instantiating &options.FindOptions{} and mutating its fields. diff --git a/pkg/db/study/confidential-id-map.go b/pkg/db/study/confidential-id-map.go index 36d1d0a3..48e8e4cc 100644 --- a/pkg/db/study/confidential-id-map.go +++ b/pkg/db/study/confidential-id-map.go @@ -1,14 +1,15 @@ package study import ( - "fmt" "log/slog" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) +var confidentialIDMapIndexNames []string + var indexesForConfidentialIDMapCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -25,18 +26,17 @@ func (dbService *StudyDBService) DropIndexForConfidentialIDMapCollection(instanc collection := dbService.collectionConfidentialIDMap(instanceID) if dropAll { - _, err := collection.Indexes().DropAll(ctx) + err := collection.Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for confidentialIDMap", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, index := range indexesForConfidentialIDMapCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for confidentialIDMap collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range confidentialIDMapIndexNames { + if indexName == "" { + slog.Error("Index name is empty for confidentialIDMap collection") continue } - indexName := *index.Options.Name - _, err := collection.Indexes().DropOne(ctx, indexName) + err := collection.Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for confidentialIDMap", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("indexName", indexName)) } @@ -48,10 +48,11 @@ func (dbService *StudyDBService) CreateDefaultIndexesForConfidentialIDMapCollect ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionConfidentialIDMap(instanceID).Indexes().CreateMany(ctx, indexesForConfidentialIDMapCollection) + names, err := dbService.collectionConfidentialIDMap(instanceID).Indexes().CreateMany(ctx, indexesForConfidentialIDMapCollection) if err != nil { slog.Error("Error creating index for confidentialIDMap", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } + confidentialIDMapIndexNames = names } func (dbService *StudyDBService) AddConfidentialIDMapEntry(instanceID, confidentialID, profileID, studyKey string) error { diff --git a/pkg/db/study/confidentialResponses.go b/pkg/db/study/confidentialResponses.go index afa1b689..d221277e 100644 --- a/pkg/db/study/confidentialResponses.go +++ b/pkg/db/study/confidentialResponses.go @@ -82,10 +82,10 @@ func (dbService *StudyDBService) ReplaceConfidentialResponse(instanceID string, } upsert := true - options := options.ReplaceOptions{ - Upsert: &upsert, - } - _, err := dbService.collectionConfidentialResponses(instanceID, studyKey).ReplaceOne(ctx, filter, response, &options) + options := options.Replace(). + SetUpsert(upsert) + + _, err := dbService.collectionConfidentialResponses(instanceID, studyKey).ReplaceOne(ctx, filter, response, options) return err } diff --git a/pkg/db/study/db.go b/pkg/db/study/db.go index 94b7a465..64bc02ae 100644 --- a/pkg/db/study/db.go +++ b/pkg/db/study/db.go @@ -38,7 +38,7 @@ type StudyDBService struct { } func NewStudyDBService(configs db.DBConfig) (*StudyDBService, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) + _, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) defer cancel() dbClient, err := mongo.Connect( diff --git a/pkg/db/study/participantFileInfos.go b/pkg/db/study/participantFileInfos.go index 8d4e89df..481365a2 100644 --- a/pkg/db/study/participantFileInfos.go +++ b/pkg/db/study/participantFileInfos.go @@ -5,14 +5,15 @@ import ( "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" studytypes "github.com/case-framework/case-backend/pkg/study/types" ) +var participantFileIndexNames []string + var indexesForParticipantFilesCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -36,18 +37,17 @@ func (dbService *StudyDBService) DropIndexForParticipantFilesCollection(instance collection := dbService.collectionFiles(instanceID, studyKey) if dropAll { - _, err := collection.Indexes().DropAll(ctx) + err := collection.Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for participant files", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } } else { - for _, index := range indexesForParticipantFilesCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for participant files collection", slog.String("index", fmt.Sprintf("%+v", index)), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) + for _, indexName := range participantFileIndexNames { + if indexName == "" { + slog.Error("Index name is empty for participant files collection", slog.String("index", fmt.Sprintf("%+v", indexName)), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) continue } - indexName := *index.Options.Name - _, err := collection.Indexes().DropOne(ctx, indexName) + err := collection.Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for participant files", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey), slog.String("indexName", indexName)) } @@ -60,10 +60,11 @@ func (dbService *StudyDBService) CreateDefaultIndexesForParticipantFilesCollecti defer cancel() collection := dbService.collectionFiles(instanceID, studyKey) - _, err := collection.Indexes().CreateMany(ctx, indexesForParticipantFilesCollection) + names, err := collection.Indexes().CreateMany(ctx, indexesForParticipantFilesCollection) if err != nil { slog.Error("Error creating index for participant files", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } + participantFileIndexNames = names } // get one by id @@ -71,7 +72,7 @@ func (dbService *StudyDBService) GetParticipantFileInfoByID(instanceID string, s ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(fileInfoID) + _id, err := bson.ObjectIDFromHex(fileInfoID) if err != nil { return participantFileInfo, err } @@ -89,7 +90,7 @@ func (dbService *StudyDBService) DeleteParticipantFileInfoByID(instanceID string ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(fileInfoID) + _id, err := bson.ObjectIDFromHex(fileInfoID) if err != nil { return err } @@ -132,7 +133,7 @@ func (dbService *StudyDBService) GetParticipantFileInfos(instanceID string, stud ) sortByCreatedAt := bson.D{ - primitive.E{Key: "createdAt", Value: -1}, + {Key: "createdAt", Value: -1}, } opts := options.Find() @@ -161,7 +162,7 @@ func (dbService *StudyDBService) CreateParticipantFileInfo(instanceID string, st return fileInfo, err } - if oid, ok := result.InsertedID.(primitive.ObjectID); ok { + if oid, ok := result.InsertedID.(bson.ObjectID); ok { fileInfo.ID = oid } @@ -173,7 +174,7 @@ func (dbService *StudyDBService) UpdateParticipantFileInfoPathAndStatus(instance ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(fileInfoID) + _id, err := bson.ObjectIDFromHex(fileInfoID) if err != nil { return err } diff --git a/pkg/db/study/participants.go b/pkg/db/study/participants.go index b91f4f6a..dca24ec8 100644 --- a/pkg/db/study/participants.go +++ b/pkg/db/study/participants.go @@ -7,14 +7,15 @@ import ( "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" studyTypes "github.com/case-framework/case-backend/pkg/study/types" ) +var participantIndexNames []string + var indexesForParticipantsCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -56,18 +57,17 @@ func (dbService *StudyDBService) DropIndexForParticipantsCollection(instanceID s collection := dbService.collectionParticipants(instanceID, studyKey) if dropAll { - _, err := collection.Indexes().DropAll(ctx) + err := collection.Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for participants", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } } else { - for _, index := range indexesForParticipantsCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for participants collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range participantIndexNames { + if indexName == "" { + slog.Error("Index name is empty for participants collection", slog.String("index", fmt.Sprintf("%+v", indexName))) continue } - indexName := *index.Options.Name - _, err := collection.Indexes().DropOne(ctx, indexName) + err := collection.Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for participants", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey), slog.String("indexName", indexName)) } @@ -80,10 +80,11 @@ func (dbService *StudyDBService) CreateDefaultIndexesForParticipantsCollection(i defer cancel() collection := dbService.collectionParticipants(instanceID, studyKey) - _, err := collection.Indexes().CreateMany(ctx, indexesForParticipantsCollection) + names, err := collection.Indexes().CreateMany(ctx, indexesForParticipantsCollection) if err != nil { slog.Error("Error creating index for participants", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } + participantIndexNames = names } func (dbService *StudyDBService) SaveParticipantState(instanceID string, studyKey string, pState studyTypes.Participant) (studyTypes.Participant, error) { @@ -95,13 +96,12 @@ func (dbService *StudyDBService) SaveParticipantState(instanceID string, studyKe upsert := true rd := options.After - qOpts := options.FindOneAndReplaceOptions{ - Upsert: &upsert, - ReturnDocument: &rd, - } + qOpts := options.FindOneAndReplace(). + SetUpsert(upsert). + SetReturnDocument(rd) elem := studyTypes.Participant{} err := dbService.collectionParticipants(instanceID, studyKey).FindOneAndReplace( - ctx, filter, pState, &qOpts, + ctx, filter, pState, qOpts, ).Decode(&elem) return elem, err } @@ -117,7 +117,7 @@ func (dbService *StudyDBService) UpdateParticipantIfNotModified(instanceID strin filter["modifiedAt"] = bson.M{"$lte": pState.ModifiedAt} } - pState.ID = primitive.NilObjectID + pState.ID = bson.NilObjectID pState.ModifiedAt = time.Now().Unix() update := bson.M{"$set": pState} diff --git a/pkg/db/study/reports.go b/pkg/db/study/reports.go index ca3f0f09..fe0875a2 100644 --- a/pkg/db/study/reports.go +++ b/pkg/db/study/reports.go @@ -7,10 +7,9 @@ import ( "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" studyTypes "github.com/case-framework/case-backend/pkg/study/types" ) @@ -23,6 +22,8 @@ type ReportKeyFilters struct { ToTS int64 } +var reportIndexNames []string + var indexesForReportsCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -53,18 +54,17 @@ func (dbService *StudyDBService) DropIndexForReportsCollection(instanceID string collection := dbService.collectionReports(instanceID, studyKey) if dropAll { - _, err := collection.Indexes().DropAll(ctx) + err := collection.Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for reports", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } } else { - for _, index := range indexesForReportsCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for reports collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range reportIndexNames { + if indexName == "" { + slog.Error("Index name is empty for reports collection", slog.String("index", fmt.Sprintf("%+v", indexName))) continue } - indexName := *index.Options.Name - _, err := collection.Indexes().DropOne(ctx, indexName) + err := collection.Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for reports", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey), slog.String("indexName", indexName)) } @@ -77,10 +77,11 @@ func (dbService *StudyDBService) CreateDefaultIndexesForReportsCollection(instan defer cancel() collection := dbService.collectionReports(instanceID, studyKey) - _, err := collection.Indexes().CreateMany(ctx, indexesForReportsCollection) + names, err := collection.Indexes().CreateMany(ctx, indexesForReportsCollection) if err != nil { slog.Error("Error creating index for reports", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } + reportIndexNames = names } func (dbService *StudyDBService) SaveReport(instanceID string, studyKey string, report studyTypes.Report) error { @@ -95,7 +96,7 @@ func (dbService *StudyDBService) GetReportByID(instanceID string, studyKey strin ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(reportID) + _id, err := bson.ObjectIDFromHex(reportID) if err != nil { return report, err } @@ -127,7 +128,7 @@ func (dbService *StudyDBService) UpdateReportData( ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(reportID) + _id, err := bson.ObjectIDFromHex(reportID) if err != nil { return err } @@ -155,7 +156,7 @@ func (dbService *StudyDBService) UpdateReportData( } var reportSortOnTimestamp = bson.D{ - primitive.E{Key: "timestamp", Value: -1}, + {Key: "timestamp", Value: -1}, } // get report count for query @@ -280,16 +281,9 @@ func (dbService *StudyDBService) GetUniqueReportKeysForStudy( } } - res, err := dbService.collectionReports(instanceID, studyKey).Distinct(ctx, "key", filter) - if err != nil { + var keys []string + if err := dbService.collectionReports(instanceID, studyKey).Distinct(ctx, "key", filter).Decode(&keys); err != nil { return nil, err } - - keys := make([]string, 0, len(res)) - for _, r := range res { - if v, ok := r.(string); ok { - keys = append(keys, v) - } - } return keys, nil } diff --git a/pkg/db/study/responses.go b/pkg/db/study/responses.go index 08e760f4..06da9e58 100644 --- a/pkg/db/study/responses.go +++ b/pkg/db/study/responses.go @@ -7,14 +7,15 @@ import ( "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" studyTypes "github.com/case-framework/case-backend/pkg/study/types" ) +var responseIndexNames []string + var indexesForResponsesCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -55,18 +56,17 @@ func (dbService *StudyDBService) DropIndexForResponsesCollection(instanceID stri defer cancel() if dropAll { - _, err := dbService.collectionResponses(instanceID, studyKey).Indexes().DropAll(ctx) + err := dbService.collectionResponses(instanceID, studyKey).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for responses", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } } else { - for _, index := range indexesForResponsesCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for responses collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range responseIndexNames { + if indexName == "" { + slog.Error("Index name is empty for responses collection", slog.String("index", fmt.Sprintf("%+v", indexName))) continue } - indexName := *index.Options.Name - _, err := dbService.collectionResponses(instanceID, studyKey).Indexes().DropOne(ctx, indexName) + err := dbService.collectionResponses(instanceID, studyKey).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for responses", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey), slog.String("indexName", indexName)) } @@ -79,10 +79,11 @@ func (dbService *StudyDBService) CreateDefaultIndexesForResponsesCollection(inst defer cancel() collection := dbService.collectionResponses(instanceID, studyKey) - _, err := collection.Indexes().CreateMany(ctx, indexesForResponsesCollection) + names, err := collection.Indexes().CreateMany(ctx, indexesForResponsesCollection) if err != nil { slog.Error("Error creating index for responses", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } + responseIndexNames = names } func (dbService *StudyDBService) AddSurveyResponse(instanceID string, studyKey string, response studyTypes.SurveyResponse) (string, error) { @@ -93,7 +94,7 @@ func (dbService *StudyDBService) AddSurveyResponse(instanceID string, studyKey s response.ArrivedAt = time.Now().Unix() } res, err := dbService.collectionResponses(instanceID, studyKey).InsertOne(ctx, response) - id := res.InsertedID.(primitive.ObjectID) + id := res.InsertedID.(bson.ObjectID) return id.Hex(), err } @@ -102,7 +103,7 @@ func (dbService *StudyDBService) GetResponseByID(instanceID string, studyKey str ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(responseID) + _id, err := bson.ObjectIDFromHex(responseID) if err != nil { return response, err } @@ -158,11 +159,11 @@ func (dbService *StudyDBService) GetResponsesCount(instanceID string, studyKey s } type ResponseInfo struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Key string `bson:"key" json:"key"` - ParticipantID string `bson:"participantID" json:"participantId"` - VersionID string `bson:"versionID" json:"versionId"` - ArrivedAt int64 `bson:"arrivedAt" json:"arrivedAt"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Key string `bson:"key" json:"key"` + ParticipantID string `bson:"participantID" json:"participantId"` + VersionID string `bson:"versionID" json:"versionId"` + ArrivedAt int64 `bson:"arrivedAt" json:"arrivedAt"` } func (dbService *StudyDBService) GetResponseInfos(instanceID string, studyKey string, filter bson.M, page int64, limit int64) (responseInfos []ResponseInfo, paginationInfo *PaginationInfos, err error) { @@ -183,7 +184,7 @@ func (dbService *StudyDBService) GetResponseInfos(instanceID string, studyKey st skip := (paginationInfo.CurrentPage - 1) * paginationInfo.PageSize sortBySubmittedAt := bson.D{ - primitive.E{Key: "submittedAt", Value: -1}, + {Key: "submittedAt", Value: -1}, } opts := options.Find() @@ -192,11 +193,11 @@ func (dbService *StudyDBService) GetResponseInfos(instanceID string, studyKey st opts.SetLimit(paginationInfo.PageSize) projection := bson.D{ - primitive.E{Key: "_id", Value: 1}, - primitive.E{Key: "key", Value: 1}, - primitive.E{Key: "participantID", Value: 1}, - primitive.E{Key: "versionID", Value: 1}, - primitive.E{Key: "arrivedAt", Value: 1}, + {Key: "_id", Value: 1}, + {Key: "key", Value: 1}, + {Key: "participantID", Value: 1}, + {Key: "versionID", Value: 1}, + {Key: "arrivedAt", Value: 1}, } opts.SetProjection(projection) @@ -252,7 +253,7 @@ func (dbService *StudyDBService) DeleteResponseByID(instanceID string, studyKey ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(responseID) + _id, err := bson.ObjectIDFromHex(responseID) if err != nil { return err } diff --git a/pkg/db/study/study-code-lists.go b/pkg/db/study/study-code-lists.go index ab5fe0b9..7f672368 100644 --- a/pkg/db/study/study-code-lists.go +++ b/pkg/db/study/study-code-lists.go @@ -5,13 +5,15 @@ import ( "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" studytypes "github.com/case-framework/case-backend/pkg/study/types" ) +var studyCodeListIndexNames []string + var indexesForStudyCodeListsCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -29,18 +31,17 @@ func (dbService *StudyDBService) DropIndexForStudyCodeListsCollection(instanceID collection := dbService.collectionStudyCodeLists(instanceID) if dropAll { - _, err := collection.Indexes().DropAll(ctx) + err := collection.Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for studyCodeLists", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, index := range indexesForStudyCodeListsCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for studyCodeLists collection", slog.String("index", fmt.Sprintf("%+v", index)), slog.String("instanceID", instanceID)) + for _, indexName := range studyCodeListIndexNames { + if indexName == "" { + slog.Error("Index name is empty for studyCodeLists collection", slog.String("index", fmt.Sprintf("%+v", indexName)), slog.String("instanceID", instanceID)) continue } - indexName := *index.Options.Name - _, err := collection.Indexes().DropOne(ctx, indexName) + err := collection.Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for studyCodeLists", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("indexName", indexName)) } @@ -53,10 +54,11 @@ func (dbService *StudyDBService) CreateDefaultIndexesForStudyCodeListsCollection defer cancel() collection := dbService.collectionStudyCodeLists(instanceID) - _, err := collection.Indexes().CreateMany(ctx, indexesForStudyCodeListsCollection) + names, err := collection.Indexes().CreateMany(ctx, indexesForStudyCodeListsCollection) if err != nil { slog.Error("Error creating index for studyCodeLists", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } + studyCodeListIndexNames = names } func (dbService *StudyDBService) AddStudyCodeListEntry(instanceID string, studyKey string, listKey string, code string) error { diff --git a/pkg/db/study/study-counters.go b/pkg/db/study/study-counters.go index 13285e41..84e8825e 100644 --- a/pkg/db/study/study-counters.go +++ b/pkg/db/study/study-counters.go @@ -4,9 +4,9 @@ import ( "fmt" "log/slog" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" ) type StudyCounter struct { @@ -15,6 +15,8 @@ type StudyCounter struct { Value int64 `json:"value" bson:"value"` } +var studyCounterIndexNames []string + var indexesForStudyCountersCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -31,18 +33,17 @@ func (dbService *StudyDBService) DropIndexForStudyCountersCollection(instanceID collection := dbService.collectionStudyCounters(instanceID) if dropAll { - _, err := collection.Indexes().DropAll(ctx) + err := collection.Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for studyCounters", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, index := range indexesForStudyCountersCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for studyCounters collection", slog.String("index", fmt.Sprintf("%+v", index)), slog.String("instanceID", instanceID)) + for _, indexName := range studyCounterIndexNames { + if indexName == "" { + slog.Error("Index name is empty for studyCounters collection", slog.String("index", fmt.Sprintf("%+v", indexName)), slog.String("instanceID", instanceID)) continue } - indexName := *index.Options.Name - _, err := collection.Indexes().DropOne(ctx, indexName) + err := collection.Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for studyCounters", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("indexName", indexName)) } @@ -54,10 +55,11 @@ func (dbService *StudyDBService) CreateDefaultIndexesForStudyCountersCollection( ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionStudyCounters(instanceID).Indexes().CreateMany(ctx, indexesForStudyCountersCollection) + names, err := dbService.collectionStudyCounters(instanceID).Indexes().CreateMany(ctx, indexesForStudyCountersCollection) if err != nil { slog.Error("Error creating index for studyCounters", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } + studyCounterIndexNames = names } // Get current counter value (without incrementing) diff --git a/pkg/db/study/study-variables.go b/pkg/db/study/study-variables.go index 4e5ca7a1..7aa2dc38 100644 --- a/pkg/db/study/study-variables.go +++ b/pkg/db/study/study-variables.go @@ -6,14 +6,15 @@ import ( "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" studytypes "github.com/case-framework/case-backend/pkg/study/types" ) +var studyVariableIndexNames []string + var indexesForStudyVariablesCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -40,18 +41,17 @@ func (dbService *StudyDBService) DropIndexForStudyVariablesCollection(instanceID collection := dbService.collectionStudyVariables(instanceID) if dropAll { - _, err := collection.Indexes().DropAll(ctx) + err := collection.Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for studyVariables", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, index := range indexesForStudyVariablesCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for studyVariables collection", slog.String("index", fmt.Sprintf("%+v", index)), slog.String("instanceID", instanceID)) + for _, indexName := range studyVariableIndexNames { + if indexName == "" { + slog.Error("Index name is empty for studyVariables collection", slog.String("index", fmt.Sprintf("%+v", indexName)), slog.String("instanceID", instanceID)) continue } - indexName := *index.Options.Name - _, err := collection.Indexes().DropOne(ctx, indexName) + err := collection.Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for studyVariables", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("indexName", indexName)) } @@ -63,10 +63,11 @@ func (dbService *StudyDBService) CreateDefaultIndexesForStudyVariablesCollection ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionStudyVariables(instanceID).Indexes().CreateMany(ctx, indexesForStudyVariablesCollection) + names, err := dbService.collectionStudyVariables(instanceID).Indexes().CreateMany(ctx, indexesForStudyVariablesCollection) if err != nil { slog.Error("Error creating index for studyVariables", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } + studyVariableIndexNames = names } // create a study variable @@ -90,7 +91,7 @@ func (dbService *StudyDBService) CreateStudyVariable(instanceID string, variable if err != nil { return "", err } - id := res.InsertedID.(primitive.ObjectID) + id := res.InsertedID.(bson.ObjectID) return id.Hex(), nil } @@ -183,7 +184,7 @@ func (dbService *StudyDBService) GetStudyVariableByID(instanceID string, id stri ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(id) + _id, err := bson.ObjectIDFromHex(id) if err != nil { return studytypes.StudyVariables{}, err } @@ -216,7 +217,7 @@ func (dbService *StudyDBService) DeleteStudyVariableByID(instanceID string, id s ctx, cancel := dbService.getContext() defer cancel() - _id, err := primitive.ObjectIDFromHex(id) + _id, err := bson.ObjectIDFromHex(id) if err != nil { return err } diff --git a/pkg/db/study/studyInfos.go b/pkg/db/study/studyInfos.go index 1564c48b..adc49a39 100644 --- a/pkg/db/study/studyInfos.go +++ b/pkg/db/study/studyInfos.go @@ -4,14 +4,15 @@ import ( "fmt" "log/slog" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" studyTypes "github.com/case-framework/case-backend/pkg/study/types" ) +var studyInfoIndexNames []string + var indexesForStudyInfosCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -26,18 +27,17 @@ func (dbService *StudyDBService) DropIndexForStudyInfosCollection(instanceID str defer cancel() if dropAll { - _, err := dbService.collectionStudyInfos(instanceID).Indexes().DropAll(ctx) + err := dbService.collectionStudyInfos(instanceID).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for studyInfos", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, index := range indexesForStudyInfosCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for studyInfos collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range studyInfoIndexNames { + if indexName == "" { + slog.Error("Index name is empty for studyInfos collection", slog.String("index", fmt.Sprintf("%+v", indexName))) continue } - indexName := *index.Options.Name - _, err := dbService.collectionStudyInfos(instanceID).Indexes().DropOne(ctx, indexName) + err := dbService.collectionStudyInfos(instanceID).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for studyInfos", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("indexName", indexName)) } @@ -49,10 +49,11 @@ func (dbService *StudyDBService) CreateDefaultIndexesForStudyInfosCollection(ins ctx, cancel := dbService.getContext() defer cancel() - _, err := dbService.collectionStudyInfos(instanceID).Indexes().CreateMany(ctx, indexesForStudyInfosCollection) + names, err := dbService.collectionStudyInfos(instanceID).Indexes().CreateMany(ctx, indexesForStudyInfosCollection) if err != nil { slog.Error("Error creating index for studyInfos", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } + studyInfoIndexNames = names } // get studies @@ -68,9 +69,9 @@ func (dbService *StudyDBService) GetStudies(instanceID string, statusFilter stri opts := options.Find() if onlyKeys { projection := bson.D{ - primitive.E{Key: "key", Value: 1}, - primitive.E{Key: "secretKey", Value: 1}, - primitive.E{Key: "configs.idMappingMethod", Value: 1}, + {Key: "key", Value: 1}, + {Key: "secretKey", Value: 1}, + {Key: "configs.idMappingMethod", Value: 1}, } opts.SetProjection(projection) } diff --git a/pkg/db/study/studyRules.go b/pkg/db/study/studyRules.go index 7bb2ae0f..7ee3d553 100644 --- a/pkg/db/study/studyRules.go +++ b/pkg/db/study/studyRules.go @@ -4,14 +4,15 @@ import ( "fmt" "log/slog" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" studyTypes "github.com/case-framework/case-backend/pkg/study/types" ) +var studyRuleIndexNames []string + var indexesForStudyRulesCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -35,18 +36,17 @@ func (dbService *StudyDBService) DropIndexForStudyRulesCollection(instanceID str collection := dbService.collectionStudyRules(instanceID) if dropAll { - _, err := collection.Indexes().DropAll(ctx) + err := collection.Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for studyRules", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, index := range indexesForStudyRulesCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for studyRules collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range studyRuleIndexNames { + if indexName == "" { + slog.Error("Index name is empty for studyRules collection", slog.String("index", fmt.Sprintf("%+v", indexName))) continue } - indexName := *index.Options.Name - _, err := collection.Indexes().DropOne(ctx, indexName) + err := collection.Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for studyRules", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("indexName", indexName)) } @@ -59,10 +59,11 @@ func (dbService *StudyDBService) CreateDefaultIndexesForStudyRulesCollection(ins defer cancel() collection := dbService.collectionStudyRules(instanceID) - _, err := collection.Indexes().CreateMany(ctx, indexesForStudyRulesCollection) + names, err := collection.Indexes().CreateMany(ctx, indexesForStudyRulesCollection) if err != nil { slog.Error("Error creating index for studyRules", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } + studyRuleIndexNames = names } func (dbService *StudyDBService) deleteStudyRules(instanceID string, studyKey string) error { @@ -90,16 +91,14 @@ func (dbService *StudyDBService) GetCurrentStudyRules(instanceID string, studyKe collection := dbService.collectionStudyRules(instanceID) sortByPublished := bson.D{ - primitive.E{Key: "uploadedAt", Value: -1}, + {Key: "uploadedAt", Value: -1}, } filter := bson.M{ "studyKey": studyKey, } - opts := &options.FindOneOptions{ - Sort: sortByPublished, - } + opts := options.FindOne().SetSort(sortByPublished) err = collection.FindOne(ctx, filter, opts).Decode(&rules) if err != nil { @@ -115,7 +114,7 @@ func (dbService *StudyDBService) GetStudyRulesByID(instanceID string, studyKey s collection := dbService.collectionStudyRules(instanceID) - _id, err := primitive.ObjectIDFromHex(id) + _id, err := bson.ObjectIDFromHex(id) if err != nil { return rules, err } @@ -140,7 +139,7 @@ func (dbService *StudyDBService) DeleteStudyRulesByID(instanceID string, studyKe collection := dbService.collectionStudyRules(instanceID) - _id, err := primitive.ObjectIDFromHex(id) + _id, err := bson.ObjectIDFromHex(id) if err != nil { return err } @@ -169,8 +168,8 @@ func (dbService *StudyDBService) GetStudyRulesHistory(instanceID string, studyKe opts := options.Find().SetSort(bson.D{{Key: "uploadedAt", Value: -1}}) opts.SetProjection(bson.D{ - primitive.E{Key: "rules", Value: 0}, - primitive.E{Key: "serialisedRules", Value: 0}, + {Key: "rules", Value: 0}, + {Key: "serialisedRules", Value: 0}, }) cursor, err := collection.Find(ctx, filter, opts) if err != nil { diff --git a/pkg/db/study/surveys.go b/pkg/db/study/surveys.go index 3f8c08af..1e61b5a6 100644 --- a/pkg/db/study/surveys.go +++ b/pkg/db/study/surveys.go @@ -6,14 +6,15 @@ import ( "log/slog" "time" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" studyTypes "github.com/case-framework/case-backend/pkg/study/types" ) +var surveyIndexNames []string + var indexesForSurveysCollection = []mongo.IndexModel{ { Keys: bson.D{ @@ -50,18 +51,17 @@ func (dbService *StudyDBService) DropIndexForSurveysCollection(instanceID string defer cancel() if dropAll { - _, err := dbService.collectionSurveys(instanceID, studyKey).Indexes().DropAll(ctx) + err := dbService.collectionSurveys(instanceID, studyKey).Indexes().DropAll(ctx) if err != nil { slog.Error("Error dropping all indexes for surveys", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } } else { - for _, index := range indexesForSurveysCollection { - if index.Options == nil || index.Options.Name == nil { - slog.Error("Index name is nil for surveys collection", slog.String("index", fmt.Sprintf("%+v", index))) + for _, indexName := range surveyIndexNames { + if indexName == "" { + slog.Error("Index name is empty for surveys collection", slog.String("index", fmt.Sprintf("%+v", indexName))) continue } - indexName := *index.Options.Name - _, err := dbService.collectionSurveys(instanceID, studyKey).Indexes().DropOne(ctx, indexName) + err := dbService.collectionSurveys(instanceID, studyKey).Indexes().DropOne(ctx, indexName) if err != nil { slog.Error("Error dropping index for surveys", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey), slog.String("indexName", indexName)) } @@ -74,10 +74,11 @@ func (dbService *StudyDBService) CreateDefaultIndexesForSurveysCollection(instan defer cancel() collection := dbService.collectionSurveys(instanceID, studyKey) - _, err := collection.Indexes().CreateMany(ctx, indexesForSurveysCollection) + names, err := collection.Indexes().CreateMany(ctx, indexesForSurveysCollection) if err != nil { slog.Error("Error creating index for surveys", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } + surveyIndexNames = names } func (dbService *StudyDBService) SaveSurveyVersion(instanceID string, studyKey string, survey *studyTypes.Survey) (err error) { @@ -88,7 +89,7 @@ func (dbService *StudyDBService) SaveSurveyVersion(instanceID string, studyKey s if err != nil { return err } - survey.ID = ret.InsertedID.(primitive.ObjectID) + survey.ID = ret.InsertedID.(bson.ObjectID) return nil } @@ -101,26 +102,21 @@ func (dbService *StudyDBService) GetSurveyKeysForStudy(instanceID string, studyK if !includeUnpublished { filter["unpublished"] = 0 } - res, err := dbService.collectionSurveys(instanceID, studyKey).Distinct(ctx, "surveyDefinition.key", filter) - if err != nil { - return surveyKeys, err - } - surveyKeys = make([]string, len(res)) - for i, r := range res { - surveyKeys[i] = r.(string) + if err = dbService.collectionSurveys(instanceID, studyKey).Distinct(ctx, "surveyDefinition.key", filter).Decode(&surveyKeys); err != nil { + return nil, err } - return surveyKeys, err + return surveyKeys, nil } var ( sortByPublishedDesc = bson.D{ - primitive.E{Key: "published", Value: -1}, + {Key: "published", Value: -1}, } projectionToRemoveSurveyContentAndRules = bson.D{ - primitive.E{Key: "surveyDefinition.items", Value: 0}, - primitive.E{Key: "prefillRules", Value: 0}, - primitive.E{Key: "contextRules", Value: 0}, + {Key: "surveyDefinition.items", Value: 0}, + {Key: "prefillRules", Value: 0}, + {Key: "contextRules", Value: 0}, } ) @@ -132,11 +128,9 @@ func (dbService *StudyDBService) GetSurveyVersions(instanceID string, studyKey s if len(surveyKey) > 0 { filter["surveyDefinition.key"] = surveyKey } - opts := &options.FindOptions{} - - opts.SetProjection(projectionToRemoveSurveyContentAndRules) - - opts.SetSort(sortByPublishedDesc) + opts := options.Find(). + SetProjection(projectionToRemoveSurveyContentAndRules). + SetSort(sortByPublishedDesc) cur, err := dbService.collectionSurveys(instanceID, studyKey).Find( ctx, @@ -181,8 +175,7 @@ func (dbService *StudyDBService) GetCurrentSurveyVersion(instanceID string, stud }, } - opts := &options.FindOneOptions{} - opts.SetSort(sortByPublishedDesc) + opts := options.FindOne().SetSort(sortByPublishedDesc) err = dbService.collectionSurveys(instanceID, studyKey).FindOne(ctx, filter, opts).Decode(&survey) if err != nil { diff --git a/pkg/study/types/participant.go b/pkg/study/types/participant.go index cb22db13..2f261667 100644 --- a/pkg/study/types/participant.go +++ b/pkg/study/types/participant.go @@ -1,6 +1,6 @@ package types -import "go.mongodb.org/mongo-driver/bson/primitive" +import "go.mongodb.org/mongo-driver/v2/bson" const ( PARTICIPANT_STUDY_STATUS_ACTIVE = "active" @@ -12,7 +12,7 @@ const ( // Participant defines the datamodel for current state of the participant in a study as stored in the database type Participant struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` ParticipantID string `bson:"participantID" json:"participantId"` // reference to the study specific participant ID CurrentStudySession string `bson:"currentStudySession" json:"currentStudySession"` ModifiedAt int64 `bson:"modifiedAt" json:"modifiedAt"` diff --git a/pkg/study/types/participantFileinfo.go b/pkg/study/types/participantFileinfo.go index 9479efdf..67dec0fb 100644 --- a/pkg/study/types/participantFileinfo.go +++ b/pkg/study/types/participantFileinfo.go @@ -3,7 +3,7 @@ package types import ( "time" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) const ( @@ -12,12 +12,12 @@ const ( ) type FileInfo struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - ParticipantID string `bson:"participantID,omitempty" json:"participantID,omitempty"` - Status string `bson:"status,omitempty" json:"status,omitempty"` - UploadedBy string `bson:"uploadedBy,omitempty" json:"uploadedBy,omitempty"` // if not uploaded by the participant - Path string `bson:"path,omitempty" json:"path,omitempty"` - PreviewPath string `bson:"previewPath,omitempty" json:"previewPath,omitempty"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ParticipantID string `bson:"participantID,omitempty" json:"participantID,omitempty"` + Status string `bson:"status,omitempty" json:"status,omitempty"` + UploadedBy string `bson:"uploadedBy,omitempty" json:"uploadedBy,omitempty"` // if not uploaded by the participant + Path string `bson:"path,omitempty" json:"path,omitempty"` + PreviewPath string `bson:"previewPath,omitempty" json:"previewPath,omitempty"` SubmittedAt int64 `bson:"submittedAt,omitempty" json:"submittedAt,omitempty"` // deprecated, use CreatedAt instead CreatedAt time.Time `bson:"createdAt,omitempty" json:"createdAt,omitempty"` diff --git a/pkg/study/types/report.go b/pkg/study/types/report.go index 1803776e..28ae8c44 100644 --- a/pkg/study/types/report.go +++ b/pkg/study/types/report.go @@ -3,17 +3,17 @@ package types import ( "time" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) type Report struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Key string `bson:"key" json:"key"` - ParticipantID string `bson:"participantID" json:"participantID"` // reference to the study specific participant ID - ResponseID string `bson:"responseID" json:"responseID"` // reference to the report - Timestamp int64 `bson:"timestamp" json:"timestamp"` - ModifiedAt time.Time `bson:"modifiedAt,omitempty" json:"modifiedAt,omitempty"` // if report is updated later, this is the time of the update - Data []ReportData `bson:"data" json:"data,omitempty"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Key string `bson:"key" json:"key"` + ParticipantID string `bson:"participantID" json:"participantID"` // reference to the study specific participant ID + ResponseID string `bson:"responseID" json:"responseID"` // reference to the report + Timestamp int64 `bson:"timestamp" json:"timestamp"` + ModifiedAt time.Time `bson:"modifiedAt,omitempty" json:"modifiedAt,omitempty"` // if report is updated later, this is the time of the update + Data []ReportData `bson:"data" json:"data,omitempty"` } type ReportData struct { diff --git a/pkg/study/types/survey.go b/pkg/study/types/survey.go index 53042e4d..11e5356c 100644 --- a/pkg/study/types/survey.go +++ b/pkg/study/types/survey.go @@ -1,6 +1,6 @@ package types -import "go.mongodb.org/mongo-driver/bson/primitive" +import "go.mongodb.org/mongo-driver/v2/bson" const ( SURVEY_AVAILABLE_FOR_PUBLIC = "public" @@ -10,14 +10,14 @@ const ( ) type Survey struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - SurveyKey string `bson:"surveyKey,omitempty" json:"surveyKey,omitempty"` - Props SurveyProps `bson:"props,omitempty" json:"props,omitempty"` - PrefillRules []Expression `bson:"prefillRules,omitempty" json:"prefillRules,omitempty"` - ContextRules *SurveyContextDef `bson:"contextRules,omitempty" json:"contextRules,omitempty"` - MaxItemsPerPage *MaxItemsPerPage `bson:"maxItemsPerPage,omitempty" json:"maxItemsPerPage,omitempty"` - AvailableFor string `bson:"availableFor,omitempty" json:"availableFor,omitempty"` - RequireLoginBeforeSubmission bool `bson:"requireLoginBeforeSubmission,omitempty" json:"requireLoginBeforeSubmission,omitempty"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + SurveyKey string `bson:"surveyKey,omitempty" json:"surveyKey,omitempty"` + Props SurveyProps `bson:"props,omitempty" json:"props,omitempty"` + PrefillRules []Expression `bson:"prefillRules,omitempty" json:"prefillRules,omitempty"` + ContextRules *SurveyContextDef `bson:"contextRules,omitempty" json:"contextRules,omitempty"` + MaxItemsPerPage *MaxItemsPerPage `bson:"maxItemsPerPage,omitempty" json:"maxItemsPerPage,omitempty"` + AvailableFor string `bson:"availableFor,omitempty" json:"availableFor,omitempty"` + RequireLoginBeforeSubmission bool `bson:"requireLoginBeforeSubmission,omitempty" json:"requireLoginBeforeSubmission,omitempty"` Published int64 `bson:"published,omitempty" json:"published,omitempty"` Unpublished int64 `bson:"unpublished,omitempty" json:"unpublished,omitempty"` diff --git a/pkg/study/types/surveyResponse.go b/pkg/study/types/surveyResponse.go index af8e26a1..f041f361 100644 --- a/pkg/study/types/surveyResponse.go +++ b/pkg/study/types/surveyResponse.go @@ -1,9 +1,9 @@ package types -import "go.mongodb.org/mongo-driver/bson/primitive" +import "go.mongodb.org/mongo-driver/v2/bson" type SurveyResponse struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` Key string `bson:"key" json:"key"` ParticipantID string `bson:"participantID" json:"participantId"` VersionID string `bson:"versionID" json:"versionId"` diff --git a/pkg/study/types/task.go b/pkg/study/types/task.go index 66185972..4e9515e9 100644 --- a/pkg/study/types/task.go +++ b/pkg/study/types/task.go @@ -3,7 +3,7 @@ package types import ( "time" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) const ( @@ -15,12 +15,12 @@ const ( ) type Task struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - CreatedAt time.Time `bson:"createdAt" json:"createdAt"` - CreatedBy string `bson:"createdBy" json:"createdBy"` - UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"` - Status string `bson:"status" json:"status"` - TargetCount int `bson:"targetCount" json:"targetCount"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + CreatedAt time.Time `bson:"createdAt" json:"createdAt"` + CreatedBy string `bson:"createdBy" json:"createdBy"` + UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"` + Status string `bson:"status" json:"status"` + TargetCount int `bson:"targetCount" json:"targetCount"` ProcessedCount int `bson:"processedCount" json:"processedCount"` ResultFile string `bson:"resultFile" json:"resultFile"` FileType string `bson:"fileType" json:"fileType"` From cd8c37560d5d11fcaf497cd2b2f4bbbaf3f29b80 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 18 Mar 2026 13:17:47 +0100 Subject: [PATCH 08/38] package study: update import to mongodriver v2 and changed changed any instance of primitive.ObjectID to bson.ObjectId. --- pkg/study/participant-data.go | 2 +- pkg/study/study-service.go | 7 +++---- pkg/study/studyengine/actions.go | 4 ++-- pkg/study/studyengine/expressions.go | 2 +- pkg/study/studyengine/expressions_test.go | 2 +- pkg/study/studyengine/types.go | 2 +- pkg/study/types/study-code-list.go | 12 ++++++------ pkg/study/types/study-message.go | 10 +++++----- pkg/study/types/study-variables.go | 6 +++--- pkg/study/types/study.go | 4 ++-- pkg/study/types/studyRules.go | 14 +++++++------- pkg/study/types/survey-item.go | 12 ++++++------ pkg/study/utils/participantID_test.go | 8 ++++---- 13 files changed, 42 insertions(+), 43 deletions(-) diff --git a/pkg/study/participant-data.go b/pkg/study/participant-data.go index 2411c6f2..b7eefb3b 100644 --- a/pkg/study/participant-data.go +++ b/pkg/study/participant-data.go @@ -9,7 +9,7 @@ import ( "github.com/case-framework/case-backend/pkg/study/studyengine" studyTypes "github.com/case-framework/case-backend/pkg/study/types" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" ) type AssignedSurveyWithContext struct { diff --git a/pkg/study/study-service.go b/pkg/study/study-service.go index 627e2e06..841a98aa 100644 --- a/pkg/study/study-service.go +++ b/pkg/study/study-service.go @@ -12,8 +12,7 @@ import ( "github.com/case-framework/case-backend/pkg/study/types" studyTypes "github.com/case-framework/case-backend/pkg/study/types" studyUtils "github.com/case-framework/case-backend/pkg/study/utils" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) var ( @@ -127,7 +126,7 @@ func OnRegisterTempParticipant(instanceID string, studyKey string) (pState *stud return } - tempProfileID := primitive.NewObjectID().Hex() + tempProfileID := bson.NewObjectID().Hex() participantID, _, err := ComputeParticipantIDs(study, tempProfileID) if err != nil { slog.Error("Error computing participant IDs", slog.String("instanceID", instanceID), slog.String("studyKey", studyKey), slog.String("error", err.Error())) @@ -170,7 +169,7 @@ func OnRegisterVirtualParticipant(instanceID string, studyKey string) (pState *s return } - virtualProfileID := primitive.NewObjectID().Hex() + virtualProfileID := bson.NewObjectID().Hex() participantID, _, err := ComputeParticipantIDs(study, virtualProfileID) if err != nil { slog.Error("Error computing participant IDs", slog.String("instanceID", instanceID), slog.String("studyKey", studyKey), slog.String("error", err.Error())) diff --git a/pkg/study/studyengine/actions.go b/pkg/study/studyengine/actions.go index 96ea2247..8dadd3f5 100644 --- a/pkg/study/studyengine/actions.go +++ b/pkg/study/studyengine/actions.go @@ -15,7 +15,7 @@ import ( httpclient "github.com/case-framework/case-backend/pkg/http-client" studyTypes "github.com/case-framework/case-backend/pkg/study/types" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) func ActionEval(action studyTypes.Expression, oldState ActionData, event StudyEvent) (newState ActionData, err error) { @@ -553,7 +553,7 @@ func addMessage(action studyTypes.Expression, oldState ActionData, event StudyEv } newMessage := studyTypes.ParticipantMessage{ - ID: primitive.NewObjectID().Hex(), + ID: bson.NewObjectID().Hex(), Type: messageType, ScheduledFor: int64(timestamp), } diff --git a/pkg/study/studyengine/expressions.go b/pkg/study/studyengine/expressions.go index add6ecf5..1536415b 100644 --- a/pkg/study/studyengine/expressions.go +++ b/pkg/study/studyengine/expressions.go @@ -13,7 +13,7 @@ import ( "github.com/case-framework/case-backend/pkg/apihelpers" httpclient "github.com/case-framework/case-backend/pkg/http-client" studyTypes "github.com/case-framework/case-backend/pkg/study/types" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" ) func ExpressionEval(expression studyTypes.Expression, evalCtx EvalContext) (val interface{}, err error) { diff --git a/pkg/study/studyengine/expressions_test.go b/pkg/study/studyengine/expressions_test.go index 0d979552..505e2750 100644 --- a/pkg/study/studyengine/expressions_test.go +++ b/pkg/study/studyengine/expressions_test.go @@ -7,7 +7,7 @@ import ( studyDB "github.com/case-framework/case-backend/pkg/db/study" studyTypes "github.com/case-framework/case-backend/pkg/study/types" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" ) // Reference/Lookup methods diff --git a/pkg/study/studyengine/types.go b/pkg/study/studyengine/types.go index 69be806b..60a9957c 100644 --- a/pkg/study/studyengine/types.go +++ b/pkg/study/studyengine/types.go @@ -6,7 +6,7 @@ import ( studyDB "github.com/case-framework/case-backend/pkg/db/study" studyTypes "github.com/case-framework/case-backend/pkg/study/types" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" ) const ( diff --git a/pkg/study/types/study-code-list.go b/pkg/study/types/study-code-list.go index 86bae580..7b1e5063 100644 --- a/pkg/study/types/study-code-list.go +++ b/pkg/study/types/study-code-list.go @@ -3,13 +3,13 @@ package types import ( "time" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) type StudyCodeListEntry struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - StudyKey string `bson:"studyKey" json:"studyKey"` - ListKey string `bson:"listKey" json:"listKey"` - Code string `bson:"code" json:"code"` - AddedAt time.Time `bson:"addedAt" json:"addedAt"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + StudyKey string `bson:"studyKey" json:"studyKey"` + ListKey string `bson:"listKey" json:"listKey"` + Code string `bson:"code" json:"code"` + AddedAt time.Time `bson:"addedAt" json:"addedAt"` } diff --git a/pkg/study/types/study-message.go b/pkg/study/types/study-message.go index 9af55e02..fea15ebc 100644 --- a/pkg/study/types/study-message.go +++ b/pkg/study/types/study-message.go @@ -1,10 +1,10 @@ package types -import "go.mongodb.org/mongo-driver/bson/primitive" +import "go.mongodb.org/mongo-driver/v2/bson" type StudyMessage struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Type string `bson:"type,omitempty" json:"type,omitempty"` - Payload map[string]string `bson:"payload,omitempty" json:"payload,omitempty"` - ParticipantID string `bson:"participantID,omitempty" json:"participantID,omitempty"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Type string `bson:"type,omitempty" json:"type,omitempty"` + Payload map[string]string `bson:"payload,omitempty" json:"payload,omitempty"` + ParticipantID string `bson:"participantID,omitempty" json:"participantID,omitempty"` } diff --git a/pkg/study/types/study-variables.go b/pkg/study/types/study-variables.go index e307266d..d997c99d 100644 --- a/pkg/study/types/study-variables.go +++ b/pkg/study/types/study-variables.go @@ -10,7 +10,7 @@ import ( "strings" "time" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) type StudyVariablesType string @@ -24,7 +24,7 @@ const ( ) type StudyVariables struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` CreatedAt time.Time `bson:"createdAt" json:"createdAt"` ConfigUpdatedAt time.Time `bson:"configUpdatedAt" json:"configUpdatedAt"` ValueUpdatedAt time.Time `bson:"valueUpdatedAt" json:"valueUpdatedAt"` @@ -48,7 +48,7 @@ type StudyVariables struct { func (sv *StudyVariables) UnmarshalJSON(data []byte) error { // Define a wire struct that treats Value as raw JSON. type studyVariablesWire struct { - ID primitive.ObjectID `json:"id,omitempty"` + ID bson.ObjectID `json:"id,omitempty"` CreatedAt time.Time `json:"createdAt"` ConfigUpdatedAt time.Time `json:"configUpdatedAt"` ValueUpdatedAt time.Time `json:"valueUpdatedAt"` diff --git a/pkg/study/types/study.go b/pkg/study/types/study.go index d834b3b0..0580a116 100644 --- a/pkg/study/types/study.go +++ b/pkg/study/types/study.go @@ -1,6 +1,6 @@ package types -import "go.mongodb.org/mongo-driver/bson/primitive" +import "go.mongodb.org/mongo-driver/v2/bson" const ( STUDY_STATUS_ACTIVE = "active" @@ -12,7 +12,7 @@ const ( ) type Study struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` Key string `bson:"key" json:"key"` SecretKey string `bson:"secretKey" json:"secretKey"` Status string `bson:"status" json:"status"` diff --git a/pkg/study/types/studyRules.go b/pkg/study/types/studyRules.go index bc7a8d42..2fb5a6ee 100644 --- a/pkg/study/types/studyRules.go +++ b/pkg/study/types/studyRules.go @@ -3,16 +3,16 @@ package types import ( "encoding/json" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) type StudyRules struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - StudyKey string `bson:"studyKey" json:"studyKey"` - UploadedAt int64 `bson:"uploadedAt" json:"uploadedAt"` - UploadedBy string `bson:"uploadedBy" json:"uploadedBy"` - Rules []Expression `bson:"rules,omitempty" json:"rules"` - SerialisedRules string `bson:"serialisedRules,omitempty" json:"serialisedRules,omitempty"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + StudyKey string `bson:"studyKey" json:"studyKey"` + UploadedAt int64 `bson:"uploadedAt" json:"uploadedAt"` + UploadedBy string `bson:"uploadedBy" json:"uploadedBy"` + Rules []Expression `bson:"rules,omitempty" json:"rules"` + SerialisedRules string `bson:"serialisedRules,omitempty" json:"serialisedRules,omitempty"` } func (studyRules *StudyRules) MarshalRules() error { diff --git a/pkg/study/types/survey-item.go b/pkg/study/types/survey-item.go index e9c82f18..7eea4dd0 100644 --- a/pkg/study/types/survey-item.go +++ b/pkg/study/types/survey-item.go @@ -1,6 +1,6 @@ package types -import "go.mongodb.org/mongo-driver/bson/primitive" +import "go.mongodb.org/mongo-driver/v2/bson" const ( SURVEY_ITEM_TYPE_PAGE_BREAK = "pageBreak" @@ -8,11 +8,11 @@ const ( ) type SurveyItem struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Key string `bson:"key" json:"key"` - Follows []string `bson:"follows,omitempty" json:"follows,omitempty"` - Condition *Expression `bson:"condition,omitempty" json:"condition,omitempty"` - Priority float32 `bson:"priority,omitempty" json:"priority,omitempty"` + ID bson.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Key string `bson:"key" json:"key"` + Follows []string `bson:"follows,omitempty" json:"follows,omitempty"` + Condition *Expression `bson:"condition,omitempty" json:"condition,omitempty"` + Priority float32 `bson:"priority,omitempty" json:"priority,omitempty"` Metadata map[string]string `bson:"metadata,omitempty" json:"metadata,omitempty"` diff --git a/pkg/study/utils/participantID_test.go b/pkg/study/utils/participantID_test.go index 5a1d565d..a6006f12 100644 --- a/pkg/study/utils/participantID_test.go +++ b/pkg/study/utils/participantID_test.go @@ -5,7 +5,7 @@ import ( "encoding/hex" "testing" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) func createGlobalKey() string { @@ -21,8 +21,8 @@ func testProfileIDtoParticipantMethod(t *testing.T, method string, studySecret s globalKey := createGlobalKey() - testProfileID := primitive.NewObjectID().Hex() - testProfileID2 := primitive.NewObjectID().Hex() + testProfileID := bson.NewObjectID().Hex() + testProfileID2 := bson.NewObjectID().Hex() t.Run("same user same keys", func(t *testing.T) { pId, err := ProfileIDtoParticipantID(testProfileID, globalKey, studySecret, method) @@ -134,7 +134,7 @@ func benchmarkMappingParticipantID(b *testing.B, method string) { studySecret := "this!study.-a.sd" globalKey := createGlobalKey() for n := 0; n < b.N; n++ { - testProfileID := primitive.NewObjectID().Hex() + testProfileID := bson.NewObjectID().Hex() _, err := ProfileIDtoParticipantID(testProfileID, globalKey, studySecret, method) if err != nil { b.Errorf("unexpected error: %s", err.Error()) From c2ab6ae554666fbf1346d7b813e13550ffc96e66 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 18 Mar 2026 13:22:47 +0100 Subject: [PATCH 09/38] package user-management: updated to mongodriver v2, changed any instance of primitive.ObjectID to bson.ObjectId. --- pkg/user-management/types/temptoken.go | 16 ++++++------- pkg/user-management/utils/profiles_test.go | 26 +++++++++++----------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/pkg/user-management/types/temptoken.go b/pkg/user-management/types/temptoken.go index 479acddd..4ca46b20 100644 --- a/pkg/user-management/types/temptoken.go +++ b/pkg/user-management/types/temptoken.go @@ -3,7 +3,7 @@ package types import ( "time" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) const ( @@ -17,11 +17,11 @@ const ( ) type TempToken struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"token_id,omitempty"` - Token string `bson:"token" json:"token"` - Expiration time.Time `bson:"expiration" json:"expiration"` - Purpose string `bson:"purpose" json:"purpose"` - UserID string `bson:"userID" json:"userID"` - Info map[string]string `bson:"info" json:"info"` - InstanceID string `bson:"instanceID" json:"instanceID"` + ID bson.ObjectID `bson:"_id,omitempty" json:"token_id,omitempty"` + Token string `bson:"token" json:"token"` + Expiration time.Time `bson:"expiration" json:"expiration"` + Purpose string `bson:"purpose" json:"purpose"` + UserID string `bson:"userID" json:"userID"` + Info map[string]string `bson:"info" json:"info"` + InstanceID string `bson:"instanceID" json:"instanceID"` } diff --git a/pkg/user-management/utils/profiles_test.go b/pkg/user-management/utils/profiles_test.go index 08671f7f..0430f4c4 100644 --- a/pkg/user-management/utils/profiles_test.go +++ b/pkg/user-management/utils/profiles_test.go @@ -4,14 +4,14 @@ import ( "testing" userTypes "github.com/case-framework/case-backend/pkg/user-management/types" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) func TestGetMainAndOtherProfiles(t *testing.T) { t.Run("with a single profile with main flag", func(t *testing.T) { user := userTypes.User{ Profiles: []userTypes.Profile{ - {ID: primitive.NewObjectID(), MainProfile: true}, + {ID: bson.NewObjectID(), MainProfile: true}, }, } main, others := GetMainAndOtherProfiles(user) @@ -26,7 +26,7 @@ func TestGetMainAndOtherProfiles(t *testing.T) { t.Run("with a single profile without main flag", func(t *testing.T) { user := userTypes.User{ Profiles: []userTypes.Profile{ - {ID: primitive.NewObjectID()}, + {ID: bson.NewObjectID()}, }, } main, others := GetMainAndOtherProfiles(user) @@ -41,10 +41,10 @@ func TestGetMainAndOtherProfiles(t *testing.T) { t.Run("with mulitple profiles without main flag", func(t *testing.T) { user := userTypes.User{ Profiles: []userTypes.Profile{ - {ID: primitive.NewObjectID(), MainProfile: false}, - {ID: primitive.NewObjectID(), MainProfile: false}, - {ID: primitive.NewObjectID(), MainProfile: false}, - {ID: primitive.NewObjectID(), MainProfile: false}, + {ID: bson.NewObjectID(), MainProfile: false}, + {ID: bson.NewObjectID(), MainProfile: false}, + {ID: bson.NewObjectID(), MainProfile: false}, + {ID: bson.NewObjectID(), MainProfile: false}, }, } main, others := GetMainAndOtherProfiles(user) @@ -59,9 +59,9 @@ func TestGetMainAndOtherProfiles(t *testing.T) { t.Run("with mulitple profiles one main flag", func(t *testing.T) { user := userTypes.User{ Profiles: []userTypes.Profile{ - {ID: primitive.NewObjectID(), MainProfile: false}, - {ID: primitive.NewObjectID(), MainProfile: true}, - {ID: primitive.NewObjectID(), MainProfile: false}, + {ID: bson.NewObjectID(), MainProfile: false}, + {ID: bson.NewObjectID(), MainProfile: true}, + {ID: bson.NewObjectID(), MainProfile: false}, }, } main, others := GetMainAndOtherProfiles(user) @@ -76,9 +76,9 @@ func TestGetMainAndOtherProfiles(t *testing.T) { t.Run("with mulitple profiles multiply main flag", func(t *testing.T) { user := userTypes.User{ Profiles: []userTypes.Profile{ - {ID: primitive.NewObjectID(), MainProfile: false}, - {ID: primitive.NewObjectID(), MainProfile: true}, - {ID: primitive.NewObjectID(), MainProfile: true}, + {ID: bson.NewObjectID(), MainProfile: false}, + {ID: bson.NewObjectID(), MainProfile: true}, + {ID: bson.NewObjectID(), MainProfile: true}, }, } main, others := GetMainAndOtherProfiles(user) From ad068337cf695230d78b28c137e7f194a4f793c9 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 18 Mar 2026 14:29:57 +0100 Subject: [PATCH 10/38] services: updated the mongo driver to v2 --- pkg/apihelpers/parsePaginatedQuery.go | 2 +- services/management-api/apihandlers/study-management.go | 9 ++++----- services/participant-api/apihandlers/authentication.go | 5 ++--- services/participant-api/apihandlers/password-reset.go | 2 +- services/participant-api/apihandlers/study-service.go | 2 +- services/participant-api/apihandlers/user-management.go | 2 +- 6 files changed, 10 insertions(+), 12 deletions(-) diff --git a/pkg/apihelpers/parsePaginatedQuery.go b/pkg/apihelpers/parsePaginatedQuery.go index e9ecc1f6..4946d943 100644 --- a/pkg/apihelpers/parsePaginatedQuery.go +++ b/pkg/apihelpers/parsePaginatedQuery.go @@ -8,7 +8,7 @@ import ( surveyresponses "github.com/case-framework/case-backend/pkg/study/exporter/survey-responses" "github.com/gin-gonic/gin" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" ) type PagenatedQuery struct { diff --git a/services/management-api/apihandlers/study-management.go b/services/management-api/apihandlers/study-management.go index 5819385e..46c4efee 100644 --- a/services/management-api/apihandlers/study-management.go +++ b/services/management-api/apihandlers/study-management.go @@ -25,8 +25,7 @@ import ( studyutils "github.com/case-framework/case-backend/pkg/study/utils" "github.com/case-framework/case-backend/pkg/utils" "github.com/gin-gonic/gin" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" studyDB "github.com/case-framework/case-backend/pkg/db/study" studyService "github.com/case-framework/case-backend/pkg/study" @@ -1227,7 +1226,7 @@ func (h *HttpEndpoints) exportStudyConfig(c *gin.Context) { configWriter.WriteKeyValue("exportedAt", time.Now()) if includeConfig { - study.ID = primitive.NilObjectID + study.ID = bson.NilObjectID study.NextTimerEvent = 0 study.Stats = studyTypes.StudyStats{} configWriter.WriteString(",") @@ -1238,7 +1237,7 @@ func (h *HttpEndpoints) exportStudyConfig(c *gin.Context) { rules, err := h.studyDBConn.GetCurrentStudyRules(token.InstanceID, studyKey) if err == nil { - rules.ID = primitive.NilObjectID + rules.ID = bson.NilObjectID rules.UploadedBy = "" configWriter.WriteString(",") configWriter.WriteKeyValue("rules", rules) @@ -1257,7 +1256,7 @@ func (h *HttpEndpoints) exportStudyConfig(c *gin.Context) { slog.Error("failed to get latest survey", slog.String("error", err.Error())) continue } - survey.ID = primitive.NilObjectID + survey.ID = bson.NilObjectID surveys = append(surveys, survey) } diff --git a/services/participant-api/apihandlers/authentication.go b/services/participant-api/apihandlers/authentication.go index 02d450b8..dff7ee83 100644 --- a/services/participant-api/apihandlers/authentication.go +++ b/services/participant-api/apihandlers/authentication.go @@ -17,8 +17,7 @@ import ( "github.com/case-framework/case-backend/pkg/user-management/pwhash" umUtils "github.com/case-framework/case-backend/pkg/user-management/utils" "github.com/gin-gonic/gin" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" userTypes "github.com/case-framework/case-backend/pkg/user-management/types" ) @@ -310,7 +309,7 @@ func (h *HttpEndpoints) signupWithEmail(c *gin.Context) { return } - newUser.ID, _ = primitive.ObjectIDFromHex(id) + newUser.ID, _ = bson.ObjectIDFromHex(id) // contact verification in go routine go h.prepAndSendEmailVerification( diff --git a/services/participant-api/apihandlers/password-reset.go b/services/participant-api/apihandlers/password-reset.go index 2fd9b383..ffbc147f 100644 --- a/services/participant-api/apihandlers/password-reset.go +++ b/services/participant-api/apihandlers/password-reset.go @@ -10,7 +10,7 @@ import ( userTypes "github.com/case-framework/case-backend/pkg/user-management/types" umUtils "github.com/case-framework/case-backend/pkg/user-management/utils" "github.com/gin-gonic/gin" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" emailTypes "github.com/case-framework/case-backend/pkg/messaging/types" ) diff --git a/services/participant-api/apihandlers/study-service.go b/services/participant-api/apihandlers/study-service.go index 7d22ab04..5ee23c82 100644 --- a/services/participant-api/apihandlers/study-service.go +++ b/services/participant-api/apihandlers/study-service.go @@ -15,7 +15,7 @@ import ( jwthandling "github.com/case-framework/case-backend/pkg/jwt-handling" "github.com/case-framework/case-backend/pkg/utils" "github.com/gin-gonic/gin" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" studyService "github.com/case-framework/case-backend/pkg/study" surveydefinition "github.com/case-framework/case-backend/pkg/study/exporter/survey-definition" diff --git a/services/participant-api/apihandlers/user-management.go b/services/participant-api/apihandlers/user-management.go index 2b9122da..97d0e27f 100644 --- a/services/participant-api/apihandlers/user-management.go +++ b/services/participant-api/apihandlers/user-management.go @@ -13,7 +13,7 @@ import ( emailTypes "github.com/case-framework/case-backend/pkg/messaging/types" studyTypes "github.com/case-framework/case-backend/pkg/study/types" "github.com/gin-gonic/gin" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" studyService "github.com/case-framework/case-backend/pkg/study" "github.com/case-framework/case-backend/pkg/user-management/pwhash" From c85f42b3a69f3e03676c1082b4d5c1e5449fe30b Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 18 Mar 2026 14:38:52 +0100 Subject: [PATCH 11/38] update jobs packages with new mongo-driver v2 --- go.mod | 3 --- go.sum | 6 ------ jobs/db-migration/main.go | 2 +- jobs/messaging/participant-messages.go | 5 ++--- jobs/study-daily-data-export/response-exporter.go | 2 +- jobs/study-timer/main.go | 2 +- pkg/db/study/researcher-messages.go | 7 +++---- 7 files changed, 8 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index ffb22bd9..1653cc82 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/gin-contrib/cors v1.7.6 github.com/gin-gonic/gin v1.10.1 github.com/golang-jwt/jwt/v5 v5.3.0 - go.mongodb.org/mongo-driver v1.17.4 go.mongodb.org/mongo-driver/v2 v2.5.0 ) @@ -25,7 +24,6 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/goccy/go-json v0.10.5 // indirect - github.com/golang/snappy v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -35,7 +33,6 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/montanaflynn/stats v0.7.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect diff --git a/go.sum b/go.sum index 908d6c0d..9bf02032 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,6 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= -github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= -github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -54,8 +52,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= -github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -84,8 +80,6 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw= -go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= diff --git a/jobs/db-migration/main.go b/jobs/db-migration/main.go index 36ad56be..09d9f59a 100644 --- a/jobs/db-migration/main.go +++ b/jobs/db-migration/main.go @@ -8,7 +8,7 @@ import ( "path/filepath" "time" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" ) func main() { diff --git a/jobs/messaging/participant-messages.go b/jobs/messaging/participant-messages.go index 3cd75468..525e6bf3 100644 --- a/jobs/messaging/participant-messages.go +++ b/jobs/messaging/participant-messages.go @@ -11,8 +11,7 @@ import ( messagingTypes "github.com/case-framework/case-backend/pkg/messaging/types" studyservice "github.com/case-framework/case-backend/pkg/study" studyTypes "github.com/case-framework/case-backend/pkg/study/types" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" ) func handleParticipantMessages(wg *sync.WaitGroup) { @@ -174,7 +173,7 @@ func getRelevantMessages(p studyTypes.Participant) []studyTypes.StudyMessage { if message.ScheduledFor > time.Now().Unix() { continue } - _id, err := primitive.ObjectIDFromHex(message.ID) + _id, err := bson.ObjectIDFromHex(message.ID) if err != nil { slog.Error("Error parsing message id", slog.String("messageID", message.ID), slog.String("error", err.Error())) continue diff --git a/jobs/study-daily-data-export/response-exporter.go b/jobs/study-daily-data-export/response-exporter.go index 3d48eb04..048d1a0b 100644 --- a/jobs/study-daily-data-export/response-exporter.go +++ b/jobs/study-daily-data-export/response-exporter.go @@ -13,7 +13,7 @@ import ( surveydefinition "github.com/case-framework/case-backend/pkg/study/exporter/survey-definition" surveyresponses "github.com/case-framework/case-backend/pkg/study/exporter/survey-responses" studyTypes "github.com/case-framework/case-backend/pkg/study/types" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" ) func runResponseExportsForTask(rExpTask ResponseExportTask) { diff --git a/jobs/study-timer/main.go b/jobs/study-timer/main.go index 283c4802..9e6b0e99 100644 --- a/jobs/study-timer/main.go +++ b/jobs/study-timer/main.go @@ -7,7 +7,7 @@ import ( studyservice "github.com/case-framework/case-backend/pkg/study" studyTypes "github.com/case-framework/case-backend/pkg/study/types" studyUtils "github.com/case-framework/case-backend/pkg/study/utils" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" ) func main() { diff --git a/pkg/db/study/researcher-messages.go b/pkg/db/study/researcher-messages.go index e1991586..71f7ed0e 100644 --- a/pkg/db/study/researcher-messages.go +++ b/pkg/db/study/researcher-messages.go @@ -3,8 +3,7 @@ package study import ( "log/slog" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/v2/bson" studyTypes "github.com/case-framework/case-backend/pkg/study/types" ) @@ -52,9 +51,9 @@ func (dbService *StudyDBService) DeleteResearcherMessages(instanceID string, stu ctx, cancel := dbService.getContext() defer cancel() - idsToDelete := []primitive.ObjectID{} + idsToDelete := []bson.ObjectID{} for _, id := range messageIDs { - _id, err := primitive.ObjectIDFromHex(id) + _id, err := bson.ObjectIDFromHex(id) if err != nil { slog.Debug("unexpected error while converting id to ObjectID: %v", slog.String("error", err.Error())) continue From ac4a047cb86f6cfc5c90e4c11c44c9000802d801 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 18 Mar 2026 14:54:56 +0100 Subject: [PATCH 12/38] renamed files for consistent naming --- .../{parsePaginatedQuery.go => parse-paginated-query.go} | 0 pkg/db/messaging/{scheduledEmail.go => scheduled-email.go} | 0 .../study/{confidentialResponses.go => confidential-responses.go} | 0 .../study/{participantFileInfos.go => participant-file-infos.go} | 0 pkg/db/study/{studyInfos.go => study-infos.go} | 0 pkg/db/study/{studyRules.go => study-rules.go} | 0 pkg/db/study/{taskQueue.go => task-queue.go} | 0 pkg/messaging/types/{emailTemplate.go => email-template.go} | 0 pkg/study/types/{assignedSurvey.go => assigned-survey.go} | 0 pkg/study/types/{localisedObject.go => localised-object.go} | 0 .../types/{participantFileinfo.go => participant-file-info.go} | 0 pkg/study/types/{studyRules.go => study-rules.go} | 0 pkg/study/types/{surveyResponse.go => survey-response.go} | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename pkg/apihelpers/{parsePaginatedQuery.go => parse-paginated-query.go} (100%) rename pkg/db/messaging/{scheduledEmail.go => scheduled-email.go} (100%) rename pkg/db/study/{confidentialResponses.go => confidential-responses.go} (100%) rename pkg/db/study/{participantFileInfos.go => participant-file-infos.go} (100%) rename pkg/db/study/{studyInfos.go => study-infos.go} (100%) rename pkg/db/study/{studyRules.go => study-rules.go} (100%) rename pkg/db/study/{taskQueue.go => task-queue.go} (100%) rename pkg/messaging/types/{emailTemplate.go => email-template.go} (100%) rename pkg/study/types/{assignedSurvey.go => assigned-survey.go} (100%) rename pkg/study/types/{localisedObject.go => localised-object.go} (100%) rename pkg/study/types/{participantFileinfo.go => participant-file-info.go} (100%) rename pkg/study/types/{studyRules.go => study-rules.go} (100%) rename pkg/study/types/{surveyResponse.go => survey-response.go} (100%) diff --git a/pkg/apihelpers/parsePaginatedQuery.go b/pkg/apihelpers/parse-paginated-query.go similarity index 100% rename from pkg/apihelpers/parsePaginatedQuery.go rename to pkg/apihelpers/parse-paginated-query.go diff --git a/pkg/db/messaging/scheduledEmail.go b/pkg/db/messaging/scheduled-email.go similarity index 100% rename from pkg/db/messaging/scheduledEmail.go rename to pkg/db/messaging/scheduled-email.go diff --git a/pkg/db/study/confidentialResponses.go b/pkg/db/study/confidential-responses.go similarity index 100% rename from pkg/db/study/confidentialResponses.go rename to pkg/db/study/confidential-responses.go diff --git a/pkg/db/study/participantFileInfos.go b/pkg/db/study/participant-file-infos.go similarity index 100% rename from pkg/db/study/participantFileInfos.go rename to pkg/db/study/participant-file-infos.go diff --git a/pkg/db/study/studyInfos.go b/pkg/db/study/study-infos.go similarity index 100% rename from pkg/db/study/studyInfos.go rename to pkg/db/study/study-infos.go diff --git a/pkg/db/study/studyRules.go b/pkg/db/study/study-rules.go similarity index 100% rename from pkg/db/study/studyRules.go rename to pkg/db/study/study-rules.go diff --git a/pkg/db/study/taskQueue.go b/pkg/db/study/task-queue.go similarity index 100% rename from pkg/db/study/taskQueue.go rename to pkg/db/study/task-queue.go diff --git a/pkg/messaging/types/emailTemplate.go b/pkg/messaging/types/email-template.go similarity index 100% rename from pkg/messaging/types/emailTemplate.go rename to pkg/messaging/types/email-template.go diff --git a/pkg/study/types/assignedSurvey.go b/pkg/study/types/assigned-survey.go similarity index 100% rename from pkg/study/types/assignedSurvey.go rename to pkg/study/types/assigned-survey.go diff --git a/pkg/study/types/localisedObject.go b/pkg/study/types/localised-object.go similarity index 100% rename from pkg/study/types/localisedObject.go rename to pkg/study/types/localised-object.go diff --git a/pkg/study/types/participantFileinfo.go b/pkg/study/types/participant-file-info.go similarity index 100% rename from pkg/study/types/participantFileinfo.go rename to pkg/study/types/participant-file-info.go diff --git a/pkg/study/types/studyRules.go b/pkg/study/types/study-rules.go similarity index 100% rename from pkg/study/types/studyRules.go rename to pkg/study/types/study-rules.go diff --git a/pkg/study/types/surveyResponse.go b/pkg/study/types/survey-response.go similarity index 100% rename from pkg/study/types/surveyResponse.go rename to pkg/study/types/survey-response.go From f01976786bc9c07ed925331d1fe10803ede7c79c Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Mon, 23 Mar 2026 07:30:26 +0100 Subject: [PATCH 13/38] Fix management user db: decouple DropIndexes(defaults) from CreateIndexes by using statically defined default indexes, so default indexes are dropped reliably even when create was not run beforehand. --- mongodb-driver-update.md | 29 ++++++++-------- pkg/db/management-user/app-roles.go | 39 ++++++++++++++-------- pkg/db/management-user/management-users.go | 11 +++--- pkg/db/management-user/permissions.go | 11 +++--- pkg/db/management-user/service-users.go | 19 +++++++---- pkg/db/management-user/sessions.go | 11 +++--- 6 files changed, 72 insertions(+), 48 deletions(-) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index 63d8ba16..d57f8c4b 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -6,7 +6,7 @@ - context.Context parameter has been removed from mongo.Connect() because the deployment connector doesn’t accept a context, meaning that the context passed to mongo.Connect() in previous versions didn't serve a purpose. - Simplfied DropOne and DropAll methods by removing the server response -- Index Model: The old `IndexOptionsBuilder` type was removed and `IndexModel.Options.Name` is no longer accessible as a field. Required steps: +- TESTING REQUIRED: Index Model: The old `IndexOptionsBuilder` type was removed and `IndexModel.Options.Name` is no longer accessible as a field. Required steps: - define variable for index names - capture index names returned by CreateMany - use the stored names when dropping indexes @@ -17,30 +17,30 @@ #### email-templates -- save email template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (testing? Line 128/129) +- TESTING REQUIRED: save email template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (testing? Line 128/129) #### scheduled-emails -- save scheduled-emails: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal +- TESTING REQUIRED: save scheduled-emails: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal #### sms-templates -- save sms template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal +- TESTING REQUIRED: save sms template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal ### participant user #### user-attributes -- The UpdateOptions has been chnaged to UpdateOneOptions to configure UpdateOne operation. +- TESTING REQUIRED: The UpdateOptions has been chnaged to UpdateOneOptions to configure UpdateOne operation. #### users -- add user: MongoDB Go Driver v2 no longer allows constructing or modifying option structs directly, so update options must now be created through the new builder API (options.UpdateOne().SetUpsert(true)) instead of setting fields on UpdateOptions manually. -- update user in db: FindOneAndReplaceOptions can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOneAndReplace().SetReturnDocument(options.After)) instead of setting option fields directly. +- add user: MongoDB Go Driver v2 no longer allows constructing or modifying option structs directly, so update options must now be created through the new builder API (options.UpdateOne().SetUpsert(true)) instead of setting fields on UpdateOptions manually. TESTING REQUIRED +- update user in db: FindOneAndReplaceOptions can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOneAndReplace().SetReturnDocument(options.After)) instead of setting option fields directly. TESTING REQUIRED #### otps -- update the callback for mongo.WithSession to use a context.Context implementation, rather than the custom mongo.SessionContext +- update the callback for mongo.WithSession to use a context.Context implementation, rather than the custom mongo.SessionContext TESTING REQUIRED TEST: If you want to be extra safe, you can: Deploy this change to a non‑production environment first. @@ -52,22 +52,23 @@ run concurrent OTP creations to confirm only the allowed number of documents is #### participants -- configure FindOneAndReplaceOptions through options.FindOneAndReplace().Set... instead of filling the struct fields directly. +- configure FindOneAndReplaceOptions through options.FindOneAndReplace().Set... instead of filling the struct fields directly. TESTING REQUIRED #### confidential responses -- configure replace options via the options.Replace() builder (for example, options.Replace().SetUpsert(true)) instead of instantiating a ReplaceOptions struct literal and passing its address. +- configure replace options via the options.Replace() builder (for example, options.Replace().SetUpsert(true)) instead of instantiating a ReplaceOptions struct literal and passing its address. TESTING REQUIRED #### study-rules -- &options.FindOneOptions{ Sort: sortByPublished } becomes options.FindOne().SetSort(sortByPublished). +- &options.FindOneOptions{ Sort: sortByPublished } becomes options.FindOne().SetSort(sortByPublished). TESTING REQUIRED #### reports - GetUniqueReportKeysForStudy:`Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop. +TESTING REQUIRED: #### surveys -- GetSurveyKeysForStudy: `Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop. -- GetCurrentSurveyVersion: create FindOneOptions using the options.FindOne() builder and setters (for example, options.FindOne().SetSort(sortByPublishedDesc)) instead of instantiating &options.FindOneOptions{} and mutating its fields. -- GetSurveyVersions: create FindOptions using the options.Find() builder and its setters (for example, options.Find().SetProjection(...).SetSort(...)) instead of instantiating &options.FindOptions{} and mutating its fields. +- GetSurveyKeysForStudy: `Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop.TESTING REQUIRED: +- GetCurrentSurveyVersion: create FindOneOptions using the options.FindOne() builder and setters (for example, options.FindOne().SetSort(sortByPublishedDesc)) instead of instantiating &options.FindOneOptions{} and mutating its fields. TESTING REQUIRED: +- GetSurveyVersions: create FindOptions using the options.Find() builder and its setters (for example, options.Find().SetProjection(...).SetSort(...)) instead of instantiating &options.FindOptions{} and mutating its fields. TESTING REQUIRED: diff --git a/pkg/db/management-user/app-roles.go b/pkg/db/management-user/app-roles.go index d2bc5d17..72624570 100644 --- a/pkg/db/management-user/app-roles.go +++ b/pkg/db/management-user/app-roles.go @@ -20,16 +20,28 @@ func (dbService *ManagementUserDBService) collectionAppRoleTemplates(instanceID return dbService.DBClient.Database(dbService.getDBName(instanceID)).Collection(COLLECTION_NAME_APP_ROLE_TEMPLATES) } -var appRoleIndexNames []string +const ( + idxAppRolesSubjectID = "subjectId_1" + idxAppRolesAppName = "appName_1" + idxAppRolesUniqueSubjectTypeRoleKey = "uniq_subjectType_1_subjectId_1_appName_1_role_1" + idxAppRoleTemplatesUniqueAppRole = "uniq_appName_1_role_1" + idxAppRoleTemplatesAppName = "appName_1" +) + +var defaultAppRoleIndexNames = []string{ + idxAppRolesSubjectID, + idxAppRolesAppName, + idxAppRolesUniqueSubjectTypeRoleKey, +} var indexesForAppRolesCollection = []mongo.IndexModel{ { Keys: bson.D{{Key: "subjectId", Value: 1}}, - Options: options.Index().SetName("subjectId_1"), + Options: options.Index().SetName(idxAppRolesSubjectID), }, { Keys: bson.D{{Key: "appName", Value: 1}}, - Options: options.Index().SetName("appName_1"), + Options: options.Index().SetName(idxAppRolesAppName), }, { Keys: bson.D{ @@ -38,7 +50,7 @@ var indexesForAppRolesCollection = []mongo.IndexModel{ {Key: "appName", Value: 1}, {Key: "role", Value: 1}, }, - Options: options.Index().SetName("uniq_subjectType_1_subjectId_1_appName_1_role_1").SetUnique(true), + Options: options.Index().SetName(idxAppRolesUniqueSubjectTypeRoleKey).SetUnique(true), }, } @@ -51,7 +63,7 @@ func (dbService *ManagementUserDBService) DropIndexForAppRolesCollection(instanc slog.Error("Error dropping all indexes for app roles", slog.String("error", err.Error())) } } else { - for _, indexName := range appRoleIndexNames { + for _, indexName := range defaultAppRoleIndexNames { if indexName == "" { slog.Error("Index name is empty for app roles collection") continue @@ -68,23 +80,25 @@ func (dbService *ManagementUserDBService) CreateDefaultIndexesForAppRolesCollect ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionAppRoles(instanceID).Indexes().CreateMany(ctx, indexesForAppRolesCollection) + _, err := dbService.collectionAppRoles(instanceID).Indexes().CreateMany(ctx, indexesForAppRolesCollection) if err != nil { slog.Error("Error creating index for app roles", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } - appRoleIndexNames = names } -var appRoleTemplateIndexNames []string +var defaultAppRoleTemplateIndexNames = []string{ + idxAppRoleTemplatesUniqueAppRole, + idxAppRoleTemplatesAppName, +} var indexesForAppRoleTemplatesCollection = []mongo.IndexModel{ { Keys: bson.D{{Key: "appName", Value: 1}, {Key: "role", Value: 1}}, - Options: options.Index().SetName("uniq_appName_1_role_1").SetUnique(true), + Options: options.Index().SetName(idxAppRoleTemplatesUniqueAppRole).SetUnique(true), }, { Keys: bson.D{{Key: "appName", Value: 1}}, - Options: options.Index().SetName("appName_1"), + Options: options.Index().SetName(idxAppRoleTemplatesAppName), }, } @@ -97,7 +111,7 @@ func (dbService *ManagementUserDBService) DropIndexForAppRoleTemplatesCollection slog.Error("Error dropping all indexes for app role templates", slog.String("error", err.Error())) } } else { - for _, indexName := range appRoleTemplateIndexNames { + for _, indexName := range defaultAppRoleTemplateIndexNames { if indexName == "" { slog.Error("Index name is empty for app role templates collection") continue @@ -114,11 +128,10 @@ func (dbService *ManagementUserDBService) CreateDefaultIndexesForAppRoleTemplate ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionAppRoleTemplates(instanceID).Indexes().CreateMany(ctx, indexesForAppRoleTemplatesCollection) + _, err := dbService.collectionAppRoleTemplates(instanceID).Indexes().CreateMany(ctx, indexesForAppRoleTemplatesCollection) if err != nil { slog.Error("Error creating index for app role templates", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } - appRoleTemplateIndexNames = names } /// App role templates diff --git a/pkg/db/management-user/management-users.go b/pkg/db/management-user/management-users.go index b0a064e8..e701efa8 100644 --- a/pkg/db/management-user/management-users.go +++ b/pkg/db/management-user/management-users.go @@ -9,12 +9,14 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/options" ) -var managementUserIndexNames []string +const idxManagementUsersSub = "uniq_sub_1" + +var defaultManagementUserIndexNames = []string{idxManagementUsersSub} var indexesForManagementUsersCollection = []mongo.IndexModel{ { Keys: bson.D{{Key: "sub", Value: 1}}, - Options: options.Index().SetUnique(true).SetName("uniq_sub_1"), + Options: options.Index().SetUnique(true).SetName(idxManagementUsersSub), }, } @@ -28,7 +30,7 @@ func (dbService *ManagementUserDBService) DropIndexForManagementUsersCollection( slog.Error("Error dropping all indexes for management users", slog.String("error", err.Error())) } } else { - for _, indexName := range managementUserIndexNames { + for _, indexName := range defaultManagementUserIndexNames { if indexName == "" { slog.Error("Index name is empty for management users collection", slog.String("instanceID", instanceID)) continue @@ -44,11 +46,10 @@ func (dbService *ManagementUserDBService) DropIndexForManagementUsersCollection( func (dbService *ManagementUserDBService) CreateDefaultIndexesForManagementUsersCollection(instanceID string) { ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionManagementUsers(instanceID).Indexes().CreateMany(ctx, indexesForManagementUsersCollection) + _, err := dbService.collectionManagementUsers(instanceID).Indexes().CreateMany(ctx, indexesForManagementUsersCollection) if err != nil { slog.Error("Error creating index for management users", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } - managementUserIndexNames = names } func (dbService *ManagementUserDBService) CreateUser( diff --git a/pkg/db/management-user/permissions.go b/pkg/db/management-user/permissions.go index 287c23ab..a80f0de3 100644 --- a/pkg/db/management-user/permissions.go +++ b/pkg/db/management-user/permissions.go @@ -8,7 +8,9 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/options" ) -var permissionIndexNames []string +const idxPermissionsSubjectResourceAction = "subjectId_1_subjectType_1_resourceType_1_resourceKey_1_action_1" + +var defaultPermissionIndexNames = []string{idxPermissionsSubjectResourceAction} var indexesForPermissionsCollection = []mongo.IndexModel{ { @@ -19,7 +21,7 @@ var indexesForPermissionsCollection = []mongo.IndexModel{ {Key: "resourceKey", Value: 1}, {Key: "action", Value: 1}, }, - Options: options.Index().SetName("subjectId_1_subjectType_1_resourceType_1_resourceKey_1_action_1"), + Options: options.Index().SetName(idxPermissionsSubjectResourceAction), }, } @@ -33,7 +35,7 @@ func (dbService *ManagementUserDBService) DropIndexForPermissionsCollection(inst slog.Error("Error dropping all indexes for permissions: ", slog.String("error", err.Error())) } } else { - for _, indexName := range permissionIndexNames { + for _, indexName := range defaultPermissionIndexNames { if indexName == "" { slog.Error("Index name is empty for permissions collection") continue @@ -49,11 +51,10 @@ func (dbService *ManagementUserDBService) DropIndexForPermissionsCollection(inst func (dbService *ManagementUserDBService) CreateDefaultIndexesForPermissionsCollection(instanceID string) { ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionPermissions(instanceID).Indexes().CreateMany(ctx, indexesForPermissionsCollection) + _, err := dbService.collectionPermissions(instanceID).Indexes().CreateMany(ctx, indexesForPermissionsCollection) if err != nil { slog.Error("Error creating index for permissions: ", slog.String("error", err.Error())) } - permissionIndexNames = names } // Create permission diff --git a/pkg/db/management-user/service-users.go b/pkg/db/management-user/service-users.go index 3b0d17df..2e6d3216 100644 --- a/pkg/db/management-user/service-users.go +++ b/pkg/db/management-user/service-users.go @@ -10,7 +10,15 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/options" ) -var serviceUserAPIKeyIndexNames []string +const ( + idxServiceUserAPIKeysKey = "key_1" + idxServiceUserAPIKeysExpiresAt = "expiresAt_1" +) + +var defaultServiceUserAPIKeyIndexNames = []string{ + idxServiceUserAPIKeysKey, + idxServiceUserAPIKeysExpiresAt, +} func (dbService *ManagementUserDBService) collectionServiceUsers(instanceID string) *mongo.Collection { return dbService.DBClient.Database(dbService.getDBName(instanceID)).Collection(COLLECTION_NAME_SERVICE_USERS) @@ -25,13 +33,13 @@ var indexesForServiceUserAPIKeysCollection = []mongo.IndexModel{ Keys: bson.D{ {Key: "key", Value: 1}, }, - Options: options.Index().SetUnique(true).SetName("key_1"), + Options: options.Index().SetUnique(true).SetName(idxServiceUserAPIKeysKey), }, { Keys: bson.D{ {Key: "expiresAt", Value: 1}, }, - Options: options.Index().SetExpireAfterSeconds(0).SetName("expiresAt_1"), + Options: options.Index().SetExpireAfterSeconds(0).SetName(idxServiceUserAPIKeysExpiresAt), }, } @@ -45,7 +53,7 @@ func (dbService *ManagementUserDBService) DropIndexForServiceUserAPIKeysCollecti slog.Error("Error dropping all indexes for service user API keys: ", slog.String("error", err.Error())) } } else { - for _, indexName := range serviceUserAPIKeyIndexNames { + for _, indexName := range defaultServiceUserAPIKeyIndexNames { if indexName == "" { slog.Error("Index name is empty for service user API keys collection") continue @@ -62,11 +70,10 @@ func (dbService *ManagementUserDBService) CreateDefaultIndexesForServiceUserAPIK ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionServiceUserAPIKeys(instanceID).Indexes().CreateMany(ctx, indexesForServiceUserAPIKeysCollection) + _, err := dbService.collectionServiceUserAPIKeys(instanceID).Indexes().CreateMany(ctx, indexesForServiceUserAPIKeysCollection) if err != nil { slog.Error("Error creating index for service user API keys: ", slog.String("error", err.Error())) } - serviceUserAPIKeyIndexNames = names } // CreateServiceUser creates a new service user diff --git a/pkg/db/management-user/sessions.go b/pkg/db/management-user/sessions.go index 2798553b..d14e34e1 100644 --- a/pkg/db/management-user/sessions.go +++ b/pkg/db/management-user/sessions.go @@ -9,7 +9,9 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/options" ) -var sessionIndexNames []string +const idxSessionsCreatedAt = "createdAt_1" + +var defaultSessionIndexNames = []string{idxSessionsCreatedAt} func (dbService *ManagementUserDBService) collectionSessions(instanceID string) *mongo.Collection { return dbService.DBClient.Database(dbService.getDBName(instanceID)).Collection(COLLECTION_NAME_SESSIONS) @@ -18,7 +20,7 @@ func (dbService *ManagementUserDBService) collectionSessions(instanceID string) var indexesForSessionsCollection = []mongo.IndexModel{ { Keys: bson.D{{Key: "createdAt", Value: 1}}, - Options: options.Index().SetExpireAfterSeconds(REMOVE_SESSIONS_AFTER).SetName("createdAt_1"), + Options: options.Index().SetExpireAfterSeconds(REMOVE_SESSIONS_AFTER).SetName(idxSessionsCreatedAt), }, } @@ -32,7 +34,7 @@ func (dbService *ManagementUserDBService) DropIndexForSessionsCollection(instanc slog.Error("Error dropping all indexes for sessions", slog.String("error", err.Error())) } } else { - for _, indexName := range sessionIndexNames { + for _, indexName := range defaultSessionIndexNames { if indexName == "" { slog.Error("Index name is empty for sessions collection") continue @@ -49,11 +51,10 @@ func (dbService *ManagementUserDBService) CreateDefaultIndexesForSessionsCollect ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionSessions(instanceID).Indexes().CreateMany(ctx, indexesForSessionsCollection) + _, err := dbService.collectionSessions(instanceID).Indexes().CreateMany(ctx, indexesForSessionsCollection) if err != nil { slog.Error("Error creating index for sessions: ", slog.String("error", err.Error())) } - sessionIndexNames = names } // Session represents a user session, created when a user logs in From b5ecff76e43eddd17a174761e4f1d42717fb3bd1 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Mon, 23 Mar 2026 07:31:42 +0100 Subject: [PATCH 14/38] Fix messaging and global infos db: decouple DropIndexes(defaults) from CreateIndexes --- pkg/db/global-infos/blocked-jwts.go | 19 +++++++++++++------ pkg/db/global-infos/temptoken.go | 23 ++++++++++++++++------- pkg/db/messaging/email-templates.go | 16 +++++++++------- pkg/db/messaging/sent-emails.go | 16 +++++++++------- pkg/db/messaging/sent-sms.go | 13 ++++++++----- pkg/db/messaging/sms-templates.go | 13 ++++++++----- 6 files changed, 63 insertions(+), 37 deletions(-) diff --git a/pkg/db/global-infos/blocked-jwts.go b/pkg/db/global-infos/blocked-jwts.go index 5322c3d6..d1435f06 100644 --- a/pkg/db/global-infos/blocked-jwts.go +++ b/pkg/db/global-infos/blocked-jwts.go @@ -9,7 +9,15 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/options" ) -var blockedJwtIndexNames []string +const ( + idxBlockedJwtsToken = "token_1" + idxBlockedJwtsExpiresAt = "expiresAt_1" +) + +var defaultBlockedJwtIndexNames = []string{ + idxBlockedJwtsToken, + idxBlockedJwtsExpiresAt, +} type BlockedJwt struct { Token string `bson:"token"` @@ -21,13 +29,13 @@ var indexesForBlockedJwtsCollection = []mongo.IndexModel{ Keys: bson.D{ {Key: "token", Value: 1}, }, - Options: options.Index().SetName("token_1"), + Options: options.Index().SetName(idxBlockedJwtsToken), }, { Keys: bson.D{ {Key: "expiresAt", Value: 1}, }, - Options: options.Index().SetExpireAfterSeconds(0).SetName("expiresAt_1"), + Options: options.Index().SetExpireAfterSeconds(0).SetName(idxBlockedJwtsExpiresAt), }, } @@ -41,7 +49,7 @@ func (dbService *GlobalInfosDBService) DropIndexForBlockedJwtsCollection(dropAll slog.Error("Error dropping all indexes for blocked jwts", slog.String("error", err.Error())) } } else { - for _, indexName := range blockedJwtIndexNames { + for _, indexName := range defaultBlockedJwtIndexNames { if indexName == "" { slog.Error("Index name is empty for blocked jwts collection") continue @@ -58,11 +66,10 @@ func (dbService *GlobalInfosDBService) CreateDefaultIndexesForBlockedJwtsCollect ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionBlockedJwts().Indexes().CreateMany(ctx, indexesForBlockedJwtsCollection) + _, err := dbService.collectionBlockedJwts().Indexes().CreateMany(ctx, indexesForBlockedJwtsCollection) if err != nil { slog.Error("Error creating index for blocked jwts", slog.String("error", err.Error())) } - blockedJwtIndexNames = names } // AddBlockedJwt adds a JWT token to the blocked list with the specified expiration time diff --git a/pkg/db/global-infos/temptoken.go b/pkg/db/global-infos/temptoken.go index a09243ce..64265123 100644 --- a/pkg/db/global-infos/temptoken.go +++ b/pkg/db/global-infos/temptoken.go @@ -13,7 +13,17 @@ import ( umUtils "github.com/case-framework/case-backend/pkg/user-management/utils" ) -var temptokenIndexNames []string +const ( + idxTemptokensUserIDInstanceIDPurpose = "userID_1_instanceID_1_purpose_1" + idxTemptokensExpiration = "expiration_1" + idxTemptokensToken = "token_1" +) + +var defaultTemptokenIndexNames = []string{ + idxTemptokensUserIDInstanceIDPurpose, + idxTemptokensExpiration, + idxTemptokensToken, +} var indexesForTemptokensCollection = []mongo.IndexModel{ { @@ -22,19 +32,19 @@ var indexesForTemptokensCollection = []mongo.IndexModel{ {Key: "instanceID", Value: 1}, {Key: "purpose", Value: 1}, }, - Options: options.Index().SetName("userID_1_instanceID_1_purpose_1"), + Options: options.Index().SetName(idxTemptokensUserIDInstanceIDPurpose), }, { Keys: bson.D{ {Key: "expiration", Value: 1}, }, - Options: options.Index().SetExpireAfterSeconds(0).SetName("expiration_1"), + Options: options.Index().SetExpireAfterSeconds(0).SetName(idxTemptokensExpiration), }, { Keys: bson.D{ {Key: "token", Value: 1}, }, - Options: options.Index().SetUnique(true).SetName("token_1"), + Options: options.Index().SetUnique(true).SetName(idxTemptokensToken), }, } @@ -47,7 +57,7 @@ func (dbService *GlobalInfosDBService) DropIndexForTemptokensCollection(dropAll slog.Error("Error dropping indexes for temptokens", slog.String("error", err.Error())) } } else { - for _, indexName := range temptokenIndexNames { + for _, indexName := range defaultTemptokenIndexNames { if indexName == "" { slog.Error("Index name is empty for temptokens collection") continue @@ -64,11 +74,10 @@ func (dbService *GlobalInfosDBService) CreateDefaultIndexesForTemptokensCollecti ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionTemptokens().Indexes().CreateMany(ctx, indexesForTemptokensCollection) + _, err := dbService.collectionTemptokens().Indexes().CreateMany(ctx, indexesForTemptokensCollection) if err != nil { slog.Error("Error creating index for temptokens", slog.String("error", err.Error())) } - temptokenIndexNames = names } func (dbService *GlobalInfosDBService) AddTempToken(t userTypes.TempToken) (token string, err error) { diff --git a/pkg/db/messaging/email-templates.go b/pkg/db/messaging/email-templates.go index 8ef8d7dc..177925f0 100644 --- a/pkg/db/messaging/email-templates.go +++ b/pkg/db/messaging/email-templates.go @@ -1,7 +1,6 @@ package messaging import ( - "fmt" "log/slog" "go.mongodb.org/mongo-driver/v2/bson" @@ -11,7 +10,11 @@ import ( messagingTypes "github.com/case-framework/case-backend/pkg/messaging/types" ) -var emailTemplateIndexNames []string +const idxEmailTemplatesMessageTypeStudyKey = "messageType_1_studyKey_1" + +var defaultEmailTemplateIndexNames = []string{ + idxEmailTemplatesMessageTypeStudyKey, +} var indexesForEmailTemplatesCollection = []mongo.IndexModel{ { @@ -19,7 +22,7 @@ var indexesForEmailTemplatesCollection = []mongo.IndexModel{ {Key: "messageType", Value: 1}, {Key: "studyKey", Value: 1}, }, - Options: options.Index().SetUnique(true).SetName("messageType_1_studyKey_1"), + Options: options.Index().SetUnique(true).SetName(idxEmailTemplatesMessageTypeStudyKey), }, } @@ -33,9 +36,9 @@ func (messagingDBService *MessagingDBService) DropIndexForEmailTemplatesCollecti slog.Error("Error dropping all indexes for email templates", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, indexName := range emailTemplateIndexNames { + for _, indexName := range defaultEmailTemplateIndexNames { if indexName == "" { - slog.Error("Index name is empty for email templates collection", slog.String("index", fmt.Sprintf("%+v", indexName))) + slog.Error("Index name is empty for email templates collection") continue } err := messagingDBService.collectionEmailTemplates(instanceID).Indexes().DropOne(ctx, indexName) @@ -49,11 +52,10 @@ func (messagingDBService *MessagingDBService) DropIndexForEmailTemplatesCollecti func (messagingDBService *MessagingDBService) CreateDefaultIndexesForEmailTemplatesCollection(instanceID string) { ctx, cancel := messagingDBService.getContext() defer cancel() - names, err := messagingDBService.collectionEmailTemplates(instanceID).Indexes().CreateMany(ctx, indexesForEmailTemplatesCollection) + _, err := messagingDBService.collectionEmailTemplates(instanceID).Indexes().CreateMany(ctx, indexesForEmailTemplatesCollection) if err != nil { slog.Error("Error creating index for email templates", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } - emailTemplateIndexNames = names } // find all email templates with study key empty diff --git a/pkg/db/messaging/sent-emails.go b/pkg/db/messaging/sent-emails.go index 12c341fb..7550d90b 100644 --- a/pkg/db/messaging/sent-emails.go +++ b/pkg/db/messaging/sent-emails.go @@ -1,7 +1,6 @@ package messaging import ( - "fmt" "log/slog" "time" @@ -11,7 +10,11 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/options" ) -var sentEmailIndexNames []string +const idxSentEmailsUserIDSentAt = "userId_1_sentAt_1" + +var defaultSentEmailIndexNames = []string{ + idxSentEmailsUserIDSentAt, +} var indexesForSentEmailsCollection = []mongo.IndexModel{ { @@ -19,7 +22,7 @@ var indexesForSentEmailsCollection = []mongo.IndexModel{ {Key: "userId", Value: 1}, {Key: "sentAt", Value: 1}, }, - Options: options.Index().SetName("userId_1_sentAt_1"), + Options: options.Index().SetName(idxSentEmailsUserIDSentAt), }, } @@ -33,9 +36,9 @@ func (dbService *MessagingDBService) DropIndexForSentEmailsCollection(instanceID slog.Error("Error dropping all indexes for sent emails", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, indexName := range sentEmailIndexNames { + for _, indexName := range defaultSentEmailIndexNames { if indexName == "" { - slog.Error("Index name is empty for sent emails collection", slog.String("index", fmt.Sprintf("%+v", indexName))) + slog.Error("Index name is empty for sent emails collection") continue } err := dbService.collectionSentEmails(instanceID).Indexes().DropOne(ctx, indexName) @@ -50,11 +53,10 @@ func (dbService *MessagingDBService) CreateDefaultIndexesForSentEmailsCollection ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionSentEmails(instanceID).Indexes().CreateMany(ctx, indexesForSentEmailsCollection) + _, err := dbService.collectionSentEmails(instanceID).Indexes().CreateMany(ctx, indexesForSentEmailsCollection) if err != nil { slog.Error("Error creating index for sent emails", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } - sentEmailIndexNames = names } func (dbService *MessagingDBService) AddToSentEmails(instanceID string, email messagingTypes.OutgoingEmail) (messagingTypes.OutgoingEmail, error) { diff --git a/pkg/db/messaging/sent-sms.go b/pkg/db/messaging/sent-sms.go index 20b18285..28dc9280 100644 --- a/pkg/db/messaging/sent-sms.go +++ b/pkg/db/messaging/sent-sms.go @@ -10,7 +10,11 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/options" ) -var sentSMSIndexNames []string +const idxSentSMSUserIDSentAtMessageType = "userID_1_sentAt_1_messageType_1" + +var defaultSentSMSIndexNames = []string{ + idxSentSMSUserIDSentAtMessageType, +} var indexesForSentSMSCollection = []mongo.IndexModel{ { @@ -19,7 +23,7 @@ var indexesForSentSMSCollection = []mongo.IndexModel{ {Key: "sentAt", Value: 1}, {Key: "messageType", Value: 1}, }, - Options: options.Index().SetName("userID_1_sentAt_1_messageType_1"), + Options: options.Index().SetName(idxSentSMSUserIDSentAtMessageType), }, } @@ -33,7 +37,7 @@ func (dbService *MessagingDBService) DropIndexForSentSMSCollection(instanceID st slog.Error("Error dropping all indexes for sent SMS", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, indexName := range sentSMSIndexNames { + for _, indexName := range defaultSentSMSIndexNames { if indexName == "" { slog.Error("Index name is empty for sent SMS collection") continue @@ -50,11 +54,10 @@ func (dbService *MessagingDBService) CreateDefaultIndexesForSentSMSCollection(in ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionSentSMS(instanceID).Indexes().CreateMany(ctx, indexesForSentSMSCollection) + _, err := dbService.collectionSentSMS(instanceID).Indexes().CreateMany(ctx, indexesForSentSMSCollection) if err != nil { slog.Error("Error creating index for sent SMS", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } - sentSMSIndexNames = names } func (dbService *MessagingDBService) AddToSentSMS(instanceID string, sms types.SentSMS) (types.SentSMS, error) { diff --git a/pkg/db/messaging/sms-templates.go b/pkg/db/messaging/sms-templates.go index 4cdd9c80..ab28c704 100644 --- a/pkg/db/messaging/sms-templates.go +++ b/pkg/db/messaging/sms-templates.go @@ -10,14 +10,18 @@ import ( messagingTypes "github.com/case-framework/case-backend/pkg/messaging/types" ) -var smsTemplateIndexNames []string +const idxSMSTemplatesMessageType = "messageType_1" + +var defaultSMSTemplateIndexNames = []string{ + idxSMSTemplatesMessageType, +} var indexesForSMSTemplatesCollection = []mongo.IndexModel{ { Keys: bson.D{ {Key: "messageType", Value: 1}, }, - Options: options.Index().SetUnique(true).SetName("messageType_1"), + Options: options.Index().SetUnique(true).SetName(idxSMSTemplatesMessageType), }, } @@ -30,7 +34,7 @@ func (messagingDBService *MessagingDBService) DropIndexForSMSTemplatesCollection slog.Error("Error dropping all indexes for SMS templates", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, indexName := range smsTemplateIndexNames { + for _, indexName := range defaultSMSTemplateIndexNames { if indexName == "" { slog.Error("Index name is empty for SMS templates collection") continue @@ -47,11 +51,10 @@ func (messagingDBService *MessagingDBService) CreateDefaultIndexesForSMSTemplate ctx, cancel := messagingDBService.getContext() defer cancel() - names, err := messagingDBService.collectionSMSTemplates(instanceID).Indexes().CreateMany(ctx, indexesForSMSTemplatesCollection) + _, err := messagingDBService.collectionSMSTemplates(instanceID).Indexes().CreateMany(ctx, indexesForSMSTemplatesCollection) if err != nil { slog.Error("Error creating index for SMS templates", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } - smsTemplateIndexNames = names } // save email template (if id is empty, insert, else update) From cf679f6dcb32755ec40db600684de23d9f635738 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Tue, 24 Mar 2026 12:23:18 +0100 Subject: [PATCH 15/38] Fix study and participant user db: decouple DropIndexes from CreateIndexes --- pkg/db/participant-user/failedOtpAttempts.go | 18 +++++++---- pkg/db/participant-user/otps.go | 18 +++++++---- pkg/db/participant-user/renew-tokens.go | 28 ++++++++++------ pkg/db/participant-user/user-attributes.go | 13 +++++--- pkg/db/participant-user/users.go | 33 +++++++++++++------ pkg/db/study/confidential-id-map.go | 13 +++++--- pkg/db/study/confidential-responses.go | 18 ++++++----- pkg/db/study/participant-file-infos.go | 22 ++++++++----- pkg/db/study/participants.go | 34 +++++++++++++------- pkg/db/study/reports.go | 23 +++++++++---- pkg/db/study/responses.go | 34 +++++++++++++------- pkg/db/study/study-code-lists.go | 16 +++++---- pkg/db/study/study-counters.go | 18 ++++++----- pkg/db/study/study-infos.go | 16 +++++---- pkg/db/study/study-rules.go | 22 ++++++++----- pkg/db/study/study-variables.go | 16 +++++---- pkg/db/study/surveys.go | 30 +++++++++++------ pkg/db/study/task-queue.go | 17 +++++----- 18 files changed, 245 insertions(+), 144 deletions(-) diff --git a/pkg/db/participant-user/failedOtpAttempts.go b/pkg/db/participant-user/failedOtpAttempts.go index 209e6867..07c92d94 100644 --- a/pkg/db/participant-user/failedOtpAttempts.go +++ b/pkg/db/participant-user/failedOtpAttempts.go @@ -10,10 +10,15 @@ import ( ) const ( - FAILED_OTP_ATTEMP_WINDOW = 60 * 5 + FAILED_OTP_ATTEMP_WINDOW = 60 * 5 + idxFailedOtpAttemptsUserID = "userID_1" + idxFailedOtpAttemptsTimestamp = "timestamp_1" ) -var failedOTPAttemptIndexNames []string +var defaultFailedOtpAttemptIndexNames = []string{ + idxFailedOtpAttemptsUserID, + idxFailedOtpAttemptsTimestamp, +} type FailedOtpAttempt struct { Timestamp time.Time `json:"timestamp" bson:"timestamp"` @@ -25,13 +30,13 @@ var indexesForFailedOtpAttemptsCollection = []mongo.IndexModel{ Keys: bson.D{ {Key: "userID", Value: 1}, }, - Options: options.Index().SetName("userID_1"), + Options: options.Index().SetName(idxFailedOtpAttemptsUserID), }, { Keys: bson.D{ {Key: "timestamp", Value: 1}, }, - Options: options.Index().SetExpireAfterSeconds(FAILED_OTP_ATTEMP_WINDOW).SetName("timestamp_1"), + Options: options.Index().SetExpireAfterSeconds(FAILED_OTP_ATTEMP_WINDOW).SetName(idxFailedOtpAttemptsTimestamp), }, } @@ -45,7 +50,7 @@ func (dbService *ParticipantUserDBService) DropIndexForFailedOtpAttemptsCollecti slog.Error("Error dropping all indexes for FailedOtpAttempts", slog.String("error", err.Error())) } } else { - for _, indexName := range failedOTPAttemptIndexNames { + for _, indexName := range defaultFailedOtpAttemptIndexNames { if indexName == "" { slog.Error("Index name is empty for FailedOtpAttempts collection") continue @@ -62,11 +67,10 @@ func (dbService *ParticipantUserDBService) CreateDefaultIndexesForFailedOtpAttem ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionFailedOtpAttempts(instanceID).Indexes().CreateMany(ctx, indexesForFailedOtpAttemptsCollection) + _, err := dbService.collectionFailedOtpAttempts(instanceID).Indexes().CreateMany(ctx, indexesForFailedOtpAttemptsCollection) if err != nil { slog.Error("Error creating index for FailedOtpAttempts", slog.String("error", err.Error())) } - failedOTPAttemptIndexNames = names } func (dbService *ParticipantUserDBService) CountFailedOtpAttempts(instanceID string, userID string) (int64, error) { diff --git a/pkg/db/participant-user/otps.go b/pkg/db/participant-user/otps.go index 65072b05..2af41dfd 100644 --- a/pkg/db/participant-user/otps.go +++ b/pkg/db/participant-user/otps.go @@ -14,10 +14,15 @@ import ( ) const ( - OTP_TTL = 60 * 15 + OTP_TTL = 60 * 15 + idxOTPsUserIDCode = "uniq_userID_1_code_1" + idxOTPsCreatedAt = "createdAt_1" ) -var otpIndexNames []string +var defaultOTPIndexNames = []string{ + idxOTPsUserIDCode, + idxOTPsCreatedAt, +} var indexesForOTPsCollection = []mongo.IndexModel{ { @@ -25,13 +30,13 @@ var indexesForOTPsCollection = []mongo.IndexModel{ {Key: "userID", Value: 1}, {Key: "code", Value: 1}, }, - Options: options.Index().SetUnique(true).SetName("uniq_userID_1_code_1"), + Options: options.Index().SetUnique(true).SetName(idxOTPsUserIDCode), }, { Keys: bson.D{ {Key: "createdAt", Value: 1}, }, - Options: options.Index().SetExpireAfterSeconds(OTP_TTL).SetName("createdAt_1"), + Options: options.Index().SetExpireAfterSeconds(OTP_TTL).SetName(idxOTPsCreatedAt), }, } @@ -45,7 +50,7 @@ func (dbService *ParticipantUserDBService) DropIndexForOTPsCollection(instanceID slog.Error("Error dropping all indexes for OTPs", slog.String("error", err.Error())) } } else { - for _, indexName := range otpIndexNames { + for _, indexName := range defaultOTPIndexNames { if indexName == "" { slog.Error("Index name is empty for OTPs collection") continue @@ -62,11 +67,10 @@ func (dbService *ParticipantUserDBService) CreateDefaultIndexesForOTPsCollection ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionOTPs(instanceID).Indexes().CreateMany(ctx, indexesForOTPsCollection) + _, err := dbService.collectionOTPs(instanceID).Indexes().CreateMany(ctx, indexesForOTPsCollection) if err != nil { slog.Error("Error creating index for OTPs", slog.String("error", err.Error())) } - otpIndexNames = names } func (dbService *ParticipantUserDBService) CreateOTP(instanceID string, userID string, code string, t userTypes.OTPType, maxOTPCount int64) error { diff --git a/pkg/db/participant-user/renew-tokens.go b/pkg/db/participant-user/renew-tokens.go index f35c43cf..2875a030 100644 --- a/pkg/db/participant-user/renew-tokens.go +++ b/pkg/db/participant-user/renew-tokens.go @@ -13,11 +13,20 @@ import ( ) const ( - RENEW_TOKEN_GRACE_PERIOD = 30 // seconds - RENEW_TOKEN_DEFAULT_LIFETIME = 60 * 60 * 24 * 90 + RENEW_TOKEN_GRACE_PERIOD = 30 // seconds + RENEW_TOKEN_DEFAULT_LIFETIME = 60 * 60 * 24 * 90 + idxRenewTokensUserIDRenewTokenExpiresAt = "userID_1_renewToken_1_expiresAt_1" + idxRenewTokensUserIDSessionID = "userID_1_sessionID_1" + idxRenewTokensExpiresAt = "expiresAt_1" + idxRenewTokensUniqueRenewToken = "uniq_renewToken_1" ) -var renewTokenIndexNames []string +var defaultRenewTokenIndexNames = []string{ + idxRenewTokensUserIDRenewTokenExpiresAt, + idxRenewTokensUserIDSessionID, + idxRenewTokensExpiresAt, + idxRenewTokensUniqueRenewToken, +} var indexesForRenewTokensCollection = []mongo.IndexModel{ { @@ -26,26 +35,26 @@ var indexesForRenewTokensCollection = []mongo.IndexModel{ {Key: "renewToken", Value: 1}, {Key: "expiresAt", Value: 1}, }, - Options: options.Index().SetName("userID_1_renewToken_1_expiresAt_1"), + Options: options.Index().SetName(idxRenewTokensUserIDRenewTokenExpiresAt), }, { Keys: bson.D{ {Key: "userID", Value: 1}, {Key: "sessionID", Value: 1}, }, - Options: options.Index().SetName("userID_1_sessionID_1"), + Options: options.Index().SetName(idxRenewTokensUserIDSessionID), }, { Keys: bson.D{ {Key: "expiresAt", Value: 1}, }, - Options: options.Index().SetExpireAfterSeconds(RENEW_TOKEN_GRACE_PERIOD).SetName("expiresAt_1"), + Options: options.Index().SetExpireAfterSeconds(RENEW_TOKEN_GRACE_PERIOD).SetName(idxRenewTokensExpiresAt), }, { Keys: bson.D{ {Key: "renewToken", Value: 1}, }, - Options: options.Index().SetUnique(true).SetName("uniq_renewToken_1"), + Options: options.Index().SetUnique(true).SetName(idxRenewTokensUniqueRenewToken), }, } @@ -59,7 +68,7 @@ func (dbService *ParticipantUserDBService) DropIndexForRenewTokensCollection(ins slog.Error("Error dropping all indexes for renew tokens", slog.String("error", err.Error())) } } else { - for _, indexName := range renewTokenIndexNames { + for _, indexName := range defaultRenewTokenIndexNames { if indexName == "" { slog.Error("Index name is empty for renew tokens collection") continue @@ -76,13 +85,12 @@ func (dbService *ParticipantUserDBService) CreateDefaultIndexesForRenewTokensCol ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionRenewTokens(instanceID).Indexes().CreateMany( + _, err := dbService.collectionRenewTokens(instanceID).Indexes().CreateMany( ctx, indexesForRenewTokensCollection, ) if err != nil { slog.Error("Error creating index for renew tokens", slog.String("error", err.Error())) } - renewTokenIndexNames = names } func (dbService *ParticipantUserDBService) CreateRenewToken(instanceID string, userID string, token string, lifeTimeInSec int, sessionID string) error { diff --git a/pkg/db/participant-user/user-attributes.go b/pkg/db/participant-user/user-attributes.go index e62fd318..cef31760 100644 --- a/pkg/db/participant-user/user-attributes.go +++ b/pkg/db/participant-user/user-attributes.go @@ -12,12 +12,16 @@ import ( userTypes "github.com/case-framework/case-backend/pkg/user-management/types" ) -var participantUserAttributeIndexNames []string +const idxParticipantUserAttributesUserIdType = "userId_1_type_1" + +var defaultParticipantUserAttributeIndexNames = []string{ + idxParticipantUserAttributesUserIdType, +} var indexesForParticipantUserAttributesCollection = []mongo.IndexModel{ { Keys: bson.D{{Key: "userId", Value: 1}, {Key: "type", Value: 1}}, - Options: options.Index().SetName("userId_1_type_1").SetUnique(true), + Options: options.Index().SetName(idxParticipantUserAttributesUserIdType).SetUnique(true), }, } @@ -31,7 +35,7 @@ func (dbService *ParticipantUserDBService) DropIndexForParticipantUserAttributes slog.Error("Error dropping all indexes for participant user attributes", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, indexName := range participantUserAttributeIndexNames { + for _, indexName := range defaultParticipantUserAttributeIndexNames { if indexName == "" { slog.Error("Index name is empty for participant user attributes collection", slog.String("instanceID", instanceID)) continue @@ -48,11 +52,10 @@ func (dbService *ParticipantUserDBService) CreateDefaultIndexesForParticipantUse ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionParticipantUserAttributes(instanceID).Indexes().CreateMany(ctx, indexesForParticipantUserAttributesCollection) + _, err := dbService.collectionParticipantUserAttributes(instanceID).Indexes().CreateMany(ctx, indexesForParticipantUserAttributesCollection) if err != nil { slog.Error("Error creating index for participant user attributes", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } - participantUserAttributeIndexNames = names } // Create or update a user attribute for a user by type diff --git a/pkg/db/participant-user/users.go b/pkg/db/participant-user/users.go index a3d1758d..69e0f8ca 100644 --- a/pkg/db/participant-user/users.go +++ b/pkg/db/participant-user/users.go @@ -13,40 +13,54 @@ import ( umTypes "github.com/case-framework/case-backend/pkg/user-management/types" ) -var participantUserIndexNames []string +const ( + idxParticipantUsersMarkedForDeletion = "timestamps.markedForDeletion_1" + idxParticipantUsersUniqueAccountID = "uniq_account.accountID_1" + idxParticipantUsersCreatedAt = "timestamps.createdAt_1" + idxParticipantUsersAccountConfirmedCreate = "account.accountConfirmedAt_1_timestamps.createdAt_1" + idxParticipantUsersWeeklyMessageDay = "contactPreferences.receiveWeeklyMessageDayOfWeek_1" +) + +var defaultParticipantUserIndexNames = []string{ + idxParticipantUsersMarkedForDeletion, + idxParticipantUsersUniqueAccountID, + idxParticipantUsersCreatedAt, + idxParticipantUsersAccountConfirmedCreate, + idxParticipantUsersWeeklyMessageDay, +} var indexesForParticipantUsersCollection = []mongo.IndexModel{ { Keys: bson.D{ {Key: "timestamps.markedForDeletion", Value: 1}, }, - Options: options.Index().SetName("timestamps.markedForDeletion_1"), + Options: options.Index().SetName(idxParticipantUsersMarkedForDeletion), }, { Keys: bson.D{ {Key: "account.accountID", Value: 1}, }, Options: options.Index().SetUnique(true). - SetName("uniq_account.accountID_1"), + SetName(idxParticipantUsersUniqueAccountID), }, { Keys: bson.D{ {Key: "timestamps.createdAt", Value: 1}, }, - Options: options.Index().SetName("timestamps.createdAt_1"), + Options: options.Index().SetName(idxParticipantUsersCreatedAt), }, { Keys: bson.D{ {Key: "account.accountConfirmedAt", Value: 1}, {Key: "timestamps.createdAt", Value: 1}, }, - Options: options.Index().SetName("account.accountConfirmedAt_1_timestamps.createdAt_1"), + Options: options.Index().SetName(idxParticipantUsersAccountConfirmedCreate), }, { Keys: bson.D{ {Key: "contactPreferences.receiveWeeklyMessageDayOfWeek", Value: 1}, }, - Options: options.Index().SetName("contactPreferences.receiveWeeklyMessageDayOfWeek_1"), + Options: options.Index().SetName(idxParticipantUsersWeeklyMessageDay), }, } @@ -60,7 +74,7 @@ func (dbService *ParticipantUserDBService) DropIndexForParticipantUsersCollectio slog.Error("Error dropping all indexes for participant users", slog.String("error", err.Error())) } } else { - for _, indexName := range participantUserIndexNames { + for _, indexName := range defaultParticipantUserIndexNames { if indexName == "" { slog.Error("Index name is empty for participant users collection") continue @@ -77,13 +91,12 @@ func (dbService *ParticipantUserDBService) CreateDefaultIndexesForParticipantUse ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionParticipantUsers(instanceID).Indexes().CreateMany( + _, err := dbService.collectionParticipantUsers(instanceID).Indexes().CreateMany( ctx, indexesForParticipantUsersCollection, ) if err != nil { slog.Error("Error creating index for participant users", slog.String("error", err.Error())) } - participantUserIndexNames = names } func (dbService *ParticipantUserDBService) FixFieldNameForContactInfos(instanceID string) error { @@ -203,7 +216,7 @@ func (dbService *ParticipantUserDBService) _updateUserInDB(orgID string, user um elem := umTypes.User{} filter := bson.M{"_id": user.ID} fro := options.FindOneAndReplace(). - SetReturnDocument(options.After) + SetReturnDocument(options.After) err := dbService.collectionParticipantUsers(orgID).FindOneAndReplace(ctx, filter, user, fro).Decode(&elem) return elem, err } diff --git a/pkg/db/study/confidential-id-map.go b/pkg/db/study/confidential-id-map.go index 48e8e4cc..217bb553 100644 --- a/pkg/db/study/confidential-id-map.go +++ b/pkg/db/study/confidential-id-map.go @@ -8,7 +8,11 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/options" ) -var confidentialIDMapIndexNames []string +const idxConfidentialIDMapConfidentialIDStudyKey = "confidentialID_1_studyKey_1" + +var defaultConfidentialIDMapIndexNames = []string{ + idxConfidentialIDMapConfidentialIDStudyKey, +} var indexesForConfidentialIDMapCollection = []mongo.IndexModel{ { @@ -16,7 +20,7 @@ var indexesForConfidentialIDMapCollection = []mongo.IndexModel{ {Key: "confidentialID", Value: 1}, {Key: "studyKey", Value: 1}, }, - Options: options.Index().SetUnique(true).SetName("confidentialID_1_studyKey_1"), + Options: options.Index().SetUnique(true).SetName(idxConfidentialIDMapConfidentialIDStudyKey), }, } @@ -31,7 +35,7 @@ func (dbService *StudyDBService) DropIndexForConfidentialIDMapCollection(instanc slog.Error("Error dropping all indexes for confidentialIDMap", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, indexName := range confidentialIDMapIndexNames { + for _, indexName := range defaultConfidentialIDMapIndexNames { if indexName == "" { slog.Error("Index name is empty for confidentialIDMap collection") continue @@ -48,11 +52,10 @@ func (dbService *StudyDBService) CreateDefaultIndexesForConfidentialIDMapCollect ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionConfidentialIDMap(instanceID).Indexes().CreateMany(ctx, indexesForConfidentialIDMapCollection) + _, err := dbService.collectionConfidentialIDMap(instanceID).Indexes().CreateMany(ctx, indexesForConfidentialIDMapCollection) if err != nil { slog.Error("Error creating index for confidentialIDMap", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } - confidentialIDMapIndexNames = names } func (dbService *StudyDBService) AddConfidentialIDMapEntry(instanceID, confidentialID, profileID, studyKey string) error { diff --git a/pkg/db/study/confidential-responses.go b/pkg/db/study/confidential-responses.go index d221277e..edd2365d 100644 --- a/pkg/db/study/confidential-responses.go +++ b/pkg/db/study/confidential-responses.go @@ -3,7 +3,6 @@ package study import ( "context" "errors" - "fmt" "log/slog" studyTypes "github.com/case-framework/case-backend/pkg/study/types" @@ -12,18 +11,22 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/options" ) +const idxConfidentialResponsesParticipantIDKey = "participantID_1_key_1" + +var defaultConfidentialResponseIndexNames = []string{ + idxConfidentialResponsesParticipantIDKey, +} + var indexesForConfidentialResponsesCollection = []mongo.IndexModel{ { Keys: bson.D{ {Key: "participantID", Value: 1}, {Key: "key", Value: 1}, }, - Options: options.Index().SetName("participantID_1_key_1"), + Options: options.Index().SetName(idxConfidentialResponsesParticipantIDKey), }, } -var confidentialResponseIndexNames []string - func (dbService *StudyDBService) DropIndexForConfidentialResponsesCollection(instanceID string, studyKey string, dropAll bool) { ctx, cancel := dbService.getContext() defer cancel() @@ -36,9 +39,9 @@ func (dbService *StudyDBService) DropIndexForConfidentialResponsesCollection(ins slog.Error("Error dropping all indexes for confidential responses", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } } else { - for _, indexName := range confidentialResponseIndexNames { + for _, indexName := range defaultConfidentialResponseIndexNames { if indexName == "" { - slog.Error("Index name is empty for confidential responses collection", slog.String("index", fmt.Sprintf("%+v", indexName))) + slog.Error("Index name is empty for confidential responses collection") continue } err := collection.Indexes().DropOne(ctx, indexName) @@ -54,11 +57,10 @@ func (dbService *StudyDBService) CreateDefaultIndexesForConfidentialResponsesCol defer cancel() collection := dbService.collectionConfidentialResponses(instanceID, studyKey) - names, err := collection.Indexes().CreateMany(ctx, indexesForConfidentialResponsesCollection) + _, err := collection.Indexes().CreateMany(ctx, indexesForConfidentialResponsesCollection) if err != nil { slog.Error("Error creating index for confidential responses", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } - confidentialResponseIndexNames = names } func (dbService *StudyDBService) AddConfidentialResponse(instanceID string, studyKey string, response studyTypes.SurveyResponse) (string, error) { diff --git a/pkg/db/study/participant-file-infos.go b/pkg/db/study/participant-file-infos.go index 481365a2..cb357fa6 100644 --- a/pkg/db/study/participant-file-infos.go +++ b/pkg/db/study/participant-file-infos.go @@ -1,7 +1,6 @@ package study import ( - "fmt" "log/slog" "time" @@ -12,7 +11,15 @@ import ( studytypes "github.com/case-framework/case-backend/pkg/study/types" ) -var participantFileIndexNames []string +const ( + idxParticipantFilesParticipantIDCreatedAt = "participantID_1_createdAt_-1" + idxParticipantFilesCreatedAt = "createdAt_-1" +) + +var defaultParticipantFileIndexNames = []string{ + idxParticipantFilesParticipantIDCreatedAt, + idxParticipantFilesCreatedAt, +} var indexesForParticipantFilesCollection = []mongo.IndexModel{ { @@ -20,13 +27,13 @@ var indexesForParticipantFilesCollection = []mongo.IndexModel{ {Key: "participantID", Value: 1}, {Key: "createdAt", Value: -1}, }, - Options: options.Index().SetName("participantID_1_createdAt_-1"), + Options: options.Index().SetName(idxParticipantFilesParticipantIDCreatedAt), }, { Keys: bson.D{ {Key: "createdAt", Value: -1}, }, - Options: options.Index().SetName("createdAt_-1"), + Options: options.Index().SetName(idxParticipantFilesCreatedAt), }, } @@ -42,9 +49,9 @@ func (dbService *StudyDBService) DropIndexForParticipantFilesCollection(instance slog.Error("Error dropping all indexes for participant files", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } } else { - for _, indexName := range participantFileIndexNames { + for _, indexName := range defaultParticipantFileIndexNames { if indexName == "" { - slog.Error("Index name is empty for participant files collection", slog.String("index", fmt.Sprintf("%+v", indexName)), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) + slog.Error("Index name is empty for participant files collection", slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) continue } err := collection.Indexes().DropOne(ctx, indexName) @@ -60,11 +67,10 @@ func (dbService *StudyDBService) CreateDefaultIndexesForParticipantFilesCollecti defer cancel() collection := dbService.collectionFiles(instanceID, studyKey) - names, err := collection.Indexes().CreateMany(ctx, indexesForParticipantFilesCollection) + _, err := collection.Indexes().CreateMany(ctx, indexesForParticipantFilesCollection) if err != nil { slog.Error("Error creating index for participant files", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } - participantFileIndexNames = names } // get one by id diff --git a/pkg/db/study/participants.go b/pkg/db/study/participants.go index dca24ec8..62c73d58 100644 --- a/pkg/db/study/participants.go +++ b/pkg/db/study/participants.go @@ -3,7 +3,6 @@ package study import ( "context" "errors" - "fmt" "log/slog" "time" @@ -14,39 +13,53 @@ import ( studyTypes "github.com/case-framework/case-backend/pkg/study/types" ) -var participantIndexNames []string +const ( + idxParticipantsParticipantID = "participantID_1" + idxParticipantsStudyStatus = "studyStatus_1" + idxParticipantsEnteredAt = "enteredAt_1" + idxParticipantsMessagesScheduledForStatus = "messages.scheduledFor_1_studyStatus_1" + idxParticipantsMessagesScheduledFor = "messages.scheduledFor_1" +) + +var defaultParticipantIndexNames = []string{ + idxParticipantsParticipantID, + idxParticipantsStudyStatus, + idxParticipantsEnteredAt, + idxParticipantsMessagesScheduledForStatus, + idxParticipantsMessagesScheduledFor, +} var indexesForParticipantsCollection = []mongo.IndexModel{ { Keys: bson.D{ {Key: "participantID", Value: 1}, }, - Options: options.Index().SetUnique(true).SetName("participantID_1"), + Options: options.Index().SetUnique(true).SetName(idxParticipantsParticipantID), }, { Keys: bson.D{ {Key: "studyStatus", Value: 1}, }, - Options: options.Index().SetName("studyStatus_1"), + Options: options.Index().SetName(idxParticipantsStudyStatus), }, { Keys: bson.D{ {Key: "enteredAt", Value: 1}, }, - Options: options.Index().SetName("enteredAt_1"), + Options: options.Index().SetName(idxParticipantsEnteredAt), }, { Keys: bson.D{ {Key: "messages.scheduledFor", Value: 1}, {Key: "studyStatus", Value: 1}, }, - Options: options.Index().SetName("messages.scheduledFor_1_studyStatus_1"), + Options: options.Index().SetName(idxParticipantsMessagesScheduledForStatus), }, { Keys: bson.D{ {Key: "messages.scheduledFor", Value: 1}, }, - Options: options.Index().SetName("messages.scheduledFor_1"), + Options: options.Index().SetName(idxParticipantsMessagesScheduledFor), }, } @@ -62,9 +75,9 @@ func (dbService *StudyDBService) DropIndexForParticipantsCollection(instanceID s slog.Error("Error dropping all indexes for participants", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } } else { - for _, indexName := range participantIndexNames { + for _, indexName := range defaultParticipantIndexNames { if indexName == "" { - slog.Error("Index name is empty for participants collection", slog.String("index", fmt.Sprintf("%+v", indexName))) + slog.Error("Index name is empty for participants collection") continue } err := collection.Indexes().DropOne(ctx, indexName) @@ -80,11 +93,10 @@ func (dbService *StudyDBService) CreateDefaultIndexesForParticipantsCollection(i defer cancel() collection := dbService.collectionParticipants(instanceID, studyKey) - names, err := collection.Indexes().CreateMany(ctx, indexesForParticipantsCollection) + _, err := collection.Indexes().CreateMany(ctx, indexesForParticipantsCollection) if err != nil { slog.Error("Error creating index for participants", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } - participantIndexNames = names } func (dbService *StudyDBService) SaveParticipantState(instanceID string, studyKey string, pState studyTypes.Participant) (studyTypes.Participant, error) { diff --git a/pkg/db/study/reports.go b/pkg/db/study/reports.go index fe0875a2..34b4c4fb 100644 --- a/pkg/db/study/reports.go +++ b/pkg/db/study/reports.go @@ -22,20 +22,30 @@ type ReportKeyFilters struct { ToTS int64 } -var reportIndexNames []string +const ( + idxReportsParticipantID = "participantID_1" + idxReportsTimestamp = "timestamp_1" + idxReportsParticipantIDKeyTimestamp = "participantID_1_key_1_timestamp_1" +) + +var defaultReportIndexNames = []string{ + idxReportsParticipantID, + idxReportsTimestamp, + idxReportsParticipantIDKeyTimestamp, +} var indexesForReportsCollection = []mongo.IndexModel{ { Keys: bson.D{ {Key: "participantID", Value: 1}, }, - Options: options.Index().SetName("participantID_1"), + Options: options.Index().SetName(idxReportsParticipantID), }, { Keys: bson.D{ {Key: "timestamp", Value: 1}, }, - Options: options.Index().SetName("timestamp_1"), + Options: options.Index().SetName(idxReportsTimestamp), }, { Keys: bson.D{ @@ -43,7 +53,7 @@ var indexesForReportsCollection = []mongo.IndexModel{ {Key: "key", Value: 1}, {Key: "timestamp", Value: 1}, }, - Options: options.Index().SetName("participantID_1_key_1_timestamp_1"), + Options: options.Index().SetName(idxReportsParticipantIDKeyTimestamp), }, } @@ -59,7 +69,7 @@ func (dbService *StudyDBService) DropIndexForReportsCollection(instanceID string slog.Error("Error dropping all indexes for reports", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } } else { - for _, indexName := range reportIndexNames { + for _, indexName := range defaultReportIndexNames { if indexName == "" { slog.Error("Index name is empty for reports collection", slog.String("index", fmt.Sprintf("%+v", indexName))) continue @@ -77,11 +87,10 @@ func (dbService *StudyDBService) CreateDefaultIndexesForReportsCollection(instan defer cancel() collection := dbService.collectionReports(instanceID, studyKey) - names, err := collection.Indexes().CreateMany(ctx, indexesForReportsCollection) + _, err := collection.Indexes().CreateMany(ctx, indexesForReportsCollection) if err != nil { slog.Error("Error creating index for reports", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } - reportIndexNames = names } func (dbService *StudyDBService) SaveReport(instanceID string, studyKey string, report studyTypes.Report) error { diff --git a/pkg/db/study/responses.go b/pkg/db/study/responses.go index 06da9e58..39fe31ee 100644 --- a/pkg/db/study/responses.go +++ b/pkg/db/study/responses.go @@ -3,7 +3,6 @@ package study import ( "context" "errors" - "fmt" "log/slog" "time" @@ -14,14 +13,28 @@ import ( studyTypes "github.com/case-framework/case-backend/pkg/study/types" ) -var responseIndexNames []string +const ( + idxResponsesParticipantID = "participantID_1" + idxResponsesParticipantIDKeySubmittedAt = "participantID_1_key_1_submittedAt_1" + idxResponsesSubmittedAt = "submittedAt_1" + idxResponsesArrivedAt = "arrivedAt_1" + idxResponsesKey = "key_1" +) + +var defaultResponseIndexNames = []string{ + idxResponsesParticipantID, + idxResponsesParticipantIDKeySubmittedAt, + idxResponsesSubmittedAt, + idxResponsesArrivedAt, + idxResponsesKey, +} var indexesForResponsesCollection = []mongo.IndexModel{ { Keys: bson.D{ {Key: "participantID", Value: 1}, }, - Options: options.Index().SetName("participantID_1"), + Options: options.Index().SetName(idxResponsesParticipantID), }, { Keys: bson.D{ @@ -29,25 +42,25 @@ var indexesForResponsesCollection = []mongo.IndexModel{ {Key: "key", Value: 1}, {Key: "submittedAt", Value: 1}, }, - Options: options.Index().SetName("participantID_1_key_1_submittedAt_1"), + Options: options.Index().SetName(idxResponsesParticipantIDKeySubmittedAt), }, { Keys: bson.D{ {Key: "submittedAt", Value: 1}, }, - Options: options.Index().SetName("submittedAt_1"), + Options: options.Index().SetName(idxResponsesSubmittedAt), }, { Keys: bson.D{ {Key: "arrivedAt", Value: 1}, }, - Options: options.Index().SetName("arrivedAt_1"), + Options: options.Index().SetName(idxResponsesArrivedAt), }, { Keys: bson.D{ {Key: "key", Value: 1}, }, - Options: options.Index().SetName("key_1"), + Options: options.Index().SetName(idxResponsesKey), }, } @@ -61,9 +74,9 @@ func (dbService *StudyDBService) DropIndexForResponsesCollection(instanceID stri slog.Error("Error dropping all indexes for responses", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } } else { - for _, indexName := range responseIndexNames { + for _, indexName := range defaultResponseIndexNames { if indexName == "" { - slog.Error("Index name is empty for responses collection", slog.String("index", fmt.Sprintf("%+v", indexName))) + slog.Error("Index name is empty for responses collection") continue } err := dbService.collectionResponses(instanceID, studyKey).Indexes().DropOne(ctx, indexName) @@ -79,11 +92,10 @@ func (dbService *StudyDBService) CreateDefaultIndexesForResponsesCollection(inst defer cancel() collection := dbService.collectionResponses(instanceID, studyKey) - names, err := collection.Indexes().CreateMany(ctx, indexesForResponsesCollection) + _, err := collection.Indexes().CreateMany(ctx, indexesForResponsesCollection) if err != nil { slog.Error("Error creating index for responses", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } - responseIndexNames = names } func (dbService *StudyDBService) AddSurveyResponse(instanceID string, studyKey string, response studyTypes.SurveyResponse) (string, error) { diff --git a/pkg/db/study/study-code-lists.go b/pkg/db/study/study-code-lists.go index 7f672368..69889581 100644 --- a/pkg/db/study/study-code-lists.go +++ b/pkg/db/study/study-code-lists.go @@ -1,7 +1,6 @@ package study import ( - "fmt" "log/slog" "time" @@ -12,7 +11,11 @@ import ( studytypes "github.com/case-framework/case-backend/pkg/study/types" ) -var studyCodeListIndexNames []string +const idxStudyCodeListsStudyKeyListKeyCode = "studyKey_1_listKey_1_code_1" + +var defaultStudyCodeListIndexNames = []string{ + idxStudyCodeListsStudyKeyListKeyCode, +} var indexesForStudyCodeListsCollection = []mongo.IndexModel{ { @@ -21,7 +24,7 @@ var indexesForStudyCodeListsCollection = []mongo.IndexModel{ {Key: "listKey", Value: 1}, {Key: "code", Value: 1}, }, - Options: options.Index().SetUnique(true).SetName("studyKey_1_listKey_1_code_1"), + Options: options.Index().SetUnique(true).SetName(idxStudyCodeListsStudyKeyListKeyCode), }, } @@ -36,9 +39,9 @@ func (dbService *StudyDBService) DropIndexForStudyCodeListsCollection(instanceID slog.Error("Error dropping all indexes for studyCodeLists", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, indexName := range studyCodeListIndexNames { + for _, indexName := range defaultStudyCodeListIndexNames { if indexName == "" { - slog.Error("Index name is empty for studyCodeLists collection", slog.String("index", fmt.Sprintf("%+v", indexName)), slog.String("instanceID", instanceID)) + slog.Error("Index name is empty for studyCodeLists collection", slog.String("instanceID", instanceID)) continue } err := collection.Indexes().DropOne(ctx, indexName) @@ -54,11 +57,10 @@ func (dbService *StudyDBService) CreateDefaultIndexesForStudyCodeListsCollection defer cancel() collection := dbService.collectionStudyCodeLists(instanceID) - names, err := collection.Indexes().CreateMany(ctx, indexesForStudyCodeListsCollection) + _, err := collection.Indexes().CreateMany(ctx, indexesForStudyCodeListsCollection) if err != nil { slog.Error("Error creating index for studyCodeLists", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } - studyCodeListIndexNames = names } func (dbService *StudyDBService) AddStudyCodeListEntry(instanceID string, studyKey string, listKey string, code string) error { diff --git a/pkg/db/study/study-counters.go b/pkg/db/study/study-counters.go index 84e8825e..2c349f03 100644 --- a/pkg/db/study/study-counters.go +++ b/pkg/db/study/study-counters.go @@ -1,7 +1,6 @@ package study import ( - "fmt" "log/slog" "go.mongodb.org/mongo-driver/v2/bson" @@ -9,21 +8,25 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/options" ) +const idxStudyCountersStudyKeyScope = "studyKey_1_scope_1" + +var defaultStudyCounterIndexNames = []string{ + idxStudyCountersStudyKeyScope, +} + type StudyCounter struct { StudyKey string `json:"studyKey" bson:"studyKey"` Scope string `json:"scope" bson:"scope"` Value int64 `json:"value" bson:"value"` } -var studyCounterIndexNames []string - var indexesForStudyCountersCollection = []mongo.IndexModel{ { Keys: bson.D{ {Key: "studyKey", Value: 1}, {Key: "scope", Value: 1}, }, - Options: options.Index().SetUnique(true).SetName("studyKey_1_scope_1"), + Options: options.Index().SetUnique(true).SetName(idxStudyCountersStudyKeyScope), }, } @@ -38,9 +41,9 @@ func (dbService *StudyDBService) DropIndexForStudyCountersCollection(instanceID slog.Error("Error dropping all indexes for studyCounters", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, indexName := range studyCounterIndexNames { + for _, indexName := range defaultStudyCounterIndexNames { if indexName == "" { - slog.Error("Index name is empty for studyCounters collection", slog.String("index", fmt.Sprintf("%+v", indexName)), slog.String("instanceID", instanceID)) + slog.Error("Index name is empty for studyCounters collection", slog.String("instanceID", instanceID)) continue } err := collection.Indexes().DropOne(ctx, indexName) @@ -55,11 +58,10 @@ func (dbService *StudyDBService) CreateDefaultIndexesForStudyCountersCollection( ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionStudyCounters(instanceID).Indexes().CreateMany(ctx, indexesForStudyCountersCollection) + _, err := dbService.collectionStudyCounters(instanceID).Indexes().CreateMany(ctx, indexesForStudyCountersCollection) if err != nil { slog.Error("Error creating index for studyCounters", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } - studyCounterIndexNames = names } // Get current counter value (without incrementing) diff --git a/pkg/db/study/study-infos.go b/pkg/db/study/study-infos.go index adc49a39..b7d6f8b8 100644 --- a/pkg/db/study/study-infos.go +++ b/pkg/db/study/study-infos.go @@ -1,7 +1,6 @@ package study import ( - "fmt" "log/slog" "go.mongodb.org/mongo-driver/v2/bson" @@ -11,14 +10,18 @@ import ( studyTypes "github.com/case-framework/case-backend/pkg/study/types" ) -var studyInfoIndexNames []string +const idxStudyInfosKey = "key_1" + +var defaultStudyInfoIndexNames = []string{ + idxStudyInfosKey, +} var indexesForStudyInfosCollection = []mongo.IndexModel{ { Keys: bson.D{ {Key: "key", Value: 1}, }, - Options: options.Index().SetUnique(true).SetName("key_1"), + Options: options.Index().SetUnique(true).SetName(idxStudyInfosKey), }, } @@ -32,9 +35,9 @@ func (dbService *StudyDBService) DropIndexForStudyInfosCollection(instanceID str slog.Error("Error dropping all indexes for studyInfos", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, indexName := range studyInfoIndexNames { + for _, indexName := range defaultStudyInfoIndexNames { if indexName == "" { - slog.Error("Index name is empty for studyInfos collection", slog.String("index", fmt.Sprintf("%+v", indexName))) + slog.Error("Index name is empty for studyInfos collection") continue } err := dbService.collectionStudyInfos(instanceID).Indexes().DropOne(ctx, indexName) @@ -49,11 +52,10 @@ func (dbService *StudyDBService) CreateDefaultIndexesForStudyInfosCollection(ins ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionStudyInfos(instanceID).Indexes().CreateMany(ctx, indexesForStudyInfosCollection) + _, err := dbService.collectionStudyInfos(instanceID).Indexes().CreateMany(ctx, indexesForStudyInfosCollection) if err != nil { slog.Error("Error creating index for studyInfos", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } - studyInfoIndexNames = names } // get studies diff --git a/pkg/db/study/study-rules.go b/pkg/db/study/study-rules.go index 7ee3d553..767bc3c4 100644 --- a/pkg/db/study/study-rules.go +++ b/pkg/db/study/study-rules.go @@ -1,7 +1,6 @@ package study import ( - "fmt" "log/slog" "go.mongodb.org/mongo-driver/v2/bson" @@ -11,21 +10,29 @@ import ( studyTypes "github.com/case-framework/case-backend/pkg/study/types" ) -var studyRuleIndexNames []string +const ( + idxStudyRulesStudyKey = "studyKey_1" + idxStudyRulesUploadedAtStudyKey = "uploadedAt_1_studyKey_1" +) + +var defaultStudyRuleIndexNames = []string{ + idxStudyRulesStudyKey, + idxStudyRulesUploadedAtStudyKey, +} var indexesForStudyRulesCollection = []mongo.IndexModel{ { Keys: bson.D{ {Key: "studyKey", Value: 1}, }, - Options: options.Index().SetName("studyKey_1"), + Options: options.Index().SetName(idxStudyRulesStudyKey), }, { Keys: bson.D{ {Key: "uploadedAt", Value: 1}, {Key: "studyKey", Value: 1}, }, - Options: options.Index().SetName("uploadedAt_1_studyKey_1"), + Options: options.Index().SetName(idxStudyRulesUploadedAtStudyKey), }, } @@ -41,9 +48,9 @@ func (dbService *StudyDBService) DropIndexForStudyRulesCollection(instanceID str slog.Error("Error dropping all indexes for studyRules", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, indexName := range studyRuleIndexNames { + for _, indexName := range defaultStudyRuleIndexNames { if indexName == "" { - slog.Error("Index name is empty for studyRules collection", slog.String("index", fmt.Sprintf("%+v", indexName))) + slog.Error("Index name is empty for studyRules collection") continue } err := collection.Indexes().DropOne(ctx, indexName) @@ -59,11 +66,10 @@ func (dbService *StudyDBService) CreateDefaultIndexesForStudyRulesCollection(ins defer cancel() collection := dbService.collectionStudyRules(instanceID) - names, err := collection.Indexes().CreateMany(ctx, indexesForStudyRulesCollection) + _, err := collection.Indexes().CreateMany(ctx, indexesForStudyRulesCollection) if err != nil { slog.Error("Error creating index for studyRules", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } - studyRuleIndexNames = names } func (dbService *StudyDBService) deleteStudyRules(instanceID string, studyKey string) error { diff --git a/pkg/db/study/study-variables.go b/pkg/db/study/study-variables.go index 7aa2dc38..3acceb56 100644 --- a/pkg/db/study/study-variables.go +++ b/pkg/db/study/study-variables.go @@ -1,7 +1,6 @@ package study import ( - "fmt" "log/slog" "time" @@ -13,7 +12,11 @@ import ( studytypes "github.com/case-framework/case-backend/pkg/study/types" ) -var studyVariableIndexNames []string +const idxStudyVariablesStudyKeyKey = "studyKey_1_key_1" + +var defaultStudyVariableIndexNames = []string{ + idxStudyVariablesStudyKeyKey, +} var indexesForStudyVariablesCollection = []mongo.IndexModel{ { @@ -21,7 +24,7 @@ var indexesForStudyVariablesCollection = []mongo.IndexModel{ {Key: "studyKey", Value: 1}, {Key: "key", Value: 1}, }, - Options: options.Index().SetUnique(true).SetName("studyKey_1_key_1"), + Options: options.Index().SetUnique(true).SetName(idxStudyVariablesStudyKeyKey), }, } @@ -46,9 +49,9 @@ func (dbService *StudyDBService) DropIndexForStudyVariablesCollection(instanceID slog.Error("Error dropping all indexes for studyVariables", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, indexName := range studyVariableIndexNames { + for _, indexName := range defaultStudyVariableIndexNames { if indexName == "" { - slog.Error("Index name is empty for studyVariables collection", slog.String("index", fmt.Sprintf("%+v", indexName)), slog.String("instanceID", instanceID)) + slog.Error("Index name is empty for studyVariables collection", slog.String("instanceID", instanceID)) continue } err := collection.Indexes().DropOne(ctx, indexName) @@ -63,11 +66,10 @@ func (dbService *StudyDBService) CreateDefaultIndexesForStudyVariablesCollection ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionStudyVariables(instanceID).Indexes().CreateMany(ctx, indexesForStudyVariablesCollection) + _, err := dbService.collectionStudyVariables(instanceID).Indexes().CreateMany(ctx, indexesForStudyVariablesCollection) if err != nil { slog.Error("Error creating index for studyVariables", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } - studyVariableIndexNames = names } // create a study variable diff --git a/pkg/db/study/surveys.go b/pkg/db/study/surveys.go index 1e61b5a6..79cf69d0 100644 --- a/pkg/db/study/surveys.go +++ b/pkg/db/study/surveys.go @@ -2,7 +2,6 @@ package study import ( "errors" - "fmt" "log/slog" "time" @@ -13,7 +12,19 @@ import ( studyTypes "github.com/case-framework/case-backend/pkg/study/types" ) -var surveyIndexNames []string +const ( + idxSurveysSurveyDefinitionUnpublishedPublished = "surveyDefinition.key_1_unpublished_1_published_-1" + idxSurveysPublishedSurveyDefinition = "published_1_surveyDefinition.key_1" + idxSurveysUnpublished = "unpublished_1" + idxSurveysSurveyDefinitionVersionIDUnique = "surveyDefinition.key_1_versionID_1" +) + +var defaultSurveyIndexNames = []string{ + idxSurveysSurveyDefinitionUnpublishedPublished, + idxSurveysPublishedSurveyDefinition, + idxSurveysUnpublished, + idxSurveysSurveyDefinitionVersionIDUnique, +} var indexesForSurveysCollection = []mongo.IndexModel{ { @@ -22,27 +33,27 @@ var indexesForSurveysCollection = []mongo.IndexModel{ {Key: "unpublished", Value: 1}, {Key: "published", Value: -1}, }, - Options: options.Index().SetName("surveyDefinition.key_1_unpublished_1_published_-1"), + Options: options.Index().SetName(idxSurveysSurveyDefinitionUnpublishedPublished), }, { Keys: bson.D{ {Key: "published", Value: 1}, {Key: "surveyDefinition.key", Value: 1}, }, - Options: options.Index().SetName("published_1_surveyDefinition.key_1"), + Options: options.Index().SetName(idxSurveysPublishedSurveyDefinition), }, { Keys: bson.D{ {Key: "unpublished", Value: 1}, }, - Options: options.Index().SetName("unpublished_1"), + Options: options.Index().SetName(idxSurveysUnpublished), }, { Keys: bson.D{ {Key: "surveyDefinition.key", Value: 1}, {Key: "versionID", Value: 1}, }, - Options: options.Index().SetName("surveyDefinition.key_1_versionID_1").SetUnique(true), + Options: options.Index().SetName(idxSurveysSurveyDefinitionVersionIDUnique).SetUnique(true), }, } @@ -56,9 +67,9 @@ func (dbService *StudyDBService) DropIndexForSurveysCollection(instanceID string slog.Error("Error dropping all indexes for surveys", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } } else { - for _, indexName := range surveyIndexNames { + for _, indexName := range defaultSurveyIndexNames { if indexName == "" { - slog.Error("Index name is empty for surveys collection", slog.String("index", fmt.Sprintf("%+v", indexName))) + slog.Error("Index name is empty for surveys collection") continue } err := dbService.collectionSurveys(instanceID, studyKey).Indexes().DropOne(ctx, indexName) @@ -74,11 +85,10 @@ func (dbService *StudyDBService) CreateDefaultIndexesForSurveysCollection(instan defer cancel() collection := dbService.collectionSurveys(instanceID, studyKey) - names, err := collection.Indexes().CreateMany(ctx, indexesForSurveysCollection) + _, err := collection.Indexes().CreateMany(ctx, indexesForSurveysCollection) if err != nil { slog.Error("Error creating index for surveys", slog.String("error", err.Error()), slog.String("instanceID", instanceID), slog.String("studyKey", studyKey)) } - surveyIndexNames = names } func (dbService *StudyDBService) SaveSurveyVersion(instanceID string, studyKey string, survey *studyTypes.Survey) (err error) { diff --git a/pkg/db/study/task-queue.go b/pkg/db/study/task-queue.go index d1c79feb..5fd67522 100644 --- a/pkg/db/study/task-queue.go +++ b/pkg/db/study/task-queue.go @@ -1,7 +1,6 @@ package study import ( - "fmt" "log/slog" "time" @@ -14,17 +13,20 @@ import ( const ( REMOVE_TASK_FROM_QUEUE_AFTER = 60 * 60 * 24 * 2 // 2 days + idxTaskQueueUpdatedAt = "updatedAt_1" ) +var defaultTaskQueueIndexNames = []string{ + idxTaskQueueUpdatedAt, +} + var indexesForTaskQueueCollection = []mongo.IndexModel{ { Keys: bson.D{{Key: "updatedAt", Value: 1}}, - Options: options.Index().SetExpireAfterSeconds(REMOVE_TASK_FROM_QUEUE_AFTER).SetName("updatedAt_1"), + Options: options.Index().SetExpireAfterSeconds(REMOVE_TASK_FROM_QUEUE_AFTER).SetName(idxTaskQueueUpdatedAt), }, } -var taskQueueIndexNames []string - func (dbService *StudyDBService) DropIndexForTaskQueueCollection(instanceID string, dropAll bool) { ctx, cancel := dbService.getContext() defer cancel() @@ -35,9 +37,9 @@ func (dbService *StudyDBService) DropIndexForTaskQueueCollection(instanceID stri slog.Error("Error dropping all indexes for task queue", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } } else { - for _, indexName := range taskQueueIndexNames { + for _, indexName := range defaultTaskQueueIndexNames { if indexName == "" { - slog.Error("Index name is empty for task queue collection", slog.String("index", fmt.Sprintf("%+v", indexName))) + slog.Error("Index name is empty for task queue collection") continue } err := dbService.collectionTaskQueue(instanceID).Indexes().DropOne(ctx, indexName) @@ -52,11 +54,10 @@ func (dbService *StudyDBService) CreateDefaultIndexesForTaskQueueCollection(inst ctx, cancel := dbService.getContext() defer cancel() - names, err := dbService.collectionTaskQueue(instanceID).Indexes().CreateMany(ctx, indexesForTaskQueueCollection) + _, err := dbService.collectionTaskQueue(instanceID).Indexes().CreateMany(ctx, indexesForTaskQueueCollection) if err != nil { slog.Error("Error creating index for task queue", slog.String("error", err.Error()), slog.String("instanceID", instanceID)) } - taskQueueIndexNames = names } // create task From d8e668921af63f0d94e652531e7395a76f7e1b68 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Fri, 27 Mar 2026 10:29:44 +0100 Subject: [PATCH 16/38] Update mongo-driver update protocol --- mongodb-driver-update.md | 74 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 6 deletions(-) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index d57f8c4b..41573a80 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -6,18 +6,16 @@ - context.Context parameter has been removed from mongo.Connect() because the deployment connector doesn’t accept a context, meaning that the context passed to mongo.Connect() in previous versions didn't serve a purpose. - Simplfied DropOne and DropAll methods by removing the server response -- TESTING REQUIRED: Index Model: The old `IndexOptionsBuilder` type was removed and `IndexModel.Options.Name` is no longer accessible as a field. Required steps: - - define variable for index names - - capture index names returned by CreateMany - - use the stored names when dropping indexes - - maybe store indexNames as field of messagingDBService struct?!? (e.g. `emailTemplateIndexNames map[string][]`) - removed unused return value for context.WithTimeout() +- Index Model: The old `IndexOptionsBuilder` type was removed and `IndexModel.Options.Name` is no longer accessible as a field. Required steps: + - define static default index name constants/lists per collection + - reuse those names for both index creation and `DropOne` in `drop defaults` ### Messaging #### email-templates -- TESTING REQUIRED: save email template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (testing? Line 128/129) +- save email template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (testing? Line 128/129) #### scheduled-emails @@ -72,3 +70,67 @@ TESTING REQUIRED: - GetSurveyKeysForStudy: `Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop.TESTING REQUIRED: - GetCurrentSurveyVersion: create FindOneOptions using the options.FindOne() builder and setters (for example, options.FindOne().SetSort(sortByPublishedDesc)) instead of instantiating &options.FindOneOptions{} and mutating its fields. TESTING REQUIRED: - GetSurveyVersions: create FindOptions using the options.Find() builder and its setters (for example, options.Find().SetProjection(...).SetSort(...)) instead of instantiating &options.FindOptions{} and mutating its fields. TESTING REQUIRED: + +## Manual Test Protocol (Index Migration) + +Date: 24.03.2026 + +Goal: + +- Preserve MongoDB driver v1 behavior for default index drop/recreate flows after migration to driver v2. +- Verify that custom manually added indexes are not affected by default-index drop. + +Executed commands: + +```bash +CONFIG_FILE_PATH=test/jobs/dbm-01-before.yaml go run ./jobs/db-migration/*.go +CONFIG_FILE_PATH=test/jobs/dbm-02-drop-defaults.yaml go run ./jobs/db-migration/*.go +CONFIG_FILE_PATH=test/jobs/dbm-03-create-defaults.yaml go run ./jobs/db-migration/*.go +``` + +Test steps: + +- Phase 1 (`dbm-01-before.yaml`): baseline index snapshot exported. +- Manual step: added at least one custom index directly in MongoDB. +- Phase 2 (`dbm-02-drop-defaults.yaml`): dropped default indexes. +- Phase 3 (`dbm-03-create-defaults.yaml`): recreated default indexes. + +Result summary: + +- Default indexes were removed in phase 2 and recreated in phase 3 as expected. +- Manually added custom index remained untouched by `drop defaults`. + +Conclusion: + +- The previous v1 behavior is preserved under v2: + - `drop defaults` removes only default indexes. + - custom manually added indexes remain untouched. + - `create defaults` restores the default indexes again. + +## Manual Test Protocol (SaveEmailTemplate) + +Date: 25.03.2026 + +Scope: + +- Verify unchanged behavior for `SaveEmailTemplate` after switching to `options.FindOneAndReplace().SetUpsert(false).SetReturnDocument(options.After)`. +- Covered cases: create, update existing, and study-template flow. +- Not covered in this run: update with valid but non-existing `id` (requires direct API request outside UI). + +Test execution: + +1. Created new global email templates in UI and saved them. +2. Edited existing global email templates in UI and saved again. +3. Repeated create and update flow for study-scoped email templates. + +Expected and observed results: + +1. Create path (`id` empty): new template is inserted and returned with generated `id`. +2. Update path (`id` exists): existing template is replaced/updated and returned with same `id`. +3. Study-template behavior: same create/update behavior as global templates. +4. Unique index behavior: no duplicate email templates with identical `messageType` are created (default unique index is enforced). + +Conclusion: + +- No behavioral change observed in the covered `SaveEmailTemplate` flows. +- Current implementation remains consistent with previous driver-v1 behavior for UI-accessible paths. From 34ac9fd2e4835d8ca68c60267d29c22f05c3647f Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:09:23 +0200 Subject: [PATCH 17/38] update test protocol --- mongodb-driver-update.md | 56 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index 41573a80..5f32ea6c 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -10,20 +10,21 @@ - Index Model: The old `IndexOptionsBuilder` type was removed and `IndexModel.Options.Name` is no longer accessible as a field. Required steps: - define static default index name constants/lists per collection - reuse those names for both index creation and `DropOne` in `drop defaults` + - TESTING REQUIRED: expired Indexes and unique indexes ### Messaging #### email-templates -- save email template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (testing? Line 128/129) +- save email template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested, see SaveEmailTemplate protocol) #### scheduled-emails -- TESTING REQUIRED: save scheduled-emails: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal +- save scheduled-emails: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested) #### sms-templates -- TESTING REQUIRED: save sms template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal +- save sms template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested, see SaveScheduledEmail protocol) ### participant user @@ -69,7 +70,7 @@ TESTING REQUIRED: - GetSurveyKeysForStudy: `Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop.TESTING REQUIRED: - GetCurrentSurveyVersion: create FindOneOptions using the options.FindOne() builder and setters (for example, options.FindOne().SetSort(sortByPublishedDesc)) instead of instantiating &options.FindOneOptions{} and mutating its fields. TESTING REQUIRED: -- GetSurveyVersions: create FindOptions using the options.Find() builder and its setters (for example, options.Find().SetProjection(...).SetSort(...)) instead of instantiating &options.FindOptions{} and mutating its fields. TESTING REQUIRED: +- GetSurveyVersions: create FindOptions using the options.Find() builder and its setters (for example, options.Find().SetProjection(...).SetSort(...)) instead of instantiating &options.FindOptions{} and mutating its fields. ## Manual Test Protocol (Index Migration) @@ -134,3 +135,50 @@ Conclusion: - No behavioral change observed in the covered `SaveEmailTemplate` flows. - Current implementation remains consistent with previous driver-v1 behavior for UI-accessible paths. + +## Manual Test Protocol (SaveScheduledEmail) + +Date: 07.04.2026 + +Scope: + +- Verify unchanged behavior for scheduled-email and sms-template save flows after switching to options-builder usage for FindOneAndReplace options. + +Test execution: + +1. Created new scheduled emails in UI and saved them. +2. Updated existing scheduled emails in UI and saved the changes. +3. Created new SMS templates in UI and saved them. +4. Updated existing SMS templates in UI and saved the changes. + +Expected and observed results: + +1. Create path: new schedules are created successfully. +2. Update path: existing schedules are updated successfully. +3. Create/update path for SMS templates: works successfully as before. + +Conclusion: + +- No behavioral change observed in the tested `SaveScheduledEmail` and `SaveSMSTemplate` create/update flows. + +## Manual Test Protocol (GetSurveyVersions) + +Date: 09.04.2026 + +Scope: + +- Verify unchanged behavior for `GetSurveyVersions` after switching to `options.Find()` builder with setters. + +Test execution: + +1. Opened survey version list in UI for a study/survey. +2. Verified API request used endpoint `GET /v1/studies/:studyKey/surveys/:surveyKey/versions`. + +Expected and observed results: + +1. Survey versions are returned and displayed as expected. +2. No behavioral change observed compared to previous behavior. + +Conclusion: + +- `GetSurveyVersions` behavior is unchanged for the tested UI/API flow. From 79aa54bfa69b897b1c2baeaaf1dcd62ea12c71a1 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:29:00 +0200 Subject: [PATCH 18/38] Update test protocol --- mongodb-driver-update.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index 5f32ea6c..19948f48 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -69,7 +69,7 @@ TESTING REQUIRED: #### surveys - GetSurveyKeysForStudy: `Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop.TESTING REQUIRED: -- GetCurrentSurveyVersion: create FindOneOptions using the options.FindOne() builder and setters (for example, options.FindOne().SetSort(sortByPublishedDesc)) instead of instantiating &options.FindOneOptions{} and mutating its fields. TESTING REQUIRED: +- GetCurrentSurveyVersion: create FindOneOptions using the options.FindOne() builder and setters (for example, options.FindOne().SetSort(sortByPublishedDesc)) instead of instantiating &options.FindOneOptions{} and mutating its fields. - GetSurveyVersions: create FindOptions using the options.Find() builder and its setters (for example, options.Find().SetProjection(...).SetSort(...)) instead of instantiating &options.FindOptions{} and mutating its fields. ## Manual Test Protocol (Index Migration) @@ -182,3 +182,25 @@ Expected and observed results: Conclusion: - `GetSurveyVersions` behavior is unchanged for the tested UI/API flow. + +## Manual Test Protocol (GetCurrentSurveyVersions) + +Date: 10.04.2026 + +Scope: + +- Verify unchanged behavior for `GetCurrentSurveyVersions` after switching to `options.FindOne()` builder with setters. + +Test execution: + +1. Exported study configuration as JSON via the UI (study configuration export). +2. `GetCurrentSurveyVersions` is called internally during this export to retrieve the current version of all surveys belonging to the study. + +Expected and observed results: + +1. The exported JSON file is identical to the file exported with the previous MongoDB driver version. +2. No behavioral change observed. + +Conclusion: + +- `GetCurrentSurveyVersions` behavior is unchanged after the driver migration. From 5a650a34edd1dc9dcf39504173589e89447caa90 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Mon, 13 Apr 2026 12:52:06 +0200 Subject: [PATCH 19/38] Update test protocol --- mongodb-driver-update.md | 50 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index 19948f48..d29bd6bb 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -30,7 +30,7 @@ #### user-attributes -- TESTING REQUIRED: The UpdateOptions has been chnaged to UpdateOneOptions to configure UpdateOne operation. +- TESTING REQUIRED: The UpdateOptions has been changed to UpdateOneOptions to configure UpdateOne operation. #### users @@ -55,11 +55,11 @@ run concurrent OTP creations to confirm only the allowed number of documents is #### confidential responses -- configure replace options via the options.Replace() builder (for example, options.Replace().SetUpsert(true)) instead of instantiating a ReplaceOptions struct literal and passing its address. TESTING REQUIRED +- configure replace options via the options.Replace() builder (for example, options.Replace().SetUpsert(true)) instead of instantiating a ReplaceOptions struct literal and passing its address. #### study-rules -- &options.FindOneOptions{ Sort: sortByPublished } becomes options.FindOne().SetSort(sortByPublished). TESTING REQUIRED +- &options.FindOneOptions{ Sort: sortByPublished } becomes options.FindOne().SetSort(sortByPublished). #### reports @@ -204,3 +204,47 @@ Expected and observed results: Conclusion: - `GetCurrentSurveyVersions` behavior is unchanged after the driver migration. + +## Manual Test Protocol (GetCurrentStudyRules) + +Date: 10.04.2026 + +Scope: + +- Verify unchanged behavior for `GetCurrentStudyRules` after switching to `options.FindOne()` builder with setters. + +Test execution: + +1. Exported study configuration as JSON via the UI (study configuration export). +2. `GetCurrentStudyRules` is called internally during this export to retrieve the current study rules belonging to the study. + +Expected and observed results: + +1. The exported JSON file is identical to the file exported with the previous MongoDB driver version. +2. No behavioral change observed. + +Conclusion: + +- `GetCurrentStudyRules` behavior is unchanged after the driver migration. + +## Manual Test Protocol (ReplaceConfidentialResponse) + +Date: 13.04.2026 + +Scope: + +- Verify unchanged behavior for `ReplaceConfidentialResponse` after switching to `options.Replace().SetUpsert(true)` builder usage. + +Test execution: + +1. Submitted a survey with confidential responses for a participant for the first time (insert path). +2. Submitted the same survey again for the same participant (replace path), so the existing confidential response was replaced by the new one. + +Expected and observed results: + +1. First submission: confidential response was inserted as a new document in the database. +2. Subsequent submission: existing confidential response was replaced by the new response, no duplicate was created. + +Conclusion: + +- No behavioral change observed for the insert and replace paths of `ReplaceConfidentialResponse`. From 16d85be581e07429728a781e0d563fb89064d2cd Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 15 Apr 2026 12:25:22 +0200 Subject: [PATCH 20/38] Update test protocol --- mongodb-driver-update.md | 94 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index d29bd6bb..c371fe32 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -30,12 +30,12 @@ #### user-attributes -- TESTING REQUIRED: The UpdateOptions has been changed to UpdateOneOptions to configure UpdateOne operation. +- The UpdateOptions has been changed to UpdateOneOptions to configure UpdateOne operation. (manually tested, see `SetUserAttribute` protocol) #### users -- add user: MongoDB Go Driver v2 no longer allows constructing or modifying option structs directly, so update options must now be created through the new builder API (options.UpdateOne().SetUpsert(true)) instead of setting fields on UpdateOptions manually. TESTING REQUIRED -- update user in db: FindOneAndReplaceOptions can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOneAndReplace().SetReturnDocument(options.After)) instead of setting option fields directly. TESTING REQUIRED +- add user: MongoDB Go Driver v2 no longer allows constructing or modifying option structs directly, so update options must now be created through the new builder API (options.UpdateOne().SetUpsert(true)) instead of setting fields on UpdateOptions manually. +- update user in db: FindOneAndReplaceOptions can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOneAndReplace().SetReturnDocument(options.After)) instead of setting option fields directly. (manually tested, see `_updateUserInDB` protocol) #### otps @@ -51,7 +51,7 @@ run concurrent OTP creations to confirm only the allowed number of documents is #### participants -- configure FindOneAndReplaceOptions through options.FindOneAndReplace().Set... instead of filling the struct fields directly. TESTING REQUIRED +- saveParticipantSate: configure FindOneAndReplaceOptions through options.FindOneAndReplace().Set... instead of filling the struct fields directly. #### confidential responses @@ -248,3 +248,89 @@ Expected and observed results: Conclusion: - No behavioral change observed for the insert and replace paths of `ReplaceConfidentialResponse`. + +## Manual Test Protocol (SaveParticipantState) + +Date: 13.04.2026 + +Scope: + +- Verify unchanged behavior for `SaveParticipantState` after switching to `options.FindOneAndReplace().SetUpsert(true).SetReturnDocument(options.After)` builder usage. + +Test execution: + +1. Created a new participant by enrolling in a study (insert path). +2. Changed the state of an existing participant (replace path), e.g. by submitting a survey that modifies participant flags or survey assignments. + +Expected and observed results: + +1. Insert path: new participant document was created successfully in the database. +2. Replace path: existing participant state was updated correctly, no duplicate was created. + +Conclusion: + +- No behavioral change observed for the insert and replace paths of `SaveParticipantState`. + +## Manual Test Protocol (AddUser) + +Date: 14.04.2026 + +Scope: + +- Verify unchanged behavior for `AddUser` after switching to `options.UpdateOne().SetUpsert(true)` builder usage. + +Test execution: + +1. Created a new user with a fresh email address (insert path). +2. Attempted to create another new user with the same email address as an already existing user (duplicate path). + +Expected and observed results: + +1. Insert path: new user was created successfully in the database. +2. Duplicate path: error `"user already exists"` was returned correctly, no duplicate user was created in the database. + +Conclusion: + +- No behavioral change observed for the insert and duplicate paths of `AddUser`. + +## Manual Test Protocol (_updateUserInDB) + +Date: 14.04.2026 + +Scope: + +- Verify unchanged behavior for `_updateUserInDB` (called via `ReplaceUser`) after switching to `options.FindOneAndReplace().SetReturnDocument(options.After)` builder usage. + +Test execution: + +1. Logged in as a participant user (triggers `ReplaceUser` → `_updateUserInDB` to update login timestamps). +2. Created a new profile for the logged-in user (triggers another `ReplaceUser` → `_updateUserInDB` to persist the updated user document). + +Expected and observed results: + +1. Login: user document was updated correctly in the database (e.g. `lastLogin` timestamp). +2. New profile: profile was saved correctly and the updated user document was returned, no duplicate was created. + +Conclusion: + +- No behavioral change observed for the replace path of `_updateUserInDB`. + +## Manual Test Protocol (SetUserAttribute) + +Date: 15.04.2026 + +Scope: + +- Verify unchanged behavior for `SetUserAttribute` after switching to `options.UpdateOne().SetUpsert(true)` builder usage. + +Test execution: + +1. Registered a new user at flusurvey (insert path: user attribute document created for the first time). + +Expected and observed results: + +1. Insert path: user attribute was created successfully as a new document in the database. + +Conclusion: + +- No behavioral change observed for the insert path of `SetUserAttribute`. From 4576e99c12ee993d25bdf94fde904fae094752d7 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:26:56 +0200 Subject: [PATCH 21/38] Update test protocol --- mongodb-driver-update.md | 51 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index c371fe32..2edd3310 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -63,12 +63,11 @@ run concurrent OTP creations to confirm only the allowed number of documents is #### reports -- GetUniqueReportKeysForStudy:`Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop. -TESTING REQUIRED: +- GetUniqueReportKeysForStudy:`Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop. (manually tested, see `GetUniqueReportKeysForStudy` protocol) #### surveys -- GetSurveyKeysForStudy: `Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop.TESTING REQUIRED: +- GetSurveyKeysForStudy: `Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop. (manually tested, see `GetSurveyKeysForStudy` protocol) - GetCurrentSurveyVersion: create FindOneOptions using the options.FindOne() builder and setters (for example, options.FindOne().SetSort(sortByPublishedDesc)) instead of instantiating &options.FindOneOptions{} and mutating its fields. - GetSurveyVersions: create FindOptions using the options.Find() builder and its setters (for example, options.Find().SetProjection(...).SetSort(...)) instead of instantiating &options.FindOptions{} and mutating its fields. @@ -334,3 +333,49 @@ Expected and observed results: Conclusion: - No behavioral change observed for the insert path of `SetUserAttribute`. + +## Manual Test Protocol (GetUniqueReportKeysForStudy) + +Date: 15.04.2026 + +Scope: + +- Verify unchanged behavior for `GetUniqueReportKeysForStudy` after switching to the new `Distinct()` API that returns a result type decoded directly into `[]string`. + +Test execution: + +1. Opened the reports page in the CASE management UI for a study with existing reports. +2. Checked the dropdown that lists all available report keys. +3. Applied a `participantID` filter and verified the dropdown updated accordingly. +4. Applied `from` and `until` date filters and verified the dropdown updated accordingly. + +Expected and observed results: + +1. All available and filtered report keys were listed correctly in the dropdown. +2. No behavioral change observed compared to previous behavior. + +Conclusion: + +- No behavioral change observed for `GetUniqueReportKeysForStudy` after the driver migration. + +## Manual Test Protocol (GetSurveyKeysForStudy) + +Date: 15.04.2026 + +Scope: + +- Verify unchanged behavior for `GetSurveyKeysForStudy` after switching to the new `Distinct()` API that returns a result type decoded directly into `[]string`. + +Test execution: + +1. Opened the CASE management UI and checked the survey dropdown used to manually assign a survey to a participant – verified that all available survey keys were listed correctly. +2. Exported study configuration as JSON via the UI and verified that the survey keys are correctly included in the export. + +Expected and observed results: + +1. All available survey keys were listed correctly in the dropdown. +2. The exported study configuration JSON contained the correct survey keys, identical to the export with the previous MongoDB driver version. + +Conclusion: + +- No behavioral change observed for `GetSurveyKeysForStudy` after the driver migration. From 6d769d40f8215744d7e16e95a5fda8d7972fa314 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:30:30 +0200 Subject: [PATCH 22/38] Update test protocol --- mongodb-driver-update.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index 2edd3310..cd67568b 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -99,6 +99,8 @@ Result summary: - Default indexes were removed in phase 2 and recreated in phase 3 as expected. - Manually added custom index remained untouched by `drop defaults`. +- Unique index behavior verified: inserting a document with the same index field value as an existing document is rejected by MongoDB. +- TTL index behavior verified: documents are automatically deleted by MongoDB once the `expireAfterSeconds` threshold has passed. Conclusion: @@ -106,6 +108,7 @@ Conclusion: - `drop defaults` removes only default indexes. - custom manually added indexes remain untouched. - `create defaults` restores the default indexes again. + - unique and TTL index properties are correctly applied after recreation. ## Manual Test Protocol (SaveEmailTemplate) @@ -379,3 +382,31 @@ Expected and observed results: Conclusion: - No behavioral change observed for `GetSurveyKeysForStudy` after the driver migration. + +## Manual Test Protocol (CreateOTP) + +Date: 21.04.2026 + +Scope: + +- Verify unchanged behavior for `CreateOTP` after switching the `mongo.WithSession` callback from `mongo.SessionContext` to `context.Context`. + +Test execution: + +1. Triggered an OTP request via the login flow (normal case: OTP created and email received). +2. Verified the OTP by entering the received code successfully. +3. Triggered OTP requests repeatedly until the `maxOTPCount` limit was reached — subsequent requests correctly returned an error. +4. Waited for OTP expiry (TTL of 15 minutes) and verified the OTP document was automatically deleted from the database. + +Note: During testing, two OTPs were created in one session due to the frontend sending the OTP request twice. This is a pre-existing frontend behavior unrelated to the MongoDB driver migration. + +Expected and observed results: + +1. Normal case: OTP was created and email was sent successfully. +2. Verification: OTP verification succeeded with the correct code. +3. Limit case: Error returned correctly after exceeding `maxOTPCount`, no additional OTP was created. +4. TTL: OTP document was deleted automatically after expiry. + +Conclusion: + +- No behavioral change observed for `CreateOTP` after the driver migration. From c723f95a872fc6a5fe8a5f8911e52c0a926d611b Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:44:50 +0200 Subject: [PATCH 23/38] Update test protocol --- mongodb-driver-update.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index cd67568b..c8790891 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -1,4 +1,8 @@ -# Update mongodb driver +# Update mongodb driver: v1 → v2 + +## Overview of Changes + +The following is a list of all changes made for the update of the MongoDB Go Driver from v1 to v2. The `bson/primitive` types were replaced globally throughout the codebase. This is followed by changes that were applied uniformly across all DB packages, and finally by changes specific to individual DB collections. - The bson/primitive package has been merged into the bson package —> changed any instance of primitive.ObjectID to bson.ObjectId. @@ -39,13 +43,7 @@ #### otps -- update the callback for mongo.WithSession to use a context.Context implementation, rather than the custom mongo.SessionContext TESTING REQUIRED - -TEST: If you want to be extra safe, you can: -Deploy this change to a non‑production environment first. -Run a few test flows that: -exceed the maxOTPCount to ensure the “too many OTP requests” path still works, -run concurrent OTP creations to confirm only the allowed number of documents is written. +- update the callback for mongo.WithSession to use a context.Context implementation, rather than the custom mongo.SessionContext ### study @@ -398,8 +396,6 @@ Test execution: 3. Triggered OTP requests repeatedly until the `maxOTPCount` limit was reached — subsequent requests correctly returned an error. 4. Waited for OTP expiry (TTL of 15 minutes) and verified the OTP document was automatically deleted from the database. -Note: During testing, two OTPs were created in one session due to the frontend sending the OTP request twice. This is a pre-existing frontend behavior unrelated to the MongoDB driver migration. - Expected and observed results: 1. Normal case: OTP was created and email was sent successfully. From ce8de5149cb80b465b23dafb2955bb28bbf19efa Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:50:16 +0200 Subject: [PATCH 24/38] Update test protocol --- mongodb-driver-update.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index c8790891..8f358ffd 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -2,11 +2,13 @@ ## Overview of Changes +### All files + The following is a list of all changes made for the update of the MongoDB Go Driver from v1 to v2. The `bson/primitive` types were replaced globally throughout the codebase. This is followed by changes that were applied uniformly across all DB packages, and finally by changes specific to individual DB collections. - The bson/primitive package has been merged into the bson package —> changed any instance of primitive.ObjectID to bson.ObjectId. -## All DB packages +### All DB packages - context.Context parameter has been removed from mongo.Connect() because the deployment connector doesn’t accept a context, meaning that the context passed to mongo.Connect() in previous versions didn't serve a purpose. - Simplfied DropOne and DropAll methods by removing the server response @@ -30,7 +32,7 @@ The following is a list of all changes made for the update of the MongoDB Go Dri - save sms template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested, see SaveScheduledEmail protocol) -### participant user +### Participant User #### user-attributes @@ -43,9 +45,9 @@ The following is a list of all changes made for the update of the MongoDB Go Dri #### otps -- update the callback for mongo.WithSession to use a context.Context implementation, rather than the custom mongo.SessionContext +- update the callback for mongo.WithSession to use a context.Context implementation, rather than the custom mongo.SessionContext -### study +### Study #### participants From 34efe3293052b04544da1f45b92d5eb439bf9b4e Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Tue, 21 Apr 2026 15:02:11 +0200 Subject: [PATCH 25/38] Update test protocol --- mongodb-driver-update.md | 53 ++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index 8f358ffd..dcc12c34 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -2,35 +2,34 @@ ## Overview of Changes -### All files - The following is a list of all changes made for the update of the MongoDB Go Driver from v1 to v2. The `bson/primitive` types were replaced globally throughout the codebase. This is followed by changes that were applied uniformly across all DB packages, and finally by changes specific to individual DB collections. +### All files + - The bson/primitive package has been merged into the bson package —> changed any instance of primitive.ObjectID to bson.ObjectId. ### All DB packages -- context.Context parameter has been removed from mongo.Connect() because the deployment connector doesn’t accept a context, meaning that the context passed to mongo.Connect() in previous versions didn't serve a purpose. -- Simplfied DropOne and DropAll methods by removing the server response +- context.Context parameter has been removed from mongo.Connect(): the deployment connector doesn’t accept a context, meaning that the context passed to mongo.Connect() in previous versions didn't serve a purpose. +- Simplfied DropOne and DropAll methods by removing the unused server response - removed unused return value for context.WithTimeout() - Index Model: The old `IndexOptionsBuilder` type was removed and `IndexModel.Options.Name` is no longer accessible as a field. Required steps: - define static default index name constants/lists per collection - reuse those names for both index creation and `DropOne` in `drop defaults` - - TESTING REQUIRED: expired Indexes and unique indexes ### Messaging #### email-templates -- save email template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested, see SaveEmailTemplate protocol) +- `SaveEmailTemplate`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested, see SaveEmailTemplate protocol) #### scheduled-emails -- save scheduled-emails: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested) +- `SaveScheduledEmail`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested) #### sms-templates -- save sms template: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested, see SaveScheduledEmail protocol) +- `SaveSMSTemplate`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested, see SaveScheduledEmail protocol) ### Participant User @@ -40,18 +39,18 @@ The following is a list of all changes made for the update of the MongoDB Go Dri #### users -- add user: MongoDB Go Driver v2 no longer allows constructing or modifying option structs directly, so update options must now be created through the new builder API (options.UpdateOne().SetUpsert(true)) instead of setting fields on UpdateOptions manually. -- update user in db: FindOneAndReplaceOptions can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOneAndReplace().SetReturnDocument(options.After)) instead of setting option fields directly. (manually tested, see `_updateUserInDB` protocol) +- `AddUser`: MongoDB Go Driver v2 no longer allows constructing or modifying option structs directly, so update options must now be created through the new builder API (options.UpdateOne().SetUpsert(true)) instead of setting fields on UpdateOptions manually. +- `_updateUserInDB`: FindOneAndReplaceOptions can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOneAndReplace().SetReturnDocument(options.After)) instead of setting option fields directly. (manually tested, see `_updateUserInDB` protocol) #### otps -- update the callback for mongo.WithSession to use a context.Context implementation, rather than the custom mongo.SessionContext +- `CreateOTP`: update the callback for mongo.WithSession to use a context.Context implementation, rather than the custom mongo.SessionContext ### Study #### participants -- saveParticipantSate: configure FindOneAndReplaceOptions through options.FindOneAndReplace().Set... instead of filling the struct fields directly. +- `SaveParticipantState`: configure FindOneAndReplaceOptions through options.FindOneAndReplace().Set... instead of filling the struct fields directly. #### confidential responses @@ -71,7 +70,9 @@ The following is a list of all changes made for the update of the MongoDB Go Dri - GetCurrentSurveyVersion: create FindOneOptions using the options.FindOne() builder and setters (for example, options.FindOne().SetSort(sortByPublishedDesc)) instead of instantiating &options.FindOneOptions{} and mutating its fields. - GetSurveyVersions: create FindOptions using the options.Find() builder and its setters (for example, options.Find().SetProjection(...).SetSort(...)) instead of instantiating &options.FindOptions{} and mutating its fields. -## Manual Test Protocol (Index Migration) +## Manual Test Protocol + +### Index Migration Date: 24.03.2026 @@ -110,7 +111,7 @@ Conclusion: - `create defaults` restores the default indexes again. - unique and TTL index properties are correctly applied after recreation. -## Manual Test Protocol (SaveEmailTemplate) +### SaveEmailTemplate Date: 25.03.2026 @@ -138,7 +139,7 @@ Conclusion: - No behavioral change observed in the covered `SaveEmailTemplate` flows. - Current implementation remains consistent with previous driver-v1 behavior for UI-accessible paths. -## Manual Test Protocol (SaveScheduledEmail) +### SaveScheduledEmail Date: 07.04.2026 @@ -163,7 +164,7 @@ Conclusion: - No behavioral change observed in the tested `SaveScheduledEmail` and `SaveSMSTemplate` create/update flows. -## Manual Test Protocol (GetSurveyVersions) +### GetSurveyVersions Date: 09.04.2026 @@ -185,7 +186,7 @@ Conclusion: - `GetSurveyVersions` behavior is unchanged for the tested UI/API flow. -## Manual Test Protocol (GetCurrentSurveyVersions) +### GetCurrentSurveyVersions Date: 10.04.2026 @@ -207,7 +208,7 @@ Conclusion: - `GetCurrentSurveyVersions` behavior is unchanged after the driver migration. -## Manual Test Protocol (GetCurrentStudyRules) +### GetCurrentStudyRules Date: 10.04.2026 @@ -229,7 +230,7 @@ Conclusion: - `GetCurrentStudyRules` behavior is unchanged after the driver migration. -## Manual Test Protocol (ReplaceConfidentialResponse) +### ReplaceConfidentialResponse Date: 13.04.2026 @@ -251,7 +252,7 @@ Conclusion: - No behavioral change observed for the insert and replace paths of `ReplaceConfidentialResponse`. -## Manual Test Protocol (SaveParticipantState) +### SaveParticipantState Date: 13.04.2026 @@ -273,7 +274,7 @@ Conclusion: - No behavioral change observed for the insert and replace paths of `SaveParticipantState`. -## Manual Test Protocol (AddUser) +### AddUser Date: 14.04.2026 @@ -295,7 +296,7 @@ Conclusion: - No behavioral change observed for the insert and duplicate paths of `AddUser`. -## Manual Test Protocol (_updateUserInDB) +### _updateUserInDB Date: 14.04.2026 @@ -317,7 +318,7 @@ Conclusion: - No behavioral change observed for the replace path of `_updateUserInDB`. -## Manual Test Protocol (SetUserAttribute) +### SetUserAttribute Date: 15.04.2026 @@ -337,7 +338,7 @@ Conclusion: - No behavioral change observed for the insert path of `SetUserAttribute`. -## Manual Test Protocol (GetUniqueReportKeysForStudy) +### GetUniqueReportKeysForStudy Date: 15.04.2026 @@ -361,7 +362,7 @@ Conclusion: - No behavioral change observed for `GetUniqueReportKeysForStudy` after the driver migration. -## Manual Test Protocol (GetSurveyKeysForStudy) +### GetSurveyKeysForStudy Date: 15.04.2026 @@ -383,7 +384,7 @@ Conclusion: - No behavioral change observed for `GetSurveyKeysForStudy` after the driver migration. -## Manual Test Protocol (CreateOTP) +### CreateOTP Date: 21.04.2026 From b97d86bfa41cd930308214220c8107a890927573 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:25:00 +0200 Subject: [PATCH 26/38] Update test protocol --- mongodb-driver-update.md | 51 ++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index dcc12c34..5031ec30 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -6,69 +6,70 @@ The following is a list of all changes made for the update of the MongoDB Go Dri ### All files -- The bson/primitive package has been merged into the bson package —> changed any instance of primitive.ObjectID to bson.ObjectId. +- **primitve package**: The bson/primitive package has been merged into the bson package —> changed any instance of primitive.ObjectID to bson.ObjectID. ### All DB packages -- context.Context parameter has been removed from mongo.Connect(): the deployment connector doesn’t accept a context, meaning that the context passed to mongo.Connect() in previous versions didn't serve a purpose. -- Simplfied DropOne and DropAll methods by removing the unused server response -- removed unused return value for context.WithTimeout() -- Index Model: The old `IndexOptionsBuilder` type was removed and `IndexModel.Options.Name` is no longer accessible as a field. Required steps: +- `mongo.Connect()`: context.Context parameter has been removed. (The deployment connector doesn’t accept a context, meaning that the context passed to mongo.Connect() in previous versions didn't serve a purpose.) +- `DropOne` and `DropAll`: simplified methods by removing the unused server response +- `context.WithTimeout()`: removed unused return value +- **Index Model**: The old `IndexOptionsBuilder` type was removed and `IndexModel.Options.Name` is no longer accessible as a field. Required steps: - define static default index name constants/lists per collection - reuse those names for both index creation and `DropOne` in `drop defaults` + - manually tested, see [`Index migration` test protocol](#index-migration) ### Messaging #### email-templates -- `SaveEmailTemplate`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested, see SaveEmailTemplate protocol) +- `SaveEmailTemplate`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested, see [`SaveEmailTemplate` test protocol](#saveemailtemplate)) #### scheduled-emails -- `SaveScheduledEmail`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested) +- `SaveScheduledEmail`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested, see [`SaveScheduledEmail` test protocol](#savescheduledemail-and-savesmstemplate)) #### sms-templates -- `SaveSMSTemplate`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested, see SaveScheduledEmail protocol) +- `SaveSMSTemplate`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested, see [`SaveScheduledEmail` test protocol](#savescheduledemail-and-savesmstemplate)) ### Participant User #### user-attributes -- The UpdateOptions has been changed to UpdateOneOptions to configure UpdateOne operation. (manually tested, see `SetUserAttribute` protocol) +- `SetUserAttribute`: `UpdateOptions` has been changed to `UpdateOneOptions` to configure UpdateOne operation. (manually tested, see [`SetUserAttribute` test protocol](#setuserattribute)) #### users -- `AddUser`: MongoDB Go Driver v2 no longer allows constructing or modifying option structs directly, so update options must now be created through the new builder API (options.UpdateOne().SetUpsert(true)) instead of setting fields on UpdateOptions manually. -- `_updateUserInDB`: FindOneAndReplaceOptions can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOneAndReplace().SetReturnDocument(options.After)) instead of setting option fields directly. (manually tested, see `_updateUserInDB` protocol) +- `AddUser`: MongoDB Go Driver v2 no longer allows constructing or modifying option structs directly, so update options must now be created through the new builder API (options.UpdateOne().SetUpsert(true)) instead of setting fields on UpdateOptions manually. (manually tested, see [`AddUser` test protocol](#adduser)) +- `_updateUserInDB`: `FindOneAndReplaceOptions` can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOneAndReplace().SetReturnDocument(options.After)) instead of setting option fields directly. (manually tested, see [`_updateUserInDB` test protocol](#_updateuserindb)) #### otps -- `CreateOTP`: update the callback for mongo.WithSession to use a context.Context implementation, rather than the custom mongo.SessionContext +- `CreateOTP`: update the callback for mongo.WithSession to use a context.Context implementation, rather than the custom mongo.SessionContext (manually tested, see [`CreateOTP` test protocol](#createotp)) ### Study #### participants -- `SaveParticipantState`: configure FindOneAndReplaceOptions through options.FindOneAndReplace().Set... instead of filling the struct fields directly. +- `SaveParticipantState`: `FindOneAndReplaceOptions` can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOneAndReplace().SetUpsert(true).SetReturnDocument(options.After)) instead of setting option fields directly. (manually tested, see [`SaveParticipantState` test protocol](#saveparticipantstate)) -#### confidential responses +#### confidential-responses -- configure replace options via the options.Replace() builder (for example, options.Replace().SetUpsert(true)) instead of instantiating a ReplaceOptions struct literal and passing its address. +- `ReplaceConfidentialResponse`: `ReplaceOptions` can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.Replace().SetUpsert(true)) instead of setting option fields directly. (manually tested, see [`ReplaceConfidentialResponse` test protocol](#replaceconfidentialresponse)) #### study-rules -- &options.FindOneOptions{ Sort: sortByPublished } becomes options.FindOne().SetSort(sortByPublished). +- `GetCurrentStudyRules`: `FindOneOptions` can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOne().SetSort(sortByPublished)) instead of setting option fields directly. (manually tested, see [`GetCurrentStudyRules` test protocol](#getcurrentstudyrules)) #### reports -- GetUniqueReportKeysForStudy:`Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop. (manually tested, see `GetUniqueReportKeysForStudy` protocol) +- `GetUniqueReportKeysForStudy`: `Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop. (manually tested, see [`GetUniqueReportKeysForStudy` test protocol](#getuniquereportkeysforstudy)) #### surveys -- GetSurveyKeysForStudy: `Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop. (manually tested, see `GetSurveyKeysForStudy` protocol) -- GetCurrentSurveyVersion: create FindOneOptions using the options.FindOne() builder and setters (for example, options.FindOne().SetSort(sortByPublishedDesc)) instead of instantiating &options.FindOneOptions{} and mutating its fields. -- GetSurveyVersions: create FindOptions using the options.Find() builder and its setters (for example, options.Find().SetProjection(...).SetSort(...)) instead of instantiating &options.FindOptions{} and mutating its fields. +- `GetSurveyKeysForStudy`: `Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop. (manually tested, see [`GetSurveyKeysForStudy` test protocol](#getsurveykeysforstudy)) +- `GetCurrentSurveyVersion`: create FindOneOptions using the options.FindOne() builder and setters (for example, options.FindOne().SetSort(sortByPublishedDesc)) instead of instantiating &options.FindOneOptions{} and mutating its fields. (manually tested, see [`GetCurrentSurveyVersion` test protocol](#getcurrentsurveyversion)) +- `GetSurveyVersions`: create FindOptions using the options.Find() builder and its setters (for example, options.Find().SetProjection(...).SetSort(...)) instead of instantiating &options.FindOptions{} and mutating its fields. (manually tested, see [`GetSurveyVersions` test protocol](#getsurveyversions)) ## Manual Test Protocol @@ -139,7 +140,7 @@ Conclusion: - No behavioral change observed in the covered `SaveEmailTemplate` flows. - Current implementation remains consistent with previous driver-v1 behavior for UI-accessible paths. -### SaveScheduledEmail +### SaveScheduledEmail and SaveSMSTemplate Date: 07.04.2026 @@ -186,18 +187,18 @@ Conclusion: - `GetSurveyVersions` behavior is unchanged for the tested UI/API flow. -### GetCurrentSurveyVersions +### GetCurrentSurveyVersion Date: 10.04.2026 Scope: -- Verify unchanged behavior for `GetCurrentSurveyVersions` after switching to `options.FindOne()` builder with setters. +- Verify unchanged behavior for `GetCurrentSurveyVersion` after switching to `options.FindOne()` builder with setters. Test execution: 1. Exported study configuration as JSON via the UI (study configuration export). -2. `GetCurrentSurveyVersions` is called internally during this export to retrieve the current version of all surveys belonging to the study. +2. `GetCurrentSurveyVersion` is called internally during this export to retrieve the current version of all surveys belonging to the study. Expected and observed results: @@ -206,7 +207,7 @@ Expected and observed results: Conclusion: -- `GetCurrentSurveyVersions` behavior is unchanged after the driver migration. +- `GetCurrentSurveyVersion` behavior is unchanged after the driver migration. ### GetCurrentStudyRules From 3cee36c2dd2db5b4b473c75075647084d33ad7d8 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:48:10 +0200 Subject: [PATCH 27/38] Update test protocol --- mongodb-driver-update.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/mongodb-driver-update.md b/mongodb-driver-update.md index 5031ec30..3195c272 100644 --- a/mongodb-driver-update.md +++ b/mongodb-driver-update.md @@ -2,11 +2,11 @@ ## Overview of Changes -The following is a list of all changes made for the update of the MongoDB Go Driver from v1 to v2. The `bson/primitive` types were replaced globally throughout the codebase. This is followed by changes that were applied uniformly across all DB packages, and finally by changes specific to individual DB collections. +The following lists all changes in the upgrade from MongoDB Go Driver v1 to v2. The `bson/primitive` types were replaced globally throughout the codebase. This is followed by changes that were applied uniformly across all DB packages, and finally by changes specific to individual DB collections. ### All files -- **primitve package**: The bson/primitive package has been merged into the bson package —> changed any instance of primitive.ObjectID to bson.ObjectID. +- **primitive package**: The bson/primitive package has been merged into the bson package —> changed any instance of primitive.ObjectID to bson.ObjectID. ### All DB packages @@ -16,60 +16,60 @@ The following is a list of all changes made for the update of the MongoDB Go Dri - **Index Model**: The old `IndexOptionsBuilder` type was removed and `IndexModel.Options.Name` is no longer accessible as a field. Required steps: - define static default index name constants/lists per collection - reuse those names for both index creation and `DropOne` in `drop defaults` - - manually tested, see [`Index migration` test protocol](#index-migration) + - tested, see [Index migration test protocol](#index-migration) ### Messaging #### email-templates -- `SaveEmailTemplate`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested, see [`SaveEmailTemplate` test protocol](#saveemailtemplate)) +- `SaveEmailTemplate`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (tested, see [SaveEmailTemplate test protocol](#saveemailtemplate)) #### scheduled-emails -- `SaveScheduledEmail`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested, see [`SaveScheduledEmail` test protocol](#savescheduledemail-and-savesmstemplate)) +- `SaveScheduledEmail`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (tested, see [SaveScheduledEmail test protocol](#savescheduledemail-and-savesmstemplate)) #### sms-templates -- `SaveSMSTemplate`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (manually tested, see [`SaveScheduledEmail` test protocol](#savescheduledemail-and-savesmstemplate)) +- `SaveSMSTemplate`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (tested, see [SaveScheduledEmail test protocol](#savescheduledemail-and-savesmstemplate)) ### Participant User #### user-attributes -- `SetUserAttribute`: `UpdateOptions` has been changed to `UpdateOneOptions` to configure UpdateOne operation. (manually tested, see [`SetUserAttribute` test protocol](#setuserattribute)) +- `SetUserAttribute`: `UpdateOptions` has been changed to `UpdateOneOptions` to configure UpdateOne operation. (tested, see [SetUserAttribute test protocol](#setuserattribute)) #### users -- `AddUser`: MongoDB Go Driver v2 no longer allows constructing or modifying option structs directly, so update options must now be created through the new builder API (options.UpdateOne().SetUpsert(true)) instead of setting fields on UpdateOptions manually. (manually tested, see [`AddUser` test protocol](#adduser)) -- `_updateUserInDB`: `FindOneAndReplaceOptions` can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOneAndReplace().SetReturnDocument(options.After)) instead of setting option fields directly. (manually tested, see [`_updateUserInDB` test protocol](#_updateuserindb)) +- `AddUser`: MongoDB Go Driver v2 no longer allows constructing or modifying option structs directly, so update options must now be created through the new builder API (options.UpdateOne().SetUpsert(true)) instead of setting fields on UpdateOptions manually. (tested, see [AddUser test protocol](#adduser)) +- `_updateUserInDB`: `FindOneAndReplaceOptions` can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOneAndReplace().SetReturnDocument(options.After)) instead of setting option fields directly. (tested, see [_updateUserInDB test protocol](#_updateuserindb)) #### otps -- `CreateOTP`: update the callback for mongo.WithSession to use a context.Context implementation, rather than the custom mongo.SessionContext (manually tested, see [`CreateOTP` test protocol](#createotp)) +- `CreateOTP`: update the callback for mongo.WithSession to use a context.Context implementation, rather than the custom mongo.SessionContext (tested, see [CreateOTP test protocol](#createotp)) ### Study #### participants -- `SaveParticipantState`: `FindOneAndReplaceOptions` can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOneAndReplace().SetUpsert(true).SetReturnDocument(options.After)) instead of setting option fields directly. (manually tested, see [`SaveParticipantState` test protocol](#saveparticipantstate)) +- `SaveParticipantState`: `FindOneAndReplaceOptions` can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOneAndReplace().SetUpsert(true).SetReturnDocument(options.After)) instead of setting option fields directly. (tested, see [SaveParticipantState test protocol](#saveparticipantstate)) #### confidential-responses -- `ReplaceConfidentialResponse`: `ReplaceOptions` can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.Replace().SetUpsert(true)) instead of setting option fields directly. (manually tested, see [`ReplaceConfidentialResponse` test protocol](#replaceconfidentialresponse)) +- `ReplaceConfidentialResponse`: `ReplaceOptions` can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.Replace().SetUpsert(true)) instead of setting option fields directly. (tested, see [ReplaceConfidentialResponse test protocol](#replaceconfidentialresponse)) #### study-rules -- `GetCurrentStudyRules`: `FindOneOptions` can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOne().SetSort(sortByPublished)) instead of setting option fields directly. (manually tested, see [`GetCurrentStudyRules` test protocol](#getcurrentstudyrules)) +- `GetCurrentStudyRules`: `FindOneOptions` can no longer be created or populated as a struct, so the v2 driver requires using the builder pattern (options.FindOne().SetSort(sortByPublished)) instead of setting option fields directly. (tested, see [GetCurrentStudyRules test protocol](#getcurrentstudyrules)) #### reports -- `GetUniqueReportKeysForStudy`: `Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop. (manually tested, see [`GetUniqueReportKeysForStudy` test protocol](#getuniquereportkeysforstudy)) +- `GetUniqueReportKeysForStudy`: `Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop. (tested, see [GetUniqueReportKeysForStudy test protocol](#getuniquereportkeysforstudy)) #### surveys -- `GetSurveyKeysForStudy`: `Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop. (manually tested, see [`GetSurveyKeysForStudy` test protocol](#getsurveykeysforstudy)) -- `GetCurrentSurveyVersion`: create FindOneOptions using the options.FindOne() builder and setters (for example, options.FindOne().SetSort(sortByPublishedDesc)) instead of instantiating &options.FindOneOptions{} and mutating its fields. (manually tested, see [`GetCurrentSurveyVersion` test protocol](#getcurrentsurveyversion)) -- `GetSurveyVersions`: create FindOptions using the options.Find() builder and its setters (for example, options.Find().SetProjection(...).SetSort(...)) instead of instantiating &options.FindOptions{} and mutating its fields. (manually tested, see [`GetSurveyVersions` test protocol](#getsurveyversions)) +- `GetSurveyKeysForStudy`: `Distinct()` no longer returns `([]interface{}, error)`; it returns a single result type on which you call `.Decode(&target)` directly into a `[]string`, eliminating the manual type-assertion loop. (tested, see [GetSurveyKeysForStudy test protocol](#getsurveykeysforstudy)) +- `GetCurrentSurveyVersion`: create FindOneOptions using the options.FindOne() builder and setters (for example, options.FindOne().SetSort(sortByPublishedDesc)) instead of instantiating &options.FindOneOptions{} and mutating its fields. (tested, see [GetCurrentSurveyVersion test protocol](#getcurrentsurveyversion)) +- `GetSurveyVersions`: create FindOptions using the options.Find() builder and its setters (for example, options.Find().SetProjection(...).SetSort(...)) instead of instantiating &options.FindOptions{} and mutating its fields. (tested, see [GetSurveyVersions test protocol](#getsurveyversions)) ## Manual Test Protocol From 70507e4ceb138f6a27b54af83d7e13d384af9356 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 22 Apr 2026 12:50:44 +0200 Subject: [PATCH 28/38] move test protocol to test folder --- mongodb-driver-update.md => test/mongodb-driver-update.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename mongodb-driver-update.md => test/mongodb-driver-update.md (100%) diff --git a/mongodb-driver-update.md b/test/mongodb-driver-update.md similarity index 100% rename from mongodb-driver-update.md rename to test/mongodb-driver-update.md From 986e3b2747ba0cfafe2643aabee027052cbe7e0e Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:55:58 +0200 Subject: [PATCH 29/38] rename variable to avoid shadowing options package --- pkg/db/study/confidential-responses.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pkg/db/study/confidential-responses.go b/pkg/db/study/confidential-responses.go index edd2365d..b7ad0ab5 100644 --- a/pkg/db/study/confidential-responses.go +++ b/pkg/db/study/confidential-responses.go @@ -83,11 +83,9 @@ func (dbService *StudyDBService) ReplaceConfidentialResponse(instanceID string, "key": response.Key, } - upsert := true - options := options.Replace(). - SetUpsert(upsert) + opts := options.Replace().SetUpsert(true) - _, err := dbService.collectionConfidentialResponses(instanceID, studyKey).ReplaceOne(ctx, filter, response, options) + _, err := dbService.collectionConfidentialResponses(instanceID, studyKey).ReplaceOne(ctx, filter, response, opts) return err } From 30f644fd87819f96d0b1bc23f377816b95848fb0 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:35:37 +0200 Subject: [PATCH 30/38] Remove dead context.WithTimeout since mongo.Connect no longer accepts a context parameter --- pkg/db/global-infos/db.go | 3 --- pkg/db/management-user/db.go | 3 --- pkg/db/messaging/db.go | 3 --- pkg/db/participant-user/db.go | 3 --- pkg/db/study/db.go | 3 --- test/mongodb-driver-update.md | 2 +- 6 files changed, 1 insertion(+), 16 deletions(-) diff --git a/pkg/db/global-infos/db.go b/pkg/db/global-infos/db.go index 0c4218da..69727cf6 100644 --- a/pkg/db/global-infos/db.go +++ b/pkg/db/global-infos/db.go @@ -26,9 +26,6 @@ type GlobalInfosDBService struct { } func NewGlobalInfosDBService(configs db.DBConfig) (*GlobalInfosDBService, error) { - _, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) - defer cancel() - dbClient, err := mongo.Connect( options.Client().ApplyURI(configs.URI), options.Client().SetMaxConnIdleTime(time.Duration(configs.IdleConnTimeout)*time.Second), diff --git a/pkg/db/management-user/db.go b/pkg/db/management-user/db.go index a59ccec4..422860a1 100644 --- a/pkg/db/management-user/db.go +++ b/pkg/db/management-user/db.go @@ -35,9 +35,6 @@ type ManagementUserDBService struct { } func NewManagementUserDBService(configs db.DBConfig) (*ManagementUserDBService, error) { - _, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) - defer cancel() - dbClient, err := mongo.Connect( options.Client().ApplyURI(configs.URI), options.Client().SetMaxConnIdleTime(time.Duration(configs.IdleConnTimeout)*time.Second), diff --git a/pkg/db/messaging/db.go b/pkg/db/messaging/db.go index dd75ca8a..aa09e2ca 100644 --- a/pkg/db/messaging/db.go +++ b/pkg/db/messaging/db.go @@ -30,9 +30,6 @@ type MessagingDBService struct { } func NewMessagingDBService(configs db.DBConfig) (*MessagingDBService, error) { - _, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) - defer cancel() - dbClient, err := mongo.Connect( options.Client().ApplyURI(configs.URI), options.Client().SetMaxConnIdleTime(time.Duration(configs.IdleConnTimeout)*time.Second), diff --git a/pkg/db/participant-user/db.go b/pkg/db/participant-user/db.go index ba1628ea..9653691b 100644 --- a/pkg/db/participant-user/db.go +++ b/pkg/db/participant-user/db.go @@ -29,9 +29,6 @@ type ParticipantUserDBService struct { } func NewParticipantUserDBService(configs db.DBConfig) (*ParticipantUserDBService, error) { - _, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) - defer cancel() - dbClient, err := mongo.Connect( options.Client().ApplyURI(configs.URI), options.Client().SetMaxConnIdleTime(time.Duration(configs.IdleConnTimeout)*time.Second), diff --git a/pkg/db/study/db.go b/pkg/db/study/db.go index 64bc02ae..20b60e77 100644 --- a/pkg/db/study/db.go +++ b/pkg/db/study/db.go @@ -38,9 +38,6 @@ type StudyDBService struct { } func NewStudyDBService(configs db.DBConfig) (*StudyDBService, error) { - _, cancel := context.WithTimeout(context.Background(), time.Duration(configs.Timeout)*time.Second) - defer cancel() - dbClient, err := mongo.Connect( options.Client().ApplyURI(configs.URI), options.Client().SetMaxConnIdleTime(time.Duration(configs.IdleConnTimeout)*time.Second), diff --git a/test/mongodb-driver-update.md b/test/mongodb-driver-update.md index 3195c272..1cab29b9 100644 --- a/test/mongodb-driver-update.md +++ b/test/mongodb-driver-update.md @@ -11,8 +11,8 @@ The following lists all changes in the upgrade from MongoDB Go Driver v1 to v2. ### All DB packages - `mongo.Connect()`: context.Context parameter has been removed. (The deployment connector doesn’t accept a context, meaning that the context passed to mongo.Connect() in previous versions didn't serve a purpose.) +- `context.WithTimeout()`: removed unused `context.WithTimeout()` call (including `defer cancel()`) before `mongo.Connect()` — the context was discarded with `_` and never passed to `mongo.Connect()`, which in v2 no longer accepts a context parameter anyway. - `DropOne` and `DropAll`: simplified methods by removing the unused server response -- `context.WithTimeout()`: removed unused return value - **Index Model**: The old `IndexOptionsBuilder` type was removed and `IndexModel.Options.Name` is no longer accessible as a field. Required steps: - define static default index name constants/lists per collection - reuse those names for both index creation and `DropOne` in `drop defaults` From 374b01d972a71b34f1e10711071af2498e8553d9 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:36:18 +0200 Subject: [PATCH 31/38] rename variable to avoid shadowing --- pkg/db/messaging/scheduled-email.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/db/messaging/scheduled-email.go b/pkg/db/messaging/scheduled-email.go index 367c76dd..36aa42ea 100644 --- a/pkg/db/messaging/scheduled-email.go +++ b/pkg/db/messaging/scheduled-email.go @@ -105,12 +105,12 @@ func (dbService *MessagingDBService) SaveScheduledEmail(instanceID string, sched if !scheduledEmail.ID.IsZero() { filter := bson.M{"_id": scheduledEmail.ID} - options := options.FindOneAndReplace(). + opts := options.FindOneAndReplace(). SetUpsert(false). SetReturnDocument(options.After) elem := messagingTypes.ScheduledEmail{} err := dbService.collectionEmailSchedules(instanceID). - FindOneAndReplace(ctx, filter, scheduledEmail, options). + FindOneAndReplace(ctx, filter, scheduledEmail, opts). Decode(&elem) return elem, err } else { From 1c9101ae62ee946305d1305c0b7a06f6c687db56 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:38:09 +0200 Subject: [PATCH 32/38] remove redundant fmt.Sprintf --- pkg/db/study/reports.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/db/study/reports.go b/pkg/db/study/reports.go index 34b4c4fb..4313eb59 100644 --- a/pkg/db/study/reports.go +++ b/pkg/db/study/reports.go @@ -71,7 +71,7 @@ func (dbService *StudyDBService) DropIndexForReportsCollection(instanceID string } else { for _, indexName := range defaultReportIndexNames { if indexName == "" { - slog.Error("Index name is empty for reports collection", slog.String("index", fmt.Sprintf("%+v", indexName))) + slog.Error("Index name is empty for reports collection") continue } err := collection.Indexes().DropOne(ctx, indexName) From 4e3e138cda3745258d24b7ca9493c0b446da27c0 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Wed, 22 Apr 2026 15:40:49 +0200 Subject: [PATCH 33/38] Avoid passing explicit nil as variadic opts --- pkg/db/study/confidential-responses.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/db/study/confidential-responses.go b/pkg/db/study/confidential-responses.go index b7ad0ab5..6bdbf4ba 100644 --- a/pkg/db/study/confidential-responses.go +++ b/pkg/db/study/confidential-responses.go @@ -104,7 +104,6 @@ func (dbService *StudyDBService) FindConfidentialResponses(instanceID string, st cur, err := dbService.collectionConfidentialResponses(instanceID, studyKey).Find( ctx, filter, - nil, ) if err != nil { From fe7ea7950f02b19f1ed29eff89829233b1dad537 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Thu, 23 Apr 2026 08:13:17 +0200 Subject: [PATCH 34/38] Avoid passing explicit nil to DeleteMany --- pkg/db/participant-user/renew-tokens.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/db/participant-user/renew-tokens.go b/pkg/db/participant-user/renew-tokens.go index 2875a030..a1f77aff 100644 --- a/pkg/db/participant-user/renew-tokens.go +++ b/pkg/db/participant-user/renew-tokens.go @@ -117,7 +117,7 @@ func (dbService *ParticipantUserDBService) DeleteRenewTokenByToken(instanceID st ctx, cancel := dbService.getContext() defer cancel() - res, err := dbService.collectionRenewTokens(instanceID).DeleteOne(ctx, filter, nil) + res, err := dbService.collectionRenewTokens(instanceID).DeleteOne(ctx, filter) if err != nil { return err } @@ -132,7 +132,7 @@ func (dbService *ParticipantUserDBService) DeleteRenewTokensForUser(instanceID s ctx, cancel := dbService.getContext() defer cancel() - res, err := dbService.collectionRenewTokens(instanceID).DeleteMany(ctx, filter, nil) + res, err := dbService.collectionRenewTokens(instanceID).DeleteMany(ctx, filter) if err != nil { return 0, err } @@ -144,7 +144,7 @@ func (dbService *ParticipantUserDBService) DeleteRenewTokensForSession(instanceI ctx, cancel := dbService.getContext() defer cancel() - res, err := dbService.collectionRenewTokens(instanceID).DeleteMany(ctx, filter, nil) + res, err := dbService.collectionRenewTokens(instanceID).DeleteMany(ctx, filter) if err != nil { return 0, err } From 0cf3766095a03cd1e236bf5193daad9370c9f963 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:37:25 +0200 Subject: [PATCH 35/38] Add error handling for InsertOne --- pkg/db/study/confidential-responses.go | 5 ++++- pkg/db/study/responses.go | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/db/study/confidential-responses.go b/pkg/db/study/confidential-responses.go index 6bdbf4ba..7774ff8f 100644 --- a/pkg/db/study/confidential-responses.go +++ b/pkg/db/study/confidential-responses.go @@ -70,8 +70,11 @@ func (dbService *StudyDBService) AddConfidentialResponse(instanceID string, stud return "", errors.New("participantID must be defined") } res, err := dbService.collectionConfidentialResponses(instanceID, studyKey).InsertOne(ctx, response) + if err != nil { + return "", err + } id := res.InsertedID.(bson.ObjectID) - return id.Hex(), err + return id.Hex(), nil } func (dbService *StudyDBService) ReplaceConfidentialResponse(instanceID string, studyKey string, response studyTypes.SurveyResponse) error { diff --git a/pkg/db/study/responses.go b/pkg/db/study/responses.go index 39fe31ee..44ae7112 100644 --- a/pkg/db/study/responses.go +++ b/pkg/db/study/responses.go @@ -106,8 +106,11 @@ func (dbService *StudyDBService) AddSurveyResponse(instanceID string, studyKey s response.ArrivedAt = time.Now().Unix() } res, err := dbService.collectionResponses(instanceID, studyKey).InsertOne(ctx, response) + if err != nil { + return "", err + } id := res.InsertedID.(bson.ObjectID) - return id.Hex(), err + return id.Hex(), nil } // get response by id From 0800e7e30e19a70de535f2673c55333bf998df54 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Fri, 24 Apr 2026 10:37:58 +0200 Subject: [PATCH 36/38] Avoid passing nil to DeleteOne --- pkg/db/messaging/outgoing-emails.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/db/messaging/outgoing-emails.go b/pkg/db/messaging/outgoing-emails.go index f817bdcd..1b4ac26b 100644 --- a/pkg/db/messaging/outgoing-emails.go +++ b/pkg/db/messaging/outgoing-emails.go @@ -111,7 +111,7 @@ func (dbService *MessagingDBService) DeleteOutgoingEmail(instanceID string, id s _id, _ := bson.ObjectIDFromHex(id) filter := bson.M{"_id": _id} - res, err := dbService.collectionOutgoingEmails(instanceID).DeleteOne(ctx, filter, nil) + res, err := dbService.collectionOutgoingEmails(instanceID).DeleteOne(ctx, filter) if err != nil { return err } From 456f2e12185d104d5197b136d03d5f7d8730d14d Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Tue, 28 Apr 2026 12:33:03 +0200 Subject: [PATCH 37/38] Fix typo --- test/mongodb-driver-update.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mongodb-driver-update.md b/test/mongodb-driver-update.md index 1cab29b9..4cda35e9 100644 --- a/test/mongodb-driver-update.md +++ b/test/mongodb-driver-update.md @@ -30,7 +30,7 @@ The following lists all changes in the upgrade from MongoDB Go Driver v1 to v2. #### sms-templates -- `SaveSMSTemplate`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (tested, see [SaveScheduledEmail test protocol](#savescheduledemail-and-savesmstemplate)) +- `SaveSMSTemplate`: use the options builder, and pass a pointer returned by options.FindOneAndReplace() instead of constructing FindOneAndReplaceOptions as a struct literal (tested, see [SaveSMSTemplate test protocol](#savescheduledemail-and-savesmstemplate)) ### Participant User From 25739206568b101b8eb2edd40a98d11621398a65 Mon Sep 17 00:00:00 2001 From: kschneider84 <77678452+kschneider84@users.noreply.github.com> Date: Mon, 15 Jun 2026 11:24:09 +0200 Subject: [PATCH 38/38] Update mongo driver version --- jobs/user-management/migrate-account-info.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jobs/user-management/migrate-account-info.go b/jobs/user-management/migrate-account-info.go index eac09846..40278f4b 100644 --- a/jobs/user-management/migrate-account-info.go +++ b/jobs/user-management/migrate-account-info.go @@ -9,7 +9,7 @@ import ( studyUtils "github.com/case-framework/case-backend/pkg/study/utils" umTypes "github.com/case-framework/case-backend/pkg/user-management/types" umUtils "github.com/case-framework/case-backend/pkg/user-management/utils" - "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/v2/bson" ) func migrateAccountInfo() {