Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/api/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async fn main() -> Result<(), DynError> {
.try_build()
.await?;

NexusApiBuilder(api_context).start(None).await?
NexusApiBuilder::new(api_context).start(None).await?
}
};

Expand Down
59 changes: 44 additions & 15 deletions nexus-webapi/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,47 +26,70 @@ pub const API_CONFIG_FILE_NAME: &str = "api-config.toml";
type ServerHandle = Handle<SocketAddr>;

#[derive(Debug)]
pub struct NexusApiBuilder(pub ApiContext);
pub struct NexusApiBuilder {
api_context: ApiContext,
enable_key_republisher: bool,
}

impl NexusApiBuilder {
pub fn new(api_context: ApiContext) -> Self {
Self {
api_context,
enable_key_republisher: true,
}
}

/// Enables or disables the [KeyRepublisher].
///
/// When enabled (default), [NexusApi] will publish its pkarr packet to the DHT on startup
/// and periodically republish it every hour. When disabled, no publishing occurs.
pub fn enable_key_republisher(mut self, enable: bool) -> Self {
self.enable_key_republisher = enable;
self
}

/// Sets the service name for observability (tracing, logging, monitoring)
pub fn name(mut self, name: String) -> Self {
self.0.api_config.name = name;
self.api_context.api_config.name = name;

self
}

/// Configures the logging level for the service, determining verbosity and log output
pub fn log_level(mut self, log_level: Level) -> Self {
self.0.api_config.stack.log_level = log_level;
self.api_context.api_config.stack.log_level = log_level;

self
}

/// Sets the directory for storing static files on the server
pub fn files_path(mut self, files_path: PathBuf) -> Self {
self.0.api_config.stack.files_path = files_path;
self.api_context.api_config.stack.files_path = files_path;

self
}

/// Sets the OpenTelemetry endpoint for tracing and monitoring
pub fn otlp_endpoint(mut self, otlp_endpoint: Option<String>) -> Self {
self.0.api_config.stack.otlp_endpoint = otlp_endpoint;
self.api_context.api_config.stack.otlp_endpoint = otlp_endpoint;

self
}

/// Sets the database configuration, including graph database and Redis settings
pub fn db(mut self, db: DatabaseConfig) -> Self {
self.0.api_config.stack.db = db;
self.api_context.api_config.stack.db = db;

self
}

/// Opens ddbb connections and initialises tracing layer (if provided in config)
pub async fn init_stack(&self) -> Result<(), DynError> {
StackManager::setup(&self.0.api_config.name, &self.0.api_config.stack).await
StackManager::setup(
&self.api_context.api_config.name,
&self.api_context.api_config.stack,
)
.await
}

/// Creates and starts a [NexusApi] instance.
Expand All @@ -83,7 +106,7 @@ impl NexusApiBuilder {
.await
.inspect_err(|e| error!("Failed to initialize stack: {e}"))?;

let nexus_api = NexusApi::start(self.0)
let nexus_api = NexusApi::start(self.api_context, self.enable_key_republisher)
.await
.inspect_err(|e| error!("Failed to start Nexus API: {e}"))?;

Expand All @@ -110,8 +133,8 @@ pub struct NexusApi {
pubky_tls_handle: ServerHandle,

#[allow(dead_code)]
// Keep this alive. Republishing is stopped when the instance is dropped.
key_republisher: KeyRepublisher,
// Keep this alive if present. Republishing is stopped when the instance is dropped.
key_republisher: Option<KeyRepublisher>,
}

impl NexusApi {
Expand All @@ -134,7 +157,7 @@ impl NexusApi {
.try_build()
.await?;

NexusApiBuilder(api_context).start(shutdown_rx).await
NexusApiBuilder::new(api_context).start(shutdown_rx).await
}
Err(_) => NexusApi::start_from_daemon(config_dir, shutdown_rx).await,
}
Expand All @@ -156,11 +179,13 @@ impl NexusApi {
.try_build()
.await?;

NexusApiBuilder(api_context).start(Some(shutdown_rx)).await
NexusApiBuilder::new(api_context)
.start(Some(shutdown_rx))
.await
}

/// It sets up the necessary routes, binds to the specified address, and starts the Axum server
pub async fn start(ctx: ApiContext) -> Result<Self, DynError> {
pub async fn start(ctx: ApiContext, enable_key_republisher: bool) -> Result<Self, DynError> {
// Create all the routes of the API
let router = routes::routes(ctx.api_config.stack.files_path.clone());
debug!(?ctx.api_config, "Running NexusAPI with config");
Expand All @@ -171,8 +196,12 @@ impl NexusApi {
let (pubky_tls_handle, pubky_tls_socket) =
Self::start_pubky_tls_server(&ctx, router).await?;

let ks_ctx = derive_key_publisher_context(&ctx, pubky_tls_socket.port());
let key_republisher = KeyRepublisher::start(&ks_ctx).await?;
let key_republisher = if enable_key_republisher {
let ks_ctx = derive_key_publisher_context(&ctx, pubky_tls_socket.port());
Some(KeyRepublisher::start(&ks_ctx).await?)
} else {
None
};

Ok(NexusApi {
ctx,
Expand Down
4 changes: 2 additions & 2 deletions nexus-webapi/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ impl MockDb {
.try_build()
.await
.expect("Failed to create ApiContext");
NexusApiBuilder(api_context)
NexusApiBuilder::new(api_context)
.init_stack()
.await
.expect("Failed to initialize stack");
Expand All @@ -41,7 +41,7 @@ impl MockDb {
.try_build()
.await
.expect("Failed to create ApiContext");
NexusApiBuilder(api_context)
NexusApiBuilder::new(api_context)
.init_stack()
.await
.expect("Failed to initialize stack");
Expand Down
4 changes: 2 additions & 2 deletions nexus-webapi/tests/endpoints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ async fn test_info_endpoint() -> Result<()> {

#[tokio_shared_rt::test(shared)]
async fn test_pkarr_endpoint() -> Result<()> {
let test_server = TestServiceServer::get_test_server().await;
let test_server = TestServiceServer::get_test_server_with_key_republisher().await;
let pubky_tls_dns_url = test_server.nexus_api.pubky_tls_dns_url();

let sdk = test_server.testnet.sdk()?;
Expand All @@ -67,7 +67,7 @@ async fn test_pkarr_endpoint() -> Result<()> {

#[tokio_shared_rt::test(shared)]
async fn test_events_endpoint() -> Result<()> {
let test_server = TestServiceServer::get_test_server().await;
let test_server = TestServiceServer::get_test_server_with_key_republisher().await;
let pubky_tls_dns_url = test_server.nexus_api.pubky_tls_dns_url();

let client = &test_server.testnet.client_builder().build()?;
Expand Down
26 changes: 23 additions & 3 deletions nexus-webapi/tests/utils/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,37 @@ pub struct TestServiceServer {
}

static TEST_SERVER: OnceCell<TestServiceServer> = OnceCell::const_new();
static TEST_SERVER_WITH_KEY_REPUBLISHER: OnceCell<TestServiceServer> = OnceCell::const_new();

impl TestServiceServer {
pub async fn get_test_server() -> &'static TestServiceServer {
TEST_SERVER
.get_or_init(|| async {
let testnet = pubky_testnet::Testnet::new().await.unwrap();
let nexus_api = Self::start_server(&testnet).await.unwrap();
let nexus_api = Self::start_server(&testnet, false).await.unwrap();
TestServiceServer { nexus_api, testnet }
})
.await
}

async fn start_server(testnet: &pubky_testnet::Testnet) -> Result<NexusApi> {
/// Returns a test server with the [KeyRepublisher] enabled.
///
/// Use this in tests that access the API via the Pubky TLS DNS URL, which requires
/// the server's pkarr packet to be published to the DHT.
pub async fn get_test_server_with_key_republisher() -> &'static TestServiceServer {
TEST_SERVER_WITH_KEY_REPUBLISHER
.get_or_init(|| async {
let testnet = pubky_testnet::Testnet::new().await.unwrap();
let nexus_api = Self::start_server(&testnet, true).await.unwrap();
TestServiceServer { nexus_api, testnet }
})
.await
}

async fn start_server(
testnet: &pubky_testnet::Testnet,
enable_key_republisher: bool,
) -> Result<NexusApi> {
let test_api_config = ApiConfig {
// When we define the sockets, use local port 0 so OS assigns an available port
public_addr: SocketAddr::from(([127, 0, 0, 1], 0)),
Expand All @@ -42,7 +60,9 @@ impl TestServiceServer {
.try_build()
.await
.expect("Failed to create ApiContext");
let nexus_builder = NexusApiBuilder(api_context).files_path(get_files_dir_test_pathbuf());
let nexus_builder = NexusApiBuilder::new(api_context)
.files_path(get_files_dir_test_pathbuf())
.enable_key_republisher(enable_key_republisher);

let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
let _ = shutdown_tx.send(true); // We want the test server to return right away after start()
Expand Down
2 changes: 1 addition & 1 deletion nexusd/src/launcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl DaemonLauncher {
let api_context = ApiContextBuilder::from_config_dir(config_dir.clone())
.try_build()
.await?;
let nexus_webapi_builder = NexusApiBuilder(api_context);
let nexus_webapi_builder = NexusApiBuilder::new(api_context);

let config = DaemonConfig::read_or_create_config_file(config_dir).await?;
let nexus_watcher_builder = NexusWatcherBuilder::with_stack(config.watcher, &config.stack);
Expand Down