synthwave/src/library/album.rs
2024-10-06 01:12:26 +02:00

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
}
})
}
}