mirror of
https://github.com/aljazceru/pubky-core.git
synced 2026-01-01 13:24:20 +01:00
@@ -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<u64> for &Timestamp {
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Timestamp {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let bytes = self.to_bytes();
|
||||
bytes.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Timestamp {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
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 {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Option<bytes::Bytes>> {
|
||||
|
||||
@@ -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<Str, Bytes>;
|
||||
|
||||
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(())
|
||||
@@ -74,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::delete(&url);
|
||||
let value = event.serialize();
|
||||
|
||||
let key = Timestamp::now().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
|
||||
@@ -198,7 +226,7 @@ pub struct Entry {
|
||||
/// Encoding version
|
||||
version: usize,
|
||||
/// Modified at
|
||||
timestamp: u64,
|
||||
timestamp: Timestamp,
|
||||
content_hash: [u8; 32],
|
||||
content_length: usize,
|
||||
content_type: String,
|
||||
@@ -209,10 +237,7 @@ pub struct Entry {
|
||||
|
||||
impl Entry {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
timestamp: Timestamp::now().into_inner(),
|
||||
..Default::default()
|
||||
}
|
||||
Default::default()
|
||||
}
|
||||
|
||||
// === Setters ===
|
||||
|
||||
58
pubky-homeserver/src/database/tables/events.rs
Normal file
58
pubky-homeserver/src/database/tables/events.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
//! 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<Str, Bytes>;
|
||||
|
||||
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 delete(url: &str) -> Self {
|
||||
Self::Delete(url.to_string())
|
||||
}
|
||||
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
to_allocvec(self).expect("Session::serialize")
|
||||
}
|
||||
|
||||
pub fn deserialize(bytes: &[u8]) -> core::result::Result<Self, postcard::Error> {
|
||||
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",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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?).
|
||||
|
||||
71
pubky-homeserver/src/routes/feed.rs
Normal file
71
pubky-homeserver/src/routes/feed.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
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<AppState>,
|
||||
Query(params): Query<HashMap<String, String>>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
let txn = state.db.env.read_txn()?;
|
||||
|
||||
let limit = params
|
||||
.get("limit")
|
||||
.and_then(|l| l.parse::<u16>().ok())
|
||||
.unwrap_or(MAX_LIST_LIMIT)
|
||||
.min(MAX_LIST_LIMIT);
|
||||
|
||||
let mut cursor = params
|
||||
.get("cursor")
|
||||
.map(|c| c.as_str())
|
||||
.unwrap_or("0000000000000");
|
||||
|
||||
// Guard against bad cursor
|
||||
if cursor.len() < 13 {
|
||||
cursor = "0000000000000"
|
||||
}
|
||||
|
||||
let mut result: Vec<String> = vec![];
|
||||
let mut next_cursor = cursor.to_string();
|
||||
|
||||
for _ in 0..limit {
|
||||
match state
|
||||
.db
|
||||
.tables
|
||||
.events
|
||||
.get_greater_than(&txn, &next_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())
|
||||
}
|
||||
@@ -62,7 +62,7 @@ pub async fn put(
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
State(mut state): State<AppState>,
|
||||
State(state): State<AppState>,
|
||||
pubky: Pubky,
|
||||
path: EntryPath,
|
||||
Query(params): Query<HashMap<String, String>>,
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -100,7 +100,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() {
|
||||
@@ -206,25 +206,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();
|
||||
|
||||
{
|
||||
@@ -233,14 +232,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"
|
||||
);
|
||||
@@ -252,8 +248,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"
|
||||
);
|
||||
@@ -272,8 +268,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"
|
||||
);
|
||||
@@ -292,11 +288,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"
|
||||
);
|
||||
@@ -307,10 +300,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();
|
||||
@@ -318,8 +308,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"
|
||||
);
|
||||
@@ -338,8 +328,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"
|
||||
);
|
||||
@@ -357,14 +347,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"
|
||||
);
|
||||
@@ -383,11 +370,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"
|
||||
);
|
||||
@@ -407,11 +391,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"
|
||||
);
|
||||
@@ -429,24 +410,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();
|
||||
|
||||
{
|
||||
@@ -461,13 +444,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"
|
||||
);
|
||||
@@ -486,8 +469,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"
|
||||
);
|
||||
@@ -507,8 +490,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"
|
||||
);
|
||||
@@ -528,9 +511,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"
|
||||
);
|
||||
@@ -549,13 +532,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"
|
||||
);
|
||||
@@ -575,8 +558,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"
|
||||
);
|
||||
@@ -597,8 +580,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"
|
||||
);
|
||||
@@ -619,11 +602,117 @@ 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[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 pubky = keypair.public_key();
|
||||
|
||||
let urls = vec![
|
||||
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();
|
||||
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::<Vec<_>>();
|
||||
|
||||
cursor = lines.last().unwrap().split(" ").last().unwrap().to_string();
|
||||
|
||||
assert_eq!(
|
||||
lines,
|
||||
vec![
|
||||
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}",)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
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::<Vec<_>>();
|
||||
|
||||
assert_eq!(
|
||||
lines,
|
||||
vec![
|
||||
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()
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user