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() + ] + ) + } + } }