diff options
-rw-r--r-- | adenosine-pds/src/atp_db.sql | 11 | ||||
-rw-r--r-- | adenosine-pds/src/bin/adenosine-pds.rs | 2 | ||||
-rw-r--r-- | adenosine-pds/src/db.rs | 4 | ||||
-rw-r--r-- | adenosine-pds/src/lib.rs | 5 | ||||
-rw-r--r-- | adenosine-pds/src/mst.rs | 26 | ||||
-rw-r--r-- | adenosine-pds/src/repo.rs | 156 | ||||
-rw-r--r-- | adenosine-pds/tests/test_repro_mst.rs | 2 |
7 files changed, 181 insertions, 25 deletions
diff --git a/adenosine-pds/src/atp_db.sql b/adenosine-pds/src/atp_db.sql index 918a89c..a6fce1c 100644 --- a/adenosine-pds/src/atp_db.sql +++ b/adenosine-pds/src/atp_db.sql @@ -6,7 +6,7 @@ CREATE TABLE account( username TEXT NOT NULL, email TEXT NOT NULL, password_bcrypt TEXT NOT NULL, - signing_key TEXT NOT NULL, + signing_key TEXT NOT NULL ); CREATE UNIQUE INDEX account_username_uniq_idx on account(lower(username)); CREATE UNIQUE INDEX account_email_uniq_idx on account(lower(email)); @@ -14,19 +14,19 @@ CREATE UNIQUE INDEX account_email_uniq_idx on account(lower(email)); CREATE TABLE did_doc( did TEXT PRIMARY KEY NOT NULL, doc_json TEXT NOT NULL, - seen_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, + seen_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT ( DATETIME('now') ) ); CREATE TABLE session( did TEXT NOT NULL, jwt TEXT NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT ( DATETIME('now') ), PRIMARY KEY(did, jwt) ); CREATE TABLE repo( did TEXT PRIMARY KEY NOT NULL, - head_commit TEXT NOT NULL, + head_commit TEXT NOT NULL ); CREATE TABLE record( @@ -44,7 +44,6 @@ CREATE TABLE password_reset( PRIMARY KEY(did, token) ); - ----------- bsky app/index tables - +-- TODO diff --git a/adenosine-pds/src/bin/adenosine-pds.rs b/adenosine-pds/src/bin/adenosine-pds.rs index b76d015..0159ab7 100644 --- a/adenosine-pds/src/bin/adenosine-pds.rs +++ b/adenosine-pds/src/bin/adenosine-pds.rs @@ -83,6 +83,6 @@ fn main() -> Result<()> { run_server(port, &opt.blockstore_db_path, &opt.atp_db_path) } Command::Import { car_path } => load_car_to_sqlite(&opt.blockstore_db_path, &car_path), - Command::Inspect {} => dump_mst_keys(&opt.blockstore_db_path), + Command::Inspect {} => mst::dump_mst_keys(&opt.blockstore_db_path), } } diff --git a/adenosine-pds/src/db.rs b/adenosine-pds/src/db.rs index 1d6d4ed..905210c 100644 --- a/adenosine-pds/src/db.rs +++ b/adenosine-pds/src/db.rs @@ -61,7 +61,7 @@ impl AtpDatabase { pub fn get_record(&mut self, did: &str, collection: &str, tid: &str) -> Result<Value> { let mut stmt = self.conn.prepare_cached( - "SELECT record_json FROM record WHERE did = ?1 collection = ?2 tid = ?3", + "SELECT record_json FROM record WHERE did = ?1 AND collection = ?2 AND tid = ?3", )?; Ok(stmt.query_row(params!(did, collection, tid), |row| { row.get(0).map(|v: String| Value::from_str(&v)) @@ -71,7 +71,7 @@ impl AtpDatabase { pub fn get_record_list(&mut self, did: &str, collection: &str) -> Result<Vec<String>> { let mut stmt = self .conn - .prepare_cached("SELECT tid FROM record WHERE did = ?1 collection = ?2")?; + .prepare_cached("SELECT tid FROM record WHERE did = ?1 AND collection = ?2")?; let ret = stmt .query_and_then(params!(did, collection), |row| { let v: String = row.get(0)?; diff --git a/adenosine-pds/src/lib.rs b/adenosine-pds/src/lib.rs index a8dc46a..0ef081d 100644 --- a/adenosine-pds/src/lib.rs +++ b/adenosine-pds/src/lib.rs @@ -9,12 +9,13 @@ use ipfs_sqlite_block_store::BlockStore; mod car; mod db; mod models; -mod mst; +pub mod mst; +mod repo; pub use car::{load_car_to_blockstore, load_car_to_sqlite}; pub use db::AtpDatabase; pub use models::*; -pub use mst::{dump_mst_keys, repro_mst}; +pub use repo::{RepoCommit, RepoStore}; pub fn run_server(port: u16, blockstore_db_path: &PathBuf, atp_db_path: &PathBuf) -> Result<()> { // TODO: some static files? https://github.com/tomaka/rouille/blob/master/examples/static-files.rs diff --git a/adenosine-pds/src/mst.rs b/adenosine-pds/src/mst.rs index 429d8c8..3e01a92 100644 --- a/adenosine-pds/src/mst.rs +++ b/adenosine-pds/src/mst.rs @@ -12,25 +12,25 @@ use std::collections::BTreeMap; use std::path::PathBuf; #[derive(Debug, DagCbor, PartialEq, Eq)] -struct CommitNode { - root: Cid, - sig: Box<[u8]>, +pub struct CommitNode { + pub root: Cid, + pub sig: Box<[u8]>, } #[derive(Debug, DagCbor, PartialEq, Eq)] -struct RootNode { - auth_token: Option<String>, - prev: Option<Cid>, +pub struct RootNode { + pub auth_token: Option<String>, + pub prev: Option<Cid>, // TODO: not 'metadata'? - meta: Cid, - data: Cid, + pub meta: Cid, + pub data: Cid, } #[derive(Debug, DagCbor, PartialEq, Eq)] -struct MetadataNode { - datastore: String, // "mst" - did: String, - version: u8, // 1 +pub struct MetadataNode { + pub datastore: String, // "mst" + pub did: String, + pub version: u8, // 1 } #[derive(Debug, DagCbor, PartialEq, Eq)] @@ -132,7 +132,7 @@ pub fn dump_mst_keys(db_path: &PathBuf) -> Result<()> { Ok(()) } -fn collect_mst_keys( +pub fn collect_mst_keys( db: &mut BlockStore<libipld::DefaultParams>, cid: &Cid, map: &mut BTreeMap<String, Cid>, diff --git a/adenosine-pds/src/repo.rs b/adenosine-pds/src/repo.rs new file mode 100644 index 0000000..e5de504 --- /dev/null +++ b/adenosine-pds/src/repo.rs @@ -0,0 +1,156 @@ +use crate::mst::{collect_mst_keys, CommitNode, MetadataNode, RootNode}; +use anyhow::{anyhow, Result}; +use ipfs_sqlite_block_store::BlockStore; +use libipld::cbor::DagCborCodec; +use libipld::multihash::Code; +use libipld::prelude::Codec; +use libipld::store::DefaultParams; +use libipld::{Block, Cid, DagCbor, Ipld}; +use std::borrow::Cow; +use std::collections::BTreeMap; +use std::path::PathBuf; +use std::str::FromStr; + +pub struct RepoCommit { + pub sig: Box<[u8]>, + pub did: String, + pub prev: Option<String>, + pub mst_cid: String, +} + +pub struct RepoStore { + db: BlockStore<libipld::DefaultParams>, +} + +impl RepoStore { + pub fn open(db_path: &PathBuf) -> Result<Self> { + Ok(RepoStore { + db: BlockStore::open(db_path, Default::default())?, + }) + } + + pub fn open_ephemeral() -> Result<Self> { + Ok(RepoStore { + db: BlockStore::open_path(ipfs_sqlite_block_store::DbPath::Memory, Default::default())?, + }) + } + + pub fn get_ipld(&mut self, cid: &str) -> Result<Ipld> { + let ipld_cid = Cid::from_str(cid)?; + if let Some(b) = self.db.get_block(&ipld_cid)? { + let block: Block<DefaultParams> = Block::new(ipld_cid, b)?; + block.ipld() + } else { + Err(anyhow!("missing IPLD CID: {}", cid)) + } + } + + pub fn get_blob(&mut self, cid: &str) -> Result<Option<Vec<u8>>> { + let cid = Cid::from_str(cid)?; + Ok(self.db.get_block(&cid)?) + } + + /// Returns CID that was inserted + pub fn put_ipld(&mut self, record: &Ipld) -> Result<String> { + let block = Block::<DefaultParams>::encode(DagCborCodec, Code::Sha2_256, record)?; + let cid = block.cid().clone(); + self.db.put_block(block, None)?; + Ok(cid.to_string()) + } + + /// Returns CID that was inserted + pub fn put_blob(&mut self, data: Vec<u8>) -> Result<String> { + let block = Block::<DefaultParams>::encode(libipld::raw::RawCodec, Code::Sha2_256, &data)?; + let cid = block.cid().clone(); + self.db.put_block(block, None)?; + Ok(cid.to_string()) + } + + /// Quick alias lookup + pub fn get_root(&mut self, did: &str) -> Result<Option<String>> { + Ok(self + .db + .resolve(Cow::from(did.as_bytes()))? + .map(|cid| cid.to_string())) + } + + pub fn get_commit(&mut self, commit_cid: &str) -> Result<RepoCommit> { + // read records by CID: commit, root, meta + let commit_node: CommitNode = DagCborCodec.decode( + &self + .db + .get_block(&Cid::from_str(commit_cid)?)? + .ok_or(anyhow!("expected commit block in store"))?, + )?; + let root_node: RootNode = DagCborCodec.decode( + &self + .db + .get_block(&commit_node.root)? + .ok_or(anyhow!("expected root block in store"))?, + )?; + let metadata_node: MetadataNode = DagCborCodec.decode( + &self + .db + .get_block(&root_node.meta)? + .ok_or(anyhow!("expected metadata block in store"))?, + )?; + assert_eq!(metadata_node.datastore, "mst"); + assert_eq!(metadata_node.version, 1); + Ok(RepoCommit { + sig: commit_node.sig, + did: metadata_node.did, + prev: root_node.prev.map(|cid| cid.to_string()), + mst_cid: root_node.data.to_string(), + }) + } + + pub fn get_record_by_key(&mut self, commit_cid: &str, key: &str) -> Result<Option<Ipld>> { + let map = self.as_map(commit_cid)?; + if let Some(cid) = map.get(key) { + self.get_ipld(&cid.to_string()).map(|v| Some(v)) + } else { + Ok(None) + } + } + + pub fn write_root(&mut self, did: &str, mst_cid: &str, prev: Option<&str>) -> Result<String> { + unimplemented!() + } + + pub fn write_commit(&mut self, did: &str, root_cid: &str, sig: &str) -> Result<String> { + // TODO: also update alias to point to new commit + unimplemented!() + } + + pub fn write_map(&self, map: Result<BTreeMap<String, String>>) -> Result<String> { + unimplemented!() + } + + fn as_cid_map(&mut self, commit_cid: &str) -> Result<BTreeMap<String, Cid>> { + let commit = self.get_commit(commit_cid)?; + let mut cid_map: BTreeMap<String, Cid> = Default::default(); + let mst_cid = Cid::from_str(&commit.mst_cid)?; + collect_mst_keys(&mut self.db, &mst_cid, &mut cid_map)?; + Ok(cid_map) + } + + /// Returns all the keys for a directory, as a sorted vec of strings + pub fn as_map(&mut self, commit_cid: &str) -> Result<BTreeMap<String, String>> { + let cid_map = self.as_cid_map(commit_cid)?; + let ret_map: BTreeMap<String, String> = + BTreeMap::from_iter(cid_map.into_iter().map(|(k, v)| (k, v.to_string()))); + Ok(ret_map) + } + + /// Exports in CAR format to a Writer + /// + /// The "from" commit CID feature is not implemented. + pub fn write_car<W: std::io::Write>( + &mut self, + did: &str, + _from_commit_cid: Option<&str>, + out: &mut W, + ) -> Result<()> { + unimplemented!() + } +} diff --git a/adenosine-pds/tests/test_repro_mst.rs b/adenosine-pds/tests/test_repro_mst.rs index ad5fba1..9a23c03 100644 --- a/adenosine-pds/tests/test_repro_mst.rs +++ b/adenosine-pds/tests/test_repro_mst.rs @@ -1,4 +1,4 @@ -use adenosine_pds::repro_mst; +use adenosine_pds::mst::repro_mst; use std::path::PathBuf; use std::str::FromStr; |