Write on the margins of the internet. Powered by the AT Protocol. margin.at
extension web atproto comments

Various optimizations and caching implemented

+1405 -956
+48
backend/internal/api/cache.go
··· 1 + package api 2 + 3 + import ( 4 + "sync" 5 + "time" 6 + ) 7 + 8 + type ProfileCache interface { 9 + Get(did string) (Author, bool) 10 + Set(did string, profile Author) 11 + } 12 + type InMemoryCache struct { 13 + cache sync.Map 14 + ttl time.Duration 15 + } 16 + 17 + type cachedProfile struct { 18 + Author Author 19 + ExpiresAt time.Time 20 + } 21 + 22 + func NewInMemoryCache(ttl time.Duration) *InMemoryCache { 23 + return &InMemoryCache{ 24 + ttl: ttl, 25 + } 26 + } 27 + 28 + func (c *InMemoryCache) Get(did string) (Author, bool) { 29 + val, ok := c.cache.Load(did) 30 + if !ok { 31 + return Author{}, false 32 + } 33 + 34 + entry := val.(cachedProfile) 35 + if time.Now().After(entry.ExpiresAt) { 36 + c.cache.Delete(did) 37 + return Author{}, false 38 + } 39 + 40 + return entry.Author, true 41 + } 42 + 43 + func (c *InMemoryCache) Set(did string, profile Author) { 44 + c.cache.Store(did, cachedProfile{ 45 + Author: profile, 46 + ExpiresAt: time.Now().Add(c.ttl), 47 + }) 48 + }
+168 -81
backend/internal/api/hydration.go
··· 13 13 "margin.at/internal/db" 14 14 ) 15 15 16 + var ( 17 + Cache ProfileCache = NewInMemoryCache(5 * time.Minute) 18 + ) 19 + 16 20 type Author struct { 17 21 DID string `json:"did"` 18 22 Handle string `json:"handle"` ··· 148 152 149 153 profiles := fetchProfilesForDIDs(collectDIDs(annotations, func(a db.Annotation) string { return a.AuthorDID })) 150 154 155 + var likeCounts map[string]int 156 + var replyCounts map[string]int 157 + var viewerLikes map[string]bool 158 + 159 + if database != nil { 160 + uris := make([]string, len(annotations)) 161 + for i, a := range annotations { 162 + uris[i] = a.URI 163 + } 164 + 165 + likeCounts, _ = database.GetLikeCounts(uris) 166 + replyCounts, _ = database.GetReplyCounts(uris) 167 + if viewerDID != "" { 168 + viewerLikes, _ = database.GetViewerLikes(viewerDID, uris) 169 + } 170 + } 171 + 151 172 result := make([]APIAnnotation, len(annotations)) 152 173 for i, a := range annotations { 153 174 var body *APIBody ··· 208 229 } 209 230 210 231 if database != nil { 211 - result[i].LikeCount, _ = database.GetLikeCount(a.URI) 212 - result[i].ReplyCount, _ = database.GetReplyCount(a.URI) 213 - if viewerDID != "" { 214 - if _, err := database.GetLikeByUserAndSubject(viewerDID, a.URI); err == nil { 215 - result[i].ViewerHasLiked = true 216 - } 232 + result[i].LikeCount = likeCounts[a.URI] 233 + result[i].ReplyCount = replyCounts[a.URI] 234 + if viewerLikes != nil && viewerLikes[a.URI] { 235 + result[i].ViewerHasLiked = true 217 236 } 218 237 } 219 238 } ··· 228 247 229 248 profiles := fetchProfilesForDIDs(collectDIDs(highlights, func(h db.Highlight) string { return h.AuthorDID })) 230 249 250 + var likeCounts map[string]int 251 + var replyCounts map[string]int 252 + var viewerLikes map[string]bool 253 + 254 + if database != nil { 255 + uris := make([]string, len(highlights)) 256 + for i, h := range highlights { 257 + uris[i] = h.URI 258 + } 259 + 260 + likeCounts, _ = database.GetLikeCounts(uris) 261 + replyCounts, _ = database.GetReplyCounts(uris) 262 + if viewerDID != "" { 263 + viewerLikes, _ = database.GetViewerLikes(viewerDID, uris) 264 + } 265 + } 266 + 231 267 result := make([]APIHighlight, len(highlights)) 232 268 for i, h := range highlights { 233 269 var selector *APISelector ··· 272 308 } 273 309 274 310 if database != nil { 275 - result[i].LikeCount, _ = database.GetLikeCount(h.URI) 276 - result[i].ReplyCount, _ = database.GetReplyCount(h.URI) 277 - if viewerDID != "" { 278 - if _, err := database.GetLikeByUserAndSubject(viewerDID, h.URI); err == nil { 279 - result[i].ViewerHasLiked = true 280 - } 311 + result[i].LikeCount = likeCounts[h.URI] 312 + result[i].ReplyCount = replyCounts[h.URI] 313 + if viewerLikes != nil && viewerLikes[h.URI] { 314 + result[i].ViewerHasLiked = true 281 315 } 282 316 } 283 317 } ··· 292 326 293 327 profiles := fetchProfilesForDIDs(collectDIDs(bookmarks, func(b db.Bookmark) string { return b.AuthorDID })) 294 328 329 + var likeCounts map[string]int 330 + var replyCounts map[string]int 331 + var viewerLikes map[string]bool 332 + 333 + if database != nil { 334 + uris := make([]string, len(bookmarks)) 335 + for i, b := range bookmarks { 336 + uris[i] = b.URI 337 + } 338 + 339 + likeCounts, _ = database.GetLikeCounts(uris) 340 + replyCounts, _ = database.GetReplyCounts(uris) 341 + if viewerDID != "" { 342 + viewerLikes, _ = database.GetViewerLikes(viewerDID, uris) 343 + } 344 + } 345 + 295 346 result := make([]APIBookmark, len(bookmarks)) 296 347 for i, b := range bookmarks { 297 348 var tags []string ··· 326 377 CID: cid, 327 378 } 328 379 if database != nil { 329 - result[i].LikeCount, _ = database.GetLikeCount(b.URI) 330 - result[i].ReplyCount, _ = database.GetReplyCount(b.URI) 331 - if viewerDID != "" { 332 - if _, err := database.GetLikeByUserAndSubject(viewerDID, b.URI); err == nil { 333 - result[i].ViewerHasLiked = true 334 - } 380 + result[i].LikeCount = likeCounts[b.URI] 381 + result[i].ReplyCount = replyCounts[b.URI] 382 + if viewerLikes != nil && viewerLikes[b.URI] { 383 + result[i].ViewerHasLiked = true 335 384 } 336 385 } 337 386 } ··· 388 437 389 438 func fetchProfilesForDIDs(dids []string) map[string]Author { 390 439 profiles := make(map[string]Author) 440 + missingDIDs := make([]string, 0) 391 441 392 442 for _, did := range dids { 393 - profiles[did] = Author{ 394 - DID: did, 395 - Handle: "unknown", 443 + if author, ok := Cache.Get(did); ok { 444 + profiles[did] = author 445 + } else { 446 + missingDIDs = append(missingDIDs, did) 396 447 } 397 448 } 398 449 399 - if len(dids) == 0 { 450 + if len(missingDIDs) == 0 { 400 451 return profiles 401 452 } 402 453 ··· 404 455 var wg sync.WaitGroup 405 456 var mu sync.Mutex 406 457 407 - for i := 0; i < len(dids); i += batchSize { 458 + for i := 0; i < len(missingDIDs); i += batchSize { 408 459 end := i + batchSize 409 - if end > len(dids) { 410 - end = len(dids) 460 + if end > len(missingDIDs) { 461 + end = len(missingDIDs) 411 462 } 412 - batch := dids[i:end] 463 + batch := missingDIDs[i:end] 413 464 414 465 wg.Add(1) 415 466 go func(actors []string) { ··· 417 468 fetched, err := fetchProfiles(actors) 418 469 if err == nil { 419 470 mu.Lock() 471 + defer mu.Unlock() 420 472 for k, v := range fetched { 421 473 profiles[k] = v 474 + Cache.Set(k, v) 422 475 } 423 - mu.Unlock() 424 476 } 425 477 }(batch) 426 478 } ··· 484 536 485 537 profiles := fetchProfilesForDIDs(collectDIDs(items, func(i db.CollectionItem) string { return i.AuthorDID })) 486 538 539 + var collectionURIs []string 540 + var annotationURIs []string 541 + var highlightURIs []string 542 + var bookmarkURIs []string 543 + 544 + for _, item := range items { 545 + collectionURIs = append(collectionURIs, item.CollectionURI) 546 + if strings.Contains(item.AnnotationURI, "at.margin.annotation") { 547 + annotationURIs = append(annotationURIs, item.AnnotationURI) 548 + } else if strings.Contains(item.AnnotationURI, "at.margin.highlight") { 549 + highlightURIs = append(highlightURIs, item.AnnotationURI) 550 + } else if strings.Contains(item.AnnotationURI, "at.margin.bookmark") { 551 + bookmarkURIs = append(bookmarkURIs, item.AnnotationURI) 552 + } 553 + } 554 + 555 + collectionsMap := make(map[string]APICollection) 556 + if len(collectionURIs) > 0 { 557 + colls, err := database.GetCollectionsByURIs(collectionURIs) 558 + if err == nil { 559 + collProfiles := fetchProfilesForDIDs(collectDIDs(colls, func(c db.Collection) string { return c.AuthorDID })) 560 + for _, coll := range colls { 561 + icon := "" 562 + if coll.Icon != nil { 563 + icon = *coll.Icon 564 + } 565 + desc := "" 566 + if coll.Description != nil { 567 + desc = *coll.Description 568 + } 569 + collectionsMap[coll.URI] = APICollection{ 570 + URI: coll.URI, 571 + Name: coll.Name, 572 + Description: desc, 573 + Icon: icon, 574 + Creator: collProfiles[coll.AuthorDID], 575 + CreatedAt: coll.CreatedAt, 576 + IndexedAt: coll.IndexedAt, 577 + } 578 + } 579 + } 580 + } 581 + 582 + annotationsMap := make(map[string]APIAnnotation) 583 + if len(annotationURIs) > 0 { 584 + rawAnnos, err := database.GetAnnotationsByURIs(annotationURIs) 585 + if err == nil { 586 + hydrated, _ := hydrateAnnotations(database, rawAnnos, viewerDID) 587 + for _, a := range hydrated { 588 + annotationsMap[a.ID] = a 589 + } 590 + } 591 + } 592 + 593 + highlightsMap := make(map[string]APIHighlight) 594 + if len(highlightURIs) > 0 { 595 + rawHighlights, err := database.GetHighlightsByURIs(highlightURIs) 596 + if err == nil { 597 + hydrated, _ := hydrateHighlights(database, rawHighlights, viewerDID) 598 + for _, h := range hydrated { 599 + highlightsMap[h.ID] = h 600 + } 601 + } 602 + } 603 + 604 + bookmarksMap := make(map[string]APIBookmark) 605 + if len(bookmarkURIs) > 0 { 606 + rawBookmarks, err := database.GetBookmarksByURIs(bookmarkURIs) 607 + if err == nil { 608 + hydrated, _ := hydrateBookmarks(database, rawBookmarks, viewerDID) 609 + for _, b := range hydrated { 610 + bookmarksMap[b.ID] = b 611 + } 612 + } 613 + } 614 + 487 615 result := make([]APICollectionItem, len(items)) 488 616 for i, item := range items { 489 617 apiItem := APICollectionItem{ ··· 495 623 Position: item.Position, 496 624 } 497 625 498 - if coll, err := database.GetCollectionByURI(item.CollectionURI); err == nil { 499 - icon := "" 500 - if coll.Icon != nil { 501 - icon = *coll.Icon 502 - } 503 - desc := "" 504 - if coll.Description != nil { 505 - desc = *coll.Description 506 - } 507 - apiItem.Collection = &APICollection{ 508 - URI: coll.URI, 509 - Name: coll.Name, 510 - Description: desc, 511 - Icon: icon, 512 - Creator: profiles[coll.AuthorDID], 513 - CreatedAt: coll.CreatedAt, 514 - IndexedAt: coll.IndexedAt, 515 - } 626 + if coll, ok := collectionsMap[item.CollectionURI]; ok { 627 + apiItem.Collection = &coll 516 628 } 517 629 518 - if strings.Contains(item.AnnotationURI, "at.margin.annotation") { 519 - if a, err := database.GetAnnotationByURI(item.AnnotationURI); err == nil { 520 - hydrated, _ := hydrateAnnotations(database, []db.Annotation{*a}, viewerDID) 521 - if len(hydrated) > 0 { 522 - apiItem.Annotation = &hydrated[0] 523 - } 524 - } 525 - } else if strings.Contains(item.AnnotationURI, "at.margin.highlight") { 526 - if h, err := database.GetHighlightByURI(item.AnnotationURI); err == nil { 527 - hydrated, _ := hydrateHighlights(database, []db.Highlight{*h}, viewerDID) 528 - if len(hydrated) > 0 { 529 - apiItem.Highlight = &hydrated[0] 530 - } 531 - } 532 - } else if strings.Contains(item.AnnotationURI, "at.margin.bookmark") { 533 - if b, err := database.GetBookmarkByURI(item.AnnotationURI); err == nil { 534 - hydrated, _ := hydrateBookmarks(database, []db.Bookmark{*b}, viewerDID) 535 - if len(hydrated) > 0 { 536 - apiItem.Bookmark = &hydrated[0] 537 - } else { 538 - log.Printf("Failed to hydrate bookmark %s: empty hydration result\n", item.AnnotationURI) 539 - } 540 - } else { 541 - } 542 - } else { 543 - log.Printf("Unknown item type for URI: %s\n", item.AnnotationURI) 630 + if val, ok := annotationsMap[item.AnnotationURI]; ok { 631 + apiItem.Annotation = &val 632 + } else if val, ok := highlightsMap[item.AnnotationURI]; ok { 633 + apiItem.Highlight = &val 634 + } else if val, ok := bookmarksMap[item.AnnotationURI]; ok { 635 + apiItem.Bookmark = &val 544 636 } 545 637 546 638 result[i] = apiItem ··· 577 669 578 670 replyMap := make(map[string]APIReply) 579 671 if len(replyURIs) > 0 { 580 - var replies []db.Reply 581 - for _, uri := range replyURIs { 582 - r, err := database.GetReplyByURI(uri) 583 - if err == nil { 584 - replies = append(replies, *r) 672 + replies, err := database.GetRepliesByURIs(replyURIs) 673 + if err == nil { 674 + hydratedReplies, _ := hydrateReplies(replies) 675 + for _, r := range hydratedReplies { 676 + replyMap[r.ID] = r 585 677 } 586 - } 587 - 588 - hydratedReplies, _ := hydrateReplies(replies) 589 - for _, r := range hydratedReplies { 590 - replyMap[r.ID] = r 591 678 } 592 679 } 593 680
+1
backend/internal/db/db.go
··· 241 241 )`) 242 242 db.Exec(`CREATE INDEX IF NOT EXISTS idx_likes_subject_uri ON likes(subject_uri)`) 243 243 db.Exec(`CREATE INDEX IF NOT EXISTS idx_likes_author_did ON likes(author_did)`) 244 + db.Exec(`CREATE INDEX IF NOT EXISTS idx_likes_author_subject ON likes(author_did, subject_uri)`) 244 245 245 246 db.Exec(`CREATE TABLE IF NOT EXISTS collections ( 246 247 uri TEXT PRIMARY KEY,
+7 -875
backend/internal/db/queries.go
··· 10 10 "time" 11 11 ) 12 12 13 - func (db *DB) CreateAnnotation(a *Annotation) error { 14 - _, err := db.Exec(db.Rebind(` 15 - INSERT INTO annotations (uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid) 16 - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 17 - ON CONFLICT(uri) DO UPDATE SET 18 - motivation = excluded.motivation, 19 - body_value = excluded.body_value, 20 - body_format = excluded.body_format, 21 - body_uri = excluded.body_uri, 22 - target_title = excluded.target_title, 23 - selector_json = excluded.selector_json, 24 - tags_json = excluded.tags_json, 25 - indexed_at = excluded.indexed_at, 26 - cid = excluded.cid 27 - `), a.URI, a.AuthorDID, a.Motivation, a.BodyValue, a.BodyFormat, a.BodyURI, a.TargetSource, a.TargetHash, a.TargetTitle, a.SelectorJSON, a.TagsJSON, a.CreatedAt, a.IndexedAt, a.CID) 28 - return err 29 - } 30 - 31 - func (db *DB) GetAnnotationByURI(uri string) (*Annotation, error) { 32 - var a Annotation 33 - err := db.QueryRow(db.Rebind(` 34 - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 35 - FROM annotations 36 - WHERE uri = ? 37 - `), uri).Scan(&a.URI, &a.AuthorDID, &a.Motivation, &a.BodyValue, &a.BodyFormat, &a.BodyURI, &a.TargetSource, &a.TargetHash, &a.TargetTitle, &a.SelectorJSON, &a.TagsJSON, &a.CreatedAt, &a.IndexedAt, &a.CID) 38 - if err != nil { 39 - return nil, err 40 - } 41 - return &a, nil 42 - } 43 - 44 - func (db *DB) GetAnnotationsByTargetHash(targetHash string, limit, offset int) ([]Annotation, error) { 45 - rows, err := db.Query(db.Rebind(` 46 - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 47 - FROM annotations 48 - WHERE target_hash = ? 49 - ORDER BY created_at DESC 50 - LIMIT ? OFFSET ? 51 - `), targetHash, limit, offset) 52 - if err != nil { 53 - return nil, err 54 - } 55 - defer rows.Close() 56 - 57 - return scanAnnotations(rows) 58 - } 59 - 60 - func (db *DB) GetAnnotationsByAuthor(authorDID string, limit, offset int) ([]Annotation, error) { 61 - rows, err := db.Query(db.Rebind(` 62 - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 63 - FROM annotations 64 - WHERE author_did = ? 65 - ORDER BY created_at DESC 66 - LIMIT ? OFFSET ? 67 - `), authorDID, limit, offset) 68 - if err != nil { 69 - return nil, err 70 - } 71 - defer rows.Close() 72 - 73 - return scanAnnotations(rows) 74 - } 75 - 76 - func (db *DB) GetAnnotationsByMotivation(motivation string, limit, offset int) ([]Annotation, error) { 77 - rows, err := db.Query(db.Rebind(` 78 - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 79 - FROM annotations 80 - WHERE motivation = ? 81 - ORDER BY created_at DESC 82 - LIMIT ? OFFSET ? 83 - `), motivation, limit, offset) 84 - if err != nil { 85 - return nil, err 86 - } 87 - defer rows.Close() 88 - 89 - return scanAnnotations(rows) 90 - } 91 - 92 - func (db *DB) GetRecentAnnotations(limit, offset int) ([]Annotation, error) { 93 - rows, err := db.Query(db.Rebind(` 94 - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 95 - FROM annotations 96 - ORDER BY created_at DESC 97 - LIMIT ? OFFSET ? 98 - `), limit, offset) 99 - if err != nil { 100 - return nil, err 101 - } 102 - defer rows.Close() 103 - 104 - return scanAnnotations(rows) 105 - } 106 - 107 - func (db *DB) GetAnnotationsByTag(tag string, limit, offset int) ([]Annotation, error) { 108 - pattern := "%\"" + tag + "\"%" 109 - rows, err := db.Query(db.Rebind(` 110 - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 111 - FROM annotations 112 - WHERE tags_json LIKE ? 113 - ORDER BY created_at DESC 114 - LIMIT ? OFFSET ? 115 - `), pattern, limit, offset) 116 - if err != nil { 117 - return nil, err 118 - } 119 - defer rows.Close() 120 - 121 - return scanAnnotations(rows) 122 - } 123 - 124 - func (db *DB) DeleteAnnotation(uri string) error { 125 - _, err := db.Exec(db.Rebind(`DELETE FROM annotations WHERE uri = ?`), uri) 126 - return err 127 - } 128 - 129 - func (db *DB) UpdateAnnotation(uri, bodyValue, tagsJSON, cid string) error { 130 - _, err := db.Exec(db.Rebind(` 131 - UPDATE annotations 132 - SET body_value = ?, tags_json = ?, cid = ?, indexed_at = ? 133 - WHERE uri = ? 134 - `), bodyValue, tagsJSON, cid, time.Now(), uri) 135 - return err 136 - } 137 - 138 - func (db *DB) UpdateHighlight(uri, color, tagsJSON, cid string) error { 139 - _, err := db.Exec(db.Rebind(` 140 - UPDATE highlights 141 - SET color = ?, tags_json = ?, cid = ?, indexed_at = ? 142 - WHERE uri = ? 143 - `), color, tagsJSON, cid, time.Now(), uri) 144 - return err 145 - } 146 - 147 - func (db *DB) UpdateBookmark(uri, title, description, tagsJSON, cid string) error { 148 - _, err := db.Exec(db.Rebind(` 149 - UPDATE bookmarks 150 - SET title = ?, description = ?, tags_json = ?, cid = ?, indexed_at = ? 151 - WHERE uri = ? 152 - `), title, description, tagsJSON, cid, time.Now(), uri) 153 - return err 154 - } 155 - 156 13 type EditHistory struct { 157 14 ID int `json:"id"` 158 15 URI string `json:"uri"` ··· 162 19 EditedAt time.Time `json:"editedAt"` 163 20 } 164 21 165 - func (db *DB) SaveEditHistory(uri, recordType, previousContent string, previousCID *string) error { 166 - _, err := db.Exec(db.Rebind(` 167 - INSERT INTO edit_history (uri, record_type, previous_content, previous_cid, edited_at) 168 - VALUES (?, ?, ?, ?, ?) 169 - `), uri, recordType, previousContent, previousCID, time.Now()) 170 - return err 171 - } 172 - 173 - func (db *DB) GetEditHistory(uri string) ([]EditHistory, error) { 174 - rows, err := db.Query(db.Rebind(` 175 - SELECT id, uri, record_type, previous_content, previous_cid, edited_at 176 - FROM edit_history 177 - WHERE uri = ? 178 - ORDER BY edited_at DESC 179 - `), uri) 180 - if err != nil { 181 - return nil, err 182 - } 183 - defer rows.Close() 184 - 185 - var history []EditHistory 186 - for rows.Next() { 187 - var h EditHistory 188 - if err := rows.Scan(&h.ID, &h.URI, &h.RecordType, &h.PreviousContent, &h.PreviousCID, &h.EditedAt); err != nil { 189 - return nil, err 190 - } 191 - history = append(history, h) 192 - } 193 - return history, nil 194 - } 195 - 196 22 func scanAnnotations(rows interface { 197 23 Next() bool 198 24 Scan(...interface{}) error ··· 208 34 return annotations, nil 209 35 } 210 36 211 - func (db *DB) CreateHighlight(h *Highlight) error { 212 - _, err := db.Exec(db.Rebind(` 213 - INSERT INTO highlights (uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid) 214 - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 215 - ON CONFLICT(uri) DO UPDATE SET 216 - target_title = excluded.target_title, 217 - selector_json = excluded.selector_json, 218 - color = excluded.color, 219 - tags_json = excluded.tags_json, 220 - indexed_at = excluded.indexed_at, 221 - cid = excluded.cid 222 - `), h.URI, h.AuthorDID, h.TargetSource, h.TargetHash, h.TargetTitle, h.SelectorJSON, h.Color, h.TagsJSON, h.CreatedAt, h.IndexedAt, h.CID) 223 - return err 224 - } 225 - 226 - func (db *DB) GetHighlightByURI(uri string) (*Highlight, error) { 227 - var h Highlight 228 - err := db.QueryRow(db.Rebind(` 229 - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 230 - FROM highlights 231 - WHERE uri = ? 232 - `), uri).Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID) 233 - if err != nil { 234 - return nil, err 235 - } 236 - return &h, nil 237 - } 238 - 239 - func (db *DB) GetRecentHighlights(limit, offset int) ([]Highlight, error) { 240 - rows, err := db.Query(db.Rebind(` 241 - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 242 - FROM highlights 243 - ORDER BY created_at DESC 244 - LIMIT ? OFFSET ? 245 - `), limit, offset) 246 - if err != nil { 247 - return nil, err 248 - } 249 - defer rows.Close() 250 - 251 - var highlights []Highlight 252 - for rows.Next() { 253 - var h Highlight 254 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 255 - return nil, err 256 - } 257 - highlights = append(highlights, h) 258 - } 259 - return highlights, nil 260 - } 261 - 262 - func (db *DB) GetHighlightsByTag(tag string, limit, offset int) ([]Highlight, error) { 263 - pattern := "%\"" + tag + "\"%" 264 - rows, err := db.Query(db.Rebind(` 265 - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 266 - FROM highlights 267 - WHERE tags_json LIKE ? 268 - ORDER BY created_at DESC 269 - LIMIT ? OFFSET ? 270 - `), pattern, limit, offset) 271 - if err != nil { 272 - return nil, err 273 - } 274 - defer rows.Close() 275 - 276 - var highlights []Highlight 277 - for rows.Next() { 278 - var h Highlight 279 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 280 - return nil, err 281 - } 282 - highlights = append(highlights, h) 283 - } 284 - return highlights, nil 285 - } 286 - 287 - func (db *DB) GetRecentBookmarks(limit, offset int) ([]Bookmark, error) { 288 - rows, err := db.Query(db.Rebind(` 289 - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 290 - FROM bookmarks 291 - ORDER BY created_at DESC 292 - LIMIT ? OFFSET ? 293 - `), limit, offset) 294 - if err != nil { 295 - return nil, err 296 - } 297 - defer rows.Close() 298 - 299 - var bookmarks []Bookmark 300 - for rows.Next() { 301 - var b Bookmark 302 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 303 - return nil, err 304 - } 305 - bookmarks = append(bookmarks, b) 306 - } 307 - return bookmarks, nil 308 - } 309 - 310 - func (db *DB) GetBookmarksByTag(tag string, limit, offset int) ([]Bookmark, error) { 311 - pattern := "%\"" + tag + "\"%" 312 - rows, err := db.Query(db.Rebind(` 313 - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 314 - FROM bookmarks 315 - WHERE tags_json LIKE ? 316 - ORDER BY created_at DESC 317 - LIMIT ? OFFSET ? 318 - `), pattern, limit, offset) 319 - if err != nil { 320 - return nil, err 321 - } 322 - defer rows.Close() 323 - 324 - var bookmarks []Bookmark 325 - for rows.Next() { 326 - var b Bookmark 327 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 328 - return nil, err 329 - } 330 - bookmarks = append(bookmarks, b) 331 - } 332 - return bookmarks, nil 333 - } 334 - 335 - func (db *DB) GetAnnotationsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Annotation, error) { 336 - pattern := "%\"" + tag + "\"%" 337 - rows, err := db.Query(db.Rebind(` 338 - SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 339 - FROM annotations 340 - WHERE author_did = ? AND tags_json LIKE ? 341 - ORDER BY created_at DESC 342 - LIMIT ? OFFSET ? 343 - `), authorDID, pattern, limit, offset) 344 - if err != nil { 345 - return nil, err 346 - } 347 - defer rows.Close() 348 - 349 - return scanAnnotations(rows) 350 - } 351 - 352 - func (db *DB) GetHighlightsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Highlight, error) { 353 - pattern := "%\"" + tag + "\"%" 354 - rows, err := db.Query(db.Rebind(` 355 - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 356 - FROM highlights 357 - WHERE author_did = ? AND tags_json LIKE ? 358 - ORDER BY created_at DESC 359 - LIMIT ? OFFSET ? 360 - `), authorDID, pattern, limit, offset) 361 - if err != nil { 362 - return nil, err 363 - } 364 - defer rows.Close() 365 - 366 - var highlights []Highlight 367 - for rows.Next() { 368 - var h Highlight 369 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 370 - return nil, err 371 - } 372 - highlights = append(highlights, h) 373 - } 374 - return highlights, nil 375 - } 376 - 377 - func (db *DB) GetBookmarksByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Bookmark, error) { 378 - pattern := "%\"" + tag + "\"%" 379 - rows, err := db.Query(db.Rebind(` 380 - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 381 - FROM bookmarks 382 - WHERE author_did = ? AND tags_json LIKE ? 383 - ORDER BY created_at DESC 384 - LIMIT ? OFFSET ? 385 - `), authorDID, pattern, limit, offset) 386 - if err != nil { 387 - return nil, err 388 - } 389 - defer rows.Close() 390 - 391 - var bookmarks []Bookmark 392 - for rows.Next() { 393 - var b Bookmark 394 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 395 - return nil, err 396 - } 397 - bookmarks = append(bookmarks, b) 398 - } 399 - return bookmarks, nil 400 - } 401 - 402 - func (db *DB) GetHighlightsByTargetHash(targetHash string, limit, offset int) ([]Highlight, error) { 403 - rows, err := db.Query(db.Rebind(` 404 - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 405 - FROM highlights 406 - WHERE target_hash = ? 407 - ORDER BY created_at DESC 408 - LIMIT ? OFFSET ? 409 - `), targetHash, limit, offset) 410 - if err != nil { 411 - return nil, err 412 - } 413 - defer rows.Close() 414 - 415 - var highlights []Highlight 416 - for rows.Next() { 417 - var h Highlight 418 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 419 - return nil, err 420 - } 421 - highlights = append(highlights, h) 422 - } 423 - return highlights, nil 424 - } 425 - 426 - func (db *DB) GetHighlightsByAuthor(authorDID string, limit, offset int) ([]Highlight, error) { 427 - rows, err := db.Query(db.Rebind(` 428 - SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 429 - FROM highlights 430 - WHERE author_did = ? 431 - ORDER BY created_at DESC 432 - LIMIT ? OFFSET ? 433 - `), authorDID, limit, offset) 434 - if err != nil { 435 - return nil, err 436 - } 437 - defer rows.Close() 438 - 439 - var highlights []Highlight 440 - for rows.Next() { 441 - var h Highlight 442 - if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 443 - return nil, err 444 - } 445 - highlights = append(highlights, h) 446 - } 447 - return highlights, nil 448 - } 449 - 450 - func (db *DB) DeleteHighlight(uri string) error { 451 - _, err := db.Exec(db.Rebind(`DELETE FROM highlights WHERE uri = ?`), uri) 452 - return err 453 - } 454 - 455 - func (db *DB) CreateBookmark(b *Bookmark) error { 456 - _, err := db.Exec(db.Rebind(` 457 - INSERT INTO bookmarks (uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid) 458 - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 459 - ON CONFLICT(uri) DO UPDATE SET 460 - title = excluded.title, 461 - description = excluded.description, 462 - tags_json = excluded.tags_json, 463 - indexed_at = excluded.indexed_at, 464 - cid = excluded.cid 465 - `), b.URI, b.AuthorDID, b.Source, b.SourceHash, b.Title, b.Description, b.TagsJSON, b.CreatedAt, b.IndexedAt, b.CID) 466 - return err 467 - } 468 - 469 - func (db *DB) GetBookmarkByURI(uri string) (*Bookmark, error) { 470 - var b Bookmark 471 - err := db.QueryRow(db.Rebind(` 472 - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 473 - FROM bookmarks 474 - WHERE uri = ? 475 - `), uri).Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID) 476 - if err != nil { 477 - return nil, err 478 - } 479 - return &b, nil 480 - } 481 - 482 - func (db *DB) GetBookmarksByAuthor(authorDID string, limit, offset int) ([]Bookmark, error) { 483 - rows, err := db.Query(db.Rebind(` 484 - SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 485 - FROM bookmarks 486 - WHERE author_did = ? 487 - ORDER BY created_at DESC 488 - LIMIT ? OFFSET ? 489 - `), authorDID, limit, offset) 490 - if err != nil { 491 - return nil, err 492 - } 493 - defer rows.Close() 494 - 495 - var bookmarks []Bookmark 496 - for rows.Next() { 497 - var b Bookmark 498 - if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 499 - return nil, err 500 - } 501 - bookmarks = append(bookmarks, b) 502 - } 503 - return bookmarks, nil 504 - } 505 - 506 - func (db *DB) DeleteBookmark(uri string) error { 507 - _, err := db.Exec(db.Rebind(`DELETE FROM bookmarks WHERE uri = ?`), uri) 508 - return err 509 - } 510 - 511 - func (db *DB) CreateReply(r *Reply) error { 512 - _, err := db.Exec(db.Rebind(` 513 - INSERT INTO replies (uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid) 514 - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) 515 - ON CONFLICT(uri) DO UPDATE SET 516 - text = excluded.text, 517 - format = excluded.format, 518 - indexed_at = excluded.indexed_at, 519 - cid = excluded.cid 520 - `), r.URI, r.AuthorDID, r.ParentURI, r.RootURI, r.Text, r.Format, r.CreatedAt, r.IndexedAt, r.CID) 521 - return err 522 - } 523 - 524 - func (db *DB) GetRepliesByRoot(rootURI string) ([]Reply, error) { 525 - rows, err := db.Query(db.Rebind(` 526 - SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid 527 - FROM replies 528 - WHERE root_uri = ? 529 - ORDER BY created_at ASC 530 - `), rootURI) 531 - if err != nil { 532 - return nil, err 533 - } 534 - defer rows.Close() 535 - 536 - var replies []Reply 537 - for rows.Next() { 538 - var r Reply 539 - if err := rows.Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID); err != nil { 540 - return nil, err 541 - } 542 - replies = append(replies, r) 543 - } 544 - return replies, nil 545 - } 546 - 547 - func (db *DB) GetReplyByURI(uri string) (*Reply, error) { 548 - var r Reply 549 - err := db.QueryRow(db.Rebind(` 550 - SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid 551 - FROM replies 552 - WHERE uri = ? 553 - `), uri).Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID) 554 - if err != nil { 555 - return nil, err 556 - } 557 - return &r, nil 558 - } 559 - 560 - func (db *DB) DeleteReply(uri string) error { 561 - _, err := db.Exec(db.Rebind(`DELETE FROM replies WHERE uri = ?`), uri) 562 - return err 563 - } 564 - 565 - func (db *DB) GetRepliesByAuthor(authorDID string) ([]Reply, error) { 566 - rows, err := db.Query(db.Rebind(` 567 - SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid 568 - FROM replies 569 - WHERE author_did = ? 570 - ORDER BY created_at DESC 571 - `), authorDID) 572 - if err != nil { 573 - return nil, err 574 - } 575 - defer rows.Close() 576 - 577 - var replies []Reply 578 - for rows.Next() { 579 - var r Reply 580 - if err := rows.Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID); err != nil { 581 - return nil, err 582 - } 583 - replies = append(replies, r) 584 - } 585 - return replies, nil 586 - } 587 - 588 37 func (db *DB) AnnotationExists(uri string) bool { 589 38 var count int 590 39 db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM annotations WHERE uri = ?`), uri).Scan(&count) 591 40 return count > 0 592 41 } 593 42 594 - func (db *DB) GetOrphanedRepliesByAuthor(authorDID string) ([]Reply, error) { 595 - rows, err := db.Query(db.Rebind(` 596 - SELECT r.uri, r.author_did, r.parent_uri, r.root_uri, r.text, r.format, r.created_at, r.indexed_at, r.cid 597 - FROM replies r 598 - LEFT JOIN annotations a ON r.root_uri = a.uri 599 - WHERE r.author_did = ? AND a.uri IS NULL 600 - `), authorDID) 601 - if err != nil { 602 - return nil, err 603 - } 604 - defer rows.Close() 605 - 606 - var replies []Reply 607 - for rows.Next() { 608 - var r Reply 609 - if err := rows.Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID); err != nil { 610 - return nil, err 611 - } 612 - replies = append(replies, r) 613 - } 614 - return replies, nil 615 - } 616 - 617 - func (db *DB) CreateLike(l *Like) error { 618 - _, err := db.Exec(db.Rebind(` 619 - INSERT INTO likes (uri, author_did, subject_uri, created_at, indexed_at) 620 - VALUES (?, ?, ?, ?, ?) 621 - ON CONFLICT(uri) DO NOTHING 622 - `), l.URI, l.AuthorDID, l.SubjectURI, l.CreatedAt, l.IndexedAt) 623 - return err 624 - } 625 - 626 - func (db *DB) DeleteLike(uri string) error { 627 - _, err := db.Exec(db.Rebind(`DELETE FROM likes WHERE uri = ?`), uri) 628 - return err 629 - } 630 - 631 - func (db *DB) GetLikeCount(subjectURI string) (int, error) { 632 - var count int 633 - err := db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM likes WHERE subject_uri = ?`), subjectURI).Scan(&count) 634 - return count, err 635 - } 636 - 637 - func (db *DB) GetReplyCount(rootURI string) (int, error) { 638 - var count int 639 - err := db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM replies WHERE root_uri = ?`), rootURI).Scan(&count) 640 - return count, err 641 - } 642 - 643 - func (db *DB) GetLikeByUserAndSubject(userDID, subjectURI string) (*Like, error) { 644 - var like Like 645 - err := db.QueryRow(db.Rebind(` 646 - SELECT uri, author_did, subject_uri, created_at, indexed_at 647 - FROM likes 648 - WHERE author_did = ? AND subject_uri = ? 649 - `), userDID, subjectURI).Scan(&like.URI, &like.AuthorDID, &like.SubjectURI, &like.CreatedAt, &like.IndexedAt) 650 - if err != nil { 651 - return nil, err 652 - } 653 - return &like, nil 654 - } 655 - 656 - func (db *DB) CreateCollection(c *Collection) error { 657 - _, err := db.Exec(db.Rebind(` 658 - INSERT INTO collections (uri, author_did, name, description, icon, created_at, indexed_at) 659 - VALUES (?, ?, ?, ?, ?, ?, ?) 660 - ON CONFLICT(uri) DO UPDATE SET 661 - name = excluded.name, 662 - description = excluded.description, 663 - icon = excluded.icon, 664 - indexed_at = excluded.indexed_at 665 - `), c.URI, c.AuthorDID, c.Name, c.Description, c.Icon, c.CreatedAt, c.IndexedAt) 666 - return err 667 - } 668 - 669 - func (db *DB) GetCollectionsByAuthor(authorDID string) ([]Collection, error) { 670 - rows, err := db.Query(db.Rebind(` 671 - SELECT uri, author_did, name, description, icon, created_at, indexed_at 672 - FROM collections 673 - WHERE author_did = ? 674 - ORDER BY created_at DESC 675 - `), authorDID) 676 - if err != nil { 677 - return nil, err 678 - } 679 - defer rows.Close() 680 - 681 - var collections []Collection 682 - for rows.Next() { 683 - var c Collection 684 - if err := rows.Scan(&c.URI, &c.AuthorDID, &c.Name, &c.Description, &c.Icon, &c.CreatedAt, &c.IndexedAt); err != nil { 685 - return nil, err 686 - } 687 - collections = append(collections, c) 688 - } 689 - return collections, nil 690 - } 691 - 692 - func (db *DB) GetCollectionByURI(uri string) (*Collection, error) { 693 - var c Collection 694 - err := db.QueryRow(db.Rebind(` 695 - SELECT uri, author_did, name, description, icon, created_at, indexed_at 696 - FROM collections 697 - WHERE uri = ? 698 - `), uri).Scan(&c.URI, &c.AuthorDID, &c.Name, &c.Description, &c.Icon, &c.CreatedAt, &c.IndexedAt) 699 - if err != nil { 700 - return nil, err 701 - } 702 - return &c, nil 703 - } 704 - 705 - func (db *DB) DeleteCollection(uri string) error { 706 - 707 - db.Exec(db.Rebind(`DELETE FROM collection_items WHERE collection_uri = ?`), uri) 708 - _, err := db.Exec(db.Rebind(`DELETE FROM collections WHERE uri = ?`), uri) 709 - return err 710 - } 711 - 712 - func (db *DB) AddToCollection(item *CollectionItem) error { 713 - _, err := db.Exec(db.Rebind(` 714 - INSERT INTO collection_items (uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at) 715 - VALUES (?, ?, ?, ?, ?, ?, ?) 716 - ON CONFLICT(uri) DO UPDATE SET 717 - position = excluded.position, 718 - indexed_at = excluded.indexed_at 719 - `), item.URI, item.AuthorDID, item.CollectionURI, item.AnnotationURI, item.Position, item.CreatedAt, item.IndexedAt) 720 - return err 721 - } 722 - 723 - func (db *DB) GetCollectionItems(collectionURI string) ([]CollectionItem, error) { 724 - rows, err := db.Query(db.Rebind(` 725 - SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at 726 - FROM collection_items 727 - WHERE collection_uri = ? 728 - ORDER BY position ASC, created_at DESC 729 - `), collectionURI) 730 - if err != nil { 731 - return nil, err 732 - } 733 - defer rows.Close() 734 - 735 - var items []CollectionItem 736 - for rows.Next() { 737 - var item CollectionItem 738 - if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { 739 - return nil, err 740 - } 741 - items = append(items, item) 742 - } 743 - return items, nil 744 - } 745 - 746 - func (db *DB) RemoveFromCollection(uri string) error { 747 - _, err := db.Exec(db.Rebind(`DELETE FROM collection_items WHERE uri = ?`), uri) 748 - return err 749 - } 750 - 751 - func (db *DB) GetRecentCollectionItems(limit, offset int) ([]CollectionItem, error) { 752 - rows, err := db.Query(db.Rebind(` 753 - SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at 754 - FROM collection_items 755 - ORDER BY created_at DESC 756 - LIMIT ? OFFSET ? 757 - `), limit, offset) 758 - if err != nil { 759 - return nil, err 760 - } 761 - defer rows.Close() 762 - 763 - var items []CollectionItem 764 - for rows.Next() { 765 - var item CollectionItem 766 - if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { 767 - return nil, err 768 - } 769 - items = append(items, item) 770 - } 771 - return items, nil 772 - } 773 - 774 - func (db *DB) GetCollectionURIsForAnnotation(annotationURI string) ([]string, error) { 775 - rows, err := db.Query(db.Rebind(` 776 - SELECT collection_uri FROM collection_items WHERE annotation_uri = ? 777 - `), annotationURI) 778 - if err != nil { 779 - return nil, err 780 - } 781 - defer rows.Close() 782 - 783 - var uris []string 784 - for rows.Next() { 785 - var uri string 786 - if err := rows.Scan(&uri); err != nil { 787 - return nil, err 788 - } 789 - uris = append(uris, uri) 790 - } 791 - return uris, nil 792 - } 793 - 794 - func (db *DB) SaveSession(id, did, handle, accessToken, refreshToken, dpopKey string, expiresAt time.Time) error { 795 - _, err := db.Exec(db.Rebind(` 796 - INSERT INTO sessions (id, did, handle, access_token, refresh_token, dpop_key, created_at, expires_at) 797 - VALUES (?, ?, ?, ?, ?, ?, ?, ?) 798 - ON CONFLICT(id) DO UPDATE SET 799 - access_token = excluded.access_token, 800 - refresh_token = excluded.refresh_token, 801 - dpop_key = excluded.dpop_key, 802 - expires_at = excluded.expires_at 803 - `), id, did, handle, accessToken, refreshToken, dpopKey, time.Now(), expiresAt) 804 - return err 805 - } 806 - 807 - func (db *DB) GetSession(id string) (did, handle, accessToken, refreshToken, dpopKey string, err error) { 808 - err = db.QueryRow(db.Rebind(` 809 - SELECT did, handle, access_token, refresh_token, COALESCE(dpop_key, '') 810 - FROM sessions 811 - WHERE id = ? AND expires_at > ? 812 - `), id, time.Now()).Scan(&did, &handle, &accessToken, &refreshToken, &dpopKey) 813 - return 814 - } 815 - 816 - func (db *DB) DeleteSession(id string) error { 817 - _, err := db.Exec(db.Rebind(`DELETE FROM sessions WHERE id = ?`), id) 818 - return err 819 - } 820 - 821 43 func HashURL(rawURL string) string { 822 44 parsed, err := url.Parse(rawURL) 823 45 if err != nil { ··· 844 66 return string(b) 845 67 } 846 68 847 - func (db *DB) CreateNotification(n *Notification) error { 848 - _, err := db.Exec(db.Rebind(` 849 - INSERT INTO notifications (recipient_did, actor_did, type, subject_uri, created_at) 850 - VALUES (?, ?, ?, ?, ?) 851 - `), n.RecipientDID, n.ActorDID, n.Type, n.SubjectURI, n.CreatedAt) 852 - return err 853 - } 854 - 855 - func (db *DB) GetNotifications(recipientDID string, limit, offset int) ([]Notification, error) { 856 - rows, err := db.Query(db.Rebind(` 857 - SELECT id, recipient_did, actor_did, type, subject_uri, created_at, read_at 858 - FROM notifications 859 - WHERE recipient_did = ? 860 - ORDER BY created_at DESC 861 - LIMIT ? OFFSET ? 862 - `), recipientDID, limit, offset) 863 - if err != nil { 864 - return nil, err 865 - } 866 - defer rows.Close() 867 - 868 - var notifications []Notification 869 - for rows.Next() { 870 - var n Notification 871 - if err := rows.Scan(&n.ID, &n.RecipientDID, &n.ActorDID, &n.Type, &n.SubjectURI, &n.CreatedAt, &n.ReadAt); err != nil { 872 - continue 873 - } 874 - notifications = append(notifications, n) 875 - } 876 - return notifications, nil 877 - } 878 - 879 - func (db *DB) GetUnreadNotificationCount(recipientDID string) (int, error) { 880 - var count int 881 - err := db.QueryRow(db.Rebind(` 882 - SELECT COUNT(*) FROM notifications WHERE recipient_did = ? AND read_at IS NULL 883 - `), recipientDID).Scan(&count) 884 - return count, err 885 - } 886 - 887 - func (db *DB) MarkNotificationsRead(recipientDID string) error { 888 - _, err := db.Exec(db.Rebind(` 889 - UPDATE notifications SET read_at = ? WHERE recipient_did = ? AND read_at IS NULL 890 - `), time.Now(), recipientDID) 891 - return err 892 - } 893 - 894 69 func (db *DB) GetAuthorByURI(uri string) (string, error) { 895 70 var authorDID string 896 71 err := db.QueryRow(db.Rebind(`SELECT author_did FROM annotations WHERE uri = ?`), uri).Scan(&authorDID) ··· 911 86 return "", fmt.Errorf("uri not found or no author") 912 87 } 913 88 914 - func (db *DB) CreateAPIKey(key *APIKey) error { 915 - _, err := db.Exec(db.Rebind(` 916 - INSERT INTO api_keys (id, owner_did, name, key_hash, created_at) 917 - VALUES (?, ?, ?, ?, ?) 918 - `), key.ID, key.OwnerDID, key.Name, key.KeyHash, key.CreatedAt) 919 - return err 920 - } 921 - 922 - func (db *DB) GetAPIKeysByOwner(ownerDID string) ([]APIKey, error) { 923 - rows, err := db.Query(db.Rebind(` 924 - SELECT id, owner_did, name, key_hash, created_at, last_used_at 925 - FROM api_keys 926 - WHERE owner_did = ? 927 - ORDER BY created_at DESC 928 - `), ownerDID) 929 - if err != nil { 930 - return nil, err 89 + func buildPlaceholders(n int) string { 90 + if n == 0 { 91 + return "" 931 92 } 932 - defer rows.Close() 933 - 934 - var keys []APIKey 935 - for rows.Next() { 936 - var k APIKey 937 - if err := rows.Scan(&k.ID, &k.OwnerDID, &k.Name, &k.KeyHash, &k.CreatedAt, &k.LastUsedAt); err != nil { 938 - return nil, err 939 - } 940 - keys = append(keys, k) 93 + placeholders := make([]string, n) 94 + for i := range placeholders { 95 + placeholders[i] = "?" 941 96 } 942 - return keys, nil 943 - } 944 - 945 - func (db *DB) GetAPIKeyByHash(keyHash string) (*APIKey, error) { 946 - var k APIKey 947 - err := db.QueryRow(db.Rebind(` 948 - SELECT id, owner_did, name, key_hash, created_at, last_used_at 949 - FROM api_keys 950 - WHERE key_hash = ? 951 - `), keyHash).Scan(&k.ID, &k.OwnerDID, &k.Name, &k.KeyHash, &k.CreatedAt, &k.LastUsedAt) 952 - if err != nil { 953 - return nil, err 954 - } 955 - return &k, nil 956 - } 957 - 958 - func (db *DB) DeleteAPIKey(id, ownerDID string) error { 959 - _, err := db.Exec(db.Rebind(`DELETE FROM api_keys WHERE id = ? AND owner_did = ?`), id, ownerDID) 960 - return err 961 - } 962 - 963 - func (db *DB) UpdateAPIKeyLastUsed(id string) error { 964 - _, err := db.Exec(db.Rebind(`UPDATE api_keys SET last_used_at = ? WHERE id = ?`), time.Now(), id) 965 - return err 97 + return strings.Join(placeholders, ", ") 966 98 }
+172
backend/internal/db/queries_annotations.go
··· 1 + package db 2 + 3 + import ( 4 + "time" 5 + ) 6 + 7 + func (db *DB) CreateAnnotation(a *Annotation) error { 8 + _, err := db.Exec(db.Rebind(` 9 + INSERT INTO annotations (uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid) 10 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 11 + ON CONFLICT(uri) DO UPDATE SET 12 + motivation = excluded.motivation, 13 + body_value = excluded.body_value, 14 + body_format = excluded.body_format, 15 + body_uri = excluded.body_uri, 16 + target_title = excluded.target_title, 17 + selector_json = excluded.selector_json, 18 + tags_json = excluded.tags_json, 19 + indexed_at = excluded.indexed_at, 20 + cid = excluded.cid 21 + `), a.URI, a.AuthorDID, a.Motivation, a.BodyValue, a.BodyFormat, a.BodyURI, a.TargetSource, a.TargetHash, a.TargetTitle, a.SelectorJSON, a.TagsJSON, a.CreatedAt, a.IndexedAt, a.CID) 22 + return err 23 + } 24 + 25 + func (db *DB) GetAnnotationByURI(uri string) (*Annotation, error) { 26 + var a Annotation 27 + err := db.QueryRow(db.Rebind(` 28 + SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 29 + FROM annotations 30 + WHERE uri = ? 31 + `), uri).Scan(&a.URI, &a.AuthorDID, &a.Motivation, &a.BodyValue, &a.BodyFormat, &a.BodyURI, &a.TargetSource, &a.TargetHash, &a.TargetTitle, &a.SelectorJSON, &a.TagsJSON, &a.CreatedAt, &a.IndexedAt, &a.CID) 32 + if err != nil { 33 + return nil, err 34 + } 35 + return &a, nil 36 + } 37 + 38 + func (db *DB) GetAnnotationsByTargetHash(targetHash string, limit, offset int) ([]Annotation, error) { 39 + rows, err := db.Query(db.Rebind(` 40 + SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 41 + FROM annotations 42 + WHERE target_hash = ? 43 + ORDER BY created_at DESC 44 + LIMIT ? OFFSET ? 45 + `), targetHash, limit, offset) 46 + if err != nil { 47 + return nil, err 48 + } 49 + defer rows.Close() 50 + 51 + return scanAnnotations(rows) 52 + } 53 + 54 + func (db *DB) GetAnnotationsByAuthor(authorDID string, limit, offset int) ([]Annotation, error) { 55 + rows, err := db.Query(db.Rebind(` 56 + SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 57 + FROM annotations 58 + WHERE author_did = ? 59 + ORDER BY created_at DESC 60 + LIMIT ? OFFSET ? 61 + `), authorDID, limit, offset) 62 + if err != nil { 63 + return nil, err 64 + } 65 + defer rows.Close() 66 + 67 + return scanAnnotations(rows) 68 + } 69 + 70 + func (db *DB) GetAnnotationsByMotivation(motivation string, limit, offset int) ([]Annotation, error) { 71 + rows, err := db.Query(db.Rebind(` 72 + SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 73 + FROM annotations 74 + WHERE motivation = ? 75 + ORDER BY created_at DESC 76 + LIMIT ? OFFSET ? 77 + `), motivation, limit, offset) 78 + if err != nil { 79 + return nil, err 80 + } 81 + defer rows.Close() 82 + 83 + return scanAnnotations(rows) 84 + } 85 + 86 + func (db *DB) GetRecentAnnotations(limit, offset int) ([]Annotation, error) { 87 + rows, err := db.Query(db.Rebind(` 88 + SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 89 + FROM annotations 90 + ORDER BY created_at DESC 91 + LIMIT ? OFFSET ? 92 + `), limit, offset) 93 + if err != nil { 94 + return nil, err 95 + } 96 + defer rows.Close() 97 + 98 + return scanAnnotations(rows) 99 + } 100 + 101 + func (db *DB) GetAnnotationsByTag(tag string, limit, offset int) ([]Annotation, error) { 102 + pattern := "%\"" + tag + "\"%" 103 + rows, err := db.Query(db.Rebind(` 104 + SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 105 + FROM annotations 106 + WHERE tags_json LIKE ? 107 + ORDER BY created_at DESC 108 + LIMIT ? OFFSET ? 109 + `), pattern, limit, offset) 110 + if err != nil { 111 + return nil, err 112 + } 113 + defer rows.Close() 114 + 115 + return scanAnnotations(rows) 116 + } 117 + 118 + func (db *DB) DeleteAnnotation(uri string) error { 119 + _, err := db.Exec(db.Rebind(`DELETE FROM annotations WHERE uri = ?`), uri) 120 + return err 121 + } 122 + 123 + func (db *DB) UpdateAnnotation(uri, bodyValue, tagsJSON, cid string) error { 124 + _, err := db.Exec(db.Rebind(` 125 + UPDATE annotations 126 + SET body_value = ?, tags_json = ?, cid = ?, indexed_at = ? 127 + WHERE uri = ? 128 + `), bodyValue, tagsJSON, cid, time.Now(), uri) 129 + return err 130 + } 131 + 132 + func (db *DB) GetAnnotationsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Annotation, error) { 133 + pattern := "%\"" + tag + "\"%" 134 + rows, err := db.Query(db.Rebind(` 135 + SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 136 + FROM annotations 137 + WHERE author_did = ? AND tags_json LIKE ? 138 + ORDER BY created_at DESC 139 + LIMIT ? OFFSET ? 140 + `), authorDID, pattern, limit, offset) 141 + if err != nil { 142 + return nil, err 143 + } 144 + defer rows.Close() 145 + 146 + return scanAnnotations(rows) 147 + } 148 + 149 + func (db *DB) GetAnnotationsByURIs(uris []string) ([]Annotation, error) { 150 + if len(uris) == 0 { 151 + return []Annotation{}, nil 152 + } 153 + 154 + query := db.Rebind(` 155 + SELECT uri, author_did, motivation, body_value, body_format, body_uri, target_source, target_hash, target_title, selector_json, tags_json, created_at, indexed_at, cid 156 + FROM annotations 157 + WHERE uri IN (` + buildPlaceholders(len(uris)) + `) 158 + `) 159 + 160 + args := make([]interface{}, len(uris)) 161 + for i, uri := range uris { 162 + args[i] = uri 163 + } 164 + 165 + rows, err := db.Query(query, args...) 166 + if err != nil { 167 + return nil, err 168 + } 169 + defer rows.Close() 170 + 171 + return scanAnnotations(rows) 172 + }
+176
backend/internal/db/queries_bookmarks.go
··· 1 + package db 2 + 3 + import ( 4 + "time" 5 + ) 6 + 7 + func (db *DB) CreateBookmark(b *Bookmark) error { 8 + _, err := db.Exec(db.Rebind(` 9 + INSERT INTO bookmarks (uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid) 10 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 11 + ON CONFLICT(uri) DO UPDATE SET 12 + title = excluded.title, 13 + description = excluded.description, 14 + tags_json = excluded.tags_json, 15 + indexed_at = excluded.indexed_at, 16 + cid = excluded.cid 17 + `), b.URI, b.AuthorDID, b.Source, b.SourceHash, b.Title, b.Description, b.TagsJSON, b.CreatedAt, b.IndexedAt, b.CID) 18 + return err 19 + } 20 + 21 + func (db *DB) GetBookmarkByURI(uri string) (*Bookmark, error) { 22 + var b Bookmark 23 + err := db.QueryRow(db.Rebind(` 24 + SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 25 + FROM bookmarks 26 + WHERE uri = ? 27 + `), uri).Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID) 28 + if err != nil { 29 + return nil, err 30 + } 31 + return &b, nil 32 + } 33 + 34 + func (db *DB) GetRecentBookmarks(limit, offset int) ([]Bookmark, error) { 35 + rows, err := db.Query(db.Rebind(` 36 + SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 37 + FROM bookmarks 38 + ORDER BY created_at DESC 39 + LIMIT ? OFFSET ? 40 + `), limit, offset) 41 + if err != nil { 42 + return nil, err 43 + } 44 + defer rows.Close() 45 + 46 + var bookmarks []Bookmark 47 + for rows.Next() { 48 + var b Bookmark 49 + if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 50 + return nil, err 51 + } 52 + bookmarks = append(bookmarks, b) 53 + } 54 + return bookmarks, nil 55 + } 56 + 57 + func (db *DB) GetBookmarksByTag(tag string, limit, offset int) ([]Bookmark, error) { 58 + pattern := "%\"" + tag + "\"%" 59 + rows, err := db.Query(db.Rebind(` 60 + SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 61 + FROM bookmarks 62 + WHERE tags_json LIKE ? 63 + ORDER BY created_at DESC 64 + LIMIT ? OFFSET ? 65 + `), pattern, limit, offset) 66 + if err != nil { 67 + return nil, err 68 + } 69 + defer rows.Close() 70 + 71 + var bookmarks []Bookmark 72 + for rows.Next() { 73 + var b Bookmark 74 + if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 75 + return nil, err 76 + } 77 + bookmarks = append(bookmarks, b) 78 + } 79 + return bookmarks, nil 80 + } 81 + 82 + func (db *DB) GetBookmarksByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Bookmark, error) { 83 + pattern := "%\"" + tag + "\"%" 84 + rows, err := db.Query(db.Rebind(` 85 + SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 86 + FROM bookmarks 87 + WHERE author_did = ? AND tags_json LIKE ? 88 + ORDER BY created_at DESC 89 + LIMIT ? OFFSET ? 90 + `), authorDID, pattern, limit, offset) 91 + if err != nil { 92 + return nil, err 93 + } 94 + defer rows.Close() 95 + 96 + var bookmarks []Bookmark 97 + for rows.Next() { 98 + var b Bookmark 99 + if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 100 + return nil, err 101 + } 102 + bookmarks = append(bookmarks, b) 103 + } 104 + return bookmarks, nil 105 + } 106 + 107 + func (db *DB) GetBookmarksByAuthor(authorDID string, limit, offset int) ([]Bookmark, error) { 108 + rows, err := db.Query(db.Rebind(` 109 + SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 110 + FROM bookmarks 111 + WHERE author_did = ? 112 + ORDER BY created_at DESC 113 + LIMIT ? OFFSET ? 114 + `), authorDID, limit, offset) 115 + if err != nil { 116 + return nil, err 117 + } 118 + defer rows.Close() 119 + 120 + var bookmarks []Bookmark 121 + for rows.Next() { 122 + var b Bookmark 123 + if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 124 + return nil, err 125 + } 126 + bookmarks = append(bookmarks, b) 127 + } 128 + return bookmarks, nil 129 + } 130 + 131 + func (db *DB) DeleteBookmark(uri string) error { 132 + _, err := db.Exec(db.Rebind(`DELETE FROM bookmarks WHERE uri = ?`), uri) 133 + return err 134 + } 135 + 136 + func (db *DB) UpdateBookmark(uri, title, description, tagsJSON, cid string) error { 137 + _, err := db.Exec(db.Rebind(` 138 + UPDATE bookmarks 139 + SET title = ?, description = ?, tags_json = ?, cid = ?, indexed_at = ? 140 + WHERE uri = ? 141 + `), title, description, tagsJSON, cid, time.Now(), uri) 142 + return err 143 + } 144 + 145 + func (db *DB) GetBookmarksByURIs(uris []string) ([]Bookmark, error) { 146 + if len(uris) == 0 { 147 + return []Bookmark{}, nil 148 + } 149 + 150 + query := db.Rebind(` 151 + SELECT uri, author_did, source, source_hash, title, description, tags_json, created_at, indexed_at, cid 152 + FROM bookmarks 153 + WHERE uri IN (` + buildPlaceholders(len(uris)) + `) 154 + `) 155 + 156 + args := make([]interface{}, len(uris)) 157 + for i, uri := range uris { 158 + args[i] = uri 159 + } 160 + 161 + rows, err := db.Query(query, args...) 162 + if err != nil { 163 + return nil, err 164 + } 165 + defer rows.Close() 166 + 167 + var bookmarks []Bookmark 168 + for rows.Next() { 169 + var b Bookmark 170 + if err := rows.Scan(&b.URI, &b.AuthorDID, &b.Source, &b.SourceHash, &b.Title, &b.Description, &b.TagsJSON, &b.CreatedAt, &b.IndexedAt, &b.CID); err != nil { 171 + return nil, err 172 + } 173 + bookmarks = append(bookmarks, b) 174 + } 175 + return bookmarks, nil 176 + }
+172
backend/internal/db/queries_collections.go
··· 1 + package db 2 + 3 + func (db *DB) CreateCollection(c *Collection) error { 4 + _, err := db.Exec(db.Rebind(` 5 + INSERT INTO collections (uri, author_did, name, description, icon, created_at, indexed_at) 6 + VALUES (?, ?, ?, ?, ?, ?, ?) 7 + ON CONFLICT(uri) DO UPDATE SET 8 + name = excluded.name, 9 + description = excluded.description, 10 + icon = excluded.icon, 11 + indexed_at = excluded.indexed_at 12 + `), c.URI, c.AuthorDID, c.Name, c.Description, c.Icon, c.CreatedAt, c.IndexedAt) 13 + return err 14 + } 15 + 16 + func (db *DB) GetCollectionsByAuthor(authorDID string) ([]Collection, error) { 17 + rows, err := db.Query(db.Rebind(` 18 + SELECT uri, author_did, name, description, icon, created_at, indexed_at 19 + FROM collections 20 + WHERE author_did = ? 21 + ORDER BY created_at DESC 22 + `), authorDID) 23 + if err != nil { 24 + return nil, err 25 + } 26 + defer rows.Close() 27 + 28 + var collections []Collection 29 + for rows.Next() { 30 + var c Collection 31 + if err := rows.Scan(&c.URI, &c.AuthorDID, &c.Name, &c.Description, &c.Icon, &c.CreatedAt, &c.IndexedAt); err != nil { 32 + return nil, err 33 + } 34 + collections = append(collections, c) 35 + } 36 + return collections, nil 37 + } 38 + 39 + func (db *DB) GetCollectionByURI(uri string) (*Collection, error) { 40 + var c Collection 41 + err := db.QueryRow(db.Rebind(` 42 + SELECT uri, author_did, name, description, icon, created_at, indexed_at 43 + FROM collections 44 + WHERE uri = ? 45 + `), uri).Scan(&c.URI, &c.AuthorDID, &c.Name, &c.Description, &c.Icon, &c.CreatedAt, &c.IndexedAt) 46 + if err != nil { 47 + return nil, err 48 + } 49 + return &c, nil 50 + } 51 + 52 + func (db *DB) DeleteCollection(uri string) error { 53 + 54 + db.Exec(db.Rebind(`DELETE FROM collection_items WHERE collection_uri = ?`), uri) 55 + _, err := db.Exec(db.Rebind(`DELETE FROM collections WHERE uri = ?`), uri) 56 + return err 57 + } 58 + 59 + func (db *DB) AddToCollection(item *CollectionItem) error { 60 + _, err := db.Exec(db.Rebind(` 61 + INSERT INTO collection_items (uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at) 62 + VALUES (?, ?, ?, ?, ?, ?, ?) 63 + ON CONFLICT(uri) DO UPDATE SET 64 + position = excluded.position, 65 + indexed_at = excluded.indexed_at 66 + `), item.URI, item.AuthorDID, item.CollectionURI, item.AnnotationURI, item.Position, item.CreatedAt, item.IndexedAt) 67 + return err 68 + } 69 + 70 + func (db *DB) GetCollectionItems(collectionURI string) ([]CollectionItem, error) { 71 + rows, err := db.Query(db.Rebind(` 72 + SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at 73 + FROM collection_items 74 + WHERE collection_uri = ? 75 + ORDER BY position ASC, created_at DESC 76 + `), collectionURI) 77 + if err != nil { 78 + return nil, err 79 + } 80 + defer rows.Close() 81 + 82 + var items []CollectionItem 83 + for rows.Next() { 84 + var item CollectionItem 85 + if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { 86 + return nil, err 87 + } 88 + items = append(items, item) 89 + } 90 + return items, nil 91 + } 92 + 93 + func (db *DB) RemoveFromCollection(uri string) error { 94 + _, err := db.Exec(db.Rebind(`DELETE FROM collection_items WHERE uri = ?`), uri) 95 + return err 96 + } 97 + 98 + func (db *DB) GetRecentCollectionItems(limit, offset int) ([]CollectionItem, error) { 99 + rows, err := db.Query(db.Rebind(` 100 + SELECT uri, author_did, collection_uri, annotation_uri, position, created_at, indexed_at 101 + FROM collection_items 102 + ORDER BY created_at DESC 103 + LIMIT ? OFFSET ? 104 + `), limit, offset) 105 + if err != nil { 106 + return nil, err 107 + } 108 + defer rows.Close() 109 + 110 + var items []CollectionItem 111 + for rows.Next() { 112 + var item CollectionItem 113 + if err := rows.Scan(&item.URI, &item.AuthorDID, &item.CollectionURI, &item.AnnotationURI, &item.Position, &item.CreatedAt, &item.IndexedAt); err != nil { 114 + return nil, err 115 + } 116 + items = append(items, item) 117 + } 118 + return items, nil 119 + } 120 + 121 + func (db *DB) GetCollectionURIsForAnnotation(annotationURI string) ([]string, error) { 122 + rows, err := db.Query(db.Rebind(` 123 + SELECT collection_uri FROM collection_items WHERE annotation_uri = ? 124 + `), annotationURI) 125 + if err != nil { 126 + return nil, err 127 + } 128 + defer rows.Close() 129 + 130 + var uris []string 131 + for rows.Next() { 132 + var uri string 133 + if err := rows.Scan(&uri); err != nil { 134 + return nil, err 135 + } 136 + uris = append(uris, uri) 137 + } 138 + return uris, nil 139 + } 140 + 141 + func (db *DB) GetCollectionsByURIs(uris []string) ([]Collection, error) { 142 + if len(uris) == 0 { 143 + return []Collection{}, nil 144 + } 145 + 146 + query := db.Rebind(` 147 + SELECT uri, author_did, name, description, icon, created_at, indexed_at 148 + FROM collections 149 + WHERE uri IN (` + buildPlaceholders(len(uris)) + `) 150 + `) 151 + 152 + args := make([]interface{}, len(uris)) 153 + for i, uri := range uris { 154 + args[i] = uri 155 + } 156 + 157 + rows, err := db.Query(query, args...) 158 + if err != nil { 159 + return nil, err 160 + } 161 + defer rows.Close() 162 + 163 + var collections []Collection 164 + for rows.Next() { 165 + var c Collection 166 + if err := rows.Scan(&c.URI, &c.AuthorDID, &c.Name, &c.Description, &c.Icon, &c.CreatedAt, &c.IndexedAt); err != nil { 167 + return nil, err 168 + } 169 + collections = append(collections, c) 170 + } 171 + return collections, nil 172 + }
+201
backend/internal/db/queries_highlights.go
··· 1 + package db 2 + 3 + import ( 4 + "time" 5 + ) 6 + 7 + func (db *DB) CreateHighlight(h *Highlight) error { 8 + _, err := db.Exec(db.Rebind(` 9 + INSERT INTO highlights (uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid) 10 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 11 + ON CONFLICT(uri) DO UPDATE SET 12 + target_title = excluded.target_title, 13 + selector_json = excluded.selector_json, 14 + color = excluded.color, 15 + tags_json = excluded.tags_json, 16 + indexed_at = excluded.indexed_at, 17 + cid = excluded.cid 18 + `), h.URI, h.AuthorDID, h.TargetSource, h.TargetHash, h.TargetTitle, h.SelectorJSON, h.Color, h.TagsJSON, h.CreatedAt, h.IndexedAt, h.CID) 19 + return err 20 + } 21 + 22 + func (db *DB) GetHighlightByURI(uri string) (*Highlight, error) { 23 + var h Highlight 24 + err := db.QueryRow(db.Rebind(` 25 + SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 26 + FROM highlights 27 + WHERE uri = ? 28 + `), uri).Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID) 29 + if err != nil { 30 + return nil, err 31 + } 32 + return &h, nil 33 + } 34 + 35 + func (db *DB) GetRecentHighlights(limit, offset int) ([]Highlight, error) { 36 + rows, err := db.Query(db.Rebind(` 37 + SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 38 + FROM highlights 39 + ORDER BY created_at DESC 40 + LIMIT ? OFFSET ? 41 + `), limit, offset) 42 + if err != nil { 43 + return nil, err 44 + } 45 + defer rows.Close() 46 + 47 + var highlights []Highlight 48 + for rows.Next() { 49 + var h Highlight 50 + if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 51 + return nil, err 52 + } 53 + highlights = append(highlights, h) 54 + } 55 + return highlights, nil 56 + } 57 + 58 + func (db *DB) GetHighlightsByTag(tag string, limit, offset int) ([]Highlight, error) { 59 + pattern := "%\"" + tag + "\"%" 60 + rows, err := db.Query(db.Rebind(` 61 + SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 62 + FROM highlights 63 + WHERE tags_json LIKE ? 64 + ORDER BY created_at DESC 65 + LIMIT ? OFFSET ? 66 + `), pattern, limit, offset) 67 + if err != nil { 68 + return nil, err 69 + } 70 + defer rows.Close() 71 + 72 + var highlights []Highlight 73 + for rows.Next() { 74 + var h Highlight 75 + if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 76 + return nil, err 77 + } 78 + highlights = append(highlights, h) 79 + } 80 + return highlights, nil 81 + } 82 + 83 + func (db *DB) GetHighlightsByTagAndAuthor(tag, authorDID string, limit, offset int) ([]Highlight, error) { 84 + pattern := "%\"" + tag + "\"%" 85 + rows, err := db.Query(db.Rebind(` 86 + SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 87 + FROM highlights 88 + WHERE author_did = ? AND tags_json LIKE ? 89 + ORDER BY created_at DESC 90 + LIMIT ? OFFSET ? 91 + `), authorDID, pattern, limit, offset) 92 + if err != nil { 93 + return nil, err 94 + } 95 + defer rows.Close() 96 + 97 + var highlights []Highlight 98 + for rows.Next() { 99 + var h Highlight 100 + if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 101 + return nil, err 102 + } 103 + highlights = append(highlights, h) 104 + } 105 + return highlights, nil 106 + } 107 + 108 + func (db *DB) GetHighlightsByTargetHash(targetHash string, limit, offset int) ([]Highlight, error) { 109 + rows, err := db.Query(db.Rebind(` 110 + SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 111 + FROM highlights 112 + WHERE target_hash = ? 113 + ORDER BY created_at DESC 114 + LIMIT ? OFFSET ? 115 + `), targetHash, limit, offset) 116 + if err != nil { 117 + return nil, err 118 + } 119 + defer rows.Close() 120 + 121 + var highlights []Highlight 122 + for rows.Next() { 123 + var h Highlight 124 + if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 125 + return nil, err 126 + } 127 + highlights = append(highlights, h) 128 + } 129 + return highlights, nil 130 + } 131 + 132 + func (db *DB) GetHighlightsByAuthor(authorDID string, limit, offset int) ([]Highlight, error) { 133 + rows, err := db.Query(db.Rebind(` 134 + SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 135 + FROM highlights 136 + WHERE author_did = ? 137 + ORDER BY created_at DESC 138 + LIMIT ? OFFSET ? 139 + `), authorDID, limit, offset) 140 + if err != nil { 141 + return nil, err 142 + } 143 + defer rows.Close() 144 + 145 + var highlights []Highlight 146 + for rows.Next() { 147 + var h Highlight 148 + if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 149 + return nil, err 150 + } 151 + highlights = append(highlights, h) 152 + } 153 + return highlights, nil 154 + } 155 + 156 + func (db *DB) DeleteHighlight(uri string) error { 157 + _, err := db.Exec(db.Rebind(`DELETE FROM highlights WHERE uri = ?`), uri) 158 + return err 159 + } 160 + 161 + func (db *DB) UpdateHighlight(uri, color, tagsJSON, cid string) error { 162 + _, err := db.Exec(db.Rebind(` 163 + UPDATE highlights 164 + SET color = ?, tags_json = ?, cid = ?, indexed_at = ? 165 + WHERE uri = ? 166 + `), color, tagsJSON, cid, time.Now(), uri) 167 + return err 168 + } 169 + 170 + func (db *DB) GetHighlightsByURIs(uris []string) ([]Highlight, error) { 171 + if len(uris) == 0 { 172 + return []Highlight{}, nil 173 + } 174 + 175 + query := db.Rebind(` 176 + SELECT uri, author_did, target_source, target_hash, target_title, selector_json, color, tags_json, created_at, indexed_at, cid 177 + FROM highlights 178 + WHERE uri IN (` + buildPlaceholders(len(uris)) + `) 179 + `) 180 + 181 + args := make([]interface{}, len(uris)) 182 + for i, uri := range uris { 183 + args[i] = uri 184 + } 185 + 186 + rows, err := db.Query(query, args...) 187 + if err != nil { 188 + return nil, err 189 + } 190 + defer rows.Close() 191 + 192 + var highlights []Highlight 193 + for rows.Next() { 194 + var h Highlight 195 + if err := rows.Scan(&h.URI, &h.AuthorDID, &h.TargetSource, &h.TargetHash, &h.TargetTitle, &h.SelectorJSON, &h.Color, &h.TagsJSON, &h.CreatedAt, &h.IndexedAt, &h.CID); err != nil { 196 + return nil, err 197 + } 198 + highlights = append(highlights, h) 199 + } 200 + return highlights, nil 201 + }
+36
backend/internal/db/queries_history.go
··· 1 + package db 2 + 3 + import ( 4 + "time" 5 + ) 6 + 7 + func (db *DB) SaveEditHistory(uri, recordType, previousContent string, previousCID *string) error { 8 + _, err := db.Exec(db.Rebind(` 9 + INSERT INTO edit_history (uri, record_type, previous_content, previous_cid, edited_at) 10 + VALUES (?, ?, ?, ?, ?) 11 + `), uri, recordType, previousContent, previousCID, time.Now()) 12 + return err 13 + } 14 + 15 + func (db *DB) GetEditHistory(uri string) ([]EditHistory, error) { 16 + rows, err := db.Query(db.Rebind(` 17 + SELECT id, uri, record_type, previous_content, previous_cid, edited_at 18 + FROM edit_history 19 + WHERE uri = ? 20 + ORDER BY edited_at DESC 21 + `), uri) 22 + if err != nil { 23 + return nil, err 24 + } 25 + defer rows.Close() 26 + 27 + var history []EditHistory 28 + for rows.Next() { 29 + var h EditHistory 30 + if err := rows.Scan(&h.ID, &h.URI, &h.RecordType, &h.PreviousContent, &h.PreviousCID, &h.EditedAt); err != nil { 31 + return nil, err 32 + } 33 + history = append(history, h) 34 + } 35 + return history, nil 36 + }
+59
backend/internal/db/queries_keys.go
··· 1 + package db 2 + 3 + import ( 4 + "time" 5 + ) 6 + 7 + func (db *DB) CreateAPIKey(key *APIKey) error { 8 + _, err := db.Exec(db.Rebind(` 9 + INSERT INTO api_keys (id, owner_did, name, key_hash, created_at) 10 + VALUES (?, ?, ?, ?, ?) 11 + `), key.ID, key.OwnerDID, key.Name, key.KeyHash, key.CreatedAt) 12 + return err 13 + } 14 + 15 + func (db *DB) GetAPIKeysByOwner(ownerDID string) ([]APIKey, error) { 16 + rows, err := db.Query(db.Rebind(` 17 + SELECT id, owner_did, name, key_hash, created_at, last_used_at 18 + FROM api_keys 19 + WHERE owner_did = ? 20 + ORDER BY created_at DESC 21 + `), ownerDID) 22 + if err != nil { 23 + return nil, err 24 + } 25 + defer rows.Close() 26 + 27 + var keys []APIKey 28 + for rows.Next() { 29 + var k APIKey 30 + if err := rows.Scan(&k.ID, &k.OwnerDID, &k.Name, &k.KeyHash, &k.CreatedAt, &k.LastUsedAt); err != nil { 31 + return nil, err 32 + } 33 + keys = append(keys, k) 34 + } 35 + return keys, nil 36 + } 37 + 38 + func (db *DB) GetAPIKeyByHash(keyHash string) (*APIKey, error) { 39 + var k APIKey 40 + err := db.QueryRow(db.Rebind(` 41 + SELECT id, owner_did, name, key_hash, created_at, last_used_at 42 + FROM api_keys 43 + WHERE key_hash = ? 44 + `), keyHash).Scan(&k.ID, &k.OwnerDID, &k.Name, &k.KeyHash, &k.CreatedAt, &k.LastUsedAt) 45 + if err != nil { 46 + return nil, err 47 + } 48 + return &k, nil 49 + } 50 + 51 + func (db *DB) DeleteAPIKey(id, ownerDID string) error { 52 + _, err := db.Exec(db.Rebind(`DELETE FROM api_keys WHERE id = ? AND owner_did = ?`), id, ownerDID) 53 + return err 54 + } 55 + 56 + func (db *DB) UpdateAPIKeyLastUsed(id string) error { 57 + _, err := db.Exec(db.Rebind(`UPDATE api_keys SET last_used_at = ? WHERE id = ?`), time.Now(), id) 58 + return err 59 + }
+105
backend/internal/db/queries_likes.go
··· 1 + package db 2 + 3 + func (db *DB) CreateLike(l *Like) error { 4 + _, err := db.Exec(db.Rebind(` 5 + INSERT INTO likes (uri, author_did, subject_uri, created_at, indexed_at) 6 + VALUES (?, ?, ?, ?, ?) 7 + ON CONFLICT(uri) DO NOTHING 8 + `), l.URI, l.AuthorDID, l.SubjectURI, l.CreatedAt, l.IndexedAt) 9 + return err 10 + } 11 + 12 + func (db *DB) DeleteLike(uri string) error { 13 + _, err := db.Exec(db.Rebind(`DELETE FROM likes WHERE uri = ?`), uri) 14 + return err 15 + } 16 + 17 + func (db *DB) GetLikeCount(subjectURI string) (int, error) { 18 + var count int 19 + err := db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM likes WHERE subject_uri = ?`), subjectURI).Scan(&count) 20 + return count, err 21 + } 22 + 23 + func (db *DB) GetLikeByUserAndSubject(userDID, subjectURI string) (*Like, error) { 24 + var like Like 25 + err := db.QueryRow(db.Rebind(` 26 + SELECT uri, author_did, subject_uri, created_at, indexed_at 27 + FROM likes 28 + WHERE author_did = ? AND subject_uri = ? 29 + `), userDID, subjectURI).Scan(&like.URI, &like.AuthorDID, &like.SubjectURI, &like.CreatedAt, &like.IndexedAt) 30 + if err != nil { 31 + return nil, err 32 + } 33 + return &like, nil 34 + } 35 + 36 + func (db *DB) GetLikeCounts(subjectURIs []string) (map[string]int, error) { 37 + if len(subjectURIs) == 0 { 38 + return map[string]int{}, nil 39 + } 40 + 41 + query := db.Rebind(` 42 + SELECT subject_uri, COUNT(*) 43 + FROM likes 44 + WHERE subject_uri IN (` + buildPlaceholders(len(subjectURIs)) + `) 45 + GROUP BY subject_uri 46 + `) 47 + 48 + args := make([]interface{}, len(subjectURIs)) 49 + for i, uri := range subjectURIs { 50 + args[i] = uri 51 + } 52 + 53 + rows, err := db.Query(query, args...) 54 + if err != nil { 55 + return nil, err 56 + } 57 + defer rows.Close() 58 + 59 + counts := make(map[string]int) 60 + for rows.Next() { 61 + var uri string 62 + var count int 63 + if err := rows.Scan(&uri, &count); err != nil { 64 + return nil, err 65 + } 66 + counts[uri] = count 67 + } 68 + 69 + return counts, nil 70 + } 71 + 72 + func (db *DB) GetViewerLikes(viewerDID string, subjectURIs []string) (map[string]bool, error) { 73 + if len(subjectURIs) == 0 { 74 + return map[string]bool{}, nil 75 + } 76 + 77 + query := db.Rebind(` 78 + SELECT subject_uri 79 + FROM likes 80 + WHERE author_did = ? AND subject_uri IN (` + buildPlaceholders(len(subjectURIs)) + `) 81 + `) 82 + 83 + args := make([]interface{}, len(subjectURIs)+1) 84 + args[0] = viewerDID 85 + for i, uri := range subjectURIs { 86 + args[i+1] = uri 87 + } 88 + 89 + rows, err := db.Query(query, args...) 90 + if err != nil { 91 + return nil, err 92 + } 93 + defer rows.Close() 94 + 95 + likes := make(map[string]bool) 96 + for rows.Next() { 97 + var uri string 98 + if err := rows.Scan(&uri); err != nil { 99 + return nil, err 100 + } 101 + likes[uri] = true 102 + } 103 + 104 + return likes, nil 105 + }
+52
backend/internal/db/queries_notifications.go
··· 1 + package db 2 + 3 + import ( 4 + "time" 5 + ) 6 + 7 + func (db *DB) CreateNotification(n *Notification) error { 8 + _, err := db.Exec(db.Rebind(` 9 + INSERT INTO notifications (recipient_did, actor_did, type, subject_uri, created_at) 10 + VALUES (?, ?, ?, ?, ?) 11 + `), n.RecipientDID, n.ActorDID, n.Type, n.SubjectURI, n.CreatedAt) 12 + return err 13 + } 14 + 15 + func (db *DB) GetNotifications(recipientDID string, limit, offset int) ([]Notification, error) { 16 + rows, err := db.Query(db.Rebind(` 17 + SELECT id, recipient_did, actor_did, type, subject_uri, created_at, read_at 18 + FROM notifications 19 + WHERE recipient_did = ? 20 + ORDER BY created_at DESC 21 + LIMIT ? OFFSET ? 22 + `), recipientDID, limit, offset) 23 + if err != nil { 24 + return nil, err 25 + } 26 + defer rows.Close() 27 + 28 + var notifications []Notification 29 + for rows.Next() { 30 + var n Notification 31 + if err := rows.Scan(&n.ID, &n.RecipientDID, &n.ActorDID, &n.Type, &n.SubjectURI, &n.CreatedAt, &n.ReadAt); err != nil { 32 + continue 33 + } 34 + notifications = append(notifications, n) 35 + } 36 + return notifications, nil 37 + } 38 + 39 + func (db *DB) GetUnreadNotificationCount(recipientDID string) (int, error) { 40 + var count int 41 + err := db.QueryRow(db.Rebind(` 42 + SELECT COUNT(*) FROM notifications WHERE recipient_did = ? AND read_at IS NULL 43 + `), recipientDID).Scan(&count) 44 + return count, err 45 + } 46 + 47 + func (db *DB) MarkNotificationsRead(recipientDID string) error { 48 + _, err := db.Exec(db.Rebind(` 49 + UPDATE notifications SET read_at = ? WHERE recipient_did = ? AND read_at IS NULL 50 + `), time.Now(), recipientDID) 51 + return err 52 + }
+176
backend/internal/db/queries_replies.go
··· 1 + package db 2 + 3 + func (db *DB) CreateReply(r *Reply) error { 4 + _, err := db.Exec(db.Rebind(` 5 + INSERT INTO replies (uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid) 6 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) 7 + ON CONFLICT(uri) DO UPDATE SET 8 + text = excluded.text, 9 + format = excluded.format, 10 + indexed_at = excluded.indexed_at, 11 + cid = excluded.cid 12 + `), r.URI, r.AuthorDID, r.ParentURI, r.RootURI, r.Text, r.Format, r.CreatedAt, r.IndexedAt, r.CID) 13 + return err 14 + } 15 + 16 + func (db *DB) GetRepliesByRoot(rootURI string) ([]Reply, error) { 17 + rows, err := db.Query(db.Rebind(` 18 + SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid 19 + FROM replies 20 + WHERE root_uri = ? 21 + ORDER BY created_at ASC 22 + `), rootURI) 23 + if err != nil { 24 + return nil, err 25 + } 26 + defer rows.Close() 27 + 28 + var replies []Reply 29 + for rows.Next() { 30 + var r Reply 31 + if err := rows.Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID); err != nil { 32 + return nil, err 33 + } 34 + replies = append(replies, r) 35 + } 36 + return replies, nil 37 + } 38 + 39 + func (db *DB) GetReplyByURI(uri string) (*Reply, error) { 40 + var r Reply 41 + err := db.QueryRow(db.Rebind(` 42 + SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid 43 + FROM replies 44 + WHERE uri = ? 45 + `), uri).Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID) 46 + if err != nil { 47 + return nil, err 48 + } 49 + return &r, nil 50 + } 51 + 52 + func (db *DB) DeleteReply(uri string) error { 53 + _, err := db.Exec(db.Rebind(`DELETE FROM replies WHERE uri = ?`), uri) 54 + return err 55 + } 56 + 57 + func (db *DB) GetRepliesByAuthor(authorDID string) ([]Reply, error) { 58 + rows, err := db.Query(db.Rebind(` 59 + SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid 60 + FROM replies 61 + WHERE author_did = ? 62 + ORDER BY created_at DESC 63 + `), authorDID) 64 + if err != nil { 65 + return nil, err 66 + } 67 + defer rows.Close() 68 + 69 + var replies []Reply 70 + for rows.Next() { 71 + var r Reply 72 + if err := rows.Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID); err != nil { 73 + return nil, err 74 + } 75 + replies = append(replies, r) 76 + } 77 + return replies, nil 78 + } 79 + 80 + func (db *DB) GetOrphanedRepliesByAuthor(authorDID string) ([]Reply, error) { 81 + rows, err := db.Query(db.Rebind(` 82 + SELECT r.uri, r.author_did, r.parent_uri, r.root_uri, r.text, r.format, r.created_at, r.indexed_at, r.cid 83 + FROM replies r 84 + LEFT JOIN annotations a ON r.root_uri = a.uri 85 + WHERE r.author_did = ? AND a.uri IS NULL 86 + `), authorDID) 87 + if err != nil { 88 + return nil, err 89 + } 90 + defer rows.Close() 91 + 92 + var replies []Reply 93 + for rows.Next() { 94 + var r Reply 95 + if err := rows.Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID); err != nil { 96 + return nil, err 97 + } 98 + replies = append(replies, r) 99 + } 100 + return replies, nil 101 + } 102 + 103 + func (db *DB) GetReplyCount(rootURI string) (int, error) { 104 + var count int 105 + err := db.QueryRow(db.Rebind(`SELECT COUNT(*) FROM replies WHERE root_uri = ?`), rootURI).Scan(&count) 106 + return count, err 107 + } 108 + 109 + func (db *DB) GetReplyCounts(rootURIs []string) (map[string]int, error) { 110 + if len(rootURIs) == 0 { 111 + return map[string]int{}, nil 112 + } 113 + 114 + query := db.Rebind(` 115 + SELECT root_uri, COUNT(*) 116 + FROM replies 117 + WHERE root_uri IN (` + buildPlaceholders(len(rootURIs)) + `) 118 + GROUP BY root_uri 119 + `) 120 + 121 + args := make([]interface{}, len(rootURIs)) 122 + for i, uri := range rootURIs { 123 + args[i] = uri 124 + } 125 + 126 + rows, err := db.Query(query, args...) 127 + if err != nil { 128 + return nil, err 129 + } 130 + defer rows.Close() 131 + 132 + counts := make(map[string]int) 133 + for rows.Next() { 134 + var uri string 135 + var count int 136 + if err := rows.Scan(&uri, &count); err != nil { 137 + return nil, err 138 + } 139 + counts[uri] = count 140 + } 141 + 142 + return counts, nil 143 + } 144 + 145 + func (db *DB) GetRepliesByURIs(uris []string) ([]Reply, error) { 146 + if len(uris) == 0 { 147 + return []Reply{}, nil 148 + } 149 + 150 + query := db.Rebind(` 151 + SELECT uri, author_did, parent_uri, root_uri, text, format, created_at, indexed_at, cid 152 + FROM replies 153 + WHERE uri IN (` + buildPlaceholders(len(uris)) + `) 154 + `) 155 + 156 + args := make([]interface{}, len(uris)) 157 + for i, uri := range uris { 158 + args[i] = uri 159 + } 160 + 161 + rows, err := db.Query(query, args...) 162 + if err != nil { 163 + return nil, err 164 + } 165 + defer rows.Close() 166 + 167 + var replies []Reply 168 + for rows.Next() { 169 + var r Reply 170 + if err := rows.Scan(&r.URI, &r.AuthorDID, &r.ParentURI, &r.RootURI, &r.Text, &r.Format, &r.CreatedAt, &r.IndexedAt, &r.CID); err != nil { 171 + return nil, err 172 + } 173 + replies = append(replies, r) 174 + } 175 + return replies, nil 176 + }
+32
backend/internal/db/queries_sessions.go
··· 1 + package db 2 + 3 + import ( 4 + "time" 5 + ) 6 + 7 + func (db *DB) SaveSession(id, did, handle, accessToken, refreshToken, dpopKey string, expiresAt time.Time) error { 8 + _, err := db.Exec(db.Rebind(` 9 + INSERT INTO sessions (id, did, handle, access_token, refresh_token, dpop_key, created_at, expires_at) 10 + VALUES (?, ?, ?, ?, ?, ?, ?, ?) 11 + ON CONFLICT(id) DO UPDATE SET 12 + access_token = excluded.access_token, 13 + refresh_token = excluded.refresh_token, 14 + dpop_key = excluded.dpop_key, 15 + expires_at = excluded.expires_at 16 + `), id, did, handle, accessToken, refreshToken, dpopKey, time.Now(), expiresAt) 17 + return err 18 + } 19 + 20 + func (db *DB) GetSession(id string) (did, handle, accessToken, refreshToken, dpopKey string, err error) { 21 + err = db.QueryRow(db.Rebind(` 22 + SELECT did, handle, access_token, refresh_token, COALESCE(dpop_key, '') 23 + FROM sessions 24 + WHERE id = ? AND expires_at > ? 25 + `), id, time.Now()).Scan(&did, &handle, &accessToken, &refreshToken, &dpopKey) 26 + return 27 + } 28 + 29 + func (db *DB) DeleteSession(id string) error { 30 + _, err := db.Exec(db.Rebind(`DELETE FROM sessions WHERE id = ?`), id) 31 + return err 32 + }