Monorepo for Tangled tangled.org
12

Configure Feed

Select the types of activity you want to include in your feed.

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