fleshed out subsonic more
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
use sea_orm::sea_query::Expr;
|
||||||
use sea_orm::*;
|
use sea_orm::*;
|
||||||
|
|
||||||
use crate::entities::album::{self, ActiveModel, Entity as Albums, Model as Album};
|
use crate::entities::album::{self, ActiveModel, Entity as Albums, Model as Album};
|
||||||
@@ -84,6 +85,131 @@ pub async fn get_by_artist(db: &DatabaseConnection, artist_id: i32) -> DbResult<
|
|||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_random(db: &DatabaseConnection, count: u64) -> DbResult<Vec<Album>> {
|
||||||
|
Ok(Albums::find()
|
||||||
|
.order_by(Expr::cust("RANDOM()"), Order::Asc)
|
||||||
|
.limit(count)
|
||||||
|
.all(db)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_newest(db: &DatabaseConnection, limit: u64, offset: u64) -> DbResult<Vec<Album>> {
|
||||||
|
Ok(Albums::find()
|
||||||
|
.order_by_desc(Expr::cust("COALESCE(year, 0)"))
|
||||||
|
.order_by_asc(album::Column::Name)
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
|
.all(db)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_by_year_range(
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
from: i32,
|
||||||
|
to: i32,
|
||||||
|
limit: u64,
|
||||||
|
offset: u64,
|
||||||
|
) -> DbResult<Vec<Album>> {
|
||||||
|
let (lo, hi) = if from <= to { (from, to) } else { (to, from) };
|
||||||
|
Ok(Albums::find()
|
||||||
|
.filter(album::Column::Year.gte(lo))
|
||||||
|
.filter(album::Column::Year.lte(hi))
|
||||||
|
.order_by_asc(album::Column::Year)
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
|
.all(db)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_by_genre(
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
genre: &str,
|
||||||
|
limit: u64,
|
||||||
|
offset: u64,
|
||||||
|
) -> DbResult<Vec<Album>> {
|
||||||
|
use crate::entities::track;
|
||||||
|
|
||||||
|
// Find album IDs that have tracks matching this genre
|
||||||
|
let pattern = format!("%{genre}%");
|
||||||
|
let album_ids: Vec<i32> = track::Entity::find()
|
||||||
|
.filter(Expr::cust_with_values(
|
||||||
|
"LOWER(genre) LIKE LOWER(?)",
|
||||||
|
[pattern],
|
||||||
|
))
|
||||||
|
.filter(track::Column::AlbumId.is_not_null())
|
||||||
|
.all(db)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|t| t.album_id)
|
||||||
|
.collect::<std::collections::HashSet<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if album_ids.is_empty() {
|
||||||
|
return Ok(vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Albums::find()
|
||||||
|
.filter(album::Column::Id.is_in(album_ids))
|
||||||
|
.order_by_asc(album::Column::Name)
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
|
.all(db)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_alphabetical_by_artist(
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
limit: u64,
|
||||||
|
offset: u64,
|
||||||
|
) -> DbResult<Vec<Album>> {
|
||||||
|
Ok(Albums::find()
|
||||||
|
.order_by_asc(Expr::cust("LOWER(album_artist)"))
|
||||||
|
.order_by_asc(Expr::cust("LOWER(name)"))
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
|
.all(db)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_recent(db: &DatabaseConnection, limit: u64, offset: u64) -> DbResult<Vec<Album>> {
|
||||||
|
use crate::entities::track;
|
||||||
|
|
||||||
|
// Find albums ordered by their most recently added track
|
||||||
|
let tracks = track::Entity::find()
|
||||||
|
.filter(track::Column::AlbumId.is_not_null())
|
||||||
|
.order_by_desc(track::Column::AddedAt)
|
||||||
|
.all(db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Collect unique album IDs in order of most recent track
|
||||||
|
let mut seen = std::collections::HashSet::new();
|
||||||
|
let album_ids: Vec<i32> = tracks
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|t| t.album_id)
|
||||||
|
.filter(|id| seen.insert(*id))
|
||||||
|
.skip(offset as usize)
|
||||||
|
.take(limit as usize)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if album_ids.is_empty() {
|
||||||
|
return Ok(vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch albums and preserve the ordering
|
||||||
|
let albums = Albums::find()
|
||||||
|
.filter(album::Column::Id.is_in(album_ids.clone()))
|
||||||
|
.all(db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let album_map: std::collections::HashMap<i32, Album> =
|
||||||
|
albums.into_iter().map(|a| (a.id, a)).collect();
|
||||||
|
Ok(album_ids
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|id| album_map.get(&id).cloned())
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn update(db: &DatabaseConnection, id: i32, model: ActiveModel) -> DbResult<Album> {
|
pub async fn update(db: &DatabaseConnection, id: i32, model: ActiveModel) -> DbResult<Album> {
|
||||||
let mut active = model;
|
let mut active = model;
|
||||||
active.id = Set(id);
|
active.id = Set(id);
|
||||||
|
|||||||
@@ -171,6 +171,54 @@ pub async fn get_random(db: &DatabaseConnection, count: u64) -> DbResult<Vec<Tra
|
|||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get random tracks with optional genre and year range filters.
|
||||||
|
pub async fn get_random_filtered(
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
count: u64,
|
||||||
|
genre: Option<&str>,
|
||||||
|
from_year: Option<i32>,
|
||||||
|
to_year: Option<i32>,
|
||||||
|
) -> DbResult<Vec<Track>> {
|
||||||
|
let mut query = Tracks::find();
|
||||||
|
if let Some(g) = genre {
|
||||||
|
let pattern = format!("%{g}%");
|
||||||
|
query = query.filter(Expr::cust_with_values(
|
||||||
|
"LOWER(genre) LIKE LOWER(?)",
|
||||||
|
[pattern],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Some(y) = from_year {
|
||||||
|
query = query.filter(track::Column::Year.gte(y));
|
||||||
|
}
|
||||||
|
if let Some(y) = to_year {
|
||||||
|
query = query.filter(track::Column::Year.lte(y));
|
||||||
|
}
|
||||||
|
Ok(query
|
||||||
|
.order_by(Expr::cust("RANDOM()"), Order::Asc)
|
||||||
|
.limit(count)
|
||||||
|
.all(db)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get tracks matching a genre with pagination.
|
||||||
|
pub async fn get_by_genre_paginated(
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
genre: &str,
|
||||||
|
limit: u64,
|
||||||
|
offset: u64,
|
||||||
|
) -> DbResult<Vec<Track>> {
|
||||||
|
let pattern = format!("%{genre}%");
|
||||||
|
Ok(Tracks::find()
|
||||||
|
.filter(Expr::cust_with_values(
|
||||||
|
"LOWER(genre) LIKE LOWER(?)",
|
||||||
|
[pattern],
|
||||||
|
))
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
|
.all(db)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get tracks added within the last N days.
|
/// Get tracks added within the last N days.
|
||||||
pub async fn get_recent(db: &DatabaseConnection, days: u32, limit: u64) -> DbResult<Vec<Track>> {
|
pub async fn get_recent(db: &DatabaseConnection, days: u32, limit: u64) -> DbResult<Vec<Track>> {
|
||||||
let cutoff = Utc::now().naive_utc() - chrono::Duration::days(i64::from(days));
|
let cutoff = Utc::now().naive_utc() - chrono::Duration::days(i64::from(days));
|
||||||
|
|||||||
Reference in New Issue
Block a user