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