A vibe coded tangled fork which supports pijul.

appview: remove unnecessary pinnedrepoDIDs

Lewis: May this revision serve well! <lewis@tangled.org>

authored by

Lewis and committed by tangled.org e77ddd01 f2cdabf1

+131 -151
+2 -3
api/tangled/actorprofile.go
··· 27 27 Links []string `json:"links,omitempty" cborgen:"links,omitempty"` 28 28 // location: Free-form location text. 29 29 Location *string `json:"location,omitempty" cborgen:"location,omitempty"` 30 - // pinnedRepositories: Any ATURI, it is up to appviews to validate these fields. 31 - PinnedRepositories []string `json:"pinnedRepositories,omitempty" cborgen:"pinnedRepositories,omitempty"` 32 - PinnedRepositoryDids []string `json:"pinnedRepositoryDids,omitempty" cborgen:"pinnedRepositoryDids,omitempty"` 30 + // pinnedRepositories: Pinned repositories. Values are repo DIDs for repos that have them, or AT-URIs for legacy repos. 31 + PinnedRepositories []string `json:"pinnedRepositories,omitempty" cborgen:"pinnedRepositories,omitempty"` 33 32 // preferredHandle: A handle the user prefers to be displayed as. 34 33 PreferredHandle *string `json:"preferredHandle,omitempty" cborgen:"preferredHandle,omitempty"` 35 34 // pronouns: Preferred gender pronouns.
+2 -82
api/tangled/cbor_gen.go
··· 26 26 } 27 27 28 28 cw := cbg.NewCborWriter(w) 29 - fieldCount := 11 29 + fieldCount := 10 30 30 31 31 if t.Avatar == nil { 32 32 fieldCount-- ··· 45 45 } 46 46 47 47 if t.PinnedRepositories == nil { 48 - fieldCount-- 49 - } 50 - 51 - if t.PinnedRepositoryDids == nil { 52 48 fieldCount-- 53 49 } 54 50 ··· 344 340 return err 345 341 } 346 342 for _, v := range t.PinnedRepositories { 347 - if len(v) > 1000000 { 348 - return xerrors.Errorf("Value in field v was too long") 349 - } 350 - 351 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil { 352 - return err 353 - } 354 - if _, err := cw.WriteString(string(v)); err != nil { 355 - return err 356 - } 357 - 358 - } 359 - } 360 - 361 - // t.PinnedRepositoryDids ([]string) (slice) 362 - if t.PinnedRepositoryDids != nil { 363 - 364 - if len("pinnedRepositoryDids") > 1000000 { 365 - return xerrors.Errorf("Value in field \"pinnedRepositoryDids\" was too long") 366 - } 367 - 368 - if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("pinnedRepositoryDids"))); err != nil { 369 - return err 370 - } 371 - if _, err := cw.WriteString(string("pinnedRepositoryDids")); err != nil { 372 - return err 373 - } 374 - 375 - if len(t.PinnedRepositoryDids) > 8192 { 376 - return xerrors.Errorf("Slice value in field t.PinnedRepositoryDids was too long") 377 - } 378 - 379 - if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.PinnedRepositoryDids))); err != nil { 380 - return err 381 - } 382 - for _, v := range t.PinnedRepositoryDids { 383 343 if len(v) > 1000000 { 384 344 return xerrors.Errorf("Value in field v was too long") 385 345 } ··· 421 381 422 382 n := extra 423 383 424 - nameBuf := make([]byte, 20) 384 + nameBuf := make([]byte, 18) 425 385 for i := uint64(0); i < n; i++ { 426 386 nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000) 427 387 if err != nil { ··· 686 646 } 687 647 688 648 t.PinnedRepositories[i] = string(sval) 689 - } 690 - 691 - } 692 - } 693 - // t.PinnedRepositoryDids ([]string) (slice) 694 - case "pinnedRepositoryDids": 695 - 696 - maj, extra, err = cr.ReadHeader() 697 - if err != nil { 698 - return err 699 - } 700 - 701 - if extra > 8192 { 702 - return fmt.Errorf("t.PinnedRepositoryDids: array too large (%d)", extra) 703 - } 704 - 705 - if maj != cbg.MajArray { 706 - return fmt.Errorf("expected cbor array") 707 - } 708 - 709 - if extra > 0 { 710 - t.PinnedRepositoryDids = make([]string, extra) 711 - } 712 - 713 - for i := 0; i < int(extra); i++ { 714 - { 715 - var maj byte 716 - var extra uint64 717 - var err error 718 - _ = maj 719 - _ = extra 720 - _ = err 721 - 722 - { 723 - sval, err := cbg.ReadStringWithMax(cr, 1000000) 724 - if err != nil { 725 - return err 726 - } 727 - 728 - t.PinnedRepositoryDids[i] = string(sval) 729 649 } 730 650 731 651 }
+34
appview/db/db.go
··· 1334 1334 return err 1335 1335 }) 1336 1336 1337 + conn.ExecContext(ctx, "pragma foreign_keys = off;") 1338 + orm.RunMigration(conn, logger, "drop-pinned-repos-at-uri-fk", func(tx *sql.Tx) error { 1339 + _, err := tx.Exec(` 1340 + create table if not exists profile_pinned_repositories_new ( 1341 + id integer primary key autoincrement, 1342 + did text not null, 1343 + pin text not null, 1344 + 1345 + unique(did, pin), 1346 + foreign key (did) references profile(did) on delete cascade 1347 + ); 1348 + 1349 + insert into profile_pinned_repositories_new (id, did, pin) 1350 + select id, did, at_uri from profile_pinned_repositories; 1351 + 1352 + drop table profile_pinned_repositories; 1353 + 1354 + alter table profile_pinned_repositories_new rename to profile_pinned_repositories; 1355 + `) 1356 + return err 1357 + }) 1358 + conn.ExecContext(ctx, "pragma foreign_keys = on;") 1359 + 1360 + orm.RunMigration(conn, logger, "reset-profile-pin-rewrites", func(tx *sql.Tx) error { 1361 + _, err := tx.Exec(` 1362 + update pds_rewrite_status 1363 + set status = 'pending', 1364 + updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') 1365 + where record_nsid = 'sh.tangled.actor.profile' 1366 + and status = 'done' 1367 + `) 1368 + return err 1369 + }) 1370 + 1337 1371 return &DB{ 1338 1372 db, 1339 1373 logger,
+16 -16
appview/db/profile.go
··· 220 220 } 221 221 222 222 _, err := tx.Exec( 223 - `insert into profile_pinned_repositories (did, at_uri) values (?, ?)`, 223 + `insert into profile_pinned_repositories (did, pin) values (?, ?)`, 224 224 profile.Did, 225 225 pin, 226 226 ) ··· 328 328 idxs[did] = idx + 1 329 329 } 330 330 331 - pinsQuery := fmt.Sprintf("select at_uri, did from profile_pinned_repositories where did in (%s)", inClause) 331 + pinsQuery := fmt.Sprintf("select pin, did from profile_pinned_repositories where did in (%s)", inClause) 332 332 rows, err = e.Query(pinsQuery, args...) 333 333 if err != nil { 334 334 return nil, err ··· 340 340 idxs[did] = 0 341 341 } 342 342 for rows.Next() { 343 - var link syntax.ATURI 343 + var pin string 344 344 var did string 345 - if err = rows.Scan(&link, &did); err != nil { 345 + if err = rows.Scan(&pin, &did); err != nil { 346 346 return nil, err 347 347 } 348 348 349 349 idx := idxs[did] 350 - profileMap[did].PinnedRepos[idx] = link 350 + profileMap[did].PinnedRepos[idx] = pin 351 351 idxs[did] = idx + 1 352 352 } 353 353 ··· 435 435 i++ 436 436 } 437 437 438 - rows, err = e.Query(`select at_uri from profile_pinned_repositories where did = ?`, did) 438 + rows, err = e.Query(`select pin from profile_pinned_repositories where did = ?`, did) 439 439 if err != nil { 440 440 return nil, err 441 441 } ··· 524 524 return err 525 525 } 526 526 527 - // ensure all pinned repos are either own repos or collaborating repos 528 527 repos, err := GetRepos(e, orm.FilterEq("did", profile.Did)) 529 528 if err != nil { 530 529 log.Printf("getting repos for %s: %s", profile.Did, err) ··· 535 534 log.Printf("getting collaborating repos for %s: %s", profile.Did, err) 536 535 } 537 536 538 - var validRepos []syntax.ATURI 539 - for _, r := range repos { 540 - validRepos = append(validRepos, r.RepoAt()) 541 - } 542 - for _, r := range collaboratingRepos { 543 - validRepos = append(validRepos, r.RepoAt()) 544 - } 537 + // ensure all pinned repos are either own repos or collaborating repos 538 + allRepos := append(repos, collaboratingRepos...) 545 539 546 540 for _, pinned := range profile.PinnedRepos { 547 541 if pinned == "" { 548 542 continue 549 543 } 550 - if !slices.Contains(validRepos, pinned) { 551 - return fmt.Errorf("Invalid pinned repo: `%s, does not belong to own or collaborating repos", pinned) 544 + matched := slices.ContainsFunc(allRepos, func(r models.Repo) bool { 545 + if strings.HasPrefix(pinned, "did:") { 546 + return pinned == r.RepoDid 547 + } 548 + return pinned == string(r.RepoAt()) 549 + }) 550 + if !matched { 551 + return fmt.Errorf("Invalid pinned repo: `%s`, does not belong to own or collaborating repos", pinned) 552 552 } 553 553 } 554 554
+1 -1
appview/db/repos.go
··· 630 630 } 631 631 632 632 profileRows, err := tx.Query( 633 - `SELECT DISTINCT did FROM profile_pinned_repositories WHERE at_uri = ?`, 633 + `SELECT DISTINCT did FROM profile_pinned_repositories WHERE pin = ?`, 634 634 repoAtUri, 635 635 ) 636 636 if err != nil {
+2 -2
appview/ingester.go
··· 364 364 } 365 365 } 366 366 367 - var pinned [6]syntax.ATURI 367 + var pinned [6]string 368 368 for i, r := range record.PinnedRepositories { 369 369 if i < 6 { 370 - pinned[i] = syntax.ATURI(r) 370 + pinned[i] = r 371 371 } 372 372 } 373 373
+18 -1
appview/models/profile.go
··· 2 2 3 3 import ( 4 4 "fmt" 5 + "strings" 5 6 6 7 "github.com/bluesky-social/indigo/atproto/syntax" 7 8 "tangled.org/core/api/tangled" ··· 19 20 Location string 20 21 Links [5]string 21 22 Stats [2]VanityStat 22 - PinnedRepos [6]syntax.ATURI 23 + PinnedRepos [6]string 23 24 Pronouns string 24 25 PreferredHandle syntax.Handle 25 26 } ··· 49 50 } 50 51 } 51 52 return true 53 + } 54 + 55 + func (p Profile) MatchesPinnedRepo(repo Repo) bool { 56 + for _, pin := range p.PinnedRepos { 57 + if pin == "" { 58 + continue 59 + } 60 + if strings.HasPrefix(pin, "did:") { 61 + if pin == repo.RepoDid { 62 + return true 63 + } 64 + } else if pin == string(repo.RepoAt()) { 65 + return true 66 + } 67 + } 68 + return false 52 69 } 53 70 54 71 type VanityStatKind string
+7
appview/models/repo.go
··· 81 81 return p 82 82 } 83 83 84 + func (r Repo) PinIdentifier() string { 85 + if r.RepoDid != "" { 86 + return r.RepoDid 87 + } 88 + return string(r.RepoAt()) 89 + } 90 + 84 91 func (r Repo) TopicStr() string { 85 92 return strings.Join(r.Topics, " ") 86 93 }
+10 -8
appview/oauth/handler.go
··· 376 376 if !ok { 377 377 return fmt.Errorf("unexpected type for profile record") 378 378 } 379 - var dids []string 380 - var remaining []string 381 - for _, pinUri := range rec.PinnedRepositories { 382 - repo, repoErr := db.GetRepoByAtUri(o.Db, pinUri) 379 + rewritten := make([]string, 0, len(rec.PinnedRepositories)) 380 + for _, pin := range rec.PinnedRepositories { 381 + if strings.HasPrefix(pin, "did:") { 382 + rewritten = append(rewritten, pin) 383 + continue 384 + } 385 + repo, repoErr := db.GetRepoByAtUri(o.Db, pin) 383 386 if repoErr != nil || repo.RepoDid == "" { 384 - remaining = append(remaining, pinUri) 387 + rewritten = append(rewritten, pin) 385 388 continue 386 389 } 387 - dids = append(dids, repo.RepoDid) 390 + rewritten = append(rewritten, repo.RepoDid) 388 391 } 389 - rec.PinnedRepositoryDids = append(rec.PinnedRepositoryDids, dids...) 390 - rec.PinnedRepositories = remaining 392 + rec.PinnedRepositories = rewritten 391 393 392 394 default: 393 395 return fmt.Errorf("unsupported NSID for PDS rewrite: %s", rw.RecordNsid)
+1 -1
appview/pages/templates/user/fragments/editPins.html
··· 24 24 <div id="repos" class="grid grid-cols-1 gap-1 mb-6 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700"> 25 25 {{ range $idx, $r := .AllRepos }} 26 26 <div class="flex items-center gap-2 text-base p-2 border-b border-gray-200 dark:border-gray-700"> 27 - <input type="checkbox" id="repo-{{$idx}}" name="pinnedRepo{{$idx}}" value="{{.RepoAt}}" {{if .IsPinned}}checked{{end}}> 27 + <input type="checkbox" id="repo-{{$idx}}" name="pinnedRepo{{$idx}}" value="{{.PinIdentifier}}" {{if .IsPinned}}checked{{end}}> 28 28 <label for="repo-{{$idx}}" class="my-0 py-0 normal-case font-normal w-full"> 29 29 <div class="flex justify-between items-center w-full"> 30 30 <span class="flex-shrink-0 overflow-hidden text-ellipsis ">{{ resolve .Did }}/{{.Name}}</span>
+26 -2
appview/pages/templates/user/overview.html
··· 255 255 <div id="repos" class="grid grid-cols-1 gap-4 items-stretch"> 256 256 {{ range .Repos }} 257 257 <div class="border border-gray-200 dark:border-gray-700 rounded-sm"> 258 - {{ template "user/fragments/repoCard" (list $ . false) }} 258 + {{ if .RepoDid }} 259 + {{ template "user/fragments/repoCard" (list $ . false) }} 260 + {{ else }} 261 + <div class="py-4 px-6 gap-1 flex flex-col drop-shadow-sm rounded bg-gray-50 dark:bg-gray-900 min-h-32 opacity-60"> 262 + <div class="font-medium dark:text-white flex items-center"> 263 + {{ i "book-marked" "w-4 h-4 mr-1.5 shrink-0" }} 264 + <span class="truncate min-w-0">{{ resolve .Did }}/{{ .Name }}</span> 265 + </div> 266 + <div class="text-gray-500 dark:text-gray-400 text-sm italic"> 267 + This repo is hosted on a legacy knot that ought to be upgraded. 268 + </div> 269 + </div> 270 + {{ end }} 259 271 </div> 260 272 {{ else }} 261 273 <div class="text-base text-gray-500 flex items-center justify-center italic p-12 border border-gray-200 dark:border-gray-700 rounded"> ··· 275 287 <div id="collaborating" class="grid grid-cols-1 gap-4"> 276 288 {{ range .CollaboratingRepos }} 277 289 <div class="border border-gray-200 dark:border-gray-700 rounded-sm"> 278 - {{ template "user/fragments/repoCard" (list $ . true) }} 290 + {{ if .RepoDid }} 291 + {{ template "user/fragments/repoCard" (list $ . true) }} 292 + {{ else }} 293 + <div class="py-4 px-6 gap-1 flex flex-col drop-shadow-sm rounded bg-gray-50 dark:bg-gray-900 min-h-32 opacity-60"> 294 + <div class="font-medium dark:text-white flex items-center"> 295 + {{ i "book-marked" "w-4 h-4 mr-1.5 shrink-0" }} 296 + <span class="truncate min-w-0">{{ resolve .Did }}/{{ .Name }}</span> 297 + </div> 298 + <div class="text-gray-500 dark:text-gray-400 text-sm italic"> 299 + This repo is hosted on a legacy knot that ought to be upgraded. 300 + </div> 301 + </div> 302 + {{ end }} 279 303 </div> 280 304 {{ else }} 281 305 <p class="px-6 dark:text-white">This user is not collaborating.</p>
+10 -23
appview/state/profile.go
··· 153 153 // filter out ones that are pinned 154 154 pinnedRepos := []models.Repo{} 155 155 for i, r := range repos { 156 - // if this is a pinned repo, add it 157 - if slices.Contains(profile.Profile.PinnedRepos[:], r.RepoAt()) { 156 + if profile.Profile.MatchesPinnedRepo(r) { 158 157 pinnedRepos = append(pinnedRepos, r) 159 - } 160 - 161 - // if there are no saved pins, add the first 4 repos 162 - if profile.Profile.IsPinnedReposEmpty() && i < 4 { 158 + } else if profile.Profile.IsPinnedReposEmpty() && i < 4 { 163 159 pinnedRepos = append(pinnedRepos, r) 164 160 } 165 161 } ··· 171 167 172 168 pinnedCollaboratingRepos := []models.Repo{} 173 169 for _, r := range collaboratingRepos { 174 - // if this is a pinned repo, add it 175 - if slices.Contains(profile.Profile.PinnedRepos[:], r.RepoAt()) { 170 + if profile.Profile.MatchesPinnedRepo(r) { 176 171 pinnedCollaboratingRepos = append(pinnedCollaboratingRepos, r) 177 172 } 178 173 } ··· 723 718 } 724 719 725 720 i := 0 726 - var pinnedRepos [6]syntax.ATURI 721 + var pinnedRepos [6]string 727 722 for key, values := range r.Form { 728 723 if i >= 6 { 729 724 log.Println("invalid pin update form", err) ··· 731 726 return 732 727 } 733 728 if strings.HasPrefix(key, "pinnedRepo") && len(values) > 0 && values[0] != "" && i < 6 { 734 - aturi, err := syntax.ParseATURI(values[0]) 735 - if err != nil { 736 - log.Println("invalid profile update form", err) 737 - s.pages.Notice(w, "update-profile", "Invalid form.") 738 - return 739 - } 740 - pinnedRepos[i] = aturi 729 + pinnedRepos[i] = values[0] 741 730 i++ 742 731 } 743 732 } ··· 762 751 return 763 752 } 764 753 765 - // yeah... lexgen dose not support syntax.ATURI in the record for some reason, 766 - // nor does it support exact size arrays 767 754 var pinnedRepoStrings []string 768 755 for _, r := range profile.PinnedRepos { 769 - pinnedRepoStrings = append(pinnedRepoStrings, r.String()) 756 + if r != "" { 757 + pinnedRepoStrings = append(pinnedRepoStrings, r) 758 + } 770 759 } 771 760 772 761 var vanityStats []string ··· 868 857 allRepos := []pages.PinnedRepo{} 869 858 870 859 for _, r := range repos { 871 - isPinned := slices.Contains(profile.PinnedRepos[:], r.RepoAt()) 872 860 allRepos = append(allRepos, pages.PinnedRepo{ 873 - IsPinned: isPinned, 861 + IsPinned: profile.MatchesPinnedRepo(r), 874 862 Repo: r, 875 863 }) 876 864 } 877 865 for _, r := range collaboratingRepos { 878 - isPinned := slices.Contains(profile.PinnedRepos[:], r.RepoAt()) 879 866 allRepos = append(allRepos, pages.PinnedRepo{ 880 - IsPinned: isPinned, 867 + IsPinned: profile.MatchesPinnedRepo(r), 881 868 Repo: r, 882 869 }) 883 870 }
+2 -12
lexicons/actor/profile.json
··· 60 60 "maxGraphemes": 40, 61 61 "maxLength": 400 62 62 }, 63 - "pinnedRepositoryDids": { 64 - "type": "array", 65 - "minLength": 0, 66 - "maxLength": 6, 67 - "items": { 68 - "type": "string", 69 - "format": "did" 70 - } 71 - }, 72 63 "pinnedRepositories": { 73 64 "type": "array", 74 - "description": "Any ATURI, it is up to appviews to validate these fields.", 65 + "description": "Pinned repositories. Values are repo DIDs for repos that have them, or AT-URIs for legacy repos.", 75 66 "minLength": 0, 76 67 "maxLength": 6, 77 68 "items": { 78 - "type": "string", 79 - "format": "at-uri" 69 + "type": "string" 80 70 } 81 71 }, 82 72 "pronouns": {