From f3eb95ffdb8ecede0cc4462ea72b691fd72fd1dc Mon Sep 17 00:00:00 2001 From: Seongmin Lee Date: Sat, 04 Apr 2026 12:58:12 +0000 Subject: [PATCH] appview: migrate legacy comment pds records Signed-off-by: Seongmin Lee --- appview/db/db.go | 14 ++++++++++++++ appview/migration/migrate_use_feed_comment.go | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ appview/migration/migration.go | 6 +++++- 3 file(s) changed, 132 insertion(s)(+), 1 deletion(s)(-) diff --git a/appview/db/db.go b/appview/db/db.go --- a/appview/db/db.go +++ b/appview/db/db.go @@ -2073,6 +2073,20 @@ return err }) + orm.RunMigration(conn, logger, "migrate-legacy-comments", func(tx *sql.Tx) error { + _, err := tx.Exec(` + insert into pds_migration (name, did, collection, rkey) + select + 'use-feed-comment', + did, + collection, + rkey + from comments + where collection <> 'sh.tangled.feed.comment'; + `) + return err + }) + return &DB{ db, logger, diff --git a/appview/migration/migrate_use_feed_comment.go b/appview/migration/migrate_use_feed_comment.go new file mode 100644 --- /dev/null +++ b/appview/migration/migrate_use_feed_comment.go @@ -0,0 +1,113 @@ +package migration + +import ( + "context" + "fmt" + + "github.com/bluesky-social/indigo/api/agnostic" + comatproto "github.com/bluesky-social/indigo/api/atproto" + "github.com/bluesky-social/indigo/atproto/atclient" + "github.com/bluesky-social/indigo/atproto/syntax" + "github.com/bluesky-social/indigo/lex/util" + "github.com/bluesky-social/indigo/xrpc" + "tangled.org/core/api/tangled" + "tangled.org/core/appview/db" + "tangled.org/core/orm" +) + +func (s *Migration) migrateUseFeedComment(ctx context.Context, client *atclient.APIClient, did syntax.DID, record syntax.ATURI) error { + l := s.logger.With("aturi", record) + l.Debug("migrating record") + + switch record.Collection() { + case tangled.RepoIssueCommentNSID: + case tangled.RepoPullCommentNSID: + default: + return fmt.Errorf("unexpected collection: '%s'", record.Collection()) + } + + comment, err := db.GetComment(s.db, orm.FilterEq("at_uri", record)) + if err != nil { + return fmt.Errorf("db: %w", err) + } + + comment.Collection = tangled.FeedCommentNSID + + // only update from DB if comment is deleted + if comment.Deleted != nil { + l.Info("skipping pds migration for deleted record") + + return nil + } + + // fill missing reference CIDs + if comment.Subject.Cid == "" { + cid, err := s.getRecordCid(ctx, syntax.ATURI(comment.Subject.Uri)) + if err != nil { + return fmt.Errorf("pds: getRecordCid for subject.uri: %w", err) + } + comment.Subject.Cid = cid.String() + } + if comment.ReplyTo != nil && comment.ReplyTo.Cid == "" { + uri, err := syntax.ParseATURI(comment.ReplyTo.Uri) + if err != nil { + return fmt.Errorf("invalid replyTo.uri: %w", err) + } + + // assume parent comment is already migrated to `sh.tangled.feed.comment`. + // fail if it isn't ready + uri = syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", uri.Authority(), tangled.FeedCommentNSID, uri.RecordKey())) + + cid, err := s.getRecordCid(ctx, uri) + if err != nil { + return fmt.Errorf("pds: getRecordCid for replyTo.uri: %w", err) + } + comment.ReplyTo.Uri = uri.String() + comment.ReplyTo.Cid = cid.String() + } + + // use same rkey for new record + rkey := record.RecordKey().String() + + if _, err := comatproto.RepoApplyWrites(ctx, client, &comatproto.RepoApplyWrites_Input{ + Repo: did.String(), + Writes: []*comatproto.RepoApplyWrites_Input_Writes_Elem{ + {RepoApplyWrites_Delete: &comatproto.RepoApplyWrites_Delete{ + Collection: record.Collection().String(), + Rkey: rkey, + }}, + {RepoApplyWrites_Create: &comatproto.RepoApplyWrites_Create{ + Collection: tangled.FeedCommentNSID, + Rkey: &rkey, + Value: &util.LexiconTypeDecoder{Val: comment.AsRecord()}, + }}, + }, + }); err != nil { + return fmt.Errorf("pds: applyWrites: %w", err) + } + + return nil +} + +func (s *Migration) getRecordCid(ctx context.Context, uri syntax.ATURI) (syntax.CID, error) { + ident, err := s.dir.Lookup(ctx, uri.Authority()) + if err != nil { + return "", err + } + + xrpcc := xrpc.Client{Host: ident.PDSEndpoint()} + out, err := agnostic.RepoGetRecord(ctx, &xrpcc, "", uri.Collection().String(), ident.DID.String(), uri.RecordKey().String()) + if err != nil { + return "", err + } + if out.Cid == nil { + return "", fmt.Errorf("record CID is empty") + } + + cid, err := syntax.ParseCID(*out.Cid) + if err != nil { + return "", err + } + + return cid, nil +} diff --git a/appview/migration/migration.go b/appview/migration/migration.go --- a/appview/migration/migration.go +++ b/appview/migration/migration.go @@ -45,7 +45,8 @@ onPermAuthErr: oauth.HandlePermanentAuthErr, } m.migrators = map[string]migrator{ - "add-repo-did": m.migrateAddRepoDid, + "add-repo-did": m.migrateAddRepoDid, + "use-feed-comment": m.migrateUseFeedComment, } return m } @@ -121,6 +122,9 @@ if err == nil { l.Info("migrated") migration.Status = models.PDSMigrationStatusDone + migration.ErrorMsg = nil + migration.RetryCount = 0 + migration.RetryAfter = 0 } else { l.Warn("failed to migrate", "err", err) -- tangled.sh