This repository has no description
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 GetRepoByDid(e Execer, repoDid string) (*models.Repo, error) {
640 return GetRepo(e, orm.FilterEq("repo_did", repoDid))
641}
642
643func GetForkByRepoDid(e Execer, repoDid string) (*models.Repo, error) {
644 return GetRepo(e, orm.FilterEq("repo_did", repoDid), orm.FilterNotEq("source", ""))
645}
646
647// TODO: just queue every legacy records regardless of target repo has a DID or not.
648// doable after we have `repo_did` column in db for each tables.
649func EnqueuePdsRewritesForRepo(tx *sql.Tx, repoDid, repoAtUri string) error {
650 type record struct {
651 userDidCol string
652 table string
653 nsid syntax.NSID
654 fkCol string
655 fkVal string
656 }
657 sources := []record{
658 {"did", "repos", tangled.RepoNSID, "at_uri", repoAtUri},
659 {"did", "issues", tangled.RepoIssueNSID, "repo_did", repoDid},
660 {"owner_did", "pulls", tangled.RepoPullNSID, "repo_did", repoDid},
661 {"did", "collaborators", tangled.RepoCollaboratorNSID, "repo_did", repoDid},
662 {"did", "artifacts", tangled.RepoArchiveNSID, "repo_did", repoDid},
663 {"did", "stars", tangled.FeedStarNSID, "subject", repoDid},
664 }
665
666 for _, src := range sources {
667 rows, err := tx.Query(
668 fmt.Sprintf(`SELECT %s, rkey FROM %s WHERE %s = ?`, src.userDidCol, src.table, src.fkCol),
669 src.fkVal,
670 )
671 if err != nil {
672 return fmt.Errorf("query %s for pds rewrites: %w", src.table, err)
673 }
674
675 var pairs []struct{ did, rkey string }
676 for rows.Next() {
677 var d, r string
678 if scanErr := rows.Scan(&d, &r); scanErr != nil {
679 rows.Close()
680 return fmt.Errorf("scan %s for pds rewrites: %w", src.table, scanErr)
681 }
682 pairs = append(pairs, struct{ did, rkey string }{d, r})
683 }
684 rows.Close()
685 if rowsErr := rows.Err(); rowsErr != nil {
686 return fmt.Errorf("iterate %s for pds rewrites: %w", src.table, rowsErr)
687 }
688
689 for _, p := range pairs {
690 if err := EnqueuePdsRecordMigration(context.Background(), tx, "add-repo-did", syntax.DID(p.did), src.nsid, syntax.RecordKey(p.rkey)); err != nil {
691 return fmt.Errorf("enqueue pds rewrite for %s/%s: %w", src.table, p.rkey, err)
692 }
693 }
694 }
695
696 profileRows, err := tx.Query(
697 `SELECT DISTINCT did FROM profile_pinned_repositories WHERE pin = ?`,
698 repoAtUri,
699 )
700 if err != nil {
701 return fmt.Errorf("query profile_pinned_repositories for pds rewrites: %w", err)
702 }
703 var profileDids []string
704 for profileRows.Next() {
705 var d string
706 if scanErr := profileRows.Scan(&d); scanErr != nil {
707 profileRows.Close()
708 return fmt.Errorf("scan profile_pinned_repositories for pds rewrites: %w", scanErr)
709 }
710 profileDids = append(profileDids, d)
711 }
712 profileRows.Close()
713 if profileRowsErr := profileRows.Err(); profileRowsErr != nil {
714 return fmt.Errorf("iterate profile_pinned_repositories for pds rewrites: %w", profileRowsErr)
715 }
716
717 for _, d := range profileDids {
718 if err := EnqueuePdsRecordMigration(context.Background(), tx, "add-repo-did", syntax.DID(d), tangled.ActorProfileNSID, "self"); err != nil {
719 return fmt.Errorf("enqueue pds rewrite for profile/%s: %w", d, err)
720 }
721 }
722
723 return nil
724}
725
726func CascadeRepoDid(tx *sql.Tx, repoAtUri, repoDid string) error {
727 _, err := tx.Exec(
728 `UPDATE repos SET repo_did = ? WHERE at_uri = ?`,
729 repoDid, repoAtUri,
730 )
731 if err != nil {
732 return fmt.Errorf("cascade repo_did to repos: %w", err)
733 }
734
735 _, err = tx.Exec(
736 `UPDATE repos SET source = ? WHERE source = ?`,
737 repoDid, repoAtUri,
738 )
739 if err != nil {
740 return fmt.Errorf("cascade repo_did to repos.source: %w", err)
741 }
742
743 return nil
744}
745
746func UpdateDescription(e Execer, repoDid, newDescription string) error {
747 _, err := e.Exec(
748 `update repos set description = ? where repo_did = ?`, newDescription, repoDid)
749 return err
750}
751
752func UpdateSpindle(e Execer, repoDid string, spindle *string) error {
753 _, err := e.Exec(
754 `update repos set spindle = ? where repo_did = ?`, spindle, repoDid)
755 return err
756}
757
758func SubscribeLabel(e Execer, rl *models.RepoLabel) error {
759 query := `insert or ignore into repo_labels (repo_did, label_at) values (?, ?)`
760
761 _, err := e.Exec(query, string(rl.RepoDid), rl.LabelAt.String())
762 return err
763}
764
765func UnsubscribeLabel(e Execer, filters ...orm.Filter) error {
766 var conditions []string
767 var args []any
768 for _, filter := range filters {
769 conditions = append(conditions, filter.Condition())
770 args = append(args, filter.Arg()...)
771 }
772
773 whereClause := ""
774 if conditions != nil {
775 whereClause = " where " + strings.Join(conditions, " and ")
776 }
777
778 query := fmt.Sprintf(`delete from repo_labels %s`, whereClause)
779 _, err := e.Exec(query, args...)
780 return err
781}
782
783func GetRepoLabels(e Execer, filters ...orm.Filter) ([]models.RepoLabel, error) {
784 var conditions []string
785 var args []any
786 for _, filter := range filters {
787 conditions = append(conditions, filter.Condition())
788 args = append(args, filter.Arg()...)
789 }
790
791 whereClause := ""
792 if conditions != nil {
793 whereClause = " where " + strings.Join(conditions, " and ")
794 }
795
796 query := fmt.Sprintf(`select id, repo_did, label_at from repo_labels %s`, whereClause)
797
798 rows, err := e.Query(query, args...)
799 if err != nil {
800 return nil, err
801 }
802 defer rows.Close()
803
804 var labels []models.RepoLabel
805 for rows.Next() {
806 var label models.RepoLabel
807
808 err := rows.Scan(&label.Id, &label.RepoDid, &label.LabelAt)
809 if err != nil {
810 return nil, err
811 }
812
813 labels = append(labels, label)
814 }
815
816 if err = rows.Err(); err != nil {
817 return nil, err
818 }
819
820 return labels, nil
821}
822
823func GetForkCount(e Execer, sourceDID string) (int, error) {
824 forks := 0
825 err := e.QueryRow(
826 `select count(source) from repos where source = ?`, sourceDID).Scan(&forks)
827 if err != nil {
828 return 0, err
829 }
830 return forks, nil
831}