This commit is contained in:
+28
-13
@@ -1069,19 +1069,34 @@ impl App for AdminApp {
|
||||
),
|
||||
"admin_releases_edit",
|
||||
),
|
||||
Route::with_handler_and_name(
|
||||
"/releases/{id}/delete",
|
||||
cot::router::method::post(
|
||||
|session: Session, db: Database, path: Path<PathId>| async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
views::releases_delete(admin, &db, path.0.id).await
|
||||
},
|
||||
),
|
||||
"admin_releases_delete",
|
||||
),
|
||||
{
|
||||
let pool = Arc::clone(&pool);
|
||||
let pool_config = Arc::clone(&pool_config);
|
||||
Route::with_handler_and_name(
|
||||
"/releases/{id}/delete",
|
||||
cot::router::method::post(move |session: Session, db: Database, path: Path<PathId>| {
|
||||
let pool = Arc::clone(&pool);
|
||||
let pool_config = Arc::clone(&pool_config);
|
||||
async move {
|
||||
let admin = match auth::require_admin_or_redirect(&session, &db).await {
|
||||
Ok(u) => u,
|
||||
Err(resp) => return Ok(resp),
|
||||
};
|
||||
let pg_pool = pool
|
||||
.get_or_init(|| async {
|
||||
sqlx::postgres::PgPoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect(&pool_config.database_url)
|
||||
.await
|
||||
.expect("admin pool")
|
||||
})
|
||||
.await;
|
||||
views::releases_delete(admin, &db, pg_pool, path.0.id).await
|
||||
}
|
||||
}),
|
||||
"admin_releases_delete",
|
||||
)
|
||||
},
|
||||
// -- Media Files --------------------------------------------------
|
||||
Route::with_handler_and_name(
|
||||
"/media-files",
|
||||
|
||||
+22
-90
@@ -2066,7 +2066,12 @@ pub async fn bulk_library(
|
||||
.into_response();
|
||||
}
|
||||
|
||||
let affected = apply_library_action(pool, &kind, action, &ids).await?;
|
||||
let storage_dir = if action == "delete" && matches!(kind.as_str(), "releases" | "tracks") {
|
||||
AppConfig::load_with_db(&db).await.0.agent_storage_dir
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let affected = apply_library_action(pool, &kind, action, &ids, &storage_dir).await?;
|
||||
Json(MutationResponse { ok: true, affected }).into_response()
|
||||
}
|
||||
|
||||
@@ -3620,10 +3625,11 @@ async fn apply_library_action(
|
||||
kind: &str,
|
||||
action: &str,
|
||||
ids: &[i64],
|
||||
storage_dir: &str,
|
||||
) -> cot::Result<u64> {
|
||||
match action {
|
||||
"hide" | "show" => set_library_visibility(pool, kind, ids, action == "hide").await,
|
||||
"delete" => delete_library_items(pool, kind, ids).await,
|
||||
"delete" => delete_library_items(pool, kind, ids, storage_dir).await,
|
||||
_ => Ok(0),
|
||||
}
|
||||
}
|
||||
@@ -3674,10 +3680,15 @@ async fn set_library_visibility(
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
|
||||
async fn delete_library_items(pool: &PgPool, kind: &str, ids: &[i64]) -> cot::Result<u64> {
|
||||
async fn delete_library_items(
|
||||
pool: &PgPool,
|
||||
kind: &str,
|
||||
ids: &[i64],
|
||||
storage_dir: &str,
|
||||
) -> cot::Result<u64> {
|
||||
match kind {
|
||||
"releases" => delete_releases(pool, ids).await,
|
||||
"tracks" => delete_tracks(pool, ids).await,
|
||||
"releases" => delete_releases(pool, ids, storage_dir).await,
|
||||
"tracks" => delete_tracks(pool, ids, storage_dir).await,
|
||||
"playlists" => delete_playlists(pool, ids).await,
|
||||
_ => delete_artists(pool, ids).await,
|
||||
}
|
||||
@@ -3707,95 +3718,16 @@ async fn delete_artists(pool: &PgPool, ids: &[i64]) -> cot::Result<u64> {
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
|
||||
async fn delete_releases(pool: &PgPool, ids: &[i64]) -> cot::Result<u64> {
|
||||
let track_ids =
|
||||
sqlx::query_as::<_, IdRow>("SELECT id FROM furumusic__track WHERE release_id = ANY($1)")
|
||||
.bind(ids)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(e.to_string()))?
|
||||
.into_iter()
|
||||
.map(|row| row.id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !track_ids.is_empty() {
|
||||
sqlx::query("DELETE FROM furumusic__playlist_track WHERE track_id = ANY($1)")
|
||||
.bind(&track_ids)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(e.to_string()))?;
|
||||
sqlx::query("DELETE FROM furumusic__user_liked_track WHERE track_id = ANY($1)")
|
||||
.bind(&track_ids)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(e.to_string()))?;
|
||||
sqlx::query("DELETE FROM furumusic__play_history WHERE track_id = ANY($1)")
|
||||
.bind(&track_ids)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(e.to_string()))?;
|
||||
sqlx::query("DELETE FROM furumusic__track_genre WHERE track_id = ANY($1)")
|
||||
.bind(&track_ids)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(e.to_string()))?;
|
||||
sqlx::query("DELETE FROM furumusic__track_artist WHERE track_id = ANY($1)")
|
||||
.bind(&track_ids)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(e.to_string()))?;
|
||||
}
|
||||
|
||||
sqlx::query("DELETE FROM furumusic__track WHERE release_id = ANY($1)")
|
||||
.bind(ids)
|
||||
.execute(pool)
|
||||
async fn delete_releases(pool: &PgPool, ids: &[i64], storage_dir: &str) -> cot::Result<u64> {
|
||||
crate::library_cleanup::delete_releases(pool, ids, storage_dir)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(e.to_string()))?;
|
||||
sqlx::query("DELETE FROM furumusic__release_artist WHERE release_id = ANY($1)")
|
||||
.bind(ids)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(e.to_string()))?;
|
||||
let result = sqlx::query("DELETE FROM furumusic__release WHERE id = ANY($1)")
|
||||
.bind(ids)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(e.to_string()))?;
|
||||
Ok(result.rows_affected())
|
||||
.map_err(|error| cot::Error::internal(error.to_string()))
|
||||
}
|
||||
|
||||
async fn delete_tracks(pool: &PgPool, ids: &[i64]) -> cot::Result<u64> {
|
||||
sqlx::query("DELETE FROM furumusic__playlist_track WHERE track_id = ANY($1)")
|
||||
.bind(ids)
|
||||
.execute(pool)
|
||||
async fn delete_tracks(pool: &PgPool, ids: &[i64], storage_dir: &str) -> cot::Result<u64> {
|
||||
crate::library_cleanup::delete_tracks(pool, ids, storage_dir)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(e.to_string()))?;
|
||||
sqlx::query("DELETE FROM furumusic__user_liked_track WHERE track_id = ANY($1)")
|
||||
.bind(ids)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(e.to_string()))?;
|
||||
sqlx::query("DELETE FROM furumusic__play_history WHERE track_id = ANY($1)")
|
||||
.bind(ids)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(e.to_string()))?;
|
||||
sqlx::query("DELETE FROM furumusic__track_genre WHERE track_id = ANY($1)")
|
||||
.bind(ids)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(e.to_string()))?;
|
||||
sqlx::query("DELETE FROM furumusic__track_artist WHERE track_id = ANY($1)")
|
||||
.bind(ids)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(e.to_string()))?;
|
||||
let result = sqlx::query("DELETE FROM furumusic__track WHERE id = ANY($1)")
|
||||
.bind(ids)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(e.to_string()))?;
|
||||
Ok(result.rows_affected())
|
||||
.map_err(|error| cot::Error::internal(error.to_string()))
|
||||
}
|
||||
|
||||
async fn delete_playlists(pool: &PgPool, ids: &[i64]) -> cot::Result<u64> {
|
||||
|
||||
+3
-1
@@ -1262,9 +1262,11 @@ pub async fn releases_update(
|
||||
pub async fn releases_delete(
|
||||
_admin: AuthenticatedUser,
|
||||
db: &Database,
|
||||
pool: &sqlx::PgPool,
|
||||
release_id: i64,
|
||||
) -> cot::Result<cot::http::Response<Body>> {
|
||||
Release::delete_by_id(db, release_id)
|
||||
let (config, _) = AppConfig::load_with_db(db).await;
|
||||
crate::library_cleanup::delete_releases(pool, &[release_id], &config.agent_storage_dir)
|
||||
.await
|
||||
.map_err(|e| cot::Error::internal(format!("failed to delete release: {e}")))?;
|
||||
Ok(auth::redirect("/admin/releases"))
|
||||
|
||||
Reference in New Issue
Block a user