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