fix: harden competitor create and batch kol stats
Backend CI / Backend (push) Failing after 6m48s

This commit is contained in:
2026-05-13 17:31:22 +08:00
parent 7aa786fbf4
commit 18cdbf25f8
5 changed files with 195 additions and 28 deletions
+18 -1
View File
@@ -639,6 +639,20 @@ func (s *BrandService) ListCompetitors(ctx context.Context, brandID int64) ([]Co
func (s *BrandService) CreateCompetitor(ctx context.Context, brandID int64, req CompetitorRequest) (*CompetitorResponse, error) {
actor := auth.MustActor(ctx)
req.Name = strings.TrimSpace(req.Name)
req.Website = normalizeOptionalString(req.Website)
req.Description = normalizeOptionalString(req.Description)
if req.Name == "" {
return nil, response.ErrBadRequest(40001, "invalid_params", "name is required")
}
exists, err := s.brandExists(ctx, actor.TenantID, brandID)
if err != nil {
return nil, err
}
if !exists {
return nil, response.ErrNotFound(40420, "brand_not_found", "brand not found")
}
var plJSON []byte
if req.ProductLinesJSON != nil {
plJSON = *req.ProductLinesJSON
@@ -646,11 +660,14 @@ func (s *BrandService) CreateCompetitor(ctx context.Context, brandID int64, req
var id int64
var ca interface{}
err := s.pool.QueryRow(ctx, `
err = s.pool.QueryRow(ctx, `
INSERT INTO competitors (tenant_id, brand_id, name, website, description, product_lines_json)
VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, created_at
`, actor.TenantID, brandID, req.Name, req.Website, req.Description, plJSON).Scan(&id, &ca)
if err != nil {
if isForeignKeyConstraintError(err) {
return nil, response.ErrNotFound(40420, "brand_not_found", "brand not found")
}
return nil, response.ErrInternal(50010, "create_failed", "failed to create competitor")
}
invalidateBrandCaches(ctx, s.cache, actor.TenantID, brandID)
@@ -111,19 +111,18 @@ func (s *KolDashboardService) PerPackage(ctx context.Context, actor auth.Actor)
usageByPackage[stat.PackageID] = stat
}
subscriberCounts, err := s.subRepo.CountActiveSubscriberTenantsByPackages(ctx, packageIDs)
if err != nil {
return nil, response.ErrInternal(50123, "kol_dashboard_subscriber_query_failed", err.Error())
}
result := make([]DashboardPackageStat, 0, len(packages))
for _, pkg := range packages {
subscribers, err := s.subRepo.ListActiveSubscribersForPackage(ctx, pkg.ID)
if err != nil {
return nil, response.ErrInternal(50123, "kol_dashboard_subscriber_query_failed", err.Error())
}
subscriberTenants := uniqueSubscriberTenants(subscribers)
usage := usageByPackage[pkg.ID]
result = append(result, DashboardPackageStat{
PackageID: pkg.ID,
PackageName: pkg.Name,
SubscriberTenants: subscriberTenants,
SubscriberTenants: subscriberCounts[pkg.ID],
UsageCount: usage.Total,
FailureRate: float64(usage.Failed) / float64(maxDashboardBase(usage.Total)),
})
@@ -80,9 +80,19 @@ func (s *KolPackageService) List(ctx context.Context, actor auth.Actor) ([]KolPa
return nil, response.ErrInternal(50062, "kol_package_query_failed", err.Error())
}
packageIDs := kolPackageIDs(packages)
promptCounts, err := s.packagePromptCounts(ctx, actor.TenantID, packageIDs)
if err != nil {
return nil, err
}
selfSubscriptions, err := s.packageSelfSubscriptions(ctx, actor.TenantID, packageIDs)
if err != nil {
return nil, err
}
result := make([]KolPackageResponse, 0, len(packages))
for _, pkg := range packages {
item, err := s.buildPackageResponse(ctx, profile, &pkg)
item, err := s.buildPackageResponseWithStats(profile, &pkg, promptCounts[pkg.ID], selfSubscriptions[pkg.ID])
if err != nil {
return nil, err
}
@@ -299,30 +309,31 @@ func (s *KolPackageService) buildPackageResponse(
ctx context.Context,
profile *repository.KolProfile,
pkg *repository.KolPackage,
) (*KolPackageResponse, error) {
promptCounts, err := s.packagePromptCounts(ctx, pkg.TenantID, []int64{pkg.ID})
if err != nil {
return nil, err
}
selfSubscriptions, err := s.packageSelfSubscriptions(ctx, pkg.TenantID, []int64{pkg.ID})
if err != nil {
return nil, err
}
return s.buildPackageResponseWithStats(profile, pkg, promptCounts[pkg.ID], selfSubscriptions[pkg.ID])
}
func (s *KolPackageService) buildPackageResponseWithStats(
profile *repository.KolProfile,
pkg *repository.KolPackage,
promptCount int,
selfSubscription *repository.KolSubscription,
) (*KolPackageResponse, error) {
tags, err := decodeKolStringList(pkg.Tags)
if err != nil {
return nil, response.ErrInternal(50068, "kol_package_decode_failed", err.Error())
}
promptCount := 0
if s.promptRepo != nil {
prompts, err := s.promptRepo.ListByPackage(ctx, pkg.TenantID, pkg.ID)
if err != nil {
return nil, response.ErrInternal(50069, "kol_prompt_query_failed", err.Error())
}
promptCount = len(prompts)
}
var selfSubscription *KolSubscriptionResponse
if s.subRepo != nil {
sub, err := s.subRepo.GetActiveForTenant(ctx, pkg.TenantID, pkg.ID)
if err != nil {
return nil, response.ErrInternal(50093, "kol_subscription_query_failed", err.Error())
}
selfSubscription = newKolSubscriptionResponse(sub)
}
return &KolPackageResponse{
ID: pkg.ID,
KolProfileID: pkg.KolProfileID,
@@ -337,12 +348,48 @@ func (s *KolPackageService) buildPackageResponse(
Status: pkg.Status,
PromptCount: promptCount,
SubscriberCount: 0,
SelfSubscription: selfSubscription,
SelfSubscription: newKolSubscriptionResponse(selfSubscription),
CreatedAt: pkg.CreatedAt,
UpdatedAt: pkg.UpdatedAt,
}, nil
}
func (s *KolPackageService) packagePromptCounts(ctx context.Context, tenantID int64, packageIDs []int64) (map[int64]int, error) {
if s.promptRepo == nil {
return map[int64]int{}, nil
}
counts, err := s.promptRepo.CountByPackages(ctx, tenantID, packageIDs)
if err != nil {
return nil, response.ErrInternal(50069, "kol_prompt_query_failed", err.Error())
}
return counts, nil
}
func (s *KolPackageService) packageSelfSubscriptions(ctx context.Context, tenantID int64, packageIDs []int64) (map[int64]*repository.KolSubscription, error) {
result := make(map[int64]*repository.KolSubscription, len(packageIDs))
if s.subRepo == nil {
return result, nil
}
subscriptions, err := s.subRepo.ListActiveForTenantByPackages(ctx, tenantID, packageIDs)
if err != nil {
return nil, response.ErrInternal(50093, "kol_subscription_query_failed", err.Error())
}
for packageID, sub := range subscriptions {
subscription := sub
result[packageID] = &subscription
}
return result, nil
}
func kolPackageIDs(packages []repository.KolPackage) []int64 {
ids := make([]int64, 0, len(packages))
for _, pkg := range packages {
ids = append(ids, pkg.ID)
}
return ids
}
func normalizeKolOptionalString(value *string) *string {
if value == nil {
return nil