diff --git a/api/tangled/actorprofile.go b/api/tangled/actorprofile.go index 52b68e80..c6c46939 100644 --- a/api/tangled/actorprofile.go +++ b/api/tangled/actorprofile.go @@ -27,9 +27,8 @@ type ActorProfile struct { 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 index 4c5e927d..03454b64 100644 --- a/api/tangled/cbor_gen.go +++ b/api/tangled/cbor_gen.go @@ -26,7 +26,7 @@ func (t *ActorProfile) MarshalCBOR(w io.Writer) error { } cw := cbg.NewCborWriter(w) - fieldCount := 11 + fieldCount := 10 if t.Avatar == nil { fieldCount-- @@ -48,10 +48,6 @@ func (t *ActorProfile) MarshalCBOR(w io.Writer) error { fieldCount-- } - if t.PinnedRepositoryDids == nil { - fieldCount-- - } - if t.PreferredHandle == nil { fieldCount-- } @@ -357,42 +353,6 @@ func (t *ActorProfile) MarshalCBOR(w io.Writer) error { } } - - // 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") - } - - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil { - return err - } - if _, err := cw.WriteString(string(v)); err != nil { - return err - } - - } - } return nil } @@ -421,7 +381,7 @@ func (t *ActorProfile) UnmarshalCBOR(r io.Reader) (err error) { 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 { @@ -690,46 +650,6 @@ func (t *ActorProfile) UnmarshalCBOR(r io.Reader) (err error) { } } - // 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) - } - - } - } default: // Field doesn't exist on this type, so ignore it diff --git a/appview/db/db.go b/appview/db/db.go index 13462979..2088c140 100644 --- a/appview/db/db.go +++ b/appview/db/db.go @@ -1334,6 +1334,40 @@ func Make(ctx context.Context, dbPath string) (*DB, error) { 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 index 99d42ced..8d9ffd25 100644 --- a/appview/db/profile.go +++ b/appview/db/profile.go @@ -220,7 +220,7 @@ func UpsertProfile(tx *sql.Tx, profile *models.Profile) error { } _, 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 @@ func GetProfiles(e Execer, filters ...orm.Filter) (map[string]*models.Profile, e 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 @@ func GetProfiles(e Execer, filters ...orm.Filter) (map[string]*models.Profile, e 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 @@ func GetProfile(e Execer, did string) (*models.Profile, error) { 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 @@ func ValidateProfile(e Execer, profile *models.Profile) error { 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 @@ func ValidateProfile(e Execer, profile *models.Profile) error { 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 index 276300a7..b9a3c0cc 100644 --- a/appview/db/repos.go +++ b/appview/db/repos.go @@ -630,7 +630,7 @@ func EnqueuePdsRewritesForRepo(tx *sql.Tx, repoDid, repoAtUri string) error { } 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 index 5252c344..300c032b 100644 --- a/appview/ingester.go +++ b/appview/ingester.go @@ -364,10 +364,10 @@ func (i *Ingester) ingestProfile(ctx context.Context, e *jmodels.Event) error { } } - 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 index aac5858a..90b8bd94 100644 --- 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 @@ type Profile struct { Location string Links [5]string Stats [2]VanityStat - PinnedRepos [6]syntax.ATURI + PinnedRepos [6]string Pronouns string PreferredHandle syntax.Handle } @@ -51,6 +52,22 @@ func (p Profile) IsPinnedReposEmpty() bool { 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 const ( diff --git a/appview/models/repo.go b/appview/models/repo.go index 23574b98..fd81060e 100644 --- a/appview/models/repo.go +++ b/appview/models/repo.go @@ -81,6 +81,13 @@ func (r Repo) RepoIdentifier() string { 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 index bfbbeff5..a3346128 100644 --- a/appview/oauth/handler.go +++ b/appview/oauth/handler.go @@ -376,18 +376,20 @@ func (o *OAuth) rewritePdsRecord(ctx context.Context, client *atpclient.APIClien 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 index 15c1a5bb..e5120628 100644 --- a/appview/pages/templates/user/fragments/editPins.html +++ b/appview/pages/templates/user/fragments/editPins.html @@ -24,7 +24,7 @@