129 lines
4.2 KiB
Rust
129 lines
4.2 KiB
Rust
use crate::route::ToAPI;
|
|
use serde::{Deserialize, Serialize};
|
|
use serde_json::json;
|
|
use sqlx::FromRow;
|
|
|
|
use crate::get_pg;
|
|
|
|
use super::track::Track;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
|
pub struct Album {
|
|
/// Unique identifier for the album in the database.
|
|
pub id: uuid::Uuid,
|
|
/// Human-readable title of the album.
|
|
pub title: String,
|
|
/// Optional foreign key referencing the artist ID. If present, this indicates that this album is associated with a specific artist.
|
|
pub artist: Option<uuid::Uuid>,
|
|
}
|
|
impl Album {
|
|
/// Creates a new album in the database with the given `title` and optional `artist`.
|
|
pub async fn create(title: &str, artist: Option<uuid::Uuid>) -> Self {
|
|
sqlx::query_as("INSERT INTO album (title, artist) VALUES ($1, $2) RETURNING *")
|
|
.bind(title)
|
|
.bind(artist)
|
|
.fetch_one(get_pg!())
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
/// Retrieves a list of all albums in the database.
|
|
pub async fn find_all() -> Vec<Self> {
|
|
sqlx::query_as("SELECT * FROM album")
|
|
.fetch_all(get_pg!())
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
/// Finds an album in the database by its `title` and optional `artist`.
|
|
pub async fn find(title: &str, artist: Option<uuid::Uuid>) -> Option<Self> {
|
|
sqlx::query_as("SELECT * FROM album WHERE title = $1 AND artist = $2")
|
|
.bind(title)
|
|
.bind(artist)
|
|
.fetch_optional(get_pg!())
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
/// Retrieves a list of albums from the database where the specified `column` matches a regular expression.
|
|
pub async fn find_regex_col(col: &str, query: &str) -> Vec<Self> {
|
|
sqlx::query_as(&format!("SELECT * FROM album WHERE {col} ~* $1"))
|
|
.bind(query)
|
|
.fetch_all(get_pg!())
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
/// Retrieves a list of albums from the database associated with the specified `artist`.
|
|
pub async fn find_of_artist(artist: &uuid::Uuid) -> Vec<Self> {
|
|
sqlx::query_as("SELECT * FROM album WHERE artist = $1")
|
|
.bind(artist)
|
|
.fetch_all(get_pg!())
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
/// Deletes an album from the database.
|
|
pub async fn remove(&self) {
|
|
sqlx::query("DELETE FROM album WHERE id = $1")
|
|
.bind(self.id)
|
|
.fetch_one(get_pg!())
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
/// Retrieves an album from the database by its `id`.
|
|
pub async fn get(id: &uuid::Uuid) -> Option<Self> {
|
|
sqlx::query_as("SELECT * FROM album WHERE id = $1")
|
|
.bind(id)
|
|
.fetch_optional(get_pg!())
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
/// Retrieves a list of tracks from the database associated with the specified `album`.
|
|
pub async fn get_tracks_of_album(album: &uuid::Uuid) -> Vec<Track> {
|
|
sqlx::query_as("SELECT * FROM track WHERE album = $1")
|
|
.bind(album)
|
|
.fetch_all(get_pg!())
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
/// Retrieves the cover image of an album, or `None` if it doesn't exist.
|
|
///
|
|
/// This method first retrieves a list of tracks in the album, then looks for a file named `cover.{ext}` where `{ext}` is one of `png`, `jpg`, `jpeg`, `avif`. The first existing cover file found will be returned.
|
|
pub async fn get_cover(&self) -> Option<String> {
|
|
let track_path = Self::get_tracks_of_album(&self.id)
|
|
.await
|
|
.first()?
|
|
.path
|
|
.clone();
|
|
let track_path = std::path::Path::new(&track_path);
|
|
|
|
for ext in ["png", "jpg", "jpeg", "avif", "webp"] {
|
|
let cover_file = track_path.parent()?.join(format!("cover.{ext}"));
|
|
|
|
if cover_file.exists() {
|
|
return Some(cover_file.to_str().unwrap().to_string());
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
}
|
|
|
|
impl ToAPI for Album {
|
|
async fn api(&self) -> serde_json::Value {
|
|
json!({
|
|
"id": &self.id,
|
|
"title": &self.title,
|
|
"artist": self.artist,
|
|
"cover_url": if self.get_cover().await.is_some() {
|
|
Some(format!("/album/{}/cover", self.id))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
}
|