From 0ce4a9da65129147e0c3cb7cb4507b97d5e26f62 Mon Sep 17 00:00:00 2001 From: nazeh Date: Tue, 20 Aug 2024 15:16:26 +0300 Subject: [PATCH 1/5] feat(common): impl serialize and deserialize for Timestamp --- pubky-common/src/timestamp.rs | 27 +++++++++++++++++++ .../src/database/tables/entries.rs | 7 ++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/pubky-common/src/timestamp.rs b/pubky-common/src/timestamp.rs index 4c546d5..174c7e3 100644 --- a/pubky-common/src/timestamp.rs +++ b/pubky-common/src/timestamp.rs @@ -1,5 +1,6 @@ //! Monotonic unix timestamp in microseconds +use serde::{Deserialize, Serialize}; use std::fmt::Display; use std::{ ops::{Add, Sub}, @@ -83,6 +84,12 @@ impl Timestamp { } } +impl Default for Timestamp { + fn default() -> Self { + Timestamp::now() + } +} + impl Display for Timestamp { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let bytes: [u8; 8] = self.into(); @@ -155,6 +162,26 @@ impl Sub for &Timestamp { } } +impl Serialize for Timestamp { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let bytes = self.to_bytes(); + bytes.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Timestamp { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let bytes: [u8; 8] = Deserialize::deserialize(deserializer)?; + Ok(Timestamp(u64::from_be_bytes(bytes))) + } +} + #[cfg(not(target_arch = "wasm32"))] /// Return the number of microseconds since [SystemTime::UNIX_EPOCH] fn system_time() -> u64 { diff --git a/pubky-homeserver/src/database/tables/entries.rs b/pubky-homeserver/src/database/tables/entries.rs index 22a3aa4..70dafe4 100644 --- a/pubky-homeserver/src/database/tables/entries.rs +++ b/pubky-homeserver/src/database/tables/entries.rs @@ -200,7 +200,7 @@ pub struct Entry { /// Encoding version version: usize, /// Modified at - timestamp: u64, + timestamp: Timestamp, content_hash: [u8; 32], content_length: usize, content_type: String, @@ -211,10 +211,7 @@ pub struct Entry { impl Entry { pub fn new() -> Self { - Self { - timestamp: Timestamp::now().into_inner(), - ..Default::default() - } + Default::default() } // === Setters === From b4c7fdad4545211bb0e6d4b8780a7a2ffc2f947f Mon Sep 17 00:00:00 2001 From: nazeh Date: Tue, 20 Aug 2024 21:53:01 +0300 Subject: [PATCH 2/5] feat(homeserver): add /events/ endpoint to list PUT/DELETE events --- pubky-homeserver/src/database.rs | 4 +- .../src/database/migrations/m0.rs | 4 +- pubky-homeserver/src/database/tables.rs | 9 ++- pubky-homeserver/src/database/tables/blobs.rs | 2 +- .../src/database/tables/entries.rs | 19 +++++- .../src/database/tables/events.rs | 54 +++++++++++++++ pubky-homeserver/src/routes.rs | 2 + pubky-homeserver/src/routes/feed.rs | 65 +++++++++++++++++++ pubky-homeserver/src/routes/public.rs | 4 +- 9 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 pubky-homeserver/src/database/tables/events.rs create mode 100644 pubky-homeserver/src/routes/feed.rs diff --git a/pubky-homeserver/src/database.rs b/pubky-homeserver/src/database.rs index f7174ee..4adc73d 100644 --- a/pubky-homeserver/src/database.rs +++ b/pubky-homeserver/src/database.rs @@ -9,6 +9,8 @@ pub mod tables; use tables::{Tables, TABLES_COUNT}; +pub const MAX_LIST_LIMIT: u16 = 100; + #[derive(Debug, Clone)] pub struct DB { pub(crate) env: Env, @@ -43,7 +45,7 @@ mod tests { .join(Timestamp::now().to_string()) .join("pubky"); - let mut db = DB::open(&storage).unwrap(); + let db = DB::open(&storage).unwrap(); let keypair = Keypair::random(); let path = "/pub/foo.txt"; diff --git a/pubky-homeserver/src/database/migrations/m0.rs b/pubky-homeserver/src/database/migrations/m0.rs index a690049..11c0e1a 100644 --- a/pubky-homeserver/src/database/migrations/m0.rs +++ b/pubky-homeserver/src/database/migrations/m0.rs @@ -1,6 +1,6 @@ use heed::{Env, RwTxn}; -use crate::database::tables::{blobs, entries, sessions, users}; +use crate::database::tables::{blobs, entries, events, sessions, users}; pub fn run(env: &Env, wtxn: &mut RwTxn) -> anyhow::Result<()> { let _: users::UsersTable = env.create_database(wtxn, Some(users::USERS_TABLE))?; @@ -11,5 +11,7 @@ pub fn run(env: &Env, wtxn: &mut RwTxn) -> anyhow::Result<()> { let _: entries::EntriesTable = env.create_database(wtxn, Some(entries::ENTRIES_TABLE))?; + let _: events::EventsTable = env.create_database(wtxn, Some(events::EVENTS_TABLE))?; + Ok(()) } diff --git a/pubky-homeserver/src/database/tables.rs b/pubky-homeserver/src/database/tables.rs index a019fbe..81a87da 100644 --- a/pubky-homeserver/src/database/tables.rs +++ b/pubky-homeserver/src/database/tables.rs @@ -1,5 +1,6 @@ pub mod blobs; pub mod entries; +pub mod events; pub mod sessions; pub mod users; @@ -8,12 +9,15 @@ use heed::{Env, RwTxn}; use blobs::{BlobsTable, BLOBS_TABLE}; use entries::{EntriesTable, ENTRIES_TABLE}; -pub const TABLES_COUNT: u32 = 4; +use self::events::{EventsTable, EVENTS_TABLE}; + +pub const TABLES_COUNT: u32 = 5; #[derive(Debug, Clone)] pub struct Tables { pub blobs: BlobsTable, pub entries: EntriesTable, + pub events: EventsTable, } impl Tables { @@ -25,6 +29,9 @@ impl Tables { entries: env .open_database(wtxn, Some(ENTRIES_TABLE))? .expect("Entries table already created"), + events: env + .open_database(wtxn, Some(EVENTS_TABLE))? + .expect("Events table already created"), }) } } diff --git a/pubky-homeserver/src/database/tables/blobs.rs b/pubky-homeserver/src/database/tables/blobs.rs index 1a02a09..25f57c0 100644 --- a/pubky-homeserver/src/database/tables/blobs.rs +++ b/pubky-homeserver/src/database/tables/blobs.rs @@ -12,7 +12,7 @@ pub const BLOBS_TABLE: &str = "blobs"; impl DB { pub fn get_blob( - &mut self, + &self, public_key: &PublicKey, path: &str, ) -> anyhow::Result> { diff --git a/pubky-homeserver/src/database/tables/entries.rs b/pubky-homeserver/src/database/tables/entries.rs index 70dafe4..d88b116 100644 --- a/pubky-homeserver/src/database/tables/entries.rs +++ b/pubky-homeserver/src/database/tables/entries.rs @@ -13,15 +13,15 @@ use pubky_common::{ timestamp::Timestamp, }; -use crate::database::DB; +use crate::database::{DB, MAX_LIST_LIMIT}; + +use super::events::Event; /// full_path(pubky/*path) => Entry. pub type EntriesTable = Database; pub const ENTRIES_TABLE: &str = "entries"; -const MAX_LIST_LIMIT: u16 = 100; - impl DB { pub fn put_entry( &mut self, @@ -56,6 +56,19 @@ impl DB { .entries .put(&mut wtxn, &key, &entry.serialize())?; + if path.starts_with("pub/") { + let url = format!("pubky://{key}"); + let event = Event::put(&url); + let value = event.serialize(); + + let key = entry.timestamp.to_string(); + + self.tables.events.put(&mut wtxn, &key, &value)?; + + // TODO: delete older events. + // TODO: move to events.rs + } + wtxn.commit()?; Ok(()) diff --git a/pubky-homeserver/src/database/tables/events.rs b/pubky-homeserver/src/database/tables/events.rs new file mode 100644 index 0000000..18173dc --- /dev/null +++ b/pubky-homeserver/src/database/tables/events.rs @@ -0,0 +1,54 @@ +//! Server events (Put and Delete entries) +//! +//! Useful as a realtime sync with Indexers until +//! we implement more self-authenticated merkle data. + +use heed::{ + types::{Bytes, Str}, + Database, +}; +use postcard::{from_bytes, to_allocvec}; +use serde::{Deserialize, Serialize}; + +/// Event [Timestamp] base32 => Encoded event. +pub type EventsTable = Database; + +pub const EVENTS_TABLE: &str = "events"; + +#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)] +pub enum Event { + Put(String), + Delete(String), +} + +impl Event { + pub fn put(url: &str) -> Self { + Self::Put(url.to_string()) + } + + pub fn serialize(&self) -> Vec { + to_allocvec(self).expect("Session::serialize") + } + + pub fn deserialize(bytes: &[u8]) -> core::result::Result { + if bytes[0] > 1 { + panic!("Unknown Event version"); + } + + from_bytes(bytes) + } + + pub fn url(&self) -> &str { + match self { + Event::Put(url) => url, + Event::Delete(url) => url, + } + } + + pub fn operation(&self) -> &str { + match self { + Event::Put(_) => "PUT", + Event::Delete(_) => "DEL", + } + } +} diff --git a/pubky-homeserver/src/routes.rs b/pubky-homeserver/src/routes.rs index 35615fd..163baec 100644 --- a/pubky-homeserver/src/routes.rs +++ b/pubky-homeserver/src/routes.rs @@ -11,6 +11,7 @@ use crate::server::AppState; use self::pkarr::pkarr_router; mod auth; +mod feed; mod pkarr; mod public; mod root; @@ -25,6 +26,7 @@ fn base(state: AppState) -> Router { .route("/:pubky/*path", put(public::put)) .route("/:pubky/*path", get(public::get)) .route("/:pubky/*path", delete(public::delete)) + .route("/events/", get(feed::feed)) .layer(CookieManagerLayer::new()) // TODO: revisit if we enable streaming big payloads // TODO: maybe add to a separate router (drive router?). diff --git a/pubky-homeserver/src/routes/feed.rs b/pubky-homeserver/src/routes/feed.rs new file mode 100644 index 0000000..734d239 --- /dev/null +++ b/pubky-homeserver/src/routes/feed.rs @@ -0,0 +1,65 @@ +use std::collections::HashMap; + +use axum::{ + body::Body, + extract::{Query, State}, + http::{header, Response, StatusCode}, + response::IntoResponse, +}; + +use crate::{ + database::{tables::events::Event, MAX_LIST_LIMIT}, + error::Result, + server::AppState, +}; + +pub async fn feed( + State(state): State, + Query(params): Query>, +) -> Result { + let txn = state.db.env.read_txn()?; + + let limit = params + .get("limit") + .and_then(|l| l.parse::().ok()) + .unwrap_or(MAX_LIST_LIMIT) + .min(MAX_LIST_LIMIT); + + let mut cursor = params + .get("cursor") + .map(|c| c.as_str()) + .unwrap_or("0000000000000"); + + if cursor.len() < 13 { + cursor = "0000000000000" + } + + let mut result: Vec = vec![]; + let mut next_cursor = "".to_string(); + + for _ in 0..limit { + match state.db.tables.events.get_greater_than(&txn, cursor)? { + Some((timestamp, event_bytes)) => { + let event = Event::deserialize(event_bytes)?; + + let line = format!("{} {}", event.operation(), event.url()); + next_cursor = timestamp.to_string(); + + result.push(line); + } + None => break, + }; + } + + if !result.is_empty() { + result.push(format!("cursor: {next_cursor}")) + } + + txn.commit()?; + + Ok(Response::builder() + .status(StatusCode::OK) + .header(header::CONTENT_TYPE, "text/plain") + .body(Body::from(result.join("\n"))) + .unwrap()) +} diff --git a/pubky-homeserver/src/routes/public.rs b/pubky-homeserver/src/routes/public.rs index 4dc1bf6..cdfb0a9 100644 --- a/pubky-homeserver/src/routes/public.rs +++ b/pubky-homeserver/src/routes/public.rs @@ -62,7 +62,7 @@ pub async fn put( } pub async fn get( - State(mut state): State, + State(state): State, pubky: Pubky, path: EntryPath, Query(params): Query>, @@ -96,7 +96,7 @@ pub async fn get( return Ok(Response::builder() .status(StatusCode::OK) - .header(header::CONTENT_TYPE, "application/json") + .header(header::CONTENT_TYPE, "text/plain") .body(Body::from(vec.join("\n"))) .unwrap()); } From 51e26257530c6bb10e967a320b87fd13bf9a6eca Mon Sep 17 00:00:00 2001 From: nazeh Date: Tue, 20 Aug 2024 21:57:13 +0300 Subject: [PATCH 3/5] feat(homeserver): add delete events when deleting and item --- pubky-homeserver/src/database/tables/entries.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pubky-homeserver/src/database/tables/entries.rs b/pubky-homeserver/src/database/tables/entries.rs index d88b116..e515941 100644 --- a/pubky-homeserver/src/database/tables/entries.rs +++ b/pubky-homeserver/src/database/tables/entries.rs @@ -87,6 +87,21 @@ impl DB { let deleted_entry = self.tables.entries.delete(&mut wtxn, &key)?; + // create DELETE event + if path.starts_with("pub/") { + let url = format!("pubky://{key}"); + + let event = Event::put(&url); + let value = event.serialize(); + + let key = entry.timestamp.to_string(); + + self.tables.events.put(&mut wtxn, &key, &value)?; + + // TODO: delete older events. + // TODO: move to events.rs + } + deleted_entry & deleted_blobs } else { false From 7257e2fee7e9d966ca3b7fbc11a6c6506c49de1b Mon Sep 17 00:00:00 2001 From: nazeh Date: Wed, 21 Aug 2024 11:22:24 +0300 Subject: [PATCH 4/5] fix(homeserver): /events/ iterator and write unit test in pubky --- .../src/database/tables/entries.rs | 4 +- .../src/database/tables/events.rs | 4 + pubky-homeserver/src/routes/feed.rs | 10 +- pubky/src/shared/public.rs | 106 +++++++++++++++++- 4 files changed, 119 insertions(+), 5 deletions(-) diff --git a/pubky-homeserver/src/database/tables/entries.rs b/pubky-homeserver/src/database/tables/entries.rs index e515941..1b72274 100644 --- a/pubky-homeserver/src/database/tables/entries.rs +++ b/pubky-homeserver/src/database/tables/entries.rs @@ -91,10 +91,10 @@ impl DB { if path.starts_with("pub/") { let url = format!("pubky://{key}"); - let event = Event::put(&url); + let event = Event::delete(&url); let value = event.serialize(); - let key = entry.timestamp.to_string(); + let key = Timestamp::now().to_string(); self.tables.events.put(&mut wtxn, &key, &value)?; diff --git a/pubky-homeserver/src/database/tables/events.rs b/pubky-homeserver/src/database/tables/events.rs index 18173dc..cf82e18 100644 --- a/pubky-homeserver/src/database/tables/events.rs +++ b/pubky-homeserver/src/database/tables/events.rs @@ -26,6 +26,10 @@ impl Event { Self::Put(url.to_string()) } + pub fn delete(url: &str) -> Self { + Self::Delete(url.to_string()) + } + pub fn serialize(&self) -> Vec { to_allocvec(self).expect("Session::serialize") } diff --git a/pubky-homeserver/src/routes/feed.rs b/pubky-homeserver/src/routes/feed.rs index 734d239..bd426f3 100644 --- a/pubky-homeserver/src/routes/feed.rs +++ b/pubky-homeserver/src/routes/feed.rs @@ -30,15 +30,21 @@ pub async fn feed( .map(|c| c.as_str()) .unwrap_or("0000000000000"); + // Guard against bad cursor if cursor.len() < 13 { cursor = "0000000000000" } let mut result: Vec = vec![]; - let mut next_cursor = "".to_string(); + let mut next_cursor = cursor.to_string(); for _ in 0..limit { - match state.db.tables.events.get_greater_than(&txn, cursor)? { + match state + .db + .tables + .events + .get_greater_than(&txn, &next_cursor)? + { Some((timestamp, event_bytes)) => { let event = Event::deserialize(event_bytes)?; diff --git a/pubky/src/shared/public.rs b/pubky/src/shared/public.rs index 7dbb18e..1fc7e4f 100644 --- a/pubky/src/shared/public.rs +++ b/pubky/src/shared/public.rs @@ -101,7 +101,7 @@ mod tests { use pkarr::{mainline::Testnet, Keypair}; use pubky_homeserver::Homeserver; - use reqwest::StatusCode; + use reqwest::{Method, StatusCode}; #[tokio::test] async fn put_get_delete() { @@ -627,4 +627,108 @@ mod tests { ); } } + + #[tokio::test] + async fn list_events() { + let testnet = Testnet::new(10); + let server = Homeserver::start_test(&testnet).await.unwrap(); + + let client = PubkyClient::test(&testnet); + + let keypair = Keypair::random(); + + client.signup(&keypair, &server.public_key()).await.unwrap(); + + let urls = vec![ + format!("pubky://{}/pub/a.com/a.txt", keypair.public_key()), + format!("pubky://{}/pub/example.com/a.txt", keypair.public_key()), + format!("pubky://{}/pub/example.com/b.txt", keypair.public_key()), + format!("pubky://{}/pub/example.com/c.txt", keypair.public_key()), + format!("pubky://{}/pub/example.com/d.txt", keypair.public_key()), + format!("pubky://{}/pub/example.con/d.txt", keypair.public_key()), + format!("pubky://{}/pub/example.con", keypair.public_key()), + format!("pubky://{}/pub/file", keypair.public_key()), + format!("pubky://{}/pub/file2", keypair.public_key()), + format!("pubky://{}/pub/z.com/a.txt", keypair.public_key()), + ]; + + for url in urls { + client.put(url.as_str(), &[0]).await.unwrap(); + client.delete(url.as_str()).await.unwrap(); + } + + let feed_url = format!("http://localhost:{}/events/", server.port()); + let feed_url = feed_url.as_str(); + + let client = PubkyClient::test(&testnet); + + let cursor; + + { + let response = client + .request( + Method::GET, + format!("{feed_url}?limit=10").as_str().try_into().unwrap(), + ) + .send() + .await + .unwrap(); + + let text = response.text().await.unwrap(); + let lines = text.split('\n').collect::>(); + + cursor = lines.last().unwrap().split(" ").last().unwrap().to_string(); + + assert_eq!( + lines, + vec![ + format!("PUT pubky://{}/pub/a.com/a.txt", keypair.public_key()), + format!("DEL pubky://{}/pub/a.com/a.txt", keypair.public_key()), + format!("PUT pubky://{}/pub/example.com/a.txt", keypair.public_key()), + format!("DEL pubky://{}/pub/example.com/a.txt", keypair.public_key()), + format!("PUT pubky://{}/pub/example.com/b.txt", keypair.public_key()), + format!("DEL pubky://{}/pub/example.com/b.txt", keypair.public_key()), + format!("PUT pubky://{}/pub/example.com/c.txt", keypair.public_key()), + format!("DEL pubky://{}/pub/example.com/c.txt", keypair.public_key()), + format!("PUT pubky://{}/pub/example.com/d.txt", keypair.public_key()), + format!("DEL pubky://{}/pub/example.com/d.txt", keypair.public_key()), + format!("cursor: {cursor}",) + ] + ); + } + + { + let response = client + .request( + Method::GET, + format!("{feed_url}?limit=10&cursor={cursor}") + .as_str() + .try_into() + .unwrap(), + ) + .send() + .await + .unwrap(); + + let text = response.text().await.unwrap(); + let lines = text.split('\n').collect::>(); + + assert_eq!( + lines, + vec![ + format!("PUT pubky://{}/pub/example.con/d.txt", keypair.public_key()), + format!("DEL pubky://{}/pub/example.con/d.txt", keypair.public_key()), + format!("PUT pubky://{}/pub/example.con", keypair.public_key()), + format!("DEL pubky://{}/pub/example.con", keypair.public_key()), + format!("PUT pubky://{}/pub/file", keypair.public_key()), + format!("DEL pubky://{}/pub/file", keypair.public_key()), + format!("PUT pubky://{}/pub/file2", keypair.public_key()), + format!("DEL pubky://{}/pub/file2", keypair.public_key()), + format!("PUT pubky://{}/pub/z.com/a.txt", keypair.public_key()), + format!("DEL pubky://{}/pub/z.com/a.txt", keypair.public_key()), + lines.last().unwrap().to_string() + ] + ) + } + } } From 01d0f3bce1752ad72e823270f0f522c8f005c8b1 Mon Sep 17 00:00:00 2001 From: nazeh Date: Wed, 21 Aug 2024 11:28:03 +0300 Subject: [PATCH 5/5] refactor(tests): redundant keypair.public_key() calls --- pubky/src/shared/public.rs | 231 +++++++++++++++++-------------------- 1 file changed, 108 insertions(+), 123 deletions(-) diff --git a/pubky/src/shared/public.rs b/pubky/src/shared/public.rs index 1fc7e4f..2f9e73b 100644 --- a/pubky/src/shared/public.rs +++ b/pubky/src/shared/public.rs @@ -207,25 +207,24 @@ mod tests { client.signup(&keypair, &server.public_key()).await.unwrap(); + let pubky = keypair.public_key(); + let urls = vec![ - format!("pubky://{}/pub/a.wrong/a.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/a.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/b.txt", keypair.public_key()), - format!( - "pubky://{}/pub/example.com/cc-nested/z.txt", - keypair.public_key() - ), - format!("pubky://{}/pub/example.wrong/a.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/c.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/d.txt", keypair.public_key()), - format!("pubky://{}/pub/z.wrong/a.txt", keypair.public_key()), + format!("pubky://{pubky}/pub/a.wrong/a.txt"), + format!("pubky://{pubky}/pub/example.com/a.txt"), + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"), + format!("pubky://{pubky}/pub/example.wrong/a.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), + format!("pubky://{pubky}/pub/example.com/d.txt"), + format!("pubky://{pubky}/pub/z.wrong/a.txt"), ]; for url in urls { client.put(url.as_str(), &[0]).await.unwrap(); } - let url = format!("pubky://{}/pub/example.com/extra", keypair.public_key()); + let url = format!("pubky://{pubky}/pub/example.com/extra"); let url = url.as_str(); { @@ -234,14 +233,11 @@ mod tests { assert_eq!( list, vec![ - format!("pubky://{}/pub/example.com/a.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/b.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/c.txt", keypair.public_key()), - format!( - "pubky://{}/pub/example.com/cc-nested/z.txt", - keypair.public_key() - ), - format!("pubky://{}/pub/example.com/d.txt", keypair.public_key()), + format!("pubky://{pubky}/pub/example.com/a.txt"), + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), + format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"), + format!("pubky://{pubky}/pub/example.com/d.txt"), ], "normal list with no limit or cursor" ); @@ -253,8 +249,8 @@ mod tests { assert_eq!( list, vec![ - format!("pubky://{}/pub/example.com/a.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/b.txt", keypair.public_key()), + format!("pubky://{pubky}/pub/example.com/a.txt"), + format!("pubky://{pubky}/pub/example.com/b.txt"), ], "normal list with limit but no cursor" ); @@ -273,8 +269,8 @@ mod tests { assert_eq!( list, vec![ - format!("pubky://{}/pub/example.com/b.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/c.txt", keypair.public_key()), + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), ], "normal list with limit and a file cursor" ); @@ -293,11 +289,8 @@ mod tests { assert_eq!( list, vec![ - format!( - "pubky://{}/pub/example.com/cc-nested/z.txt", - keypair.public_key() - ), - format!("pubky://{}/pub/example.com/d.txt", keypair.public_key()), + format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"), + format!("pubky://{pubky}/pub/example.com/d.txt"), ], "normal list with limit and a directory cursor" ); @@ -308,10 +301,7 @@ mod tests { .list(url) .unwrap() .limit(2) - .cursor(&format!( - "pubky://{}/pub/example.com/a.txt", - keypair.public_key() - )) + .cursor(&format!("pubky://{pubky}/pub/example.com/a.txt")) .send() .await .unwrap(); @@ -319,8 +309,8 @@ mod tests { assert_eq!( list, vec![ - format!("pubky://{}/pub/example.com/b.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/c.txt", keypair.public_key()), + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), ], "normal list with limit and a full url cursor" ); @@ -339,8 +329,8 @@ mod tests { assert_eq!( list, vec![ - format!("pubky://{}/pub/example.com/b.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/c.txt", keypair.public_key()), + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), ], "normal list with limit and a leading / cursor" ); @@ -358,14 +348,11 @@ mod tests { assert_eq!( list, vec![ - format!("pubky://{}/pub/example.com/d.txt", keypair.public_key()), - format!( - "pubky://{}/pub/example.com/cc-nested/z.txt", - keypair.public_key() - ), - format!("pubky://{}/pub/example.com/c.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/b.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/a.txt", keypair.public_key()), + format!("pubky://{pubky}/pub/example.com/d.txt"), + format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/a.txt"), ], "reverse list with no limit or cursor" ); @@ -384,11 +371,8 @@ mod tests { assert_eq!( list, vec![ - format!("pubky://{}/pub/example.com/d.txt", keypair.public_key()), - format!( - "pubky://{}/pub/example.com/cc-nested/z.txt", - keypair.public_key() - ), + format!("pubky://{pubky}/pub/example.com/d.txt"), + format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"), ], "reverse list with limit but no cursor" ); @@ -408,11 +392,8 @@ mod tests { assert_eq!( list, vec![ - format!( - "pubky://{}/pub/example.com/cc-nested/z.txt", - keypair.public_key() - ), - format!("pubky://{}/pub/example.com/c.txt", keypair.public_key()), + format!("pubky://{pubky}/pub/example.com/cc-nested/z.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), ], "reverse list with limit and cursor" ); @@ -430,24 +411,26 @@ mod tests { client.signup(&keypair, &server.public_key()).await.unwrap(); + let pubky = keypair.public_key(); + let urls = vec![ - format!("pubky://{}/pub/a.com/a.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/a.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/b.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/c.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/d.txt", keypair.public_key()), - format!("pubky://{}/pub/example.con/d.txt", keypair.public_key()), - format!("pubky://{}/pub/example.con", keypair.public_key()), - format!("pubky://{}/pub/file", keypair.public_key()), - format!("pubky://{}/pub/file2", keypair.public_key()), - format!("pubky://{}/pub/z.com/a.txt", keypair.public_key()), + format!("pubky://{pubky}/pub/a.com/a.txt"), + format!("pubky://{pubky}/pub/example.com/a.txt"), + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), + format!("pubky://{pubky}/pub/example.com/d.txt"), + format!("pubky://{pubky}/pub/example.con/d.txt"), + format!("pubky://{pubky}/pub/example.con"), + format!("pubky://{pubky}/pub/file"), + format!("pubky://{pubky}/pub/file2"), + format!("pubky://{pubky}/pub/z.com/a.txt"), ]; for url in urls { client.put(url.as_str(), &[0]).await.unwrap(); } - let url = format!("pubky://{}/pub/", keypair.public_key()); + let url = format!("pubky://{pubky}/pub/"); let url = url.as_str(); { @@ -462,13 +445,13 @@ mod tests { assert_eq!( list, vec![ - format!("pubky://{}/pub/a.com/", keypair.public_key()), - format!("pubky://{}/pub/example.com/", keypair.public_key()), - format!("pubky://{}/pub/example.con", keypair.public_key()), - format!("pubky://{}/pub/example.con/", keypair.public_key()), - format!("pubky://{}/pub/file", keypair.public_key()), - format!("pubky://{}/pub/file2", keypair.public_key()), - format!("pubky://{}/pub/z.com/", keypair.public_key()), + format!("pubky://{pubky}/pub/a.com/"), + format!("pubky://{pubky}/pub/example.com/"), + format!("pubky://{pubky}/pub/example.con"), + format!("pubky://{pubky}/pub/example.con/"), + format!("pubky://{pubky}/pub/file"), + format!("pubky://{pubky}/pub/file2"), + format!("pubky://{pubky}/pub/z.com/"), ], "normal list shallow" ); @@ -487,8 +470,8 @@ mod tests { assert_eq!( list, vec![ - format!("pubky://{}/pub/a.com/", keypair.public_key()), - format!("pubky://{}/pub/example.com/", keypair.public_key()), + format!("pubky://{pubky}/pub/a.com/"), + format!("pubky://{pubky}/pub/example.com/"), ], "normal list shallow with limit but no cursor" ); @@ -508,8 +491,8 @@ mod tests { assert_eq!( list, vec![ - format!("pubky://{}/pub/example.com/", keypair.public_key()), - format!("pubky://{}/pub/example.con", keypair.public_key()), + format!("pubky://{pubky}/pub/example.com/"), + format!("pubky://{pubky}/pub/example.con"), ], "normal list shallow with limit and a file cursor" ); @@ -529,9 +512,9 @@ mod tests { assert_eq!( list, vec![ - format!("pubky://{}/pub/example.con", keypair.public_key()), - format!("pubky://{}/pub/example.con/", keypair.public_key()), - format!("pubky://{}/pub/file", keypair.public_key()), + format!("pubky://{pubky}/pub/example.con"), + format!("pubky://{pubky}/pub/example.con/"), + format!("pubky://{pubky}/pub/file"), ], "normal list shallow with limit and a directory cursor" ); @@ -550,13 +533,13 @@ mod tests { assert_eq!( list, vec![ - format!("pubky://{}/pub/z.com/", keypair.public_key()), - format!("pubky://{}/pub/file2", keypair.public_key()), - format!("pubky://{}/pub/file", keypair.public_key()), - format!("pubky://{}/pub/example.con/", keypair.public_key()), - format!("pubky://{}/pub/example.con", keypair.public_key()), - format!("pubky://{}/pub/example.com/", keypair.public_key()), - format!("pubky://{}/pub/a.com/", keypair.public_key()), + format!("pubky://{pubky}/pub/z.com/"), + format!("pubky://{pubky}/pub/file2"), + format!("pubky://{pubky}/pub/file"), + format!("pubky://{pubky}/pub/example.con/"), + format!("pubky://{pubky}/pub/example.con"), + format!("pubky://{pubky}/pub/example.com/"), + format!("pubky://{pubky}/pub/a.com/"), ], "reverse list shallow" ); @@ -576,8 +559,8 @@ mod tests { assert_eq!( list, vec![ - format!("pubky://{}/pub/z.com/", keypair.public_key()), - format!("pubky://{}/pub/file2", keypair.public_key()), + format!("pubky://{pubky}/pub/z.com/"), + format!("pubky://{pubky}/pub/file2"), ], "reverse list shallow with limit but no cursor" ); @@ -598,8 +581,8 @@ mod tests { assert_eq!( list, vec![ - format!("pubky://{}/pub/file", keypair.public_key()), - format!("pubky://{}/pub/example.con/", keypair.public_key()), + format!("pubky://{pubky}/pub/file"), + format!("pubky://{pubky}/pub/example.con/"), ], "reverse list shallow with limit and a file cursor" ); @@ -620,8 +603,8 @@ mod tests { assert_eq!( list, vec![ - format!("pubky://{}/pub/example.con", keypair.public_key()), - format!("pubky://{}/pub/example.com/", keypair.public_key()), + format!("pubky://{pubky}/pub/example.con"), + format!("pubky://{pubky}/pub/example.com/"), ], "reverse list shallow with limit and a directory cursor" ); @@ -639,17 +622,19 @@ mod tests { client.signup(&keypair, &server.public_key()).await.unwrap(); + let pubky = keypair.public_key(); + let urls = vec![ - format!("pubky://{}/pub/a.com/a.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/a.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/b.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/c.txt", keypair.public_key()), - format!("pubky://{}/pub/example.com/d.txt", keypair.public_key()), - format!("pubky://{}/pub/example.con/d.txt", keypair.public_key()), - format!("pubky://{}/pub/example.con", keypair.public_key()), - format!("pubky://{}/pub/file", keypair.public_key()), - format!("pubky://{}/pub/file2", keypair.public_key()), - format!("pubky://{}/pub/z.com/a.txt", keypair.public_key()), + format!("pubky://{pubky}/pub/a.com/a.txt"), + format!("pubky://{pubky}/pub/example.com/a.txt"), + format!("pubky://{pubky}/pub/example.com/b.txt"), + format!("pubky://{pubky}/pub/example.com/c.txt"), + format!("pubky://{pubky}/pub/example.com/d.txt"), + format!("pubky://{pubky}/pub/example.con/d.txt"), + format!("pubky://{pubky}/pub/example.con"), + format!("pubky://{pubky}/pub/file"), + format!("pubky://{pubky}/pub/file2"), + format!("pubky://{pubky}/pub/z.com/a.txt"), ]; for url in urls { @@ -682,16 +667,16 @@ mod tests { assert_eq!( lines, vec![ - format!("PUT pubky://{}/pub/a.com/a.txt", keypair.public_key()), - format!("DEL pubky://{}/pub/a.com/a.txt", keypair.public_key()), - format!("PUT pubky://{}/pub/example.com/a.txt", keypair.public_key()), - format!("DEL pubky://{}/pub/example.com/a.txt", keypair.public_key()), - format!("PUT pubky://{}/pub/example.com/b.txt", keypair.public_key()), - format!("DEL pubky://{}/pub/example.com/b.txt", keypair.public_key()), - format!("PUT pubky://{}/pub/example.com/c.txt", keypair.public_key()), - format!("DEL pubky://{}/pub/example.com/c.txt", keypair.public_key()), - format!("PUT pubky://{}/pub/example.com/d.txt", keypair.public_key()), - format!("DEL pubky://{}/pub/example.com/d.txt", keypair.public_key()), + format!("PUT pubky://{pubky}/pub/a.com/a.txt"), + format!("DEL pubky://{pubky}/pub/a.com/a.txt"), + format!("PUT pubky://{pubky}/pub/example.com/a.txt"), + format!("DEL pubky://{pubky}/pub/example.com/a.txt"), + format!("PUT pubky://{pubky}/pub/example.com/b.txt"), + format!("DEL pubky://{pubky}/pub/example.com/b.txt"), + format!("PUT pubky://{pubky}/pub/example.com/c.txt"), + format!("DEL pubky://{pubky}/pub/example.com/c.txt"), + format!("PUT pubky://{pubky}/pub/example.com/d.txt"), + format!("DEL pubky://{pubky}/pub/example.com/d.txt"), format!("cursor: {cursor}",) ] ); @@ -716,16 +701,16 @@ mod tests { assert_eq!( lines, vec![ - format!("PUT pubky://{}/pub/example.con/d.txt", keypair.public_key()), - format!("DEL pubky://{}/pub/example.con/d.txt", keypair.public_key()), - format!("PUT pubky://{}/pub/example.con", keypair.public_key()), - format!("DEL pubky://{}/pub/example.con", keypair.public_key()), - format!("PUT pubky://{}/pub/file", keypair.public_key()), - format!("DEL pubky://{}/pub/file", keypair.public_key()), - format!("PUT pubky://{}/pub/file2", keypair.public_key()), - format!("DEL pubky://{}/pub/file2", keypair.public_key()), - format!("PUT pubky://{}/pub/z.com/a.txt", keypair.public_key()), - format!("DEL pubky://{}/pub/z.com/a.txt", keypair.public_key()), + format!("PUT pubky://{pubky}/pub/example.con/d.txt"), + format!("DEL pubky://{pubky}/pub/example.con/d.txt"), + format!("PUT pubky://{pubky}/pub/example.con"), + format!("DEL pubky://{pubky}/pub/example.con"), + format!("PUT pubky://{pubky}/pub/file"), + format!("DEL pubky://{pubky}/pub/file"), + format!("PUT pubky://{pubky}/pub/file2"), + format!("DEL pubky://{pubky}/pub/file2"), + format!("PUT pubky://{pubky}/pub/z.com/a.txt"), + format!("DEL pubky://{pubky}/pub/z.com/a.txt"), lines.last().unwrap().to_string() ] )