256 lines
8.2 KiB
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
|
|
}
|