Bootstrap geth Rust workspace
This commit is contained in:
commit
26f81ff1ef
73 changed files with 4835 additions and 0 deletions
240
crates/geth-store/src/lib.rs
Normal file
240
crates/geth-store/src/lib.rs
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
use rusqlite::{Connection, params};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum StoreError {
|
||||
#[error("sqlite error: {0}")]
|
||||
Sqlite(#[from] rusqlite::Error),
|
||||
#[error("json error: {0}")]
|
||||
Json(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
pub struct Store {
|
||||
conn: Connection,
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub fn open(path: &Path) -> Result<Self, StoreError> {
|
||||
let conn = Connection::open(path)?;
|
||||
let store = Self { conn };
|
||||
store.migrate()?;
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
pub fn open_memory() -> Result<Self, StoreError> {
|
||||
let conn = Connection::open_in_memory()?;
|
||||
let store = Self { conn };
|
||||
store.migrate()?;
|
||||
Ok(store)
|
||||
}
|
||||
|
||||
pub fn migrate(&self) -> Result<(), StoreError> {
|
||||
self.conn.execute_batch(
|
||||
r#"
|
||||
PRAGMA foreign_keys = ON;
|
||||
CREATE TABLE IF NOT EXISTS meta (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS agents (
|
||||
agent_id TEXT PRIMARY KEY,
|
||||
public_key TEXT NOT NULL,
|
||||
created_at_ms INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS nodes (
|
||||
node_id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
agent_id TEXT,
|
||||
created_at_ms INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS resources (
|
||||
resource_id TEXT PRIMARY KEY,
|
||||
kind TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
authority_ref TEXT NOT NULL,
|
||||
local_role TEXT NOT NULL,
|
||||
replication_policy TEXT NOT NULL,
|
||||
retention_policy TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
created_at_ms INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS keychain_ops (
|
||||
op_id TEXT PRIMARY KEY,
|
||||
op_json TEXT NOT NULL,
|
||||
created_at_ms INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS auth_ops (
|
||||
op_id TEXT PRIMARY KEY,
|
||||
resource_id TEXT NOT NULL,
|
||||
op_json TEXT NOT NULL,
|
||||
created_at_ms INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS grants (
|
||||
grant_id TEXT PRIMARY KEY,
|
||||
resource_id TEXT NOT NULL,
|
||||
principal_id TEXT NOT NULL,
|
||||
capability TEXT NOT NULL,
|
||||
revoked INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS resource_secrets (
|
||||
secret_id TEXT PRIMARY KEY,
|
||||
resource_id TEXT NOT NULL,
|
||||
epoch INTEGER NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
created_at_ms INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS cas_objects (
|
||||
hash TEXT PRIMARY KEY,
|
||||
size_bytes INTEGER NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
created_at_ms INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS cas_pins (
|
||||
hash TEXT PRIMARY KEY,
|
||||
pinned_at_ms INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS kv_stores (
|
||||
kv_id TEXT PRIMARY KEY,
|
||||
resource_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS db_resources (
|
||||
db_id TEXT PRIMARY KEY,
|
||||
resource_id TEXT NOT NULL,
|
||||
path TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS document_resources (
|
||||
document_id TEXT PRIMARY KEY,
|
||||
resource_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS peer_cards (
|
||||
peer_id TEXT PRIMARY KEY,
|
||||
card_json TEXT NOT NULL,
|
||||
updated_at_ms INTEGER NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS module_state (
|
||||
module TEXT PRIMARY KEY,
|
||||
state_json TEXT NOT NULL,
|
||||
updated_at_ms INTEGER NOT NULL
|
||||
);
|
||||
INSERT OR IGNORE INTO meta(key, value) VALUES ('schema_version', '1');
|
||||
"#,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn upsert_agent(&self, agent_id: &str, public_key: &str) -> Result<(), StoreError> {
|
||||
self.conn.execute(
|
||||
"INSERT OR IGNORE INTO agents(agent_id, public_key, created_at_ms) VALUES (?1, ?2, ?3)",
|
||||
params![agent_id, public_key, now_ms()],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn upsert_node(&self, node_id: &str, name: &str, agent_id: &str) -> Result<(), StoreError> {
|
||||
self.conn.execute(
|
||||
"INSERT OR IGNORE INTO nodes(node_id, name, agent_id, created_at_ms) VALUES (?1, ?2, ?3, ?4)",
|
||||
params![node_id, name, agent_id, now_ms()],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list_resources(&self) -> Result<Vec<StoredResource>, StoreError> {
|
||||
let mut stmt = self.conn.prepare(
|
||||
"SELECT resource_id, kind, name, status FROM resources ORDER BY kind, name, resource_id",
|
||||
)?;
|
||||
let rows = stmt.query_map([], |row| {
|
||||
Ok(StoredResource {
|
||||
resource_id: row.get(0)?,
|
||||
kind: row.get(1)?,
|
||||
name: row.get(2)?,
|
||||
status: row.get(3)?,
|
||||
})
|
||||
})?;
|
||||
rows.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(StoreError::from)
|
||||
}
|
||||
|
||||
pub fn insert_resource(&self, resource: &StoredResource) -> Result<(), StoreError> {
|
||||
self.conn.execute(
|
||||
r#"INSERT OR IGNORE INTO resources(
|
||||
resource_id, kind, name, authority_ref, local_role, replication_policy,
|
||||
retention_policy, status, created_at_ms
|
||||
) VALUES (?1, ?2, ?3, 'local', 'owner', 'local-only', 'keep', ?4, ?5)"#,
|
||||
params![
|
||||
resource.resource_id,
|
||||
resource.kind,
|
||||
resource.name,
|
||||
resource.status,
|
||||
now_ms()
|
||||
],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn record_cas_object(
|
||||
&self,
|
||||
hash: &str,
|
||||
size_bytes: u64,
|
||||
path: &str,
|
||||
) -> Result<(), StoreError> {
|
||||
self.conn.execute(
|
||||
"INSERT OR REPLACE INTO cas_objects(hash, size_bytes, path, created_at_ms) VALUES (?1, ?2, ?3, ?4)",
|
||||
params![hash, size_bytes as i64, path, now_ms()],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list_cas_objects(&self) -> Result<Vec<CasObject>, StoreError> {
|
||||
let mut stmt = self.conn.prepare(
|
||||
"SELECT hash, size_bytes, path FROM cas_objects ORDER BY created_at_ms, hash",
|
||||
)?;
|
||||
let rows = stmt.query_map([], |row| {
|
||||
Ok(CasObject {
|
||||
hash: row.get(0)?,
|
||||
size_bytes: row.get::<_, i64>(1)? as u64,
|
||||
path: row.get(2)?,
|
||||
})
|
||||
})?;
|
||||
rows.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(StoreError::from)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct StoredResource {
|
||||
pub resource_id: String,
|
||||
pub kind: String,
|
||||
pub name: String,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CasObject {
|
||||
pub hash: String,
|
||||
pub size_bytes: u64,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn now_ms() -> i64 {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default();
|
||||
i64::try_from(now.as_millis()).unwrap_or(i64::MAX)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn migrations_are_idempotent() {
|
||||
let store = Store::open_memory().expect("open");
|
||||
store.migrate().expect("migrate again");
|
||||
store.migrate().expect("migrate third time");
|
||||
let resources = store.list_resources().expect("resources");
|
||||
assert!(resources.is_empty());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue