From 50f263bbe0ab4b02c6701ec2ae1fcd38e3221eaf Mon Sep 17 00:00:00 2001 From: Seongmin Lee Date: Tue, 20 Jan 2026 01:05:48 +0900 Subject: [PATCH] appview: handle non-unique publicKey records - upsert public key to handle record update event - don't delete by pair of name and key. delete by name or rkey instead. Signed-off-by: Seongmin Lee --- appview/db/pubkeys.go | 24 ++++-- appview/ingester.go | 23 +++--- appview/models/pubkey.go | 38 ++++++++++ .../user/settings/fragments/keyListing.html | 2 +- appview/settings/settings.go | 76 ++++++++++--------- 5 files changed, 106 insertions(+), 57 deletions(-) diff --git a/appview/db/pubkeys.go b/appview/db/pubkeys.go index a40a2b99..8ac58eb0 100644 --- a/appview/db/pubkeys.go +++ b/appview/db/pubkeys.go @@ -6,11 +6,20 @@ import ( "tangled.org/core/appview/models" ) -func AddPublicKey(e Execer, did, name, key, rkey string) error { +func UpsertPublicKey(e Execer, pubKey models.PublicKey) error { _, err := e.Exec( - `insert or ignore into public_keys (did, name, key, rkey) - values (?, ?, ?, ?)`, - did, name, key, rkey) + `insert into public_keys (did, rkey, name, key, created) + values (?, ?, ?, ?, ?) + on conflict(did, rkey) do update set + name = excluded.name, + key = excluded.key, + created = excluded.created`, + pubKey.Did, + pubKey.Rkey, + pubKey.Name, + pubKey.Key, + pubKey.Created.Format(time.RFC3339), + ) return err } @@ -21,11 +30,12 @@ func UpdatePublicKey(e Execer, did, name, key, rkey string) error { return err } -func DeletePublicKey(e Execer, did, name, key string) error { +// for public_keys with empty rkey +func DeletePublicKeyLegacy(e Execer, did, name string) error { _, err := e.Exec(` delete from public_keys - where did = ? and name = ? and key = ?`, - did, name, key) + where did = ? and name = ? and rkey = ''`, + did, name) return err } diff --git a/appview/ingester.go b/appview/ingester.go index 9b37c60d..2a97cb56 100644 --- a/appview/ingester.go +++ b/appview/ingester.go @@ -244,6 +244,7 @@ func (i *Ingester) ingestStar(ctx context.Context, e *jmodels.Event, l *slog.Log if err != nil { return fmt.Errorf("failed to %s star record: %w", e.Commit.Operation, err) } + l.Info("processed star", "operation", e.Commit.Operation, "rkey", e.Commit.RKey) l.Info("ingested record") return nil @@ -277,6 +278,7 @@ func (i *Ingester) ingestFollow(e *jmodels.Event, l *slog.Logger) error { if err != nil { return fmt.Errorf("failed to %s follow record: %w", e.Commit.Operation, err) } + l.Info("processed follow", "operation", e.Commit.Operation, "rkey", e.Commit.RKey) l.Info("ingested record") return nil @@ -382,7 +384,7 @@ func (i *Ingester) ingestPublicKey(e *jmodels.Event, l *slog.Logger) error { l = l.With("handler", "ingestPublicKey") switch e.Commit.Operation { - case jmodels.CommitOperationCreate: + case jmodels.CommitOperationCreate, jmodels.CommitOperationUpdate: l.Debug("processing add of pubkey") raw := json.RawMessage(e.Commit.Record) record := tangled.PublicKey{} @@ -391,23 +393,17 @@ func (i *Ingester) ingestPublicKey(e *jmodels.Event, l *slog.Logger) error { l.Error("invalid record", "err", err) return err } - - name := record.Name - key := record.Key - err = db.AddPublicKey(i.Db, did, name, key, e.Commit.RKey) - case jmodels.CommitOperationUpdate: - l.Debug("processing update of pubkey") - raw := json.RawMessage(e.Commit.Record) - record := tangled.PublicKey{} - err = json.Unmarshal(raw, &record) + pubKey, err := models.PublicKeyFromRecord(syntax.DID(did), syntax.RecordKey(e.Commit.RKey), record) if err != nil { l.Error("invalid record", "err", err) return err } + if err := pubKey.Validate(); err != nil { + l.Error("invalid record", "err", err) + return err + } - name := record.Name - key := record.Key - err = db.UpdatePublicKey(i.Db, did, name, key, e.Commit.RKey) + err = db.UpsertPublicKey(i.Db, pubKey) case jmodels.CommitOperationDelete: l.Debug("processing delete of pubkey") err = db.DeletePublicKeyByRkey(i.Db, did, e.Commit.RKey) @@ -416,6 +412,7 @@ func (i *Ingester) ingestPublicKey(e *jmodels.Event, l *slog.Logger) error { if err != nil { return fmt.Errorf("failed to %s pubkey record: %w", e.Commit.Operation, err) } + l.Info("processed pubkey", "operation", e.Commit.Operation, "rkey", e.Commit.RKey) l.Info("ingested record") return nil diff --git a/appview/models/pubkey.go b/appview/models/pubkey.go index c2d64e26..9c367512 100644 --- a/appview/models/pubkey.go +++ b/appview/models/pubkey.go @@ -2,7 +2,12 @@ package models import ( "encoding/json" + "fmt" "time" + + "github.com/bluesky-social/indigo/atproto/syntax" + "github.com/gliderlabs/ssh" + "tangled.org/core/api/tangled" ) type PublicKey struct { @@ -23,3 +28,36 @@ func (p PublicKey) MarshalJSON() ([]byte, error) { Alias: (*Alias)(&p), }) } + +func (p *PublicKey) AsRecord() tangled.PublicKey { + return tangled.PublicKey{ + Name: p.Name, + Key: p.Key, + CreatedAt: p.Created.Format(time.RFC3339), + } +} + +var _ Validator = new(PublicKey) + +func (p *PublicKey) Validate() error { + if _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(p.Key)); err != nil { + return fmt.Errorf("invalid ssh key format: %w", err) + } + + return nil +} + +func PublicKeyFromRecord(did syntax.DID, rkey syntax.RecordKey, record tangled.PublicKey) (PublicKey, error) { + created, err := time.Parse(time.RFC3339, record.CreatedAt) + if err != nil { + return PublicKey{}, fmt.Errorf("invalid time format '%s'", record.CreatedAt) + } + + return PublicKey{ + Did: did.String(), + Rkey: rkey.String(), + Name: record.Name, + Key: record.Key, + Created: &created, + }, nil +} diff --git a/appview/pages/templates/user/settings/fragments/keyListing.html b/appview/pages/templates/user/settings/fragments/keyListing.html index 718134aa..3f12fc67 100644 --- a/appview/pages/templates/user/settings/fragments/keyListing.html +++ b/appview/pages/templates/user/settings/fragments/keyListing.html @@ -19,7 +19,7 @@