diff --git a/appview/ingester.go b/appview/ingester.go --- a/appview/ingester.go +++ b/appview/ingester.go @@ -364,10 +364,10 @@ } } - 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/api/tangled/actorprofile.go b/api/tangled/actorprofile.go --- a/api/tangled/actorprofile.go +++ b/api/tangled/actorprofile.go @@ -27,9 +27,8 @@ 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 @@ } cw := cbg.NewCborWriter(w) - fieldCount := 11 + fieldCount := 10 if t.Avatar == nil { fieldCount-- @@ -45,10 +45,6 @@ } if t.PinnedRepositories == nil { - fieldCount-- - } - - if t.PinnedRepositoryDids == nil { fieldCount-- } @@ -357,42 +353,6 @@ } } - - // 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 @@ 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 @@ } 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 @@ 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 @@ } _, 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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/models/profile.go b/appview/models/profile.go --- a/appview/models/profile.go +++ b/appview/models/profile.go @@ -2,6 +2,7 @@ import ( "fmt" + "strings" "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/api/tangled" @@ -19,7 +20,7 @@ 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 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 @@ 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 @@ 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) - if repoErr != nil || repo.RepoDid == "" { - remaining = append(remaining, pinUri) + rewritten := make([]string, 0, len(rec.PinnedRepositories)) + for _, pin := range rec.PinnedRepositories { + if strings.HasPrefix(pin, "did:") { + rewritten = append(rewritten, pin) continue } - dids = append(dids, repo.RepoDid) + repo, repoErr := db.GetRepoByAtUri(o.Db, pin) + if repoErr != nil || repo.RepoDid == "" { + rewritten = append(rewritten, pin) + continue + } + 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/state/profile.go b/appview/state/profile.go --- a/appview/state/profile.go +++ b/appview/state/profile.go @@ -153,13 +153,9 @@ // filter out ones that are pinned pinnedRepos := []models.Repo{} for i, r := range repos { - // if this is a pinned repo, add it - if slices.Contains(profile.Profile.PinnedRepos[:], r.RepoAt()) { + if profile.Profile.MatchesPinnedRepo(r) { pinnedRepos = append(pinnedRepos, r) - } - - // if there are no saved pins, add the first 4 repos - if profile.Profile.IsPinnedReposEmpty() && i < 4 { + } else if profile.Profile.IsPinnedReposEmpty() && i < 4 { pinnedRepos = append(pinnedRepos, r) } } @@ -171,8 +167,7 @@ pinnedCollaboratingRepos := []models.Repo{} for _, r := range collaboratingRepos { - // if this is a pinned repo, add it - if slices.Contains(profile.Profile.PinnedRepos[:], r.RepoAt()) { + if profile.Profile.MatchesPinnedRepo(r) { pinnedCollaboratingRepos = append(pinnedCollaboratingRepos, r) } } @@ -723,7 +718,7 @@ } i := 0 - var pinnedRepos [6]syntax.ATURI + var pinnedRepos [6]string for key, values := range r.Form { if i >= 6 { log.Println("invalid pin update form", err) @@ -731,13 +726,7 @@ return } if strings.HasPrefix(key, "pinnedRepo") && len(values) > 0 && values[0] != "" && i < 6 { - aturi, err := syntax.ParseATURI(values[0]) - if err != nil { - log.Println("invalid profile update form", err) - s.pages.Notice(w, "update-profile", "Invalid form.") - return - } - pinnedRepos[i] = aturi + pinnedRepos[i] = values[0] i++ } } @@ -762,11 +751,11 @@ return } - // yeah... lexgen dose not support syntax.ATURI in the record for some reason, - // nor does it support exact size arrays var pinnedRepoStrings []string for _, r := range profile.PinnedRepos { - pinnedRepoStrings = append(pinnedRepoStrings, r.String()) + if r != "" { + pinnedRepoStrings = append(pinnedRepoStrings, r) + } } var vanityStats []string @@ -868,16 +857,14 @@ allRepos := []pages.PinnedRepo{} for _, r := range repos { - isPinned := slices.Contains(profile.PinnedRepos[:], r.RepoAt()) allRepos = append(allRepos, pages.PinnedRepo{ - IsPinned: isPinned, + IsPinned: profile.MatchesPinnedRepo(r), Repo: r, }) } for _, r := range collaboratingRepos { - isPinned := slices.Contains(profile.PinnedRepos[:], r.RepoAt()) allRepos = append(allRepos, pages.PinnedRepo{ - IsPinned: isPinned, + IsPinned: profile.MatchesPinnedRepo(r), Repo: r, }) } diff --git a/lexicons/actor/profile.json b/lexicons/actor/profile.json --- a/lexicons/actor/profile.json +++ b/lexicons/actor/profile.json @@ -60,23 +60,13 @@ "maxGraphemes": 40, "maxLength": 400 }, - "pinnedRepositoryDids": { - "type": "array", - "minLength": 0, - "maxLength": 6, - "items": { - "type": "string", - "format": "did" - } - }, "pinnedRepositories": { "type": "array", - "description": "Any ATURI, it is up to appviews to validate these fields.", + "description": "Pinned repositories. Values are repo DIDs for repos that have them, or AT-URIs for legacy repos.", "minLength": 0, "maxLength": 6, "items": { - "type": "string", - "format": "at-uri" + "type": "string" } }, "pronouns": { diff --git a/appview/pages/templates/user/overview.html b/appview/pages/templates/user/overview.html --- a/appview/pages/templates/user/overview.html +++ b/appview/pages/templates/user/overview.html @@ -255,7 +255,19 @@
This user is not collaborating.
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 @@