Added merge
All checks were successful
Publish Metadata Agent Image / build-and-push-image (push) Successful in 1m7s
Publish Web Player Image / build-and-push-image (push) Successful in 1m9s
Publish Server Image / build-and-push-image (push) Successful in 2m15s

This commit is contained in:
2026-03-19 02:09:04 +00:00
parent 12d28170d2
commit e1210e6e20
4 changed files with 382 additions and 22 deletions

View File

@@ -566,6 +566,7 @@ pub struct Stats {
pub review_count: i64,
pub error_count: i64,
pub merged_count: i64,
pub active_merges: i64,
}
pub async fn get_stats(pool: &PgPool) -> Result<Stats, sqlx::Error> {
@@ -576,7 +577,139 @@ pub async fn get_stats(pool: &PgPool) -> Result<Stats, sqlx::Error> {
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?;
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 })
let (active_merges,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM artist_merges WHERE status IN ('pending','processing')").fetch_one(pool).await?;
Ok(Stats { total_tracks, total_artists, total_albums, pending_count, review_count, error_count, merged_count, active_merges })
}
// =================== Library search ===================
#[derive(Debug, Serialize, sqlx::FromRow)]
pub struct TrackRow {
pub id: i64,
pub title: String,
pub artist_name: String,
pub album_name: Option<String>,
pub year: Option<i32>,
pub track_number: Option<i32>,
pub duration_secs: Option<f64>,
pub genre: Option<String>,
}
#[derive(Debug, Serialize, sqlx::FromRow)]
pub struct AlbumRow {
pub id: i64,
pub name: String,
pub artist_name: String,
pub year: Option<i32>,
pub track_count: i64,
}
#[derive(Debug, Serialize, sqlx::FromRow)]
pub struct ArtistRow {
pub id: i64,
pub name: String,
pub album_count: i64,
pub track_count: i64,
}
pub async fn search_tracks(
pool: &PgPool,
q: &str, artist: &str, album: &str,
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,
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'
JOIN artists ar ON ar.id = ta.artist_id
LEFT JOIN albums al ON al.id = t.album_id
WHERE ($1 = '' OR t.title ILIKE '%' || $1 || '%')
AND ($2 = '' OR ar.name ILIKE '%' || $2 || '%')
AND ($3 = '' OR al.name ILIKE '%' || $3 || '%')
ORDER BY ar.name, al.name NULLS LAST, t.track_number NULLS LAST, t.title
LIMIT $4 OFFSET $5"#,
)
.bind(q).bind(artist).bind(album).bind(limit).bind(offset)
.fetch_all(pool).await
}
pub async fn count_tracks(pool: &PgPool, q: &str, artist: &str, album: &str) -> Result<i64, sqlx::Error> {
let (n,): (i64,) = sqlx::query_as(
r#"SELECT COUNT(*) 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
LEFT JOIN albums al ON al.id = t.album_id
WHERE ($1 = '' OR t.title ILIKE '%' || $1 || '%')
AND ($2 = '' OR ar.name ILIKE '%' || $2 || '%')
AND ($3 = '' OR al.name ILIKE '%' || $3 || '%')"#,
)
.bind(q).bind(artist).bind(album)
.fetch_one(pool).await?;
Ok(n)
}
pub async fn search_albums(
pool: &PgPool,
q: &str, artist: &str,
limit: i64, offset: i64,
) -> Result<Vec<AlbumRow>, sqlx::Error> {
sqlx::query_as::<_, AlbumRow>(
r#"SELECT a.id, a.name, ar.name AS artist_name, a.year,
COUNT(t.id) AS track_count
FROM albums a
JOIN artists ar ON ar.id = a.artist_id
LEFT JOIN tracks t ON t.album_id = a.id
WHERE ($1 = '' OR a.name ILIKE '%' || $1 || '%')
AND ($2 = '' OR ar.name ILIKE '%' || $2 || '%')
GROUP BY a.id, a.name, ar.name, a.year
ORDER BY ar.name, a.year NULLS LAST, a.name
LIMIT $3 OFFSET $4"#,
)
.bind(q).bind(artist).bind(limit).bind(offset)
.fetch_all(pool).await
}
pub async fn count_albums(pool: &PgPool, q: &str, artist: &str) -> Result<i64, sqlx::Error> {
let (n,): (i64,) = sqlx::query_as(
r#"SELECT COUNT(*) FROM albums a
JOIN artists ar ON ar.id = a.artist_id
WHERE ($1 = '' OR a.name ILIKE '%' || $1 || '%')
AND ($2 = '' OR ar.name ILIKE '%' || $2 || '%')"#,
)
.bind(q).bind(artist)
.fetch_one(pool).await?;
Ok(n)
}
pub async fn search_artists_lib(
pool: &PgPool,
q: &str,
limit: i64, offset: i64,
) -> Result<Vec<ArtistRow>, sqlx::Error> {
sqlx::query_as::<_, ArtistRow>(
r#"SELECT ar.id, ar.name,
COUNT(DISTINCT al.id) AS album_count,
COUNT(DISTINCT ta.track_id) AS track_count
FROM artists ar
LEFT JOIN albums al ON al.artist_id = ar.id
LEFT JOIN track_artists ta ON ta.artist_id = ar.id AND ta.role = 'primary'
WHERE ($1 = '' OR ar.name ILIKE '%' || $1 || '%')
GROUP BY ar.id, ar.name
ORDER BY ar.name
LIMIT $2 OFFSET $3"#,
)
.bind(q).bind(limit).bind(offset)
.fetch_all(pool).await
}
pub async fn count_artists_lib(pool: &PgPool, q: &str) -> Result<i64, sqlx::Error> {
let (n,): (i64,) = sqlx::query_as(
"SELECT COUNT(*) FROM artists WHERE ($1 = '' OR name ILIKE '%' || $1 || '%')"
)
.bind(q)
.fetch_one(pool).await?;
Ok(n)
}
// =================== Artist Merges ===================