(READ ONLY) Margin is an open annotation layer for the internet. Powered by the AT Protocol. margin.at
extension web atproto comments
98
fork

Configure Feed

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

Various optimizations and caching implemented

scanash00 01e5086e 736f948c

+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 + }