This repository has no description
1package db
2
3import (
4 "database/sql"
5 "encoding/json"
6 "fmt"
7 "sort"
8 "strings"
9 "time"
10
11 "github.com/bluesky-social/indigo/api/atproto"
12 "github.com/bluesky-social/indigo/atproto/syntax"
13 "tangled.org/core/api/tangled"
14 "tangled.org/core/appview/models"
15 "tangled.org/core/orm"
16)
17
18func PutComment(tx *sql.Tx, c *models.Comment, references []syntax.ATURI) error {
19 if c.Collection == "" {
20 c.Collection = tangled.FeedCommentNSID
21 }
22
23 var bodyBlobs, replyToUri, replyToCid *string
24 if len(c.Body.Blobs) > 0 {
25 encoded, err := json.Marshal(c.Body.Blobs)
26 if err != nil {
27 return fmt.Errorf("encoding blobs to json: %w", err)
28 }
29 encodedStr := string(encoded)
30 bodyBlobs = &encodedStr
31 }
32 if c.ReplyTo != nil {
33 replyToUri = &c.ReplyTo.Uri
34 replyToCid = &c.ReplyTo.Cid
35 }
36 result, err := tx.Exec(
37 // users can change the 'created' date.
38 // skip update entirely if cid is unchanged.
39 `insert into comments (
40 did,
41 collection,
42 rkey,
43 cid,
44 subject_uri,
45 subject_cid,
46 body_text,
47 body_original,
48 body_blobs,
49 created,
50 reply_to_uri,
51 reply_to_cid,
52 pull_round_idx
53 )
54 values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
55 on conflict(did, collection, rkey)
56 do update set
57 cid = excluded.cid,
58 subject_uri = excluded.subject_uri,
59 subject_cid = excluded.subject_cid,
60 body_text = excluded.body_text,
61 body_original = excluded.body_original,
62 body_blobs = excluded.body_blobs,
63 created = excluded.created,
64 reply_to_uri = excluded.reply_to_uri,
65 reply_to_cid = excluded.reply_to_cid,
66 pull_round_idx = excluded.pull_round_idx,
67 edited = ?
68 where comments.cid is not excluded.cid`,
69 c.Did,
70 c.Collection,
71 c.Rkey,
72 c.Cid,
73 c.Subject.Uri,
74 c.Subject.Cid,
75 c.Body.Text,
76 c.Body.Original,
77 bodyBlobs,
78 c.Created.Format(time.RFC3339),
79 replyToUri,
80 replyToCid,
81 c.PullRoundIdx,
82 time.Now().Format(time.RFC3339),
83 )
84 if err != nil {
85 return err
86 }
87
88 c.Id, err = result.LastInsertId()
89 if err != nil {
90 return err
91 }
92
93 affected, err := result.RowsAffected()
94 if err != nil {
95 return err
96 }
97
98 if affected > 0 {
99 // update references when comment is updated
100 if err := putReferences(tx, c.AtUri(), references); err != nil {
101 return fmt.Errorf("put reference_links: %w", err)
102 }
103 }
104
105 return nil
106}
107
108// PurgeComments actually purges a comment row from db instead of marking it as "deleted"
109func PurgeComments(e Execer, filters ...orm.Filter) error {
110 var conditions []string
111 var args []any
112 for _, filter := range filters {
113 conditions = append(conditions, filter.Condition())
114 args = append(args, filter.Arg()...)
115 }
116
117 whereClause := ""
118 if conditions != nil {
119 whereClause = " where " + strings.Join(conditions, " and ")
120 }
121
122 _, err := e.Exec(fmt.Sprintf(`delete from comments %s`, whereClause), args...)
123 return err
124}
125
126func DeleteComments(e Execer, filters ...orm.Filter) error {
127 var conditions []string
128 var args []any
129 for _, filter := range filters {
130 conditions = append(conditions, filter.Condition())
131 args = append(args, filter.Arg()...)
132 }
133
134 whereClause := ""
135 if conditions != nil {
136 whereClause = " where " + strings.Join(conditions, " and ")
137 }
138
139 query := fmt.Sprintf(
140 `update comments
141 set body_text = "",
142 body_original = null,
143 body_blobs = null,
144 deleted = strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', 'now')
145 %s`,
146 whereClause,
147 )
148
149 _, err := e.Exec(query, args...)
150 return err
151}
152
153func GetComments(e Execer, filters ...orm.Filter) ([]models.Comment, error) {
154 var comments []models.Comment
155
156 var conditions []string
157 var args []any
158 for _, filter := range filters {
159 conditions = append(conditions, filter.Condition())
160 args = append(args, filter.Arg()...)
161 }
162
163 whereClause := ""
164 if conditions != nil {
165 whereClause = " where " + strings.Join(conditions, " and ")
166 }
167
168 query := fmt.Sprintf(`
169 select
170 id,
171 did,
172 collection,
173 rkey,
174 cid,
175 subject_uri,
176 subject_cid,
177 body_text,
178 body_original,
179 body_blobs,
180 created,
181 reply_to_uri,
182 reply_to_cid,
183 pull_round_idx,
184 edited,
185 deleted
186 from
187 comments
188 %s
189 `, whereClause)
190
191 rows, err := e.Query(query, args...)
192 if err != nil {
193 return nil, err
194 }
195 defer rows.Close()
196
197 for rows.Next() {
198 var comment models.Comment
199 var created string
200 var cid, bodyBlobs, replyToUri, replyToCid, edited, deleted sql.Null[string]
201 err := rows.Scan(
202 &comment.Id,
203 &comment.Did,
204 &comment.Collection,
205 &comment.Rkey,
206 &cid,
207 &comment.Subject.Uri,
208 &comment.Subject.Cid,
209 &comment.Body.Text,
210 &comment.Body.Original,
211 &bodyBlobs,
212 &created,
213 &replyToUri,
214 &replyToCid,
215 &comment.PullRoundIdx,
216 &edited,
217 &deleted,
218 )
219 if err != nil {
220 return nil, err
221 }
222
223 if cid.Valid && cid.V != "" {
224 comment.Cid = syntax.CID(cid.V)
225 }
226
227 if bodyBlobs.Valid && bodyBlobs.V != "" {
228 if err := json.Unmarshal([]byte(bodyBlobs.V), &comment.Body.Blobs); err != nil {
229 return nil, fmt.Errorf("decoding blobs: %w", err)
230 }
231 }
232
233 if t, err := time.Parse(time.RFC3339, created); err == nil {
234 comment.Created = t
235 }
236
237 if replyToUri.Valid && replyToCid.Valid {
238 comment.ReplyTo = &atproto.RepoStrongRef{
239 Uri: replyToUri.V,
240 Cid: replyToCid.V,
241 }
242 }
243
244 if edited.Valid {
245 if t, err := time.Parse(time.RFC3339, edited.V); err == nil {
246 comment.Edited = &t
247 }
248 }
249
250 if deleted.Valid {
251 if t, err := time.Parse(time.RFC3339, deleted.V); err == nil {
252 comment.Deleted = &t
253 }
254 }
255
256 comments = append(comments, comment)
257 }
258
259 if err := rows.Err(); err != nil {
260 return nil, err
261 }
262
263 sort.Slice(comments, func(i, j int) bool {
264 return comments[i].Created.Before(comments[j].Created)
265 })
266
267 return comments, nil
268}