修复(#126): 修复家庭组邀请记录漏查验收分支
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -23,8 +23,9 @@ type InviteRelation struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type paidOrderRow struct {
|
type paidOrderRow struct {
|
||||||
UserId int64 `gorm:"column:user_id"`
|
UserId int64 `gorm:"column:user_id"`
|
||||||
OrderNo string `gorm:"column:order_no"`
|
SubscriptionUserId int64 `gorm:"column:subscription_user_id"`
|
||||||
|
OrderNo string `gorm:"column:order_no"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type systemLogRow struct {
|
type systemLogRow struct {
|
||||||
@@ -81,7 +82,7 @@ func QueryBenefits(ctx context.Context, db *gorm.DB, relations []InviteRelation)
|
|||||||
var paidOrders []paidOrderRow
|
var paidOrders []paidOrderRow
|
||||||
if err := db.WithContext(ctx).
|
if err := db.WithContext(ctx).
|
||||||
Table("`order`").
|
Table("`order`").
|
||||||
Select("user_id, order_no").
|
Select("user_id, subscription_user_id, order_no").
|
||||||
Where("user_id IN ? AND status IN ?", inviteeIds, []int{2, 5}).
|
Where("user_id IN ? AND status IN ?", inviteeIds, []int{2, 5}).
|
||||||
Scan(&paidOrders).Error; err != nil {
|
Scan(&paidOrders).Error; err != nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query invitee paid orders failed: %v", err)
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query invitee paid orders failed: %v", err)
|
||||||
@@ -92,11 +93,16 @@ func QueryBenefits(ctx context.Context, db *gorm.DB, relations []InviteRelation)
|
|||||||
|
|
||||||
orderNos := make([]string, 0, len(paidOrders))
|
orderNos := make([]string, 0, len(paidOrders))
|
||||||
orderToInvitee := make(map[string]int64, len(paidOrders))
|
orderToInvitee := make(map[string]int64, len(paidOrders))
|
||||||
|
orderToSubscriptionUser := make(map[string]int64, len(paidOrders))
|
||||||
inviterIds := make([]int64, 0, len(relations))
|
inviterIds := make([]int64, 0, len(relations))
|
||||||
inviteeAndInviterSet := make(map[int64]struct{}, len(relations)*2)
|
inviteeAndInviterSet := make(map[int64]struct{}, len(relations)*2)
|
||||||
for _, order := range paidOrders {
|
for _, order := range paidOrders {
|
||||||
orderNos = append(orderNos, order.OrderNo)
|
orderNos = append(orderNos, order.OrderNo)
|
||||||
orderToInvitee[order.OrderNo] = order.UserId
|
orderToInvitee[order.OrderNo] = order.UserId
|
||||||
|
if order.SubscriptionUserId > 0 && order.SubscriptionUserId != order.UserId {
|
||||||
|
orderToSubscriptionUser[order.OrderNo] = order.SubscriptionUserId
|
||||||
|
inviteeAndInviterSet[order.SubscriptionUserId] = struct{}{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, relation := range relations {
|
for _, relation := range relations {
|
||||||
inviterIds = append(inviterIds, relation.InviterId)
|
inviterIds = append(inviterIds, relation.InviterId)
|
||||||
@@ -111,7 +117,7 @@ func QueryBenefits(ctx context.Context, db *gorm.DB, relations []InviteRelation)
|
|||||||
if err := fillCommissionBenefits(ctx, db, result, orderToInvitee, inviteeToInviter, orderNos, inviterIds); err != nil {
|
if err := fillCommissionBenefits(ctx, db, result, orderToInvitee, inviteeToInviter, orderNos, inviterIds); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := fillGiftBenefits(ctx, db, result, orderToInvitee, inviteeToInviter, orderNos, inviteeAndInviterIds); err != nil {
|
if err := fillGiftBenefits(ctx, db, result, orderToInvitee, inviteeToInviter, orderToSubscriptionUser, orderNos, inviteeAndInviterIds); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +149,7 @@ func fillCommissionBenefits(ctx context.Context, db *gorm.DB, benefits map[int64
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fillGiftBenefits(ctx context.Context, db *gorm.DB, benefits map[int64]Benefits, orderToInvitee map[string]int64, inviteeToInviter map[int64]int64, orderNos []string, userIds []int64) error {
|
func fillGiftBenefits(ctx context.Context, db *gorm.DB, benefits map[int64]Benefits, orderToInvitee map[string]int64, inviteeToInviter map[int64]int64, orderToSubscriptionUser map[string]int64, orderNos []string, userIds []int64) error {
|
||||||
var rows []systemLogRow
|
var rows []systemLogRow
|
||||||
if err := db.WithContext(ctx).
|
if err := db.WithContext(ctx).
|
||||||
Table("system_logs").
|
Table("system_logs").
|
||||||
@@ -170,6 +176,8 @@ func fillGiftBenefits(ctx context.Context, db *gorm.DB, benefits map[int64]Benef
|
|||||||
benefit.InviterGiftDays += content.Amount
|
benefit.InviterGiftDays += content.Amount
|
||||||
case inviteeId:
|
case inviteeId:
|
||||||
benefit.InviteeGiftDays += content.Amount
|
benefit.InviteeGiftDays += content.Amount
|
||||||
|
case orderToSubscriptionUser[content.OrderNo]:
|
||||||
|
benefit.InviteeGiftDays += content.Amount
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package invite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/DATA-DOG/go-sqlmock"
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQueryBenefitsCountsFamilyOwnerGiftAsInviteeGift(t *testing.T) {
|
||||||
|
db, mock, cleanup := newBenefitsTestDB(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
mock.ExpectQuery("COUNT(*) as cnt").
|
||||||
|
WithArgs(int64(200), 2, 5).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"user_id", "cnt"}).AddRow(200, 1))
|
||||||
|
mock.ExpectQuery("SELECT user_id, subscription_user_id, order_no FROM `order`").
|
||||||
|
WithArgs(int64(200), 2, 5).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"user_id", "subscription_user_id", "order_no"}).AddRow(200, 900, "family-order"))
|
||||||
|
mock.ExpectQuery("object_id IN").
|
||||||
|
WithArgs(33, int64(100), "family-order", 331, 332).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"object_id", "content"}))
|
||||||
|
mock.ExpectQuery("object_id IN").
|
||||||
|
WithArgs(34, int64(900), int64(200), int64(100), "family-order").
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"object_id", "content"}).
|
||||||
|
AddRow(900, `{"type":341,"order_no":"family-order","amount":7,"balance":7,"remark":"邀请赠送"}`))
|
||||||
|
|
||||||
|
benefits, err := QueryBenefits(context.Background(), db, []InviteRelation{{InviteeId: 200, InviterId: 100}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("QueryBenefits returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
benefit := benefits[200]
|
||||||
|
if benefit.InviteeGiftDays != 7 {
|
||||||
|
t.Fatalf("InviteeGiftDays = %d, want 7", benefit.InviteeGiftDays)
|
||||||
|
}
|
||||||
|
if benefit.InviterGiftDays != 0 {
|
||||||
|
t.Fatalf("InviterGiftDays = %d, want 0", benefit.InviterGiftDays)
|
||||||
|
}
|
||||||
|
assertBenefitsExpectations(t, mock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryBenefitsKeepsDirectInviteeGift(t *testing.T) {
|
||||||
|
db, mock, cleanup := newBenefitsTestDB(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
mock.ExpectQuery("COUNT(*) as cnt").
|
||||||
|
WithArgs(int64(200), 2, 5).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"user_id", "cnt"}).AddRow(200, 1))
|
||||||
|
mock.ExpectQuery("SELECT user_id, subscription_user_id, order_no FROM `order`").
|
||||||
|
WithArgs(int64(200), 2, 5).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"user_id", "subscription_user_id", "order_no"}).AddRow(200, 0, "direct-order"))
|
||||||
|
mock.ExpectQuery("object_id IN").
|
||||||
|
WithArgs(33, int64(100), "direct-order", 331, 332).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"object_id", "content"}))
|
||||||
|
mock.ExpectQuery("object_id IN").
|
||||||
|
WithArgs(34, int64(200), int64(100), "direct-order").
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"object_id", "content"}).
|
||||||
|
AddRow(200, `{"type":341,"order_no":"direct-order","amount":5,"balance":5,"remark":"邀请赠送"}`))
|
||||||
|
|
||||||
|
benefits, err := QueryBenefits(context.Background(), db, []InviteRelation{{InviteeId: 200, InviterId: 100}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("QueryBenefits returned error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := benefits[200].InviteeGiftDays; got != 5 {
|
||||||
|
t.Fatalf("InviteeGiftDays = %d, want 5", got)
|
||||||
|
}
|
||||||
|
assertBenefitsExpectations(t, mock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBenefitsTestDB(t *testing.T) (*gorm.DB, sqlmock.Sqlmock, func()) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherFunc(func(expectedSQL, actualSQL string) error {
|
||||||
|
if strings.Contains(actualSQL, expectedSQL) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("actual sql %q does not contain %q", actualSQL, expectedSQL)
|
||||||
|
})))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create sqlmock: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err := gorm.Open(mysql.New(mysql.Config{Conn: sqlDB, SkipInitializeWithVersion: true}), &gorm.Config{})
|
||||||
|
if err != nil {
|
||||||
|
_ = sqlDB.Close()
|
||||||
|
t.Fatalf("open gorm db: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, mock, func() {
|
||||||
|
_ = sqlDB.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertBenefitsExpectations(t *testing.T, mock sqlmock.Sqlmock) {
|
||||||
|
t.Helper()
|
||||||
|
if err := mock.ExpectationsWereMet(); err != nil {
|
||||||
|
t.Fatalf("unmet sql expectations: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
logmodel "github.com/perfect-panel/server/internal/model/log"
|
logmodel "github.com/perfect-panel/server/internal/model/log"
|
||||||
ordermodel "github.com/perfect-panel/server/internal/model/order"
|
|
||||||
"github.com/perfect-panel/server/internal/model/user"
|
"github.com/perfect-panel/server/internal/model/user"
|
||||||
"github.com/perfect-panel/server/internal/svc"
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
"github.com/perfect-panel/server/internal/types"
|
"github.com/perfect-panel/server/internal/types"
|
||||||
@@ -14,6 +13,7 @@ import (
|
|||||||
"github.com/perfect-panel/server/pkg/logger"
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
"github.com/perfect-panel/server/pkg/xerr"
|
"github.com/perfect-panel/server/pkg/xerr"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -45,8 +45,10 @@ type parsedInviteRecordLog struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type inviteOrderUser struct {
|
type inviteOrderUser struct {
|
||||||
OrderNo string `gorm:"column:order_no"`
|
OrderNo string `gorm:"column:order_no"`
|
||||||
UserId int64 `gorm:"column:user_id"`
|
UserId int64 `gorm:"column:user_id"`
|
||||||
|
SubscriptionUserId int64 `gorm:"column:subscription_user_id"`
|
||||||
|
RefererId int64 `gorm:"column:referer_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get invite gift records
|
// Get invite gift records
|
||||||
@@ -67,9 +69,17 @@ func (l *GetInviteRecordsLogic) GetInviteRecords(req *types.GetInviteRecordsRequ
|
|||||||
|
|
||||||
normalizeInviteRecordsPagination(req)
|
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).
|
query := l.svcCtx.DB.WithContext(l.ctx).
|
||||||
Table("system_logs").
|
Table("system_logs").
|
||||||
Where("type = ? AND object_id = ?", logmodel.TypeGift.Uint8(), u.Id).
|
Where("type = ? AND object_id IN ?", logmodel.TypeGift.Uint8(), visibleUserIds).
|
||||||
Where("JSON_VALID(content) = 1").
|
Where("JSON_VALID(content) = 1").
|
||||||
Where("JSON_UNQUOTE(JSON_EXTRACT(content, '$.remark')) = ?", "邀请赠送")
|
Where("JSON_UNQUOTE(JSON_EXTRACT(content, '$.remark')) = ?", "邀请赠送")
|
||||||
if req.StartTime > 0 {
|
if req.StartTime > 0 {
|
||||||
@@ -79,20 +89,10 @@ func (l *GetInviteRecordsLogic) GetInviteRecords(req *types.GetInviteRecordsRequ
|
|||||||
query = query.Where("created_at <= FROM_UNIXTIME(?)", req.EndTime)
|
query = query.Where("created_at <= FROM_UNIXTIME(?)", req.EndTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
var total int64
|
|
||||||
if err = query.Count(&total).Error; err != nil {
|
|
||||||
l.Errorw("[GetInviteRecords] count logs failed",
|
|
||||||
logger.Field("error", err.Error()),
|
|
||||||
logger.Field("user_id", u.Id))
|
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "count logs failed: %v", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
var logs []inviteRecordLog
|
var logs []inviteRecordLog
|
||||||
if err = query.
|
if err = query.
|
||||||
Select("id, object_id, content, CAST(UNIX_TIMESTAMP(created_at) * 1000 AS SIGNED) AS created_at").
|
Select("id, object_id, content, CAST(UNIX_TIMESTAMP(created_at) * 1000 AS SIGNED) AS created_at").
|
||||||
Order("created_at DESC, id DESC").
|
Order("created_at DESC, id DESC").
|
||||||
Limit(req.Size).
|
|
||||||
Offset((req.Page - 1) * req.Size).
|
|
||||||
Scan(&logs).Error; err != nil {
|
Scan(&logs).Error; err != nil {
|
||||||
l.Errorw("[GetInviteRecords] query logs failed",
|
l.Errorw("[GetInviteRecords] query logs failed",
|
||||||
logger.Field("error", err.Error()),
|
logger.Field("error", err.Error()),
|
||||||
@@ -102,7 +102,7 @@ func (l *GetInviteRecordsLogic) GetInviteRecords(req *types.GetInviteRecordsRequ
|
|||||||
|
|
||||||
parsedLogs, orderNos := l.parseInviteRecordContents(logs)
|
parsedLogs, orderNos := l.parseInviteRecordContents(logs)
|
||||||
if len(logs) == 0 || len(parsedLogs) == 0 {
|
if len(logs) == 0 || len(parsedLogs) == 0 {
|
||||||
return &types.GetInviteRecordsResponse{Total: total, List: []types.InviteRecord{}}, nil
|
return &types.GetInviteRecordsResponse{Total: 0, List: []types.InviteRecord{}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
orders, err := l.queryInviteRecordOrders(orderNos)
|
orders, err := l.queryInviteRecordOrders(orderNos)
|
||||||
@@ -113,10 +113,15 @@ func (l *GetInviteRecordsLogic) GetInviteRecords(req *types.GetInviteRecordsRequ
|
|||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query orders failed: %v", err.Error())
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query orders failed: %v", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
list := make([]types.InviteRecord, 0, len(parsedLogs))
|
allRecords := make([]types.InviteRecord, 0, len(parsedLogs))
|
||||||
for _, parsed := range parsedLogs {
|
for _, parsed := range parsedLogs {
|
||||||
content := parsed.content
|
content := parsed.content
|
||||||
logItem := parsed.log
|
logItem := parsed.log
|
||||||
|
orderInfo, hasOrder := orders[content.OrderNo]
|
||||||
|
if !l.canViewInviteRecord(u.Id, logItem.ObjectId, hasOrder, orderInfo) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
record := types.InviteRecord{
|
record := types.InviteRecord{
|
||||||
Role: inviteRecordRoleInviter,
|
Role: inviteRecordRoleInviter,
|
||||||
GiftDays: content.Amount,
|
GiftDays: content.Amount,
|
||||||
@@ -124,7 +129,7 @@ func (l *GetInviteRecordsLogic) GetInviteRecords(req *types.GetInviteRecordsRequ
|
|||||||
CreatedAt: logItem.CreatedAt,
|
CreatedAt: logItem.CreatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
if orderInfo, ok := orders[content.OrderNo]; ok {
|
if hasOrder {
|
||||||
peerId := orderInfo.UserId
|
peerId := orderInfo.UserId
|
||||||
if orderInfo.UserId == u.Id {
|
if orderInfo.UserId == u.Id {
|
||||||
record.Role = inviteRecordRoleInvitee
|
record.Role = inviteRecordRoleInvitee
|
||||||
@@ -135,15 +140,56 @@ func (l *GetInviteRecordsLogic) GetInviteRecords(req *types.GetInviteRecordsRequ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
list = append(list, record)
|
allRecords = append(allRecords, record)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
total := int64(len(allRecords))
|
||||||
|
list := paginateInviteRecords(allRecords, req.Page, req.Size)
|
||||||
return &types.GetInviteRecordsResponse{
|
return &types.GetInviteRecordsResponse{
|
||||||
Total: total,
|
Total: total,
|
||||||
List: list,
|
List: list,
|
||||||
}, nil
|
}, 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) {
|
func normalizeInviteRecordsPagination(req *types.GetInviteRecordsRequest) {
|
||||||
if req.Page < 1 {
|
if req.Page < 1 {
|
||||||
req.Page = 1
|
req.Page = 1
|
||||||
@@ -156,6 +202,18 @@ func normalizeInviteRecordsPagination(req *types.GetInviteRecordsRequest) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func (l *GetInviteRecordsLogic) parseInviteRecordContents(logs []inviteRecordLog) ([]parsedInviteRecordLog, []string) {
|
||||||
parsedLogs := make([]parsedInviteRecordLog, 0, len(logs))
|
parsedLogs := make([]parsedInviteRecordLog, 0, len(logs))
|
||||||
orderNos := make([]string, 0, len(logs))
|
orderNos := make([]string, 0, len(logs))
|
||||||
@@ -183,9 +241,10 @@ func (l *GetInviteRecordsLogic) queryInviteRecordOrders(orderNos []string) (map[
|
|||||||
|
|
||||||
var orderData []inviteOrderUser
|
var orderData []inviteOrderUser
|
||||||
err := l.svcCtx.DB.WithContext(l.ctx).
|
err := l.svcCtx.DB.WithContext(l.ctx).
|
||||||
Model(&ordermodel.Order{}).
|
Table("`order`").
|
||||||
Select("order_no, user_id").
|
Select("`order`.order_no, `order`.user_id, `order`.subscription_user_id, invitee.referer_id").
|
||||||
Where("order_no IN ?", orderNos).
|
Joins("LEFT JOIN user invitee ON invitee.id = `order`.user_id").
|
||||||
|
Where("`order`.order_no IN ?", orderNos).
|
||||||
Scan(&orderData).Error
|
Scan(&orderData).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -20,16 +20,14 @@ func TestGetInviteRecordsInviter(t *testing.T) {
|
|||||||
svcCtx, mock, cleanup := newInviteRecordsTestSvc(t)
|
svcCtx, mock, cleanup := newInviteRecordsTestSvc(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
mock.ExpectQuery("count(*)").
|
expectNoInviteRecordsFamily(t, mock, 100)
|
||||||
WithArgs(34, int64(100), "邀请赠送").
|
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
|
||||||
mock.ExpectQuery("SELECT id, object_id, content").
|
mock.ExpectQuery("SELECT id, object_id, content").
|
||||||
WithArgs(34, int64(100), "邀请赠送", 10).
|
WithArgs(34, int64(100), "邀请赠送").
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"id", "object_id", "content", "created_at"}).
|
WillReturnRows(sqlmock.NewRows([]string{"id", "object_id", "content", "created_at"}).
|
||||||
AddRow(1, 100, `{"order_no":"order-1","amount":7,"remark":"邀请赠送"}`, 1779934580000))
|
AddRow(1, 100, `{"order_no":"order-1","amount":7,"remark":"邀请赠送"}`, 1779934580000))
|
||||||
mock.ExpectQuery("SELECT order_no, user_id FROM `order`").
|
mock.ExpectQuery("SELECT `order`.order_no, `order`.user_id, `order`.subscription_user_id, invitee.referer_id FROM `order`").
|
||||||
WithArgs("order-1").
|
WithArgs("order-1").
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"order_no", "user_id"}).AddRow("order-1", 200))
|
WillReturnRows(sqlmock.NewRows([]string{"order_no", "user_id", "subscription_user_id", "referer_id"}).AddRow("order-1", 200, 200, 100))
|
||||||
|
|
||||||
resp, err := NewGetInviteRecordsLogic(inviteRecordsContext(100, 0), svcCtx).GetInviteRecords(&types.GetInviteRecordsRequest{Page: 1, Size: 10})
|
resp, err := NewGetInviteRecordsLogic(inviteRecordsContext(100, 0), svcCtx).GetInviteRecords(&types.GetInviteRecordsRequest{Page: 1, Size: 10})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -49,16 +47,14 @@ func TestGetInviteRecordsInvitee(t *testing.T) {
|
|||||||
svcCtx, mock, cleanup := newInviteRecordsTestSvc(t)
|
svcCtx, mock, cleanup := newInviteRecordsTestSvc(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
mock.ExpectQuery("count(*)").
|
expectNoInviteRecordsFamily(t, mock, 200)
|
||||||
WithArgs(34, int64(200), "邀请赠送").
|
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
|
||||||
mock.ExpectQuery("SELECT id, object_id, content").
|
mock.ExpectQuery("SELECT id, object_id, content").
|
||||||
WithArgs(34, int64(200), "邀请赠送", 10).
|
WithArgs(34, int64(200), "邀请赠送").
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"id", "object_id", "content", "created_at"}).
|
WillReturnRows(sqlmock.NewRows([]string{"id", "object_id", "content", "created_at"}).
|
||||||
AddRow(2, 200, `{"order_no":"order-2","amount":7,"remark":"邀请赠送"}`, 1779934590000))
|
AddRow(2, 200, `{"order_no":"order-2","amount":7,"remark":"邀请赠送"}`, 1779934590000))
|
||||||
mock.ExpectQuery("SELECT order_no, user_id FROM `order`").
|
mock.ExpectQuery("SELECT `order`.order_no, `order`.user_id, `order`.subscription_user_id, invitee.referer_id FROM `order`").
|
||||||
WithArgs("order-2").
|
WithArgs("order-2").
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"order_no", "user_id"}).AddRow("order-2", 200))
|
WillReturnRows(sqlmock.NewRows([]string{"order_no", "user_id", "subscription_user_id", "referer_id"}).AddRow("order-2", 200, 200, 100))
|
||||||
|
|
||||||
resp, err := NewGetInviteRecordsLogic(inviteRecordsContext(200, 100), svcCtx).GetInviteRecords(&types.GetInviteRecordsRequest{Page: 1, Size: 10})
|
resp, err := NewGetInviteRecordsLogic(inviteRecordsContext(200, 100), svcCtx).GetInviteRecords(&types.GetInviteRecordsRequest{Page: 1, Size: 10})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -78,16 +74,14 @@ func TestGetInviteRecordsMissingOrderReturnsDirtyRecord(t *testing.T) {
|
|||||||
svcCtx, mock, cleanup := newInviteRecordsTestSvc(t)
|
svcCtx, mock, cleanup := newInviteRecordsTestSvc(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
mock.ExpectQuery("count(*)").
|
expectNoInviteRecordsFamily(t, mock, 100)
|
||||||
WithArgs(34, int64(100), "邀请赠送").
|
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(1))
|
|
||||||
mock.ExpectQuery("SELECT id, object_id, content").
|
mock.ExpectQuery("SELECT id, object_id, content").
|
||||||
WithArgs(34, int64(100), "邀请赠送", 10).
|
WithArgs(34, int64(100), "邀请赠送").
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"id", "object_id", "content", "created_at"}).
|
WillReturnRows(sqlmock.NewRows([]string{"id", "object_id", "content", "created_at"}).
|
||||||
AddRow(3, 100, `{"order_no":"missing-order","amount":7,"remark":"邀请赠送"}`, 1779934600000))
|
AddRow(3, 100, `{"order_no":"missing-order","amount":7,"remark":"邀请赠送"}`, 1779934600000))
|
||||||
mock.ExpectQuery("SELECT order_no, user_id FROM `order`").
|
mock.ExpectQuery("SELECT `order`.order_no, `order`.user_id, `order`.subscription_user_id, invitee.referer_id FROM `order`").
|
||||||
WithArgs("missing-order").
|
WithArgs("missing-order").
|
||||||
WillReturnRows(sqlmock.NewRows([]string{"order_no", "user_id"}))
|
WillReturnRows(sqlmock.NewRows([]string{"order_no", "user_id", "subscription_user_id", "referer_id"}))
|
||||||
|
|
||||||
resp, err := NewGetInviteRecordsLogic(inviteRecordsContext(100, 0), svcCtx).GetInviteRecords(&types.GetInviteRecordsRequest{Page: 1, Size: 10})
|
resp, err := NewGetInviteRecordsLogic(inviteRecordsContext(100, 0), svcCtx).GetInviteRecords(&types.GetInviteRecordsRequest{Page: 1, Size: 10})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -102,6 +96,62 @@ func TestGetInviteRecordsMissingOrderReturnsDirtyRecord(t *testing.T) {
|
|||||||
assertInviteRecordsExpectations(t, mock)
|
assertInviteRecordsExpectations(t, mock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetInviteRecordsFamilyMemberSeesOwnerGiftLog(t *testing.T) {
|
||||||
|
svcCtx, mock, cleanup := newInviteRecordsTestSvc(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
expectInviteRecordsFamilyMember(t, mock, 200, 900)
|
||||||
|
mock.ExpectQuery("SELECT id, object_id, content").
|
||||||
|
WithArgs(34, int64(200), int64(900), "邀请赠送").
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"id", "object_id", "content", "created_at"}).
|
||||||
|
AddRow(4, 900, `{"order_no":"family-order","amount":7,"remark":"邀请赠送"}`, 1779934610000))
|
||||||
|
mock.ExpectQuery("SELECT `order`.order_no, `order`.user_id, `order`.subscription_user_id, invitee.referer_id FROM `order`").
|
||||||
|
WithArgs("family-order").
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"order_no", "user_id", "subscription_user_id", "referer_id"}).AddRow("family-order", 200, 900, 100))
|
||||||
|
|
||||||
|
resp, err := NewGetInviteRecordsLogic(inviteRecordsContext(200, 100), svcCtx).GetInviteRecords(&types.GetInviteRecordsRequest{Page: 1, Size: 10})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetInviteRecords returned error: %v", err)
|
||||||
|
}
|
||||||
|
assertInviteRecordResponse(t, resp, types.InviteRecord{
|
||||||
|
Role: inviteRecordRoleInvitee,
|
||||||
|
PeerHash: hash.InvitePeerHash(100),
|
||||||
|
GiftDays: 7,
|
||||||
|
OrderNo: "family-order",
|
||||||
|
CreatedAt: 1779934610000,
|
||||||
|
})
|
||||||
|
assertInviteRecordsExpectations(t, mock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetInviteRecordsOwnerDoesNotSeeMemberGiftLog(t *testing.T) {
|
||||||
|
svcCtx, mock, cleanup := newInviteRecordsTestSvc(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
expectInviteRecordsFamilyOwner(t, mock, 900)
|
||||||
|
mock.ExpectQuery("SELECT id, object_id, content").
|
||||||
|
WithArgs(34, int64(900), "邀请赠送").
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"id", "object_id", "content", "created_at"}).
|
||||||
|
AddRow(5, 900, `{"order_no":"member-order","amount":7,"remark":"邀请赠送"}`, 1779934620000))
|
||||||
|
mock.ExpectQuery("SELECT `order`.order_no, `order`.user_id, `order`.subscription_user_id, invitee.referer_id FROM `order`").
|
||||||
|
WithArgs("member-order").
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"order_no", "user_id", "subscription_user_id", "referer_id"}).AddRow("member-order", 200, 900, 100))
|
||||||
|
|
||||||
|
resp, err := NewGetInviteRecordsLogic(inviteRecordsContext(900, 0), svcCtx).GetInviteRecords(&types.GetInviteRecordsRequest{Page: 1, Size: 10})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetInviteRecords returned error: %v", err)
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatal("response is nil")
|
||||||
|
}
|
||||||
|
if resp.Total != 0 {
|
||||||
|
t.Fatalf("Total = %d, want 0", resp.Total)
|
||||||
|
}
|
||||||
|
if len(resp.List) != 0 {
|
||||||
|
t.Fatalf("len(List) = %d, want 0", len(resp.List))
|
||||||
|
}
|
||||||
|
assertInviteRecordsExpectations(t, mock)
|
||||||
|
}
|
||||||
|
|
||||||
func newInviteRecordsTestSvc(t *testing.T) (*svc.ServiceContext, sqlmock.Sqlmock, func()) {
|
func newInviteRecordsTestSvc(t *testing.T) (*svc.ServiceContext, sqlmock.Sqlmock, func()) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -126,6 +176,27 @@ func newInviteRecordsTestSvc(t *testing.T) (*svc.ServiceContext, sqlmock.Sqlmock
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func expectNoInviteRecordsFamily(t *testing.T, mock sqlmock.Sqlmock, userId int64) {
|
||||||
|
t.Helper()
|
||||||
|
mock.ExpectQuery("FROM `user_family_member` JOIN user_family").
|
||||||
|
WithArgs(1, userId, 1, 1).
|
||||||
|
WillReturnError(gorm.ErrRecordNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectInviteRecordsFamilyMember(t *testing.T, mock sqlmock.Sqlmock, userId, ownerUserId int64) {
|
||||||
|
t.Helper()
|
||||||
|
mock.ExpectQuery("FROM `user_family_member` JOIN user_family").
|
||||||
|
WithArgs(1, userId, 1, 1).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"family_id", "role", "owner_user_id"}).AddRow(800, 2, ownerUserId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectInviteRecordsFamilyOwner(t *testing.T, mock sqlmock.Sqlmock, userId int64) {
|
||||||
|
t.Helper()
|
||||||
|
mock.ExpectQuery("FROM `user_family_member` JOIN user_family").
|
||||||
|
WithArgs(1, userId, 1, 1).
|
||||||
|
WillReturnRows(sqlmock.NewRows([]string{"family_id", "role", "owner_user_id"}).AddRow(800, 1, userId))
|
||||||
|
}
|
||||||
|
|
||||||
func inviteRecordsContext(userId, refererId int64) context.Context {
|
func inviteRecordsContext(userId, refererId int64) context.Context {
|
||||||
return context.WithValue(context.Background(), constant.CtxKeyUser, &modeluser.User{
|
return context.WithValue(context.Background(), constant.CtxKeyUser, &modeluser.User{
|
||||||
Id: userId,
|
Id: userId,
|
||||||
|
|||||||
Reference in New Issue
Block a user