From ca30a769524727a3596fededadf6d2ee4dea4f2f Mon Sep 17 00:00:00 2001 From: "Xinwei Xiong(cubxxw)" <3293172751nss@gmail.com> Date: Tue, 21 Nov 2023 08:55:10 +0800 Subject: [PATCH] refactor: add openim mysql to mongo refactor Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com> --- internal/rpc/friend/friend.go | 45 ++- pkg/common/convert/friend.go | 56 +++- pkg/common/convert/user.go | 65 ++-- pkg/common/convert/user_test.go | 86 ++++++ pkg/common/db/cache/friend.go | 24 +- pkg/common/db/controller/user.go | 40 ++- pkg/common/db/newmgo/friend.go | 169 +++++++++++ pkg/common/db/newmgo/friend_request.go | 48 +++ pkg/common/db/newmgo/mgotool/tool.go | 10 + .../db/relation/friend_request_model.go | 286 +++++++++--------- pkg/common/db/table/relation/friend.go | 79 ++--- .../db/table/relation/friend_request.go | 55 ++-- 12 files changed, 673 insertions(+), 290 deletions(-) create mode 100644 pkg/common/convert/user_test.go create mode 100644 pkg/common/db/newmgo/friend.go create mode 100644 pkg/common/db/newmgo/friend_request.go diff --git a/internal/rpc/friend/friend.go b/internal/rpc/friend/friend.go index c563f77fe..e79888d0c 100644 --- a/internal/rpc/friend/friend.go +++ b/internal/rpc/friend/friend.go @@ -37,8 +37,9 @@ import ( "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/relation" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/newmgo" tablerelation "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification" ) @@ -52,41 +53,61 @@ type friendServer struct { } func Start(client registry.SvcDiscoveryRegistry, server *grpc.Server) error { - db, err := relation.NewGormDB() + // Initialize MongoDB + mongo, err := unrelation.NewMongo() if err != nil { return err } - if err := db.AutoMigrate(&tablerelation.FriendModel{}, &tablerelation.FriendRequestModel{}, &tablerelation.BlackModel{}); err != nil { + + // Initialize Redis + rdb, err := cache.NewRedis() + if err != nil { return err } - rdb, err := cache.NewRedis() + + friendMongoDB, err := newmgo.NewFriendMongo(mongo.GetDatabase()) + if err != nil { + return err + } + + friendRequestMongoDB, err := newmgo.NewFriendRequestMongo(mongo.GetDatabase()) if err != nil { return err } - blackDB := relation.NewBlackGorm(db) - friendDB := relation.NewFriendGorm(db) + + blackMongoDB, err := newmgo.NewBlackMongo(mongo.GetDatabase()) + if err != nil { + return err + } + + // Initialize RPC clients userRpcClient := rpcclient.NewUserRpcClient(client) msgRpcClient := rpcclient.NewMessageRpcClient(client) + + // Initialize notification sender notificationSender := notification.NewFriendNotificationSender( &msgRpcClient, notification.WithRpcFunc(userRpcClient.GetUsersInfo), ) + + // Register Friend server with refactored MongoDB and Redis integrations pbfriend.RegisterFriendServer(server, &friendServer{ friendDatabase: controller.NewFriendDatabase( - friendDB, - relation.NewFriendRequestGorm(db), - cache.NewFriendCacheRedis(rdb, friendDB, cache.GetDefaultOpt()), - tx.NewGorm(db), + friendMongoDB, + friendRequestMongoDB, + cache.NewFriendCacheRedis(rdb, friendMongoDB, cache.GetDefaultOpt()), + tx.NewMongo(mongo.GetClient()), ), blackDatabase: controller.NewBlackDatabase( - blackDB, - cache.NewBlackCacheRedis(rdb, blackDB, cache.GetDefaultOpt()), + blackMongoDB, + cache.NewBlackCacheRedis(rdb, blackMongoDB, cache.GetDefaultOpt()), ), userRpcClient: &userRpcClient, notificationSender: notificationSender, RegisterCenter: client, conversationRpcClient: rpcclient.NewConversationRpcClient(client), }) + return nil } diff --git a/pkg/common/convert/friend.go b/pkg/common/convert/friend.go index 7003c8aa6..c81cd98d6 100644 --- a/pkg/common/convert/friend.go +++ b/pkg/common/convert/friend.go @@ -16,6 +16,7 @@ package convert import ( "context" + "fmt" "github.com/OpenIMSDK/protocol/sdkws" "github.com/OpenIMSDK/tools/utils" @@ -31,23 +32,22 @@ func FriendPb2DB(friend *sdkws.FriendInfo) *relation.FriendModel { return dbFriend } -func FriendDB2Pb( - ctx context.Context, - friendDB *relation.FriendModel, +func FriendDB2Pb(ctx context.Context, friendDB *relation.FriendModel, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error), ) (*sdkws.FriendInfo, error) { - pbfriend := &sdkws.FriendInfo{FriendUser: &sdkws.UserInfo{}} - utils.CopyStructFields(pbfriend, friendDB) users, err := getUsers(ctx, []string{friendDB.FriendUserID}) if err != nil { return nil, err } - pbfriend.FriendUser.UserID = users[friendDB.FriendUserID].UserID - pbfriend.FriendUser.Nickname = users[friendDB.FriendUserID].Nickname - pbfriend.FriendUser.FaceURL = users[friendDB.FriendUserID].FaceURL - pbfriend.FriendUser.Ex = users[friendDB.FriendUserID].Ex - pbfriend.CreateTime = friendDB.CreateTime.Unix() - return pbfriend, nil + user, ok := users[friendDB.FriendUserID] + if !ok { + return nil, fmt.Errorf("user not found: %s", friendDB.FriendUserID) + } + + return &sdkws.FriendInfo{ + FriendUser: user, + CreateTime: friendDB.CreateTime.Unix(), + }, nil } func FriendsDB2Pb( @@ -118,3 +118,37 @@ func FriendRequestDB2Pb( } return res, nil } + +// FriendPb2DBMap converts a FriendInfo protobuf object to a map suitable for database operations. +// It only includes non-zero or non-empty fields in the map. +func FriendPb2DBMap(friend *sdkws.FriendInfo) map[string]any { + if friend == nil { + return nil + } + + val := make(map[string]any) + + // Assuming FriendInfo has similar fields to those in FriendModel. + // Add or remove fields based on your actual FriendInfo and FriendModel structures. + if friend.FriendUser != nil { + if friend.FriendUser.UserID != "" { + val["friend_user_id"] = friend.FriendUser.UserID + } + if friend.FriendUser.Nickname != "" { + val["nickname"] = friend.FriendUser.Nickname + } + if friend.FriendUser.FaceURL != "" { + val["face_url"] = friend.FriendUser.FaceURL + } + if friend.FriendUser.Ex != "" { + val["ex"] = friend.FriendUser.Ex + } + } + if friend.CreateTime != 0 { + val["create_time"] = friend.CreateTime // You might need to convert this to a proper time format. + } + + // Include other fields from FriendInfo as needed, similar to the above pattern. + + return val +} diff --git a/pkg/common/convert/user.go b/pkg/common/convert/user.go index 782b9ab1c..8d960546a 100644 --- a/pkg/common/convert/user.go +++ b/pkg/common/convert/user.go @@ -22,31 +22,33 @@ import ( relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" ) -func UsersDB2Pb(users []*relationtb.UserModel) (result []*sdkws.UserInfo) { +func UsersDB2Pb(users []*relationtb.UserModel) []*sdkws.UserInfo { + result := make([]*sdkws.UserInfo, 0, len(users)) for _, user := range users { - var userPb sdkws.UserInfo - userPb.UserID = user.UserID - userPb.Nickname = user.Nickname - userPb.FaceURL = user.FaceURL - userPb.Ex = user.Ex - userPb.CreateTime = user.CreateTime.UnixMilli() - userPb.AppMangerLevel = user.AppMangerLevel - userPb.GlobalRecvMsgOpt = user.GlobalRecvMsgOpt - result = append(result, &userPb) + userPb := &sdkws.UserInfo{ + UserID: user.UserID, + Nickname: user.Nickname, + FaceURL: user.FaceURL, + Ex: user.Ex, + CreateTime: user.CreateTime.UnixMilli(), + AppMangerLevel: user.AppMangerLevel, + GlobalRecvMsgOpt: user.GlobalRecvMsgOpt, + } + result = append(result, userPb) } return result } func UserPb2DB(user *sdkws.UserInfo) *relationtb.UserModel { - var userDB relationtb.UserModel - userDB.UserID = user.UserID - userDB.Nickname = user.Nickname - userDB.FaceURL = user.FaceURL - userDB.Ex = user.Ex - userDB.CreateTime = time.UnixMilli(user.CreateTime) - userDB.AppMangerLevel = user.AppMangerLevel - userDB.GlobalRecvMsgOpt = user.GlobalRecvMsgOpt - return &userDB + return &relationtb.UserModel{ + UserID: user.UserID, + Nickname: user.Nickname, + FaceURL: user.FaceURL, + Ex: user.Ex, + CreateTime: time.UnixMilli(user.CreateTime), + AppMangerLevel: user.AppMangerLevel, + GlobalRecvMsgOpt: user.GlobalRecvMsgOpt, + } } func UserPb2DBMap(user *sdkws.UserInfo) map[string]any { @@ -54,20 +56,19 @@ func UserPb2DBMap(user *sdkws.UserInfo) map[string]any { return nil } val := make(map[string]any) - if user.Nickname != "" { - val["nickname"] = user.Nickname - } - if user.FaceURL != "" { - val["face_url"] = user.FaceURL - } - if user.Ex != "" { - val["ex"] = user.FaceURL - } - if user.AppMangerLevel != 0 { - val["app_manger_level"] = user.AppMangerLevel + fields := map[string]any{ + "nickname": user.Nickname, + "face_url": user.FaceURL, + "ex": user.Ex, + "app_manager_level": user.AppMangerLevel, + "global_recv_msg_opt": user.GlobalRecvMsgOpt, } - if user.GlobalRecvMsgOpt != 0 { - val["global_recv_msg_opt"] = user.GlobalRecvMsgOpt + for key, value := range fields { + if v, ok := value.(string); ok && v != "" { + val[key] = v + } else if v, ok := value.(int32); ok && v != 0 { + val[key] = v + } } return val } diff --git a/pkg/common/convert/user_test.go b/pkg/common/convert/user_test.go new file mode 100644 index 000000000..e58fa16c1 --- /dev/null +++ b/pkg/common/convert/user_test.go @@ -0,0 +1,86 @@ +// 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 convert + +import ( + "reflect" + "testing" + + "github.com/OpenIMSDK/protocol/sdkws" + relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" +) + +func TestUsersDB2Pb(t *testing.T) { + type args struct { + users []*relationtb.UserModel + } + tests := []struct { + name string + args args + wantResult []*sdkws.UserInfo + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotResult := UsersDB2Pb(tt.args.users); !reflect.DeepEqual(gotResult, tt.wantResult) { + t.Errorf("UsersDB2Pb() = %v, want %v", gotResult, tt.wantResult) + } + }) + } +} + +func TestUserPb2DB(t *testing.T) { + type args struct { + user *sdkws.UserInfo + } + tests := []struct { + name string + args args + want *relationtb.UserModel + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := UserPb2DB(tt.args.user); !reflect.DeepEqual(got, tt.want) { + t.Errorf("UserPb2DB() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestUserPb2DBMap(t *testing.T) { + user := &sdkws.UserInfo{ + Nickname: "TestUser", + FaceURL: "http://openim.io/logo.jpg", + Ex: "Extra Data", + AppMangerLevel: 1, + GlobalRecvMsgOpt: 2, + } + + expected := map[string]any{ + "nickname": "TestUser", + "face_url": "http://openim.io/logo.jpg", + "ex": "Extra Data", + "app_manager_level": int32(1), + "global_recv_msg_opt": int32(2), + } + + result := UserPb2DBMap(user) + if !reflect.DeepEqual(result, expected) { + t.Errorf("UserPb2DBMap returned unexpected map. Got %v, want %v", result, expected) + } +} diff --git a/pkg/common/db/cache/friend.go b/pkg/common/db/cache/friend.go index 64a358984..1708f7664 100644 --- a/pkg/common/db/cache/friend.go +++ b/pkg/common/db/cache/friend.go @@ -33,19 +33,20 @@ const ( friendKey = "FRIEND_INFO:" ) -// args fn will exec when no data in msgCache. +// FriendCache is an interface for caching friend-related data. type FriendCache interface { metaCache NewCache() FriendCache GetFriendIDs(ctx context.Context, ownerUserID string) (friendIDs []string, err error) - // call when friendID List changed + // Called when friendID list changed DelFriendIDs(ownerUserID ...string) FriendCache - // get single friendInfo from msgCache + // Get single friendInfo from the cache GetFriend(ctx context.Context, ownerUserID, friendUserID string) (friend *relationtb.FriendModel, err error) - // del friend when friend info changed + // Delete friend when friend info changed DelFriend(ownerUserID, friendUserID string) FriendCache } +// FriendCacheRedis is an implementation of the FriendCache interface using Redis. type FriendCacheRedis struct { metaCache friendDB relationtb.FriendModelInterface @@ -53,6 +54,7 @@ type FriendCacheRedis struct { rcClient *rockscache.Client } +// NewFriendCacheRedis creates a new instance of FriendCacheRedis. func NewFriendCacheRedis(rdb redis.UniversalClient, friendDB relationtb.FriendModelInterface, options rockscache.Options) FriendCache { rcClient := rockscache.NewClient(rdb, options) @@ -64,6 +66,7 @@ func NewFriendCacheRedis(rdb redis.UniversalClient, friendDB relationtb.FriendMo } } +// NewCache creates a new instance of FriendCacheRedis with the same configuration. func (f *FriendCacheRedis) NewCache() FriendCache { return &FriendCacheRedis{ rcClient: f.rcClient, @@ -73,24 +76,29 @@ func (f *FriendCacheRedis) NewCache() FriendCache { } } +// getFriendIDsKey returns the key for storing friend IDs in the cache. func (f *FriendCacheRedis) getFriendIDsKey(ownerUserID string) string { return friendIDsKey + ownerUserID } +// getTwoWayFriendsIDsKey returns the key for storing two-way friend IDs in the cache. func (f *FriendCacheRedis) getTwoWayFriendsIDsKey(ownerUserID string) string { return TwoWayFriendsIDsKey + ownerUserID } +// getFriendKey returns the key for storing friend info in the cache. func (f *FriendCacheRedis) getFriendKey(ownerUserID, friendUserID string) string { return friendKey + ownerUserID + "-" + friendUserID } +// GetFriendIDs retrieves friend IDs from the cache or the database if not found. func (f *FriendCacheRedis) GetFriendIDs(ctx context.Context, ownerUserID string) (friendIDs []string, err error) { return getCache(ctx, f.rcClient, f.getFriendIDsKey(ownerUserID), f.expireTime, func(ctx context.Context) ([]string, error) { return f.friendDB.FindFriendUserIDs(ctx, ownerUserID) }) } +// DelFriendIDs deletes friend IDs from the cache. func (f *FriendCacheRedis) DelFriendIDs(ownerUserIDs ...string) FriendCache { newGroupCache := f.NewCache() keys := make([]string, 0, len(ownerUserIDs)) @@ -102,7 +110,7 @@ func (f *FriendCacheRedis) DelFriendIDs(ownerUserIDs ...string) FriendCache { return newGroupCache } -// todo. +// GetTwoWayFriendIDs retrieves two-way friend IDs from the cache. func (f *FriendCacheRedis) GetTwoWayFriendIDs(ctx context.Context, ownerUserID string) (twoWayFriendIDs []string, err error) { friendIDs, err := f.GetFriendIDs(ctx, ownerUserID) if err != nil { @@ -121,6 +129,7 @@ func (f *FriendCacheRedis) GetTwoWayFriendIDs(ctx context.Context, ownerUserID s return twoWayFriendIDs, nil } +// DelTwoWayFriendIDs deletes two-way friend IDs from the cache. func (f *FriendCacheRedis) DelTwoWayFriendIDs(ctx context.Context, ownerUserID string) FriendCache { newFriendCache := f.NewCache() newFriendCache.AddKeys(f.getTwoWayFriendsIDsKey(ownerUserID)) @@ -128,14 +137,15 @@ func (f *FriendCacheRedis) DelTwoWayFriendIDs(ctx context.Context, ownerUserID s return newFriendCache } -func (f *FriendCacheRedis) GetFriend(ctx context.Context, ownerUserID, - friendUserID string) (friend *relationtb.FriendModel, err error) { +// GetFriend retrieves friend info from the cache or the database if not found. +func (f *FriendCacheRedis) GetFriend(ctx context.Context, ownerUserID, friendUserID string) (friend *relationtb.FriendModel, err error) { return getCache(ctx, f.rcClient, f.getFriendKey(ownerUserID, friendUserID), f.expireTime, func(ctx context.Context) (*relationtb.FriendModel, error) { return f.friendDB.Take(ctx, ownerUserID, friendUserID) }) } +// DelFriend deletes friend info from the cache. func (f *FriendCacheRedis) DelFriend(ownerUserID, friendUserID string) FriendCache { newFriendCache := f.NewCache() newFriendCache.AddKeys(f.getFriendKey(ownerUserID, friendUserID)) diff --git a/pkg/common/db/controller/user.go b/pkg/common/db/controller/user.go index 32501ee2b..6299e7e87 100644 --- a/pkg/common/db/controller/user.go +++ b/pkg/common/db/controller/user.go @@ -79,21 +79,35 @@ func NewUserDatabase(userDB relation.UserModelInterface, cache cache.UserCache, return &userDatabase{userDB: userDB, cache: cache, tx: tx, mongoDB: mongoDB} } -func (u *userDatabase) InitOnce(ctx context.Context, users []*relation.UserModel) (err error) { - userIDs := utils.Slice(users, func(e *relation.UserModel) string { - return e.UserID - }) - result, err := u.userDB.Find(ctx, userIDs) - if err != nil { - return err - } - miss := utils.SliceAnySub(users, result, func(e *relation.UserModel) string { return e.UserID }) - if len(miss) > 0 { - _ = u.userDB.Create(ctx, miss) - } - return nil + +func (u *userDatabase) InitOnce(ctx context.Context, users []*relation.UserModel) error { + // Extract user IDs from the given user models. + userIDs := utils.Slice(users, func(e *relation.UserModel) string { + return e.UserID + }) + + // Find existing users in the database. + existingUsers, err := u.userDB.Find(ctx, userIDs) + if err != nil { + return err + } + + // Determine which users are missing from the database. + missingUsers := utils.SliceAnySub(users, existingUsers, func(e *relation.UserModel) string { + return e.UserID + }) + + // Create records for missing users. + if len(missingUsers) > 0 { + if err := u.userDB.Create(ctx, missingUsers); err != nil { + return err + } + } + + return nil } + // FindWithError Get the information of the specified user and return an error if the userID is not found. func (u *userDatabase) FindWithError(ctx context.Context, userIDs []string) (users []*relation.UserModel, err error) { users, err = u.cache.GetUsersInfo(ctx, userIDs) diff --git a/pkg/common/db/newmgo/friend.go b/pkg/common/db/newmgo/friend.go new file mode 100644 index 000000000..b02cff7c0 --- /dev/null +++ b/pkg/common/db/newmgo/friend.go @@ -0,0 +1,169 @@ +package newmgo + +import ( + "context" + + "github.com/openimsdk/open-im-server/v3/pkg/common/db/newmgo/mgotool" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/open-im-server/v3/pkg/common/pagination" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +// 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) { + return &FriendMgo{ + coll: db.Collection(relation.FriendModelCollectionName), + }, nil +} + +// Create inserts multiple friend records. +func (f *FriendMgo) Create(ctx context.Context, friends []*relation.FriendModel) error { + return mgotool.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}, + } + _, err := f.coll.DeleteMany(ctx, filter) + if err != nil { + return err + } + return nil +} + +// 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, + } + update := bson.M{"$set": args} + err := mgotool.UpdateOne(ctx, f.coll, filter, update, true) + if err != nil { + return err + } + return nil +} + +// 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 { + args := map[string]interface{}{"remark": remark} + return f.UpdateByMap(ctx, ownerUserID, friendUserID, args) +} + +// 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, + } + friend, err := mgotool.FindOne[*relation.FriendModel](ctx, f.coll, filter) + if err != nil { + return nil, err + } + return friend, nil +} + +// 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}, + }, + } + friends, err := mgotool.Find[*relation.FriendModel](ctx, f.coll, filter) + if err != nil { + return nil, err + } + return friends, nil +} + +// 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}, + } + friends, err := mgotool.Find[*relation.FriendModel](ctx, f.coll, filter) + if err != nil { + return nil, err + } + return friends, nil +} + +// 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, + } + friends, err := mgotool.Find[*relation.FriendModel](ctx, f.coll, filter) + if err != nil { + return nil, err + } + return friends, nil +} + +// FindOwnerFriends retrieves a paginated list of friends for a given owner. +func (f *FriendMgo) FindOwnerFriends(ctx context.Context, ownerUserID string, pagination pagination.Pagination, showNumber int32) ([]*relation.FriendModel, int64, error) { + filter := bson.M{"owner_user_id": ownerUserID} + count, friends, err := mgotool.FindPage[*relation.FriendModel](ctx, f.coll, filter, pagination) + if err != nil { + return nil, 0, err + } + return friends, count, nil +} + +// 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, showNumber int32) ([]*relation.FriendModel, int64, error) { + filter := bson.M{"friend_user_id": friendUserID} + count, friends, err := mgotool.FindPage[*relation.FriendModel](ctx, f.coll, filter, pagination) + if err != nil { + return nil, 0, err + } + return friends, count, nil +} + +// 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} + friends := []*relation.FriendModel{} + friends, err := mgotool.Find[*relation.FriendModel](ctx, f.coll, filter) + if err != nil { + return nil, err + } + + friendUserIDs := make([]string, len(friends)) + for i, friend := range friends { + friendUserIDs[i] = friend.FriendUserID + } + return friendUserIDs, nil +} + +// NewTx creates a new transaction. +func (f *FriendMgo) NewTx(tx any) relation.FriendModelInterface { + panic("not implemented") + return nil +} diff --git a/pkg/common/db/newmgo/friend_request.go b/pkg/common/db/newmgo/friend_request.go new file mode 100644 index 000000000..99f4dff56 --- /dev/null +++ b/pkg/common/db/newmgo/friend_request.go @@ -0,0 +1,48 @@ +package newmgo + +import ( + "context" + + "github.com/openimsdk/open-im-server/v3/pkg/common/db/newmgo/mgotool" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" +) + +type FriendRequestMgo struct { + coll *mongo.Collection +} + +func NewFriendRequestMongo(db *mongo.Database) (relation.FriendRequestModelInterface, error) { + return &FriendRequestMgo{ + coll: db.Collection(relation.FriendRequestModelCollectionName), + }, nil +} + +func (f *FriendRequestMgo) Create(ctx context.Context, friendRequests []*relation.FriendRequestModel) error { + return mgotool.InsertMany(ctx, f.coll, friendRequests) +} + +func (f *FriendRequestMgo) Delete(ctx context.Context, fromUserID, toUserID string) (err error) { + return mgotool.Delete[*relation.FriendRequestModel](ctx, f.coll, bson.M{"from_user_id": fromUserID, "to_user_id": toUserID}) +} + +func (f *FriendRequestMgo) UpdateByMap(ctx context.Context, formUserID, toUserID string, args map[string]any) (err error) { + if len(args) == 0 { + return nil + } + return mgotool.UpdateOne(ctx, f.coll, bson.M{"from_user_id": formUserID, "to_user_id": toUserID}, bson.M{"$set": args}, true) +} + +func (f *FriendRequestMgo) Update(ctx context.Context, friendRequest *relation.FriendRequestModel) (err error) { + return mgotool.UpdateOne(ctx, f.coll, bson.M{"_id": friendRequest.ID}, bson.M{"$set": friendRequest}, true) +} + +func (f *FriendRequestMgo) Find(ctx context.Context, fromUserID, toUserID string) (friendRequest *relation.FriendRequestModel, err error) { + return mgotool.FindOne[*relation.FriendRequestModel](ctx, f.coll, bson.M{"from_user_id": fromUserID, "to_user_id": toUserID}) +} + +func (f *FriendRequestMgo) Take(ctx context.Context, fromUserID, toUserID string) (friendRequest *relation.FriendRequestModel, err error) { + return f.Find(ctx, fromUserID, toUserID) +} + diff --git a/pkg/common/db/newmgo/mgotool/tool.go b/pkg/common/db/newmgo/mgotool/tool.go index 4feb48833..c2c05949c 100644 --- a/pkg/common/db/newmgo/mgotool/tool.go +++ b/pkg/common/db/newmgo/mgotool/tool.go @@ -2,12 +2,14 @@ package mgotool import ( "context" + "github.com/OpenIMSDK/tools/errs" "github.com/openimsdk/open-im-server/v3/pkg/common/pagination" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) + func basic[T any]() bool { var t T switch any(t).(type) { @@ -175,6 +177,14 @@ func DeleteMany(ctx context.Context, coll *mongo.Collection, filter any, opts .. return nil } +// TODO +func Delete[T any](ctx context.Context, coll *mongo.Collection, filter any, opts ...*options.DeleteOptions) error { + if _, err := coll.DeleteMany(ctx, filter, opts...); err != nil { + return errs.Wrap(err) + } + return nil +} + func Aggregate[T any](ctx context.Context, coll *mongo.Collection, pipeline any, opts ...*options.AggregateOptions) ([]T, error) { cur, err := coll.Aggregate(ctx, pipeline, opts...) if err != nil { diff --git a/pkg/common/db/relation/friend_request_model.go b/pkg/common/db/relation/friend_request_model.go index 6ee25b26c..e215f8850 100644 --- a/pkg/common/db/relation/friend_request_model.go +++ b/pkg/common/db/relation/friend_request_model.go @@ -14,151 +14,141 @@ package relation -import ( - "context" - - "gorm.io/gorm" - - "github.com/OpenIMSDK/tools/utils" - - "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" -) - -type FriendRequestGorm struct { - *MetaDB -} - -func NewFriendRequestGorm(db *gorm.DB) relation.FriendRequestModelInterface { - return &FriendRequestGorm{NewMetaDB(db, &relation.FriendRequestModel{})} -} - -func (f *FriendRequestGorm) NewTx(tx any) relation.FriendRequestModelInterface { - return &FriendRequestGorm{NewMetaDB(tx.(*gorm.DB), &relation.FriendRequestModel{})} -} - -// 插入多条记录. -func (f *FriendRequestGorm) Create(ctx context.Context, friendRequests []*relation.FriendRequestModel) (err error) { - return utils.Wrap(f.db(ctx).Create(&friendRequests).Error, "") -} - -// 删除记录. -func (f *FriendRequestGorm) Delete(ctx context.Context, fromUserID, toUserID string) (err error) { - return utils.Wrap( - f.db(ctx). - Where("from_user_id = ? AND to_user_id = ?", fromUserID, toUserID). - Delete(&relation.FriendRequestModel{}). - Error, - "", - ) -} - -// 更新零值. -func (f *FriendRequestGorm) UpdateByMap( - ctx context.Context, - fromUserID string, - toUserID string, - args map[string]any, -) (err error) { - return utils.Wrap( - f.db(ctx). - Model(&relation.FriendRequestModel{}). - Where("from_user_id = ? AND to_user_id =?", fromUserID, toUserID). - Updates(args). - Error, - "", - ) -} - -// 更新记录 (非零值). -func (f *FriendRequestGorm) Update(ctx context.Context, friendRequest *relation.FriendRequestModel) (err error) { - fr2 := *friendRequest - fr2.FromUserID = "" - fr2.ToUserID = "" - return utils.Wrap( - f.db(ctx). - Where("from_user_id = ? AND to_user_id =?", friendRequest.FromUserID, friendRequest.ToUserID). - Updates(fr2). - Error, - "", - ) -} - -// 获取来指定用户的好友申请 未找到 不返回错误. -func (f *FriendRequestGorm) Find( - ctx context.Context, - fromUserID, toUserID string, -) (friendRequest *relation.FriendRequestModel, err error) { - friendRequest = &relation.FriendRequestModel{} - err = utils.Wrap( - f.db(ctx).Where("from_user_id = ? and to_user_id = ?", fromUserID, toUserID).Find(friendRequest).Error, - "", - ) - return friendRequest, err -} - -func (f *FriendRequestGorm) Take( - ctx context.Context, - fromUserID, toUserID string, -) (friendRequest *relation.FriendRequestModel, err error) { - friendRequest = &relation.FriendRequestModel{} - err = utils.Wrap( - f.db(ctx).Where("from_user_id = ? and to_user_id = ?", fromUserID, toUserID).Take(friendRequest).Error, - "", - ) - return friendRequest, err -} - -// 获取toUserID收到的好友申请列表. -func (f *FriendRequestGorm) FindToUserID( - ctx context.Context, - toUserID string, - pageNumber, showNumber int32, -) (friendRequests []*relation.FriendRequestModel, total int64, err error) { - err = f.db(ctx).Model(&relation.FriendRequestModel{}).Where("to_user_id = ? ", toUserID).Count(&total).Error - if err != nil { - return nil, 0, utils.Wrap(err, "") - } - err = utils.Wrap( - f.db(ctx). - Where("to_user_id = ? ", toUserID). - Limit(int(showNumber)). - Offset(int(pageNumber-1)*int(showNumber)). - Find(&friendRequests). - Error, - "", - ) - return -} - -// 获取fromUserID发出去的好友申请列表. -func (f *FriendRequestGorm) FindFromUserID( - ctx context.Context, - fromUserID string, - pageNumber, showNumber int32, -) (friendRequests []*relation.FriendRequestModel, total int64, err error) { - err = f.db(ctx).Model(&relation.FriendRequestModel{}).Where("from_user_id = ? ", fromUserID).Count(&total).Error - if err != nil { - return nil, 0, utils.Wrap(err, "") - } - err = utils.Wrap( - f.db(ctx). - Where("from_user_id = ? ", fromUserID). - Limit(int(showNumber)). - Offset(int(pageNumber-1)*int(showNumber)). - Find(&friendRequests). - Error, - "", - ) - return -} - -func (f *FriendRequestGorm) FindBothFriendRequests(ctx context.Context, fromUserID, toUserID string) (friends []*relation.FriendRequestModel, err error) { - err = utils.Wrap( - f.db(ctx). - Where("(from_user_id = ? AND to_user_id = ?) OR (from_user_id = ? AND to_user_id = ?)", fromUserID, toUserID, toUserID, fromUserID). - Find(&friends). - Error, - "", - ) - return -} +// type FriendRequestGorm struct { +// *MetaDB +// } + +// func NewFriendRequestGorm(db *gorm.DB) relation.FriendRequestModelInterface { +// return &FriendRequestGorm{NewMetaDB(db, &relation.FriendRequestModel{})} +// } + +// func (f *FriendRequestGorm) NewTx(tx any) relation.FriendRequestModelInterface { +// return &FriendRequestGorm{NewMetaDB(tx.(*gorm.DB), &relation.FriendRequestModel{})} +// } + +// // 插入多条记录. +// func (f *FriendRequestGorm) Create(ctx context.Context, friendRequests []*relation.FriendRequestModel) (err error) { +// return utils.Wrap(f.db(ctx).Create(&friendRequests).Error, "") +// } + +// // 删除记录. +// func (f *FriendRequestGorm) Delete(ctx context.Context, fromUserID, toUserID string) (err error) { +// return utils.Wrap( +// f.db(ctx). +// Where("from_user_id = ? AND to_user_id = ?", fromUserID, toUserID). +// Delete(&relation.FriendRequestModel{}). +// Error, +// "", +// ) +// } + +// // 更新零值. +// func (f *FriendRequestGorm) UpdateByMap( +// ctx context.Context, +// fromUserID string, +// toUserID string, +// args map[string]any, +// ) (err error) { +// return utils.Wrap( +// f.db(ctx). +// Model(&relation.FriendRequestModel{}). +// Where("from_user_id = ? AND to_user_id =?", fromUserID, toUserID). +// Updates(args). +// Error, +// "", +// ) +// } + +// // 更新记录 (非零值). +// func (f *FriendRequestGorm) Update(ctx context.Context, friendRequest *relation.FriendRequestModel) (err error) { +// fr2 := *friendRequest +// fr2.FromUserID = "" +// fr2.ToUserID = "" +// return utils.Wrap( +// f.db(ctx). +// Where("from_user_id = ? AND to_user_id =?", friendRequest.FromUserID, friendRequest.ToUserID). +// Updates(fr2). +// Error, +// "", +// ) +// } + +// // 获取来指定用户的好友申请 未找到 不返回错误. +// func (f *FriendRequestGorm) Find( +// ctx context.Context, +// fromUserID, toUserID string, +// ) (friendRequest *relation.FriendRequestModel, err error) { +// friendRequest = &relation.FriendRequestModel{} +// err = utils.Wrap( +// f.db(ctx).Where("from_user_id = ? and to_user_id = ?", fromUserID, toUserID).Find(friendRequest).Error, +// "", +// ) +// return friendRequest, err +// } + +// func (f *FriendRequestGorm) Take( +// ctx context.Context, +// fromUserID, toUserID string, +// ) (friendRequest *relation.FriendRequestModel, err error) { +// friendRequest = &relation.FriendRequestModel{} +// err = utils.Wrap( +// f.db(ctx).Where("from_user_id = ? and to_user_id = ?", fromUserID, toUserID).Take(friendRequest).Error, +// "", +// ) +// return friendRequest, err +// } + +// // 获取toUserID收到的好友申请列表. +// func (f *FriendRequestGorm) FindToUserID( +// ctx context.Context, +// toUserID string, +// pageNumber, showNumber int32, +// ) (friendRequests []*relation.FriendRequestModel, total int64, err error) { +// err = f.db(ctx).Model(&relation.FriendRequestModel{}).Where("to_user_id = ? ", toUserID).Count(&total).Error +// if err != nil { +// return nil, 0, utils.Wrap(err, "") +// } +// err = utils.Wrap( +// f.db(ctx). +// Where("to_user_id = ? ", toUserID). +// Limit(int(showNumber)). +// Offset(int(pageNumber-1)*int(showNumber)). +// Find(&friendRequests). +// Error, +// "", +// ) +// return +// } + +// // 获取fromUserID发出去的好友申请列表. +// func (f *FriendRequestGorm) FindFromUserID( +// ctx context.Context, +// fromUserID string, +// pageNumber, showNumber int32, +// ) (friendRequests []*relation.FriendRequestModel, total int64, err error) { +// err = f.db(ctx).Model(&relation.FriendRequestModel{}).Where("from_user_id = ? ", fromUserID).Count(&total).Error +// if err != nil { +// return nil, 0, utils.Wrap(err, "") +// } +// err = utils.Wrap( +// f.db(ctx). +// Where("from_user_id = ? ", fromUserID). +// Limit(int(showNumber)). +// Offset(int(pageNumber-1)*int(showNumber)). +// Find(&friendRequests). +// Error, +// "", +// ) +// return +// } + +// func (f *FriendRequestGorm) FindBothFriendRequests(ctx context.Context, fromUserID, toUserID string) (friends []*relation.FriendRequestModel, err error) { +// err = utils.Wrap( +// f.db(ctx). +// Where("(from_user_id = ? AND to_user_id = ?) OR (from_user_id = ? AND to_user_id = ?)", fromUserID, toUserID, toUserID, fromUserID). +// Find(&friends). +// Error, +// "", +// ) +// return +// } diff --git a/pkg/common/db/table/relation/friend.go b/pkg/common/db/table/relation/friend.go index 24aac756e..481b23525 100644 --- a/pkg/common/db/table/relation/friend.go +++ b/pkg/common/db/table/relation/friend.go @@ -17,62 +17,65 @@ package relation import ( "context" "time" + + "go.mongodb.org/mongo-driver/bson/primitive" ) const ( - FriendModelTableName = "friends" + FriendModelCollectionName = "friends" ) +// OwnerUserID string `gorm:"column:owner_user_id;primary_key;size:64"` +// FriendUserID string `gorm:"column:friend_user_id;primary_key;size:64"` +// Remark string `gorm:"column:remark;size:255"` +// CreateTime time.Time `gorm:"column:create_time;autoCreateTime"` +// AddSource int32 `gorm:"column:add_source"` +// OperatorUserID string `gorm:"column:operator_user_id;size:64"` +// Ex string `gorm:"column:ex;size:1024"` + +// FriendModel represents the data structure for a friend relationship in MongoDB. type FriendModel struct { - OwnerUserID string `gorm:"column:owner_user_id;primary_key;size:64"` - FriendUserID string `gorm:"column:friend_user_id;primary_key;size:64"` - Remark string `gorm:"column:remark;size:255"` - CreateTime time.Time `gorm:"column:create_time;autoCreateTime"` - AddSource int32 `gorm:"column:add_source"` - OperatorUserID string `gorm:"column:operator_user_id;size:64"` - Ex string `gorm:"column:ex;size:1024"` + ID primitive.ObjectID `bson:"_id,omitempty"` + OwnerUserID string `bson:"owner_user_id"` + FriendUserID string `bson:"friend_user_id"` + Remark string `bson:"remark"` + CreateTime time.Time `bson:"create_time"` + AddSource int32 `bson:"add_source"` + OperatorUserID string `bson:"operator_user_id"` + Ex string `bson:"ex"` } -func (FriendModel) TableName() string { - return FriendModelTableName +// CollectionName returns the name of the MongoDB collection. +func (FriendModel) CollectionName() string { + return FriendModelCollectionName } +// FriendModelInterface defines the operations for managing friends in MongoDB. type FriendModelInterface interface { - // 插入多条记录 + // Create inserts multiple friend records. Create(ctx context.Context, friends []*FriendModel) (err error) - // 删除ownerUserID指定的好友 + // Delete removes specified friends of the owner user. Delete(ctx context.Context, ownerUserID string, friendUserIDs []string) (err error) - // 更新ownerUserID单个好友信息 更新零值 + // UpdateByMap updates specific fields of a friend document using a map. UpdateByMap(ctx context.Context, ownerUserID string, friendUserID string, args map[string]any) (err error) - // 更新好友信息的非零值 - Update(ctx context.Context, friends []*FriendModel) (err error) - // 更新好友备注(也支持零值 ) + // Update modifies multiple friend documents. + // Update(ctx context.Context, friends []*FriendModel) (err error) + // UpdateRemark updates the remark for a specific friend. UpdateRemark(ctx context.Context, ownerUserID, friendUserID, remark string) (err error) - // 获取单个好友信息,如没找到 返回错误 + // Take retrieves a single friend document. Returns an error if not found. Take(ctx context.Context, ownerUserID, friendUserID string) (friend *FriendModel, err error) - // 查找好友关系,如果是双向关系,则都返回 + // FindUserState finds the friendship status between two users. FindUserState(ctx context.Context, userID1, userID2 string) (friends []*FriendModel, err error) - // 获取 owner指定的好友列表 如果有friendUserIDs不存在,也不返回错误 + // FindFriends retrieves a list of friends for a given owner. Missing friends do not cause an error. FindFriends(ctx context.Context, ownerUserID string, friendUserIDs []string) (friends []*FriendModel, err error) - // 获取哪些人添加了friendUserID 如果有ownerUserIDs不存在,也不返回错误 - FindReversalFriends( - ctx context.Context, - friendUserID string, - ownerUserIDs []string, - ) (friends []*FriendModel, err error) - // 获取ownerUserID好友列表 支持翻页 - FindOwnerFriends( - ctx context.Context, - ownerUserID string, - pageNumber, showNumber int32, - ) (friends []*FriendModel, total int64, err error) - // 获取哪些人添加了friendUserID 支持翻页 - FindInWhoseFriends( - ctx context.Context, - friendUserID string, - pageNumber, showNumber int32, - ) (friends []*FriendModel, total int64, err error) - // 获取好友UserID列表 + // FindReversalFriends finds users who have added the specified user as a friend. + FindReversalFriends(ctx context.Context, friendUserID string, ownerUserIDs []string) (friends []*FriendModel, err error) + // FindOwnerFriends retrieves a paginated list of friends for a given owner. + FindOwnerFriends(ctx context.Context, ownerUserID string, pageNumber, showNumber int32) (friends []*FriendModel, total int64, err error) + // FindInWhoseFriends finds users who have added the specified user as a friend, with pagination. + FindInWhoseFriends(ctx context.Context, friendUserID string, pageNumber, showNumber int32) (friends []*FriendModel, total int64, err error) + // FindFriendUserIDs retrieves a list of friend user IDs for a given owner. FindFriendUserIDs(ctx context.Context, ownerUserID string) (friendUserIDs []string, err error) + // NewTx creates a new transaction. NewTx(tx any) FriendModelInterface } diff --git a/pkg/common/db/table/relation/friend_request.go b/pkg/common/db/table/relation/friend_request.go index e101aee6a..90d4a7b72 100644 --- a/pkg/common/db/table/relation/friend_request.go +++ b/pkg/common/db/table/relation/friend_request.go @@ -17,50 +17,47 @@ package relation import ( "context" "time" + + "go.mongodb.org/mongo-driver/bson/primitive" ) -const FriendRequestModelTableName = "friend_requests" +const FriendRequestModelCollectionName = "friend_requests" type FriendRequestModel struct { - FromUserID string `gorm:"column:from_user_id;primary_key;size:64"` - ToUserID string `gorm:"column:to_user_id;primary_key;size:64"` - HandleResult int32 `gorm:"column:handle_result"` - ReqMsg string `gorm:"column:req_msg;size:255"` - CreateTime time.Time `gorm:"column:create_time; autoCreateTime"` - HandlerUserID string `gorm:"column:handler_user_id;size:64"` - HandleMsg string `gorm:"column:handle_msg;size:255"` - HandleTime time.Time `gorm:"column:handle_time"` - Ex string `gorm:"column:ex;size:1024"` + ID primitive.ObjectID `bson:"_id,omitempty"` + FromUserID string `bson:"from_user_id"` + ToUserID string `bson:"to_user_id"` + HandleResult int32 `bson:"handle_result"` + ReqMsg string `bson:"req_msg"` + CreateTime time.Time `bson:"create_time"` + HandlerUserID string `bson:"handler_user_id"` + HandleMsg string `bson:"handle_msg"` + HandleTime time.Time `bson:"handle_time"` + Ex string `bson:"ex"` } -func (FriendRequestModel) TableName() string { - return FriendRequestModelTableName +func (FriendRequestModel) CollectionName() string { + return FriendRequestModelCollectionName } type FriendRequestModelInterface interface { - // 插入多条记录 + // Insert multiple records Create(ctx context.Context, friendRequests []*FriendRequestModel) (err error) - // 删除记录 + // Delete record Delete(ctx context.Context, fromUserID, toUserID string) (err error) - // 更新零值 + // Update with zero values UpdateByMap(ctx context.Context, formUserID string, toUserID string, args map[string]any) (err error) - // 更新多条记录 (非零值) + // Update multiple records (non-zero values) Update(ctx context.Context, friendRequest *FriendRequestModel) (err error) - // 获取来指定用户的好友申请 未找到 不返回错误 + // Get friend requests sent to a specific user, no error returned if not found Find(ctx context.Context, fromUserID, toUserID string) (friendRequest *FriendRequestModel, err error) Take(ctx context.Context, fromUserID, toUserID string) (friendRequest *FriendRequestModel, err error) - // 获取toUserID收到的好友申请列表 - FindToUserID( - ctx context.Context, - toUserID string, - pageNumber, showNumber int32, - ) (friendRequests []*FriendRequestModel, total int64, err error) - // 获取fromUserID发出去的好友申请列表 - FindFromUserID( - ctx context.Context, - fromUserID string, - pageNumber, showNumber int32, - ) (friendRequests []*FriendRequestModel, total int64, err error) + // Get list of friend requests received by toUserID + FindToUserID(ctx context.Context,toUserID string,pageNumber, showNumber int32,) (friendRequests []*FriendRequestModel, total int64, err error) + // Get list of friend requests sent by fromUserID + FindFromUserID(ctx context.Context,fromUserID string,pageNumber, showNumber int32,) (friendRequests []*FriendRequestModel, total int64, err error) FindBothFriendRequests(ctx context.Context, fromUserID, toUserID string) (friends []*FriendRequestModel, err error) NewTx(tx any) FriendRequestModelInterface + // Check if the record exists + Exist(ctx context.Context, userID string) (exist bool, err error) }