diff --git a/server/src/docs/mod.rs b/server/src/docs/mod.rs index 4a0e6f0..850d825 100644 --- a/server/src/docs/mod.rs +++ b/server/src/docs/mod.rs @@ -1,6 +1,9 @@ use crate::models::bulk::{BulkResponse, ListResponse}; use crate::models::error::ErrorResponse; use crate::models::profile::ENSProfile; +use crate::routes::address::AddressGetBulkQuery; +use crate::routes::name::NameGetBulkQuery; +use crate::routes::universal::UniversalGetBulkQuery; use enstate_shared::meta::AppMeta; use enstate_shared::utils::vec; use utoipa::openapi::{ExternalDocs, License, Tag}; @@ -16,11 +19,12 @@ use utoipa::OpenApi; crate::routes::address::get, crate::routes::name::get, crate::routes::universal::get, crate::routes::address::get_bulk, crate::routes::name::get_bulk, crate::routes::universal::get_bulk, crate::routes::address::get_bulk_sse, crate::routes::name::get_bulk_sse, crate::routes::universal::get_bulk_sse, + crate::routes::address::post_bulk_sse, crate::routes::name::post_bulk_sse, crate::routes::universal::post_bulk_sse, crate::routes::header::get, crate::routes::image::get, crate::routes::root::get, ), - components(schemas(ENSProfile, ListResponse>, ErrorResponse, AppMeta)) + components(schemas(ENSProfile, ListResponse>, ErrorResponse, AppMeta, UniversalGetBulkQuery)), )] pub struct ApiDoc; diff --git a/server/src/http.rs b/server/src/http.rs index 51db861..d66f54c 100644 --- a/server/src/http.rs +++ b/server/src/http.rs @@ -1,7 +1,7 @@ use axum::response::{Html, Redirect}; use std::{net::SocketAddr, sync::Arc}; -use axum::{routing::get, Router}; +use axum::{routing::get, routing::post, Router}; use tokio::net::TcpListener; use tokio_util::sync::CancellationToken; use tower_http::cors::CorsLayer; @@ -64,9 +64,9 @@ pub fn setup(state: AppState) -> App { .route("/bulk/a", get(routes::address::get_bulk)) .route("/bulk/n", get(routes::name::get_bulk)) .route("/bulk/u", get(routes::universal::get_bulk)) - .route("/sse/a", get(routes::address::get_bulk_sse)) - .route("/sse/n", get(routes::name::get_bulk_sse)) - .route("/sse/u", get(routes::universal::get_bulk_sse)) + .route("/sse/a", get(routes::address::get_bulk_sse).post(routes::address::post_bulk_sse)) + .route("/sse/n", get(routes::name::get_bulk_sse).post(routes::name::post_bulk_sse)) + .route("/sse/u", get(routes::universal::get_bulk_sse).post(routes::universal::post_bulk_sse)) .fallback(routes::four_oh_four::handler) .layer(CorsLayer::permissive()) .layer(TraceLayer::new_for_http()) diff --git a/server/src/routes/address.rs b/server/src/routes/address.rs index 3429da8..9aa4d29 100644 --- a/server/src/routes/address.rs +++ b/server/src/routes/address.rs @@ -201,3 +201,25 @@ pub async fn get_bulk_sse( Sse::new(UnboundedReceiverStream::new(event_rx)) .keep_alive(axum::response::sse::KeepAlive::new().interval(Duration::from_secs(1))) } + +/// /sse/a +/// +/// Same as the GET version, but using POST with a JSON body instead of query parameters allowing for larger requests. +#[utoipa::path( + post, + tag = "Stream Profiles", + path = "/sse/a", + responses( + (status = 200, description = "Successfully found address.", body = BulkResponse), + (status = BAD_REQUEST, description = "Invalid address.", body = ErrorResponse), + (status = NOT_FOUND, description = "No name was associated with this address.", body = ErrorResponse), + (status = UNPROCESSABLE_ENTITY, description = "Reverse record not owned by this address.", body = ErrorResponse), + ), + request_body = AddressGetBulkQuery, +)] +pub async fn post_bulk_sse( + State(state): State>, + Json(query): Json, +) -> impl IntoResponse { + get_bulk_sse(Qs(query), State(state)).await +} diff --git a/server/src/routes/name.rs b/server/src/routes/name.rs index fe43fb9..9bb7896 100644 --- a/server/src/routes/name.rs +++ b/server/src/routes/name.rs @@ -155,3 +155,23 @@ pub async fn get_bulk_sse( Sse::new(UnboundedReceiverStream::new(event_rx)) .keep_alive(axum::response::sse::KeepAlive::new().interval(Duration::from_secs(1))) } + +/// /sse/n +/// +/// Same as the GET version, but using POST with a JSON body instead of query parameters allowing for larger requests. +#[utoipa::path( + post, + tag = "Stream Profiles", + path = "/sse/n", + responses( + (status = 200, description = "Successfully found name.", body = ListButWithLength>), + (status = NOT_FOUND, description = "No name could be found.", body = ErrorResponse), + ), + request_body = NameGetBulkQuery, +)] +pub async fn post_bulk_sse( + State(state): State>, + Json(query): Json, +) -> impl IntoResponse { + get_bulk_sse(Qs(query), State(state)).await +} diff --git a/server/src/routes/universal.rs b/server/src/routes/universal.rs index 507f4f9..2b32dc0 100644 --- a/server/src/routes/universal.rs +++ b/server/src/routes/universal.rs @@ -14,14 +14,15 @@ use enstate_shared::core::{ENSService, Profile}; use futures::future::join_all; use serde::Deserialize; use tokio_stream::wrappers::UnboundedReceiverStream; -use utoipa::IntoParams; +use utoipa::openapi::schema; +use utoipa::{IntoParams, ToSchema}; use crate::models::bulk::{BulkResponse, ListResponse}; use crate::models::sse::SSEResponse; use crate::routes::{profile_http_error_mapper, validate_bulk_input, FreshQuery, Qs, RouteError}; /// /u/{name_or_address} -/// +/// /// The Universal Endpoint supports looking up both names and addresses. /// /// Here is an example of a valid request that looks up an address or name: @@ -29,7 +30,7 @@ use crate::routes::{profile_http_error_mapper, validate_bulk_input, FreshQuery, /// /u/luc.eth /// /u/0x225f137127d9067788314bc7fcc1f36746a3c3B5 /// ``` -/// +/// /// You can also use the [useProfile](https://github.com/v3xlabs/use-enstate/blob/master/src/hooks/useProfile.ts) hook from [use-enstate](https://github.com/v3xlabs/use-enstate). #[utoipa::path( get, @@ -64,25 +65,28 @@ pub async fn get( })? } -#[derive(Deserialize, IntoParams)] +#[derive(Deserialize, IntoParams, ToSchema)] pub struct UniversalGetBulkQuery { // TODO (@antony1060): remove when proper serde error handling + // list of names or addresses to lookup + #[schema(example = json!(["luc.eth", "nick.eth", "helgesson.eth", "irc.eth", "khori.eth", "v3x.eth"]))] #[serde(default)] queries: Vec, + #[schema(example = "false")] #[serde(flatten)] fresh: FreshQuery, } /// /bulk/u -/// +/// /// The Universal Endpoint supports looking up both names and addresses. -/// +/// /// Here is an example of a valid request that looks up multiple names: /// ```url /// /bulk/u?queries[]=luc.eth&queries[]=nick.eth&queries[]=helgesson.eth&queries[]=irc.eth&queries[]=khori.eth&queries[]=v3x.eth /// ``` -/// +/// /// You can also use the [useBulkProfile](https://github.com/v3xlabs/use-enstate/blob/master/src/hooks/useBulkProfile.ts) hook from [use-enstate](https://github.com/v3xlabs/use-enstate). #[utoipa::path( get, @@ -116,14 +120,14 @@ pub async fn get_bulk( } /// /sse/u -/// +/// /// The Universal Endpoint supports looking up both names and addresses. -/// +/// /// Here is an example of a valid request that looks up multiple names: /// ```url /// /sse/u?queries[]=luc.eth&queries[]=nick.eth&queries[]=helgesson.eth&queries[]=irc.eth&queries[]=khori.eth&queries[]=v3x.eth /// ``` -/// +/// /// You can also use the [useSSEProfiles](https://github.com/v3xlabs/use-enstate/blob/master/src/hooks/useSSEProfiles.ts) hook from [use-enstate](https://github.com/v3xlabs/use-enstate). #[utoipa::path( get, @@ -174,6 +178,27 @@ pub async fn get_bulk_sse( .keep_alive(axum::response::sse::KeepAlive::new().interval(Duration::from_secs(1))) } +/// /sse/u +/// +/// Same as the GET version, but using POST with a JSON body instead of query parameters allowing for larger requests. +#[utoipa::path( + post, + tag = "Stream Profiles", + path = "/sse/u", + responses( + (status = 200, description = "Successfully found name or address.", body = BulkResponse), + (status = NOT_FOUND, description = "No name or address could be found.", body = ErrorResponse), + (status = UNPROCESSABLE_ENTITY, description = "Reverse record not owned by this address.", body = ErrorResponse), + ), + request_body = UniversalGetBulkQuery, +)] +pub async fn post_bulk_sse( + State(state): State>, + Json(query): Json, +) -> impl IntoResponse { + get_bulk_sse(Qs(query), State(state)).await +} + // helper function for above async fn profile_from_lookup_guess( lookup: Result,