hi-server/internal/logic/public/user/familyBindingHelper.go

256 lines
8.2 KiB
Go

package user
import (
"context"
"time"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type familyJoinResult struct {
FamilyId int64
OwnerUserId int64
}
type familyBindingHelper struct {
ctx context.Context
svcCtx *svc.ServiceContext
}
func newFamilyBindingHelper(ctx context.Context, svcCtx *svc.ServiceContext) *familyBindingHelper {
return &familyBindingHelper{
ctx: ctx,
svcCtx: svcCtx,
}
}
func (h *familyBindingHelper) getUserEmailMethod(userId int64) (*user.AuthMethods, error) {
method, err := h.svcCtx.UserModel.FindUserAuthMethodByUserId(h.ctx, "email", userId)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query user email auth method failed")
}
return method, nil
}
func validateMemberJoinConflict(ownerFamilyId int64, memberRecord *user.UserFamilyMember) error {
if memberRecord == nil {
return nil
}
if ownerFamilyId != 0 && memberRecord.FamilyId == ownerFamilyId {
if memberRecord.Status == user.FamilyMemberActive {
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyAlreadyBound), "user already in this family")
}
return nil
}
if memberRecord.Status == user.FamilyMemberActive {
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyCrossBindForbidden), "user already belongs to another family")
}
return nil
}
func (h *familyBindingHelper) validateJoinFamily(ownerUserId, memberUserId int64) error {
if ownerUserId == memberUserId {
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyAlreadyBound), "user already bound to this family")
}
var ownerFamily user.UserFamily
err := h.svcCtx.DB.WithContext(h.ctx).
Unscoped().
Model(&user.UserFamily{}).
Where("owner_user_id = ?", ownerUserId).
First(&ownerFamily).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query owner family failed")
}
var memberRecord user.UserFamilyMember
memberExists := false
err = h.svcCtx.DB.WithContext(h.ctx).
Unscoped().
Model(&user.UserFamilyMember{}).
Where("user_id = ?", memberUserId).
First(&memberRecord).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query member family relation failed")
}
if err == nil {
memberExists = true
}
if memberExists {
if err = validateMemberJoinConflict(ownerFamily.Id, &memberRecord); err != nil {
return err
}
}
if ownerFamily.Id == 0 {
return nil
}
var activeCount int64
if err = h.svcCtx.DB.WithContext(h.ctx).
Model(&user.UserFamilyMember{}).
Where("family_id = ? AND status = ?", ownerFamily.Id, user.FamilyMemberActive).
Count(&activeCount).Error; err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "count family members failed")
}
if activeCount >= ownerFamily.MaxMembers {
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyMemberLimitExceeded), "family member limit exceeded")
}
return nil
}
func (h *familyBindingHelper) joinFamily(ownerUserId, memberUserId int64, source string) (*familyJoinResult, error) {
if ownerUserId == memberUserId {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.FamilyAlreadyBound), "user already bound to this family")
}
result := &familyJoinResult{
OwnerUserId: ownerUserId,
}
err := h.svcCtx.DB.WithContext(h.ctx).Transaction(func(tx *gorm.DB) error {
ownerFamily, err := h.getOrCreateOwnerFamily(tx, ownerUserId)
if err != nil {
return err
}
result.FamilyId = ownerFamily.Id
if err = h.ensureOwnerMember(tx, ownerFamily.Id, ownerUserId); err != nil {
return err
}
var memberRecord user.UserFamilyMember
err = tx.Unscoped().Model(&user.UserFamilyMember{}).Where("user_id = ?", memberUserId).First(&memberRecord).Error
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query member family relation failed")
}
memberExists := err == nil
if memberExists {
if err = validateMemberJoinConflict(ownerFamily.Id, &memberRecord); err != nil {
return err
}
}
var activeCount int64
if err = tx.Model(&user.UserFamilyMember{}).
Where("family_id = ? AND status = ?", ownerFamily.Id, user.FamilyMemberActive).
Count(&activeCount).Error; err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "count family members failed")
}
if activeCount >= ownerFamily.MaxMembers {
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyMemberLimitExceeded), "family member limit exceeded")
}
now := time.Now()
if !memberExists {
memberRecord = user.UserFamilyMember{
FamilyId: ownerFamily.Id,
UserId: memberUserId,
Role: user.FamilyRoleMember,
Status: user.FamilyMemberActive,
JoinSource: source,
JoinedAt: now,
}
if err = tx.Create(&memberRecord).Error; err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create family member failed")
}
return nil
}
if memberRecord.FamilyId != ownerFamily.Id {
memberRecord.FamilyId = ownerFamily.Id
}
memberRecord.Status = user.FamilyMemberActive
memberRecord.Role = user.FamilyRoleMember
memberRecord.JoinSource = source
memberRecord.JoinedAt = now
memberRecord.LeftAt = nil
memberRecord.DeletedAt = gorm.DeletedAt{}
if err = tx.Unscoped().Save(&memberRecord).Error; err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update family member failed")
}
return nil
})
if err != nil {
return nil, err
}
return result, nil
}
func (h *familyBindingHelper) getOrCreateOwnerFamily(tx *gorm.DB, ownerUserId int64) (*user.UserFamily, error) {
var ownerFamily user.UserFamily
err := tx.Unscoped().Clauses(clause.Locking{Strength: "UPDATE"}).
Model(&user.UserFamily{}).
Where("owner_user_id = ?", ownerUserId).
First(&ownerFamily).Error
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query owner family failed")
}
ownerFamily = user.UserFamily{
OwnerUserId: ownerUserId,
MaxMembers: user.DefaultFamilyMaxSize,
Status: user.FamilyStatusActive,
}
if err = tx.Create(&ownerFamily).Error; err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create owner family failed")
}
} else if ownerFamily.Status != user.FamilyStatusActive || ownerFamily.DeletedAt.Valid {
ownerFamily.Status = user.FamilyStatusActive
ownerFamily.DeletedAt = gorm.DeletedAt{}
if err = tx.Unscoped().Save(&ownerFamily).Error; err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "activate owner family failed")
}
}
return &ownerFamily, nil
}
func (h *familyBindingHelper) ensureOwnerMember(tx *gorm.DB, familyId, ownerUserId int64) error {
var ownerMember user.UserFamilyMember
err := tx.Unscoped().Model(&user.UserFamilyMember{}).Where("user_id = ?", ownerUserId).First(&ownerMember).Error
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query owner family member failed")
}
ownerMember = user.UserFamilyMember{
FamilyId: familyId,
UserId: ownerUserId,
Role: user.FamilyRoleOwner,
Status: user.FamilyMemberActive,
JoinSource: "owner_init",
JoinedAt: time.Now(),
}
if err = tx.Create(&ownerMember).Error; err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create owner family member failed")
}
return nil
}
if ownerMember.FamilyId != familyId {
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyCrossBindForbidden), "owner already belongs to another family")
}
if ownerMember.Status != user.FamilyMemberActive || ownerMember.Role != user.FamilyRoleOwner || ownerMember.DeletedAt.Valid {
ownerMember.Status = user.FamilyMemberActive
ownerMember.Role = user.FamilyRoleOwner
ownerMember.LeftAt = nil
ownerMember.DeletedAt = gorm.DeletedAt{}
if err = tx.Unscoped().Save(&ownerMember).Error; err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update owner family member failed")
}
}
return nil
}