- commit
- fec17a90a7f8e62742fdb75fc54c77be7804fbac
- parent
- ff254bfea41381760de1dbc3dfe871af52a03f8d
- author
- Dami <dami.etoile@gmail.com>
| M | internal/ | 6 | ++- |
| M | internal/ | 20 | +++++++++ |
| M | internal/ | 3 | + |
| A | internal/ | 66 | ++++++++++++++++++++++++++++++ |
@@ -50,6 +50,7 @@ func (s *Store) Vanish(ctx context.Context, pubkey string, until int64) error {
5050 {"DELETE FROM events WHERE pubkey=? AND created_at<=?", []any{pubkey, until}},5151 {"DELETE FROM events WHERE kind=1059 AND id IN(SELECT event_id FROM tags WHERE name='p' AND value=?)", []any{pubkey}},5252 {"DELETE FROM list_history WHERE owner=?", []any{pubkey}},53+ {"DELETE FROM wiki_revisions WHERE author=?", []any{pubkey}},5354 }5455 for _, q := range statements {5556 if _, err := tx.ExecContext(ctx, q.sql, q.args...); err != nil {@@ -93,7 +94,10 @@ func (s *Store) EraseAuthor(ctx context.Context, pubkey string) (int64, error) {
9394 if err != nil {9495 return err9596 }96- _, err = tx.ExecContext(ctx, "DELETE FROM list_history WHERE owner=?", pubkey)97+ if _, err = tx.ExecContext(ctx, "DELETE FROM list_history WHERE owner=?", pubkey); err != nil {98+ return err99+ }100+ _, err = tx.ExecContext(ctx, "DELETE FROM wiki_revisions WHERE author=?", pubkey)97101 return err98102 })99103 return n, err@@ -159,6 +159,14 @@ func replace(ctx context.Context, tx *sql.Tx, e event.Event, now int64) error {
159159 return fmt.Errorf("archive replaced list: %w", err)160160 }161161 }162+ if e.Kind == kindWikiArticle {163+ // A wiki page keeps its history: the version being replaced moves164+ // to wiki_revisions with the id of the event that replaced it.165+ _, err := tx.ExecContext(ctx, "INSERT OR IGNORE INTO wiki_revisions(event_id,author,d,created_at,raw,superseded_by) SELECT id,pubkey,d,created_at,raw,? FROM events WHERE pubkey=? AND kind=? AND d=?", e.ID, e.PubKey, e.Kind, d)166+ if err != nil {167+ return fmt.Errorf("archive replaced wiki revision: %w", err)168+ }169+ }162170 _, err = tx.ExecContext(ctx, "DELETE FROM events WHERE pubkey=? AND kind=? AND d=?", e.PubKey, e.Kind, d)163171 if err != nil {164172 return fmt.Errorf("replace event: %w", err)@@ -182,6 +190,11 @@ func applyDeletion(ctx context.Context, tx *sql.Tx, e event.Event, tag []string)
182190 if _, err := tx.ExecContext(ctx, "DELETE FROM list_history WHERE kind=? AND owner=? AND d=? AND created_at<=?", kind, e.PubKey, parts[2], e.CreatedAt); err != nil {183191 return fmt.Errorf("delete list history: %w", err)184192 }193+ if kind == kindWikiArticle {194+ if _, err := tx.ExecContext(ctx, "DELETE FROM wiki_revisions WHERE author=? AND d=? AND created_at<=?", e.PubKey, parts[2], e.CreatedAt); err != nil {195+ return fmt.Errorf("delete wiki revisions: %w", err)196+ }197+ }185198 } else {186199 if _, err := tx.ExecContext(ctx, "DELETE FROM events WHERE id=? AND (pubkey=? OR (kind=1059 AND EXISTS(SELECT 1 FROM tags WHERE event_id=events.id AND name='p' AND value=?)))", tag[1], e.PubKey, e.PubKey); err != nil {187200 return fmt.Errorf("delete event: %w", err)@@ -189,6 +202,9 @@ func applyDeletion(ctx context.Context, tx *sql.Tx, e event.Event, tag []string)
189202 if _, err := tx.ExecContext(ctx, "DELETE FROM list_history WHERE owner=? AND event_id=?", e.PubKey, tag[1]); err != nil {190203 return fmt.Errorf("delete list version: %w", err)191204 }205+ if _, err := tx.ExecContext(ctx, "DELETE FROM wiki_revisions WHERE author=? AND event_id=?", e.PubKey, tag[1]); err != nil {206+ return fmt.Errorf("delete wiki revision: %w", err)207+ }192208 }193209 _, err := tx.ExecContext(ctx, "INSERT INTO deletions(author,target_type,target,until) VALUES(?,?,?,?) ON CONFLICT(author,target_type,target) DO UPDATE SET until=max(until,excluded.until)", e.PubKey, tag[0], tag[1], e.CreatedAt)194210 if err != nil {@@ -243,6 +259,10 @@ func indexed(kind int, mode string) bool {
243259 244260 func isListKind(kind int) bool { return kind == 3 || kind == 10002 || kind == 10003 || kind == 30003 }245261 262+// kindWikiArticle is the NIP-54 article kind, whose replaced versions are263+// kept as revisions.264+const kindWikiArticle = 30818265+246266 func AddIntents(ctx context.Context, tx *sql.Tx, intents []Intent, now int64) error {247267 for _, intent := range intents {248268 key := sha256.Sum256([]byte(intent.Kind + "\x00" + intent.EventID + "\x00" + intent.Target))@@ -27,6 +27,9 @@ CREATE TABLE IF NOT EXISTS marmot_principals(event_id TEXT PRIMARY KEY REFERENCE
2727 CREATE INDEX IF NOT EXISTS marmot_principal_pk ON marmot_principals(pubkey);2828 CREATE TABLE IF NOT EXISTS list_history(owner TEXT NOT NULL,kind INTEGER NOT NULL,d TEXT NOT NULL DEFAULT '',event_id TEXT NOT NULL,created_at INTEGER NOT NULL,saved_at INTEGER NOT NULL,expires INTEGER NOT NULL DEFAULT 0,raw TEXT NOT NULL,PRIMARY KEY(owner,kind,d,event_id));2929 CREATE INDEX IF NOT EXISTS list_history_owner ON list_history(owner,saved_at DESC);30+CREATE TABLE IF NOT EXISTS wiki_revisions(event_id TEXT PRIMARY KEY,author TEXT NOT NULL,d TEXT NOT NULL,created_at INTEGER NOT NULL,raw TEXT NOT NULL,superseded_by TEXT NOT NULL);31+CREATE INDEX IF NOT EXISTS wiki_revisions_page ON wiki_revisions(d,created_at DESC);32+CREATE INDEX IF NOT EXISTS wiki_revisions_author ON wiki_revisions(author,d,created_at DESC);3033 CREATE TABLE IF NOT EXISTS settings(key TEXT PRIMARY KEY,value TEXT NOT NULL);3134 CREATE TABLE IF NOT EXISTS work_intents (3235 id TEXT PRIMARY KEY,kind TEXT NOT NULL,event_id TEXT NOT NULL,target TEXT NOT NULL,payload TEXT NOT NULL,@@ -0,0 +1,66 @@
1+package storage2+3+// Wiki revisions. A kind 30818 article is addressable, so a newer version4+// from the same author replaces the older one in events. The replaced5+// event is kept here with the id of the event that replaced it, until the6+// author deletes it with a kind 5 naming the revision or the page.7+8+import (9+ "context"10+ "database/sql"11+ "encoding/json"12+ "errors"13+ "fmt"14+15+ "github.com/FelineStateMachine/tinyrelay/internal/event"16+)17+18+// WikiRevision is one archived version of a wiki page.19+type WikiRevision struct {20+ ID string21+ Author string22+ D string23+ CreatedAt int6424+ SupersededBy string25+ Event event.Event26+}27+28+// WikiRevisions lists archived revisions newest first. An empty author or29+// d matches every author or page.30+func (s *Store) WikiRevisions(ctx context.Context, author, d string) ([]WikiRevision, error) {31+ rows, err := s.db.QueryContext(ctx, "SELECT event_id,author,d,created_at,superseded_by,raw FROM wiki_revisions WHERE (?='' OR author=?) AND (?='' OR d=?) ORDER BY created_at DESC,event_id", author, author, d, d)32+ if err != nil {33+ return nil, fmt.Errorf("query wiki revisions: %w", err)34+ }35+ defer rows.Close()36+ result := []WikiRevision{}37+ for rows.Next() {38+ var row WikiRevision39+ var raw string40+ if err := rows.Scan(&row.ID, &row.Author, &row.D, &row.CreatedAt, &row.SupersededBy, &raw); err != nil {41+ return nil, err42+ }43+ if err := json.Unmarshal([]byte(raw), &row.Event); err != nil {44+ return nil, fmt.Errorf("decode wiki revision %s: %w", row.ID, err)45+ }46+ result = append(result, row)47+ }48+ return result, rows.Err()49+}50+51+// WikiRevision reads one archived revision by event id.52+func (s *Store) WikiRevision(ctx context.Context, id string) (WikiRevision, bool, error) {53+ var row WikiRevision54+ var raw string55+ err := s.db.QueryRowContext(ctx, "SELECT event_id,author,d,created_at,superseded_by,raw FROM wiki_revisions WHERE event_id=?", id).Scan(&row.ID, &row.Author, &row.D, &row.CreatedAt, &row.SupersededBy, &raw)56+ if errors.Is(err, sql.ErrNoRows) {57+ return row, false, nil58+ }59+ if err != nil {60+ return row, false, fmt.Errorf("read wiki revision: %w", err)61+ }62+ if err := json.Unmarshal([]byte(raw), &row.Event); err != nil {63+ return row, false, fmt.Errorf("decode wiki revision %s: %w", id, err)64+ }65+ return row, true, nil66+}