diff --git a/src/api_key.rs b/src/api_key.rs index 06a1216..b000143 100644 --- a/src/api_key.rs +++ b/src/api_key.rs @@ -2,6 +2,8 @@ use std::borrow::Cow; use std::str::FromStr; use base64::Engine; +use poem_openapi::registry::{MetaSchema, MetaSchemaRef}; +use poem_openapi::types::{ParseFromJSON, ToJSON}; use rand::rngs::OsRng; use rand::Rng; use serde::Serialize; @@ -121,6 +123,59 @@ impl FromStr for ApiKey { } } +impl poem_openapi::types::Type for ApiKey { + const IS_REQUIRED: bool = true; + + type RawValueType = ApiKey; + + type RawElementValueType = ApiKey; + + fn name() -> std::borrow::Cow<'static, str> { + "string(api-key)".into() + } + + fn schema_ref() -> MetaSchemaRef { + let mut schema_ref = MetaSchema::new_with_format("string", "api-key"); + + schema_ref.example = Some(serde_json::Value::String( + "MyDwh6wBRyOAkrA-ANOGjViioo3fXMa53nbdLhezV4s=".to_string(), + )); + schema_ref.title = Some("Api Key".to_string()); + schema_ref.description = Some("Base64 encoded API key"); + + MetaSchemaRef::Inline(Box::new(schema_ref)) + } + + fn as_raw_value(&self) -> Option<&Self::RawValueType> { + Some(self) + } + + fn raw_element_iter<'a>( + &'a self, + ) -> Box + 'a> { + Box::new(self.as_raw_value().into_iter()) + } +} + +impl ParseFromJSON for ApiKey { + fn parse_from_json( + value: Option, + ) -> poem_openapi::types::ParseResult { + // TODO: Better error handling + let value = value + .ok_or_else(|| poem_openapi::types::ParseError::expected_input())?; + + serde_json::from_value(value) + .map_err(|_| poem_openapi::types::ParseError::expected_input()) + } +} + +impl ToJSON for ApiKey { + fn to_json(&self) -> Option { + serde_json::to_value(self).ok() + } +} + impl ApiKey { pub fn reveal(&self) -> eyre::Result { let relayer_id = uuid::Uuid::parse_str(&self.relayer_id) diff --git a/src/new_server.rs b/src/new_server.rs index 38eda50..ac7c2c3 100644 --- a/src/new_server.rs +++ b/src/new_server.rs @@ -11,16 +11,16 @@ use poem_openapi::payload::{Json, PlainText}; use poem_openapi::{ApiResponse, OpenApi, OpenApiService}; use url::Url; +use crate::api_key::ApiKey; use crate::app::App; +use crate::server::routes::relayer::CreateApiKeyResponse; use crate::service::Service; use crate::task_runner::TaskRunner; use crate::types::{ CreateRelayerRequest, CreateRelayerResponse, NetworkInfo, NewNetworkInfo, - RelayerInfo, + RelayerInfo, RelayerUpdate, }; -pub mod types; - struct AdminApi; #[derive(ApiResponse)] @@ -37,6 +37,7 @@ enum AdminResponse { #[OpenApi(prefix_path = "/1/admin/")] impl AdminApi { + /// Create Relayer #[oai(path = "/relayer", method = "post")] async fn create_relayer( &self, @@ -86,6 +87,7 @@ impl AdminApi { })) } + /// Get Relayers #[oai(path = "/relayers", method = "get")] async fn get_relayers( &self, @@ -96,20 +98,81 @@ impl AdminApi { Ok(Json(relayer_info)) } + /// Get Relayer #[oai(path = "/relayer/:relayer_id", method = "get")] - async fn update_relayer(&self, app: Data<&Arc>) -> AdminResponse { - todo!() + async fn get_relayer( + &self, + app: Data<&Arc>, + Path(relayer_id): Path, + ) -> Result> { + let relayer_info = + app.db.get_relayer(&relayer_id).await.map_err(|err| { + poem::error::Error::from_string( + err.to_string(), + StatusCode::INTERNAL_SERVER_ERROR, + ) + })?; + + Ok(Json(relayer_info)) + } + + /// Update Relayer + #[oai(path = "/relayer/:relayer_id", method = "post")] + async fn update_relayer( + &self, + app: Data<&Arc>, + Path(relayer_id): Path, + Json(req): Json, + ) -> Result<()> { + app.db + .update_relayer(&relayer_id, &req) + .await + .map_err(|err| { + poem::error::Error::from_string( + err.to_string(), + StatusCode::INTERNAL_SERVER_ERROR, + ) + })?; + + Ok(()) } + /// Reset Relayer transactions + /// + /// Purges unsent transactions, useful for unstucking the relayer #[oai(path = "/relayer/:relayer_id/reset", method = "post")] async fn purge_unsent_txs( &self, app: Data<&Arc>, Path(relayer_id): Path, - ) -> AdminResponse { - todo!() + ) -> Result<()> { + app.db.purge_unsent_txs(&relayer_id).await.map_err(|err| { + poem::error::Error::from_string( + err.to_string(), + StatusCode::INTERNAL_SERVER_ERROR, + ) + })?; + + Ok(()) + } + + /// Create Relayer API Key + #[oai(path = "/relayer/:relayer_id/key", method = "post")] + async fn create_relayer_api_key( + &self, + app: Data<&Arc>, + Path(relayer_id): Path, + ) -> Result> { + let api_key = ApiKey::random(&relayer_id); + + app.db + .create_api_key(&relayer_id, api_key.api_key_secret_hash()) + .await?; + + Ok(Json(CreateApiKeyResponse { api_key })) } + /// Create Network #[oai(path = "/network/:chain_id", method = "post")] async fn create_network( &self, @@ -153,6 +216,7 @@ impl AdminApi { Ok(()) } + /// Get Networks #[oai(path = "/networks", method = "get")] async fn list_networks( &self, diff --git a/src/new_server/types.rs b/src/new_server/types.rs deleted file mode 100644 index 589dc90..0000000 --- a/src/new_server/types.rs +++ /dev/null @@ -1,4 +0,0 @@ -use poem_openapi::Object; -use serde::{Deserialize, Serialize}; -use sqlx::FromRow; - diff --git a/src/server.rs b/src/server.rs index fbbe2ab..9911027 100644 --- a/src/server.rs +++ b/src/server.rs @@ -41,15 +41,15 @@ pub async fn _spawn_server( .with_state(app.clone()); let mut admin_routes = Router::new() - .route("/relayer", post(create_relayer)) - .route("/relayer/:relayer_id/reset", post(purge_unsent_txs)) - .route("/relayers", get(get_relayers)) + .route("/relayer", post(create_relayer)) // done + .route("/relayer/:relayer_id/reset", post(purge_unsent_txs)) // done + .route("/relayers", get(get_relayers)) // done .route( "/relayer/:relayer_id", - post(update_relayer).get(get_relayer), + post(update_relayer).get(get_relayer), // done ) - .route("/relayer/:relayer_id/key", post(create_relayer_api_key)) - .route("/network/:chain_id", post(routes::network::create_network)) + .route("/relayer/:relayer_id/key", post(create_relayer_api_key)) // done + .route("/network/:chain_id", post(routes::network::create_network)) // done .with_state(app.clone()); if let Some((username, password)) = app.config.server.credentials() { diff --git a/src/server/routes/relayer.rs b/src/server/routes/relayer.rs index ad87b09..0882ab0 100644 --- a/src/server/routes/relayer.rs +++ b/src/server/routes/relayer.rs @@ -4,6 +4,7 @@ use axum::extract::{Json, Path, State}; use ethers::signers::Signer; use ethers::types::Address; use eyre::Result; +use poem_openapi::Object; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -50,8 +51,9 @@ pub enum JsonRpcVersion { V2, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Object)] #[serde(rename_all = "camelCase")] +#[oai(rename_all = "camelCase")] pub struct CreateApiKeyResponse { pub api_key: ApiKey, } diff --git a/src/types.rs b/src/types.rs index fb1ae74..46cf356 100644 --- a/src/types.rs +++ b/src/types.rs @@ -53,8 +53,9 @@ pub struct RelayerInfo { } -#[derive(Deserialize, Serialize, Debug, Clone, Default)] +#[derive(Deserialize, Serialize, Debug, Clone, Default, Object)] #[serde(rename_all = "camelCase")] +#[oai(rename_all = "camelCase")] pub struct RelayerUpdate { #[serde(default)] pub relayer_name: Option, @@ -70,6 +71,7 @@ pub struct RelayerUpdate { #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Object)] #[serde(rename_all = "camelCase")] +#[oai(rename_all = "camelCase")] pub struct RelayerGasPriceLimit { pub value: U256Wrapper, pub chain_id: i64,