diff --git a/api/tangled/actorprofile.go b/api/tangled/actorprofile.go --- a/api/tangled/actorprofile.go +++ b/api/tangled/actorprofile.go @@ -27,9 +27,8 @@ Description *string `json:"description,omitempty" cborgen:"description,omitempty"` Links []string `json:"links,omitempty" cborgen:"links,omitempty"` // location: Free-form location text. Location *string `json:"location,omitempty" cborgen:"location,omitempty"` - // pinnedRepositories: Any ATURI, it is up to appviews to validate these fields. - PinnedRepositories []string `json:"pinnedRepositories,omitempty" cborgen:"pinnedRepositories,omitempty"` - PinnedRepositoryDids []string `json:"pinnedRepositoryDids,omitempty" cborgen:"pinnedRepositoryDids,omitempty"` + // pinnedRepositories: Pinned repositories. Values are repo DIDs for repos that have them, or AT-URIs for legacy repos. + PinnedRepositories []string `json:"pinnedRepositories,omitempty" cborgen:"pinnedRepositories,omitempty"` // preferredHandle: A handle the user prefers to be displayed as. PreferredHandle *string `json:"preferredHandle,omitempty" cborgen:"preferredHandle,omitempty"` // pronouns: Preferred gender pronouns. diff --git a/api/tangled/cbor_gen.go b/api/tangled/cbor_gen.go --- a/api/tangled/cbor_gen.go +++ b/api/tangled/cbor_gen.go @@ -26,7 +26,7 @@ return err } cw := cbg.NewCborWriter(w) - fieldCount := 11 + fieldCount := 10 if t.Avatar == nil { fieldCount-- @@ -45,10 +45,6 @@ fieldCount-- } if t.PinnedRepositories == nil { - fieldCount-- - } - - if t.PinnedRepositoryDids == nil { fieldCount-- } @@ -344,42 +340,6 @@ if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.PinnedRepositories))); err != nil { return err } for _, v := range t.PinnedRepositories { - if len(v) > 1000000 { - return xerrors.Errorf("Value in field v was too long") - } - - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil { - return err - } - if _, err := cw.WriteString(string(v)); err != nil { - return err - } - - } - } - - // t.PinnedRepositoryDids ([]string) (slice) - if t.PinnedRepositoryDids != nil { - - if len("pinnedRepositoryDids") > 1000000 { - return xerrors.Errorf("Value in field \"pinnedRepositoryDids\" was too long") - } - - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("pinnedRepositoryDids"))); err != nil { - return err - } - if _, err := cw.WriteString(string("pinnedRepositoryDids")); err != nil { - return err - } - - if len(t.PinnedRepositoryDids) > 8192 { - return xerrors.Errorf("Slice value in field t.PinnedRepositoryDids was too long") - } - - if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.PinnedRepositoryDids))); err != nil { - return err - } - for _, v := range t.PinnedRepositoryDids { if len(v) > 1000000 { return xerrors.Errorf("Value in field v was too long") } @@ -421,7 +381,7 @@ } n := extra - nameBuf := make([]byte, 20) + nameBuf := make([]byte, 18) for i := uint64(0); i < n; i++ { nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) if err != nil { @@ -686,46 +646,6 @@ return err } t.PinnedRepositories[i] = string(sval) - } - - } - } - // t.PinnedRepositoryDids ([]string) (slice) - case "pinnedRepositoryDids": - - maj, extra, err = cr.ReadHeader() - if err != nil { - return err - } - - if extra > 8192 { - return fmt.Errorf("t.PinnedRepositoryDids: array too large (%d)", extra) - } - - if maj != cbg.MajArray { - return fmt.Errorf("expected cbor array") - } - - if extra > 0 { - t.PinnedRepositoryDids = make([]string, extra) - } - - for i := 0; i < int(extra); i++ { - { - var maj byte - var extra uint64 - var err error - _ = maj - _ = extra - _ = err - - { - sval, err := cbg.ReadStringWithMax(cr, 1000000) - if err != nil { - return err - } - - t.PinnedRepositoryDids[i] = string(sval) } } diff --git a/appview/db/db.go b/appview/db/db.go --- a/appview/db/db.go +++ b/appview/db/db.go @@ -1334,6 +1334,40 @@ _, err := tx.Exec(`update registrations set needs_upgrade = 1`) return err }) + conn.ExecContext(ctx, "pragma foreign_keys = off;") + orm.RunMigration(conn, logger, "drop-pinned-repos-at-uri-fk", func(tx *sql.Tx) error { + _, err := tx.Exec(` + create table if not exists profile_pinned_repositories_new ( + id integer primary key autoincrement, + did text not null, + pin text not null, + + unique(did, pin), + foreign key (did) references profile(did) on delete cascade + ); + + insert into profile_pinned_repositories_new (id, did, pin) + select id, did, at_uri from profile_pinned_repositories; + + drop table profile_pinned_repositories; + + alter table profile_pinned_repositories_new rename to profile_pinned_repositories; + `) + return err + }) + conn.ExecContext(ctx, "pragma foreign_keys = on;") + + orm.RunMigration(conn, logger, "reset-profile-pin-rewrites", func(tx *sql.Tx) error { + _, err := tx.Exec(` + update pds_rewrite_status + set status = 'pending', + updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') + where record_nsid = 'sh.tangled.actor.profile' + and status = 'done' + `) + return err + }) + return &DB{ db, logger, diff --git a/appview/db/profile.go b/appview/db/profile.go --- a/appview/db/profile.go +++ b/appview/db/profile.go @@ -220,7 +220,7 @@ continue } _, err := tx.Exec( - `insert into profile_pinned_repositories (did, at_uri) values (?, ?)`, + `insert into profile_pinned_repositories (did, pin) values (?, ?)`, profile.Did, pin, ) @@ -328,7 +328,7 @@ profileMap[did].Links[idx] = link idxs[did] = idx + 1 } - pinsQuery := fmt.Sprintf("select at_uri, did from profile_pinned_repositories where did in (%s)", inClause) + pinsQuery := fmt.Sprintf("select pin, did from profile_pinned_repositories where did in (%s)", inClause) rows, err = e.Query(pinsQuery, args...) if err != nil { return nil, err @@ -340,14 +340,14 @@ for did := range profileMap { idxs[did] = 0 } for rows.Next() { - var link syntax.ATURI + var pin string var did string - if err = rows.Scan(&link, &did); err != nil { + if err = rows.Scan(&pin, &did); err != nil { return nil, err } idx := idxs[did] - profileMap[did].PinnedRepos[idx] = link + profileMap[did].PinnedRepos[idx] = pin idxs[did] = idx + 1 } @@ -435,7 +435,7 @@ profile.Stats[i].Value = value i++ } - rows, err = e.Query(`select at_uri from profile_pinned_repositories where did = ?`, did) + rows, err = e.Query(`select pin from profile_pinned_repositories where did = ?`, did) if err != nil { return nil, err } @@ -524,7 +524,6 @@ if err != nil { return err } - // ensure all pinned repos are either own repos or collaborating repos repos, err := GetRepos(e, orm.FilterEq("did", profile.Did)) if err != nil { log.Printf("getting repos for %s: %s", profile.Did, err) @@ -535,20 +534,21 @@ if err != nil { log.Printf("getting collaborating repos for %s: %s", profile.Did, err) } - var validRepos []syntax.ATURI - for _, r := range repos { - validRepos = append(validRepos, r.RepoAt()) - } - for _, r := range collaboratingRepos { - validRepos = append(validRepos, r.RepoAt()) - } + // ensure all pinned repos are either own repos or collaborating repos + allRepos := append(repos, collaboratingRepos...) for _, pinned := range profile.PinnedRepos { if pinned == "" { continue } - if !slices.Contains(validRepos, pinned) { - return fmt.Errorf("Invalid pinned repo: `%s, does not belong to own or collaborating repos", pinned) + matched := slices.ContainsFunc(allRepos, func(r models.Repo) bool { + if strings.HasPrefix(pinned, "did:") { + return pinned == r.RepoDid + } + return pinned == string(r.RepoAt()) + }) + if !matched { + return fmt.Errorf("Invalid pinned repo: `%s`, does not belong to own or collaborating repos", pinned) } } diff --git a/appview/db/repos.go b/appview/db/repos.go --- a/appview/db/repos.go +++ b/appview/db/repos.go @@ -630,7 +630,7 @@ } } profileRows, err := tx.Query( - `SELECT DISTINCT did FROM profile_pinned_repositories WHERE at_uri = ?`, + `SELECT DISTINCT did FROM profile_pinned_repositories WHERE pin = ?`, repoAtUri, ) if err != nil { diff --git a/appview/ingester.go b/appview/ingester.go --- a/appview/ingester.go +++ b/appview/ingester.go @@ -364,10 +364,10 @@ stats[i].Kind = models.ParseVanityStatKind(s) } } - var pinned [6]syntax.ATURI + var pinned [6]string for i, r := range record.PinnedRepositories { if i < 6 { - pinned[i] = syntax.ATURI(r) + pinned[i] = r } } diff --git a/appview/models/profile.go b/appview/models/profile.go --- a/appview/models/profile.go +++ b/appview/models/profile.go @@ -2,6 +2,7 @@ package models import ( "fmt" + "strings" "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/api/tangled" @@ -19,7 +20,7 @@ IncludeBluesky bool Location string Links [5]string Stats [2]VanityStat - PinnedRepos [6]syntax.ATURI + PinnedRepos [6]string Pronouns string PreferredHandle syntax.Handle } @@ -49,6 +50,22 @@ return false } } return true +} + +func (p Profile) MatchesPinnedRepo(repo Repo) bool { + for _, pin := range p.PinnedRepos { + if pin == "" { + continue + } + if strings.HasPrefix(pin, "did:") { + if pin == repo.RepoDid { + return true + } + } else if pin == string(repo.RepoAt()) { + return true + } + } + return false } type VanityStatKind string diff --git a/appview/models/repo.go b/appview/models/repo.go --- a/appview/models/repo.go +++ b/appview/models/repo.go @@ -81,6 +81,13 @@ p, _ := securejoin.SecureJoin(r.Did, r.Name) return p } +func (r Repo) PinIdentifier() string { + if r.RepoDid != "" { + return r.RepoDid + } + return string(r.RepoAt()) +} + func (r Repo) TopicStr() string { return strings.Join(r.Topics, " ") } diff --git a/appview/oauth/handler.go b/appview/oauth/handler.go --- a/appview/oauth/handler.go +++ b/appview/oauth/handler.go @@ -376,18 +376,20 @@ rec, ok := val.(*tangled.ActorProfile) if !ok { return fmt.Errorf("unexpected type for profile record") } - var dids []string - var remaining []string - for _, pinUri := range rec.PinnedRepositories { - repo, repoErr := db.GetRepoByAtUri(o.Db, pinUri) + rewritten := make([]string, 0, len(rec.PinnedRepositories)) + for _, pin := range rec.PinnedRepositories { + if strings.HasPrefix(pin, "did:") { + rewritten = append(rewritten, pin) + continue + } + repo, repoErr := db.GetRepoByAtUri(o.Db, pin) if repoErr != nil || repo.RepoDid == "" { - remaining = append(remaining, pinUri) + rewritten = append(rewritten, pin) continue } - dids = append(dids, repo.RepoDid) + rewritten = append(rewritten, repo.RepoDid) } - rec.PinnedRepositoryDids = append(rec.PinnedRepositoryDids, dids...) - rec.PinnedRepositories = remaining + rec.PinnedRepositories = rewritten default: return fmt.Errorf("unsupported NSID for PDS rewrite: %s", rw.RecordNsid) diff --git a/appview/pages/templates/user/fragments/editPins.html b/appview/pages/templates/user/fragments/editPins.html --- a/appview/pages/templates/user/fragments/editPins.html +++ b/appview/pages/templates/user/fragments/editPins.html @@ -24,7 +24,7 @@
{{ range $idx, $r := .AllRepos }}
- +