1package db
2
3import (
4 "log"
5 "time"
6
7 "github.com/bluesky-social/indigo/atproto/syntax"
8)
9
10type ReactionKind string
11
12const (
13 Like ReactionKind = "👍"
14 Unlike ReactionKind = "👎"
15 Laugh ReactionKind = "😆"
16 Celebration ReactionKind = "🎉"
17 Confused ReactionKind = "🫤"
18 Heart ReactionKind = "❤️"
19 Rocket ReactionKind = "🚀"
20 Eyes ReactionKind = "👀"
21)
22
23func (rk ReactionKind) String() string {
24 return string(rk)
25}
26
27var OrderedReactionKinds = []ReactionKind{
28 Like,
29 Unlike,
30 Laugh,
31 Celebration,
32 Confused,
33 Heart,
34 Rocket,
35 Eyes,
36}
37
38func ParseReactionKind(raw string) (ReactionKind, bool) {
39 k, ok := (map[string]ReactionKind{
40 "👍": Like,
41 "👎": Unlike,
42 "😆": Laugh,
43 "🎉": Celebration,
44 "🫤": Confused,
45 "❤️": Heart,
46 "🚀": Rocket,
47 "👀": Eyes,
48 })[raw]
49 return k, ok
50}
51
52type Reaction struct {
53 ReactedByDid string
54 ThreadAt syntax.ATURI
55 Created time.Time
56 Rkey string
57 Kind ReactionKind
58}
59
60func AddReaction(e Execer, reactedByDid string, threadAt syntax.ATURI, kind ReactionKind, rkey string) error {
61 query := `insert or ignore into reactions (reacted_by_did, thread_at, kind, rkey) values (?, ?, ?, ?)`
62 _, err := e.Exec(query, reactedByDid, threadAt, kind, rkey)
63 return err
64}
65
66// Get a reaction record
67func GetReaction(e Execer, reactedByDid string, threadAt syntax.ATURI, kind ReactionKind) (*Reaction, error) {
68 query := `
69 select reacted_by_did, thread_at, created, rkey
70 from reactions
71 where reacted_by_did = ? and thread_at = ? and kind = ?`
72 row := e.QueryRow(query, reactedByDid, threadAt, kind)
73
74 var reaction Reaction
75 var created string
76 err := row.Scan(&reaction.ReactedByDid, &reaction.ThreadAt, &created, &reaction.Rkey)
77 if err != nil {
78 return nil, err
79 }
80
81 createdAtTime, err := time.Parse(time.RFC3339, created)
82 if err != nil {
83 log.Println("unable to determine followed at time")
84 reaction.Created = time.Now()
85 } else {
86 reaction.Created = createdAtTime
87 }
88
89 return &reaction, nil
90}
91
92// Remove a reaction
93func DeleteReaction(e Execer, reactedByDid string, threadAt syntax.ATURI, kind ReactionKind) error {
94 _, err := e.Exec(`delete from reactions where reacted_by_did = ? and thread_at = ? and kind = ?`, reactedByDid, threadAt, kind)
95 return err
96}
97
98// Remove a reaction
99func DeleteReactionByRkey(e Execer, reactedByDid string, rkey string) error {
100 _, err := e.Exec(`delete from reactions where reacted_by_did = ? and rkey = ?`, reactedByDid, rkey)
101 return err
102}
103
104func GetReactionCount(e Execer, threadAt syntax.ATURI, kind ReactionKind) (int, error) {
105 count := 0
106 err := e.QueryRow(
107 `select count(reacted_by_did) from reactions where thread_at = ? and kind = ?`, threadAt, kind).Scan(&count)
108 if err != nil {
109 return 0, err
110 }
111 return count, nil
112}
113
114func GetReactionCountMap(e Execer, threadAt syntax.ATURI) (map[ReactionKind]int, error) {
115 countMap := map[ReactionKind]int{}
116 for _, kind := range OrderedReactionKinds {
117 count, err := GetReactionCount(e, threadAt, kind)
118 if err != nil {
119 return map[ReactionKind]int{}, nil
120 }
121 countMap[kind] = count
122 }
123 return countMap, nil
124}
125
126func GetReactionStatus(e Execer, userDid string, threadAt syntax.ATURI, kind ReactionKind) bool {
127 if _, err := GetReaction(e, userDid, threadAt, kind); err != nil {
128 return false
129 } else {
130 return true
131 }
132}
133
134func GetReactionStatusMap(e Execer, userDid string, threadAt syntax.ATURI) map[ReactionKind]bool {
135 statusMap := map[ReactionKind]bool{}
136 for _, kind := range OrderedReactionKinds {
137 count := GetReactionStatus(e, userDid, threadAt, kind)
138 statusMap[kind] = count
139 }
140 return statusMap
141}