From 531be478599f76e79dfd30f5083f370a41c58a21 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Tue, 05 May 2026 17:38:20 +0000 Subject: [PATCH] appview/db: support vouch evidences in DB Signed-off-by: oppiliappan --- appview/ingester.go | 29 ++++++++++++++++++++++++++++- appview/db/db.go | 45 ++++++++++++++++++++++++++++++++++++++++----- appview/db/vouch.go | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- appview/models/vouch.go | 1 + appview/state/vouch.go | 27 ++++++++++++++++++++++++++- 5 file(s) changed, 172 insertion(s)(+), 16 deletion(s)(-) diff --git a/appview/ingester.go b/appview/ingester.go --- a/appview/ingester.go +++ b/appview/ingester.go @@ -260,13 +260,40 @@ 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/db/db.go b/appview/db/db.go --- a/appview/db/db.go +++ b/appview/db/db.go @@ -651,11 +651,6 @@ 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, @@ -669,6 +664,19 @@ status text not null check (status in ('subscribed', 'dismissed')), email text, 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 @@ -1474,6 +1482,33 @@ `) 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, diff --git a/appview/db/vouch.go b/appview/db/vouch.go --- a/appview/db/vouch.go +++ b/appview/db/vouch.go @@ -15,9 +15,43 @@ ) 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 @@ 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 @@ } 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 @@ 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 } @@ -163,6 +225,12 @@ if reason.Valid { 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) diff --git a/appview/models/vouch.go b/appview/models/vouch.go --- a/appview/models/vouch.go +++ b/appview/models/vouch.go @@ -38,6 +38,7 @@ 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 --- a/appview/state/vouch.go +++ b/appview/state/vouch.go @@ -103,6 +103,15 @@ 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 @@ Kind: string(kind), Reason: reasonPtr, CreatedAt: createdAt, + Evidences: evidences, }}, }) if err != nil { @@ -143,11 +153,26 @@ Cid: newCid, Kind: kind, Reason: reasonPtr, + Evidences: evidences, } - err = db.AddVouch(s.db, vouch) + 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(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 } -- tangled.sh