Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602package db
import ( "database/sql" "errors" "fmt" "log" "slices" "strings" "time"
"github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/appview/models")
func GetRepos(e Execer, limit int, filters ...filter) ([]models.Repo, error) { repoMap := make(map[syntax.ATURI]*models.Repo)
var conditions []string var args []any for _, filter := range filters { conditions = append(conditions, filter.Condition()) args = append(args, filter.Arg()...) }
whereClause := "" if conditions != nil { whereClause = " where " + strings.Join(conditions, " and ") }
limitClause := "" if limit != 0 { limitClause = fmt.Sprintf(" limit %d", limit) }
repoQuery := fmt.Sprintf( `select id, did, name, knot, rkey, created, description, website, topics, source, spindle from repos r %s order by created desc %s`, whereClause, limitClause, ) rows, err := e.Query(repoQuery, args...)
if err != nil { return nil, fmt.Errorf("failed to execute repo query: %w ", err) }
for rows.Next() { var repo models.Repo var createdAt string var description, website, topicStr, source, spindle sql.NullString
err := rows.Scan( &repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &createdAt, &description, &website, &topicStr, &source, &spindle, ) if err != nil { return nil, fmt.Errorf("failed to execute repo query: %w ", err) }
if t, err := time.Parse(time.RFC3339, createdAt); err == nil { repo.Created = t } if description.Valid { repo.Description = description.String } if website.Valid { repo.Website = website.String } if topicStr.Valid { repo.Topics = strings.Fields(topicStr.String) } if source.Valid { repo.Source = source.String } if spindle.Valid { repo.Spindle = spindle.String }
repo.RepoStats = &models.RepoStats{} repoMap[repo.RepoAt()] = &repo }
if err = rows.Err(); err != nil { return nil, fmt.Errorf("failed to execute repo query: %w ", err) }
inClause := strings.TrimSuffix(strings.Repeat("?, ", len(repoMap)), ", ") args = make([]any, len(repoMap))
i := 0 for _, r := range repoMap { args[i] = r.RepoAt() i++ }
// Get labels for all repos labelsQuery := fmt.Sprintf( `select repo_at, label_at from repo_labels where repo_at in (%s)`, inClause, ) rows, err = e.Query(labelsQuery, args...) if err != nil { return nil, fmt.Errorf("failed to execute labels query: %w ", err) } for rows.Next() { var repoat, labelat string if err := rows.Scan(&repoat, &labelat); err != nil { log.Println("err", "err", err) continue } if r, ok := repoMap[syntax.ATURI(repoat)]; ok { r.Labels = append(r.Labels, labelat) } } if err = rows.Err(); err != nil { return nil, fmt.Errorf("failed to execute labels query: %w ", err) }
languageQuery := fmt.Sprintf( ` select repo_at, language from ( select repo_at, language, row_number() over ( partition by repo_at order by bytes desc ) as rn from repo_languages where repo_at in (%s) and is_default_ref = 1 ) where rn = 1 `, inClause, ) rows, err = e.Query(languageQuery, args...) if err != nil { return nil, fmt.Errorf("failed to execute lang query: %w ", err) } for rows.Next() { var repoat, lang string if err := rows.Scan(&repoat, &lang); err != nil { log.Println("err", "err", err) continue } if r, ok := repoMap[syntax.ATURI(repoat)]; ok { r.RepoStats.Language = lang } } if err = rows.Err(); err != nil { return nil, fmt.Errorf("failed to execute lang query: %w ", err) }
starCountQuery := fmt.Sprintf( `select subject_at, count(1) from stars where subject_at in (%s) group by subject_at`, inClause, ) rows, err = e.Query(starCountQuery, args...) if err != nil { return nil, fmt.Errorf("failed to execute star-count query: %w ", err) } for rows.Next() { var repoat string var count int if err := rows.Scan(&repoat, &count); err != nil { log.Println("err", "err", err) continue } if r, ok := repoMap[syntax.ATURI(repoat)]; ok { r.RepoStats.StarCount = count } } if err = rows.Err(); err != nil { return nil, fmt.Errorf("failed to execute star-count query: %w ", err) }
issueCountQuery := fmt.Sprintf( `select repo_at, count(case when open = 1 then 1 end) as open_count, count(case when open = 0 then 1 end) as closed_count from issues where repo_at in (%s) group by repo_at`, inClause, ) rows, err = e.Query(issueCountQuery, args...) if err != nil { return nil, fmt.Errorf("failed to execute issue-count query: %w ", err) } for rows.Next() { var repoat string var open, closed int if err := rows.Scan(&repoat, &open, &closed); err != nil { log.Println("err", "err", err) continue } if r, ok := repoMap[syntax.ATURI(repoat)]; ok { r.RepoStats.IssueCount.Open = open r.RepoStats.IssueCount.Closed = closed } } if err = rows.Err(); err != nil { return nil, fmt.Errorf("failed to execute issue-count query: %w ", err) }
pullCountQuery := fmt.Sprintf( `select repo_at, count(case when state = ? then 1 end) as open_count, count(case when state = ? then 1 end) as merged_count, count(case when state = ? then 1 end) as closed_count, count(case when state = ? then 1 end) as deleted_count from pulls where repo_at in (%s) group by repo_at`, inClause, ) args = append([]any{ models.PullOpen, models.PullMerged, models.PullClosed, models.PullDeleted, }, args...) rows, err = e.Query( pullCountQuery, args..., ) if err != nil { return nil, fmt.Errorf("failed to execute pulls-count query: %w ", err) } for rows.Next() { var repoat string var open, merged, closed, deleted int if err := rows.Scan(&repoat, &open, &merged, &closed, &deleted); err != nil { log.Println("err", "err", err) continue } if r, ok := repoMap[syntax.ATURI(repoat)]; ok { r.RepoStats.PullCount.Open = open r.RepoStats.PullCount.Merged = merged r.RepoStats.PullCount.Closed = closed r.RepoStats.PullCount.Deleted = deleted } } if err = rows.Err(); err != nil { return nil, fmt.Errorf("failed to execute pulls-count query: %w ", err) }
var repos []models.Repo for _, r := range repoMap { repos = append(repos, *r) }
slices.SortFunc(repos, func(a, b models.Repo) int { if a.Created.After(b.Created) { return -1 } return 1 })
return repos, nil}
// helper to get exactly one repofunc GetRepo(e Execer, filters ...filter) (*models.Repo, error) { repos, err := GetRepos(e, 0, filters...) if err != nil { return nil, err }
if repos == nil { return nil, sql.ErrNoRows }
if len(repos) != 1 { return nil, fmt.Errorf("too many rows returned") }
return &repos[0], nil}
func CountRepos(e Execer, filters ...filter) (int64, error) { var conditions []string var args []any for _, filter := range filters { conditions = append(conditions, filter.Condition()) args = append(args, filter.Arg()...) }
whereClause := "" if conditions != nil { whereClause = " where " + strings.Join(conditions, " and ") }
repoQuery := fmt.Sprintf(`select count(1) from repos %s`, whereClause) var count int64 err := e.QueryRow(repoQuery, args...).Scan(&count)
if !errors.Is(err, sql.ErrNoRows) && err != nil { return 0, err }
return count, nil}
func GetRepoByAtUri(e Execer, atUri string) (*models.Repo, error) { var repo models.Repo var nullableDescription sql.NullString var nullableWebsite sql.NullString var nullableTopicStr sql.NullString
row := e.QueryRow(`select id, did, name, knot, created, rkey, description, website, topics from repos where at_uri = ?`, atUri)
var createdAt string if err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.Rkey, &nullableDescription, &nullableWebsite, &nullableTopicStr); err != nil { return nil, err } createdAtTime, _ := time.Parse(time.RFC3339, createdAt) repo.Created = createdAtTime
if nullableDescription.Valid { repo.Description = nullableDescription.String } if nullableWebsite.Valid { repo.Website = nullableWebsite.String } if nullableTopicStr.Valid { repo.Topics = strings.Fields(nullableTopicStr.String) }
return &repo, nil}
func PutRepo(tx *sql.Tx, repo models.Repo) error { _, err := tx.Exec( `update repos set knot = ?, description = ?, website = ?, topics = ? where did = ? and rkey = ? `, repo.Knot, repo.Description, repo.Website, repo.TopicStr(), repo.Did, repo.Rkey, ) return err}
func AddRepo(tx *sql.Tx, repo *models.Repo) error { _, err := tx.Exec( `insert into repos (did, name, knot, rkey, at_uri, description, website, topics, source) values (?, ?, ?, ?, ?, ?, ?, ?, ?)`, repo.Did, repo.Name, repo.Knot, repo.Rkey, repo.RepoAt().String(), repo.Description, repo.Website, repo.TopicStr(), repo.Source, ) if err != nil { return fmt.Errorf("failed to insert repo: %w", err) }
for _, dl := range repo.Labels { if err := SubscribeLabel(tx, &models.RepoLabel{ RepoAt: repo.RepoAt(), LabelAt: syntax.ATURI(dl), }); err != nil { return fmt.Errorf("failed to subscribe to label: %w", err) } }
return nil}
func RemoveRepo(e Execer, did, name string) error { _, err := e.Exec(`delete from repos where did = ? and name = ?`, did, name) return err}
func GetRepoSource(e Execer, repoAt syntax.ATURI) (string, error) { var nullableSource sql.NullString err := e.QueryRow(`select source from repos where at_uri = ?`, repoAt).Scan(&nullableSource) if err != nil { return "", err } return nullableSource.String, nil}
func GetRepoSourceRepo(e Execer, repoAt syntax.ATURI) (*models.Repo, error) { source, err := GetRepoSource(e, repoAt) if source == "" || errors.Is(err, sql.ErrNoRows) { return nil, nil } if err != nil { return nil, err } return GetRepoByAtUri(e, source)}
func GetForksByDid(e Execer, did string) ([]models.Repo, error) { var repos []models.Repo
rows, err := e.Query( `select distinct r.id, r.did, r.name, r.knot, r.rkey, r.description, r.website, r.created, r.source from repos r left join collaborators c on r.at_uri = c.repo_at where (r.did = ? or c.subject_did = ?) and r.source is not null and r.source != '' order by r.created desc`, did, did, ) if err != nil { return nil, err } defer rows.Close()
for rows.Next() { var repo models.Repo var createdAt string var nullableDescription sql.NullString var nullableWebsite sql.NullString var nullableSource sql.NullString
err := rows.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &nullableWebsite, &createdAt, &nullableSource) if err != nil { return nil, err }
if nullableDescription.Valid { repo.Description = nullableDescription.String }
if nullableSource.Valid { repo.Source = nullableSource.String }
createdAtTime, err := time.Parse(time.RFC3339, createdAt) if err != nil { repo.Created = time.Now() } else { repo.Created = createdAtTime }
repos = append(repos, repo) }
if err := rows.Err(); err != nil { return nil, err }
return repos, nil}
func GetForkByDid(e Execer, did string, name string) (*models.Repo, error) { var repo models.Repo var createdAt string var nullableDescription sql.NullString var nullableWebsite sql.NullString var nullableTopicStr sql.NullString var nullableSource sql.NullString
row := e.QueryRow( `select id, did, name, knot, rkey, description, website, topics, created, source from repos where did = ? and name = ? and source is not null and source != ''`, did, name, )
err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &nullableWebsite, &nullableTopicStr, &createdAt, &nullableSource) if err != nil { return nil, err }
if nullableDescription.Valid { repo.Description = nullableDescription.String }
if nullableWebsite.Valid { repo.Website = nullableWebsite.String }
if nullableTopicStr.Valid { repo.Topics = strings.Fields(nullableTopicStr.String) }
if nullableSource.Valid { repo.Source = nullableSource.String }
createdAtTime, err := time.Parse(time.RFC3339, createdAt) if err != nil { repo.Created = time.Now() } else { repo.Created = createdAtTime }
return &repo, nil}
func UpdateDescription(e Execer, repoAt, newDescription string) error { _, err := e.Exec( `update repos set description = ? where at_uri = ?`, newDescription, repoAt) return err}
func UpdateSpindle(e Execer, repoAt string, spindle *string) error { _, err := e.Exec( `update repos set spindle = ? where at_uri = ?`, spindle, repoAt) return err}
func SubscribeLabel(e Execer, rl *models.RepoLabel) error { query := `insert or ignore into repo_labels (repo_at, label_at) values (?, ?)`
_, err := e.Exec(query, rl.RepoAt.String(), rl.LabelAt.String()) return err}
func UnsubscribeLabel(e Execer, filters ...filter) error { var conditions []string var args []any for _, filter := range filters { conditions = append(conditions, filter.Condition()) args = append(args, filter.Arg()...) }
whereClause := "" if conditions != nil { whereClause = " where " + strings.Join(conditions, " and ") }
query := fmt.Sprintf(`delete from repo_labels %s`, whereClause) _, err := e.Exec(query, args...) return err}
func GetRepoLabels(e Execer, filters ...filter) ([]models.RepoLabel, error) { var conditions []string var args []any for _, filter := range filters { conditions = append(conditions, filter.Condition()) args = append(args, filter.Arg()...) }
whereClause := "" if conditions != nil { whereClause = " where " + strings.Join(conditions, " and ") }
query := fmt.Sprintf(`select id, repo_at, label_at from repo_labels %s`, whereClause)
rows, err := e.Query(query, args...) if err != nil { return nil, err } defer rows.Close()
var labels []models.RepoLabel for rows.Next() { var label models.RepoLabel
err := rows.Scan(&label.Id, &label.RepoAt, &label.LabelAt) if err != nil { return nil, err }
labels = append(labels, label) }
if err = rows.Err(); err != nil { return nil, err }
return labels, nil}