pinakes-server: integrate plugin system into routes & application state

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ib5d482326cae1dcb43603bffb76a6a186a6a6964
This commit is contained in:
raf 2026-03-08 15:06:11 +03:00
commit e9c5390c45
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
6 changed files with 206 additions and 46 deletions

View file

@ -48,6 +48,15 @@ pub async fn create_collection(
req.filter_query.as_deref(),
)
.await?;
state.emit_plugin_event(
"CollectionCreated",
&serde_json::json!({
"id": col.id.to_string(),
"name": col.name,
}),
);
Ok(Json(CollectionResponse::from(col)))
}
@ -73,6 +82,12 @@ pub async fn delete_collection(
Path(id): Path<Uuid>,
) -> Result<Json<serde_json::Value>, ApiError> {
state.storage.delete_collection(id).await?;
state.emit_plugin_event(
"CollectionDeleted",
&serde_json::json!({"id": id.to_string()}),
);
Ok(Json(serde_json::json!({"deleted": true})))
}

View file

@ -87,8 +87,12 @@ pub async fn import_media(
State(state): State<AppState>,
Json(req): Json<ImportRequest>,
) -> Result<Json<ImportResponse>, ApiError> {
let result =
pinakes_core::import::import_file(&state.storage, &req.path).await?;
let result = pinakes_core::import::import_file(
&state.storage,
&req.path,
state.plugin_pipeline.as_ref(),
)
.await?;
if let Some(ref dispatcher) = state.webhook_dispatcher {
let id = result.media_id.0.to_string();
@ -197,6 +201,11 @@ pub async fn update_media(
});
}
state.emit_plugin_event(
"MediaUpdated",
&serde_json::json!({"media_id": item.id.to_string()}),
);
Ok(Json(MediaResponse::from(item)))
}
@ -227,6 +236,14 @@ pub async fn delete_media(
tracing::warn!(path = %thumb_path.display(), error = %e, "failed to remove thumbnail");
}
state.emit_plugin_event(
"MediaDeleted",
&serde_json::json!({
"media_id": media_id.to_string(),
"path": item.path.to_string_lossy(),
}),
);
Ok(Json(serde_json::json!({"deleted": true})))
}
@ -362,8 +379,12 @@ pub async fn import_with_options(
State(state): State<AppState>,
Json(req): Json<ImportWithOptionsRequest>,
) -> Result<Json<ImportResponse>, ApiError> {
let result =
pinakes_core::import::import_file(&state.storage, &req.path).await?;
let result = pinakes_core::import::import_file(
&state.storage,
&req.path,
state.plugin_pipeline.as_ref(),
)
.await?;
if !result.was_duplicate {
apply_import_post_processing(
@ -400,7 +421,13 @@ pub async fn batch_import(
let mut errors = 0usize;
for path in &req.paths {
match pinakes_core::import::import_file(&state.storage, path).await {
match pinakes_core::import::import_file(
&state.storage,
path,
state.plugin_pipeline.as_ref(),
)
.await
{
Ok(result) => {
if result.was_duplicate {
duplicates += 1;
@ -458,6 +485,7 @@ pub async fn import_directory_endpoint(
&req.path,
&ignore_patterns,
concurrency,
state.plugin_pipeline.as_ref(),
)
.await?;
@ -1065,6 +1093,11 @@ pub async fn soft_delete_media(
)
.await?;
state.emit_plugin_event(
"MediaDeleted",
&serde_json::json!({"media_id": media_id.to_string(), "trashed": true}),
);
Ok(Json(serde_json::json!({"deleted": true, "trashed": true})))
}
@ -1106,6 +1139,11 @@ pub async fn restore_media(
)
.await?;
state.emit_plugin_event(
"MediaUpdated",
&serde_json::json!({"media_id": media_id.to_string(), "restored": true}),
);
Ok(Json(MediaResponse::from(item)))
}

View file

@ -127,6 +127,17 @@ pub async fn toggle_plugin(
})?;
}
// Re-discover capabilities after toggle so cached data stays current
if let Some(ref pipeline) = state.plugin_pipeline
&& let Err(e) = pipeline.discover_capabilities().await
{
tracing::warn!(
plugin_id = %id,
error = %e,
"failed to re-discover capabilities after plugin toggle"
);
}
Ok(Json(serde_json::json!({
"id": id,
"enabled": req.enabled
@ -150,5 +161,16 @@ pub async fn reload_plugin(
))
})?;
// Re-discover capabilities after reload so cached data stays current
if let Some(ref pipeline) = state.plugin_pipeline
&& let Err(e) = pipeline.discover_capabilities().await
{
tracing::warn!(
plugin_id = %id,
error = %e,
"failed to re-discover capabilities after plugin reload"
);
}
Ok(Json(serde_json::json!({"reloaded": true})))
}

View file

@ -58,6 +58,15 @@ pub async fn tag_media(
) -> Result<Json<serde_json::Value>, ApiError> {
pinakes_core::tags::tag_media(&state.storage, MediaId(media_id), req.tag_id)
.await?;
state.emit_plugin_event(
"MediaTagged",
&serde_json::json!({
"media_id": media_id.to_string(),
"tag_id": req.tag_id.to_string(),
}),
);
Ok(Json(serde_json::json!({"tagged": true})))
}
@ -67,6 +76,15 @@ pub async fn untag_media(
) -> Result<Json<serde_json::Value>, ApiError> {
pinakes_core::tags::untag_media(&state.storage, MediaId(media_id), tag_id)
.await?;
state.emit_plugin_event(
"MediaUntagged",
&serde_json::json!({
"media_id": media_id.to_string(),
"tag_id": tag_id.to_string(),
}),
);
Ok(Json(serde_json::json!({"untagged": true})))
}