docs(api): complete OpenAPI coverage for all routes
Add missing utoipa annotations:
- GET /books/{id}/thumbnail
- GET/POST /settings, /settings/{key}
- POST /settings/cache/clear
- GET /settings/cache/stats, /settings/thumbnail/stats
Add 'settings' tag and register all new schemas.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -347,6 +347,21 @@ use axum::{
|
|||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Get book thumbnail image
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/books/{id}/thumbnail",
|
||||||
|
tag = "books",
|
||||||
|
params(
|
||||||
|
("id" = String, Path, description = "Book UUID"),
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "WebP thumbnail image", content_type = "image/webp"),
|
||||||
|
(status = 404, description = "Book not found or thumbnail not available"),
|
||||||
|
(status = 401, description = "Unauthorized"),
|
||||||
|
),
|
||||||
|
security(("Bearer" = []))
|
||||||
|
)]
|
||||||
pub async fn get_thumbnail(
|
pub async fn get_thumbnail(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(book_id): Path<Uuid>,
|
Path(book_id): Path<Uuid>,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use utoipa::OpenApi;
|
|||||||
paths(
|
paths(
|
||||||
crate::books::list_books,
|
crate::books::list_books,
|
||||||
crate::books::get_book,
|
crate::books::get_book,
|
||||||
|
crate::books::get_thumbnail,
|
||||||
crate::books::list_series,
|
crate::books::list_series,
|
||||||
crate::pages::get_page,
|
crate::pages::get_page,
|
||||||
crate::search::search_books,
|
crate::search::search_books,
|
||||||
@@ -27,6 +28,12 @@ use utoipa::OpenApi;
|
|||||||
crate::tokens::list_tokens,
|
crate::tokens::list_tokens,
|
||||||
crate::tokens::create_token,
|
crate::tokens::create_token,
|
||||||
crate::tokens::revoke_token,
|
crate::tokens::revoke_token,
|
||||||
|
crate::settings::get_settings,
|
||||||
|
crate::settings::get_setting,
|
||||||
|
crate::settings::update_setting,
|
||||||
|
crate::settings::clear_cache,
|
||||||
|
crate::settings::get_cache_stats,
|
||||||
|
crate::settings::get_thumbnail_stats,
|
||||||
),
|
),
|
||||||
components(
|
components(
|
||||||
schemas(
|
schemas(
|
||||||
@@ -51,6 +58,10 @@ use utoipa::OpenApi;
|
|||||||
crate::tokens::CreateTokenRequest,
|
crate::tokens::CreateTokenRequest,
|
||||||
crate::tokens::TokenResponse,
|
crate::tokens::TokenResponse,
|
||||||
crate::tokens::CreatedTokenResponse,
|
crate::tokens::CreatedTokenResponse,
|
||||||
|
crate::settings::UpdateSettingRequest,
|
||||||
|
crate::settings::ClearCacheResponse,
|
||||||
|
crate::settings::CacheStats,
|
||||||
|
crate::settings::ThumbnailStats,
|
||||||
ErrorResponse,
|
ErrorResponse,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -62,6 +73,7 @@ use utoipa::OpenApi;
|
|||||||
(name = "libraries", description = "Library management endpoints (Admin only)"),
|
(name = "libraries", description = "Library management endpoints (Admin only)"),
|
||||||
(name = "indexing", description = "Search index management and job control (Admin only)"),
|
(name = "indexing", description = "Search index management and job control (Admin only)"),
|
||||||
(name = "tokens", description = "API token management (Admin only)"),
|
(name = "tokens", description = "API token management (Admin only)"),
|
||||||
|
(name = "settings", description = "Application settings and cache management (Admin only)"),
|
||||||
),
|
),
|
||||||
modifiers(&SecurityAddon)
|
modifiers(&SecurityAddon)
|
||||||
)]
|
)]
|
||||||
|
|||||||
@@ -6,28 +6,29 @@ use axum::{
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use sqlx::Row;
|
use sqlx::Row;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::{error::ApiError, state::AppState};
|
use crate::{error::ApiError, state::AppState};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct UpdateSettingRequest {
|
pub struct UpdateSettingRequest {
|
||||||
pub value: Value,
|
pub value: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct ClearCacheResponse {
|
pub struct ClearCacheResponse {
|
||||||
pub success: bool,
|
pub success: bool,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct CacheStats {
|
pub struct CacheStats {
|
||||||
pub total_size_mb: f64,
|
pub total_size_mb: f64,
|
||||||
pub file_count: u64,
|
pub file_count: u64,
|
||||||
pub directory: String,
|
pub directory: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct ThumbnailStats {
|
pub struct ThumbnailStats {
|
||||||
pub total_size_mb: f64,
|
pub total_size_mb: f64,
|
||||||
pub file_count: u64,
|
pub file_count: u64,
|
||||||
@@ -43,7 +44,18 @@ pub fn settings_routes() -> Router<AppState> {
|
|||||||
.route("/settings/thumbnail/stats", get(get_thumbnail_stats))
|
.route("/settings/thumbnail/stats", get(get_thumbnail_stats))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_settings(State(state): State<AppState>) -> Result<Json<Value>, ApiError> {
|
/// List all settings
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/settings",
|
||||||
|
tag = "settings",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "All settings as key/value object"),
|
||||||
|
(status = 401, description = "Unauthorized"),
|
||||||
|
),
|
||||||
|
security(("Bearer" = []))
|
||||||
|
)]
|
||||||
|
pub async fn get_settings(State(state): State<AppState>) -> Result<Json<Value>, ApiError> {
|
||||||
let rows = sqlx::query(r#"SELECT key, value FROM app_settings"#)
|
let rows = sqlx::query(r#"SELECT key, value FROM app_settings"#)
|
||||||
.fetch_all(&state.pool)
|
.fetch_all(&state.pool)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -58,7 +70,20 @@ async fn get_settings(State(state): State<AppState>) -> Result<Json<Value>, ApiE
|
|||||||
Ok(Json(Value::Object(settings)))
|
Ok(Json(Value::Object(settings)))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_setting(
|
/// Get a single setting by key
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/settings/{key}",
|
||||||
|
tag = "settings",
|
||||||
|
params(("key" = String, Path, description = "Setting key")),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Setting value"),
|
||||||
|
(status = 404, description = "Setting not found"),
|
||||||
|
(status = 401, description = "Unauthorized"),
|
||||||
|
),
|
||||||
|
security(("Bearer" = []))
|
||||||
|
)]
|
||||||
|
pub async fn get_setting(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
axum::extract::Path(key): axum::extract::Path<String>,
|
axum::extract::Path(key): axum::extract::Path<String>,
|
||||||
) -> Result<Json<Value>, ApiError> {
|
) -> Result<Json<Value>, ApiError> {
|
||||||
@@ -76,7 +101,20 @@ async fn get_setting(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_setting(
|
/// Create or update a setting
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/settings/{key}",
|
||||||
|
tag = "settings",
|
||||||
|
params(("key" = String, Path, description = "Setting key")),
|
||||||
|
request_body = UpdateSettingRequest,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Updated setting value"),
|
||||||
|
(status = 401, description = "Unauthorized"),
|
||||||
|
),
|
||||||
|
security(("Bearer" = []))
|
||||||
|
)]
|
||||||
|
pub async fn update_setting(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
axum::extract::Path(key): axum::extract::Path<String>,
|
axum::extract::Path(key): axum::extract::Path<String>,
|
||||||
Json(body): Json<UpdateSettingRequest>,
|
Json(body): Json<UpdateSettingRequest>,
|
||||||
@@ -99,7 +137,18 @@ async fn update_setting(
|
|||||||
Ok(Json(value))
|
Ok(Json(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn clear_cache(State(_state): State<AppState>) -> Result<Json<ClearCacheResponse>, ApiError> {
|
/// Clear the image page cache
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/settings/cache/clear",
|
||||||
|
tag = "settings",
|
||||||
|
responses(
|
||||||
|
(status = 200, body = ClearCacheResponse),
|
||||||
|
(status = 401, description = "Unauthorized"),
|
||||||
|
),
|
||||||
|
security(("Bearer" = []))
|
||||||
|
)]
|
||||||
|
pub async fn clear_cache(State(_state): State<AppState>) -> Result<Json<ClearCacheResponse>, ApiError> {
|
||||||
let cache_dir = std::env::var("IMAGE_CACHE_DIR")
|
let cache_dir = std::env::var("IMAGE_CACHE_DIR")
|
||||||
.unwrap_or_else(|_| "/tmp/stripstream-image-cache".to_string());
|
.unwrap_or_else(|_| "/tmp/stripstream-image-cache".to_string());
|
||||||
|
|
||||||
@@ -128,7 +177,18 @@ async fn clear_cache(State(_state): State<AppState>) -> Result<Json<ClearCacheRe
|
|||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_cache_stats(State(_state): State<AppState>) -> Result<Json<CacheStats>, ApiError> {
|
/// Get image page cache statistics
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/settings/cache/stats",
|
||||||
|
tag = "settings",
|
||||||
|
responses(
|
||||||
|
(status = 200, body = CacheStats),
|
||||||
|
(status = 401, description = "Unauthorized"),
|
||||||
|
),
|
||||||
|
security(("Bearer" = []))
|
||||||
|
)]
|
||||||
|
pub async fn get_cache_stats(State(_state): State<AppState>) -> Result<Json<CacheStats>, ApiError> {
|
||||||
let cache_dir = std::env::var("IMAGE_CACHE_DIR")
|
let cache_dir = std::env::var("IMAGE_CACHE_DIR")
|
||||||
.unwrap_or_else(|_| "/tmp/stripstream-image-cache".to_string());
|
.unwrap_or_else(|_| "/tmp/stripstream-image-cache".to_string());
|
||||||
|
|
||||||
@@ -208,7 +268,18 @@ fn compute_dir_stats(path: &std::path::Path) -> (u64, u64) {
|
|||||||
(total_size, file_count)
|
(total_size, file_count)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_thumbnail_stats(State(_state): State<AppState>) -> Result<Json<ThumbnailStats>, ApiError> {
|
/// Get thumbnail storage statistics
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/settings/thumbnail/stats",
|
||||||
|
tag = "settings",
|
||||||
|
responses(
|
||||||
|
(status = 200, body = ThumbnailStats),
|
||||||
|
(status = 401, description = "Unauthorized"),
|
||||||
|
),
|
||||||
|
security(("Bearer" = []))
|
||||||
|
)]
|
||||||
|
pub async fn get_thumbnail_stats(State(_state): State<AppState>) -> Result<Json<ThumbnailStats>, ApiError> {
|
||||||
let settings = sqlx::query(r#"SELECT value FROM app_settings WHERE key = 'thumbnail'"#)
|
let settings = sqlx::query(r#"SELECT value FROM app_settings WHERE key = 'thumbnail'"#)
|
||||||
.fetch_optional(&_state.pool)
|
.fetch_optional(&_state.pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
Reference in New Issue
Block a user