Monorepo for Tangled
0

Configure Feed

Select the types of activity you want to include in your feed.

appview: migrate legacy comment pds records

Signed-off-by: Seongmin Lee <git@boltless.me>

Seongmin Lee (May 22, 2026, 7:55 PM +0900) f3eb95ff 147807ef

+132 -1
+14
appview/db/db.go
··· 2073 2073 return err 2074 2074 }) 2075 2075 2076 + orm.RunMigration(conn, logger, "migrate-legacy-comments", func(tx *sql.Tx) error { 2077 + _, err := tx.Exec(` 2078 + insert into pds_migration (name, did, collection, rkey) 2079 + select 2080 + 'use-feed-comment', 2081 + did, 2082 + collection, 2083 + rkey 2084 + from comments 2085 + where collection <> 'sh.tangled.feed.comment'; 2086 + `) 2087 + return err 2088 + }) 2089 + 2076 2090 return &DB{ 2077 2091 db, 2078 2092 logger,
+113
appview/migration/migrate_use_feed_comment.go
··· 1 + package migration 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + 7 + "github.com/bluesky-social/indigo/api/agnostic" 8 + comatproto "github.com/bluesky-social/indigo/api/atproto" 9 + "github.com/bluesky-social/indigo/atproto/atclient" 10 + "github.com/bluesky-social/indigo/atproto/syntax" 11 + "github.com/bluesky-social/indigo/lex/util" 12 + "github.com/bluesky-social/indigo/xrpc" 13 + "tangled.org/core/api/tangled" 14 + "tangled.org/core/appview/db" 15 + "tangled.org/core/orm" 16 + ) 17 + 18 + func (s *Migration) migrateUseFeedComment(ctx context.Context, client *atclient.APIClient, did syntax.DID, record syntax.ATURI) error { 19 + l := s.logger.With("aturi", record) 20 + l.Debug("migrating record") 21 + 22 + switch record.Collection() { 23 + case tangled.RepoIssueCommentNSID: 24 + case tangled.RepoPullCommentNSID: 25 + default: 26 + return fmt.Errorf("unexpected collection: '%s'", record.Collection()) 27 + } 28 + 29 + comment, err := db.GetComment(s.db, orm.FilterEq("at_uri", record)) 30 + if err != nil { 31 + return fmt.Errorf("db: %w", err) 32 + } 33 + 34 + comment.Collection = tangled.FeedCommentNSID 35 + 36 + // only update from DB if comment is deleted 37 + if comment.Deleted != nil { 38 + l.Info("skipping pds migration for deleted record") 39 + 40 + return nil 41 + } 42 + 43 + // fill missing reference CIDs 44 + if comment.Subject.Cid == "" { 45 + cid, err := s.getRecordCid(ctx, syntax.ATURI(comment.Subject.Uri)) 46 + if err != nil { 47 + return fmt.Errorf("pds: getRecordCid for subject.uri: %w", err) 48 + } 49 + comment.Subject.Cid = cid.String() 50 + } 51 + if comment.ReplyTo != nil && comment.ReplyTo.Cid == "" { 52 + uri, err := syntax.ParseATURI(comment.ReplyTo.Uri) 53 + if err != nil { 54 + return fmt.Errorf("invalid replyTo.uri: %w", err) 55 + } 56 + 57 + // assume parent comment is already migrated to `sh.tangled.feed.comment`. 58 + // fail if it isn't ready 59 + uri = syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", uri.Authority(), tangled.FeedCommentNSID, uri.RecordKey())) 60 + 61 + cid, err := s.getRecordCid(ctx, uri) 62 + if err != nil { 63 + return fmt.Errorf("pds: getRecordCid for replyTo.uri: %w", err) 64 + } 65 + comment.ReplyTo.Uri = uri.String() 66 + comment.ReplyTo.Cid = cid.String() 67 + } 68 + 69 + // use same rkey for new record 70 + rkey := record.RecordKey().String() 71 + 72 + if _, err := comatproto.RepoApplyWrites(ctx, client, &comatproto.RepoApplyWrites_Input{ 73 + Repo: did.String(), 74 + Writes: []*comatproto.RepoApplyWrites_Input_Writes_Elem{ 75 + {RepoApplyWrites_Delete: &comatproto.RepoApplyWrites_Delete{ 76 + Collection: record.Collection().String(), 77 + Rkey: rkey, 78 + }}, 79 + {RepoApplyWrites_Create: &comatproto.RepoApplyWrites_Create{ 80 + Collection: tangled.FeedCommentNSID, 81 + Rkey: &rkey, 82 + Value: &util.LexiconTypeDecoder{Val: comment.AsRecord()}, 83 + }}, 84 + }, 85 + }); err != nil { 86 + return fmt.Errorf("pds: applyWrites: %w", err) 87 + } 88 + 89 + return nil 90 + } 91 + 92 + func (s *Migration) getRecordCid(ctx context.Context, uri syntax.ATURI) (syntax.CID, error) { 93 + ident, err := s.dir.Lookup(ctx, uri.Authority()) 94 + if err != nil { 95 + return "", err 96 + } 97 + 98 + xrpcc := xrpc.Client{Host: ident.PDSEndpoint()} 99 + out, err := agnostic.RepoGetRecord(ctx, &xrpcc, "", uri.Collection().String(), ident.DID.String(), uri.RecordKey().String()) 100 + if err != nil { 101 + return "", err 102 + } 103 + if out.Cid == nil { 104 + return "", fmt.Errorf("record CID is empty") 105 + } 106 + 107 + cid, err := syntax.ParseCID(*out.Cid) 108 + if err != nil { 109 + return "", err 110 + } 111 + 112 + return cid, nil 113 + }
+5 -1
appview/migration/migration.go
··· 45 45 onPermAuthErr: oauth.HandlePermanentAuthErr, 46 46 } 47 47 m.migrators = map[string]migrator{ 48 - "add-repo-did": m.migrateAddRepoDid, 48 + "add-repo-did": m.migrateAddRepoDid, 49 + "use-feed-comment": m.migrateUseFeedComment, 49 50 } 50 51 return m 51 52 } ··· 121 122 if err == nil { 122 123 l.Info("migrated") 123 124 migration.Status = models.PDSMigrationStatusDone 125 + migration.ErrorMsg = nil 126 + migration.RetryCount = 0 127 + migration.RetryAfter = 0 124 128 } else { 125 129 l.Warn("failed to migrate", "err", err) 126 130