feat: unify owned and competitor work analysis
This commit is contained in:
+53
-30
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user