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,
|
||||
};
|
||||
|
||||
/// 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(
|
||||
State(state): State<AppState>,
|
||||
Path(book_id): Path<Uuid>,
|
||||
|
||||
@@ -6,6 +6,7 @@ use utoipa::OpenApi;
|
||||
paths(
|
||||
crate::books::list_books,
|
||||
crate::books::get_book,
|
||||
crate::books::get_thumbnail,
|
||||
crate::books::list_series,
|
||||
crate::pages::get_page,
|
||||
crate::search::search_books,
|
||||
@@ -27,6 +28,12 @@ use utoipa::OpenApi;
|
||||
crate::tokens::list_tokens,
|
||||
crate::tokens::create_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(
|
||||
schemas(
|
||||
@@ -51,6 +58,10 @@ use utoipa::OpenApi;
|
||||
crate::tokens::CreateTokenRequest,
|
||||
crate::tokens::TokenResponse,
|
||||
crate::tokens::CreatedTokenResponse,
|
||||
crate::settings::UpdateSettingRequest,
|
||||
crate::settings::ClearCacheResponse,
|
||||
crate::settings::CacheStats,
|
||||
crate::settings::ThumbnailStats,
|
||||
ErrorResponse,
|
||||
)
|
||||
),
|
||||
@@ -62,6 +73,7 @@ use utoipa::OpenApi;
|
||||
(name = "libraries", description = "Library management endpoints (Admin only)"),
|
||||
(name = "indexing", description = "Search index management and job control (Admin only)"),
|
||||
(name = "tokens", description = "API token management (Admin only)"),
|
||||
(name = "settings", description = "Application settings and cache management (Admin only)"),
|
||||
),
|
||||
modifiers(&SecurityAddon)
|
||||
)]
|
||||
|
||||
@@ -6,28 +6,29 @@ use axum::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use sqlx::Row;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::{error::ApiError, state::AppState};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UpdateSettingRequest {
|
||||
pub value: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct ClearCacheResponse {
|
||||
pub success: bool,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CacheStats {
|
||||
pub total_size_mb: f64,
|
||||
pub file_count: u64,
|
||||
pub directory: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct ThumbnailStats {
|
||||
pub total_size_mb: f64,
|
||||
pub file_count: u64,
|
||||
@@ -43,7 +44,18 @@ pub fn settings_routes() -> Router<AppState> {
|
||||
.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"#)
|
||||
.fetch_all(&state.pool)
|
||||
.await?;
|
||||
@@ -58,7 +70,20 @@ async fn get_settings(State(state): State<AppState>) -> Result<Json<Value>, ApiE
|
||||
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>,
|
||||
axum::extract::Path(key): axum::extract::Path<String>,
|
||||
) -> 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>,
|
||||
axum::extract::Path(key): axum::extract::Path<String>,
|
||||
Json(body): Json<UpdateSettingRequest>,
|
||||
@@ -99,7 +137,18 @@ async fn update_setting(
|
||||
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")
|
||||
.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))
|
||||
}
|
||||
|
||||
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")
|
||||
.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)
|
||||
}
|
||||
|
||||
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'"#)
|
||||
.fetch_optional(&_state.pool)
|
||||
.await?;
|
||||
|
||||
Reference in New Issue
Block a user