package user import ( "context" "time" modelUser "github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" "gorm.io/gorm/clause" ) type accountMergeResult struct { OwnerUserID int64 DeviceUserID int64 MovedDevices []modelUser.Device RemovedSubscribes []modelUser.Subscribe } type accountMergeHelper struct { ctx context.Context svcCtx *svc.ServiceContext } func newAccountMergeHelper(ctx context.Context, svcCtx *svc.ServiceContext) *accountMergeHelper { return &accountMergeHelper{ ctx: ctx, svcCtx: svcCtx, } } func (h *accountMergeHelper) mergeIntoOwner(ownerUserID, deviceUserID int64, source string) (*accountMergeResult, error) { if ownerUserID == 0 || deviceUserID == 0 { return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "merge user id is empty") } if ownerUserID == deviceUserID { return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "cannot merge same user id") } result := &accountMergeResult{ OwnerUserID: ownerUserID, DeviceUserID: deviceUserID, } err := h.svcCtx.DB.WithContext(h.ctx).Transaction(func(tx *gorm.DB) error { var owner modelUser.User if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where("id = ?", ownerUserID). First(&owner).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "owner user %d not found", ownerUserID) } return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query owner user failed") } var device modelUser.User if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}). Where("id = ?", deviceUserID). First(&device).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "device user %d not found", deviceUserID) } return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query device user failed") } exitHelper := newFamilyExitHelper(h.ctx, h.svcCtx) if err := exitHelper.removeUserFromActiveFamily(tx, deviceUserID, false); err != nil { return err } removedSubscribes, err := clearMemberSubscribes(tx, deviceUserID) if err != nil { return err } result.RemovedSubscribes = removedSubscribes var devices []modelUser.Device if err := tx.Where("user_id = ?", deviceUserID).Find(&devices).Error; err != nil { return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query device list failed") } if len(devices) > 0 { if err := tx.Model(&modelUser.Device{}). Where("user_id = ?", deviceUserID). Updates(map[string]interface{}{ "user_id": ownerUserID, "updated_at": time.Now(), }).Error; err != nil { return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device ownership failed") } } result.MovedDevices = devices if err := tx.Model(&modelUser.AuthMethods{}). Where("user_id = ?", deviceUserID). Update("user_id", ownerUserID).Error; err != nil { return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "migrate auth methods failed") } if err := tx.Model(&modelUser.User{}). Where("id = ?", deviceUserID). Update("enable", false).Error; err != nil { return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "disable device user failed") } if err := tx.Where("id = ?", deviceUserID).Delete(&modelUser.User{}).Error; err != nil { return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "soft delete device user failed") } return nil }) if err != nil { return nil, err } if err := h.clearCaches(result); err != nil { return nil, err } logger.WithContext(h.ctx).Infow("device account merged into owner", logger.Field("owner_user_id", ownerUserID), logger.Field("device_user_id", deviceUserID), logger.Field("moved_devices", len(result.MovedDevices)), logger.Field("removed_subscribes", len(result.RemovedSubscribes)), logger.Field("source", source), ) return result, nil } func (h *accountMergeHelper) clearCaches(result *accountMergeResult) error { if result == nil { return nil } if err := h.svcCtx.UserModel.ClearUserCache(h.ctx, &modelUser.User{Id: result.OwnerUserID}, &modelUser.User{Id: result.DeviceUserID}, ); err != nil { return err } if len(result.MovedDevices) > 0 { deviceModels := make([]*modelUser.Device, 0, len(result.MovedDevices)) for i := range result.MovedDevices { device := result.MovedDevices[i] deviceModels = append(deviceModels, &device) } if err := h.svcCtx.UserModel.ClearDeviceCache(h.ctx, deviceModels...); err != nil { return err } } if len(result.RemovedSubscribes) > 0 { familyHelper := newFamilyBindingHelper(h.ctx, h.svcCtx) if err := familyHelper.clearRemovedMemberSubscribeCache(result.RemovedSubscribes); err != nil { return err } } return nil }