10 Commits

Author SHA1 Message Date
a31539dc8f Merge pull request 'Separate types for inserting and fetching' (#218) from 217-separate-insert-fetch-types into main
Some checks failed
Push Workflows / rustfmt (push) Successful in 9s
Push Workflows / docs (push) Successful in 38s
Push Workflows / clippy (push) Successful in 36s
Push Workflows / leptos-test (push) Successful in 1m13s
Push Workflows / test (push) Successful in 1m30s
Push Workflows / build (push) Successful in 2m32s
Push Workflows / docker-build (push) Failing after 9m20s
Push Workflows / nix-build (push) Successful in 12m12s
Reviewed-on: #218
2025-05-05 02:32:46 +00:00
eda4e42150 Update flake.nix to use libretunes_macro git dependency
Some checks failed
Push Workflows / docs (push) Successful in 1m14s
Push Workflows / rustfmt (push) Successful in 10s
Push Workflows / clippy (push) Successful in 1m40s
Push Workflows / leptos-test (push) Successful in 3m0s
Push Workflows / test (push) Successful in 3m11s
Push Workflows / build (push) Successful in 4m6s
Push Workflows / docker-build (push) Failing after 10m51s
Push Workflows / nix-build (push) Successful in 13m11s
2025-05-05 02:08:36 +00:00
54d629d504 Use db_type for User
Some checks failed
Push Workflows / rustfmt (push) Successful in 8s
Push Workflows / nix-build (push) Failing after 45s
Push Workflows / docs (push) Successful in 3m20s
Push Workflows / clippy (push) Successful in 4m24s
Push Workflows / test (push) Successful in 5m58s
Push Workflows / leptos-test (push) Successful in 6m59s
Push Workflows / build (push) Successful in 7m53s
Push Workflows / docker-build (push) Failing after 11m47s
2025-05-05 01:25:20 +00:00
6486bbbdda Use db_type for Playlist 2025-05-05 01:10:17 +00:00
b727832c8e Use db_type for HistoryEntry 2025-05-05 01:07:07 +00:00
7c4058884e Use db_type for Artist 2025-05-05 01:05:20 +00:00
a67bd37d11 Use db_type for Album 2025-05-05 00:53:57 +00:00
3f43ef2d20 Use libretunes_macro::db_type instead of manual Song/NewSong structs 2025-05-05 00:38:37 +00:00
0b599f4038 Add libretunes_macro dependency 2025-05-05 00:38:01 +00:00
c02363c698 Create NewSong type 2025-05-04 21:36:34 +00:00
27 changed files with 148 additions and 288 deletions

18
Cargo.lock generated
View File

@ -2008,6 +2008,7 @@ dependencies = [
"leptos_icons", "leptos_icons",
"leptos_meta", "leptos_meta",
"leptos_router", "leptos_router",
"libretunes_macro",
"log", "log",
"multer", "multer",
"once_cell", "once_cell",
@ -2026,6 +2027,15 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "libretunes_macro"
version = "0.1.0"
source = "git+https://git.libretunes.xyz/LibreTunes/LibreTunes-Macro.git?branch=main#5e051ca70634f22547ecb59c9af556e851d3cb0d"
dependencies = [
"quote",
"syn",
]
[[package]] [[package]]
name = "linear-map" name = "linear-map"
version = "1.2.0" version = "1.2.0"
@ -2572,9 +2582,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.35" version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -3171,9 +3181,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.91" version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -62,6 +62,7 @@ dotenvy = { version = "0.15.7", optional = true }
reqwest = { version = "0.12.9", default-features = false, optional = true } reqwest = { version = "0.12.9", default-features = false, optional = true }
futures = { version = "0.3.25", default-features = false, optional = true } futures = { version = "0.3.25", default-features = false, optional = true }
once_cell = { version = "1.20", default-features = false, optional = true } once_cell = { version = "1.20", default-features = false, optional = true }
libretunes_macro = { git = "https://git.libretunes.xyz/LibreTunes/LibreTunes-Macro.git", branch = "main" }
[features] [features]
hydrate = [ hydrate = [

View File

@ -66,6 +66,11 @@
cargoLock = { cargoLock = {
lockFile = ./Cargo.lock; lockFile = ./Cargo.lock;
# Needed because of git dependency
outputHashes = {
"libretunes_macro-0.1.0" = "sha256-hve1eZV6KMBK5LiW/F801qKds0hXg6ID9pd9fPvKJZY=";
};
}; };
LIBCLANG_PATH = pkgs.lib.makeLibraryPath [ pkgs.llvmPackages_latest.libclang.lib ]; LIBCLANG_PATH = pkgs.lib.makeLibraryPath [ pkgs.llvmPackages_latest.libclang.lib ];

View File

@ -41,7 +41,7 @@ pub async fn get_album(id: i32) -> Result<Option<frontend::Album>, ServerFnError
.unwrap_or("/assets/images/placeholders/MusicPlaceholder.svg".to_string()); .unwrap_or("/assets/images/placeholders/MusicPlaceholder.svg".to_string());
let album = frontend::Album { let album = frontend::Album {
id: album.id.unwrap(), id: album.id,
title: album.title, title: album.title,
artists, artists,
release_date: album.release_date, release_date: album.release_date,
@ -62,7 +62,6 @@ pub async fn get_songs(id: i32) -> Result<Vec<frontend::Song>, ServerFnError> {
let db_con = &mut get_db_conn(); let db_con = &mut get_db_conn();
let song_list = if let Some(user) = user { let song_list = if let Some(user) = user {
let user_id = user.id.unwrap();
let song_list: Vec<( let song_list: Vec<(
backend::Album, backend::Album,
Option<backend::Song>, Option<backend::Song>,
@ -80,12 +79,12 @@ pub async fn get_songs(id: i32) -> Result<Vec<frontend::Song>, ServerFnError> {
.left_join( .left_join(
song_likes::table.on(songs::id song_likes::table.on(songs::id
.eq(song_likes::song_id) .eq(song_likes::song_id)
.and(song_likes::user_id.eq(user_id))), .and(song_likes::user_id.eq(user.id))),
) )
.left_join( .left_join(
song_dislikes::table.on(songs::id song_dislikes::table.on(songs::id
.eq(song_dislikes::song_id) .eq(song_dislikes::song_id)
.and(song_dislikes::user_id.eq(user_id))), .and(song_dislikes::user_id.eq(user.id))),
) )
.select(( .select((
albums::all_columns, albums::all_columns,
@ -135,7 +134,7 @@ pub async fn get_songs(id: i32) -> Result<Vec<frontend::Song>, ServerFnError> {
for (album, song, artist, like, dislike) in song_list { for (album, song, artist, like, dislike) in song_list {
if let Some(song) = song { if let Some(song) = song {
if let Some(stored_songdata) = album_songs.get_mut(&song.id.unwrap()) { if let Some(stored_songdata) = album_songs.get_mut(&song.id) {
// If the song is already in the map, update the artists // If the song is already in the map, update the artists
if let Some(artist) = artist { if let Some(artist) = artist {
stored_songdata.artists.push(artist); stored_songdata.artists.push(artist);
@ -156,7 +155,7 @@ pub async fn get_songs(id: i32) -> Result<Vec<frontend::Song>, ServerFnError> {
); );
let songdata = frontend::Song { let songdata = frontend::Song {
id: song.id.unwrap(), id: song.id,
title: song.title, title: song.title,
artists: artist.map(|artist| vec![artist]).unwrap_or_default(), artists: artist.map(|artist| vec![artist]).unwrap_or_default(),
album: Some(album), album: Some(album),
@ -166,10 +165,10 @@ pub async fn get_songs(id: i32) -> Result<Vec<frontend::Song>, ServerFnError> {
song_path: song.storage_path, song_path: song.storage_path,
image_path, image_path,
like_dislike, like_dislike,
added_date: song.added_date.unwrap(), added_date: song.added_date,
}; };
album_songs.insert(song.id.unwrap(), songdata); album_songs.insert(song.id, songdata);
} }
} }
} }

View File

@ -28,7 +28,7 @@ pub async fn add_album(
release_date: Option<String>, release_date: Option<String>,
image_path: Option<String>, image_path: Option<String>,
) -> Result<(), ServerFnError> { ) -> Result<(), ServerFnError> {
use crate::models::backend::Album; use crate::models::backend::NewAlbum;
use crate::schema::albums::{self}; use crate::schema::albums::{self};
use leptos::server_fn::error::NoCustomError; use leptos::server_fn::error::NoCustomError;
@ -46,8 +46,7 @@ pub async fn add_album(
let image_path_arg = image_path.filter(|image_path| !image_path.is_empty()); let image_path_arg = image_path.filter(|image_path| !image_path.is_empty());
let new_album = Album { let new_album = NewAlbum {
id: None,
title: album_title, title: album_title,
release_date: parsed_release_date, release_date: parsed_release_date,
image_path: image_path_arg, image_path: image_path_arg,

View File

@ -11,8 +11,8 @@ cfg_if! {
use crate::util::database::get_db_conn; use crate::util::database::get_db_conn;
use diesel::prelude::*; use diesel::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
use server_fn::error::NoCustomError;
use crate::models::backend::Album; use crate::models::backend::Album;
use crate::models::backend::NewArtist;
} }
} }
@ -30,10 +30,7 @@ pub async fn add_artist(artist_name: String) -> Result<(), ServerFnError> {
use crate::schema::artists::dsl::*; use crate::schema::artists::dsl::*;
use leptos::server_fn::error::NoCustomError; use leptos::server_fn::error::NoCustomError;
let new_artist = Artist { let new_artist = NewArtist { name: artist_name };
id: None,
name: artist_name,
};
let db = &mut get_db_conn(); let db = &mut get_db_conn();
diesel::insert_into(artists) diesel::insert_into(artists)
@ -78,8 +75,7 @@ pub async fn top_songs_by_artist(
.map_err(|e| { .map_err(|e| {
ServerFnError::ServerError::<NoCustomError>(format!("Error getting user: {e}")) ServerFnError::ServerError::<NoCustomError>(format!("Error getting user: {e}"))
})? })?
.id .id;
.unwrap();
let db = &mut get_db_conn(); let db = &mut get_db_conn();
let song_play_counts: Vec<(i32, i64)> = if let Some(limit) = limit { let song_play_counts: Vec<(i32, i64)> = if let Some(limit) = limit {
@ -143,11 +139,7 @@ pub async fn top_songs_by_artist(
HashMap::with_capacity(top_songs.len()); HashMap::with_capacity(top_songs.len());
for (song, album, artist, like, dislike) in top_songs { for (song, album, artist, like, dislike) in top_songs {
let song_id = song.id.ok_or(ServerFnError::ServerError::<NoCustomError>( if let Some((stored_songdata, _)) = top_songs_map.get_mut(&song.id) {
"Song id not found in database".to_string(),
))?;
if let Some((stored_songdata, _)) = top_songs_map.get_mut(&song_id) {
// If the song is already in the map, update the artists // If the song is already in the map, update the artists
if let Some(artist) = artist { if let Some(artist) = artist {
stored_songdata.artists.push(artist); stored_songdata.artists.push(artist);
@ -168,7 +160,7 @@ pub async fn top_songs_by_artist(
); );
let songdata = frontend::Song { let songdata = frontend::Song {
id: song_id, id: song.id,
title: song.title, title: song.title,
artists: artist.map(|artist| vec![artist]).unwrap_or_default(), artists: artist.map(|artist| vec![artist]).unwrap_or_default(),
album, album,
@ -178,16 +170,16 @@ pub async fn top_songs_by_artist(
song_path: song.storage_path, song_path: song.storage_path,
image_path, image_path,
like_dislike, like_dislike,
added_date: song.added_date.unwrap(), added_date: song.added_date,
}; };
let plays = song_play_counts let plays = song_play_counts
.get(&song_id) .get(&song.id)
.ok_or(ServerFnError::ServerError::<NoCustomError>( .ok_or(ServerFnError::ServerError::<NoCustomError>(
"Song id not found in history counts".to_string(), "Song id not found in history counts".to_string(),
))?; ))?;
top_songs_map.insert(song_id, (songdata, *plays)); top_songs_map.insert(song.id, (songdata, *plays));
} }
} }
@ -230,15 +222,11 @@ pub async fn albums_by_artist(
.load(db)?; .load(db)?;
for (album, artist) in album_artists { for (album, artist) in album_artists {
let album_id = album.id.ok_or(ServerFnError::ServerError::<NoCustomError>( if let Some(stored_album) = albums_map.get_mut(&album.id) {
"Album id not found in database".to_string(),
))?;
if let Some(stored_album) = albums_map.get_mut(&album_id) {
stored_album.artists.push(artist); stored_album.artists.push(artist);
} else { } else {
let albumdata = frontend::Album { let albumdata = frontend::Album {
id: album_id, id: album.id,
title: album.title, title: album.title,
artists: vec![artist], artists: vec![artist],
release_date: album.release_date, release_date: album.release_date,
@ -247,7 +235,7 @@ pub async fn albums_by_artist(
.unwrap_or("/assets/images/placeholders/MusicPlaceholder.svg".to_string()), .unwrap_or("/assets/images/placeholders/MusicPlaceholder.svg".to_string()),
}; };
albums_map.insert(album_id, albumdata); albums_map.insert(album.id, albumdata);
} }
} }

View File

@ -12,14 +12,14 @@ cfg_if! {
} }
use crate::api::users::UserCredentials; use crate::api::users::UserCredentials;
use crate::models::backend::User; use crate::models::backend::{NewUser, User};
use crate::util::serverfn_client::Client; use crate::util::serverfn_client::Client;
/// Create a new user and log them in /// Create a new user and log them in
/// Takes in a NewUser struct, with the password in plaintext /// Takes in a NewUser struct, with the password in plaintext
/// Returns a Result with the error message if the user could not be created /// Returns a Result with the error message if the user could not be created
#[server(endpoint = "signup", client = Client)] #[server(endpoint = "signup", client = Client)]
pub async fn signup(new_user: User) -> Result<(), ServerFnError> { pub async fn signup(new_user: NewUser) -> Result<(), ServerFnError> {
// Check LIBRETUNES_DISABLE_SIGNUP env var // Check LIBRETUNES_DISABLE_SIGNUP env var
if std::env::var("LIBRETUNES_DISABLE_SIGNUP").is_ok_and(|v| v == "true") { if std::env::var("LIBRETUNES_DISABLE_SIGNUP").is_ok_and(|v| v == "true") {
return Err(ServerFnError::<NoCustomError>::ServerError( return Err(ServerFnError::<NoCustomError>::ServerError(
@ -30,8 +30,7 @@ pub async fn signup(new_user: User) -> Result<(), ServerFnError> {
use crate::api::users::create_user; use crate::api::users::create_user;
// Ensure the user has no id, and is not a self-proclaimed admin // Ensure the user has no id, and is not a self-proclaimed admin
let new_user = User { let new_user = NewUser {
id: None,
admin: false, admin: false,
..new_user ..new_user
}; };

View File

@ -47,10 +47,6 @@ pub async fn upload_picture(data: MultipartData) -> Result<(), ServerFnError> {
ServerFnError::<NoCustomError>::ServerError(format!("Error getting user: {e}")) ServerFnError::<NoCustomError>::ServerError(format!("Error getting user: {e}"))
})?; })?;
let user_id = user
.id
.ok_or_else(|| ServerFnError::<NoCustomError>::ServerError("User has no id".to_string()))?;
// Read the image, and convert it to webp // Read the image, and convert it to webp
use image_convert::{to_webp, ImageResource, WEBPConfig}; use image_convert::{to_webp, ImageResource, WEBPConfig};
@ -63,7 +59,7 @@ pub async fn upload_picture(data: MultipartData) -> Result<(), ServerFnError> {
ServerFnError::<NoCustomError>::ServerError(format!("Error creating image resource: {e}")) ServerFnError::<NoCustomError>::ServerError(format!("Error creating image resource: {e}"))
})?; })?;
let profile_picture_path = format!("assets/images/profile/{user_id}.webp"); let profile_picture_path = format!("assets/images/profile/{}.webp", user.id);
let mut image_target = ImageResource::from_path(&profile_picture_path); let mut image_target = ImageResource::from_path(&profile_picture_path);
to_webp(&mut image_target, &image_source, &WEBPConfig::new()).map_err(|e| { to_webp(&mut image_target, &image_source, &WEBPConfig::new()).map_err(|e| {
ServerFnError::<NoCustomError>::ServerError(format!("Error converting image to webp: {e}")) ServerFnError::<NoCustomError>::ServerError(format!("Error converting image to webp: {e}"))
@ -87,8 +83,7 @@ pub async fn recent_songs(
.map_err(|e| { .map_err(|e| {
ServerFnError::<NoCustomError>::ServerError(format!("Error getting user: {e}")) ServerFnError::<NoCustomError>::ServerError(format!("Error getting user: {e}"))
})? })?
.id .id;
.unwrap();
let mut db_con = get_db_conn(); let mut db_con = get_db_conn();
@ -181,7 +176,7 @@ pub async fn recent_songs(
song_path: song.storage_path, song_path: song.storage_path,
image_path, image_path,
like_dislike, like_dislike,
added_date: song.added_date.unwrap(), added_date: song.added_date,
}; };
history_songs.insert(song_id, (history.date, songdata)); history_songs.insert(song_id, (history.date, songdata));
@ -211,8 +206,7 @@ pub async fn top_songs(
.map_err(|e| { .map_err(|e| {
ServerFnError::<NoCustomError>::ServerError(format!("Error getting user: {e}")) ServerFnError::<NoCustomError>::ServerError(format!("Error getting user: {e}"))
})? })?
.id .id;
.unwrap();
let mut db_con = get_db_conn(); let mut db_con = get_db_conn();
@ -277,11 +271,7 @@ pub async fn top_songs(
HashMap::with_capacity(history_counts.len()); HashMap::with_capacity(history_counts.len());
for (song, album, artist, like, dislike) in history_songs { for (song, album, artist, like, dislike) in history_songs {
let song_id = song.id.ok_or(ServerFnError::ServerError::<NoCustomError>( if let Some((_, stored_songdata)) = history_songs_map.get_mut(&song.id) {
"Song id not found in database".to_string(),
))?;
if let Some((_, stored_songdata)) = history_songs_map.get_mut(&song_id) {
// If the song is already in the map, update the artists // If the song is already in the map, update the artists
if let Some(artist) = artist { if let Some(artist) = artist {
stored_songdata.artists.push(artist); stored_songdata.artists.push(artist);
@ -302,7 +292,7 @@ pub async fn top_songs(
); );
let songdata = frontend::Song { let songdata = frontend::Song {
id: song_id, id: song.id,
title: song.title, title: song.title,
artists: artist.map(|artist| vec![artist]).unwrap_or_default(), artists: artist.map(|artist| vec![artist]).unwrap_or_default(),
album, album,
@ -312,16 +302,16 @@ pub async fn top_songs(
song_path: song.storage_path, song_path: song.storage_path,
image_path, image_path,
like_dislike, like_dislike,
added_date: song.added_date.unwrap(), added_date: song.added_date,
}; };
let plays = history_counts let plays = history_counts
.get(&song_id) .get(&song.id)
.ok_or(ServerFnError::ServerError::<NoCustomError>( .ok_or(ServerFnError::ServerError::<NoCustomError>(
"Song id not found in history counts".to_string(), "Song id not found in history counts".to_string(),
))?; ))?;
history_songs_map.insert(song_id, (*plays, songdata)); history_songs_map.insert(song.id, (*plays, songdata));
} }
} }
@ -373,9 +363,9 @@ pub async fn top_artists(
( (
plays, plays,
frontend::Artist { frontend::Artist {
id: artist.id.unwrap(), id: artist.id,
name: artist.name, name: artist.name,
image_path: format!("/assets/images/artist/{}.webp", artist.id.unwrap()), image_path: format!("/assets/images/artist/{}.webp", artist.id),
}, },
) )
}) })

View File

@ -78,13 +78,11 @@ pub async fn search_albums(
.load(&mut db_conn)?; .load(&mut db_conn)?;
for (album, artist, score) in album_artists { for (album, artist, score) in album_artists {
let album_id = album.id.unwrap(); if let Some((stored_album, _score)) = albums_map.get_mut(&album.id) {
if let Some((stored_album, _score)) = albums_map.get_mut(&album_id) {
stored_album.artists.push(artist); stored_album.artists.push(artist);
} else { } else {
let albumdata = frontend::Album { let albumdata = frontend::Album {
id: album_id, id: album.id,
title: album.title, title: album.title,
artists: vec![artist], artists: vec![artist],
release_date: album.release_date, release_date: album.release_date,
@ -93,7 +91,7 @@ pub async fn search_albums(
.unwrap_or("/assets/images/placeholders/MusicPlaceholder.svg".to_string()), .unwrap_or("/assets/images/placeholders/MusicPlaceholder.svg".to_string()),
}; };
albums_map.insert(album_id, (albumdata, score)); albums_map.insert(album.id, (albumdata, score));
} }
} }
@ -132,9 +130,9 @@ pub async fn search_artists(
.map(|(artist, score)| { .map(|(artist, score)| {
( (
frontend::Artist { frontend::Artist {
id: artist.id.unwrap(), id: artist.id,
name: artist.name, name: artist.name,
image_path: format!("/assets/images/artist/{}.webp", artist.id.unwrap()), image_path: format!("/assets/images/artist/{}.webp", artist.id),
}, },
score, score,
) )
@ -165,7 +163,6 @@ pub async fn search_songs(
let mut db_conn = get_db_conn(); let mut db_conn = get_db_conn();
let song_list = if let Some(user) = user { let song_list = if let Some(user) = user {
let user_id = user.id.unwrap();
let song_list: Vec<( let song_list: Vec<(
backend::Song, backend::Song,
Option<backend::Album>, Option<backend::Album>,
@ -186,12 +183,12 @@ pub async fn search_songs(
.left_join( .left_join(
song_likes::table.on(songs::id song_likes::table.on(songs::id
.eq(song_likes::song_id) .eq(song_likes::song_id)
.and(song_likes::user_id.eq(user_id))), .and(song_likes::user_id.eq(user.id))),
) )
.left_join( .left_join(
song_dislikes::table.on(songs::id song_dislikes::table.on(songs::id
.eq(song_dislikes::song_id) .eq(song_dislikes::song_id)
.and(song_dislikes::user_id.eq(user_id))), .and(song_dislikes::user_id.eq(user.id))),
) )
.select(( .select((
songs::all_columns, songs::all_columns,
@ -238,7 +235,7 @@ pub async fn search_songs(
HashMap::with_capacity(song_list.len()); HashMap::with_capacity(song_list.len());
for (song, album, artist, like, dislike, score) in song_list { for (song, album, artist, like, dislike, score) in song_list {
if let Some((stored_songdata, _score)) = search_songs.get_mut(&song.id.unwrap()) { if let Some((stored_songdata, _score)) = search_songs.get_mut(&song.id) {
// If the song is already in the map, update the artists // If the song is already in the map, update the artists
if let Some(artist) = artist { if let Some(artist) = artist {
stored_songdata.artists.push(artist); stored_songdata.artists.push(artist);
@ -259,7 +256,7 @@ pub async fn search_songs(
); );
let songdata = frontend::Song { let songdata = frontend::Song {
id: song.id.unwrap(), id: song.id,
title: song.title, title: song.title,
artists: artist.map(|artist| vec![artist]).unwrap_or_default(), artists: artist.map(|artist| vec![artist]).unwrap_or_default(),
album, album,
@ -269,10 +266,10 @@ pub async fn search_songs(
song_path: song.storage_path, song_path: song.storage_path,
image_path, image_path,
like_dislike, like_dislike,
added_date: song.added_date.unwrap(), added_date: song.added_date,
}; };
search_songs.insert(song.id.unwrap(), (songdata, score)); search_songs.insert(song.id, (songdata, score));
} }
} }

View File

@ -76,8 +76,7 @@ pub async fn get_song_by_id(song_id: i32) -> Result<Option<frontend::Song>, Serv
.map_err(|e| { .map_err(|e| {
ServerFnError::<NoCustomError>::ServerError(format!("Error getting user: {e}")) ServerFnError::<NoCustomError>::ServerError(format!("Error getting user: {e}"))
})? })?
.id .id;
.unwrap();
let db_con = &mut get_db_conn(); let db_con = &mut get_db_conn();
@ -131,7 +130,7 @@ pub async fn get_song_by_id(song_id: i32) -> Result<Option<frontend::Song>, Serv
}); });
Ok(Some(frontend::Song { Ok(Some(frontend::Song {
id: song.id.unwrap(), id: song.id,
title: song.title.clone(), title: song.title.clone(),
artists, artists,
album: album.clone(), album: album.clone(),
@ -141,7 +140,7 @@ pub async fn get_song_by_id(song_id: i32) -> Result<Option<frontend::Song>, Serv
song_path: song.storage_path.clone(), song_path: song.storage_path.clone(),
image_path, image_path,
like_dislike: Some((like.is_some(), dislike.is_some())), like_dislike: Some((like.is_some(), dislike.is_some())),
added_date: song.added_date.unwrap(), added_date: song.added_date,
})) }))
} }
None => Ok(None), None => Ok(None),
@ -174,8 +173,7 @@ pub async fn get_my_song_plays(song_id: i32) -> Result<i64, ServerFnError> {
.map_err(|e| { .map_err(|e| {
ServerFnError::<NoCustomError>::ServerError(format!("Error getting user: {e}")) ServerFnError::<NoCustomError>::ServerError(format!("Error getting user: {e}"))
})? })?
.id .id;
.unwrap();
let db_con = &mut get_db_conn(); let db_con = &mut get_db_conn();

View File

@ -298,9 +298,8 @@ pub async fn upload(data: MultipartData) -> Result<(), ServerFnError> {
} }
// Create the song // Create the song
use crate::models::backend::Song; use crate::models::backend::{NewSong, Song};
let song = Song { let song = NewSong {
id: None,
title, title,
album_id, album_id,
track, track,
@ -308,7 +307,6 @@ pub async fn upload(data: MultipartData) -> Result<(), ServerFnError> {
release_date, release_date,
storage_path: file_name, storage_path: file_name,
image_path: None, image_path: None,
added_date: None, // Defaults to current date
}; };
// Save the song to the database // Save the song to the database
@ -323,12 +321,6 @@ pub async fn upload(data: MultipartData) -> Result<(), ServerFnError> {
})?; })?;
// Save the song's artists to the database // Save the song's artists to the database
let song_id = song.id.ok_or_else(|| {
let msg = "Error saving song to database: song id not found after insertion".to_string();
warn!("{}", msg);
ServerFnError::<NoCustomError>::ServerError(msg)
})?;
use crate::schema::song_artists; use crate::schema::song_artists;
use diesel::ExpressionMethods; use diesel::ExpressionMethods;
@ -336,7 +328,7 @@ pub async fn upload(data: MultipartData) -> Result<(), ServerFnError> {
.into_iter() .into_iter()
.map(|artist_id| { .map(|artist_id| {
( (
song_artists::song_id.eq(song_id), song_artists::song_id.eq(song.id),
song_artists::artist_id.eq(artist_id), song_artists::artist_id.eq(artist_id),
) )
}) })

View File

@ -10,6 +10,8 @@ cfg_if::cfg_if! {
}, },
Pbkdf2 Pbkdf2
}; };
use crate::models::backend::NewUser;
} }
} }
@ -71,7 +73,7 @@ pub async fn find_user_by_id(user_id: i32) -> Result<Option<User>, ServerFnError
/// Create a new user in the database /// Create a new user in the database
/// Returns an empty Result if successful, or an error if there was a problem /// Returns an empty Result if successful, or an error if there was a problem
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
pub async fn create_user(new_user: &User) -> Result<(), ServerFnError> { pub async fn create_user(new_user: &NewUser) -> Result<(), ServerFnError> {
use crate::schema::users::dsl::*; use crate::schema::users::dsl::*;
use leptos::server_fn::error::NoCustomError; use leptos::server_fn::error::NoCustomError;
@ -92,7 +94,7 @@ pub async fn create_user(new_user: &User) -> Result<(), ServerFnError> {
})? })?
.to_string(); .to_string();
let new_user = User { let new_user = NewUser {
password: Some(password_hash), password: Some(password_hash),
..new_user.clone() ..new_user.clone()
}; };

View File

@ -35,8 +35,7 @@ pub fn Profile() -> impl IntoView {
let user_profile_picture = move || { let user_profile_picture = move || {
user.get().and_then(|user| { user.get().and_then(|user| {
if let Some(user) = user { if let Some(user) = user {
user.id?; Some(format!("/assets/images/profile/{}.webp", user.id))
Some(format!("/assets/images/profile/{}.webp", user.id.unwrap()))
} else { } else {
None None
} }

View File

@ -176,14 +176,8 @@ pub fn SongArtists(artists: Vec<Artist>) -> impl IntoView {
let i = i as isize; let i = i as isize;
view! { view! {
{ <a class="hover:underline active:text-controls-active"
if let Some(id) = artist.id { href={format!("/artist/{}", artist.id)}>{artist.name.clone()}</a>
Either::Left(view! { <a class="hover:underline active:text-controls-active"
href={format!("/artist/{id}")}>{artist.name.clone()}</a> })
} else {
Either::Right(view! { <span>{artist.name.clone()}</span> })
}
}
{ {
use std::cmp::Ordering; use std::cmp::Ordering;
@ -204,14 +198,8 @@ pub fn SongAlbum(album: Option<Album>) -> impl IntoView {
album.as_ref().map(|album| { album.as_ref().map(|album| {
view! { view! {
<span> <span>
{ <a class="hover:underline active:text-controls-active"
if let Some(id) = album.id { href={format!("/album/{}", album.id)}>{album.title.clone()}</a>
Either::Left(view! { <a class="hover:underline active:text-controls-active"
href={format!("/album/{id}")}>{album.title.clone()}</a> })
} else {
Either::Right(view! { <span>{album.title.clone()}</span> })
}
}
</span> </span>
} }
}) })

View File

@ -57,7 +57,7 @@ pub fn Upload(open: RwSignal<bool>) -> impl IntoView {
artists artists
.into_iter() .into_iter()
.map(|(artist, _score)| Artist { .map(|(artist, _score)| Artist {
id: Some(artist.id), id: artist.id,
name: artist.name, name: artist.name,
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
@ -88,7 +88,7 @@ pub fn Upload(open: RwSignal<bool>) -> impl IntoView {
albums albums
.into_iter() .into_iter()
.map(|(album, _score)| Album { .map(|(album, _score)| Album {
id: Some(album.id), id: album.id,
title: album.title, title: album.title,
release_date: album.release_date, release_date: album.release_date,
image_path: Some(album.image_path), image_path: Some(album.image_path),
@ -217,11 +217,7 @@ pub fn Artist(
let mut ids: Vec<&str> = all_artirts.split(",").collect(); let mut ids: Vec<&str> = all_artirts.split(",").collect();
//If there is only one artist in the input, get their id equivalent and add it to the string //If there is only one artist in the input, get their id equivalent and add it to the string
if ids.len() == 1 { if ids.len() == 1 {
let value_str = match artist.id { s.push_str(&artist.id.to_string());
Some(v) => v.to_string(),
None => String::from("None"),
};
s.push_str(&value_str);
s.push(','); s.push(',');
set_artists.update(|value| *value = s); set_artists.update(|value| *value = s);
//If there are multiple artists in the input, pop the last artist by string off the vector, //If there are multiple artists in the input, pop the last artist by string off the vector,
@ -232,11 +228,7 @@ pub fn Artist(
s.push_str(id); s.push_str(id);
s.push(','); s.push(',');
} }
let value_str = match artist.id { s.push_str(&artist.id.to_string());
Some(v) => v.to_string(),
None => String::from("None"),
};
s.push_str(&value_str);
s.push(','); s.push(',');
set_artists.update(|value| *value = s); set_artists.update(|value| *value = s);
} }
@ -259,11 +251,7 @@ pub fn Album(
) -> impl IntoView { ) -> impl IntoView {
//Converts album title to album id to upload a song //Converts album title to album id to upload a song
let add_album = move |_| { let add_album = move |_| {
let value_str = match album.id { set_albums.update(|value| *value = album.id.to_string());
Some(v) => v.to_string(),
None => String::from("None"),
};
set_albums.update(|value| *value = value_str);
set_filtered.update(|value| *value = vec![]); set_filtered.update(|value| *value = vec![]);
}; };
view! { view! {

View File

@ -1,26 +1,14 @@
use chrono::NaiveDate; use chrono::NaiveDate;
use libretunes_macro::db_type;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "ssr")] {
use diesel::prelude::*;
}
}
/// Model for an album /// Model for an album
#[cfg_attr( #[db_type(crate::schema::albums)]
feature = "ssr",
derive(Queryable, Selectable, Insertable, Identifiable)
)]
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::albums))]
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Album { pub struct Album {
/// A unique id for the album /// A unique id for the album
#[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))] #[omit_new]
pub id: Option<i32>, pub id: i32,
/// The album's title /// The album's title
pub title: String, pub title: String,
/// The album's release date /// The album's release date

View File

@ -1,25 +1,13 @@
use libretunes_macro::db_type;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "ssr")] {
use diesel::prelude::*;
}
}
/// Model for an artist /// Model for an artist
#[cfg_attr( #[db_type(crate::schema::artists)]
feature = "ssr",
derive(Queryable, Selectable, Insertable, Identifiable)
)]
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::artists))]
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Artist { pub struct Artist {
/// A unique id for the artist /// A unique id for the artist
#[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))] #[omit_new]
pub id: Option<i32>, pub id: i32,
/// The artist's name /// The artist's name
pub name: String, pub name: String,
} }

View File

@ -1,23 +1,14 @@
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use libretunes_macro::db_type;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "ssr")] {
use diesel::prelude::*;
}
}
/// Model for a history entry /// Model for a history entry
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))] #[db_type(crate::schema::song_history)]
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::song_history))]
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct HistoryEntry { pub struct HistoryEntry {
/// A unique id for the history entry /// A unique id for the history entry
#[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))] #[omit_new]
pub id: Option<i32>, pub id: i32,
/// The id of the user who listened to the song /// The id of the user who listened to the song
pub user_id: i32, pub user_id: i32,
/// The date the song was listened to /// The date the song was listened to

View File

@ -12,8 +12,14 @@ pub mod song;
pub mod user; pub mod user;
pub use album::Album; pub use album::Album;
pub use album::NewAlbum;
pub use artist::Artist; pub use artist::Artist;
pub use artist::NewArtist;
pub use history_entry::HistoryEntry; pub use history_entry::HistoryEntry;
pub use history_entry::NewHistoryEntry;
pub use playlist::NewPlaylist;
pub use playlist::Playlist; pub use playlist::Playlist;
pub use song::NewSong;
pub use song::Song; pub use song::Song;
pub use user::NewUser;
pub use user::User; pub use user::User;

View File

@ -1,29 +1,20 @@
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use libretunes_macro::db_type;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "ssr")] {
use diesel::prelude::*;
}
}
/// Model for a playlist /// Model for a playlist
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))] #[db_type(crate::schema::playlists)]
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::playlists))]
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct Playlist { pub struct Playlist {
/// A unique id for the playlist /// A unique id for the playlist
#[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))] #[omit_new]
pub id: Option<i32>, pub id: i32,
/// The time the playlist was created /// The time the playlist was created
#[cfg_attr(feature = "ssr", diesel(deserialize_as = NaiveDateTime))] #[omit_new]
pub created_at: Option<NaiveDateTime>, pub created_at: NaiveDateTime,
/// The time the playlist was last updated /// The time the playlist was last updated
#[cfg_attr(feature = "ssr", diesel(deserialize_as = NaiveDateTime))] #[omit_new]
pub updated_at: Option<NaiveDateTime>, pub updated_at: NaiveDateTime,
/// The id of the user who owns the playlist /// The id of the user who owns the playlist
pub owner_id: i32, pub owner_id: i32,
/// The name of the playlist /// The name of the playlist

View File

@ -1,23 +1,13 @@
use chrono::{NaiveDate, NaiveDateTime}; use chrono::{NaiveDate, NaiveDateTime};
use libretunes_macro::db_type;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use cfg_if::cfg_if; #[db_type(crate::schema::songs)]
cfg_if! {
if #[cfg(feature = "ssr")] {
use diesel::prelude::*;
}
}
/// Model for a song
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))]
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::songs))]
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct Song { pub struct Song {
/// A unique id for the song /// A unique id for the song
#[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))] #[omit_new]
pub id: Option<i32>, pub id: i32,
/// The song's title /// The song's title
pub title: String, pub title: String,
/// The album the song is from /// The album the song is from
@ -33,6 +23,6 @@ pub struct Song {
/// The path to the song's image file /// The path to the song's image file
pub image_path: Option<String>, pub image_path: Option<String>,
/// The date the song was added to the database /// The date the song was added to the database
#[cfg_attr(feature = "ssr", diesel(deserialize_as = NaiveDateTime))] #[omit_new]
pub added_date: Option<NaiveDateTime>, pub added_date: NaiveDateTime,
} }

View File

@ -1,4 +1,5 @@
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use libretunes_macro::db_type;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use cfg_if::cfg_if; use cfg_if::cfg_if;
@ -16,15 +17,12 @@ cfg_if! {
/// Various fields are wrapped in Options, because they are not always wanted for inserts/retrieval /// Various fields are wrapped in Options, because they are not always wanted for inserts/retrieval
/// Using `deserialize_as` makes Diesel use the specified type when deserializing from the database, /// Using `deserialize_as` makes Diesel use the specified type when deserializing from the database,
/// and then call `.into()` to convert it into the Option /// and then call `.into()` to convert it into the Option
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))] #[db_type(crate::schema::users)]
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::users))]
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct User { pub struct User {
/// A unique id for the user /// A unique id for the user
#[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))] #[omit_new]
// #[cfg_attr(feature = "ssr", diesel(skip_insertion))] // This feature is not yet released pub id: i32,
pub id: Option<i32>,
/// The user's username /// The user's username
pub username: String, pub username: String,
/// The user's email /// The user's email
@ -33,8 +31,8 @@ pub struct User {
#[cfg_attr(feature = "ssr", diesel(deserialize_as = String))] #[cfg_attr(feature = "ssr", diesel(deserialize_as = String))]
pub password: Option<String>, pub password: Option<String>,
/// The time the user was created /// The time the user was created
#[cfg_attr(feature = "ssr", diesel(deserialize_as = NaiveDateTime))] #[omit_new]
pub created_at: Option<NaiveDateTime>, pub created_at: NaiveDateTime,
/// Whether the user is an admin /// Whether the user is an admin
pub admin: bool, pub admin: bool,
} }
@ -64,18 +62,14 @@ impl User {
) -> Result<Vec<HistoryEntry>, Box<dyn Error>> { ) -> Result<Vec<HistoryEntry>, Box<dyn Error>> {
use crate::schema::song_history::dsl::*; use crate::schema::song_history::dsl::*;
let my_id = self
.id
.ok_or("Artist id must be present (Some) to get history")?;
let my_history = if let Some(limit) = limit { let my_history = if let Some(limit) = limit {
song_history song_history
.filter(user_id.eq(my_id)) .filter(user_id.eq(self.id))
.order(date.desc()) .order(date.desc())
.limit(limit) .limit(limit)
.load(conn)? .load(conn)?
} else { } else {
song_history.filter(user_id.eq(my_id)).load(conn)? song_history.filter(user_id.eq(self.id)).load(conn)?
}; };
Ok(my_history) Ok(my_history)
@ -106,14 +100,10 @@ impl User {
use crate::schema::song_history::dsl::*; use crate::schema::song_history::dsl::*;
use crate::schema::songs::dsl::*; use crate::schema::songs::dsl::*;
let my_id = self
.id
.ok_or("Artist id must be present (Some) to get history")?;
let my_history = if let Some(limit) = limit { let my_history = if let Some(limit) = limit {
song_history song_history
.inner_join(songs) .inner_join(songs)
.filter(user_id.eq(my_id)) .filter(user_id.eq(self.id))
.order(date.desc()) .order(date.desc())
.limit(limit) .limit(limit)
.select((date, songs::all_columns())) .select((date, songs::all_columns()))
@ -121,7 +111,7 @@ impl User {
} else { } else {
song_history song_history
.inner_join(songs) .inner_join(songs)
.filter(user_id.eq(my_id)) .filter(user_id.eq(self.id))
.order(date.desc()) .order(date.desc())
.select((date, songs::all_columns())) .select((date, songs::all_columns()))
.load(conn)? .load(conn)?
@ -148,13 +138,9 @@ impl User {
pub fn add_history(&self, song_id: i32, conn: &mut PgPooledConn) -> Result<(), Box<dyn Error>> { pub fn add_history(&self, song_id: i32, conn: &mut PgPooledConn) -> Result<(), Box<dyn Error>> {
use crate::schema::song_history; use crate::schema::song_history;
let my_id = self
.id
.ok_or("Artist id must be present (Some) to add history")?;
diesel::insert_into(song_history::table) diesel::insert_into(song_history::table)
.values(( .values((
song_history::user_id.eq(my_id), song_history::user_id.eq(self.id),
song_history::song_id.eq(song_id), song_history::song_id.eq(song_id),
)) ))
.execute(conn)?; .execute(conn)?;
@ -177,15 +163,11 @@ impl User {
use crate::schema::song_dislikes; use crate::schema::song_dislikes;
use crate::schema::song_likes; use crate::schema::song_likes;
let my_id = self
.id
.ok_or("User id must be present (Some) to like/un-like a song")?;
if like { if like {
diesel::insert_into(song_likes::table) diesel::insert_into(song_likes::table)
.values(( .values((
song_likes::song_id.eq(song_id), song_likes::song_id.eq(song_id),
song_likes::user_id.eq(my_id), song_likes::user_id.eq(self.id),
)) ))
.execute(conn)?; .execute(conn)?;
@ -194,7 +176,7 @@ impl User {
song_dislikes::table.filter( song_dislikes::table.filter(
song_dislikes::song_id song_dislikes::song_id
.eq(song_id) .eq(song_id)
.and(song_dislikes::user_id.eq(my_id)), .and(song_dislikes::user_id.eq(self.id)),
), ),
) )
.execute(conn)?; .execute(conn)?;
@ -203,7 +185,7 @@ impl User {
song_likes::table.filter( song_likes::table.filter(
song_likes::song_id song_likes::song_id
.eq(song_id) .eq(song_id)
.and(song_likes::user_id.eq(my_id)), .and(song_likes::user_id.eq(self.id)),
), ),
) )
.execute(conn)?; .execute(conn)?;
@ -221,15 +203,11 @@ impl User {
) -> Result<bool, Box<dyn Error>> { ) -> Result<bool, Box<dyn Error>> {
use crate::schema::song_likes; use crate::schema::song_likes;
let my_id = self
.id
.ok_or("User id must be present (Some) to get like status of a song")?;
let like = song_likes::table let like = song_likes::table
.filter( .filter(
song_likes::song_id song_likes::song_id
.eq(song_id) .eq(song_id)
.and(song_likes::user_id.eq(my_id)), .and(song_likes::user_id.eq(self.id)),
) )
.first::<(i32, i32)>(conn) .first::<(i32, i32)>(conn)
.optional()? .optional()?
@ -253,15 +231,11 @@ impl User {
use crate::schema::song_dislikes; use crate::schema::song_dislikes;
use crate::schema::song_likes; use crate::schema::song_likes;
let my_id = self
.id
.ok_or("User id must be present (Some) to dislike/un-dislike a song")?;
if dislike { if dislike {
diesel::insert_into(song_dislikes::table) diesel::insert_into(song_dislikes::table)
.values(( .values((
song_dislikes::song_id.eq(song_id), song_dislikes::song_id.eq(song_id),
song_dislikes::user_id.eq(my_id), song_dislikes::user_id.eq(self.id),
)) ))
.execute(conn)?; .execute(conn)?;
@ -270,7 +244,7 @@ impl User {
song_likes::table.filter( song_likes::table.filter(
song_likes::song_id song_likes::song_id
.eq(song_id) .eq(song_id)
.and(song_likes::user_id.eq(my_id)), .and(song_likes::user_id.eq(self.id)),
), ),
) )
.execute(conn)?; .execute(conn)?;
@ -279,7 +253,7 @@ impl User {
song_dislikes::table.filter( song_dislikes::table.filter(
song_dislikes::song_id song_dislikes::song_id
.eq(song_id) .eq(song_id)
.and(song_dislikes::user_id.eq(my_id)), .and(song_dislikes::user_id.eq(self.id)),
), ),
) )
.execute(conn)?; .execute(conn)?;
@ -297,15 +271,11 @@ impl User {
) -> Result<bool, Box<dyn Error>> { ) -> Result<bool, Box<dyn Error>> {
use crate::schema::song_dislikes; use crate::schema::song_dislikes;
let my_id = self
.id
.ok_or("User id must be present (Some) to get dislike status of a song")?;
let dislike = song_dislikes::table let dislike = song_dislikes::table
.filter( .filter(
song_dislikes::song_id song_dislikes::song_id
.eq(song_id) .eq(song_id)
.and(song_dislikes::user_id.eq(my_id)), .and(song_dislikes::user_id.eq(self.id)),
) )
.first::<(i32, i32)>(conn) .first::<(i32, i32)>(conn)
.optional()? .optional()?

View File

@ -44,16 +44,9 @@ impl TryInto<backend::Song> for Song {
/// due to the way the `image_path` data is handled. /// due to the way the `image_path` data is handled.
fn try_into(self) -> Result<backend::Song, Self::Error> { fn try_into(self) -> Result<backend::Song, Self::Error> {
Ok(backend::Song { Ok(backend::Song {
id: Some(self.id), id: self.id,
title: self.title, title: self.title,
album_id: self album_id: self.album.map(|album| album.id),
.album
.map(|album| {
album
.id
.ok_or("Album id must be present (Some) to convert to Song")
})
.transpose()?,
track: self.track, track: self.track,
duration: self.duration, duration: self.duration,
release_date: self.release_date, release_date: self.release_date,
@ -67,7 +60,7 @@ impl TryInto<backend::Song> for Song {
Some(self.image_path) Some(self.image_path)
}, },
added_date: Some(self.added_date), added_date: self.added_date,
}) })
} }
} }

View File

@ -83,8 +83,7 @@ fn ArtistIdProfile(#[prop(into)] id: Signal<i32>) -> impl IntoView {
#[component] #[component]
fn ArtistProfile(artist: Artist) -> impl IntoView { fn ArtistProfile(artist: Artist) -> impl IntoView {
let artist_id = artist.id.unwrap(); let profile_image_path = format!("/assets/images/artist/{}.webp", artist.id);
let profile_image_path = format!("/assets/images/artist/{artist_id}.webp");
leptos::logging::log!("Artist name: {}", artist.name); leptos::logging::log!("Artist name: {}", artist.name);

View File

@ -67,7 +67,7 @@ fn OwnProfile() -> impl IntoView {
{move || GlobalState::logged_in_user().get().map(|user| { {move || GlobalState::logged_in_user().get().map(|user| {
match user { match user {
Some(user) => { Some(user) => {
let user_id = user.id.unwrap(); let user_id = user.id;
Either::Left(view! { Either::Left(view! {
<UserProfile user /> <UserProfile user />
<TopSongs user_id={user_id} /> <TopSongs user_id={user_id} />
@ -140,8 +140,7 @@ fn UserIdProfile(#[prop(into)] id: Signal<i32>) -> impl IntoView {
/// Show a profile for a User object /// Show a profile for a User object
#[component] #[component]
fn UserProfile(user: User) -> impl IntoView { fn UserProfile(user: User) -> impl IntoView {
let user_id = user.id.unwrap(); let profile_image_path = format!("/assets/images/profile/{}.webp", user.id);
let profile_image_path = format!("/assets/images/profile/{user_id}.webp");
view! { view! {
<div class="flex"> <div class="flex">
@ -152,11 +151,7 @@ fn UserProfile(user: User) -> impl IntoView {
</div> </div>
<p class="m-2"> <p class="m-2">
{user.email} {user.email}
{ {format!(" • Joined {}", user.created_at.format("%B %Y"))}
user.created_at.map(|created_at| {
format!(" • Joined {}", created_at.format("%B %Y"))
})
}
{ {
if user.admin { if user.admin {
" • Admin" " • Admin"

View File

@ -1,7 +1,7 @@
use crate::api::auth::signup; use crate::api::auth::signup;
use crate::components::fancy_input::*; use crate::components::fancy_input::*;
use crate::components::loading::Loading; use crate::components::loading::Loading;
use crate::models::backend::User; use crate::models::backend::NewUser;
use crate::util::state::GlobalState; use crate::util::state::GlobalState;
use leptos::leptos_dom::*; use leptos::leptos_dom::*;
use leptos::prelude::*; use leptos::prelude::*;
@ -19,12 +19,10 @@ pub fn Signup() -> impl IntoView {
let on_submit = move |ev: leptos::ev::SubmitEvent| { let on_submit = move |ev: leptos::ev::SubmitEvent| {
ev.prevent_default(); ev.prevent_default();
let mut new_user = User { let new_user = NewUser {
id: None,
username: username.get_untracked(), username: username.get_untracked(),
email: email.get_untracked(), email: email.get_untracked(),
password: Some(password.get_untracked()), password: Some(password.get_untracked()),
created_at: None,
admin: false, admin: false,
}; };
log!("new user: {:?}", new_user); log!("new user: {:?}", new_user);
@ -43,9 +41,7 @@ pub fn Signup() -> impl IntoView {
// Since we're not sure what the state is, manually refetch the user // Since we're not sure what the state is, manually refetch the user
user.refetch(); user.refetch();
} else { } else {
// Manually set the user to the new user, avoiding a refetch user.refetch();
new_user.password = None;
user.set(Some(Some(new_user)));
// Redirect to the login page // Redirect to the login page
log!("Signed up successfully!"); log!("Signed up successfully!");

View File

@ -9,10 +9,8 @@ use async_trait::async_trait;
impl AuthUser for User { impl AuthUser for User {
type Id = i32; type Id = i32;
// TODO: Ideally, we shouldn't have to unwrap here
fn id(&self) -> Self::Id { fn id(&self) -> Self::Id {
self.id.unwrap() self.id
} }
fn session_auth_hash(&self) -> &[u8] { fn session_auth_hash(&self) -> &[u8] {