A vibe coded tangled fork which supports pijul.
1package db
2
3import (
4 "fmt"
5 "log"
6 "time"
7
8 "github.com/bluesky-social/indigo/atproto/syntax"
9 "tangled.org/core/appview/models"
10)
11
12func AddReaction(e Execer, did string, subjectAt syntax.ATURI, kind models.ReactionKind, rkey string) error {
13 query := `insert or ignore into reactions (did, subject_at, kind, rkey) values (?, ?, ?, ?)`
14 _, err := e.Exec(query, did, subjectAt, kind, rkey)
15 return err
16}
17
18// Get a reaction record
19func GetReaction(e Execer, did string, subjectAt syntax.ATURI, kind models.ReactionKind) (*models.Reaction, error) {
20 query := `
21 select did, subject_at, created, rkey
22 from reactions
23 where did = ? and subject_at = ? and kind = ?`
24 row := e.QueryRow(query, did, subjectAt, kind)
25
26 var reaction models.Reaction
27 var created string
28 err := row.Scan(&reaction.ReactedByDid, &reaction.ThreadAt, &created, &reaction.Rkey)
29 if err != nil {
30 return nil, err
31 }
32
33 createdAtTime, err := time.Parse(time.RFC3339, created)
34 if err != nil {
35 log.Println("unable to determine followed at time")
36 reaction.Created = time.Now()
37 } else {
38 reaction.Created = createdAtTime
39 }
40
41 return &reaction, nil
42}
43
44// Remove a reaction
45func DeleteReaction(e Execer, did syntax.DID, subjectAt syntax.ATURI, kind models.ReactionKind) ([]syntax.ATURI, error) {
46 var deleted []syntax.ATURI
47 rows, err := e.Query(
48 `delete from reactions
49 where did = ? and subject_at = ? and kind = ?
50 returning at_uri`,
51 did,
52 subjectAt,
53 kind,
54 )
55 if err != nil {
56 return nil, fmt.Errorf("deleting stars: %w", err)
57 }
58 defer rows.Close()
59
60 for rows.Next() {
61 var aturi syntax.ATURI
62 if err := rows.Scan(&aturi); err != nil {
63 return nil, fmt.Errorf("scanning at_uri: %w", err)
64 }
65 deleted = append(deleted, aturi)
66 }
67 return deleted, nil
68}
69
70// Remove a reaction
71func DeleteReactionByRkey(e Execer, did string, rkey string) error {
72 _, err := e.Exec(`delete from reactions where did = ? and rkey = ?`, did, rkey)
73 return err
74}
75
76func GetReactionCount(e Execer, subjectAt syntax.ATURI, kind models.ReactionKind) (int, error) {
77 count := 0
78 err := e.QueryRow(
79 `select count(did) from reactions where subject_at = ? and kind = ?`, subjectAt, kind).Scan(&count)
80 if err != nil {
81 return 0, err
82 }
83 return count, nil
84}
85
86func GetReactionMap(e Execer, userLimit int, subjectAt syntax.ATURI) (map[models.ReactionKind]models.ReactionDisplayData, error) {
87 query := `
88 select kind, did,
89 row_number() over (partition by kind order by created asc) as rn,
90 count(*) over (partition by kind) as total
91 from reactions
92 where subject_at = ?
93 order by kind, created asc`
94
95 rows, err := e.Query(query, subjectAt)
96 if err != nil {
97 return nil, err
98 }
99 defer rows.Close()
100
101 reactionMap := map[models.ReactionKind]models.ReactionDisplayData{}
102 for _, kind := range models.OrderedReactionKinds {
103 reactionMap[kind] = models.ReactionDisplayData{Count: 0, Users: []string{}}
104 }
105
106 for rows.Next() {
107 var kind models.ReactionKind
108 var did string
109 var rn, total int
110 if err := rows.Scan(&kind, &did, &rn, &total); err != nil {
111 return nil, err
112 }
113
114 data := reactionMap[kind]
115 data.Count = total
116 if userLimit > 0 && rn <= userLimit {
117 data.Users = append(data.Users, did)
118 }
119 reactionMap[kind] = data
120 }
121
122 return reactionMap, rows.Err()
123}
124
125func GetReactionStatus(e Execer, userDid string, threadAt syntax.ATURI, kind models.ReactionKind) bool {
126 if _, err := GetReaction(e, userDid, threadAt, kind); err != nil {
127 return false
128 } else {
129 return true
130 }
131}
132
133func GetReactionStatusMap(e Execer, userDid string, threadAt syntax.ATURI) map[models.ReactionKind]bool {
134 statusMap := map[models.ReactionKind]bool{}
135 for _, kind := range models.OrderedReactionKinds {
136 count := GetReactionStatus(e, userDid, threadAt, kind)
137 statusMap[kind] = count
138 }
139 return statusMap
140}