Added merge
This commit is contained in:
@@ -88,6 +88,7 @@ pub struct SimilarAlbum {
|
||||
pub similarity: f32,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
|
||||
pub struct AlbumImage {
|
||||
pub id: i64,
|
||||
@@ -416,6 +417,7 @@ pub async fn insert_album_image(
|
||||
Ok(row.0)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_album_images(pool: &PgPool, album_id: i64) -> Result<Vec<AlbumImage>, sqlx::Error> {
|
||||
sqlx::query_as::<_, AlbumImage>("SELECT * FROM album_images WHERE album_id = $1 ORDER BY image_type")
|
||||
.bind(album_id)
|
||||
@@ -563,6 +565,7 @@ pub struct Stats {
|
||||
pub pending_count: i64,
|
||||
pub review_count: i64,
|
||||
pub error_count: i64,
|
||||
pub merged_count: i64,
|
||||
}
|
||||
|
||||
pub async fn get_stats(pool: &PgPool) -> Result<Stats, sqlx::Error> {
|
||||
@@ -572,5 +575,200 @@ pub async fn get_stats(pool: &PgPool) -> Result<Stats, sqlx::Error> {
|
||||
let (pending_count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM pending_tracks WHERE status = 'pending'").fetch_one(pool).await?;
|
||||
let (review_count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM pending_tracks WHERE status = 'review'").fetch_one(pool).await?;
|
||||
let (error_count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM pending_tracks WHERE status = 'error'").fetch_one(pool).await?;
|
||||
Ok(Stats { total_tracks, total_artists, total_albums, pending_count, review_count, error_count })
|
||||
let (merged_count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM pending_tracks WHERE status = 'merged'").fetch_one(pool).await?;
|
||||
Ok(Stats { total_tracks, total_artists, total_albums, pending_count, review_count, error_count, merged_count })
|
||||
}
|
||||
|
||||
// =================== Artist Merges ===================
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
|
||||
pub struct ArtistMerge {
|
||||
pub id: Uuid,
|
||||
pub status: String,
|
||||
pub source_artist_ids: String,
|
||||
pub proposal: Option<String>,
|
||||
pub llm_notes: Option<String>,
|
||||
pub error_message: Option<String>,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ArtistFullData {
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
pub albums: Vec<AlbumFullData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct AlbumFullData {
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
pub year: Option<i32>,
|
||||
pub tracks: Vec<TrackBasic>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, sqlx::FromRow)]
|
||||
pub struct TrackBasic {
|
||||
pub id: i64,
|
||||
pub title: String,
|
||||
pub track_number: Option<i32>,
|
||||
pub storage_path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
pub struct TrackWithAlbum {
|
||||
pub id: i64,
|
||||
pub storage_path: String,
|
||||
pub album_name: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn insert_artist_merge(pool: &PgPool, source_artist_ids: &[i64]) -> Result<Uuid, sqlx::Error> {
|
||||
let ids_json = serde_json::to_string(source_artist_ids).unwrap_or_default();
|
||||
let row: (Uuid,) = sqlx::query_as(
|
||||
"INSERT INTO artist_merges (source_artist_ids) VALUES ($1) RETURNING id"
|
||||
).bind(&ids_json).fetch_one(pool).await?;
|
||||
Ok(row.0)
|
||||
}
|
||||
|
||||
pub async fn list_artist_merges(pool: &PgPool) -> Result<Vec<ArtistMerge>, sqlx::Error> {
|
||||
sqlx::query_as::<_, ArtistMerge>("SELECT * FROM artist_merges ORDER BY created_at DESC")
|
||||
.fetch_all(pool).await
|
||||
}
|
||||
|
||||
pub async fn get_artist_merge(pool: &PgPool, id: Uuid) -> Result<Option<ArtistMerge>, sqlx::Error> {
|
||||
sqlx::query_as::<_, ArtistMerge>("SELECT * FROM artist_merges WHERE id = $1")
|
||||
.bind(id).fetch_optional(pool).await
|
||||
}
|
||||
|
||||
pub async fn update_merge_status(pool: &PgPool, id: Uuid, status: &str, error: Option<&str>) -> Result<(), sqlx::Error> {
|
||||
sqlx::query("UPDATE artist_merges SET status = $2, error_message = $3, updated_at = NOW() WHERE id = $1")
|
||||
.bind(id).bind(status).bind(error).execute(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_merge_proposal(pool: &PgPool, id: Uuid, proposal_json: &str, notes: &str) -> Result<(), sqlx::Error> {
|
||||
sqlx::query("UPDATE artist_merges SET proposal = $2, llm_notes = $3, status = 'review', error_message = NULL, updated_at = NOW() WHERE id = $1")
|
||||
.bind(id).bind(proposal_json).bind(notes).execute(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_pending_merges_for_processing(pool: &PgPool) -> Result<Vec<Uuid>, sqlx::Error> {
|
||||
let rows: Vec<(Uuid,)> = sqlx::query_as(
|
||||
"SELECT id FROM artist_merges WHERE status = 'pending' ORDER BY created_at ASC LIMIT 5"
|
||||
).fetch_all(pool).await?;
|
||||
Ok(rows.into_iter().map(|(id,)| id).collect())
|
||||
}
|
||||
|
||||
pub async fn get_artists_full_data(pool: &PgPool, ids: &[i64]) -> Result<Vec<ArtistFullData>, sqlx::Error> {
|
||||
let mut result = Vec::new();
|
||||
for &id in ids {
|
||||
let artist: Artist = sqlx::query_as("SELECT id, name FROM artists WHERE id = $1")
|
||||
.bind(id).fetch_one(pool).await?;
|
||||
let albums: Vec<Album> = sqlx::query_as("SELECT * FROM albums WHERE artist_id = $1 ORDER BY year NULLS LAST, name")
|
||||
.bind(id).fetch_all(pool).await?;
|
||||
let mut album_data = Vec::new();
|
||||
for album in albums {
|
||||
let tracks: Vec<TrackBasic> = sqlx::query_as(
|
||||
"SELECT id, title, track_number, storage_path FROM tracks WHERE album_id = $1 ORDER BY track_number NULLS LAST, title"
|
||||
).bind(album.id).fetch_all(pool).await?;
|
||||
album_data.push(AlbumFullData { id: album.id, name: album.name, year: album.year, tracks });
|
||||
}
|
||||
result.push(ArtistFullData { id, name: artist.name, albums: album_data });
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn get_tracks_with_albums_for_artist(pool: &PgPool, artist_id: i64) -> Result<Vec<TrackWithAlbum>, sqlx::Error> {
|
||||
sqlx::query_as::<_, TrackWithAlbum>(
|
||||
r#"SELECT t.id, t.storage_path, a.name as album_name
|
||||
FROM tracks t
|
||||
LEFT JOIN albums a ON a.id = t.album_id
|
||||
WHERE t.id IN (
|
||||
SELECT track_id FROM track_artists WHERE artist_id = $1 AND role = 'primary'
|
||||
)"#
|
||||
).bind(artist_id).fetch_all(pool).await
|
||||
}
|
||||
|
||||
pub async fn rename_artist(pool: &PgPool, id: i64, new_name: &str) -> Result<(), sqlx::Error> {
|
||||
sqlx::query("UPDATE artists SET name = $2 WHERE id = $1")
|
||||
.bind(id).bind(new_name).execute(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_artist(pool: &PgPool, id: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query("DELETE FROM artists WHERE id = $1")
|
||||
.bind(id).execute(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn rename_album(pool: &PgPool, id: i64, new_name: &str) -> Result<(), sqlx::Error> {
|
||||
sqlx::query("UPDATE albums SET name = $2 WHERE id = $1")
|
||||
.bind(id).bind(new_name).execute(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_album_artist(pool: &PgPool, album_id: i64, artist_id: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query("UPDATE albums SET artist_id = $2 WHERE id = $1")
|
||||
.bind(album_id).bind(artist_id).execute(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn move_albums_to_artist(pool: &PgPool, from_artist_id: i64, to_artist_id: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query("UPDATE albums SET artist_id = $2 WHERE artist_id = $1")
|
||||
.bind(from_artist_id).bind(to_artist_id).execute(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn move_track_artists(pool: &PgPool, from_artist_id: i64, to_artist_id: i64) -> Result<(), sqlx::Error> {
|
||||
// Update, but avoid duplicate (track_id, artist_id, role) - delete first any conflicting rows
|
||||
sqlx::query(
|
||||
r#"DELETE FROM track_artists
|
||||
WHERE artist_id = $2
|
||||
AND (track_id, role) IN (
|
||||
SELECT track_id, role FROM track_artists WHERE artist_id = $1
|
||||
)"#
|
||||
).bind(from_artist_id).bind(to_artist_id).execute(pool).await?;
|
||||
sqlx::query("UPDATE track_artists SET artist_id = $2 WHERE artist_id = $1")
|
||||
.bind(from_artist_id).bind(to_artist_id).execute(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_duplicate_track_ids_in_albums(pool: &PgPool, source_album_id: i64, target_album_id: i64) -> Result<Vec<i64>, sqlx::Error> {
|
||||
let rows: Vec<(i64,)> = sqlx::query_as(
|
||||
r#"SELECT t1.id FROM tracks t1
|
||||
JOIN tracks t2 ON t1.file_hash = t2.file_hash AND t2.album_id = $2
|
||||
WHERE t1.album_id = $1"#
|
||||
).bind(source_album_id).bind(target_album_id).fetch_all(pool).await?;
|
||||
Ok(rows.into_iter().map(|(id,)| id).collect())
|
||||
}
|
||||
|
||||
pub async fn get_track_storage_path(pool: &PgPool, track_id: i64) -> Result<Option<String>, sqlx::Error> {
|
||||
let row: Option<(String,)> = sqlx::query_as("SELECT storage_path FROM tracks WHERE id = $1")
|
||||
.bind(track_id).fetch_optional(pool).await?;
|
||||
Ok(row.map(|(p,)| p))
|
||||
}
|
||||
|
||||
pub async fn delete_track(pool: &PgPool, track_id: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query("DELETE FROM track_artists WHERE track_id = $1").bind(track_id).execute(pool).await?;
|
||||
sqlx::query("DELETE FROM tracks WHERE id = $1").bind(track_id).execute(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn move_tracks_to_album(pool: &PgPool, from_album_id: i64, to_album_id: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query("UPDATE tracks SET album_id = $2 WHERE album_id = $1")
|
||||
.bind(from_album_id).bind(to_album_id).execute(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_album(pool: &PgPool, id: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query("DELETE FROM albums WHERE id = $1")
|
||||
.bind(id).execute(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_track_storage_path(pool: &PgPool, track_id: i64, new_path: &str) -> Result<(), sqlx::Error> {
|
||||
sqlx::query("UPDATE tracks SET storage_path = $2 WHERE id = $1")
|
||||
.bind(track_id).bind(new_path).execute(pool).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user