package db import ( "fmt" "log" "time" "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/appview/models" ) func UpsertReaction(e Execer, reaction models.Reaction) error { _, err := e.Exec( `insert into reactions (did, rkey, subject_at, kind, created) values (?, ?, ?, ?, ?) on conflict(did, rkey) do update set subject_at = excluded.subject_at, kind = excluded.kind, created = excluded.created`, reaction.ReactedByDid, reaction.Rkey, reaction.ThreadAt, reaction.Kind, reaction.Created.Format(time.RFC3339), ) return err } // Get a reaction record func GetReaction(e Execer, did string, subjectAt syntax.ATURI, kind models.ReactionKind) (*models.Reaction, error) { query := ` select did, subject_at, created, rkey from reactions where did = ? and subject_at = ? and kind = ?` row := e.QueryRow(query, did, subjectAt, kind) var reaction models.Reaction var created string err := row.Scan(&reaction.ReactedByDid, &reaction.ThreadAt, &created, &reaction.Rkey) if err != nil { return nil, err } createdAtTime, err := time.Parse(time.RFC3339, created) if err != nil { log.Println("unable to determine followed at time") reaction.Created = time.Now() } else { reaction.Created = createdAtTime } return &reaction, nil } // Remove a reaction func DeleteReaction(e Execer, did syntax.DID, subjectAt syntax.ATURI, kind models.ReactionKind) ([]syntax.ATURI, error) { var deleted []syntax.ATURI rows, err := e.Query( `delete from reactions where did = ? and subject_at = ? and kind = ? returning at_uri`, did, subjectAt, kind, ) if err != nil { return nil, fmt.Errorf("deleting stars: %w", err) } defer rows.Close() for rows.Next() { var aturi syntax.ATURI if err := rows.Scan(&aturi); err != nil { return nil, fmt.Errorf("scanning at_uri: %w", err) } deleted = append(deleted, aturi) } return deleted, nil } // Remove a reaction func DeleteReactionByRkey(e Execer, did string, rkey string) error { _, err := e.Exec(`delete from reactions where did = ? and rkey = ?`, did, rkey) return err } func GetReactionCount(e Execer, subjectAt syntax.ATURI, kind models.ReactionKind) (int, error) { count := 0 err := e.QueryRow( `select count(did) from reactions where subject_at = ? and kind = ?`, subjectAt, kind).Scan(&count) if err != nil { return 0, err } return count, nil } func GetReactionMap(e Execer, userLimit int, subjectAt syntax.ATURI) (map[models.ReactionKind]models.ReactionDisplayData, error) { query := ` select kind, did, row_number() over (partition by kind order by created asc) as rn, count(*) over (partition by kind) as total from reactions where subject_at = ? order by kind, created asc` rows, err := e.Query(query, subjectAt) if err != nil { return nil, err } defer rows.Close() reactionMap := map[models.ReactionKind]models.ReactionDisplayData{} for _, kind := range models.OrderedReactionKinds { reactionMap[kind] = models.ReactionDisplayData{Count: 0, Users: []string{}} } for rows.Next() { var kind models.ReactionKind var did string var rn, total int if err := rows.Scan(&kind, &did, &rn, &total); err != nil { return nil, err } data := reactionMap[kind] data.Count = total if userLimit > 0 && rn <= userLimit { data.Users = append(data.Users, did) } reactionMap[kind] = data } return reactionMap, rows.Err() } func GetReactionStatus(e Execer, userDid string, threadAt syntax.ATURI, kind models.ReactionKind) bool { if _, err := GetReaction(e, userDid, threadAt, kind); err != nil { return false } else { return true } } func GetReactionStatusMap(e Execer, userDid string, threadAt syntax.ATURI) map[models.ReactionKind]bool { statusMap := map[models.ReactionKind]bool{} for _, kind := range models.OrderedReactionKinds { count := GetReactionStatus(e, userDid, threadAt, kind) statusMap[kind] = count } return statusMap }