Some checks failed
Push Workflows / rustfmt (push) Successful in 9s
Push Workflows / mdbook (push) Successful in 22s
Push Workflows / mdbook-server (push) Successful in 4m20s
Push Workflows / docs (push) Successful in 6m25s
Push Workflows / clippy (push) Successful in 8m6s
Push Workflows / test (push) Successful in 11m5s
Push Workflows / docker-build (push) Failing after 12m5s
Push Workflows / leptos-test (push) Successful in 12m20s
Push Workflows / nix-build (push) Successful in 16m42s
Push Workflows / build (push) Successful in 13m12s
Update API and components to use LocalPath/WebPath Remove img_fallback Exchange some frontend and backend types Other path-related changes Update image/song uploading to use random paths
256 lines
8.4 KiB
Rust
256 lines
8.4 KiB
Rust
use leptos::prelude::*;
|
|
|
|
use cfg_if::cfg_if;
|
|
|
|
use crate::models::frontend;
|
|
use crate::util::error::*;
|
|
use crate::util::serverfn_client::Client;
|
|
|
|
cfg_if! {
|
|
if #[cfg(feature = "ssr")] {
|
|
use diesel::prelude::*;
|
|
use std::collections::HashMap;
|
|
|
|
use crate::models::backend::Artist;
|
|
use crate::models::backend::Album;
|
|
use crate::models::backend::NewArtist;
|
|
use crate::util::backend_state::BackendState;
|
|
use crate::util::paths::*;
|
|
}
|
|
}
|
|
|
|
/// Add an artist to the database
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `artist_name` - The name of the artist to add
|
|
///
|
|
/// # Returns
|
|
/// * `Result<(), Box<dyn Error>>` - A empty result if successful, or an error
|
|
///
|
|
#[server(endpoint = "artists/add-artist", client = Client)]
|
|
pub async fn add_artist(artist_name: String) -> BackendResult<()> {
|
|
use crate::schema::artists::dsl::*;
|
|
|
|
let new_artist = NewArtist {
|
|
name: artist_name,
|
|
image_path: None,
|
|
};
|
|
|
|
let mut db_conn = BackendState::get().await?.get_db_conn()?;
|
|
|
|
diesel::insert_into(artists)
|
|
.values(&new_artist)
|
|
.execute(&mut db_conn)
|
|
.context("Error inserting new artist into database")?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[server(endpoint = "artists/get", client = Client)]
|
|
pub async fn get_artist_by_id(artist_id: i32) -> BackendResult<Option<frontend::Artist>> {
|
|
use crate::schema::artists::dsl::*;
|
|
|
|
let mut db_conn = BackendState::get().await?.get_db_conn()?;
|
|
|
|
let artist = artists
|
|
.filter(id.eq(artist_id))
|
|
.first::<Artist>(&mut db_conn)
|
|
.optional()
|
|
.context("Error loading artist from database")?;
|
|
|
|
let Some(artist) = artist else {
|
|
return Ok(None);
|
|
};
|
|
|
|
let artist = frontend::Artist {
|
|
id: artist.id,
|
|
name: artist.name,
|
|
image_path: LocalPath::to_web_path_or_placeholder(artist.image_path),
|
|
};
|
|
|
|
Ok(Some(artist))
|
|
}
|
|
|
|
#[server(endpoint = "artists/top_songs", client = Client)]
|
|
pub async fn top_songs_by_artist(
|
|
artist_id: i32,
|
|
limit: Option<i64>,
|
|
) -> BackendResult<Vec<(frontend::Song, i64)>> {
|
|
use crate::api::auth::get_user;
|
|
use crate::models::backend::Song;
|
|
use crate::schema::*;
|
|
|
|
let user_id = get_user().await.context("Error getting logged-in user")?.id;
|
|
|
|
let mut db_conn = BackendState::get().await?.get_db_conn()?;
|
|
|
|
let song_play_counts: Vec<(i32, i64)> = if let Some(limit) = limit {
|
|
song_history::table
|
|
.group_by(song_history::song_id)
|
|
.select((song_history::song_id, diesel::dsl::count(song_history::id)))
|
|
.left_join(song_artists::table.on(song_artists::song_id.eq(song_history::song_id)))
|
|
.filter(song_artists::artist_id.eq(artist_id))
|
|
.order_by(diesel::dsl::count(song_history::id).desc())
|
|
.left_join(songs::table.on(songs::id.eq(song_history::song_id)))
|
|
.limit(limit)
|
|
.load(&mut db_conn)?
|
|
} else {
|
|
song_history::table
|
|
.group_by(song_history::song_id)
|
|
.select((song_history::song_id, diesel::dsl::count(song_history::id)))
|
|
.left_join(song_artists::table.on(song_artists::song_id.eq(song_history::song_id)))
|
|
.filter(song_artists::artist_id.eq(artist_id))
|
|
.order_by(diesel::dsl::count(song_history::id).desc())
|
|
.left_join(songs::table.on(songs::id.eq(song_history::song_id)))
|
|
.load(&mut db_conn)?
|
|
};
|
|
|
|
let song_play_counts: HashMap<i32, i64> = song_play_counts.into_iter().collect();
|
|
let top_song_ids: Vec<i32> = song_play_counts.keys().copied().collect();
|
|
|
|
let top_songs: Vec<(
|
|
Song,
|
|
Option<Album>,
|
|
Option<Artist>,
|
|
Option<(i32, i32)>,
|
|
Option<(i32, i32)>,
|
|
)> = songs::table
|
|
.filter(songs::id.eq_any(top_song_ids))
|
|
.left_join(albums::table.on(songs::album_id.eq(albums::id.nullable())))
|
|
.left_join(
|
|
song_artists::table
|
|
.inner_join(artists::table)
|
|
.on(songs::id.eq(song_artists::song_id)),
|
|
)
|
|
.left_join(
|
|
song_likes::table.on(songs::id
|
|
.eq(song_likes::song_id)
|
|
.and(song_likes::user_id.eq(user_id))),
|
|
)
|
|
.left_join(
|
|
song_dislikes::table.on(songs::id
|
|
.eq(song_dislikes::song_id)
|
|
.and(song_dislikes::user_id.eq(user_id))),
|
|
)
|
|
.select((
|
|
songs::all_columns,
|
|
albums::all_columns.nullable(),
|
|
artists::all_columns.nullable(),
|
|
song_likes::all_columns.nullable(),
|
|
song_dislikes::all_columns.nullable(),
|
|
))
|
|
.load(&mut db_conn)?;
|
|
|
|
let mut top_songs_map: HashMap<i32, (frontend::Song, i64)> =
|
|
HashMap::with_capacity(top_songs.len());
|
|
|
|
for (song, album, artist, like, dislike) in top_songs {
|
|
if let Some((stored_songdata, _)) = top_songs_map.get_mut(&song.id) {
|
|
// If the song is already in the map, update the artists
|
|
if let Some(artist) = artist {
|
|
stored_songdata.artists.push(artist);
|
|
}
|
|
} else {
|
|
let like_dislike = match (like, dislike) {
|
|
(Some(_), Some(_)) => Some((true, true)),
|
|
(Some(_), None) => Some((true, false)),
|
|
(None, Some(_)) => Some((false, true)),
|
|
_ => None,
|
|
};
|
|
|
|
let image_path = song.image_web_path_or_placeholder(album.as_ref());
|
|
|
|
let song_path = song
|
|
.storage_path
|
|
.to_web_path(AssetType::Audio)
|
|
.context(format!(
|
|
"Error converting audio path to web path for song {} (id: {})",
|
|
song.title.clone(),
|
|
song.id
|
|
))?;
|
|
|
|
let songdata = frontend::Song {
|
|
id: song.id,
|
|
title: song.title,
|
|
artists: artist.map(|artist| vec![artist]).unwrap_or_default(),
|
|
album,
|
|
track: song.track,
|
|
duration: song.duration,
|
|
release_date: song.release_date,
|
|
song_path,
|
|
image_path,
|
|
like_dislike,
|
|
added_date: song.added_date,
|
|
};
|
|
|
|
let plays = song_play_counts
|
|
.get(&song.id)
|
|
.ok_or(BackendError::InternalError(
|
|
"Song id not found in history counts",
|
|
))?;
|
|
|
|
top_songs_map.insert(song.id, (songdata, *plays));
|
|
}
|
|
}
|
|
|
|
let mut top_songs: Vec<(frontend::Song, i64)> = top_songs_map.into_values().collect();
|
|
top_songs.sort_by(|(_, plays1), (_, plays2)| plays2.cmp(plays1));
|
|
Ok(top_songs)
|
|
}
|
|
|
|
#[server(endpoint = "artists/albums", client = Client)]
|
|
pub async fn albums_by_artist(
|
|
artist_id: i32,
|
|
limit: Option<i64>,
|
|
) -> BackendResult<Vec<frontend::Album>> {
|
|
use crate::schema::*;
|
|
|
|
let mut db_conn = BackendState::get().await?.get_db_conn()?;
|
|
|
|
let album_ids = albums::table
|
|
.left_join(album_artists::table)
|
|
.filter(album_artists::artist_id.eq(artist_id))
|
|
.order_by(albums::release_date.desc())
|
|
.select(albums::id);
|
|
|
|
let album_ids = if let Some(limit) = limit {
|
|
album_ids.limit(limit).into_boxed()
|
|
} else {
|
|
album_ids.into_boxed()
|
|
};
|
|
|
|
let mut albums_map: HashMap<i32, frontend::Album> = HashMap::new();
|
|
|
|
let album_artists: Vec<(Album, Artist)> = albums::table
|
|
.filter(albums::id.eq_any(album_ids))
|
|
.inner_join(
|
|
album_artists::table
|
|
.inner_join(artists::table)
|
|
.on(albums::id.eq(album_artists::album_id)),
|
|
)
|
|
.select((albums::all_columns, artists::all_columns))
|
|
.load(&mut db_conn)
|
|
.context("Error loading album artists from database")?;
|
|
|
|
for (album, artist) in album_artists {
|
|
if let Some(stored_album) = albums_map.get_mut(&album.id) {
|
|
stored_album.artists.push(artist);
|
|
} else {
|
|
let albumdata = frontend::Album {
|
|
id: album.id,
|
|
title: album.title,
|
|
artists: vec![artist],
|
|
release_date: album.release_date,
|
|
image_path: LocalPath::to_web_path_or_placeholder(album.image_path),
|
|
};
|
|
|
|
albums_map.insert(album.id, albumdata);
|
|
}
|
|
}
|
|
|
|
let mut albums: Vec<frontend::Album> = albums_map.into_values().collect();
|
|
albums.sort_by(|a1, a2| a2.release_date.cmp(&a1.release_date));
|
|
Ok(albums)
|
|
}
|