package db import ( "context" "database/sql" "encoding/json" "time" ) type DigestCitation struct { Index int `json:"index"` URL string `json:"url"` Title string `json:"title"` Domain string `json:"domain"` } type Digest struct { ID int64 `json:"id"` Topic string `json:"topic"` Region string `json:"region"` ClusterTitle string `json:"clusterTitle"` SummaryRu string `json:"summaryRu"` Citations []DigestCitation `json:"citations"` SourcesCount int `json:"sourcesCount"` FollowUp []string `json:"followUp"` Thumbnail string `json:"thumbnail"` ShortDescription string `json:"shortDescription"` MainURL string `json:"mainUrl"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } type DigestRepository struct { db *PostgresDB } func NewDigestRepository(db *PostgresDB) *DigestRepository { return &DigestRepository{db: db} } func (r *DigestRepository) GetByTopicRegionTitle(ctx context.Context, topic, region, title string) (*Digest, error) { query := ` SELECT id, topic, region, cluster_title, summary_ru, citations, sources_count, follow_up, thumbnail, short_description, main_url, created_at, updated_at FROM digests WHERE topic = $1 AND region = $2 AND cluster_title = $3 ` var d Digest var citationsJSON, followUpJSON []byte err := r.db.db.QueryRowContext(ctx, query, topic, region, title).Scan( &d.ID, &d.Topic, &d.Region, &d.ClusterTitle, &d.SummaryRu, &citationsJSON, &d.SourcesCount, &followUpJSON, &d.Thumbnail, &d.ShortDescription, &d.MainURL, &d.CreatedAt, &d.UpdatedAt, ) if err == sql.ErrNoRows { return nil, nil } if err != nil { return nil, err } json.Unmarshal(citationsJSON, &d.Citations) json.Unmarshal(followUpJSON, &d.FollowUp) return &d, nil } func (r *DigestRepository) GetByURL(ctx context.Context, url string) (*Digest, error) { query := ` SELECT id, topic, region, cluster_title, summary_ru, citations, sources_count, follow_up, thumbnail, short_description, main_url, created_at, updated_at FROM digests WHERE main_url = $1 LIMIT 1 ` var d Digest var citationsJSON, followUpJSON []byte err := r.db.db.QueryRowContext(ctx, query, url).Scan( &d.ID, &d.Topic, &d.Region, &d.ClusterTitle, &d.SummaryRu, &citationsJSON, &d.SourcesCount, &followUpJSON, &d.Thumbnail, &d.ShortDescription, &d.MainURL, &d.CreatedAt, &d.UpdatedAt, ) if err == sql.ErrNoRows { return nil, nil } if err != nil { return nil, err } json.Unmarshal(citationsJSON, &d.Citations) json.Unmarshal(followUpJSON, &d.FollowUp) return &d, nil } func (r *DigestRepository) GetByTopicRegion(ctx context.Context, topic, region string, limit int) ([]*Digest, error) { query := ` SELECT id, topic, region, cluster_title, summary_ru, citations, sources_count, follow_up, thumbnail, short_description, main_url, created_at, updated_at FROM digests WHERE topic = $1 AND region = $2 ORDER BY created_at DESC LIMIT $3 ` rows, err := r.db.db.QueryContext(ctx, query, topic, region, limit) if err != nil { return nil, err } defer rows.Close() var digests []*Digest for rows.Next() { var d Digest var citationsJSON, followUpJSON []byte if err := rows.Scan( &d.ID, &d.Topic, &d.Region, &d.ClusterTitle, &d.SummaryRu, &citationsJSON, &d.SourcesCount, &followUpJSON, &d.Thumbnail, &d.ShortDescription, &d.MainURL, &d.CreatedAt, &d.UpdatedAt, ); err != nil { return nil, err } json.Unmarshal(citationsJSON, &d.Citations) json.Unmarshal(followUpJSON, &d.FollowUp) digests = append(digests, &d) } return digests, nil } func (r *DigestRepository) Upsert(ctx context.Context, d *Digest) error { citationsJSON, _ := json.Marshal(d.Citations) followUpJSON, _ := json.Marshal(d.FollowUp) query := ` INSERT INTO digests (topic, region, cluster_title, summary_ru, citations, sources_count, follow_up, thumbnail, short_description, main_url) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (topic, region, cluster_title) DO UPDATE SET summary_ru = EXCLUDED.summary_ru, citations = EXCLUDED.citations, sources_count = EXCLUDED.sources_count, follow_up = EXCLUDED.follow_up, thumbnail = EXCLUDED.thumbnail, short_description = EXCLUDED.short_description, main_url = EXCLUDED.main_url, updated_at = NOW() ` _, err := r.db.db.ExecContext(ctx, query, d.Topic, d.Region, d.ClusterTitle, d.SummaryRu, citationsJSON, d.SourcesCount, followUpJSON, d.Thumbnail, d.ShortDescription, d.MainURL, ) return err } func (r *DigestRepository) DeleteByTopicRegion(ctx context.Context, topic, region string) (int64, error) { result, err := r.db.db.ExecContext(ctx, "DELETE FROM digests WHERE topic = $1 AND region = $2", topic, region, ) if err != nil { return 0, err } return result.RowsAffected() }