fix(homeserver): add list limits in config

This commit is contained in:
nazeh
2024-09-27 14:43:30 +03:00
parent f029171948
commit d129e4bb6d
7 changed files with 97 additions and 57 deletions

View File

@@ -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 {
@@ -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 {
@@ -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 {
@@ -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
}
@@ -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,
}
}
}

View File

@@ -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)
@@ -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()
@@ -31,7 +34,11 @@ impl DB {
let tables = migrations::run(&env)?;
let db = DB { env, tables };
let db = DB {
env,
tables,
config,
};
Ok(db)
}
@@ -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";

View File

@@ -13,7 +13,7 @@ use pubky_common::{
timestamp::Timestamp,
};
use crate::database::{DB, MAX_LIST_LIMIT};
use crate::database::DB;
use super::events::Event;
@@ -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,
@@ -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?

View File

@@ -59,10 +59,11 @@ 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>,
@@ -70,15 +71,11 @@ impl DB {
) -> anyhow::Result<Vec<String>> {
let txn = self.env.read_txn()?;
let limit = limit.unwrap_or(DEFAULT_LIST_LIMIT).min(MAX_LIST_LIMIT);
let limit = limit
.unwrap_or(self.config.default_list_limit())
.min(self.config.max_list_limit());
let mut cursor = cursor.unwrap_or("0000000000000");
// 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();

View File

@@ -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,

View File

@@ -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>,
@@ -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()

View File

@@ -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 {
@@ -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 {
@@ -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(
@@ -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.
@@ -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 ===