From 5129d588e794696651ca2695d72489b70b683c9c Mon Sep 17 00:00:00 2001 From: Seongmin Lee Date: Fri, 24 Apr 2026 00:08:11 +0900 Subject: [PATCH] appview: ingest cid of `sh.tangled.string` records the `cid` column is nullable for now, missing ones will be filled later. Signed-off-by: Seongmin Lee --- appview/db/db.go | 7 ++++ appview/db/strings.go | 30 ++++++++------ appview/ingester.go | 3 +- appview/ingester_string_test.go | 18 +++++++++ appview/models/string.go | 12 +++--- appview/pages/templates/strings/string.html | 1 + appview/pages/url.go | 2 +- appview/strings/strings.go | 44 +++++++++++---------- 8 files changed, 77 insertions(+), 40 deletions(-) diff --git a/appview/db/db.go b/appview/db/db.go index c85aa3d4..cba011a9 100644 --- a/appview/db/db.go +++ b/appview/db/db.go @@ -2218,6 +2218,13 @@ func Make(ctx context.Context, dbPath string) (*DB, error) { return err }) + orm.RunMigration(conn, logger, "add-cid-to-strings", func(tx *sql.Tx) error { + _, err := tx.Exec(` + alter table strings add column cid text; + `) + return err + }) + return &DB{ db, logger, diff --git a/appview/db/strings.go b/appview/db/strings.go index dc20070d..3cffb710 100644 --- a/appview/db/strings.go +++ b/appview/db/strings.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/bluesky-social/indigo/atproto/syntax" "tangled.org/core/appview/models" "tangled.org/core/orm" ) @@ -16,26 +17,24 @@ func AddString(e Execer, s models.String) error { `insert into strings ( did, rkey, + cid, filename, description, content, created, edited ) - values (?, ?, ?, ?, ?, ?, null) + values (?, ?, ?, ?, ?, ?, ?, null) on conflict(did, rkey) do update set - filename = excluded.filename, + cid = excluded.cid, + filename = excluded.filename, description = excluded.description, - content = excluded.content, - edited = case - when - strings.content != excluded.content - or strings.filename != excluded.filename - or strings.description != excluded.description then ? - else strings.edited - end`, + content = excluded.content, + edited = case when strings.cid is not null then ? else strings.edited end + where strings.cid is not excluded.cid`, s.Did, s.Rkey, + s.Cid, s.Filename, s.Description, s.Contents, @@ -68,6 +67,7 @@ func GetStrings(e Execer, limit int, filters ...orm.Filter) ([]models.String, er query := fmt.Sprintf(`select did, rkey, + cid, filename, description, content, @@ -91,11 +91,12 @@ func GetStrings(e Execer, limit int, filters ...orm.Filter) ([]models.String, er for rows.Next() { var s models.String var createdAt string - var editedAt sql.NullString + var cid, editedAt sql.Null[string] if err := rows.Scan( &s.Did, &s.Rkey, + &cid, &s.Filename, &s.Description, &s.Contents, @@ -105,13 +106,18 @@ func GetStrings(e Execer, limit int, filters ...orm.Filter) ([]models.String, er return nil, err } + if cid.Valid { + s.Cid = new(syntax.CID) + *s.Cid = syntax.CID(cid.V) + } + s.Created, err = time.Parse(time.RFC3339, createdAt) if err != nil { s.Created = time.Now() } if editedAt.Valid { - e, err := time.Parse(time.RFC3339, editedAt.String) + e, err := time.Parse(time.RFC3339, editedAt.V) if err != nil { e = time.Now() } diff --git a/appview/ingester.go b/appview/ingester.go index 4b79658f..77787ed2 100644 --- a/appview/ingester.go +++ b/appview/ingester.go @@ -78,6 +78,7 @@ func (i *Ingester) Ingest() processFunc { "nsid", e.Commit.Collection, "did", e.Did, "rkey", e.Commit.RKey, + "cid", e.Commit.CID, "op", e.Commit.Operation, ) switch e.Commit.Collection { @@ -930,7 +931,7 @@ func (i *Ingester) ingestString(e *jmodels.Event, l *slog.Logger) error { return err } - string := models.StringFromRecord(did, rkey, record) + string := models.StringFromRecord(syntax.DID(did), syntax.RecordKey(rkey), syntax.CID(e.Commit.CID), record) if err = i.Validator.ValidateString(&string); err != nil { l.Error("invalid record", "err", err) diff --git a/appview/ingester_string_test.go b/appview/ingester_string_test.go index e70f891a..288fcc85 100644 --- a/appview/ingester_string_test.go +++ b/appview/ingester_string_test.go @@ -1,6 +1,7 @@ package appview import ( + "bytes" "encoding/json" "log/slog" "path/filepath" @@ -9,6 +10,9 @@ import ( "time" jmodels "github.com/bluesky-social/jetstream/pkg/models" + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multihash" + cbg "github.com/whyrusleeping/cbor-gen" "tangled.org/core/api/tangled" "tangled.org/core/appview/db" "tangled.org/core/appview/models" @@ -45,10 +49,23 @@ func makeStringEvent(t *testing.T, op, did, rkey string, record tangled.String) Collection: tangled.StringNSID, RKey: rkey, Record: raw, + CID: makeCID(t, &record), }, } } +func makeCID(t *testing.T, record cbg.CBORMarshaler) string { + buf := new(bytes.Buffer) + if err := record.MarshalCBOR(buf); err != nil { + t.Fatalf("cbor marshal record: %v", err) + } + c, err := cid.NewPrefixV1(cid.DagCBOR, multihash.SHA2_256).Sum(buf.Bytes()) + if err != nil { + t.Fatalf("cid: sum: %v", err) + } + return c.String() +} + func loadString(t *testing.T, ing *Ingester, did, rkey string) (models.String, bool) { t.Helper() rows, err := db.GetStrings(ing.Db, 0, @@ -178,6 +195,7 @@ func TestIngestString_DeleteRemovesRow(t *testing.T) { Operation: jmodels.CommitOperationDelete, Collection: tangled.StringNSID, RKey: "rk1", + CID: makeCID(t, &rec), }, } if err := ing.ingestString(del, ing.Logger); err != nil { diff --git a/appview/models/string.go b/appview/models/string.go index 7f7c9ad3..2b3075ad 100644 --- a/appview/models/string.go +++ b/appview/models/string.go @@ -13,7 +13,8 @@ import ( type String struct { Did syntax.DID - Rkey string + Rkey syntax.RecordKey + Cid *syntax.CID Filename string Description string @@ -26,8 +27,8 @@ func (s *String) AtUri() syntax.ATURI { return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", s.Did, tangled.StringNSID, s.Rkey)) } -func (s *String) AsRecord() tangled.String { - return tangled.String{ +func (s *String) AsRecord() *tangled.String { + return &tangled.String{ Filename: s.Filename, Description: s.Description, Contents: s.Contents, @@ -35,14 +36,15 @@ func (s *String) AsRecord() tangled.String { } } -func StringFromRecord(did, rkey string, record tangled.String) String { +func StringFromRecord(did syntax.DID, rkey syntax.RecordKey, cid syntax.CID, record tangled.String) String { created, err := time.Parse(time.RFC3339, record.CreatedAt) if err != nil { created = time.Now() } return String{ - Did: syntax.DID(did), + Did: did, Rkey: rkey, + Cid: &cid, Filename: record.Filename, Description: record.Description, Contents: record.Contents, diff --git a/appview/pages/templates/strings/string.html b/appview/pages/templates/strings/string.html index 0029cc4f..ed4aa02b 100644 --- a/appview/pages/templates/strings/string.html +++ b/appview/pages/templates/strings/string.html @@ -121,6 +121,7 @@ class="group/form" > +
{{ template "user/fragments/picHandleLink" .LoggedInUser.Did }} diff --git a/appview/pages/url.go b/appview/pages/url.go index c1531d86..46ba1a6d 100644 --- a/appview/pages/url.go +++ b/appview/pages/url.go @@ -109,5 +109,5 @@ func (p *Pages) MakeStringUrl(ctx context.Context, uri syntax.ATURI) (string, er if err == nil && !ownerIdentity.Handle.IsInvalidHandle() { owner = ownerIdentity.Handle.String() } - return path.Join("/strings", owner, string_.Rkey), nil + return path.Join("/strings", owner, string_.Rkey.String()), nil } diff --git a/appview/strings/strings.go b/appview/strings/strings.go index 5d6e7c3a..4c97025c 100644 --- a/appview/strings/strings.go +++ b/appview/strings/strings.go @@ -297,8 +297,6 @@ func (s *Strings) edit(w http.ResponseWriter, r *http.Request) { Created: first.Created, } - record := entry.AsRecord() - client, err := s.OAuth.AuthorizedClient(r) if err != nil { fail("Failed to create record.", err) @@ -306,19 +304,27 @@ func (s *Strings) edit(w http.ResponseWriter, r *http.Request) { } // first replace the existing record in the PDS - ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.StringNSID, entry.Did.String(), entry.Rkey) - if err != nil { - fail("Failed to updated existing record.", err) - return + var exCid string + if entry.Cid != nil { + exCid = entry.Cid.String() + } else { + ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.StringNSID, entry.Did.String(), entry.Rkey.String()) + if err != nil { + fail("Failed to get existing record.", err) + return + } + if ex.Cid == nil { + fail("Failed to get existing record.", err) + return + } + exCid = *ex.Cid } resp, err := comatproto.RepoPutRecord(r.Context(), client, &atproto.RepoPutRecord_Input{ Collection: tangled.StringNSID, Repo: entry.Did.String(), - Rkey: entry.Rkey, - SwapRecord: ex.Cid, - Record: &lexutil.LexiconTypeDecoder{ - Val: &record, - }, + Rkey: entry.Rkey.String(), + SwapRecord: &exCid, + Record: &lexutil.LexiconTypeDecoder{Val: entry.AsRecord()}, }) if err != nil { fail("Failed to updated existing record.", err) @@ -336,7 +342,7 @@ func (s *Strings) edit(w http.ResponseWriter, r *http.Request) { s.Notifier.EditString(r.Context(), &entry) // if that went okay, redir to the string - s.Pages.HxRedirect(w, "/strings/"+user.Did+"/"+entry.Rkey) + s.Pages.HxRedirect(w, fmt.Sprintf("/strings/%s/%s", entry.Did, entry.Rkey)) } } @@ -373,15 +379,13 @@ func (s *Strings) create(w http.ResponseWriter, r *http.Request) { string := models.String{ Did: syntax.DID(user.Did), - Rkey: tid.TID(), + Rkey: syntax.RecordKey(tid.TID()), Filename: filename, Description: description, Contents: content, Created: time.Now(), } - record := string.AsRecord() - client, err := s.OAuth.AuthorizedClient(r) if err != nil { fail("Failed to create record.", err) @@ -390,11 +394,9 @@ func (s *Strings) create(w http.ResponseWriter, r *http.Request) { resp, err := comatproto.RepoPutRecord(r.Context(), client, &atproto.RepoPutRecord_Input{ Collection: tangled.StringNSID, - Repo: user.Did, - Rkey: string.Rkey, - Record: &lexutil.LexiconTypeDecoder{ - Val: &record, - }, + Repo: string.Did.String(), + Rkey: string.Rkey.String(), + Record: &lexutil.LexiconTypeDecoder{Val: string.AsRecord()}, }) if err != nil { fail("Failed to create record.", err) @@ -412,7 +414,7 @@ func (s *Strings) create(w http.ResponseWriter, r *http.Request) { s.Notifier.NewString(r.Context(), &string) // successful - s.Pages.HxRedirect(w, "/strings/"+user.Did+"/"+string.Rkey) + s.Pages.HxRedirect(w, fmt.Sprintf("/strings/%s/%s", string.Did, string.Rkey)) } } -- 2.54.0