diff --git a/src/modes/server.rs b/src/modes/server.rs index 1a1dea9..672ef45 100644 --- a/src/modes/server.rs +++ b/src/modes/server.rs @@ -65,6 +65,7 @@ async fn run_server( // Add API, documentation, and pages routes let app = api::add_routes(app); + let app = api::add_docs_routes(app); let app = docs::add_routes(app); let app = pages::add_routes(app); diff --git a/src/modes/server/api/item.rs b/src/modes/server/api/item.rs index 55b368f..507554a 100644 --- a/src/modes/server/api/item.rs +++ b/src/modes/server/api/item.rs @@ -11,11 +11,30 @@ use std::path::PathBuf; use std::str::FromStr; use std::io::Read; use anyhow::{Result, anyhow}; +use utoipa::ToSchema; use crate::compression_engine::{CompressionType, get_compression_engine}; use crate::db; use crate::modes::server::common::{AppState, ApiResponse, ItemInfo, TagsQuery, check_auth, ListItemsQuery}; +#[utoipa::path( + get, + path = "/api/item/", + responses( + (status = 200, description = "Successfully retrieved list of items", body = ApiResponse>), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + params( + ("tags" = Option, Query, description = "Comma-separated list of tags to filter by"), + ("order" = Option, Query, description = "Sort order (newest or oldest)"), + ("start" = Option, Query, description = "Starting index for pagination"), + ("count" = Option, Query, description = "Number of items to return") + ), + security( + ("bearerAuth" = []) + ) +)] pub async fn handle_list_items( State(state): State, Query(params): Query, @@ -106,6 +125,18 @@ pub async fn handle_list_items( Ok(Json(response)) } +#[utoipa::path( + post, + path = "/api/item/", + responses( + (status = 200, description = "Successfully created item", body = ApiResponse), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + security( + ("bearerAuth" = []) + ) +)] pub async fn handle_post_item( State(state): State, headers: HeaderMap, @@ -129,6 +160,22 @@ pub async fn handle_post_item( Ok(Json(response)) } +#[utoipa::path( + delete, + path = "/api/item/{item_id}", + responses( + (status = 200, description = "Successfully deleted item", body = ApiResponse<()>), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Item not found"), + (status = 500, description = "Internal server error") + ), + params( + ("item_id" = String, Path, description = "ID of the item to delete") + ), + security( + ("bearerAuth" = []) + ) +)] pub async fn handle_delete_item( State(state): State, Path(item_id): Path, @@ -166,6 +213,22 @@ pub async fn handle_delete_item( } } +#[utoipa::path( + get, + path = "/api/item/latest", + responses( + (status = 200, description = "Successfully retrieved latest item content", body = ApiResponse), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Item not found"), + (status = 500, description = "Internal server error") + ), + params( + ("tags" = Option, Query, description = "Comma-separated list of tags to filter by") + ), + security( + ("bearerAuth" = []) + ) +)] pub async fn handle_get_item_latest( State(state): State, Query(params): Query, @@ -218,6 +281,22 @@ pub async fn handle_get_item_latest( } } +#[utoipa::path( + get, + path = "/api/item/{item_id}", + responses( + (status = 200, description = "Successfully retrieved item content", body = ApiResponse), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Item not found"), + (status = 500, description = "Internal server error") + ), + params( + ("item_id" = String, Path, description = "ID of the item to retrieve") + ), + security( + ("bearerAuth" = []) + ) +)] pub async fn handle_get_item( State(state): State, Path(item_id): Path, @@ -269,6 +348,22 @@ pub async fn handle_get_item( } } +#[utoipa::path( + get, + path = "/api/item/latest/content", + responses( + (status = 200, description = "Successfully retrieved latest item raw content"), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Item not found"), + (status = 500, description = "Internal server error") + ), + params( + ("tags" = Option, Query, description = "Comma-separated list of tags to filter by") + ), + security( + ("bearerAuth" = []) + ) +)] pub async fn handle_get_item_latest_content( State(state): State, Query(params): Query, @@ -316,6 +411,22 @@ pub async fn handle_get_item_latest_content( } } +#[utoipa::path( + get, + path = "/api/item/{item_id}/content", + responses( + (status = 200, description = "Successfully retrieved item raw content"), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Item not found"), + (status = 500, description = "Internal server error") + ), + params( + ("item_id" = String, Path, description = "ID of the item to retrieve") + ), + security( + ("bearerAuth" = []) + ) +)] pub async fn handle_get_item_content( State(state): State, Path(item_id): Path, @@ -416,6 +527,22 @@ async fn get_item_raw_content(item: &db::Item, data_dir: &PathBuf, conn: &mut ru Ok((content, mime_type)) } +#[utoipa::path( + get, + path = "/api/item/latest/meta", + responses( + (status = 200, description = "Successfully retrieved latest item metadata", body = ApiResponse>), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Item not found"), + (status = 500, description = "Internal server error") + ), + params( + ("tags" = Option, Query, description = "Comma-separated list of tags to filter by") + ), + security( + ("bearerAuth" = []) + ) +)] pub async fn handle_get_item_latest_meta( State(state): State, Query(params): Query, @@ -465,6 +592,22 @@ pub async fn handle_get_item_latest_meta( } } +#[utoipa::path( + get, + path = "/api/item/{item_id}/meta", + responses( + (status = 200, description = "Successfully retrieved item metadata", body = ApiResponse>), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Item not found"), + (status = 500, description = "Internal server error") + ), + params( + ("item_id" = String, Path, description = "ID of the item to retrieve metadata for") + ), + security( + ("bearerAuth" = []) + ) +)] pub async fn handle_get_item_meta( State(state): State, Path(item_id): Path, diff --git a/src/modes/server/api/mod.rs b/src/modes/server/api/mod.rs index 502fba3..2d1eef3 100644 --- a/src/modes/server/api/mod.rs +++ b/src/modes/server/api/mod.rs @@ -2,11 +2,45 @@ pub mod item; pub mod status; use axum::{ - routing::get, + routing::{get, post, delete}, Router, }; use crate::modes::server::common::AppState; +use utoipa::OpenApi; +use utoipa_swagger_ui::SwaggerUi; + +#[derive(OpenApi)] +#[openapi( + paths( + status::handle_status, + item::handle_list_items, + item::handle_post_item, + item::handle_delete_item, + item::handle_get_item_latest, + item::handle_get_item_latest_meta, + item::handle_get_item_latest_content, + item::handle_get_item, + item::handle_get_item_meta, + item::handle_get_item_content, + ), + components( + schemas( + crate::modes::server::common::ApiResponse, + crate::modes::server::common::ApiResponse>, + crate::modes::server::common::ApiResponse, + crate::modes::server::common::ApiResponse>, + crate::modes::server::common::ApiResponse<()>, + crate::modes::server::api::status::StatusInfo, + crate::modes::server::api::item::ItemInfo, + ) + ), + tags( + (name = "status", description = "Status API endpoints"), + (name = "items", description = "Item management API endpoints") + ) +)] +struct ApiDoc; pub fn add_routes(router: Router) -> Router { router @@ -22,3 +56,8 @@ pub fn add_routes(router: Router) -> Router { .route("/api/item/:id/meta", get(item::handle_get_item_meta)) .route("/api/item/:id/content", get(item::handle_get_item_content)) } + +pub fn add_docs_routes(router: Router) -> Router { + router + .merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi())) +} diff --git a/src/modes/server/api/status.rs b/src/modes/server/api/status.rs index 662ae40..aa8eaec 100644 --- a/src/modes/server/api/status.rs +++ b/src/modes/server/api/status.rs @@ -5,11 +5,24 @@ use axum::{ }; use log::warn; use std::net::SocketAddr; +use utoipa::ToSchema; use crate::modes::server::common::{AppState, ApiResponse, check_auth}; use crate::common::status::{generate_status_info, StatusInfo}; use crate::meta_plugin::MetaPluginType; +#[utoipa::path( + get, + path = "/api/status", + responses( + (status = 200, description = "Successfully retrieved status information", body = ApiResponse), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Internal server error") + ), + security( + ("bearerAuth" = []) + ) +)] pub async fn handle_status( State(state): State, headers: HeaderMap,