This commit is contained in:
@@ -3,11 +3,13 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
userModel "github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DeleteUserSubscribeLogic struct {
|
||||
@@ -27,13 +29,17 @@ func NewDeleteUserSubscribeLogic(ctx context.Context, svcCtx *svc.ServiceContext
|
||||
|
||||
func (l *DeleteUserSubscribeLogic) DeleteUserSubscribe(req *types.DeleteUserSubscribeRequest) error {
|
||||
// find user subscribe by ID
|
||||
userSubscribe, err := l.svcCtx.UserModel.FindOneSubscribe(l.ctx, req.UserSubscribeId)
|
||||
userSubscribe := &userModel.Subscribe{}
|
||||
err := l.svcCtx.DB.WithContext(l.ctx).Model(&userModel.Subscribe{}).Where("id = ?", req.UserSubscribeId).First(userSubscribe).Error
|
||||
if err != nil {
|
||||
l.Errorw("failed to find user subscribe", logger.Field("error", err.Error()), logger.Field("userSubscribeId", req.UserSubscribeId))
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.Wrapf(xerr.NewErrCodeMsg(xerr.InvalidParams, "user subscribe not found"), "failed to find user subscribe: %v", err.Error())
|
||||
}
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "failed to find user subscribe: %v", err.Error())
|
||||
}
|
||||
|
||||
err = l.svcCtx.UserModel.DeleteSubscribeById(l.ctx, req.UserSubscribeId)
|
||||
err = l.svcCtx.DB.WithContext(l.ctx).Where("id = ?", req.UserSubscribeId).Delete(&userModel.Subscribe{}).Error
|
||||
if err != nil {
|
||||
l.Errorw("failed to delete user subscribe", logger.Field("error", err.Error()), logger.Field("userSubscribeId", req.UserSubscribeId))
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "failed to delete user subscribe: %v", err.Error())
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
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/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type DissolveFamilyLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logger.Logger
|
||||
}
|
||||
|
||||
func NewDissolveFamilyLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DissolveFamilyLogic {
|
||||
return &DissolveFamilyLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logger.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DissolveFamilyLogic) DissolveFamily(req *types.DissolveFamilyRequest) error {
|
||||
var family modelUser.UserFamily
|
||||
err := l.svcCtx.DB.WithContext(l.ctx).
|
||||
Where("id = ? AND deleted_at IS NULL", req.FamilyId).
|
||||
First(&family).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyNotExist), "family does not exist")
|
||||
}
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query family failed")
|
||||
}
|
||||
if family.Status != modelUser.FamilyStatusActive {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyStatusInvalid), "family status is invalid")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
transactionErr := l.svcCtx.DB.WithContext(l.ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err = tx.Model(&modelUser.UserFamilyMember{}).
|
||||
Where("family_id = ? AND deleted_at IS NULL AND status = ?", req.FamilyId, modelUser.FamilyMemberActive).
|
||||
Updates(map[string]interface{}{
|
||||
"status": modelUser.FamilyMemberRemoved,
|
||||
"left_at": now,
|
||||
}).Error; err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "remove family members failed")
|
||||
}
|
||||
|
||||
if err = tx.Model(&modelUser.UserFamily{}).
|
||||
Where("id = ?", req.FamilyId).
|
||||
Update("status", 0).Error; err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "disable family failed")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if transactionErr != nil {
|
||||
return transactionErr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func mapFamilyStatus(status uint8) string {
|
||||
if status == user.FamilyStatusActive {
|
||||
return "active"
|
||||
}
|
||||
return "disabled"
|
||||
}
|
||||
|
||||
func mapFamilyRoleName(role uint8) string {
|
||||
switch role {
|
||||
case user.FamilyRoleOwner:
|
||||
return "owner"
|
||||
case user.FamilyRoleMember:
|
||||
return "member"
|
||||
default:
|
||||
return fmt.Sprintf("role_%d", role)
|
||||
}
|
||||
}
|
||||
|
||||
func mapFamilyMemberStatusName(status uint8) string {
|
||||
switch status {
|
||||
case user.FamilyMemberActive:
|
||||
return "active"
|
||||
case user.FamilyMemberLeft:
|
||||
return "left"
|
||||
case user.FamilyMemberRemoved:
|
||||
return "removed"
|
||||
default:
|
||||
return fmt.Sprintf("status_%d", status)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeFamilyStatusInput(status string) (uint8, bool) {
|
||||
switch strings.ToLower(strings.TrimSpace(status)) {
|
||||
case "", "all":
|
||||
return 0, false
|
||||
case "active", "1":
|
||||
return user.FamilyStatusActive, true
|
||||
case "disabled", "0":
|
||||
return 0, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
func findUserIdentifiers(ctx context.Context, db *gorm.DB, userIDs []int64) (map[int64]string, error) {
|
||||
identifierMap := make(map[int64]string)
|
||||
if len(userIDs) == 0 {
|
||||
return identifierMap, nil
|
||||
}
|
||||
|
||||
type authRow struct {
|
||||
UserId int64
|
||||
AuthType string
|
||||
AuthIdentifier string
|
||||
}
|
||||
|
||||
var rows []authRow
|
||||
err := db.WithContext(ctx).
|
||||
Table("user_auth_methods").
|
||||
Select("user_id, auth_type, auth_identifier").
|
||||
Where("user_id IN ? AND deleted_at IS NULL", userIDs).
|
||||
Scan(&rows).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
priority := map[string]int{
|
||||
"email": 1,
|
||||
"mobile": 2,
|
||||
"telegram": 3,
|
||||
"device": 4,
|
||||
}
|
||||
selectedPriority := make(map[int64]int, len(userIDs))
|
||||
|
||||
for _, row := range rows {
|
||||
currentPriority := priority[row.AuthType]
|
||||
if currentPriority == 0 {
|
||||
currentPriority = 100
|
||||
}
|
||||
if previous, exists := selectedPriority[row.UserId]; exists && previous <= currentPriority {
|
||||
continue
|
||||
}
|
||||
selectedPriority[row.UserId] = currentPriority
|
||||
identifierMap[row.UserId] = row.AuthIdentifier
|
||||
}
|
||||
|
||||
return identifierMap, nil
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
modelUser "github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type GetFamilyDetailLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logger.Logger
|
||||
}
|
||||
|
||||
func NewGetFamilyDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFamilyDetailLogic {
|
||||
return &GetFamilyDetailLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logger.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetFamilyDetailLogic) GetFamilyDetail(req *types.GetFamilyDetailRequest) (*types.FamilyDetail, error) {
|
||||
var family modelUser.UserFamily
|
||||
err := l.svcCtx.DB.WithContext(l.ctx).
|
||||
Where("id = ? AND deleted_at IS NULL", req.Id).
|
||||
First(&family).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.FamilyNotExist), "family does not exist")
|
||||
}
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query family detail failed")
|
||||
}
|
||||
|
||||
var members []modelUser.UserFamilyMember
|
||||
if err = l.svcCtx.DB.WithContext(l.ctx).
|
||||
Where("family_id = ? AND deleted_at IS NULL", family.Id).
|
||||
Order("joined_at DESC").
|
||||
Find(&members).Error; err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query family members failed")
|
||||
}
|
||||
|
||||
userIDs := make([]int64, 0, len(members)+1)
|
||||
userIDs = append(userIDs, family.OwnerUserId)
|
||||
activeMemberCount := int64(0)
|
||||
for _, member := range members {
|
||||
userIDs = append(userIDs, member.UserId)
|
||||
if member.Status == modelUser.FamilyMemberActive {
|
||||
activeMemberCount++
|
||||
}
|
||||
}
|
||||
|
||||
identifierMap, identifierErr := findUserIdentifiers(l.ctx, l.svcCtx.DB, userIDs)
|
||||
if identifierErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query family member identifiers failed")
|
||||
}
|
||||
|
||||
memberItems := make([]types.FamilyMemberItem, 0, len(members))
|
||||
for _, member := range members {
|
||||
identifier := identifierMap[member.UserId]
|
||||
if identifier == "" {
|
||||
identifier = strconv.FormatInt(member.UserId, 10)
|
||||
}
|
||||
|
||||
memberItem := types.FamilyMemberItem{
|
||||
UserId: member.UserId,
|
||||
Identifier: identifier,
|
||||
Role: member.Role,
|
||||
RoleName: mapFamilyRoleName(member.Role),
|
||||
Status: member.Status,
|
||||
StatusName: mapFamilyMemberStatusName(member.Status),
|
||||
JoinSource: member.JoinSource,
|
||||
JoinedAt: member.JoinedAt.Unix(),
|
||||
}
|
||||
if member.LeftAt != nil {
|
||||
memberItem.LeftAt = member.LeftAt.Unix()
|
||||
}
|
||||
memberItems = append(memberItems, memberItem)
|
||||
}
|
||||
|
||||
ownerIdentifier := identifierMap[family.OwnerUserId]
|
||||
if ownerIdentifier == "" {
|
||||
ownerIdentifier = strconv.FormatInt(family.OwnerUserId, 10)
|
||||
}
|
||||
|
||||
return &types.FamilyDetail{
|
||||
Summary: types.FamilySummary{
|
||||
FamilyId: family.Id,
|
||||
OwnerUserId: family.OwnerUserId,
|
||||
OwnerIdentifier: ownerIdentifier,
|
||||
Status: mapFamilyStatus(family.Status),
|
||||
ActiveMemberCount: activeMemberCount,
|
||||
MaxMembers: family.MaxMembers,
|
||||
CreatedAt: family.CreatedAt.Unix(),
|
||||
UpdatedAt: family.UpdatedAt.Unix(),
|
||||
},
|
||||
Members: memberItems,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
modelUser "github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type GetFamilyListLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logger.Logger
|
||||
}
|
||||
|
||||
func NewGetFamilyListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetFamilyListLogic {
|
||||
return &GetFamilyListLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logger.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *GetFamilyListLogic) GetFamilyList(req *types.GetFamilyListRequest) (*types.GetFamilyListResponse, error) {
|
||||
page := req.Page
|
||||
size := req.Size
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
if size <= 0 {
|
||||
size = 20
|
||||
}
|
||||
if size > 200 {
|
||||
size = 200
|
||||
}
|
||||
|
||||
query := l.svcCtx.DB.WithContext(l.ctx).
|
||||
Model(&modelUser.UserFamily{}).
|
||||
Where("user_family.deleted_at IS NULL")
|
||||
|
||||
if req.OwnerUserId != nil {
|
||||
query = query.Where("user_family.owner_user_id = ?", *req.OwnerUserId)
|
||||
}
|
||||
if req.FamilyId != nil {
|
||||
query = query.Where("user_family.id = ?", *req.FamilyId)
|
||||
}
|
||||
if req.UserId != nil {
|
||||
query = query.Where(
|
||||
"EXISTS (SELECT 1 FROM user_family_member ufm WHERE ufm.family_id = user_family.id AND ufm.deleted_at IS NULL AND ufm.status = ? AND ufm.user_id = ?)",
|
||||
modelUser.FamilyMemberActive, *req.UserId,
|
||||
)
|
||||
}
|
||||
if statusValue, ok := normalizeFamilyStatusInput(req.Status); ok {
|
||||
query = query.Where("user_family.status = ?", statusValue)
|
||||
}
|
||||
|
||||
keyword := strings.TrimSpace(req.Keyword)
|
||||
if keyword != "" {
|
||||
keywordLike := "%" + keyword + "%"
|
||||
query = query.Where(
|
||||
"(CAST(user_family.id AS CHAR) LIKE ? OR CAST(user_family.owner_user_id AS CHAR) LIKE ? OR "+
|
||||
"EXISTS (SELECT 1 FROM user_auth_methods owner_auth WHERE owner_auth.user_id = user_family.owner_user_id AND owner_auth.deleted_at IS NULL AND owner_auth.auth_identifier LIKE ?) OR "+
|
||||
"EXISTS (SELECT 1 FROM user_family_member keyword_member JOIN user_auth_methods keyword_auth ON keyword_auth.user_id = keyword_member.user_id AND keyword_auth.deleted_at IS NULL WHERE keyword_member.family_id = user_family.id AND keyword_member.deleted_at IS NULL AND keyword_auth.auth_identifier LIKE ?))",
|
||||
keywordLike, keywordLike, keywordLike, keywordLike,
|
||||
)
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "count family list failed")
|
||||
}
|
||||
|
||||
var families []modelUser.UserFamily
|
||||
if err := query.Order("user_family.id DESC").
|
||||
Limit(size).
|
||||
Offset((page - 1) * size).
|
||||
Find(&families).Error; err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query family list failed")
|
||||
}
|
||||
|
||||
if len(families) == 0 {
|
||||
return &types.GetFamilyListResponse{
|
||||
Total: total,
|
||||
List: []types.FamilySummary{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
ownerIDs := make([]int64, 0, len(families))
|
||||
familyIDs := make([]int64, 0, len(families))
|
||||
for _, family := range families {
|
||||
ownerIDs = append(ownerIDs, family.OwnerUserId)
|
||||
familyIDs = append(familyIDs, family.Id)
|
||||
}
|
||||
|
||||
identifierMap, identifierErr := findUserIdentifiers(l.ctx, l.svcCtx.DB, ownerIDs)
|
||||
if identifierErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query family owner identifiers failed")
|
||||
}
|
||||
|
||||
type familyCount struct {
|
||||
FamilyId int64
|
||||
Count int64
|
||||
}
|
||||
var counts []familyCount
|
||||
if err := l.svcCtx.DB.WithContext(l.ctx).
|
||||
Table("user_family_member").
|
||||
Select("family_id, COUNT(1) as count").
|
||||
Where("family_id IN ? AND deleted_at IS NULL AND status = ?", familyIDs, modelUser.FamilyMemberActive).
|
||||
Group("family_id").
|
||||
Scan(&counts).Error; err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query family member counts failed")
|
||||
}
|
||||
countMap := make(map[int64]int64, len(counts))
|
||||
for _, count := range counts {
|
||||
countMap[count.FamilyId] = count.Count
|
||||
}
|
||||
|
||||
list := make([]types.FamilySummary, 0, len(families))
|
||||
for _, family := range families {
|
||||
ownerIdentifier := identifierMap[family.OwnerUserId]
|
||||
if ownerIdentifier == "" {
|
||||
ownerIdentifier = strconv.FormatInt(family.OwnerUserId, 10)
|
||||
}
|
||||
|
||||
list = append(list, types.FamilySummary{
|
||||
FamilyId: family.Id,
|
||||
OwnerUserId: family.OwnerUserId,
|
||||
OwnerIdentifier: ownerIdentifier,
|
||||
Status: mapFamilyStatus(family.Status),
|
||||
ActiveMemberCount: countMap[family.Id],
|
||||
MaxMembers: family.MaxMembers,
|
||||
CreatedAt: family.CreatedAt.Unix(),
|
||||
UpdatedAt: family.UpdatedAt.Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
return &types.GetFamilyListResponse{
|
||||
Total: total,
|
||||
List: list,
|
||||
}, nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
@@ -32,5 +33,51 @@ func (l *GetUserDetailLogic) GetUserDetail(req *types.GetDetailRequest) (*types.
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get user detail error: %v", err.Error())
|
||||
}
|
||||
tool.DeepCopy(&resp, userInfo)
|
||||
|
||||
type familyRelation struct {
|
||||
FamilyId int64
|
||||
Role uint8
|
||||
FamilyStatus uint8
|
||||
OwnerUserId int64
|
||||
MaxMembers int64
|
||||
}
|
||||
|
||||
var relation familyRelation
|
||||
relationErr := l.svcCtx.DB.WithContext(l.ctx).
|
||||
Table("user_family_member").
|
||||
Select("user_family_member.family_id, user_family_member.role, user_family.status as family_status, user_family.owner_user_id, user_family.max_members").
|
||||
Joins("JOIN user_family ON user_family.id = user_family_member.family_id AND user_family.deleted_at IS NULL").
|
||||
Where("user_family_member.user_id = ? AND user_family_member.deleted_at IS NULL AND user_family_member.status = ?", req.Id, user.FamilyMemberActive).
|
||||
First(&relation).Error
|
||||
if relationErr == nil {
|
||||
resp.FamilyJoined = true
|
||||
resp.FamilyId = relation.FamilyId
|
||||
resp.FamilyRole = relation.Role
|
||||
resp.FamilyOwnerUserId = relation.OwnerUserId
|
||||
resp.FamilyMaxMembers = relation.MaxMembers
|
||||
if relation.FamilyStatus == user.FamilyStatusActive {
|
||||
resp.FamilyStatus = "active"
|
||||
} else {
|
||||
resp.FamilyStatus = "disabled"
|
||||
}
|
||||
if relation.Role == user.FamilyRoleOwner {
|
||||
resp.FamilyRoleName = "owner"
|
||||
} else if relation.Role == user.FamilyRoleMember {
|
||||
resp.FamilyRoleName = "member"
|
||||
}
|
||||
|
||||
type familyCount struct {
|
||||
Count int64
|
||||
}
|
||||
var count familyCount
|
||||
if countErr := l.svcCtx.DB.WithContext(l.ctx).
|
||||
Table("user_family_member").
|
||||
Select("COUNT(1) as count").
|
||||
Where("family_id = ? AND status = ? AND deleted_at IS NULL", relation.FamilyId, user.FamilyMemberActive).
|
||||
Scan(&count).Error; countErr == nil {
|
||||
resp.FamilyMemberCount = count.Count
|
||||
}
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
@@ -28,13 +29,17 @@ func NewGetUserListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUs
|
||||
}
|
||||
func (l *GetUserListLogic) GetUserList(req *types.GetUserListRequest) (*types.GetUserListResponse, error) {
|
||||
list, total, err := l.svcCtx.UserModel.QueryPageList(l.ctx, req.Page, req.Size, &user.UserFilterParams{
|
||||
UserId: req.UserId,
|
||||
Search: req.Search,
|
||||
Unscoped: req.Unscoped,
|
||||
SubscribeId: req.SubscribeId,
|
||||
UserSubscribeId: req.UserSubscribeId,
|
||||
ShortCode: req.ShortCode,
|
||||
Order: "DESC",
|
||||
UserId: req.UserId,
|
||||
Search: req.Search,
|
||||
Unscoped: req.Unscoped,
|
||||
SubscribeId: req.SubscribeId,
|
||||
UserSubscribeId: req.UserSubscribeId,
|
||||
ShortCode: req.ShortCode,
|
||||
FamilyJoined: req.FamilyJoined,
|
||||
FamilyStatus: req.FamilyStatus,
|
||||
FamilyOwnerUserId: req.FamilyOwnerUserId,
|
||||
FamilyId: req.FamilyId,
|
||||
Order: "DESC",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "GetUserListLogic failed: %v", err.Error())
|
||||
@@ -51,6 +56,64 @@ func (l *GetUserListLogic) GetUserList(req *types.GetUserListRequest) (*types.Ge
|
||||
l.Logger.Error("FindActiveSubscribesByUserIds failed", logger.Field("error", err.Error()))
|
||||
}
|
||||
|
||||
type familyRelation struct {
|
||||
UserId int64
|
||||
FamilyId int64
|
||||
Role uint8
|
||||
FamilyStatus uint8
|
||||
OwnerUserId int64
|
||||
MaxMembers int64
|
||||
}
|
||||
|
||||
relationMap := map[int64]familyRelation{}
|
||||
familyIds := make([]int64, 0)
|
||||
if len(userIds) > 0 {
|
||||
var relations []familyRelation
|
||||
relationErr := l.svcCtx.DB.WithContext(l.ctx).
|
||||
Table("user_family_member").
|
||||
Select("user_family_member.user_id, user_family_member.family_id, user_family_member.role, user_family.status as family_status, user_family.owner_user_id, user_family.max_members").
|
||||
Joins("JOIN user_family ON user_family.id = user_family_member.family_id AND user_family.deleted_at IS NULL").
|
||||
Where("user_family_member.user_id IN ? AND user_family_member.deleted_at IS NULL AND user_family_member.status = ?", userIds, user.FamilyMemberActive).
|
||||
Scan(&relations).Error
|
||||
if relationErr != nil {
|
||||
l.Logger.Error("query family relations failed", logger.Field("error", relationErr.Error()))
|
||||
}
|
||||
|
||||
familyIdSet := make(map[int64]struct{}, len(relations))
|
||||
for _, relation := range relations {
|
||||
if _, exists := relationMap[relation.UserId]; !exists {
|
||||
relationMap[relation.UserId] = relation
|
||||
}
|
||||
if _, ok := familyIdSet[relation.FamilyId]; ok {
|
||||
continue
|
||||
}
|
||||
familyIdSet[relation.FamilyId] = struct{}{}
|
||||
familyIds = append(familyIds, relation.FamilyId)
|
||||
}
|
||||
}
|
||||
|
||||
type familyCount struct {
|
||||
FamilyId int64
|
||||
Count int64
|
||||
}
|
||||
|
||||
familyCountMap := map[int64]int64{}
|
||||
if len(familyIds) > 0 {
|
||||
var counts []familyCount
|
||||
countErr := l.svcCtx.DB.WithContext(l.ctx).
|
||||
Table("user_family_member").
|
||||
Select("family_id, COUNT(1) as count").
|
||||
Where("family_id IN ? AND status = ? AND deleted_at IS NULL", familyIds, user.FamilyMemberActive).
|
||||
Group("family_id").
|
||||
Scan(&counts).Error
|
||||
if countErr != nil {
|
||||
l.Logger.Error("query family member count failed", logger.Field("error", countErr.Error()))
|
||||
}
|
||||
for _, count := range counts {
|
||||
familyCountMap[count.FamilyId] = count.Count
|
||||
}
|
||||
}
|
||||
|
||||
userRespList := make([]types.User, 0, len(list))
|
||||
|
||||
for _, item := range list {
|
||||
@@ -66,6 +129,7 @@ func (l *GetUserListLogic) GetUserList(req *types.GetUserListRequest) (*types.Ge
|
||||
if activeSubs != nil {
|
||||
if info, ok := activeSubs[item.Id]; ok {
|
||||
u.MemberStatus = info.MemberStatus
|
||||
u.PurchasedPackage = info.PurchasedPackage
|
||||
if info.LastTrafficAt != nil {
|
||||
trafficTime := info.LastTrafficAt.Unix()
|
||||
if trafficTime > u.LastLoginTime {
|
||||
@@ -85,6 +149,29 @@ func (l *GetUserListLogic) GetUserList(req *types.GetUserListRequest) (*types.Ge
|
||||
}
|
||||
u.AuthMethods = authMethods
|
||||
|
||||
if relation, ok := relationMap[item.Id]; ok {
|
||||
u.FamilyJoined = true
|
||||
u.FamilyId = relation.FamilyId
|
||||
u.FamilyRole = relation.Role
|
||||
u.FamilyOwnerUserId = relation.OwnerUserId
|
||||
u.FamilyMaxMembers = relation.MaxMembers
|
||||
u.FamilyMemberCount = familyCountMap[relation.FamilyId]
|
||||
switch relation.FamilyStatus {
|
||||
case user.FamilyStatusActive:
|
||||
u.FamilyStatus = "active"
|
||||
default:
|
||||
u.FamilyStatus = "disabled"
|
||||
}
|
||||
switch relation.Role {
|
||||
case user.FamilyRoleOwner:
|
||||
u.FamilyRoleName = "owner"
|
||||
case user.FamilyRoleMember:
|
||||
u.FamilyRoleName = "member"
|
||||
default:
|
||||
u.FamilyRoleName = fmt.Sprintf("role_%d", relation.Role)
|
||||
}
|
||||
}
|
||||
|
||||
userRespList = append(userRespList, u)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
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/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type RemoveFamilyMemberLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logger.Logger
|
||||
}
|
||||
|
||||
func NewRemoveFamilyMemberLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RemoveFamilyMemberLogic {
|
||||
return &RemoveFamilyMemberLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logger.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *RemoveFamilyMemberLogic) RemoveFamilyMember(req *types.RemoveFamilyMemberRequest) error {
|
||||
var family modelUser.UserFamily
|
||||
err := l.svcCtx.DB.WithContext(l.ctx).
|
||||
Where("id = ? AND deleted_at IS NULL", req.FamilyId).
|
||||
First(&family).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyNotExist), "family does not exist")
|
||||
}
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query family failed")
|
||||
}
|
||||
if family.Status != modelUser.FamilyStatusActive {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyStatusInvalid), "family status is invalid")
|
||||
}
|
||||
|
||||
var member modelUser.UserFamilyMember
|
||||
err = l.svcCtx.DB.WithContext(l.ctx).
|
||||
Where("family_id = ? AND user_id = ? AND deleted_at IS NULL AND status = ?", req.FamilyId, req.UserId, modelUser.FamilyMemberActive).
|
||||
First(&member).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyNotExist), "active family member does not exist")
|
||||
}
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query family member failed")
|
||||
}
|
||||
if member.Role == modelUser.FamilyRoleOwner {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyOwnerOperationForbidden), "cannot remove family owner")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if err = l.svcCtx.DB.WithContext(l.ctx).
|
||||
Model(&modelUser.UserFamilyMember{}).
|
||||
Where("id = ?", member.Id).
|
||||
Updates(map[string]interface{}{
|
||||
"status": modelUser.FamilyMemberRemoved,
|
||||
"left_at": now,
|
||||
}).Error; err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "remove family member failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
modelUser "github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/logger"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UpdateFamilyMaxMembersLogic struct {
|
||||
ctx context.Context
|
||||
svcCtx *svc.ServiceContext
|
||||
logger.Logger
|
||||
}
|
||||
|
||||
func NewUpdateFamilyMaxMembersLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateFamilyMaxMembersLogic {
|
||||
return &UpdateFamilyMaxMembersLogic{
|
||||
ctx: ctx,
|
||||
svcCtx: svcCtx,
|
||||
Logger: logger.WithContext(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *UpdateFamilyMaxMembersLogic) UpdateFamilyMaxMembers(req *types.UpdateFamilyMaxMembersRequest) error {
|
||||
var family modelUser.UserFamily
|
||||
err := l.svcCtx.DB.WithContext(l.ctx).
|
||||
Where("id = ? AND deleted_at IS NULL", req.FamilyId).
|
||||
First(&family).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyNotExist), "family does not exist")
|
||||
}
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query family failed")
|
||||
}
|
||||
|
||||
var activeCount int64
|
||||
if err = l.svcCtx.DB.WithContext(l.ctx).
|
||||
Table("user_family_member").
|
||||
Where("family_id = ? AND deleted_at IS NULL AND status = ?", req.FamilyId, modelUser.FamilyMemberActive).
|
||||
Count(&activeCount).Error; err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "count active members failed")
|
||||
}
|
||||
if req.MaxMembers < activeCount {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.FamilyMemberLimitExceeded), "max members below active member count")
|
||||
}
|
||||
|
||||
if err = l.svcCtx.DB.WithContext(l.ctx).
|
||||
Model(&modelUser.UserFamily{}).
|
||||
Where("id = ?", req.FamilyId).
|
||||
Update("max_members", req.MaxMembers).Error; err != nil {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update family max members failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user