This repository has no description
0

Configure Feed

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

core / appview / db / repos.go
22 kB 877 lines
1package db 2 3import ( 4 "context" 5 "database/sql" 6 "errors" 7 "fmt" 8 "log" 9 "slices" 10 "strings" 11 "time" 12 13 "github.com/bluesky-social/indigo/atproto/syntax" 14 "tangled.org/core/api/tangled" 15 "tangled.org/core/appview/models" 16 "tangled.org/core/appview/pagination" 17 "tangled.org/core/orm" 18) 19 20func RenameRepo(tx *sql.Tx, did, oldRkey, newRkey, newName string) error { 21 newAtURI := fmt.Sprintf("at://%s/sh.tangled.repo/%s", did, newRkey) 22 23 res, err := tx.Exec( 24 `update repos set rkey = ?, name = ?, at_uri = ? where did = ? and rkey = ?`, 25 newRkey, newName, newAtURI, did, oldRkey, 26 ) 27 if err != nil { 28 return fmt.Errorf("update repos row: %w", err) 29 } 30 if n, _ := res.RowsAffected(); n == 0 { 31 return fmt.Errorf("no repo row found for did=%s rkey=%s", did, oldRkey) 32 } 33 34 if _, err := tx.Exec( 35 `update pipelines set repo_name = ? where repo_owner = ? and repo_name = ?`, 36 newRkey, did, oldRkey, 37 ); err != nil { 38 return fmt.Errorf("rename pipelines.repo_name: %w", err) 39 } 40 41 return nil 42} 43 44func UpdateRepoDisplayName(e Execer, did, rkey, newName string) error { 45 _, err := e.Exec( 46 `update repos set name = ? where did = ? and rkey = ?`, 47 newName, did, rkey, 48 ) 49 return err 50} 51 52func RecordRepoRename(e Execer, ownerDid, oldRkey, repoDid string) error { 53 _, err := e.Exec( 54 `insert into repo_renames (owner_did, old_rkey, repo_did) 55 values (?, ?, ?) 56 on conflict(owner_did, old_rkey) do update set 57 repo_did = excluded.repo_did, 58 renamed_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')`, 59 ownerDid, oldRkey, repoDid, 60 ) 61 return err 62} 63 64func DeleteRepoRename(e Execer, ownerDid, oldRkey string) error { 65 _, err := e.Exec( 66 `delete from repo_renames where owner_did = ? and old_rkey = ?`, 67 ownerDid, oldRkey, 68 ) 69 return err 70} 71 72func LookupRepoRename(e Execer, ownerDid, oldRkey string) (*models.Repo, error) { 73 var repoDid string 74 err := e.QueryRow( 75 `select repo_did from repo_renames where owner_did = ? and old_rkey = ?`, 76 ownerDid, oldRkey, 77 ).Scan(&repoDid) 78 if err != nil { 79 return nil, err 80 } 81 82 repo, err := GetRepoByDid(e, repoDid) 83 if err != nil { 84 return nil, err 85 } 86 return repo, nil 87} 88 89func GetRepos(e Execer, filters ...orm.Filter) ([]models.Repo, error) { 90 return GetReposPaginated(e, pagination.Page{}, filters...) 91} 92 93func GetReposPaginated(e Execer, page pagination.Page, filters ...orm.Filter) ([]models.Repo, error) { 94 var conditions []string 95 var args []any 96 for _, filter := range filters { 97 conditions = append(conditions, filter.Condition()) 98 args = append(args, filter.Arg()...) 99 } 100 101 whereClause := "" 102 if conditions != nil { 103 whereClause = " where " + strings.Join(conditions, " and ") 104 } 105 106 pageClause := "" 107 if page.Limit != 0 { 108 pageClause = fmt.Sprintf(" limit %d offset %d", page.Limit, page.Offset) 109 } 110 111 // main query to get repos with pagination 112 query := fmt.Sprintf(` 113 select 114 id, 115 did, 116 name, 117 knot, 118 rkey, 119 created, 120 description, 121 website, 122 topics, 123 source, 124 spindle, 125 repo_did 126 from repos 127 %s 128 order by created desc 129 %s 130 `, whereClause, pageClause) 131 132 rows, err := e.Query(query, args...) 133 if err != nil { 134 return nil, err 135 } 136 defer rows.Close() 137 138 repoMap := make(map[string]*models.Repo) 139 for rows.Next() { 140 var repo models.Repo 141 var createdAt string 142 var description, website, topicStr, source, spindle, repoDid sql.NullString 143 144 err := rows.Scan( 145 &repo.Id, 146 &repo.Did, 147 &repo.Name, 148 &repo.Knot, 149 &repo.Rkey, 150 &createdAt, 151 &description, 152 &website, 153 &topicStr, 154 &source, 155 &spindle, 156 &repoDid, 157 ) 158 if err != nil { 159 return nil, err 160 } 161 162 // parse created timestamp 163 if t, err := time.Parse(time.RFC3339, createdAt); err == nil { 164 repo.Created = t 165 } 166 167 // handle nullable fields 168 if description.Valid { 169 repo.Description = description.String 170 } 171 if website.Valid { 172 repo.Website = website.String 173 } 174 if topicStr.Valid { 175 repo.Topics = strings.Fields(topicStr.String) 176 } 177 if source.Valid { 178 repo.Source = source.String 179 } 180 if spindle.Valid { 181 repo.Spindle = spindle.String 182 } 183 if repoDid.Valid { 184 repo.RepoDid = repoDid.String 185 } 186 187 repo.RepoStats = &models.RepoStats{} 188 repoMap[repo.RepoDid] = &repo 189 } 190 191 if err = rows.Err(); err != nil { 192 return nil, err 193 } 194 195 // if no repos, return early 196 if len(repoMap) == 0 { 197 return nil, nil 198 } 199 200 // build IN clause for related queries 201 inClause := strings.TrimSuffix(strings.Repeat("?, ", len(repoMap)), ", ") 202 args = make([]any, len(repoMap)) 203 i := 0 204 for _, r := range repoMap { 205 args[i] = r.RepoDid 206 i++ 207 } 208 209 // get labels for all repos 210 labelsQuery := fmt.Sprintf( 211 `select repo_did, label_at from repo_labels where repo_did in (%s)`, 212 inClause, 213 ) 214 215 rows, err = e.Query(labelsQuery, args...) 216 if err != nil { 217 return nil, err 218 } 219 defer rows.Close() 220 221 for rows.Next() { 222 var repoDid, labelat string 223 if err := rows.Scan(&repoDid, &labelat); err != nil { 224 continue 225 } 226 if r, ok := repoMap[repoDid]; ok { 227 r.Labels = append(r.Labels, labelat) 228 } 229 } 230 231 // get primary language for all repos 232 languageQuery := fmt.Sprintf(` 233 select repo_did, language 234 from ( 235 select 236 repo_did, language, 237 row_number() over ( 238 partition by repo_did 239 order by bytes desc 240 ) as rn 241 from repo_languages 242 where repo_did in (%s) 243 and is_default_ref = 1 244 and language <> '' 245 ) 246 where rn = 1 247 `, inClause) 248 249 rows, err = e.Query(languageQuery, args...) 250 if err != nil { 251 return nil, fmt.Errorf("failed to execute lang query: %w", err) 252 } 253 defer rows.Close() 254 255 for rows.Next() { 256 var repoDid, lang string 257 if err := rows.Scan(&repoDid, &lang); err != nil { 258 log.Println("err", "err", err) 259 continue 260 } 261 if r, ok := repoMap[repoDid]; ok { 262 r.RepoStats.Language = lang 263 } 264 } 265 if err = rows.Err(); err != nil { 266 return nil, fmt.Errorf("failed to execute lang query: %w", err) 267 } 268 269 // get star counts 270 starCountQuery := fmt.Sprintf( 271 `select subject, count(1) from stars where subject_type = 'repo' and subject in (%s) group by subject`, 272 inClause, 273 ) 274 275 rows, err = e.Query(starCountQuery, args...) 276 if err != nil { 277 return nil, fmt.Errorf("failed to execute star-count query: %w", err) 278 } 279 defer rows.Close() 280 281 for rows.Next() { 282 var repoDid string 283 var count int 284 if err := rows.Scan(&repoDid, &count); err != nil { 285 log.Println("err", "err", err) 286 continue 287 } 288 if r, ok := repoMap[repoDid]; ok { 289 r.RepoStats.StarCount = count 290 } 291 } 292 if err = rows.Err(); err != nil { 293 return nil, fmt.Errorf("failed to execute star-count query: %w", err) 294 } 295 296 // get issue counts 297 issueCountQuery := fmt.Sprintf(` 298 select 299 repo_did, 300 count(case when open = 1 then 1 end) as open_count, 301 count(case when open = 0 then 1 end) as closed_count 302 from issues 303 where repo_did in (%s) 304 group by repo_did 305 `, inClause) 306 307 rows, err = e.Query(issueCountQuery, args...) 308 if err != nil { 309 return nil, fmt.Errorf("failed to execute issue-count query: %w", err) 310 } 311 defer rows.Close() 312 313 for rows.Next() { 314 var repoDid string 315 var open, closed int 316 if err := rows.Scan(&repoDid, &open, &closed); err != nil { 317 log.Println("err", "err", err) 318 continue 319 } 320 if r, ok := repoMap[repoDid]; ok { 321 r.RepoStats.IssueCount.Open = open 322 r.RepoStats.IssueCount.Closed = closed 323 } 324 } 325 if err = rows.Err(); err != nil { 326 return nil, fmt.Errorf("failed to execute issue-count query: %w", err) 327 } 328 329 // get pull counts 330 pullCountQuery := fmt.Sprintf(` 331 select 332 repo_did, 333 count(case when state = ? then 1 end) as open_count, 334 count(case when state = ? then 1 end) as merged_count, 335 count(case when state = ? then 1 end) as closed_count, 336 count(case when state = ? then 1 end) as deleted_count 337 from pulls 338 where repo_did in (%s) 339 group by repo_did 340 `, inClause) 341 342 pullArgs := append([]any{ 343 models.PullOpen, 344 models.PullMerged, 345 models.PullClosed, 346 models.PullAbandoned, 347 }, args...) 348 349 rows, err = e.Query(pullCountQuery, pullArgs...) 350 if err != nil { 351 return nil, fmt.Errorf("failed to execute pulls-count query: %w", err) 352 } 353 defer rows.Close() 354 355 for rows.Next() { 356 var repoDid string 357 var open, merged, closed, deleted int 358 if err := rows.Scan(&repoDid, &open, &merged, &closed, &deleted); err != nil { 359 log.Println("err", "err", err) 360 continue 361 } 362 if r, ok := repoMap[repoDid]; ok { 363 r.RepoStats.PullCount.Open = open 364 r.RepoStats.PullCount.Merged = merged 365 r.RepoStats.PullCount.Closed = closed 366 r.RepoStats.PullCount.Deleted = deleted 367 } 368 } 369 if err = rows.Err(); err != nil { 370 return nil, fmt.Errorf("failed to execute pulls-count query: %w", err) 371 } 372 373 // get forks 374 forksInClause := strings.TrimSuffix(strings.Repeat("?, ", len(repoMap)), ", ") 375 376 forksCountQuery := fmt.Sprintf( 377 `select source, count(1) from repos where source in (%s) group by source`, 378 forksInClause, 379 ) 380 381 rows, err = e.Query(forksCountQuery, args...) 382 if err != nil { 383 return nil, fmt.Errorf("failed to execute fork-count query: %w", err) 384 } 385 defer rows.Close() 386 387 for rows.Next() { 388 var repodid string 389 var count int 390 if err := rows.Scan(&repodid, &count); err != nil { 391 log.Println("err", "err", err) 392 continue 393 } 394 395 if r, ok := repoMap[repodid]; ok { 396 r.RepoStats.ForkCount = count 397 } 398 } 399 if err = rows.Err(); err != nil { 400 return nil, fmt.Errorf("failed to execute fork-count query: %w", err) 401 } 402 403 var repos []models.Repo 404 for _, r := range repoMap { 405 repos = append(repos, *r) 406 } 407 408 // sort by created timestamp (desc) 409 slices.SortFunc(repos, func(a, b models.Repo) int { 410 if a.Created.After(b.Created) { 411 return -1 412 } 413 return 1 414 }) 415 416 return repos, nil 417} 418 419// helper to get exactly one repo 420func GetRepo(e Execer, filters ...orm.Filter) (*models.Repo, error) { 421 repos, err := GetReposPaginated(e, pagination.Page{Limit: 1}, filters...) 422 if err != nil { 423 return nil, err 424 } 425 426 if repos == nil { 427 return nil, sql.ErrNoRows 428 } 429 430 if len(repos) != 1 { 431 return nil, fmt.Errorf("too few rows returned") 432 } 433 434 return &repos[0], nil 435} 436 437func CountRepos(e Execer, filters ...orm.Filter) (int64, error) { 438 var conditions []string 439 var args []any 440 for _, filter := range filters { 441 conditions = append(conditions, filter.Condition()) 442 args = append(args, filter.Arg()...) 443 } 444 445 whereClause := "" 446 if conditions != nil { 447 whereClause = " where " + strings.Join(conditions, " and ") 448 } 449 450 repoQuery := fmt.Sprintf(`select count(1) from repos %s`, whereClause) 451 var count int64 452 err := e.QueryRow(repoQuery, args...).Scan(&count) 453 454 if !errors.Is(err, sql.ErrNoRows) && err != nil { 455 return 0, err 456 } 457 458 return count, nil 459} 460 461func GetRepoByAtUri(e Execer, atUri string) (*models.Repo, error) { 462 var repo models.Repo 463 var nullableDescription sql.NullString 464 var nullableWebsite sql.NullString 465 var nullableTopicStr sql.NullString 466 var nullableRepoDid sql.NullString 467 var nullableSource sql.NullString 468 var nullableSpindle sql.NullString 469 470 row := e.QueryRow(`select id, did, name, knot, created, rkey, description, website, topics, source, spindle, repo_did from repos where at_uri = ?`, atUri) 471 472 var createdAt string 473 if err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.Rkey, &nullableDescription, &nullableWebsite, &nullableTopicStr, &nullableSource, &nullableSpindle, &nullableRepoDid); err != nil { 474 return nil, err 475 } 476 createdAtTime, _ := time.Parse(time.RFC3339, createdAt) 477 repo.Created = createdAtTime 478 479 if nullableDescription.Valid { 480 repo.Description = nullableDescription.String 481 } 482 if nullableWebsite.Valid { 483 repo.Website = nullableWebsite.String 484 } 485 if nullableTopicStr.Valid { 486 repo.Topics = strings.Fields(nullableTopicStr.String) 487 } 488 if nullableSource.Valid { 489 repo.Source = nullableSource.String 490 } 491 if nullableSpindle.Valid { 492 repo.Spindle = nullableSpindle.String 493 } 494 if nullableRepoDid.Valid { 495 repo.RepoDid = nullableRepoDid.String 496 } 497 498 return &repo, nil 499} 500 501func PutRepo(tx *sql.Tx, repo models.Repo) error { 502 var repoDid *string 503 if repo.RepoDid != "" { 504 repoDid = &repo.RepoDid 505 } 506 _, err := tx.Exec( 507 `update repos 508 set name = ?, knot = ?, description = ?, website = ?, topics = ?, repo_did = coalesce(?, repo_did) 509 where did = ? and rkey = ? 510 `, 511 repo.Name, repo.Knot, repo.Description, repo.Website, repo.TopicStr(), repoDid, repo.Did, repo.Rkey, 512 ) 513 return err 514} 515 516func AddRepo(tx *sql.Tx, repo *models.Repo) error { 517 var repoDid *string 518 if repo.RepoDid != "" { 519 repoDid = &repo.RepoDid 520 } 521 result, err := tx.Exec( 522 `insert into repos 523 (did, name, knot, rkey, at_uri, description, website, topics, source, repo_did) 524 values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, 525 repo.Did, repo.Name, repo.Knot, repo.Rkey, repo.RepoAt().String(), repo.Description, repo.Website, repo.TopicStr(), repo.Source, repoDid, 526 ) 527 if err != nil { 528 return fmt.Errorf("failed to insert repo: %w", err) 529 } 530 531 id, err := result.LastInsertId() 532 if err != nil { 533 return fmt.Errorf("failed to get last insert id: %w", err) 534 } 535 repo.Id = id 536 537 for _, dl := range repo.Labels { 538 if err := SubscribeLabel(tx, &models.RepoLabel{ 539 RepoDid: syntax.DID(repo.RepoDid), 540 LabelAt: syntax.ATURI(dl), 541 }); err != nil { 542 return fmt.Errorf("failed to subscribe to label: %w", err) 543 } 544 } 545 546 return nil 547} 548 549func RemoveRepo(e Execer, did, rkey string) error { 550 _, err := e.Exec(`delete from repos where did = ? and rkey = ?`, did, rkey) 551 return err 552} 553 554func GetRepoSource(e Execer, repoDid string) (string, error) { 555 var nullableSource sql.NullString 556 err := e.QueryRow(`select source from repos where repo_did = ?`, repoDid).Scan(&nullableSource) 557 if err != nil { 558 return "", err 559 } 560 return nullableSource.String, nil 561} 562 563func GetRepoSourceRepo(e Execer, repoDid string) (*models.Repo, error) { 564 source, err := GetRepoSource(e, repoDid) 565 if source == "" || errors.Is(err, sql.ErrNoRows) { 566 return nil, nil 567 } 568 if err != nil { 569 return nil, err 570 } 571 if strings.HasPrefix(source, "did:") { 572 return GetRepoByDid(e, source) 573 } 574 return GetRepoByAtUri(e, source) 575} 576 577func GetForksByDid(e Execer, did string) ([]models.Repo, error) { 578 var repos []models.Repo 579 580 rows, err := e.Query( 581 `select distinct r.id, r.did, r.name, r.knot, r.rkey, r.description, r.website, r.created, r.source, r.repo_did 582 from repos r 583 left join collaborators c on r.repo_did = c.repo_did 584 where (r.did = ? or c.subject_did = ?) 585 and r.source is not null 586 and r.source != '' 587 order by r.created desc`, 588 did, did, 589 ) 590 if err != nil { 591 return nil, err 592 } 593 defer rows.Close() 594 595 for rows.Next() { 596 var repo models.Repo 597 var createdAt string 598 var nullableDescription sql.NullString 599 var nullableWebsite sql.NullString 600 var nullableSource sql.NullString 601 var nullableRepoDid sql.NullString 602 603 err := rows.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &nullableWebsite, &createdAt, &nullableSource, &nullableRepoDid) 604 if err != nil { 605 return nil, err 606 } 607 608 if nullableDescription.Valid { 609 repo.Description = nullableDescription.String 610 } 611 if nullableWebsite.Valid { 612 repo.Website = nullableWebsite.String 613 } 614 615 if nullableSource.Valid { 616 repo.Source = nullableSource.String 617 } 618 if nullableRepoDid.Valid { 619 repo.RepoDid = nullableRepoDid.String 620 } 621 622 createdAtTime, err := time.Parse(time.RFC3339, createdAt) 623 if err != nil { 624 repo.Created = time.Now() 625 } else { 626 repo.Created = createdAtTime 627 } 628 629 repos = append(repos, repo) 630 } 631 632 if err := rows.Err(); err != nil { 633 return nil, err 634 } 635 636 return repos, nil 637} 638 639func GetForkByDid(e Execer, did string, rkey string) (*models.Repo, error) { 640 var repo models.Repo 641 var createdAt string 642 var nullableDescription sql.NullString 643 var nullableWebsite sql.NullString 644 var nullableTopicStr sql.NullString 645 var nullableSource sql.NullString 646 var nullableRepoDid sql.NullString 647 648 row := e.QueryRow( 649 `select id, did, name, knot, rkey, description, website, topics, created, source, repo_did 650 from repos 651 where did = ? and rkey = ? and source is not null and source != ''`, 652 did, rkey, 653 ) 654 655 err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &nullableWebsite, &nullableTopicStr, &createdAt, &nullableSource, &nullableRepoDid) 656 if err != nil { 657 return nil, err 658 } 659 660 if nullableDescription.Valid { 661 repo.Description = nullableDescription.String 662 } 663 664 if nullableWebsite.Valid { 665 repo.Website = nullableWebsite.String 666 } 667 668 if nullableTopicStr.Valid { 669 repo.Topics = strings.Fields(nullableTopicStr.String) 670 } 671 672 if nullableSource.Valid { 673 repo.Source = nullableSource.String 674 } 675 if nullableRepoDid.Valid { 676 repo.RepoDid = nullableRepoDid.String 677 } 678 679 createdAtTime, err := time.Parse(time.RFC3339, createdAt) 680 if err != nil { 681 repo.Created = time.Now() 682 } else { 683 repo.Created = createdAtTime 684 } 685 686 return &repo, nil 687} 688 689func GetRepoByDid(e Execer, repoDid string) (*models.Repo, error) { 690 return GetRepo(e, orm.FilterEq("repo_did", repoDid)) 691} 692 693// TODO: just queue every legacy records regardless of target repo has a DID or not. 694// doable after we have `repo_did` column in db for each tables. 695func EnqueuePdsRewritesForRepo(tx *sql.Tx, repoDid, repoAtUri string) error { 696 type record struct { 697 userDidCol string 698 table string 699 nsid syntax.NSID 700 fkCol string 701 fkVal string 702 } 703 sources := []record{ 704 {"did", "repos", tangled.RepoNSID, "at_uri", repoAtUri}, 705 {"did", "issues", tangled.RepoIssueNSID, "repo_did", repoDid}, 706 {"owner_did", "pulls", tangled.RepoPullNSID, "repo_did", repoDid}, 707 {"did", "collaborators", tangled.RepoCollaboratorNSID, "repo_did", repoDid}, 708 {"did", "artifacts", tangled.RepoArchiveNSID, "repo_did", repoDid}, 709 {"did", "stars", tangled.FeedStarNSID, "subject", repoDid}, 710 } 711 712 for _, src := range sources { 713 rows, err := tx.Query( 714 fmt.Sprintf(`SELECT %s, rkey FROM %s WHERE %s = ?`, src.userDidCol, src.table, src.fkCol), 715 src.fkVal, 716 ) 717 if err != nil { 718 return fmt.Errorf("query %s for pds rewrites: %w", src.table, err) 719 } 720 721 var pairs []struct{ did, rkey string } 722 for rows.Next() { 723 var d, r string 724 if scanErr := rows.Scan(&d, &r); scanErr != nil { 725 rows.Close() 726 return fmt.Errorf("scan %s for pds rewrites: %w", src.table, scanErr) 727 } 728 pairs = append(pairs, struct{ did, rkey string }{d, r}) 729 } 730 rows.Close() 731 if rowsErr := rows.Err(); rowsErr != nil { 732 return fmt.Errorf("iterate %s for pds rewrites: %w", src.table, rowsErr) 733 } 734 735 for _, p := range pairs { 736 if err := EnqueuePdsRecordMigration(context.Background(), tx, "add-repo-did", syntax.DID(p.did), src.nsid, syntax.RecordKey(p.rkey)); err != nil { 737 return fmt.Errorf("enqueue pds rewrite for %s/%s: %w", src.table, p.rkey, err) 738 } 739 } 740 } 741 742 profileRows, err := tx.Query( 743 `SELECT DISTINCT did FROM profile_pinned_repositories WHERE pin = ?`, 744 repoAtUri, 745 ) 746 if err != nil { 747 return fmt.Errorf("query profile_pinned_repositories for pds rewrites: %w", err) 748 } 749 var profileDids []string 750 for profileRows.Next() { 751 var d string 752 if scanErr := profileRows.Scan(&d); scanErr != nil { 753 profileRows.Close() 754 return fmt.Errorf("scan profile_pinned_repositories for pds rewrites: %w", scanErr) 755 } 756 profileDids = append(profileDids, d) 757 } 758 profileRows.Close() 759 if profileRowsErr := profileRows.Err(); profileRowsErr != nil { 760 return fmt.Errorf("iterate profile_pinned_repositories for pds rewrites: %w", profileRowsErr) 761 } 762 763 for _, d := range profileDids { 764 if err := EnqueuePdsRecordMigration(context.Background(), tx, "add-repo-did", syntax.DID(d), tangled.ActorProfileNSID, "self"); err != nil { 765 return fmt.Errorf("enqueue pds rewrite for profile/%s: %w", d, err) 766 } 767 } 768 769 return nil 770} 771 772func CascadeRepoDid(tx *sql.Tx, repoAtUri, repoDid string) error { 773 _, err := tx.Exec( 774 `UPDATE repos SET repo_did = ? WHERE at_uri = ?`, 775 repoDid, repoAtUri, 776 ) 777 if err != nil { 778 return fmt.Errorf("cascade repo_did to repos: %w", err) 779 } 780 781 _, err = tx.Exec( 782 `UPDATE repos SET source = ? WHERE source = ?`, 783 repoDid, repoAtUri, 784 ) 785 if err != nil { 786 return fmt.Errorf("cascade repo_did to repos.source: %w", err) 787 } 788 789 return nil 790} 791 792func UpdateDescription(e Execer, repoDid, newDescription string) error { 793 _, err := e.Exec( 794 `update repos set description = ? where repo_did = ?`, newDescription, repoDid) 795 return err 796} 797 798func UpdateSpindle(e Execer, repoDid string, spindle *string) error { 799 _, err := e.Exec( 800 `update repos set spindle = ? where repo_did = ?`, spindle, repoDid) 801 return err 802} 803 804func SubscribeLabel(e Execer, rl *models.RepoLabel) error { 805 query := `insert or ignore into repo_labels (repo_did, label_at) values (?, ?)` 806 807 _, err := e.Exec(query, string(rl.RepoDid), rl.LabelAt.String()) 808 return err 809} 810 811func UnsubscribeLabel(e Execer, filters ...orm.Filter) error { 812 var conditions []string 813 var args []any 814 for _, filter := range filters { 815 conditions = append(conditions, filter.Condition()) 816 args = append(args, filter.Arg()...) 817 } 818 819 whereClause := "" 820 if conditions != nil { 821 whereClause = " where " + strings.Join(conditions, " and ") 822 } 823 824 query := fmt.Sprintf(`delete from repo_labels %s`, whereClause) 825 _, err := e.Exec(query, args...) 826 return err 827} 828 829func GetRepoLabels(e Execer, filters ...orm.Filter) ([]models.RepoLabel, error) { 830 var conditions []string 831 var args []any 832 for _, filter := range filters { 833 conditions = append(conditions, filter.Condition()) 834 args = append(args, filter.Arg()...) 835 } 836 837 whereClause := "" 838 if conditions != nil { 839 whereClause = " where " + strings.Join(conditions, " and ") 840 } 841 842 query := fmt.Sprintf(`select id, repo_did, label_at from repo_labels %s`, whereClause) 843 844 rows, err := e.Query(query, args...) 845 if err != nil { 846 return nil, err 847 } 848 defer rows.Close() 849 850 var labels []models.RepoLabel 851 for rows.Next() { 852 var label models.RepoLabel 853 854 err := rows.Scan(&label.Id, &label.RepoDid, &label.LabelAt) 855 if err != nil { 856 return nil, err 857 } 858 859 labels = append(labels, label) 860 } 861 862 if err = rows.Err(); err != nil { 863 return nil, err 864 } 865 866 return labels, nil 867} 868 869func GetForkCount(e Execer, sourceDID string) (int, error) { 870 forks := 0 871 err := e.QueryRow( 872 `select count(source) from repos where source = ?`, sourceDID).Scan(&forks) 873 if err != nil { 874 return 0, err 875 } 876 return forks, nil 877}