Skip to content

Commit

Permalink
fix(homeserver): add list limits in config
Browse files Browse the repository at this point in the history
  • Loading branch information
Nuhvi committed Sep 27, 2024
1 parent f029171 commit d129e4b
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 57 deletions.
29 changes: 25 additions & 4 deletions pubky-homeserver/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ use pubky_common::timestamp::Timestamp;
const DEFAULT_HOMESERVER_PORT: u16 = 6287;
const DEFAULT_STORAGE_DIR: &str = "pubky";

pub const DEFAULT_LIST_LIMIT: u16 = 100;
pub const DEFAULT_MAX_LIST_LIMIT: u16 = 1000;

/// Server configuration
#[derive(Serialize, Deserialize, Clone)]
pub struct Config {
Expand All @@ -30,6 +33,14 @@ pub struct Config {
secret_key: Option<[u8; 32]>,

dht_request_timeout: Option<Duration>,
/// The default limit of a list api if no `limit` query parameter is provided.
///
/// Defaults to `100`
default_list_limit: u16,
/// The maximum limit of a list api, even if a `limit` query parameter is provided.
///
/// Defaults to `1000`
max_list_limit: u16,
}

impl Config {
Expand Down Expand Up @@ -102,6 +113,18 @@ impl Config {
&self.domain
}

pub fn keypair(&self) -> Keypair {
Keypair::from_secret_key(&self.secret_key.unwrap_or_default())
}

pub fn default_list_limit(&self) -> u16 {
self.default_list_limit
}

pub fn max_list_limit(&self) -> u16 {
self.max_list_limit
}

/// Get the path to the storage directory
pub fn storage(&self) -> Result<PathBuf> {
let dir = if let Some(storage) = &self.storage {
Expand All @@ -116,10 +139,6 @@ impl Config {
Ok(dir.join("homeserver"))
}

pub fn keypair(&self) -> Keypair {
Keypair::from_secret_key(&self.secret_key.unwrap_or_default())
}

pub(crate) fn dht_request_timeout(&self) -> Option<Duration> {
self.dht_request_timeout
}
Expand All @@ -135,6 +154,8 @@ impl Default for Config {
storage: None,
secret_key: None,
dht_request_timeout: None,
default_list_limit: DEFAULT_LIST_LIMIT,
max_list_limit: DEFAULT_MAX_LIST_LIMIT,
}
}
}
Expand Down
28 changes: 16 additions & 12 deletions pubky-homeserver/src/database.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use std::fs;

use std::path::Path;

use heed::{Env, EnvOpenOptions};

mod migrations;
pub mod tables;

use crate::config::Config;

use tables::{Tables, TABLES_COUNT};

pub const DEFAULT_MAP_SIZE: usize = 10995116277760; // 10TB (not = disk-space used)
Expand All @@ -15,11 +15,14 @@ pub const DEFAULT_MAP_SIZE: usize = 10995116277760; // 10TB (not = disk-space us
pub struct DB {
pub(crate) env: Env,
pub(crate) tables: Tables,
pub(crate) config: Config,
}

impl DB {
pub fn open(storage: &Path) -> anyhow::Result<Self> {
fs::create_dir_all(storage).unwrap();
pub fn open(config: Config) -> anyhow::Result<Self> {
let storage = config.storage()?;

fs::create_dir_all(&storage).unwrap();

let env = unsafe {
EnvOpenOptions::new()
Expand All @@ -31,7 +34,11 @@ impl DB {

let tables = migrations::run(&env)?;

let db = DB { env, tables };
let db = DB {
env,
tables,
config,
};

Ok(db)
}
Expand All @@ -40,18 +47,15 @@ impl DB {
#[cfg(test)]
mod tests {
use bytes::Bytes;
use pkarr::Keypair;
use pubky_common::timestamp::Timestamp;
use pkarr::{mainline::Testnet, Keypair};

use crate::config::Config;

use super::DB;

#[tokio::test]
async fn entries() {
let storage = std::env::temp_dir()
.join(Timestamp::now().to_string())
.join("pubky");

let db = DB::open(&storage).unwrap();
let db = DB::open(Config::test(&Testnet::new(0))).unwrap();

let keypair = Keypair::random();
let path = "/pub/foo.txt";
Expand Down
8 changes: 5 additions & 3 deletions pubky-homeserver/src/database/tables/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use pubky_common::{
timestamp::Timestamp,
};

use crate::database::{DB, MAX_LIST_LIMIT};
use crate::database::DB;

use super::events::Event;

Expand Down Expand Up @@ -157,7 +157,7 @@ impl DB {

/// Return a list of pubky urls.
///
/// - limit defaults to and capped by [MAX_LIST_LIMIT]
/// - limit defaults to [Config::default_list_limit] and capped by [Config::max_list_limit]
pub fn list(
&self,
txn: &RoTxn,
Expand All @@ -170,7 +170,9 @@ impl DB {
// Vector to store results
let mut results = Vec::new();

let limit = limit.unwrap_or(MAX_LIST_LIMIT).min(MAX_LIST_LIMIT);
let limit = limit
.unwrap_or(self.config.default_list_limit())
.min(self.config.max_list_limit());

// TODO: make this more performant than split and allocations?

Expand Down
19 changes: 8 additions & 11 deletions pubky-homeserver/src/database/tables/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,26 +59,23 @@ impl Event {
}
}

const MAX_LIST_LIMIT: u16 = 1000;
const DEFAULT_LIST_LIMIT: u16 = 100;

impl DB {
/// Returns a list of events formatted as `<OP> <url>`.
///
/// - limit defaults to [Config::default_list_limit] and capped by [Config::max_list_limit]
/// - cursor is a 13 character string encoding of a timestamp
pub fn list_events(
&self,
limit: Option<u16>,
cursor: Option<&str>,
) -> anyhow::Result<Vec<String>> {
let txn = self.env.read_txn()?;

let limit = limit.unwrap_or(DEFAULT_LIST_LIMIT).min(MAX_LIST_LIMIT);

let mut cursor = cursor.unwrap_or("0000000000000");
let limit = limit
.unwrap_or(self.config.default_list_limit())
.min(self.config.max_list_limit());

// Cursor smaller than 13 character is invalid
// TODO: should we send an error instead?
if cursor.len() < 13 {
cursor = "0000000000000"
}
let cursor = cursor.unwrap_or("0000000000000");

let mut result: Vec<String> = vec![];
let mut next_cursor = cursor.to_string();
Expand Down
4 changes: 2 additions & 2 deletions pubky-homeserver/src/pkarr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use pkarr::{
Keypair, PkarrClientAsync, SignedPacket,
};

pub async fn publish_server_packet(
pkarr_client: PkarrClientAsync,
pub(crate) async fn publish_server_packet(
pkarr_client: &PkarrClientAsync,
keypair: &Keypair,
domain: &str,
port: u16,
Expand Down
21 changes: 20 additions & 1 deletion pubky-homeserver/src/routes/feed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ use axum::{
http::{header, Response, StatusCode},
response::IntoResponse,
};
use pubky_common::timestamp::{Timestamp, TimestampError};

use crate::{error::Result, server::AppState};
use crate::{
error::{Error, Result},
server::AppState,
};

pub async fn feed(
State(state): State<AppState>,
Expand All @@ -16,6 +20,21 @@ pub async fn feed(
let limit = params.get("limit").and_then(|l| l.parse::<u16>().ok());
let cursor = params.get("cursor").map(|c| c.as_str());

if let Some(cursor) = cursor {
if let Err(timestmap_error) = Timestamp::try_from(cursor.to_string()) {
let cause = match timestmap_error {
TimestampError::InvalidEncoding => {
"Cursor should be valid base32 Crockford encoding of a timestamp"
}
TimestampError::InvalidBytesLength(size) => {
&format!("Cursor should be 13 characters long, got: {size}")
}
};

Err(Error::new(StatusCode::BAD_REQUEST, cause.into()))?
}
}

let result = state.db.list_events(limit, cursor)?;

Ok(Response::builder()
Expand Down
45 changes: 21 additions & 24 deletions pubky-homeserver/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ use crate::{config::Config, database::DB, pkarr::publish_server_packet};

#[derive(Debug)]
pub struct Homeserver {
port: u16,
config: Config,
state: AppState,
tasks: JoinSet<std::io::Result<()>>,
}

#[derive(Clone, Debug)]
pub(crate) struct AppState {
pub verifier: AuthVerifier,
pub db: DB,
pub pkarr_client: PkarrClientAsync,
pub(crate) verifier: AuthVerifier,
pub(crate) db: DB,
pub(crate) pkarr_client: PkarrClientAsync,
pub(crate) config: Config,
pub(crate) port: u16,
}

impl Homeserver {
Expand All @@ -32,7 +33,7 @@ impl Homeserver {

let keypair = config.keypair();

let db = DB::open(&config.storage()?)?;
let db = DB::open(config.clone())?;

let pkarr_client = PkarrClient::new(Settings {
dht: DhtSettings {
Expand All @@ -44,22 +45,22 @@ impl Homeserver {
})?
.as_async();

let state = AppState {
verifier: AuthVerifier::default(),
db,
pkarr_client: pkarr_client.clone(),
};

let app = crate::routes::create_app(state);

let mut tasks = JoinSet::new();

let app = app.clone();

let listener = TcpListener::bind(SocketAddr::from(([0, 0, 0, 0], config.port()))).await?;

let port = listener.local_addr()?.port();

let state = AppState {
verifier: AuthVerifier::default(),
db,
pkarr_client,
config,
port,
};

let app = crate::routes::create_app(state.clone());

// Spawn http server task
tasks.spawn(
axum::serve(
Expand All @@ -72,15 +73,11 @@ impl Homeserver {

info!("Homeserver listening on http://localhost:{port}");

publish_server_packet(pkarr_client, &keypair, config.domain(), port).await?;
publish_server_packet(&state.pkarr_client, &keypair, state.config.domain(), port).await?;

info!("Homeserver listening on pubky://{}", keypair.public_key());

Ok(Self {
tasks,
config,
port,
})
Ok(Self { tasks, state })
}

/// Test version of [Homeserver::start], using mainline Testnet, and a temporary storage.
Expand All @@ -93,11 +90,11 @@ impl Homeserver {
// === Getters ===

pub fn port(&self) -> u16 {
self.port
self.state.port
}

pub fn public_key(&self) -> PublicKey {
self.config.keypair().public_key()
self.state.config.keypair().public_key()
}

// === Public Methods ===
Expand Down

0 comments on commit d129e4b

Please sign in to comment.