diff --git a/backend/sync_server/Cargo.toml b/backend/sync_server/Cargo.toml index f6c51a05..c9b142ff 100644 --- a/backend/sync_server/Cargo.toml +++ b/backend/sync_server/Cargo.toml @@ -19,3 +19,5 @@ tracing-subscriber = "0.3.19" serde_yaml = "0.9.34" sqlx = { version = "0.8.2", features = ["sqlite", "runtime-tokio", "uuid", "chrono"] } chrono = { version = "0.4.38", features = ["serde"] } +aide = { version = "0.13.4", features = ["axum", "axum-ws", "scalar"] } +schemars = { version = "0.8.21", features = ["chrono", "uuid1"] } diff --git a/backend/sync_server/src/database/models.rs b/backend/sync_server/src/database/models.rs index aa22d70a..1888fba7 100644 --- a/backend/sync_server/src/database/models.rs +++ b/backend/sync_server/src/database/models.rs @@ -1,12 +1,13 @@ use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; +use schemars::JsonSchema; +use serde::Serialize; use sync_lib::bytes_to_base64; pub type VaultId = String; pub type DocumentId = uuid::Uuid; pub type DocumentVersionId = i64; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] pub struct StoredDocumentVersion { pub vault_id: VaultId, pub document_id: DocumentId, @@ -25,7 +26,7 @@ impl StoredDocumentVersion { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, JsonSchema)] pub struct DocumentVersionWithoutContent { pub vault_id: VaultId, pub document_id: DocumentId, @@ -52,7 +53,7 @@ impl From for DocumentVersionWithoutContent { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, JsonSchema)] pub struct DocumentVersion { pub vault_id: VaultId, pub document_id: DocumentId, diff --git a/backend/sync_server/src/errors.rs b/backend/sync_server/src/errors.rs index 4dc778d1..4f36824b 100644 --- a/backend/sync_server/src/errors.rs +++ b/backend/sync_server/src/errors.rs @@ -1,3 +1,4 @@ +use aide::OperationOutput; use axum::{ http::StatusCode, response::{IntoResponse, Response}, @@ -36,6 +37,10 @@ impl IntoResponse for SyncServerError { } } +impl OperationOutput for SyncServerError { + type Inner = Self; +} + pub fn init_error(error: anyhow::Error) -> SyncServerError { SyncServerError::InitError(error) } diff --git a/backend/sync_server/src/server.rs b/backend/sync_server/src/server.rs index 1b3350b4..7f1abf74 100644 --- a/backend/sync_server/src/server.rs +++ b/backend/sync_server/src/server.rs @@ -1,13 +1,18 @@ use crate::app_state::AppState; +use aide::{ + axum::{ + routing::{delete, get, put}, + ApiRouter, + }, + openapi::{Info, OpenApi}, + scalar::Scalar, +}; use anyhow::Context; use anyhow::Result; -use axum::extract::DefaultBodyLimit; -use axum::extract::WebSocketUpgrade; -use axum::response::Response; -use axum::routing::delete; -use axum::{routing::get, routing::put, Router}; +use axum::response::{IntoResponse, Response}; +use axum::{extract::DefaultBodyLimit, Extension}; +use axum::{extract::WebSocketUpgrade, Json}; use log::info; - mod delete_document; mod fetch_latest_document_version; mod fetch_latest_documents; @@ -20,35 +25,48 @@ pub async fn create_server(app_state: AppState) -> Result<()> { &app_state.config.server.host, &app_state.config.server.port ); - let app = Router::new() - .route( + let mut api = OpenApi { + info: Info { + description: Some("an example API".to_string()), + ..Info::default() + }, + ..OpenApi::default() + }; + + let app = ApiRouter::new() + .api_route( "/vaults/:vault_id/documents/latest", get(fetch_latest_documents::fetch_latest_documents), ) - .route( + .api_route( "/vaults/:vault_id/documents/:document_id/versions/:parent_version_id", put(update_document::update_document), ) - .route( + .api_route( "/vaults/:vault_id/documents/:document_id", delete(delete_document::delete_document), ) - .route( + .api_route( "/vaults/:vault_id/documents/:document_id/versions/latest", get(fetch_latest_document_version::fetch_latest_document_version), ) - .route("/ws", get(handler)) + .api_route("/ws", get(handler)) + .route("/", Scalar::new("/api.json").axum_route()) + .route("/api.json", axum::routing::get(serve_api)) .layer(DefaultBodyLimit::max( app_state.config.server.max_body_size_mb * 1024 * 1024, )) - .with_state(app_state); + .with_state(app_state) + .finish_api(&mut api) + .layer(Extension(api)) + .into_make_service(); let listener = tokio::net::TcpListener::bind(address.clone()) .await .with_context(|| format!("Failed to bind to address: {}", address))?; info!( - "Listening on {}", + "Listening on http://{}", listener .local_addr() .context("Failed to get local address")? @@ -65,3 +83,7 @@ async fn handler(ws: WebSocketUpgrade) -> Response { // ... }) } + +async fn serve_api(Extension(api): Extension) -> impl IntoResponse { + Json(api) +} diff --git a/backend/sync_server/src/server/requests.rs b/backend/sync_server/src/server/requests.rs index fdcc3079..c3baf208 100644 --- a/backend/sync_server/src/server/requests.rs +++ b/backend/sync_server/src/server/requests.rs @@ -1,7 +1,8 @@ use chrono::{DateTime, Utc}; +use schemars::JsonSchema; use serde::Deserialize; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, JsonSchema)] pub struct CreateDocumentVersion { pub created_date: DateTime, pub relative_path: String, @@ -9,7 +10,7 @@ pub struct CreateDocumentVersion { pub is_binary: bool, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, JsonSchema)] pub struct DeleteDocumentVersion { pub created_date: DateTime, }