finish postgres
This commit is contained in:
parent
7b7e1a4014
commit
08e24f63f4
16 changed files with 454 additions and 306 deletions
|
@ -1,3 +1,4 @@
|
|||
use crate::route::ToAPI;
|
||||
use mongodb::bson::doc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
|
@ -15,7 +16,7 @@ pub struct Album {
|
|||
}
|
||||
|
||||
impl Album {
|
||||
pub async fn create(title: &str, artist: Option<&str>) -> Self {
|
||||
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)
|
||||
|
@ -24,6 +25,52 @@ impl Album {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn find_all() -> Vec<Self> {
|
||||
sqlx::query_as("SELECT * FROM album")
|
||||
.fetch_all(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
pub async fn remove(&self) {
|
||||
sqlx::query("DELETE FROM album WHERE id = $1")
|
||||
.bind(self.id)
|
||||
.fetch(get_pg!());
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
pub async fn get_tracks_of_album(album: &uuid::Uuid) -> Vec<Track> {
|
||||
sqlx::query_as("SELECT * FROM track WHERE album = $1")
|
||||
.bind(album)
|
||||
|
@ -55,7 +102,7 @@ impl Album {
|
|||
}
|
||||
}
|
||||
|
||||
impl Album {
|
||||
impl ToAPI for Album {
|
||||
async fn api(&self) -> serde_json::Value {
|
||||
json!({
|
||||
"id": &self.id,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::route::ToAPI;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sqlx::FromRow;
|
||||
|
@ -21,14 +22,48 @@ impl Artist {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn get(id: &uuid::Uuid) -> Option<Self> {
|
||||
sqlx::query_as("SELECT * FROM artist WHERE id = $1")
|
||||
.bind(id)
|
||||
.fetch_optional(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn find(name: &str) -> Option<Self> {
|
||||
sqlx::query_as("SELECT * FROM artist WHERE name = $1")
|
||||
.bind(name)
|
||||
.fetch_optional(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn find_all() -> Vec<Self> {
|
||||
sqlx::query_as("SELECT * FROM artist")
|
||||
.fetch_all(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn find_regex_col(col: &str, query: &str) -> Vec<Self> {
|
||||
sqlx::query_as(&format!("SELECT * FROM artist WHERE {col} ~* $1"))
|
||||
.bind(query)
|
||||
.fetch_all(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn remove(&self) {
|
||||
sqlx::query("DELETE FROM artist WHERE id = $1")
|
||||
.bind(self.id)
|
||||
.fetch(get_pg!());
|
||||
}
|
||||
|
||||
/// Gets the image of an artist or `None` if it can't be found.
|
||||
///
|
||||
/// This function gets a track from the artist. It then expects the folder structure to be `Artist/Album/Track.ext` and searches for an image file named `artist` in the artist folder.
|
||||
pub async fn get_image_of(id: &uuid::Uuid) -> Option<String> {
|
||||
// todo : fix
|
||||
let track_path = Track::find_one(doc! { "artist_id": reference_of!(Artist, id)})
|
||||
.await?
|
||||
.path;
|
||||
let track_path = Track::find_first_of_artist(id).await?.path;
|
||||
let track_path = std::path::Path::new(&track_path);
|
||||
|
||||
let artist_path = track_path.parent()?.parent()?;
|
||||
|
@ -45,7 +80,7 @@ impl Artist {
|
|||
}
|
||||
}
|
||||
|
||||
impl Artist {
|
||||
impl ToAPI for Artist {
|
||||
async fn api(&self) -> serde_json::Value {
|
||||
json!({
|
||||
"id": &self.id,
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use album::Album;
|
||||
use artist::Artist;
|
||||
use mongod::{reference_of, Model, Referencable, Reference};
|
||||
use mongodb::bson::doc;
|
||||
use serde_json::json;
|
||||
use track::Track;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::cache::RouteCache;
|
||||
use crate::{cache::RouteCache, get_pg};
|
||||
|
||||
pub mod album;
|
||||
pub mod artist;
|
||||
|
@ -39,28 +41,29 @@ impl Libary {
|
|||
Self { root_dir }
|
||||
}
|
||||
|
||||
pub async fn find_or_create_artist(&self, artist: &str) -> Reference {
|
||||
if let Some(artist) = Artist::find_one(doc! { "name": artist }).await {
|
||||
artist.reference()
|
||||
pub async fn find_or_create_artist(&self, artist: &str) -> uuid::Uuid {
|
||||
if let Some(artist) = Artist::find(artist).await {
|
||||
artist.id
|
||||
} else {
|
||||
Artist::create(artist).await.reference()
|
||||
Artist::create(artist).await.id
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn find_or_create_album(&self, album: &str, artist_id: Option<&str>) -> Reference {
|
||||
if let Some(album) = Album::find_one(doc! { "title": album, "artist_id": artist_id}).await {
|
||||
album.reference()
|
||||
pub async fn find_or_create_album(
|
||||
&self,
|
||||
album: &str,
|
||||
artist_id: Option<uuid::Uuid>,
|
||||
) -> uuid::Uuid {
|
||||
if let Some(album) = Album::find(album, artist_id).await {
|
||||
album.id
|
||||
} else {
|
||||
Album::create(album, artist_id).await.reference()
|
||||
Album::create(album, artist_id).await.id
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add_path_to_library(&self, path: &str) {
|
||||
// search for path already present
|
||||
if Track::find_one_partial(doc! { "path": path }, json!({}))
|
||||
.await
|
||||
.is_some()
|
||||
{
|
||||
if Track::of_path(path).await.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -70,7 +73,6 @@ impl Libary {
|
|||
let metadata = metadata::get_metadata(path);
|
||||
|
||||
let mut entry = json!({
|
||||
"_id": uuid::Uuid::new_v4().to_string(),
|
||||
"path": path,
|
||||
});
|
||||
|
||||
|
@ -80,7 +82,7 @@ impl Libary {
|
|||
entry
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("artist_id".into(), artist_id.into());
|
||||
.insert("artist".into(), artist_id.to_string().into());
|
||||
} else {
|
||||
log::warn!("{path} has no artist");
|
||||
}
|
||||
|
@ -92,14 +94,15 @@ impl Libary {
|
|||
entry
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("artist_id")
|
||||
.map(|x| x.as_str().unwrap()),
|
||||
.get("artist")
|
||||
.map(|x| uuid::Uuid::from_str(x.as_str().unwrap()).unwrap()),
|
||||
)
|
||||
.await;
|
||||
|
||||
entry
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("album_id".into(), album_id.into());
|
||||
.insert("album".into(), album_id.to_string().into());
|
||||
} else {
|
||||
log::warn!("{path} has no album and will be treated as single");
|
||||
}
|
||||
|
@ -136,41 +139,37 @@ impl Libary {
|
|||
}
|
||||
|
||||
pub async fn get_artists(&self) -> Vec<Artist> {
|
||||
Artist::find_all().await.unwrap()
|
||||
sqlx::query_as("SELECT * FROM artist")
|
||||
.fetch_all(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn get_albums_by_artist(&self, artist: &str) -> Vec<Album> {
|
||||
Album::find(
|
||||
doc! { "artist_id": reference_of!(Artist, artist).unwrap()},
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
pub async fn get_albums_by_artist(&self, artist: &uuid::Uuid) -> Vec<Album> {
|
||||
sqlx::query_as("SELECT * FROM album WHERE artist = $1")
|
||||
.bind(artist)
|
||||
.fetch_all(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn get_singles_by_artist(&self, artist: &str) -> Vec<Track> {
|
||||
Track::find(
|
||||
doc! {
|
||||
"album_id": None::<String>,
|
||||
"artist_id": reference_of!(Artist, artist).unwrap()
|
||||
},
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
pub async fn get_singles_by_artist(&self, artist: &uuid::Uuid) -> Vec<Track> {
|
||||
sqlx::query_as("SELECT * FROM track WHERE album IS NULL AND artist = $1")
|
||||
.bind(artist)
|
||||
.fetch_all(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn get_album_by_id(&self, album: &str) -> Option<Album> {
|
||||
pub async fn get_album_by_id(&self, album: &uuid::Uuid) -> Option<Album> {
|
||||
Album::get(album).await
|
||||
}
|
||||
|
||||
pub async fn get_track_by_id(&self, track_id: &str) -> Option<Track> {
|
||||
pub async fn get_track_by_id(&self, track_id: &uuid::Uuid) -> Option<Track> {
|
||||
Track::get(track_id).await
|
||||
}
|
||||
|
||||
pub async fn reload_metadata(&self, track_id: &str) -> Result<(), ()> {
|
||||
pub async fn reload_metadata(&self, track_id: &uuid::Uuid) -> Result<(), ()> {
|
||||
let mut track = Track::get(track_id).await.ok_or_else(|| ())?;
|
||||
let path = &track.path;
|
||||
log::info!("Rescanning metadata for {path}");
|
||||
|
@ -185,7 +184,7 @@ impl Libary {
|
|||
update
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("artist_id".into(), artist_id.into());
|
||||
.insert("artist".into(), artist_id.to_string().into());
|
||||
} else {
|
||||
log::warn!("{path} has no artist");
|
||||
}
|
||||
|
@ -197,14 +196,14 @@ impl Libary {
|
|||
update
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.get("artist_id")
|
||||
.map(|x| x.as_str().unwrap()),
|
||||
.get("artist")
|
||||
.map(|x| uuid::Uuid::from_str(x.as_str().unwrap()).unwrap()),
|
||||
)
|
||||
.await;
|
||||
update
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("album_id".into(), album_id.into());
|
||||
.insert("album".into(), album_id.to_string().into());
|
||||
} else {
|
||||
log::warn!("{path} has no album and will be treated as single");
|
||||
}
|
||||
|
@ -244,66 +243,26 @@ impl Libary {
|
|||
|
||||
pub async fn clean_lost_files(&self) {
|
||||
// tracks
|
||||
for track in Track::find_partial(doc! {}, json!({"path": 1}), None, None)
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
if !std::path::Path::new(&track.path.as_ref().unwrap()).exists() {
|
||||
log::info!("Cleaning lost {}", track.path.as_ref().unwrap());
|
||||
Track::remove(&track._id).await.unwrap();
|
||||
for track in Track::find_all().await {
|
||||
if !std::path::Path::new(&track.path).exists() {
|
||||
log::info!("Cleaning lost {}", track.path);
|
||||
track.remove().await;
|
||||
}
|
||||
}
|
||||
// albums
|
||||
for album in Album::find_partial(doc! {}, json!({"title": 1}), None, None)
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
if Track::find_partial(
|
||||
doc! { "album_id": album.reference() },
|
||||
json!({}),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
{
|
||||
log::info!(
|
||||
"Cleaning album {} with no tracks",
|
||||
album.title.as_ref().unwrap()
|
||||
);
|
||||
Album::remove(album.id()).await.unwrap();
|
||||
for album in Album::find_all().await {
|
||||
if Track::find_of_album(&album.id).await.is_empty() {
|
||||
log::info!("Cleaning album {} with no tracks", album.title);
|
||||
album.remove().await;
|
||||
}
|
||||
}
|
||||
// artists
|
||||
for artist in Artist::find_partial(doc! {}, json!({"name": 1}), None, None)
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
if Track::find_partial(
|
||||
doc! { "artist_id": artist.reference()},
|
||||
json!({}),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
&& Album::find_partial(
|
||||
doc! { "artist_id": artist.reference()},
|
||||
json!({}),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
for artist in Artist::find_all().await {
|
||||
if Track::find_first_of_artist(&artist.id).await.is_none()
|
||||
&& Album::find_of_artist(&artist.id).await.is_empty()
|
||||
{
|
||||
log::info!(
|
||||
"Cleaning artist {} with no tracks or albums",
|
||||
artist.name.as_ref().unwrap()
|
||||
);
|
||||
Artist::remove(artist.id()).await.unwrap();
|
||||
log::info!("Cleaning artist {} with no tracks or albums", artist.name);
|
||||
artist.remove().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
use mongod::{
|
||||
assert_reference_of,
|
||||
derive::{Model, Referencable},
|
||||
reference_of, Model, Referencable, Reference, Validate,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
use crate::library::{track::Track, user::User};
|
||||
use mongod::ToAPI;
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Model, Referencable)]
|
||||
use crate::{
|
||||
get_pg,
|
||||
library::{track::Track, user::User},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct Playlist {
|
||||
pub _id: String,
|
||||
pub owner: Reference,
|
||||
pub id: String,
|
||||
pub owner: String,
|
||||
pub title: String,
|
||||
pub visibility: Visibility,
|
||||
pub tracks: Vec<Reference>,
|
||||
pub tracks: Vec<uuid::Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::Type)]
|
||||
#[sqlx(type_name = "visibility", rename_all = "lowercase")]
|
||||
pub enum Visibility {
|
||||
Private,
|
||||
Public,
|
||||
}
|
||||
|
||||
impl Playlist {
|
||||
|
@ -21,50 +27,49 @@ impl Playlist {
|
|||
owner: User,
|
||||
title: &str,
|
||||
visibility: Visibility,
|
||||
tracks: &[String],
|
||||
tracks: &[uuid::Uuid],
|
||||
) -> Option<Self> {
|
||||
let mut tracks_ref = vec![];
|
||||
sqlx::query_as("INSERT INTO playlist (owner, title, visibility, tracks) VALUES ($1, $2, $3, $4) RETURNING *")
|
||||
.bind(owner.username)
|
||||
.bind(title)
|
||||
.bind(visibility)
|
||||
.bind(tracks)
|
||||
.fetch_one(get_pg!()).await.ok()
|
||||
}
|
||||
|
||||
for track in tracks {
|
||||
tracks_ref.push(reference_of!(Track, track)?);
|
||||
}
|
||||
pub async fn find_regex_col(col: &str, query: &str) -> Vec<Self> {
|
||||
sqlx::query_as(&format!("SELECT * FROM playlist WHERE {col} ~* $1"))
|
||||
.bind(query)
|
||||
.fetch_all(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
_id: uuid::Uuid::new_v4().to_string(),
|
||||
owner: owner.reference(),
|
||||
title: title.to_string(),
|
||||
visibility,
|
||||
tracks: tracks_ref,
|
||||
})
|
||||
pub async fn get(id: &uuid::Uuid) -> Option<Self> {
|
||||
sqlx::query_as("SELECT * FROM playlist WHERE id = $1")
|
||||
.bind(id)
|
||||
.fetch_optional(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn of(u: &User) -> Vec<Self> {
|
||||
sqlx::query_as("SELECT * FROM playlist WHERE owner = $1")
|
||||
.bind(&u.username)
|
||||
.fetch_all(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum Visibility {
|
||||
Private,
|
||||
Public,
|
||||
}
|
||||
|
||||
impl Validate for Playlist {
|
||||
async fn validate(&self) -> Result<(), String> {
|
||||
assert_reference_of!(self.owner, User);
|
||||
|
||||
for track in &self.tracks {
|
||||
assert_reference_of!(track, Track);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToAPI for Playlist {
|
||||
async fn api(&self) -> serde_json::Value {
|
||||
impl Playlist {
|
||||
pub async fn api(&self) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"id": self._id,
|
||||
"owner": self.owner.id(),
|
||||
"id": self.id,
|
||||
"owner": self.owner,
|
||||
"visibility": serde_json::to_value(&self.visibility).unwrap(),
|
||||
"title": self.title,
|
||||
"tracks": self.tracks.iter().map(mongod::Reference::id).collect::<Vec<_>>()
|
||||
"tracks": self.tracks
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
use serde::Serialize;
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use mongod::Model;
|
||||
use mongodb::bson::doc;
|
||||
use serde::Serialize;
|
||||
|
||||
use mongod::ToAPI;
|
||||
use crate::route::ToAPI;
|
||||
|
||||
use super::{album::Album, artist::Artist, playlist::Playlist, track::Track};
|
||||
|
||||
|
@ -74,13 +71,7 @@ pub async fn search_for(query: String) -> Option<Vec<SearchResult>> {
|
|||
let mut results: Vec<SearchResult> = Vec::new();
|
||||
|
||||
// Add artist results
|
||||
for artist in Artist::find(
|
||||
doc! { "name": { "$regex": &query, "$options": "i" } },
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
for artist in Artist::find_regex_col("name", &query).await {
|
||||
results.push(SearchResult {
|
||||
kind: "artist".to_string(),
|
||||
data: artist.api().await,
|
||||
|
@ -89,13 +80,7 @@ pub async fn search_for(query: String) -> Option<Vec<SearchResult>> {
|
|||
}
|
||||
|
||||
// Add album results
|
||||
for album in Album::find(
|
||||
doc! { "title": { "$regex": &query, "$options": "i" } },
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
for album in Album::find_regex_col("title", &query).await {
|
||||
results.push(SearchResult {
|
||||
kind: "album".to_string(),
|
||||
data: album.api().await,
|
||||
|
@ -104,28 +89,16 @@ pub async fn search_for(query: String) -> Option<Vec<SearchResult>> {
|
|||
}
|
||||
|
||||
// Add track results
|
||||
for track in Track::find(
|
||||
doc! { "title": { "$regex": &query, "$options": "i" } },
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
for track in Track::find_regex_col("title", &query).await {
|
||||
results.push(SearchResult {
|
||||
kind: "track".to_string(),
|
||||
data: track.api().await,
|
||||
score: calculate_score(&track.title, &query, Some(track.date_added)),
|
||||
score: calculate_score(&track.title, &query, Some(track.date_added.timestamp())),
|
||||
});
|
||||
}
|
||||
|
||||
// Add playlist results
|
||||
for playlist in Playlist::find(
|
||||
doc! { "title": { "$regex": &query, "$options": "i" } },
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
{
|
||||
for playlist in Playlist::find_regex_col("title", &query).await {
|
||||
results.push(SearchResult {
|
||||
kind: "playlist".to_string(),
|
||||
data: playlist.api().await,
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use sqlx::prelude::FromRow;
|
||||
use std::collections::HashSet;
|
||||
use std::{collections::HashSet, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
get_pg,
|
||||
library::{album::Album, artist::Artist},
|
||||
route::ToAPI,
|
||||
};
|
||||
|
||||
use super::{event::Event, metadata::AudioMetadata, user::User};
|
||||
|
@ -30,6 +31,75 @@ impl Track {
|
|||
.fetch(get_pg!());
|
||||
}
|
||||
|
||||
pub async fn of_path(path: &str) -> Option<Self> {
|
||||
sqlx::query_as("SELECT * FROM track WHERE path = $1")
|
||||
.bind(path)
|
||||
.fetch_optional(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn find_recently_added() -> Vec<Self> {
|
||||
sqlx::query_as("SELECT * FROM track ORDER BY date_added DESC LIMIT 90")
|
||||
.fetch_all(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn get(id: &uuid::Uuid) -> Option<Self> {
|
||||
sqlx::query_as("SELECT * FROM track WHERE id = $1")
|
||||
.bind(id)
|
||||
.fetch_optional(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn update(&self, update_set: &serde_json::Value) -> Option<()> {
|
||||
let map = update_set.as_object()?;
|
||||
let artist = map
|
||||
.get("artist")
|
||||
.map(|x| uuid::Uuid::from_str(x.as_str().unwrap()).unwrap());
|
||||
let album = map
|
||||
.get("album")
|
||||
.map(|x| uuid::Uuid::from_str(x.as_str().unwrap()).unwrap());
|
||||
let title = map.get("title")?.as_str()?;
|
||||
let meta = map.get("meta");
|
||||
|
||||
sqlx::query(
|
||||
"UPDATE track SET artist = $1, album = $2, title = $3, meta = $4 WHERE id = $5;
|
||||
",
|
||||
)
|
||||
.bind(artist)
|
||||
.bind(album)
|
||||
.bind(title)
|
||||
.bind(meta)
|
||||
.bind(self.id)
|
||||
.fetch(get_pg!());
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
pub async fn find_all() -> Vec<Self> {
|
||||
sqlx::query_as("SELECT * FROM track")
|
||||
.fetch_all(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn remove(&self) {
|
||||
sqlx::query("DELETE FROM track WHERE id = $1")
|
||||
.bind(self.id)
|
||||
.fetch(get_pg!());
|
||||
}
|
||||
|
||||
pub async fn find_of_album(album: &uuid::Uuid) -> Vec<Self> {
|
||||
sqlx::query_as("SELECT * FROM track WHERE album = $1")
|
||||
.bind(album)
|
||||
.fetch_all(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn get_latest_of_user(u: &User) -> Vec<Self> {
|
||||
let latest_events = Event::get_latest_events_of(u).await;
|
||||
let mut ids = HashSet::new();
|
||||
|
@ -59,13 +129,13 @@ impl Track {
|
|||
|
||||
/// Transcode audio
|
||||
pub fn transcode(&self, codec: &str, bitrate: u32, ext: &str) -> Option<String> {
|
||||
let transcoded = format!("./data/transcode/{codec}/{bitrate}/{}.{ext}", self._id);
|
||||
let transcoded = format!("./data/transcode/{codec}/{bitrate}/{}.{ext}", self.id);
|
||||
|
||||
if std::path::Path::new(&transcoded).exists() {
|
||||
return Some(transcoded);
|
||||
}
|
||||
|
||||
log::info!("Transcoding {} to {} {}", self._id, codec, bitrate);
|
||||
log::info!("Transcoding {} to {} {}", self.id, codec, bitrate);
|
||||
|
||||
std::fs::create_dir_all(format!("./data/transcode/{codec}/{bitrate}")).unwrap();
|
||||
|
||||
|
@ -87,38 +157,57 @@ impl Track {
|
|||
Some(transcoded)
|
||||
}
|
||||
|
||||
pub async fn find_regex_col(col: &str, query: &str) -> Vec<Self> {
|
||||
sqlx::query_as(&format!("SELECT * FROM track WHERE {col} ~* $1"))
|
||||
.bind(query)
|
||||
.fetch_all(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Find tracks with no album or artist
|
||||
pub async fn get_orphans() -> Vec<Track> {
|
||||
// todo : fix
|
||||
Self::find(
|
||||
doc! {
|
||||
"artist_id": None::<String>,
|
||||
"album_id": None::<String>
|
||||
},
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
sqlx::query_as("SELECT * FROM track WHERE artist IS NULL AND album IS NULL")
|
||||
.fetch_all(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn find_first_of_artist(artist: &uuid::Uuid) -> Option<Self> {
|
||||
sqlx::query_as("SELECT * FROM track WHERE artist = $1 LIMIT 1")
|
||||
.bind(artist)
|
||||
.fetch_optional(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn find_first_of_album(album: &uuid::Uuid) -> Option<Self> {
|
||||
sqlx::query_as("SELECT * FROM track WHERE album = $1 LIMIT 1")
|
||||
.bind(album)
|
||||
.fetch_optional(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Track {
|
||||
impl ToAPI for Track {
|
||||
async fn api(&self) -> serde_json::Value {
|
||||
// todo : fix
|
||||
let (cover, album_title, album_id) = if let Some(album_ref) = &self.album {
|
||||
let album = album_ref.get::<Album>().await;
|
||||
let album = Album::get(album_ref).await.unwrap();
|
||||
|
||||
(album.get_cover().await.is_some(), album.title, album._id)
|
||||
(album.get_cover().await.is_some(), album.title, album.id)
|
||||
} else {
|
||||
(false, String::new(), String::new())
|
||||
(false, String::new(), uuid::Uuid::nil())
|
||||
};
|
||||
|
||||
let artist_title = if let Some(artist_ref) = &self.artist {
|
||||
artist_ref
|
||||
.get_partial::<Artist>(json!({"name": 1}))
|
||||
let res: (String,) = sqlx::query_as("SELECT name FROM artist WHERE id = $1")
|
||||
.bind(artist_ref)
|
||||
.fetch_one(get_pg!())
|
||||
.await
|
||||
.name
|
||||
.unwrap();
|
||||
Some(res.0)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -126,7 +215,7 @@ impl Track {
|
|||
json!({
|
||||
"id": self.id,
|
||||
"title": self.title,
|
||||
"track_number": self.meta.as_ref().map(|x| AudioMetadata(*x).track_number()),
|
||||
"track_number": self.meta.as_ref().map(|x| AudioMetadata(x.clone()).track_number()),
|
||||
"meta": serde_json::to_value(&self.meta).unwrap(),
|
||||
"album_id": self.album,
|
||||
"album": album_title,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::route::ToAPI;
|
||||
use data_encoding::HEXUPPER;
|
||||
use rand::RngCore;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -81,6 +82,13 @@ impl User {
|
|||
Err(())
|
||||
}
|
||||
|
||||
pub async fn find_all() -> Vec<Self> {
|
||||
sqlx::query_as("SELECT * FROM user")
|
||||
.fetch_all(get_pg!())
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub async fn session(&self) -> Session {
|
||||
sqlx::query_as(
|
||||
"INSERT INTO user_session (token, user) VALUES ($1, $2) RETURNING id, token, user",
|
||||
|
@ -93,7 +101,7 @@ impl User {
|
|||
}
|
||||
|
||||
pub const fn is_admin(&self) -> bool {
|
||||
matches!(self.role, UserRole::Admin)
|
||||
matches!(self.user_role, UserRole::Admin)
|
||||
}
|
||||
|
||||
pub fn verify_pw(&self, password: &str) -> bool {
|
||||
|
@ -101,7 +109,7 @@ impl User {
|
|||
}
|
||||
}
|
||||
|
||||
impl User {
|
||||
impl ToAPI for User {
|
||||
async fn api(&self) -> serde_json::Value {
|
||||
json!({
|
||||
"username": self.username,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue