Skip to content

Commit a0c6ae6

Browse files
authored
Delete song endpoint (#167)
* Added initial code for delete song endpoint * Saving changes * More changes * Made delete song endpoint available * Response change * Added test function * Changes to code Fixing errors * How did I miss this? * Got the test working * Code cleanup and formatting * Version bump * Code changes * Workflow change
1 parent c7230d3 commit a0c6ae6

File tree

7 files changed

+359
-5
lines changed

7 files changed

+359
-5
lines changed

.github/workflows/workflow.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ jobs:
6868
env:
6969
ROOT_DIRECTORY: "/tmp"
7070
DATABASE_URL: "postgres://${{ secrets.POSTGRES_USER }}:${{ secrets.POSTGRES_PASSWORD }}@localhost:5432/${{ secrets.POSTGRES_DB }}"
71-
run: cargo test --verbose
71+
run: cargo test --verbose -- --test-threads=1
7272

7373
- name: Run clippy
7474
run: cargo clippy -- -D warnings

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "icarus"
3-
version = "0.1.98"
3+
version = "0.1.99"
44
edition = "2024"
55
rust-version = "1.88"
66

src/callers/coverart.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,88 @@ pub mod cov_db {
376376
Err(_) => Err(sqlx::Error::RowNotFound),
377377
}
378378
}
379+
380+
pub async fn get_coverart_with_song_id(
381+
pool: &sqlx::PgPool,
382+
song_id: &uuid::Uuid,
383+
) -> Result<icarus_models::coverart::CoverArt, sqlx::Error> {
384+
let result = sqlx::query(
385+
r#"
386+
SELECT id, title, path, song_id FROM "coverart" WHERE song_id = $1;
387+
"#,
388+
)
389+
.bind(song_id)
390+
.fetch_one(pool)
391+
.await
392+
.map_err(|e| {
393+
eprintln!("Error querying data: {e:?}");
394+
});
395+
396+
match result {
397+
Ok(row) => Ok(icarus_models::coverart::CoverArt {
398+
id: row
399+
.try_get("id")
400+
.map_err(|_e| sqlx::Error::RowNotFound)
401+
.unwrap(),
402+
title: row
403+
.try_get("title")
404+
.map_err(|_e| sqlx::Error::RowNotFound)
405+
.unwrap(),
406+
path: row
407+
.try_get("path")
408+
.map_err(|_e| sqlx::Error::RowNotFound)
409+
.unwrap(),
410+
data: Vec::new(),
411+
song_id: row
412+
.try_get("song_id")
413+
.map_err(|_e| sqlx::Error::RowNotFound)
414+
.unwrap(),
415+
}),
416+
Err(_) => Err(sqlx::Error::RowNotFound),
417+
}
418+
}
419+
420+
pub async fn delete_coverart(
421+
pool: &sqlx::PgPool,
422+
id: &uuid::Uuid,
423+
) -> Result<icarus_models::coverart::CoverArt, sqlx::Error> {
424+
let result = sqlx::query(
425+
r#"
426+
DELETE FROM "coverart"
427+
WHERE id = $1
428+
RETURNING id, title, path, song_id
429+
"#,
430+
)
431+
.bind(id)
432+
.fetch_one(pool)
433+
.await
434+
.map_err(|e| {
435+
eprintln!("Error deleting data: {e:?}");
436+
});
437+
438+
match result {
439+
Ok(row) => Ok(icarus_models::coverart::CoverArt {
440+
id: row
441+
.try_get("id")
442+
.map_err(|_e| sqlx::Error::RowNotFound)
443+
.unwrap(),
444+
title: row
445+
.try_get("title")
446+
.map_err(|_e| sqlx::Error::RowNotFound)
447+
.unwrap(),
448+
path: row
449+
.try_get("path")
450+
.map_err(|_e| sqlx::Error::RowNotFound)
451+
.unwrap(),
452+
song_id: row
453+
.try_get("song_id")
454+
.map_err(|_e| sqlx::Error::RowNotFound)
455+
.unwrap(),
456+
data: Vec::new(),
457+
}),
458+
Err(_err) => Err(sqlx::Error::RowNotFound),
459+
}
460+
}
379461
}
380462

381463
pub mod endpoint {

src/callers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub mod endpoints {
1919
pub const GETSONGS: &str = "/api/v2/song";
2020
pub const STREAMSONG: &str = "/api/v2/song/stream/{id}";
2121
pub const DOWNLOADSONG: &str = "/api/v2/song/download/{id}";
22+
pub const DELETESONG: &str = "/api/v2/song/{id}";
2223
pub const CREATECOVERART: &str = "/api/v2/coverart";
2324
pub const GETCOVERART: &str = "/api/v2/coverart";
2425
pub const DOWNLOADCOVERART: &str = "/api/v2/coverart/download/{id}";

src/callers/song.rs

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,20 @@ pub mod response {
173173
pub data: Vec<icarus_models::song::Song>,
174174
}
175175
}
176+
177+
pub mod delete_song {
178+
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
179+
pub struct SongAndCoverArt {
180+
pub song: icarus_models::song::Song,
181+
pub coverart: icarus_models::coverart::CoverArt,
182+
}
183+
184+
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
185+
pub struct Response {
186+
pub message: String,
187+
pub data: Vec<SongAndCoverArt>,
188+
}
189+
}
176190
}
177191

178192
// TODO: Might make a distinction between year and date in a song's tag at some point
@@ -336,6 +350,105 @@ pub mod song_db {
336350
Err(_) => Err(sqlx::Error::RowNotFound),
337351
}
338352
}
353+
354+
pub async fn delete_song(
355+
pool: &sqlx::PgPool,
356+
id: &uuid::Uuid,
357+
) -> Result<icarus_models::song::Song, sqlx::Error> {
358+
let result = sqlx::query(
359+
// icarus_models::song::Song,
360+
r#"
361+
DELETE FROM "song"
362+
WHERE id = $1
363+
RETURNING id, title, artist, album, album_artist, genre, year, disc, track, track_count, disc_count, duration, audio_type, date_created, filename, directory, user_id
364+
"#,
365+
)
366+
.bind(id)
367+
.fetch_one(pool)
368+
.await
369+
.map_err(|e| {
370+
eprintln!("Error deleting data: {e:?}")
371+
});
372+
373+
match result {
374+
Ok(row) => {
375+
let date_created_time: time::OffsetDateTime = row
376+
.try_get("date_created")
377+
.map_err(|_e| sqlx::Error::RowNotFound)
378+
.unwrap();
379+
380+
Ok(icarus_models::song::Song {
381+
id: row
382+
.try_get("id")
383+
.map_err(|_e| sqlx::Error::RowNotFound)
384+
.unwrap(),
385+
title: row
386+
.try_get("title")
387+
.map_err(|_e| sqlx::Error::RowNotFound)
388+
.unwrap(),
389+
artist: row
390+
.try_get("artist")
391+
.map_err(|_e| sqlx::Error::RowNotFound)
392+
.unwrap(),
393+
album_artist: row
394+
.try_get("album_artist")
395+
.map_err(|_e| sqlx::Error::RowNotFound)
396+
.unwrap(),
397+
album: row
398+
.try_get("album")
399+
.map_err(|_e| sqlx::Error::RowNotFound)
400+
.unwrap(),
401+
genre: row
402+
.try_get("genre")
403+
.map_err(|_e| sqlx::Error::RowNotFound)
404+
.unwrap(),
405+
year: row
406+
.try_get("year")
407+
.map_err(|_e| sqlx::Error::RowNotFound)
408+
.unwrap(),
409+
track: row
410+
.try_get("track")
411+
.map_err(|_e| sqlx::Error::RowNotFound)
412+
.unwrap(),
413+
disc: row
414+
.try_get("disc")
415+
.map_err(|_e| sqlx::Error::RowNotFound)
416+
.unwrap(),
417+
track_count: row
418+
.try_get("track_count")
419+
.map_err(|_e| sqlx::Error::RowNotFound)
420+
.unwrap(),
421+
disc_count: row
422+
.try_get("disc_count")
423+
.map_err(|_e| sqlx::Error::RowNotFound)
424+
.unwrap(),
425+
duration: row
426+
.try_get("duration")
427+
.map_err(|_e| sqlx::Error::RowNotFound)
428+
.unwrap(),
429+
audio_type: row
430+
.try_get("audio_type")
431+
.map_err(|_e| sqlx::Error::RowNotFound)
432+
.unwrap(),
433+
filename: row
434+
.try_get("filename")
435+
.map_err(|_e| sqlx::Error::RowNotFound)
436+
.unwrap(),
437+
directory: row
438+
.try_get("directory")
439+
.map_err(|_e| sqlx::Error::RowNotFound)
440+
.unwrap(),
441+
date_created: date_created_time.to_string(),
442+
user_id: row
443+
.try_get("user_id")
444+
.map_err(|_e| sqlx::Error::RowNotFound)
445+
.unwrap(),
446+
data: Vec::new(),
447+
})
448+
}
449+
Err(_) => Err(sqlx::Error::RowNotFound),
450+
}
451+
}
339452
}
340453

341454
mod song_queue {
@@ -1094,4 +1207,101 @@ pub mod endpoint {
10941207
),
10951208
}
10961209
}
1210+
1211+
pub async fn delete_song(
1212+
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
1213+
axum::extract::Path(id): axum::extract::Path<uuid::Uuid>,
1214+
) -> (
1215+
axum::http::StatusCode,
1216+
axum::Json<super::response::delete_song::Response>,
1217+
) {
1218+
let mut response = super::response::delete_song::Response::default();
1219+
1220+
match super::song_db::get_song(&pool, &id).await {
1221+
Ok(song) => {
1222+
match super::super::coverart::cov_db::get_coverart_with_song_id(&pool, &song.id)
1223+
.await
1224+
{
1225+
Ok(coverart) => {
1226+
let coverart_path = std::path::Path::new(&coverart.path);
1227+
if coverart_path.exists() {
1228+
match song.song_path() {
1229+
Ok(song_path) => {
1230+
match super::song_db::delete_song(&pool, &song.id).await {
1231+
Ok(deleted_song) => {
1232+
match super::super::coverart::cov_db::delete_coverart(
1233+
&pool,
1234+
&coverart.id,
1235+
)
1236+
.await
1237+
{
1238+
Ok(deleted_coverart) => {
1239+
match std::fs::remove_file(song_path) {
1240+
Ok(_) => match std::fs::remove_file(
1241+
&coverart.path,
1242+
) {
1243+
Ok(_) => {
1244+
response.message = String::from(super::super::response::SUCCESSFUL);
1245+
response.data.push(super::response::delete_song::SongAndCoverArt{ song: deleted_song, coverart: deleted_coverart });
1246+
(
1247+
axum::http::StatusCode::OK,
1248+
axum::Json(response),
1249+
)
1250+
}
1251+
Err(err) => {
1252+
response.message = err.to_string();
1253+
(axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response))
1254+
}
1255+
},
1256+
Err(err) => {
1257+
response.message = err.to_string();
1258+
(axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response))
1259+
}
1260+
}
1261+
}
1262+
1263+
Err(err) => {
1264+
response.message = err.to_string();
1265+
(axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response))
1266+
}
1267+
}
1268+
}
1269+
Err(err) => {
1270+
response.message = err.to_string();
1271+
(
1272+
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
1273+
axum::Json(response),
1274+
)
1275+
}
1276+
}
1277+
}
1278+
Err(err) => {
1279+
response.message = err.to_string();
1280+
(
1281+
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
1282+
axum::Json(response),
1283+
)
1284+
}
1285+
}
1286+
} else {
1287+
response.message =
1288+
String::from("Could not locate coverart on the filesystem");
1289+
(
1290+
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
1291+
axum::Json(response),
1292+
)
1293+
}
1294+
}
1295+
Err(err) => {
1296+
response.message = err.to_string();
1297+
(axum::http::StatusCode::NOT_FOUND, axum::Json(response))
1298+
}
1299+
}
1300+
}
1301+
Err(err) => {
1302+
response.message = err.to_string();
1303+
(axum::http::StatusCode::NOT_FOUND, axum::Json(response))
1304+
}
1305+
}
1306+
}
10971307
}

0 commit comments

Comments
 (0)