diff --git a/appview/db/db.go b/appview/db/db.go --- a/appview/db/db.go +++ b/appview/db/db.go @@ -1295,6 +1295,45 @@ `) return err }) + orm.RunMigration(conn, logger, "add-repo-did-column", func(tx *sql.Tx) error { + _, err := tx.Exec(` + alter table repos add column repo_did text; + create unique index if not exists idx_repos_repo_did on repos(repo_did); + `) + return err + }) + + orm.RunMigration(conn, logger, "add-pds-rewrite-status", func(tx *sql.Tx) error { + _, err := tx.Exec(` + create table if not exists pds_rewrite_status ( + id integer primary key autoincrement, + user_did text not null, + repo_did text not null, + record_nsid text not null, + record_rkey text not null, + old_repo_at text not null, + status text not null default 'pending', + updated_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), + unique(user_did, record_nsid, record_rkey) + ); + create index if not exists idx_pds_rewrite_user on pds_rewrite_status(user_did, status); + `) + return err + }) + + orm.RunMigration(conn, logger, "add-pipelines-repo-did", func(tx *sql.Tx) error { + _, err := tx.Exec(` + alter table pipelines add column repo_did text; + create index if not exists idx_pipelines_repo_did on pipelines(repo_did); + `) + return err + }) + + orm.RunMigration(conn, logger, "migrate-knots-to-repo-dids", func(tx *sql.Tx) error { + _, err := tx.Exec(`update registrations set needs_upgrade = 1`) + return err + }) + return &DB{ db, logger, diff --git a/appview/db/pipeline.go b/appview/db/pipeline.go --- a/appview/db/pipeline.go +++ b/appview/db/pipeline.go @@ -2,6 +2,7 @@ package db import ( "context" + "database/sql" "fmt" "slices" "strings" @@ -27,7 +28,7 @@ if conditions != nil { whereClause = " where " + strings.Join(conditions, " and ") } - query := fmt.Sprintf(`select id, rkey, knot, repo_owner, repo_name, sha, created from pipelines %s`, whereClause) + query := fmt.Sprintf(`select id, rkey, knot, repo_owner, repo_name, sha, created, repo_did from pipelines %s`, whereClause) rows, err := e.Query(query, args...) @@ -39,6 +40,7 @@ for rows.Next() { var pipeline models.Pipeline var createdAt string + var repoDid sql.NullString err = rows.Scan( &pipeline.Id, &pipeline.Rkey, @@ -47,6 +49,7 @@ &pipeline.RepoOwner, &pipeline.RepoName, &pipeline.Sha, &createdAt, + &repoDid, ) if err != nil { return nil, err @@ -55,6 +58,9 @@ if t, err := time.Parse(time.RFC3339, createdAt); err == nil { pipeline.Created = t } + if repoDid.Valid { + pipeline.RepoDid = repoDid.String + } pipelines = append(pipelines, pipeline) } @@ -67,6 +73,11 @@ return pipelines, nil } func AddPipeline(e Execer, pipeline models.Pipeline) error { + var repoDid *string + if pipeline.RepoDid != "" { + repoDid = &pipeline.RepoDid + } + args := []any{ pipeline.Rkey, pipeline.Knot, @@ -74,6 +85,7 @@ pipeline.RepoOwner, pipeline.RepoName, pipeline.TriggerId, pipeline.Sha, + repoDid, } placeholders := make([]string, len(args)) @@ -88,7 +100,8 @@ knot, repo_owner, repo_name, trigger_id, - sha + sha, + repo_did ) values (%s) `, strings.Join(placeholders, ",")) @@ -196,6 +209,7 @@ p.repo_owner, p.repo_name, p.sha, p.created, + p.repo_did, t.id, t.kind, t.push_ref, @@ -225,6 +239,7 @@ for rows.Next() { var p models.Pipeline var t models.Trigger var created string + var repoDid sql.NullString err := rows.Scan( &p.Id, @@ -234,6 +249,7 @@ &p.RepoOwner, &p.RepoName, &p.Sha, &created, + &repoDid, &p.TriggerId, &t.Kind, &t.PushRef, @@ -251,6 +267,9 @@ p.Created, err = time.Parse(time.RFC3339, created) if err != nil { return nil, fmt.Errorf("invalid pipeline created timestamp %q: %w", created, err) + } + if repoDid.Valid { + p.RepoDid = repoDid.String } t.Id = p.TriggerId diff --git a/appview/db/pulls.go b/appview/db/pulls.go --- a/appview/db/pulls.go +++ b/appview/db/pulls.go @@ -614,7 +614,7 @@ } func SetPullState(e Execer, repoAt syntax.ATURI, pullId int, pullState models.PullState) error { _, err := e.Exec( - `update pulls set state = ? where repo_at = ? and pull_id = ? and (state <> ? or state <> ?)`, + `update pulls set state = ? where repo_at = ? and pull_id = ? and (state <> ? and state <> ?)`, pullState, repoAt, pullId, diff --git a/appview/db/repos.go b/appview/db/repos.go --- a/appview/db/repos.go +++ b/appview/db/repos.go @@ -50,7 +50,8 @@ description, website, topics, source, - spindle + spindle, + repo_did from repos %s order by created desc @@ -67,7 +68,7 @@ repoMap := make(map[syntax.ATURI]*models.Repo) for rows.Next() { var repo models.Repo var createdAt string - var description, website, topicStr, source, spindle sql.NullString + var description, website, topicStr, source, spindle, repoDid sql.NullString err := rows.Scan( &repo.Id, @@ -81,6 +82,7 @@ &website, &topicStr, &source, &spindle, + &repoDid, ) if err != nil { return nil, err @@ -106,6 +108,9 @@ repo.Source = source.String } if spindle.Valid { repo.Spindle = spindle.String + } + if repoDid.Valid { + repo.RepoDid = repoDid.String } repo.RepoStats = &models.RepoStats{} @@ -357,11 +362,14 @@ var repo models.Repo var nullableDescription sql.NullString var nullableWebsite sql.NullString var nullableTopicStr sql.NullString + var nullableRepoDid sql.NullString + var nullableSource sql.NullString + var nullableSpindle sql.NullString - row := e.QueryRow(`select id, did, name, knot, created, rkey, description, website, topics from repos where at_uri = ?`, atUri) + row := e.QueryRow(`select id, did, name, knot, created, rkey, description, website, topics, source, spindle, repo_did 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 { + if err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.Rkey, &nullableDescription, &nullableWebsite, &nullableTopicStr, &nullableSource, &nullableSpindle, &nullableRepoDid); err != nil { return nil, err } createdAtTime, _ := time.Parse(time.RFC3339, createdAt) @@ -375,28 +383,45 @@ repo.Website = nullableWebsite.String } if nullableTopicStr.Valid { repo.Topics = strings.Fields(nullableTopicStr.String) + } + if nullableSource.Valid { + repo.Source = nullableSource.String + } + if nullableSpindle.Valid { + repo.Spindle = nullableSpindle.String + } + if nullableRepoDid.Valid { + repo.RepoDid = nullableRepoDid.String } return &repo, nil } func PutRepo(tx *sql.Tx, repo models.Repo) error { + var repoDid *string + if repo.RepoDid != "" { + repoDid = &repo.RepoDid + } _, err := tx.Exec( `update repos - set knot = ?, description = ?, website = ?, topics = ? + set knot = ?, description = ?, website = ?, topics = ?, repo_did = coalesce(?, repo_did) where did = ? and rkey = ? `, - repo.Knot, repo.Description, repo.Website, repo.TopicStr(), repo.Did, repo.Rkey, + repo.Knot, repo.Description, repo.Website, repo.TopicStr(), repoDid, repo.Did, repo.Rkey, ) return err } func AddRepo(tx *sql.Tx, repo *models.Repo) error { + var repoDid *string + if repo.RepoDid != "" { + repoDid = &repo.RepoDid + } _, 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, + (did, name, knot, rkey, at_uri, description, website, topics, source, repo_did) + values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + repo.Did, repo.Name, repo.Knot, repo.Rkey, repo.RepoAt().String(), repo.Description, repo.Website, repo.TopicStr(), repo.Source, repoDid, ) if err != nil { return fmt.Errorf("failed to insert repo: %w", err) @@ -436,6 +461,9 @@ } if err != nil { return nil, err } + if strings.HasPrefix(source, "did:") { + return GetRepoByDid(e, source) + } return GetRepoByAtUri(e, source) } @@ -443,7 +471,7 @@ 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 + `select distinct r.id, r.did, r.name, r.knot, r.rkey, r.description, r.website, r.created, r.source, r.repo_did from repos r left join collaborators c on r.at_uri = c.repo_at where (r.did = ? or c.subject_did = ?) @@ -463,18 +491,25 @@ var createdAt string var nullableDescription sql.NullString var nullableWebsite sql.NullString var nullableSource sql.NullString + var nullableRepoDid sql.NullString - err := rows.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &nullableWebsite, &createdAt, &nullableSource) + err := rows.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &nullableWebsite, &createdAt, &nullableSource, &nullableRepoDid) if err != nil { return nil, err } if nullableDescription.Valid { repo.Description = nullableDescription.String + } + if nullableWebsite.Valid { + repo.Website = nullableWebsite.String } if nullableSource.Valid { repo.Source = nullableSource.String + } + if nullableRepoDid.Valid { + repo.RepoDid = nullableRepoDid.String } createdAtTime, err := time.Parse(time.RFC3339, createdAt) @@ -501,15 +536,16 @@ var nullableDescription sql.NullString var nullableWebsite sql.NullString var nullableTopicStr sql.NullString var nullableSource sql.NullString + var nullableRepoDid sql.NullString row := e.QueryRow( - `select id, did, name, knot, rkey, description, website, topics, created, source + `select id, did, name, knot, rkey, description, website, topics, created, source, repo_did 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) + err := row.Scan(&repo.Id, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &nullableWebsite, &nullableTopicStr, &createdAt, &nullableSource, &nullableRepoDid) if err != nil { return nil, err } @@ -529,6 +565,9 @@ if nullableSource.Valid { repo.Source = nullableSource.String } + if nullableRepoDid.Valid { + repo.RepoDid = nullableRepoDid.String + } createdAtTime, err := time.Parse(time.RFC3339, createdAt) if err != nil { @@ -538,6 +577,20 @@ repo.Created = createdAtTime } return &repo, nil +} + +func GetRepoByDid(e Execer, repoDid string) (*models.Repo, error) { + return GetRepo(e, orm.FilterEq("repo_did", repoDid)) +} + +func EnqueuePdsRewrite(e Execer, userDid, repoDid, recordNsid, recordRkey, oldRepoAt string) error { + _, err := e.Exec( + `INSERT OR IGNORE INTO pds_rewrite_status + (user_did, repo_did, record_nsid, record_rkey, old_repo_at, status) + VALUES (?, ?, ?, ?, ?, 'pending')`, + userDid, repoDid, recordNsid, recordRkey, oldRepoAt, + ) + return err } func UpdateDescription(e Execer, repoAt, newDescription string) error { diff --git a/appview/models/issue.go b/appview/models/issue.go --- a/appview/models/issue.go +++ b/appview/models/issue.go @@ -44,8 +44,9 @@ references := make([]string, len(i.References)) for i, uri := range i.References { references[i] = string(uri) } + repoAtStr := i.RepoAt.String() return tangled.RepoIssue{ - Repo: i.RepoAt.String(), + Repo: &repoAtStr, Title: i.Title, Body: &i.Body, Mentions: mentions, @@ -161,8 +162,13 @@ if record.Body != nil { body = *record.Body } + var repoAt syntax.ATURI + if record.Repo != nil { + repoAt = syntax.ATURI(*record.Repo) + } + return Issue{ - RepoAt: syntax.ATURI(record.Repo), + RepoAt: repoAt, Did: did, Rkey: rkey, Created: created, diff --git a/appview/models/pipeline.go b/appview/models/pipeline.go --- a/appview/models/pipeline.go +++ b/appview/models/pipeline.go @@ -19,6 +19,7 @@ Rkey string Knot string RepoOwner syntax.DID RepoName string + RepoDid string TriggerId int Sha string Created time.Time diff --git a/appview/models/pull.go b/appview/models/pull.go --- a/appview/models/pull.go +++ b/appview/models/pull.go @@ -104,6 +104,7 @@ for i, uri := range p.References { references[i] = string(uri) } + targetRepoStr := p.RepoAt.String() record := tangled.RepoPull{ Title: p.Title, Body: &p.Body, @@ -111,7 +112,7 @@ Mentions: mentions, References: references, CreatedAt: p.Created.Format(time.RFC3339), Target: &tangled.RepoPull_Target{ - Repo: p.RepoAt.String(), + Repo: &targetRepoStr, Branch: p.TargetBranch, }, Source: source, diff --git a/appview/models/repo.go b/appview/models/repo.go --- a/appview/models/repo.go +++ b/appview/models/repo.go @@ -22,6 +22,7 @@ Website string Topics []string Spindle string Labels []string + RepoDid string // optionally, populate this when querying for reverse mappings RepoStats *RepoStats @@ -47,6 +48,11 @@ } if r.Website != "" { website = &r.Website + } + + var repoDid *string + if r.RepoDid != "" { + repoDid = &r.RepoDid } return tangled.Repo{ @@ -59,6 +65,7 @@ CreatedAt: r.Created.Format(time.RFC3339), Source: source, Spindle: spindle, Labels: r.Labels, + RepoDid: repoDid, } } @@ -66,7 +73,10 @@ func (r Repo) RepoAt() syntax.ATURI { return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", r.Did, tangled.RepoNSID, r.Rkey)) } -func (r Repo) DidSlashRepo() string { +func (r Repo) RepoIdentifier() string { + if r.RepoDid != "" { + return r.RepoDid + } p, _ := securejoin.SecureJoin(r.Did, r.Name) return p } diff --git a/appview/pages/funcmap.go b/appview/pages/funcmap.go --- a/appview/pages/funcmap.go +++ b/appview/pages/funcmap.go @@ -84,13 +84,13 @@ }, "ownerSlashRepo": func(repo *models.Repo) string { ownerId, err := p.resolver.ResolveIdent(context.Background(), repo.Did) if err != nil { - return repo.DidSlashRepo() + return repo.RepoIdentifier() } handle := ownerId.Handle if handle != "" && !handle.IsInvalidHandle() { return string(handle) + "/" + repo.Name } - return repo.DidSlashRepo() + return repo.RepoIdentifier() }, "truncateAt30": func(s string) string { if len(s) <= 30 {