Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I28cf5b7b7cff8e90e123d624d97cf9656a6a6964
292 lines
8.3 KiB
Rust
292 lines
8.3 KiB
Rust
use axum::{Json, extract::State};
|
|
|
|
use crate::{
|
|
dto::{
|
|
ConfigResponse,
|
|
RootDirRequest,
|
|
ScanningConfigResponse,
|
|
ServerConfigResponse,
|
|
UiConfigResponse,
|
|
UpdateScanningRequest,
|
|
UpdateUiConfigRequest,
|
|
},
|
|
error::ApiError,
|
|
state::AppState,
|
|
};
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/v1/config",
|
|
tag = "config",
|
|
responses(
|
|
(status = 200, description = "Current server configuration", body = ConfigResponse),
|
|
(status = 401, description = "Unauthorized"),
|
|
(status = 403, description = "Forbidden"),
|
|
(status = 500, description = "Internal server error"),
|
|
),
|
|
security(("bearer_auth" = []))
|
|
)]
|
|
pub async fn get_config(
|
|
State(state): State<AppState>,
|
|
) -> Result<Json<ConfigResponse>, ApiError> {
|
|
let config = state.config.read().await;
|
|
let roots = state.storage.list_root_dirs().await?;
|
|
|
|
let config_path = state
|
|
.config_path
|
|
.as_ref()
|
|
.map(|p| p.to_string_lossy().to_string());
|
|
let config_writable = match &state.config_path {
|
|
Some(path) => {
|
|
if path.exists() {
|
|
std::fs::metadata(path).is_ok_and(|m| !m.permissions().readonly())
|
|
} else {
|
|
path.parent().is_some_and(|parent| {
|
|
std::fs::metadata(parent).is_ok_and(|m| !m.permissions().readonly())
|
|
})
|
|
}
|
|
},
|
|
None => false,
|
|
};
|
|
|
|
Ok(Json(ConfigResponse {
|
|
backend: config.storage.backend.to_string(),
|
|
database_path: config
|
|
.storage
|
|
.sqlite
|
|
.as_ref()
|
|
.map(|s| s.path.to_string_lossy().to_string()),
|
|
roots: roots
|
|
.iter()
|
|
.map(|p| p.to_string_lossy().to_string())
|
|
.collect(),
|
|
scanning: ScanningConfigResponse {
|
|
watch: config.scanning.watch,
|
|
poll_interval_secs: config.scanning.poll_interval_secs,
|
|
ignore_patterns: config.scanning.ignore_patterns.clone(),
|
|
},
|
|
server: ServerConfigResponse {
|
|
host: config.server.host.clone(),
|
|
port: config.server.port,
|
|
},
|
|
ui: UiConfigResponse::from(&config.ui),
|
|
config_path,
|
|
config_writable,
|
|
}))
|
|
}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/v1/config/ui",
|
|
tag = "config",
|
|
responses(
|
|
(status = 200, description = "UI configuration", body = UiConfigResponse),
|
|
(status = 401, description = "Unauthorized"),
|
|
(status = 500, description = "Internal server error"),
|
|
),
|
|
security(("bearer_auth" = []))
|
|
)]
|
|
pub async fn get_ui_config(
|
|
State(state): State<AppState>,
|
|
) -> Result<Json<UiConfigResponse>, ApiError> {
|
|
let config = state.config.read().await;
|
|
Ok(Json(UiConfigResponse::from(&config.ui)))
|
|
}
|
|
|
|
#[utoipa::path(
|
|
patch,
|
|
path = "/api/v1/config/ui",
|
|
tag = "config",
|
|
request_body = UpdateUiConfigRequest,
|
|
responses(
|
|
(status = 200, description = "Updated UI configuration", body = UiConfigResponse),
|
|
(status = 401, description = "Unauthorized"),
|
|
(status = 403, description = "Forbidden"),
|
|
(status = 500, description = "Internal server error"),
|
|
),
|
|
security(("bearer_auth" = []))
|
|
)]
|
|
pub async fn update_ui_config(
|
|
State(state): State<AppState>,
|
|
Json(req): Json<UpdateUiConfigRequest>,
|
|
) -> Result<Json<UiConfigResponse>, ApiError> {
|
|
let mut config = state.config.write().await;
|
|
if let Some(theme) = req.theme {
|
|
config.ui.theme = theme;
|
|
}
|
|
if let Some(default_view) = req.default_view {
|
|
config.ui.default_view = default_view;
|
|
}
|
|
if let Some(default_page_size) = req.default_page_size {
|
|
config.ui.default_page_size = default_page_size;
|
|
}
|
|
if let Some(default_view_mode) = req.default_view_mode {
|
|
config.ui.default_view_mode = default_view_mode;
|
|
}
|
|
if let Some(auto_play) = req.auto_play_media {
|
|
config.ui.auto_play_media = auto_play;
|
|
}
|
|
if let Some(show_thumbs) = req.show_thumbnails {
|
|
config.ui.show_thumbnails = show_thumbs;
|
|
}
|
|
if let Some(collapsed) = req.sidebar_collapsed {
|
|
config.ui.sidebar_collapsed = collapsed;
|
|
}
|
|
|
|
if let Some(ref path) = state.config_path {
|
|
config.save_to_file(path).map_err(ApiError)?;
|
|
}
|
|
|
|
Ok(Json(UiConfigResponse::from(&config.ui)))
|
|
}
|
|
|
|
#[utoipa::path(
|
|
patch,
|
|
path = "/api/v1/config/scanning",
|
|
tag = "config",
|
|
request_body = UpdateScanningRequest,
|
|
responses(
|
|
(status = 200, description = "Updated configuration", body = ConfigResponse),
|
|
(status = 401, description = "Unauthorized"),
|
|
(status = 403, description = "Forbidden"),
|
|
(status = 500, description = "Internal server error"),
|
|
),
|
|
security(("bearer_auth" = []))
|
|
)]
|
|
pub async fn update_scanning_config(
|
|
State(state): State<AppState>,
|
|
Json(req): Json<UpdateScanningRequest>,
|
|
) -> Result<Json<ConfigResponse>, ApiError> {
|
|
let mut config = state.config.write().await;
|
|
if let Some(watch) = req.watch {
|
|
config.scanning.watch = watch;
|
|
}
|
|
if let Some(interval) = req.poll_interval_secs {
|
|
config.scanning.poll_interval_secs = interval;
|
|
}
|
|
if let Some(patterns) = req.ignore_patterns {
|
|
config.scanning.ignore_patterns = patterns;
|
|
}
|
|
|
|
// Persist to disk if we have a config path
|
|
if let Some(ref path) = state.config_path {
|
|
config.save_to_file(path).map_err(ApiError)?;
|
|
}
|
|
|
|
let roots = state.storage.list_root_dirs().await?;
|
|
|
|
let config_path = state
|
|
.config_path
|
|
.as_ref()
|
|
.map(|p| p.to_string_lossy().to_string());
|
|
let config_writable = match &state.config_path {
|
|
Some(path) => {
|
|
if path.exists() {
|
|
std::fs::metadata(path).is_ok_and(|m| !m.permissions().readonly())
|
|
} else {
|
|
path.parent().is_some_and(|parent| {
|
|
std::fs::metadata(parent).is_ok_and(|m| !m.permissions().readonly())
|
|
})
|
|
}
|
|
},
|
|
None => false,
|
|
};
|
|
|
|
Ok(Json(ConfigResponse {
|
|
backend: config.storage.backend.to_string(),
|
|
database_path: config
|
|
.storage
|
|
.sqlite
|
|
.as_ref()
|
|
.map(|s| s.path.to_string_lossy().to_string()),
|
|
roots: roots
|
|
.iter()
|
|
.map(|p| p.to_string_lossy().to_string())
|
|
.collect(),
|
|
scanning: ScanningConfigResponse {
|
|
watch: config.scanning.watch,
|
|
poll_interval_secs: config.scanning.poll_interval_secs,
|
|
ignore_patterns: config.scanning.ignore_patterns.clone(),
|
|
},
|
|
server: ServerConfigResponse {
|
|
host: config.server.host.clone(),
|
|
port: config.server.port,
|
|
},
|
|
ui: UiConfigResponse::from(&config.ui),
|
|
config_path,
|
|
config_writable,
|
|
}))
|
|
}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/v1/config/roots",
|
|
tag = "config",
|
|
request_body = RootDirRequest,
|
|
responses(
|
|
(status = 200, description = "Updated configuration", body = ConfigResponse),
|
|
(status = 400, description = "Bad request"),
|
|
(status = 401, description = "Unauthorized"),
|
|
(status = 403, description = "Forbidden"),
|
|
(status = 500, description = "Internal server error"),
|
|
),
|
|
security(("bearer_auth" = []))
|
|
)]
|
|
pub async fn add_root(
|
|
State(state): State<AppState>,
|
|
Json(req): Json<RootDirRequest>,
|
|
) -> Result<Json<ConfigResponse>, ApiError> {
|
|
let path = std::path::PathBuf::from(&req.path);
|
|
|
|
if !path.exists() {
|
|
return Err(ApiError(pinakes_core::error::PinakesError::FileNotFound(
|
|
path,
|
|
)));
|
|
}
|
|
|
|
state.storage.add_root_dir(path.clone()).await?;
|
|
|
|
{
|
|
let mut config = state.config.write().await;
|
|
if !config.directories.roots.contains(&path) {
|
|
config.directories.roots.push(path);
|
|
}
|
|
if let Some(ref config_path) = state.config_path {
|
|
config.save_to_file(config_path).map_err(ApiError)?;
|
|
}
|
|
}
|
|
|
|
get_config(State(state)).await
|
|
}
|
|
|
|
#[utoipa::path(
|
|
delete,
|
|
path = "/api/v1/config/roots",
|
|
tag = "config",
|
|
request_body = RootDirRequest,
|
|
responses(
|
|
(status = 200, description = "Updated configuration", body = ConfigResponse),
|
|
(status = 401, description = "Unauthorized"),
|
|
(status = 403, description = "Forbidden"),
|
|
(status = 500, description = "Internal server error"),
|
|
),
|
|
security(("bearer_auth" = []))
|
|
)]
|
|
pub async fn remove_root(
|
|
State(state): State<AppState>,
|
|
Json(req): Json<RootDirRequest>,
|
|
) -> Result<Json<ConfigResponse>, ApiError> {
|
|
let path = std::path::PathBuf::from(&req.path);
|
|
|
|
state.storage.remove_root_dir(&path).await?;
|
|
|
|
{
|
|
let mut config = state.config.write().await;
|
|
config.directories.roots.retain(|r| r != &path);
|
|
if let Some(ref config_path) = state.config_path {
|
|
config.save_to_file(config_path).map_err(ApiError)?;
|
|
}
|
|
}
|
|
|
|
get_config(State(state)).await
|
|
}
|