mirror of
https://github.com/aljazceru/pubky-core.git
synced 2025-12-31 12:54:35 +01:00
fix(homeserver): add list limits in config
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 ===
|
||||
|
||||
Reference in New Issue
Block a user