feat: unify owned and competitor work analysis

This commit is contained in:
2026-10-06 14:40:32 +08:00
parent ca3cbb8d37
commit fa7b78797d
15 changed files with 851 additions and 280 deletions
+53 -30
View File
@@ -510,35 +510,56 @@ func (s *Store) GetWorkByKey(ctx context.Context, platform, workKey string) (Wor
return result, nil
}
func (s *Store) ListWorks(ctx context.Context, filter WorkFilter) ([]Work, error) {
query, args := workSelect, make([]any, 0, 8)
where := make([]string, 0, 7)
func buildWorkQuery(filter WorkFilter) (string, []any, error) {
column := filter.SortBy
if column == "" {
column = "published_at"
}
switch column {
case "published_at", "likes", "comments_count", "shares", "collect_count", "play_count", "likes_growth":
default:
return "", nil, ErrInvalid
}
order := filter.SortOrder
if order == "" {
order = "desc"
}
if order != "asc" && order != "desc" {
return "", nil, ErrInvalid
}
if column == "likes_growth" {
// 增量在读取采样后计算;先保持发布时间与 ID 的稳定顺序。
column, order = "published_at", "desc"
}
if filter.PublishedAfter != nil && filter.PublishedBefore != nil && filter.PublishedAfter.After(*filter.PublishedBefore) {
return "", nil, ErrInvalid
}
query, args := workSelect, make([]any, 0, 11)
where := make([]string, 0, 11)
add := func(clause string, value any) {
args = append(args, value)
where = append(where, fmt.Sprintf(clause, len(args)))
}
if filter.Platform != "" {
if !ValidatePlatform(filter.Platform) {
return nil, ErrInvalid
return "", nil, ErrInvalid
}
add("platform = $%d", filter.Platform)
}
if filter.PublishedAtStatus != "" {
if filter.PublishedAtStatus != "verified" && filter.PublishedAtStatus != "future" && filter.PublishedAtStatus != "pending_verification" && filter.PublishedAtStatus != "invalid" {
return nil, ErrInvalid
return "", nil, ErrInvalid
}
add("published_at_status = $%d", filter.PublishedAtStatus)
}
if filter.SourceType != "" {
if filter.SourceType != SourceOwned && filter.SourceType != SourceCompetitor {
return nil, ErrInvalid
return "", nil, ErrInvalid
}
args = append(args, filter.SourceType)
where = append(where, fmt.Sprintf(`creator_work.source_type = $%d`, len(args)))
add("creator_work.source_type = $%d", filter.SourceType)
}
if filter.SourceID != "" {
args = append(args, filter.SourceID)
where = append(where, fmt.Sprintf(`creator_work.source_id = $%d`, len(args)))
add("creator_work.source_id = $%d", filter.SourceID)
}
if filter.PublishedAfter != nil {
add("published_at >= $%d", filter.PublishedAfter.UTC())
@@ -546,31 +567,30 @@ func (s *Store) ListWorks(ctx context.Context, filter WorkFilter) ([]Work, error
if filter.PublishedBefore != nil {
add("published_at <= $%d", filter.PublishedBefore.UTC())
}
if filter.MinLikes != nil {
if *filter.MinLikes < 0 {
return nil, ErrInvalid
for _, metric := range []struct {
column string
minimum *int64
}{{"likes", filter.MinLikes}, {"comments_count", filter.MinComments}, {"shares", filter.MinShares}, {"collect_count", filter.MinCollectCount}, {"play_count", filter.MinPlayCount}} {
if metric.minimum == nil {
continue
}
where = append(where, fmt.Sprintf("likes IS NOT NULL AND likes >= $%d", len(args)+1))
args = append(args, *filter.MinLikes)
}
if filter.MinComments != nil {
if *filter.MinComments < 0 {
return nil, ErrInvalid
if *metric.minimum < 0 {
return "", nil, ErrInvalid
}
where = append(where, fmt.Sprintf("comments_count IS NOT NULL AND comments_count >= $%d", len(args)+1))
args = append(args, *filter.MinComments)
}
if filter.MinShares != nil {
if *filter.MinShares < 0 {
return nil, ErrInvalid
}
where = append(where, fmt.Sprintf("shares IS NOT NULL AND shares >= $%d", len(args)+1))
args = append(args, *filter.MinShares)
add(metric.column+" IS NOT NULL AND "+metric.column+" >= $%d", *metric.minimum)
}
if len(where) > 0 {
query += ` WHERE ` + strings.Join(where, ` AND `)
}
query += ` ORDER BY published_at DESC NULLS LAST, id`
query += " ORDER BY " + column + " " + strings.ToUpper(order) + " NULLS LAST, id"
return query, args, nil
}
func (s *Store) ListWorks(ctx context.Context, filter WorkFilter) ([]Work, error) {
query, args, err := buildWorkQuery(filter)
if err != nil {
return nil, err
}
rows, err := s.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, databaseError(err)
@@ -780,7 +800,7 @@ func (s *Store) ListWorksPage(ctx context.Context, filter WorkFilter, page, page
if err != nil {
return Page[Work]{}, err
}
if filter.Growth.Hours != 0 || filter.Growth.MinLikes != nil {
if filter.Growth.Hours != 0 || filter.Growth.MinLikes != nil || filter.SortBy == "likes_growth" {
ids := make([]string, 0, len(items))
for _, item := range items {
ids = append(ids, item.ID)
@@ -794,6 +814,9 @@ func (s *Store) ListWorksPage(ctx context.Context, filter WorkFilter, page, page
return Page[Work]{}, err
}
}
if filter.SortBy == "likes_growth" {
sortWorksGrowth(items, filter.SortOrder)
}
return slicePage(items, page, pageSize)
}