package subscribe import ( "context" stderrors "errors" "strings" "time" "github.com/go-sql-driver/mysql" commonLogic "github.com/perfect-panel/server/internal/logic/common" "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/xerr" "github.com/pkg/errors" "gorm.io/gorm" ) const ( promoRuleTypeNewUser = "new_user" promoRuleTypeInactiveUser = "inactive_user" promoRuleTypeCampaign = "campaign" ) type subscribePromoCandidate struct { SubscribeId int64 `gorm:"column:subscribe_id"` Quantity int64 `gorm:"column:quantity"` RuleName string `gorm:"column:rule_name"` RuleType string `gorm:"column:rule_type"` PromoPrice int64 `gorm:"column:promo_price"` Params string `gorm:"column:params"` StartTime *time.Time `gorm:"column:start_time"` EndTime *time.Time `gorm:"column:end_time"` } func loadSubscribePromoMap(ctx context.Context, svcCtx *svc.ServiceContext, subscribeIDs []int64) (map[int64]map[int64]*types.SubscribePromo, error) { result := make(map[int64]map[int64]*types.SubscribePromo) if len(subscribeIDs) == 0 || svcCtx == nil || svcCtx.DB == nil { return result, nil } userInfo, _ := ctx.Value(constant.CtxKeyUser).(*user.User) userID := int64(0) if userInfo != nil { entitlement, err := commonLogic.ResolveEntitlementUser(ctx, svcCtx.DB, userInfo.Id) if err != nil { return nil, err } userID = entitlement.EffectiveUserID } candidates, err := querySubscribePromoCandidates(ctx, svcCtx, subscribeIDs, userInfo != nil) if err != nil { if isMissingPromoTableError(err) { return result, nil } return nil, err } for _, candidate := range candidates { if candidate.Quantity <= 0 { continue } if result[candidate.SubscribeId] == nil { result[candidate.SubscribeId] = make(map[int64]*types.SubscribePromo) } if _, exists := result[candidate.SubscribeId][candidate.Quantity]; exists { continue } promoResult, err := commonLogic.EvaluatePromo(ctx, svcCtx, userID, candidate.SubscribeId, candidate.Quantity) if err != nil { return nil, err } if promoResult == nil || !promoResult.Eligible { continue } result[candidate.SubscribeId][candidate.Quantity] = &types.SubscribePromo{ RuleName: promoResult.RuleName, RuleType: promoResult.RuleType, PromoPrice: promoResult.PromoPrice, ExpiresAt: unixSeconds(promoResult.ExpiresAt), } } return result, nil } func querySubscribePromoCandidates(ctx context.Context, svcCtx *svc.ServiceContext, subscribeIDs []int64, loggedIn bool) ([]subscribePromoCandidate, error) { var candidates []subscribePromoCandidate err := subscribePromoCandidatesQuery(ctx, svcCtx.DB, subscribeIDs, loggedIn). Scan(&candidates).Error if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query subscribe promo candidates failed: %v", err) } return candidates, nil } func subscribePromoCandidatesQuery(ctx context.Context, db *gorm.DB, subscribeIDs []int64, loggedIn bool) *gorm.DB { query := db.WithContext(ctx). Table("subscribe_promo AS sp"). Select("sp.subscribe_id, sp.quantity, sp.promo_price, pr.name AS rule_name, pr.type AS rule_type, pr.params, pr.start_time, pr.end_time"). Joins("JOIN promo_rule AS pr ON pr.id = sp.promo_rule_id AND pr.deleted_at IS NULL"). Where("sp.subscribe_id IN ? AND sp.promo_price > 0 AND pr.enabled = ?", subscribeIDs, true) if !loggedIn { query = query.Where("pr.type = ?", promoRuleTypeCampaign) } return query. Order("sp.subscribe_id ASC"). Order("sp.quantity ASC"). Order("pr.priority DESC"). Order("pr.id ASC") } func unixSeconds(t time.Time) int64 { if t.IsZero() { return 0 } return t.Unix() } func isMissingPromoTableError(err error) bool { var mysqlErr *mysql.MySQLError if stderrors.As(err, &mysqlErr) { return mysqlErr.Number == 1146 } return strings.Contains(err.Error(), "Error 1146") }