// Copyright © 2023 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package mgo import ( "context" "github.com/OpenIMSDK/tools/errs" "github.com/OpenIMSDK/tools/mgoutil" "github.com/OpenIMSDK/tools/pagination" "go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" ) // FriendMgo implements FriendModelInterface using MongoDB as the storage backend. type FriendMgo struct { coll *mongo.Collection } // NewFriendMongo creates a new instance of FriendMgo with the provided MongoDB database. func NewFriendMongo(db *mongo.Database) (relation.FriendModelInterface, error) { coll := db.Collection("friend") _, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{ Keys: bson.D{ {Key: "owner_user_id", Value: 1}, {Key: "friend_user_id", Value: 1}, }, Options: options.Index().SetUnique(true), }) if err != nil { return nil, err } return &FriendMgo{coll: coll}, nil } // Create inserts multiple friend records. func (f *FriendMgo) Create(ctx context.Context, friends []*relation.FriendModel) error { return mgoutil.InsertMany(ctx, f.coll, friends) } // Delete removes specified friends of the owner user. func (f *FriendMgo) Delete(ctx context.Context, ownerUserID string, friendUserIDs []string) error { filter := bson.M{ "owner_user_id": ownerUserID, "friend_user_id": bson.M{"$in": friendUserIDs}, } return mgoutil.DeleteOne(ctx, f.coll, filter) } // UpdateByMap updates specific fields of a friend document using a map. func (f *FriendMgo) UpdateByMap(ctx context.Context, ownerUserID string, friendUserID string, args map[string]interface{}) error { if len(args) == 0 { return nil } filter := bson.M{ "owner_user_id": ownerUserID, "friend_user_id": friendUserID, } return mgoutil.UpdateOne(ctx, f.coll, filter, bson.M{"$set": args}, true) } // Update modifies multiple friend documents. // func (f *FriendMgo) Update(ctx context.Context, friends []*relation.FriendModel) error { // filter := bson.M{ // "owner_user_id": ownerUserID, // "friend_user_id": friendUserID, // } // return mgotool.UpdateMany(ctx, f.coll, filter, friends) // } // UpdateRemark updates the remark for a specific friend. func (f *FriendMgo) UpdateRemark(ctx context.Context, ownerUserID, friendUserID, remark string) error { return f.UpdateByMap(ctx, ownerUserID, friendUserID, map[string]any{"remark": remark}) } // Take retrieves a single friend document. Returns an error if not found. func (f *FriendMgo) Take(ctx context.Context, ownerUserID, friendUserID string) (*relation.FriendModel, error) { filter := bson.M{ "owner_user_id": ownerUserID, "friend_user_id": friendUserID, } return mgoutil.FindOne[*relation.FriendModel](ctx, f.coll, filter) } // FindUserState finds the friendship status between two users. func (f *FriendMgo) FindUserState(ctx context.Context, userID1, userID2 string) ([]*relation.FriendModel, error) { filter := bson.M{ "$or": []bson.M{ {"owner_user_id": userID1, "friend_user_id": userID2}, {"owner_user_id": userID2, "friend_user_id": userID1}, }, } return mgoutil.Find[*relation.FriendModel](ctx, f.coll, filter) } // FindFriends retrieves a list of friends for a given owner. Missing friends do not cause an error. func (f *FriendMgo) FindFriends(ctx context.Context, ownerUserID string, friendUserIDs []string) ([]*relation.FriendModel, error) { filter := bson.M{ "owner_user_id": ownerUserID, "friend_user_id": bson.M{"$in": friendUserIDs}, } return mgoutil.Find[*relation.FriendModel](ctx, f.coll, filter) } // FindReversalFriends finds users who have added the specified user as a friend. func (f *FriendMgo) FindReversalFriends(ctx context.Context, friendUserID string, ownerUserIDs []string) ([]*relation.FriendModel, error) { filter := bson.M{ "owner_user_id": bson.M{"$in": ownerUserIDs}, "friend_user_id": friendUserID, } return mgoutil.Find[*relation.FriendModel](ctx, f.coll, filter) } // FindOwnerFriends retrieves a paginated list of friends for a given owner. func (f *FriendMgo) FindOwnerFriends(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (int64, []*relation.FriendModel, error) { filter := bson.M{"owner_user_id": ownerUserID} return mgoutil.FindPage[*relation.FriendModel](ctx, f.coll, filter, pagination) } // FindInWhoseFriends finds users who have added the specified user as a friend, with pagination. func (f *FriendMgo) FindInWhoseFriends(ctx context.Context, friendUserID string, pagination pagination.Pagination) (int64, []*relation.FriendModel, error) { filter := bson.M{"friend_user_id": friendUserID} return mgoutil.FindPage[*relation.FriendModel](ctx, f.coll, filter, pagination) } // FindFriendUserIDs retrieves a list of friend user IDs for a given owner. func (f *FriendMgo) FindFriendUserIDs(ctx context.Context, ownerUserID string) ([]string, error) { filter := bson.M{"owner_user_id": ownerUserID} return mgoutil.Find[string](ctx, f.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "friend_user_id": 1})) } // UpdatePinStatus update friend's pin status func (f *FriendMgo) UpdatePinStatus(ctx context.Context, ownerUserID string, friendUserID string, isPinned bool) (err error) { filter := bson.M{"owner_user_id": ownerUserID, "friend_user_id": friendUserID} // Create an update operation to set the "is_pinned" field to isPinned for all documents. update := bson.M{"$set": bson.M{"is_pinned": isPinned}} // Perform the update operation for all documents in the collection. _, err = f.coll.UpdateMany(ctx, filter, update) if err != nil { return errs.Wrap(err, "update pin error") } return nil }