pinakes-server: fix api key timing, notification scoping, and validate progress inputs

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ieb342b4b48034de0a2184cdf89d068316a6a6964
This commit is contained in:
raf 2026-03-08 00:42:17 +03:00
commit 2b2c1830a1
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
7 changed files with 334 additions and 179 deletions

View file

@ -8,11 +8,41 @@ use pinakes_core::{
};
use uuid::Uuid;
use crate::{dto::*, error::ApiError, state::AppState};
use crate::{
dto::{
BatchCollectionRequest,
BatchDeleteRequest,
BatchImportItemResult,
BatchImportRequest,
BatchImportResponse,
BatchMoveRequest,
BatchOperationResponse,
BatchTagRequest,
BatchUpdateRequest,
DirectoryImportRequest,
DirectoryPreviewFile,
DirectoryPreviewResponse,
EmptyTrashResponse,
ImportRequest,
ImportResponse,
ImportWithOptionsRequest,
MediaCountResponse,
MediaResponse,
MoveMediaRequest,
PaginationParams,
RenameMediaRequest,
SetCustomFieldRequest,
TrashInfoResponse,
TrashResponse,
UpdateMediaRequest,
},
error::ApiError,
state::AppState,
};
/// Apply tags and add to collection after a successful import.
/// Shared logic used by import_with_options, batch_import, and
/// import_directory_endpoint.
/// Shared logic used by `import_with_options`, `batch_import`, and
/// `import_directory_endpoint`.
async fn apply_import_post_processing(
storage: &DynStorageBackend,
media_id: MediaId,
@ -59,6 +89,17 @@ pub async fn import_media(
) -> Result<Json<ImportResponse>, ApiError> {
let result =
pinakes_core::import::import_file(&state.storage, &req.path).await?;
if let Some(ref dispatcher) = state.webhook_dispatcher {
let id = result.media_id.0.to_string();
dispatcher.dispatch(pinakes_core::webhooks::WebhookEvent::MediaCreated {
media_id: id.clone(),
});
dispatcher.dispatch(
pinakes_core::webhooks::WebhookEvent::ImportCompleted { media_id: id },
);
}
Ok(Json(ImportResponse {
media_id: result.media_id.0.to_string(),
was_duplicate: result.was_duplicate,
@ -150,6 +191,12 @@ pub async fn update_media(
)
.await?;
if let Some(ref dispatcher) = state.webhook_dispatcher {
dispatcher.dispatch(pinakes_core::webhooks::WebhookEvent::MediaUpdated {
media_id: item.id.0.to_string(),
});
}
Ok(Json(MediaResponse::from(item)))
}
@ -473,7 +520,7 @@ pub async fn preview_directory(
})?;
let recursive = req
.get("recursive")
.and_then(|v| v.as_bool())
.and_then(serde_json::Value::as_bool)
.unwrap_or(true);
let dir = std::path::PathBuf::from(path_str);
if !dir.is_dir() {
@ -515,8 +562,7 @@ pub async fn preview_directory(
// Skip hidden files/dirs
if path
.file_name()
.map(|n| n.to_string_lossy().starts_with('.'))
.unwrap_or(false)
.is_some_and(|n| n.to_string_lossy().starts_with('.'))
{
continue;
}
@ -528,7 +574,7 @@ pub async fn preview_directory(
&& let Some(mt) =
pinakes_core::media_type::MediaType::from_path(&path)
{
let size = entry.metadata().ok().map(|m| m.len()).unwrap_or(0);
let size = entry.metadata().ok().map_or(0, |m| m.len());
let file_name = path
.file_name()
.map(|n| n.to_string_lossy().to_string())
@ -579,8 +625,7 @@ pub async fn set_custom_field(
if req.value.len() > MAX_LONG_TEXT {
return Err(ApiError(
pinakes_core::error::PinakesError::InvalidOperation(format!(
"field value exceeds {} characters",
MAX_LONG_TEXT
"field value exceeds {MAX_LONG_TEXT} characters"
)),
));
}
@ -747,7 +792,7 @@ pub async fn batch_add_to_collection(
)
.await
{
Ok(_) => processed += 1,
Ok(()) => processed += 1,
Err(e) => errors.push(format!("{media_id}: {e}")),
}
}
@ -1113,10 +1158,7 @@ pub async fn permanent_delete_media(
Query(params): Query<std::collections::HashMap<String, String>>,
) -> Result<Json<serde_json::Value>, ApiError> {
let media_id = MediaId(id);
let permanent = params
.get("permanent")
.map(|v| v == "true")
.unwrap_or(false);
let permanent = params.get("permanent").is_some_and(|v| v == "true");
if permanent {
// Get item info before delete
@ -1161,6 +1203,12 @@ pub async fn permanent_delete_media(
tracing::warn!(path = %thumb_path.display(), error = %e, "failed to remove thumbnail");
}
if let Some(ref dispatcher) = state.webhook_dispatcher {
dispatcher.dispatch(pinakes_core::webhooks::WebhookEvent::MediaDeleted {
media_id: id.to_string(),
});
}
Ok(Json(
serde_json::json!({"deleted": true, "permanent": true}),
))