Federated via graft
0

Configure Feed

Select the types of activity you want to include in your feed.

1// Package state persists sync bookkeeping so the daemon can tell, across 2// restarts, what has already been mirrored and avoid duplicating issues, 3// patches or commits on every poll. 4package state 5 6import ( 7 "database/sql" 8 "fmt" 9 10 _ "modernc.org/sqlite" 11) 12 13// Store wraps the local SQLite database used to track sync state. 14type Store struct { 15 db *sql.DB 16} 17 18// Open creates (if needed) and opens the state database at path. 19func Open(path string) (*Store, error) { 20 db, err := sql.Open("sqlite", path) 21 if err != nil { 22 return nil, fmt.Errorf("open state db: %w", err) 23 } 24 if err := migrate(db); err != nil { 25 db.Close() 26 return nil, fmt.Errorf("migrate state db: %w", err) 27 } 28 return &Store{db: db}, nil 29} 30 31func (s *Store) Close() error { 32 return s.db.Close() 33} 34 35func migrate(db *sql.DB) error { 36 _, err := db.Exec(` 37CREATE TABLE IF NOT EXISTS git_cursor ( 38 repo_pair TEXT PRIMARY KEY, 39 forgejo_sha TEXT NOT NULL DEFAULT '', 40 radicle_sha TEXT NOT NULL DEFAULT '' 41); 42 43CREATE TABLE IF NOT EXISTS item_mapping ( 44 repo_pair TEXT NOT NULL, 45 kind TEXT NOT NULL, -- 'issue' or 'patch' 46 forgejo_id INTEGER NOT NULL DEFAULT 0, 47 radicle_id TEXT NOT NULL DEFAULT '', 48 content_hash TEXT NOT NULL DEFAULT '', 49 synced_at TEXT NOT NULL, 50 PRIMARY KEY (repo_pair, kind, forgejo_id, radicle_id) 51); 52 53CREATE INDEX IF NOT EXISTS idx_item_mapping_forgejo 54 ON item_mapping (repo_pair, kind, forgejo_id); 55CREATE INDEX IF NOT EXISTS idx_item_mapping_radicle 56 ON item_mapping (repo_pair, kind, radicle_id); 57`) 58 return err 59} 60 61// GitCursor returns the last-synced commit SHAs on each side for a repo pair. 62func (s *Store) GitCursor(repoPair string) (forgejoSHA, radicleSHA string, err error) { 63 row := s.db.QueryRow(`SELECT forgejo_sha, radicle_sha FROM git_cursor WHERE repo_pair = ?`, repoPair) 64 err = row.Scan(&forgejoSHA, &radicleSHA) 65 if err == sql.ErrNoRows { 66 return "", "", nil 67 } 68 return forgejoSHA, radicleSHA, err 69} 70 71// SetGitCursor records the commit SHAs last observed on each side. 72func (s *Store) SetGitCursor(repoPair, forgejoSHA, radicleSHA string) error { 73 _, err := s.db.Exec(` 74INSERT INTO git_cursor (repo_pair, forgejo_sha, radicle_sha) VALUES (?, ?, ?) 75ON CONFLICT (repo_pair) DO UPDATE SET forgejo_sha = excluded.forgejo_sha, radicle_sha = excluded.radicle_sha 76`, repoPair, forgejoSHA, radicleSHA) 77 return err 78} 79 80// ItemMapping records the correspondence between a Forgejo issue/PR and its 81// mirrored Radicle issue/patch, plus a content hash used to detect edits 82// that need re-syncing. 83type ItemMapping struct { 84 Kind string // "issue" or "patch" 85 ForgejoID int64 86 RadicleID string 87 ContentHash string 88} 89 90// FindByForgejoID looks up an existing mapping created for a Forgejo item. 91func (s *Store) FindByForgejoID(repoPair, kind string, forgejoID int64) (*ItemMapping, error) { 92 row := s.db.QueryRow(` 93SELECT radicle_id, content_hash FROM item_mapping 94WHERE repo_pair = ? AND kind = ? AND forgejo_id = ? 95`, repoPair, kind, forgejoID) 96 m := &ItemMapping{Kind: kind, ForgejoID: forgejoID} 97 err := row.Scan(&m.RadicleID, &m.ContentHash) 98 if err == sql.ErrNoRows { 99 return nil, nil 100 } 101 return m, err 102} 103 104// FindByRadicleID looks up an existing mapping created for a Radicle item. 105func (s *Store) FindByRadicleID(repoPair, kind, radicleID string) (*ItemMapping, error) { 106 row := s.db.QueryRow(` 107SELECT forgejo_id, content_hash FROM item_mapping 108WHERE repo_pair = ? AND kind = ? AND radicle_id = ? 109`, repoPair, kind, radicleID) 110 m := &ItemMapping{Kind: kind, RadicleID: radicleID} 111 err := row.Scan(&m.ForgejoID, &m.ContentHash) 112 if err == sql.ErrNoRows { 113 return nil, nil 114 } 115 return m, err 116} 117 118// Upsert records or updates a mapping between a Forgejo item and its 119// Radicle counterpart. 120func (s *Store) Upsert(repoPair string, m ItemMapping) error { 121 _, err := s.db.Exec(` 122INSERT INTO item_mapping (repo_pair, kind, forgejo_id, radicle_id, content_hash, synced_at) 123VALUES (?, ?, ?, ?, ?, datetime('now')) 124ON CONFLICT (repo_pair, kind, forgejo_id, radicle_id) 125DO UPDATE SET content_hash = excluded.content_hash, synced_at = excluded.synced_at 126`, repoPair, m.Kind, m.ForgejoID, m.RadicleID, m.ContentHash) 127 return err 128}