Added merge
This commit is contained in:
@@ -588,6 +588,7 @@ pub struct TrackRow {
|
||||
pub id: i64,
|
||||
pub title: String,
|
||||
pub artist_name: String,
|
||||
pub album_id: Option<i64>,
|
||||
pub album_name: Option<String>,
|
||||
pub year: Option<i32>,
|
||||
pub track_number: Option<i32>,
|
||||
@@ -618,7 +619,7 @@ pub async fn search_tracks(
|
||||
limit: i64, offset: i64,
|
||||
) -> Result<Vec<TrackRow>, sqlx::Error> {
|
||||
sqlx::query_as::<_, TrackRow>(
|
||||
r#"SELECT t.id, t.title, ar.name AS artist_name, al.name AS album_name,
|
||||
r#"SELECT t.id, t.title, ar.name AS artist_name, t.album_id, al.name AS album_name,
|
||||
al.year, t.track_number, t.duration_secs, t.genre
|
||||
FROM tracks t
|
||||
JOIN track_artists ta ON ta.track_id = t.id AND ta.role = 'primary'
|
||||
@@ -712,6 +713,160 @@ pub async fn count_artists_lib(pool: &PgPool, q: &str) -> Result<i64, sqlx::Erro
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
// --- Track full details ---
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct TrackFull {
|
||||
pub id: i64,
|
||||
pub title: String,
|
||||
pub artist_id: i64,
|
||||
pub artist_name: String,
|
||||
pub album_id: Option<i64>,
|
||||
pub album_name: Option<String>,
|
||||
pub track_number: Option<i32>,
|
||||
pub duration_secs: Option<f64>,
|
||||
pub genre: Option<String>,
|
||||
pub file_hash: String,
|
||||
pub file_size: i64,
|
||||
pub storage_path: String,
|
||||
pub featured_artists: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn get_track_full(pool: &PgPool, id: i64) -> Result<Option<TrackFull>, sqlx::Error> {
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct Row {
|
||||
id: i64, title: String, artist_id: i64, artist_name: String,
|
||||
album_id: Option<i64>, album_name: Option<String>,
|
||||
track_number: Option<i32>, duration_secs: Option<f64>,
|
||||
genre: Option<String>, file_hash: String, file_size: i64, storage_path: String,
|
||||
}
|
||||
let row: Option<Row> = sqlx::query_as(
|
||||
r#"SELECT t.id, t.title,
|
||||
ta_p.artist_id, ar.name AS artist_name,
|
||||
t.album_id, al.name AS album_name,
|
||||
t.track_number, t.duration_secs, t.genre,
|
||||
t.file_hash, t.file_size, t.storage_path
|
||||
FROM tracks t
|
||||
JOIN track_artists ta_p ON ta_p.track_id = t.id AND ta_p.role = 'primary'
|
||||
JOIN artists ar ON ar.id = ta_p.artist_id
|
||||
LEFT JOIN albums al ON al.id = t.album_id
|
||||
WHERE t.id = $1"#,
|
||||
).bind(id).fetch_optional(pool).await?;
|
||||
|
||||
let row = match row { Some(r) => r, None => return Ok(None) };
|
||||
|
||||
let feat: Vec<(String,)> = sqlx::query_as(
|
||||
"SELECT ar.name FROM track_artists ta JOIN artists ar ON ar.id=ta.artist_id WHERE ta.track_id=$1 AND ta.role='featured' ORDER BY ta.id"
|
||||
).bind(id).fetch_all(pool).await?;
|
||||
|
||||
Ok(Some(TrackFull {
|
||||
id: row.id, title: row.title, artist_id: row.artist_id, artist_name: row.artist_name,
|
||||
album_id: row.album_id, album_name: row.album_name, track_number: row.track_number,
|
||||
duration_secs: row.duration_secs, genre: row.genre, file_hash: row.file_hash,
|
||||
file_size: row.file_size, storage_path: row.storage_path,
|
||||
featured_artists: feat.into_iter().map(|(n,)| n).collect(),
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct TrackUpdateFields {
|
||||
pub title: String,
|
||||
pub artist_id: i64,
|
||||
pub album_id: Option<i64>,
|
||||
pub track_number: Option<i32>,
|
||||
pub genre: Option<String>,
|
||||
#[serde(default)]
|
||||
pub featured_artists: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn update_track_metadata(pool: &PgPool, id: i64, f: &TrackUpdateFields) -> Result<(), sqlx::Error> {
|
||||
sqlx::query("UPDATE tracks SET title=$2, album_id=$3, track_number=$4, genre=$5 WHERE id=$1")
|
||||
.bind(id).bind(&f.title).bind(f.album_id).bind(f.track_number).bind(&f.genre)
|
||||
.execute(pool).await?;
|
||||
sqlx::query("UPDATE track_artists SET artist_id=$2 WHERE track_id=$1 AND role='primary'")
|
||||
.bind(id).bind(f.artist_id).execute(pool).await?;
|
||||
// Rebuild featured artists
|
||||
sqlx::query("DELETE FROM track_artists WHERE track_id=$1 AND role='featured'")
|
||||
.bind(id).execute(pool).await?;
|
||||
for name in &f.featured_artists {
|
||||
let feat_id = upsert_artist(pool, name).await?;
|
||||
link_track_artist(pool, id, feat_id, "featured").await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// --- Album full details ---
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct AlbumDetails {
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
pub year: Option<i32>,
|
||||
pub artist_id: i64,
|
||||
pub artist_name: String,
|
||||
pub tracks: Vec<AlbumTrackRow>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, sqlx::FromRow)]
|
||||
pub struct AlbumTrackRow {
|
||||
pub id: i64,
|
||||
pub title: String,
|
||||
pub track_number: Option<i32>,
|
||||
pub duration_secs: Option<f64>,
|
||||
pub artist_name: String,
|
||||
pub genre: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn get_album_details(pool: &PgPool, id: i64) -> Result<Option<AlbumDetails>, sqlx::Error> {
|
||||
let row: Option<(i64, String, Option<i32>, i64, String)> = sqlx::query_as(
|
||||
"SELECT a.id, a.name, a.year, ar.id, ar.name FROM albums a JOIN artists ar ON ar.id=a.artist_id WHERE a.id=$1"
|
||||
).bind(id).fetch_optional(pool).await?;
|
||||
let (aid, aname, ayear, artist_id, artist_name) = match row { Some(r) => r, None => return Ok(None) };
|
||||
let tracks: Vec<AlbumTrackRow> = sqlx::query_as(
|
||||
r#"SELECT t.id, t.title, t.track_number, t.duration_secs, ar.name AS artist_name, t.genre
|
||||
FROM tracks t
|
||||
JOIN track_artists ta ON ta.track_id=t.id AND ta.role='primary'
|
||||
JOIN artists ar ON ar.id=ta.artist_id
|
||||
WHERE t.album_id=$1 ORDER BY t.track_number NULLS LAST, t.title"#
|
||||
).bind(id).fetch_all(pool).await?;
|
||||
Ok(Some(AlbumDetails { id: aid, name: aname, year: ayear, artist_id, artist_name, tracks }))
|
||||
}
|
||||
|
||||
pub async fn update_album_full(pool: &PgPool, id: i64, name: &str, year: Option<i32>, artist_id: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query("UPDATE albums SET name=$2, year=$3, artist_id=$4 WHERE id=$1")
|
||||
.bind(id).bind(name).bind(year).bind(artist_id).execute(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn reorder_tracks(pool: &PgPool, orders: &[(i64, i32)]) -> Result<(), sqlx::Error> {
|
||||
for &(track_id, track_number) in orders {
|
||||
sqlx::query("UPDATE tracks SET track_number=$2 WHERE id=$1")
|
||||
.bind(track_id).bind(track_number).execute(pool).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_album_cover(pool: &PgPool, album_id: i64) -> Result<Option<(String, String)>, sqlx::Error> {
|
||||
let row: Option<(String, String)> = sqlx::query_as(
|
||||
"SELECT file_path, mime_type FROM album_images WHERE album_id=$1 LIMIT 1"
|
||||
).bind(album_id).fetch_optional(pool).await?;
|
||||
Ok(row)
|
||||
}
|
||||
|
||||
pub async fn search_albums_for_artist(pool: &PgPool, q: &str, artist_id: Option<i64>) -> Result<Vec<(i64, String)>, sqlx::Error> {
|
||||
if let Some(aid) = artist_id {
|
||||
let rows: Vec<(i64, String)> = sqlx::query_as(
|
||||
"SELECT id, name FROM albums WHERE artist_id=$1 AND ($2='' OR name ILIKE '%'||$2||'%') ORDER BY year NULLS LAST, name LIMIT 15"
|
||||
).bind(aid).bind(q).fetch_all(pool).await?;
|
||||
Ok(rows)
|
||||
} else {
|
||||
let rows: Vec<(i64, String)> = sqlx::query_as(
|
||||
"SELECT id, name FROM albums WHERE $1='' OR name ILIKE '%'||$1||'%' ORDER BY name LIMIT 15"
|
||||
).bind(q).fetch_all(pool).await?;
|
||||
Ok(rows)
|
||||
}
|
||||
}
|
||||
|
||||
// =================== Artist Merges ===================
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
|
||||
|
||||
Reference in New Issue
Block a user