feat: add monitoring marked articles and retention updates
Frontend CI / Frontend (push) Successful in 7m47s
Backend CI / Backend (push) Successful in 19m26s

This commit is contained in:
2026-06-17 12:48:41 +08:00
parent 9ed857e159
commit 31c4dd9358
40 changed files with 2373 additions and 488 deletions
+25 -11
View File
@@ -96,6 +96,20 @@ type BrandLibrarySummaryResponse struct {
MaxQuestionsPerBrand int `json:"max_questions_per_brand"`
}
func formatBrandTime(value interface{}) string {
switch typed := value.(type) {
case time.Time:
return typed.UTC().Format(time.RFC3339Nano)
case *time.Time:
if typed == nil {
return ""
}
return typed.UTC().Format(time.RFC3339Nano)
default:
return fmt.Sprintf("%v", value)
}
}
func (s *BrandService) List(ctx context.Context) ([]BrandResponse, error) {
actor := auth.MustActor(ctx)
brands, err := sharedcache.LoadJSON(ctx, s.cache, &s.cacheGroup, brandListCacheKey(actor.TenantID), defaultCacheTTL(), func(loadCtx context.Context) ([]BrandResponse, error) {
@@ -197,7 +211,7 @@ func (s *BrandService) Create(ctx context.Context, req BrandRequest) (*BrandResp
KeywordCount: 0,
QuestionCount: 0,
CompetitorCount: 0,
CreatedAt: fmt.Sprintf("%v", ca),
CreatedAt: formatBrandTime(ca),
}, nil
}
@@ -367,7 +381,7 @@ func (s *BrandService) CreateKeyword(ctx context.Context, brandID int64, req Key
return nil, response.ErrInternal(50010, "create_failed", "failed to create keyword")
}
invalidateBrandCaches(ctx, s.cache, actor.TenantID, brandID)
return &KeywordResponse{ID: id, BrandID: brandID, Name: req.Name, Status: "active", CreatedAt: fmt.Sprintf("%v", ca)}, nil
return &KeywordResponse{ID: id, BrandID: brandID, Name: req.Name, Status: "active", CreatedAt: formatBrandTime(ca)}, nil
}
func (s *BrandService) UpdateKeyword(ctx context.Context, brandID, keywordID int64, req KeywordRequest) error {
@@ -587,7 +601,7 @@ func (s *BrandService) CreateQuestion(ctx context.Context, brandID int64, req Qu
KeywordID: req.KeywordID,
QuestionText: req.QuestionText,
Status: "active",
CreatedAt: fmt.Sprintf("%v", ca),
CreatedAt: formatBrandTime(ca),
}, nil
}
@@ -718,7 +732,7 @@ func (s *BrandService) CreateCompetitor(ctx context.Context, brandID int64, req
return nil, response.ErrInternal(50010, "create_failed", "failed to create competitor")
}
invalidateBrandCaches(ctx, s.cache, actor.TenantID, brandID)
return &CompetitorResponse{ID: id, BrandID: brandID, Name: req.Name, Website: req.Website, Description: req.Description, ProductLinesJSON: req.ProductLinesJSON, CreatedAt: fmt.Sprintf("%v", ca)}, nil
return &CompetitorResponse{ID: id, BrandID: brandID, Name: req.Name, Website: req.Website, Description: req.Description, ProductLinesJSON: req.ProductLinesJSON, CreatedAt: formatBrandTime(ca)}, nil
}
func (s *BrandService) UpdateCompetitor(ctx context.Context, brandID, competitorID int64, req CompetitorRequest) error {
@@ -804,8 +818,8 @@ func (s *BrandService) loadBrands(ctx context.Context, tenantID int64) ([]BrandR
if err := rows.Scan(&item.ID, &item.Name, &item.Website, &item.Description, &item.Status, &item.KeywordCount, &item.QuestionCount, &item.CompetitorCount, &createdAt, &updatedAt); err != nil {
return nil, response.ErrInternal(50010, "scan_failed", err.Error())
}
item.CreatedAt = fmt.Sprintf("%v", createdAt)
item.UpdatedAt = fmt.Sprintf("%v", updatedAt)
item.CreatedAt = formatBrandTime(createdAt)
item.UpdatedAt = formatBrandTime(updatedAt)
brands = append(brands, item)
}
return brands, nil
@@ -859,8 +873,8 @@ func (s *BrandService) loadBrandDetail(ctx context.Context, tenantID, brandID in
}
return nil, false, response.ErrInternal(50010, "query_failed", "failed to fetch brand")
}
item.CreatedAt = fmt.Sprintf("%v", createdAt)
item.UpdatedAt = fmt.Sprintf("%v", updatedAt)
item.CreatedAt = formatBrandTime(createdAt)
item.UpdatedAt = formatBrandTime(updatedAt)
return &item, true, nil
}
@@ -885,7 +899,7 @@ func (s *BrandService) loadBrandKeywords(ctx context.Context, tenantID, brandID
if err := rows.Scan(&item.ID, &item.BrandID, &item.Name, &item.Status, &createdAt); err != nil {
return nil, response.ErrInternal(50010, "scan_failed", err.Error())
}
item.CreatedAt = fmt.Sprintf("%v", createdAt)
item.CreatedAt = formatBrandTime(createdAt)
items = append(items, item)
}
return items, nil
@@ -943,7 +957,7 @@ func (s *BrandService) loadBrandQuestions(ctx context.Context, tenantID, brandID
if err := rows.Scan(&item.ID, &item.BrandID, &item.KeywordID, &item.QuestionText, &item.Status, &createdAt); err != nil {
return nil, response.ErrInternal(50010, "scan_failed", err.Error())
}
item.CreatedAt = fmt.Sprintf("%v", createdAt)
item.CreatedAt = formatBrandTime(createdAt)
items = append(items, item)
}
if err := rows.Err(); err != nil {
@@ -978,7 +992,7 @@ func (s *BrandService) loadBrandCompetitors(ctx context.Context, tenantID, brand
if err := rows.Scan(&item.ID, &item.BrandID, &item.Name, &item.Website, &item.Description, &productLinesJSON, &createdAt); err != nil {
return nil, response.ErrInternal(50010, "scan_failed", err.Error())
}
item.CreatedAt = fmt.Sprintf("%v", createdAt)
item.CreatedAt = formatBrandTime(createdAt)
if productLinesJSON != nil {
raw := json.RawMessage(productLinesJSON)
item.ProductLinesJSON = &raw