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