package main import ( "context" "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/mongo/readpref" ) const collectionName = "feeds" type FeedsStorage struct { collection *mongo.Collection } func NewFeedsStorage() FeedsStorage { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://root:password@mongo:27017")) if err != nil { panic(err) } err = client.Ping(ctx, readpref.Primary()) if err != nil { panic(err) } db := client.Database("example") names, err := db.ListCollectionNames(ctx, bson.M{}) if err != nil { panic(err) } found := false for _, n := range names { if n == collectionName { found = true break } } if !found { err := db.CreateCollection(ctx, collectionName) if err != nil { panic(err) } } return FeedsStorage{ collection: db.Collection(collectionName), } } func (s FeedsStorage) Add(ctx context.Context, name string) error { feed := Feed{ Name: name, Posts: []Post{}, } _, err := s.collection.InsertOne(ctx, feed) if err != nil { if !isDuplicateError(err) { return err } } return nil } func (s FeedsStorage) All(ctx context.Context) ([]Feed, error) { cursor, err := s.collection.Find(ctx, bson.M{}) if err != nil { return nil, err } var feeds []Feed err = cursor.All(ctx, &feeds) if err != nil { return nil, err } return feeds, nil } func (s FeedsStorage) ByName(ctx context.Context, name string) (Feed, error) { filter := bson.M{ "_id": name, } var feed Feed err := s.collection.FindOne(ctx, filter).Decode(&feed) if err != nil { return Feed{}, err } return feed, nil } func (s FeedsStorage) AppendPost(ctx context.Context, post Post) error { return s.appendPostIfNotPresent(ctx, post) } func (s FeedsStorage) UpdatePost(ctx context.Context, post Post) error { err := s.updatePostIfPresent(ctx, post) if err != nil { return err } err = s.appendPostIfNotPresent(ctx, post) if err != nil { return err } err = s.removePostIfNotInFeed(ctx, post) if err != nil { return err } return nil } func (s FeedsStorage) updatePostIfPresent(ctx context.Context, post Post) error { if len(post.Tags) == 0 { return nil } filter := bson.M{ "_id": bson.M{ "$in": post.Tags, }, "posts.id": post.ID, } update := bson.M{ "$set": bson.M{ "posts.$": post, }, } _, err := s.collection.UpdateMany(ctx, filter, update) return err } func (s FeedsStorage) appendPostIfNotPresent(ctx context.Context, post Post) error { if len(post.Tags) == 0 { return nil } filter := bson.M{ "_id": bson.M{ "$in": post.Tags, }, "posts.id": bson.M{ "$ne": post.ID, }, } update := bson.M{ "$push": bson.M{ "posts": bson.M{ "$each": bson.A{post}, "$position": 0, }, }, } _, err := s.collection.UpdateMany(ctx, filter, update) return err } func (s FeedsStorage) removePostIfNotInFeed(ctx context.Context, post Post) error { tags := post.Tags if tags == nil { tags = []string{} } filter := bson.M{ "_id": bson.M{ "$nin": tags, }, "posts.id": post.ID, } update := bson.M{ "$pull": bson.M{ "posts": bson.M{ "id": post.ID, }, }, } _, err := s.collection.UpdateMany(ctx, filter, update) if err != nil { return err } return nil } func isDuplicateError(err error) bool { mErr, ok := err.(mongo.WriteException) if !ok { return false } return mErr.WriteErrors[0].Code == 11000 }