package store import ( "context" "errors" "fmt" "time" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" ) // Optimistic-concurrency errors. HTTP handlers translate ErrVersionConflict // into a 409 with a "conflict" envelope (and the QoL reload toast). var ( ErrNotFound = errors.New("document not found") ErrVersionConflict = errors.New("version conflict") ) // UpdateVersioned applies update to the document only if its version still // matches, bumping version and updatedAt atomically. update uses normal // operators ($set/$push/...); the helper merges its bookkeeping into them. func UpdateVersioned(ctx context.Context, coll *mongo.Collection, id string, version int64, update bson.M) error { merged := bson.M{} for op, fields := range update { merged[op] = fields } set, _ := merged["$set"].(bson.M) if set == nil { set = bson.M{} } set["updatedAt"] = time.Now().UTC() merged["$set"] = set inc, _ := merged["$inc"].(bson.M) if inc == nil { inc = bson.M{} } if _, clash := inc["version"]; clash { return fmt.Errorf("update must not touch version itself") } inc["version"] = int64(1) merged["$inc"] = inc res, err := coll.UpdateOne(ctx, bson.M{"_id": id, "version": version}, merged) if err != nil { return fmt.Errorf("versioned update %s/%s: %w", coll.Name(), id, err) } if res.MatchedCount == 1 { return nil } // Distinguish "gone" from "someone got there first". n, err := coll.CountDocuments(ctx, bson.M{"_id": id}) if err != nil { return fmt.Errorf("versioned update %s/%s: existence check: %w", coll.Name(), id, err) } if n == 0 { return fmt.Errorf("%s/%s: %w", coll.Name(), id, ErrNotFound) } return fmt.Errorf("%s/%s at version %d: %w", coll.Name(), id, version, ErrVersionConflict) } // InsertOne inserts a document map, stamping createdAt/updatedAt/version so // every collection follows the ยง4 conventions. func InsertOne(ctx context.Context, coll *mongo.Collection, id string, doc bson.M) error { now := time.Now().UTC() doc["_id"] = id doc["createdAt"] = now doc["updatedAt"] = now doc["version"] = int64(1) if _, err := coll.InsertOne(ctx, doc); err != nil { return fmt.Errorf("insert %s/%s: %w", coll.Name(), id, err) } return nil }