From 531be478599f76e79dfd30f5083f370a41c58a21 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Tue, 5 May 2026 18:38:20 +0100 Subject: [PATCH] appview/db: support vouch evidences in DB Signed-off-by: oppiliappan --- appview/db/db.go | 45 ++++++++++++++++++--- appview/db/vouch.go | 86 ++++++++++++++++++++++++++++++++++++----- appview/ingester.go | 29 +++++++++++++- appview/models/vouch.go | 1 + appview/state/vouch.go | 27 ++++++++++++- 5 files changed, 172 insertions(+), 16 deletions(-) diff --git a/appview/db/db.go b/appview/db/db.go index c5e51750..48427743 100644 --- a/appview/db/db.go +++ b/appview/db/db.go @@ -651,11 +651,6 @@ func Make(ctx context.Context, dbPath string) (*DB, error) { foreign key (repo_at) references repos(at_uri) on delete cascade ); - create table if not exists migrations ( - id integer primary key autoincrement, - name text unique - ); - create table if not exists punchcard_preferences ( id integer primary key autoincrement, user_did text not null unique, @@ -671,6 +666,19 @@ func Make(ctx context.Context, dbPath string) (*DB, error) { updated_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')) ); + create table if not exists vouch_evidences ( + id integer primary key autoincrement, + vouch_id integer not null, + at_uri text not null, + unique(vouch_id, at_uri), + foreign key (vouch_id) references vouches(id) on delete cascade + ); + + create table if not exists migrations ( + id integer primary key autoincrement, + name text unique + ); + -- indexes for better performance create index if not exists idx_notifications_recipient_created on notifications(recipient_did, created desc); create index if not exists idx_notifications_recipient_read on notifications(recipient_did, read); @@ -1475,6 +1483,33 @@ func Make(ctx context.Context, dbPath string) (*DB, error) { return err }) + conn.ExecContext(ctx, "pragma foreign_keys = off;") + orm.RunMigration(conn, logger, "add-id-to-vouches", func(tx *sql.Tx) error { + _, err := tx.Exec(` + create table vouches_new ( + id integer primary key autoincrement, + did text not null, + subject_did text not null, + cid text not null, + kind text not null default 'vouch', + reason text, + created_at text not null default (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), + unique(did, subject_did), + check (did <> subject_did), + check (kind in ('vouch', 'denounce')) + ); + + insert into vouches_new (did, subject_did, cid, kind, reason, created_at) + select did, subject_did, cid, kind, reason, created_at + from vouches; + + drop table vouches; + alter table vouches_new rename to vouches; + `) + return err + }) + conn.ExecContext(ctx, "pragma foreign_keys = on;") + return &DB{ db, logger, diff --git a/appview/db/vouch.go b/appview/db/vouch.go index 349644fb..02db7883 100644 --- a/appview/db/vouch.go +++ b/appview/db/vouch.go @@ -15,9 +15,43 @@ import ( ) func AddVouch(e Execer, vouch *models.Vouch) error { - query := `insert or replace into vouches (did, subject_did, cid, kind, reason) values (?, ?, ?, ?, ?)` - _, err := e.Exec(query, vouch.Did, vouch.SubjectDid, vouch.Cid.String(), vouch.Kind, vouch.Reason) - return err + // insert if not exists + _, err := e.Exec( + `insert or ignore into vouches (did, subject_did, cid, kind, reason) values (?, ?, ?, ?, ?)`, + vouch.Did, vouch.SubjectDid, vouch.Cid.String(), vouch.Kind, vouch.Reason, + ) + if err != nil { + return err + } + + // then update + _, err = e.Exec( + `update vouches set cid = ?, kind = ?, reason = ? where did = ? and subject_did = ?`, + vouch.Cid.String(), vouch.Kind, vouch.Reason, vouch.Did, vouch.SubjectDid, + ) + if err != nil { + return err + } + + // replace evidences: delete all existing, then insert new ones. + _, err = e.Exec( + `delete from vouch_evidences where vouch_id = (select id from vouches where did = ? and subject_did = ?)`, + vouch.Did, vouch.SubjectDid, + ) + if err != nil { + return err + } + for _, uri := range vouch.Evidences { + _, err = e.Exec( + `insert into vouch_evidences (vouch_id, at_uri) + values ((select id from vouches where did = ? and subject_did = ?), ?)`, + vouch.Did, vouch.SubjectDid, uri.String(), + ) + if err != nil { + return err + } + } + return nil } func GetVouch(e Execer, did, subjectDid string) (*models.Vouch, error) { @@ -101,6 +135,30 @@ func GetVouches(e Execer, page pagination.Page, filters ...orm.Filter) ([]models return vouches, nil } +func GetVouchEvidences(e Execer, did, subjectDid string) ([]syntax.ATURI, error) { + rows, err := e.Query( + `select at_uri from vouch_evidences + where vouch_id = (select id from vouches where did = ? and subject_did = ?) + order by id asc`, + did, subjectDid, + ) + if err != nil { + return nil, err + } + defer rows.Close() + + var evidences []syntax.ATURI + for rows.Next() { + var uri string + if err := rows.Scan(&uri); err != nil { + log.Println("error scanning vouch evidence:", err) + continue + } + evidences = append(evidences, syntax.ATURI(uri)) + } + return evidences, nil +} + func DeleteVouch(e Execer, did, subjectDid string) error { _, err := e.Exec(`delete from vouches where did = ? and subject_did = ?`, did, subjectDid) return err @@ -118,14 +176,17 @@ func GetNetworkVouchTimeline(e Execer, viewerDid, profileDid string, page pagina } query := fmt.Sprintf( - `select did, subject_did, cid, kind, reason, created_at - from vouches + `select v.did, v.subject_did, v.cid, v.kind, v.reason, v.created_at, + group_concat(ve.at_uri, '|') as evidences + from vouches v + left join vouch_evidences ve on ve.vouch_id = v.id where ( - subject_did = ? and did in (select subject_did from vouches where did = ? and kind = 'vouch') + v.subject_did = ? and v.did in (select subject_did from vouches where did = ? and kind = 'vouch') ) or ( - did = ? and subject_did in (select subject_did from vouches where did = ? and kind = 'vouch') + v.did = ? and v.subject_did in (select subject_did from vouches where did = ? and kind = 'vouch') ) - order by created_at desc + group by v.did, v.subject_did + order by v.created_at desc %s`, pageClause) @@ -141,8 +202,9 @@ func GetNetworkVouchTimeline(e Execer, viewerDid, profileDid string, page pagina var cidStr string var createdAt string var reason sql.NullString + var evidences sql.NullString - if err := rows.Scan(&v.Did, &v.SubjectDid, &cidStr, &v.Kind, &reason, &createdAt); err != nil { + if err := rows.Scan(&v.Did, &v.SubjectDid, &cidStr, &v.Kind, &reason, &createdAt, &evidences); err != nil { log.Println("error scanning vouch:", err) continue } @@ -165,6 +227,12 @@ func GetNetworkVouchTimeline(e Execer, viewerDid, profileDid string, page pagina v.Reason = &reason.String } + if evidences.Valid && evidences.String != "" { + for _, s := range strings.Split(evidences.String, "|") { + v.Evidences = append(v.Evidences, syntax.ATURI(s)) + } + } + vouches = append(vouches, v) } return vouches, nil diff --git a/appview/ingester.go b/appview/ingester.go index 812ff583..0e6f01a3 100644 --- a/appview/ingester.go +++ b/appview/ingester.go @@ -260,13 +260,40 @@ func (i *Ingester) ingestVouch(ctx context.Context, e *jmodels.Event) error { return fmt.Errorf("invalid cid: %w", err) } - err = db.AddVouch(i.Db, &models.Vouch{ + var evidences []syntax.ATURI + for _, raw := range record.Evidences { + uri, parseErr := syntax.ParseATURI(raw) + if parseErr != nil { + l.Warn("invalid evidence AT-URI, skipping", "uri", raw, "err", parseErr) + continue + } + evidences = append(evidences, uri) + } + + ddb, ok := i.Db.Execer.(*db.DB) + if !ok { + return fmt.Errorf("failed to ingest vouch record, invalid db cast") + } + + tx, txErr := ddb.Begin() + if txErr != nil { + return fmt.Errorf("failed to start transaction: %w", txErr) + } + + addErr := db.AddVouch(tx, &models.Vouch{ Did: syntax.DID(did), SubjectDid: subjectId.DID, Cid: recordCid, Kind: kind, Reason: record.Reason, + Evidences: evidences, }) + if addErr != nil { + tx.Rollback() + err = addErr + } else { + err = tx.Commit() + } case jmodels.CommitOperationDelete: err = db.DeleteVouchByRkey(i.Db, did, e.Commit.RKey) diff --git a/appview/models/vouch.go b/appview/models/vouch.go index fa27068f..43ae6916 100644 --- a/appview/models/vouch.go +++ b/appview/models/vouch.go @@ -38,6 +38,7 @@ type Vouch struct { Cid cid.Cid Kind VouchKind Reason *string + Evidences []syntax.ATURI CreatedAt time.Time } diff --git a/appview/state/vouch.go b/appview/state/vouch.go index bcf2f22d..d19a7ad8 100644 --- a/appview/state/vouch.go +++ b/appview/state/vouch.go @@ -103,6 +103,15 @@ func (s *State) Vouch(w http.ResponseWriter, r *http.Request) { reasonPtr = &reason } + var evidences []string + for _, raw := range r.Form["evidences"] { + if _, err := syntax.ParseATURI(raw); err != nil { + l.Warn("invalid evidence AT-URI, skipping", "uri", raw, "err", err) + continue + } + evidences = append(evidences, raw) + } + var swapCid *string existingVouch, err := db.GetVouch(s.db, currentUser.Did, subjectDid) if err == nil { @@ -120,6 +129,7 @@ func (s *State) Vouch(w http.ResponseWriter, r *http.Request) { Kind: string(kind), Reason: reasonPtr, CreatedAt: createdAt, + Evidences: evidences, }}, }) if err != nil { @@ -143,14 +153,29 @@ func (s *State) Vouch(w http.ResponseWriter, r *http.Request) { Cid: newCid, Kind: kind, Reason: reasonPtr, + Evidences: evidences, + } + + tx, err := s.db.Begin() + if err != nil { + l.Error("failed to start transaction", "err", err) + s.pages.Notice(w, "error", "Failed to save vouch.") + return } + defer tx.Rollback() - err = db.AddVouch(s.db, vouch) + err = db.AddVouch(tx, vouch) if err != nil { l.Error("failed to add vouch to db", "err", err) s.pages.Notice(w, "error", "Failed to save vouch.") return } + if err = tx.Commit(); err != nil { + l.Error("failed to commit vouch transaction", "err", err) + s.pages.Notice(w, "error", "Failed to save vouch.") + return + } + s.pages.HxRefresh(w) } -- 2.51.2