use axum::extract::{DefaultBodyLimit, Request};
use axum::response::{IntoResponse, Response};
use axum::routing::post;
use axum::{Extension, Router};
use axum_extra::TypedHeader;
use futures::TryStreamExt;
use http::StatusCode;
use surrealdb_core::dbs::Session;
use surrealdb_iam::Action::Edit;
use surrealdb_iam::ResourceKind::Any;
use surrealdb_rpc::capabilities::RouteTarget;
use surrealdb_types::SurrealValue;
use tower_http::limit::RequestBodyLimitLayer;
use super::AppState;
use super::error::ResponseError;
use super::headers::Accept;
use crate::cnf::HTTP_MAX_IMPORT_BODY_SIZE;
use crate::ntw::error::Error as NetError;
use crate::ntw::output::Output;
pub fn router<S>() -> Router<S>
where
S: Clone + Send + Sync + 'static,
{
Router::new()
.route("/import", post(handler))
.route_layer(DefaultBodyLimit::disable())
.layer(RequestBodyLimitLayer::new(*HTTP_MAX_IMPORT_BODY_SIZE))
}
async fn handler(
Extension(state): Extension<AppState>,
Extension(session): Extension<Session>,
accept: Option<TypedHeader<Accept>>,
request: Request,
) -> Result<Response, ResponseError> {
let db = &state.datastore;
if !db.allows_http_route(&RouteTarget::Import) {
warn!("Capabilities denied HTTP route request attempt, target: '{}'", &RouteTarget::Import);
return Err(NetError::ForbiddenRoute(RouteTarget::Import.to_string()).into());
}
db.check(&session, Edit, Any.on_level(session.au.level().to_owned())).map_err(ResponseError)?;
let body_stream = request.into_body().into_data_stream().map_err(anyhow::Error::new);
match db.import_stream(&session, body_stream).await {
Ok(res) => {
let failed = res.iter().any(|r| r.result.is_err());
let output = match accept.as_deref() {
None | Some(Accept::ApplicationJson) => {
let res = res.into_value();
Output::json_value(&res)
}
Some(Accept::ApplicationCbor) => {
let res = res.into_value();
Output::cbor(res)
}
Some(Accept::ApplicationOctetStream) => {
if failed {
let report = res
.iter()
.filter_map(|r| r.result.as_ref().err())
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("\n");
Output::OctetStream(report.into_bytes())
} else {
Output::None
}
}
Some(Accept::ApplicationFlatbuffers) => {
let res = res.into_value();
Output::flatbuffers(&res)
}
Some(_) => return Err(NetError::InvalidType.into()),
};
let mut response = output.into_response();
if failed && response.status().is_success() {
*response.status_mut() = StatusCode::UNPROCESSABLE_ENTITY;
}
Ok(response)
}
Err(err) => Err(ResponseError(err)),
}
}