258 lines
7.9 KiB
Go
258 lines
7.9 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
logmodel "github.com/perfect-panel/server/internal/model/log"
|
|
"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/constant"
|
|
"github.com/perfect-panel/server/pkg/hash"
|
|
"github.com/perfect-panel/server/pkg/logger"
|
|
"github.com/perfect-panel/server/pkg/xerr"
|
|
"github.com/pkg/errors"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
const (
|
|
inviteRecordRoleInviter = "inviter"
|
|
inviteRecordRoleInvitee = "invitee"
|
|
)
|
|
|
|
type GetInviteRecordsLogic struct {
|
|
logger.Logger
|
|
ctx context.Context
|
|
svcCtx *svc.ServiceContext
|
|
}
|
|
|
|
type inviteRecordLog struct {
|
|
Id int64 `gorm:"column:id"`
|
|
ObjectId int64 `gorm:"column:object_id"`
|
|
Content string `gorm:"column:content"`
|
|
CreatedAt int64 `gorm:"column:created_at"`
|
|
}
|
|
|
|
type inviteGiftContent struct {
|
|
OrderNo string `json:"order_no"`
|
|
Amount int64 `json:"amount"`
|
|
}
|
|
|
|
type parsedInviteRecordLog struct {
|
|
log inviteRecordLog
|
|
content inviteGiftContent
|
|
}
|
|
|
|
type inviteOrderUser struct {
|
|
OrderNo string `gorm:"column:order_no"`
|
|
UserId int64 `gorm:"column:user_id"`
|
|
SubscriptionUserId int64 `gorm:"column:subscription_user_id"`
|
|
RefererId int64 `gorm:"column:referer_id"`
|
|
}
|
|
|
|
// Get invite gift records
|
|
func NewGetInviteRecordsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetInviteRecordsLogic {
|
|
return &GetInviteRecordsLogic{
|
|
Logger: logger.WithContext(ctx),
|
|
ctx: ctx,
|
|
svcCtx: svcCtx,
|
|
}
|
|
}
|
|
|
|
func (l *GetInviteRecordsLogic) GetInviteRecords(req *types.GetInviteRecordsRequest) (resp *types.GetInviteRecordsResponse, err error) {
|
|
u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
|
if !ok {
|
|
l.Errorw("[GetInviteRecords] user not found in context")
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
|
|
}
|
|
|
|
normalizeInviteRecordsPagination(req)
|
|
|
|
visibleUserIds, err := l.resolveInviteRecordVisibleUserIds(u.Id)
|
|
if err != nil {
|
|
l.Errorw("[GetInviteRecords] resolve visible users failed",
|
|
logger.Field("error", err.Error()),
|
|
logger.Field("user_id", u.Id))
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "resolve visible users failed: %v", err.Error())
|
|
}
|
|
|
|
query := l.svcCtx.DB.WithContext(l.ctx).
|
|
Table("system_logs").
|
|
Where("type = ? AND object_id IN ?", logmodel.TypeGift.Uint8(), visibleUserIds).
|
|
Where("JSON_VALID(content) = 1").
|
|
Where("JSON_UNQUOTE(JSON_EXTRACT(content, '$.remark')) = ?", "邀请赠送")
|
|
if req.StartTime > 0 {
|
|
query = query.Where("created_at >= FROM_UNIXTIME(?)", req.StartTime)
|
|
}
|
|
if req.EndTime > 0 {
|
|
query = query.Where("created_at <= FROM_UNIXTIME(?)", req.EndTime)
|
|
}
|
|
|
|
var logs []inviteRecordLog
|
|
if err = query.
|
|
Select("id, object_id, content, CAST(UNIX_TIMESTAMP(created_at) * 1000 AS SIGNED) AS created_at").
|
|
Order("created_at DESC, id DESC").
|
|
Scan(&logs).Error; err != nil {
|
|
l.Errorw("[GetInviteRecords] query logs failed",
|
|
logger.Field("error", err.Error()),
|
|
logger.Field("user_id", u.Id))
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query logs failed: %v", err.Error())
|
|
}
|
|
|
|
parsedLogs, orderNos := l.parseInviteRecordContents(logs)
|
|
if len(logs) == 0 || len(parsedLogs) == 0 {
|
|
return &types.GetInviteRecordsResponse{Total: 0, List: []types.InviteRecord{}}, nil
|
|
}
|
|
|
|
orders, err := l.queryInviteRecordOrders(orderNos)
|
|
if err != nil {
|
|
l.Errorw("[GetInviteRecords] query orders failed",
|
|
logger.Field("error", err.Error()),
|
|
logger.Field("user_id", u.Id))
|
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query orders failed: %v", err.Error())
|
|
}
|
|
|
|
allRecords := make([]types.InviteRecord, 0, len(parsedLogs))
|
|
for _, parsed := range parsedLogs {
|
|
content := parsed.content
|
|
logItem := parsed.log
|
|
orderInfo, hasOrder := orders[content.OrderNo]
|
|
if !l.canViewInviteRecord(u.Id, logItem.ObjectId, hasOrder, orderInfo) {
|
|
continue
|
|
}
|
|
|
|
record := types.InviteRecord{
|
|
Role: inviteRecordRoleInviter,
|
|
GiftDays: content.Amount,
|
|
OrderNo: content.OrderNo,
|
|
CreatedAt: logItem.CreatedAt,
|
|
}
|
|
|
|
if hasOrder {
|
|
peerId := orderInfo.UserId
|
|
if orderInfo.UserId == u.Id {
|
|
record.Role = inviteRecordRoleInvitee
|
|
peerId = u.RefererId
|
|
}
|
|
if peerId > 0 {
|
|
record.PeerHash = hash.InvitePeerHash(peerId)
|
|
}
|
|
}
|
|
|
|
allRecords = append(allRecords, record)
|
|
}
|
|
|
|
total := int64(len(allRecords))
|
|
list := paginateInviteRecords(allRecords, req.Page, req.Size)
|
|
return &types.GetInviteRecordsResponse{
|
|
Total: total,
|
|
List: list,
|
|
}, nil
|
|
}
|
|
|
|
func (l *GetInviteRecordsLogic) resolveInviteRecordVisibleUserIds(currentUserId int64) ([]int64, error) {
|
|
visibleUserIds := []int64{currentUserId}
|
|
|
|
var relation struct {
|
|
FamilyId int64 `gorm:"column:family_id"`
|
|
Role uint8 `gorm:"column:role"`
|
|
OwnerUserId int64 `gorm:"column:owner_user_id"`
|
|
}
|
|
err := l.svcCtx.DB.WithContext(l.ctx).
|
|
Model(&user.UserFamilyMember{}).
|
|
Select("user_family_member.family_id, user_family_member.role, user_family.owner_user_id").
|
|
Joins("JOIN user_family ON user_family.id = user_family_member.family_id AND user_family.deleted_at IS NULL AND user_family.status = ?", user.FamilyStatusActive).
|
|
Where("user_family_member.user_id = ? AND user_family_member.status = ? AND user_family_member.deleted_at IS NULL", currentUserId, user.FamilyMemberActive).
|
|
First(&relation).Error
|
|
if err == nil {
|
|
if relation.Role != user.FamilyRoleOwner && relation.OwnerUserId > 0 && relation.OwnerUserId != currentUserId {
|
|
visibleUserIds = append(visibleUserIds, relation.OwnerUserId)
|
|
}
|
|
return visibleUserIds, nil
|
|
}
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return visibleUserIds, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
func (l *GetInviteRecordsLogic) canViewInviteRecord(currentUserId, logObjectId int64, hasOrder bool, orderInfo inviteOrderUser) bool {
|
|
if logObjectId == currentUserId {
|
|
if hasOrder {
|
|
return orderInfo.UserId == currentUserId || orderInfo.RefererId == currentUserId
|
|
}
|
|
return true
|
|
}
|
|
if !hasOrder {
|
|
return false
|
|
}
|
|
return orderInfo.UserId == currentUserId && orderInfo.SubscriptionUserId == logObjectId
|
|
}
|
|
|
|
func normalizeInviteRecordsPagination(req *types.GetInviteRecordsRequest) {
|
|
if req.Page < 1 {
|
|
req.Page = 1
|
|
}
|
|
if req.Size < 1 {
|
|
req.Size = 10
|
|
}
|
|
if req.Size > 100 {
|
|
req.Size = 100
|
|
}
|
|
}
|
|
|
|
func paginateInviteRecords(records []types.InviteRecord, page, size int) []types.InviteRecord {
|
|
start := (page - 1) * size
|
|
if start >= len(records) {
|
|
return []types.InviteRecord{}
|
|
}
|
|
end := start + size
|
|
if end > len(records) {
|
|
end = len(records)
|
|
}
|
|
return records[start:end]
|
|
}
|
|
|
|
func (l *GetInviteRecordsLogic) parseInviteRecordContents(logs []inviteRecordLog) ([]parsedInviteRecordLog, []string) {
|
|
parsedLogs := make([]parsedInviteRecordLog, 0, len(logs))
|
|
orderNos := make([]string, 0, len(logs))
|
|
for _, logItem := range logs {
|
|
var content inviteGiftContent
|
|
if err := json.Unmarshal([]byte(logItem.Content), &content); err != nil {
|
|
l.Infow("[GetInviteRecords] parse content failed",
|
|
logger.Field("error", err.Error()),
|
|
logger.Field("log_id", logItem.Id))
|
|
continue
|
|
}
|
|
parsedLogs = append(parsedLogs, parsedInviteRecordLog{log: logItem, content: content})
|
|
if content.OrderNo != "" {
|
|
orderNos = append(orderNos, content.OrderNo)
|
|
}
|
|
}
|
|
return parsedLogs, orderNos
|
|
}
|
|
|
|
func (l *GetInviteRecordsLogic) queryInviteRecordOrders(orderNos []string) (map[string]inviteOrderUser, error) {
|
|
orders := make(map[string]inviteOrderUser, len(orderNos))
|
|
if len(orderNos) == 0 {
|
|
return orders, nil
|
|
}
|
|
|
|
var orderData []inviteOrderUser
|
|
err := l.svcCtx.DB.WithContext(l.ctx).
|
|
Table("`order`").
|
|
Select("`order`.order_no, `order`.user_id, `order`.subscription_user_id, invitee.referer_id").
|
|
Joins("LEFT JOIN user invitee ON invitee.id = `order`.user_id").
|
|
Where("`order`.order_no IN ?", orderNos).
|
|
Scan(&orderData).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, item := range orderData {
|
|
orders[item.OrderNo] = item
|
|
}
|
|
return orders, nil
|
|
}
|