Files
creator-hub/internal/creator/comment_aggregation.go
T

103 lines
4.2 KiB
Go

package creator
import (
"context"
"fmt"
"time"
)
// CommentAggregationFilter uses comment publication time, not collection time.
type CommentAggregationFilter struct {
SourceType string
SourceID string
Hours int
}
func (filter CommentAggregationFilter) Validate() error {
if filter.SourceType != SourceOwned && filter.SourceType != SourceCompetitor {
return ErrInvalid
}
switch filter.Hours {
case 1, 6, 12, 24, 72, 120, 168:
return nil
default:
return ErrInvalid
}
}
// AggregatedComment adds the owning account and work to an already collected comment.
type AggregatedComment struct {
ID string `json:"id"`
AuthorUID string `json:"author_uid"`
AuthorName string `json:"author_name"`
Content string `json:"content"`
PublishedAt time.Time `json:"published_at"`
SourceType string `json:"source_type"`
SourceID string `json:"source_id"`
SourceName string `json:"source_name"`
SourceAccountKey string `json:"source_account_key"`
WorkID string `json:"work_id"`
WorkKey string `json:"work_key"`
WorkTitle string `json:"work_title"`
WorkURL string `json:"work_url"`
}
func buildCommentAggregationQuery(filter CommentAggregationFilter, now time.Time) (string, []any, error) {
if err := filter.Validate(); err != nil {
return "", nil, err
}
query := ` FROM creator_comment c
JOIN creator_work w ON w.id = c.work_id
LEFT JOIN social_account a ON w.source_type = 'owned' AND a.account_id = w.source_id
LEFT JOIN creator_competitor rival ON w.source_type = 'competitor' AND rival.competitor_id = w.source_id
WHERE w.source_type = $1 AND c.published_at >= $2 AND c.published_at <= $3`
args := []any{filter.SourceType, now.UTC().Add(-time.Duration(filter.Hours) * time.Hour), now.UTC()}
if filter.SourceID != "" {
args = append(args, filter.SourceID)
query += " AND w.source_id = $4"
}
return query, args, nil
}
func (s *Store) ListCommentAggregationPage(ctx context.Context, filter CommentAggregationFilter, page, pageSize int, now time.Time) (Page[AggregatedComment], error) {
offset, limit, err := pageBounds(page, pageSize)
if err != nil {
return Page[AggregatedComment]{}, err
}
from, args, err := buildCommentAggregationQuery(filter, now)
if err != nil {
return Page[AggregatedComment]{}, err
}
result := Page[AggregatedComment]{Data: []AggregatedComment{}, Page: page, PageSize: pageSize}
if err := s.db.QueryRowContext(ctx, "SELECT COUNT(*)"+from, args...).Scan(&result.Total); err != nil {
return Page[AggregatedComment]{}, fmt.Errorf("count comment aggregation source=%s account=%s hours=%d: %w", filter.SourceType, filter.SourceID, filter.Hours, err)
}
query := `SELECT c.comment_id, c.author_uid, c.author_name, c.content, c.published_at,
w.source_type, w.source_id,
CASE WHEN w.source_type = 'owned' THEN COALESCE(a.name, '') ELSE COALESCE(rival.nickname, '') END,
CASE WHEN w.source_type = 'owned' THEN COALESCE(a.platform_account_key, '') ELSE COALESCE(rival.platform_account_key, '') END,
w.work_id, w.work_key, w.title, w.original_url` + from
query += fmt.Sprintf(" ORDER BY c.published_at DESC, c.id DESC LIMIT $%d OFFSET $%d", len(args)+1, len(args)+2)
args = append(args, limit, offset)
rows, err := s.db.QueryContext(ctx, query, args...)
if err != nil {
return Page[AggregatedComment]{}, fmt.Errorf("list comment aggregation source=%s account=%s hours=%d page=%d: %w", filter.SourceType, filter.SourceID, filter.Hours, page, err)
}
defer rows.Close()
for rows.Next() {
var item AggregatedComment
if err := rows.Scan(&item.ID, &item.AuthorUID, &item.AuthorName, &item.Content, &item.PublishedAt,
&item.SourceType, &item.SourceID, &item.SourceName, &item.SourceAccountKey,
&item.WorkID, &item.WorkKey, &item.WorkTitle, &item.WorkURL); err != nil {
return Page[AggregatedComment]{}, fmt.Errorf("scan comment aggregation: %w", err)
}
item.PublishedAt = item.PublishedAt.UTC()
result.Data = append(result.Data, item)
}
if err := rows.Err(); err != nil {
return Page[AggregatedComment]{}, fmt.Errorf("read comment aggregation: %w", err)
}
result.HasNext = offset+len(result.Data) < result.Total
return result, nil
}