Compare commits
10 Commits
9da05edcd4
...
a31539dc8f
Author | SHA1 | Date | |
---|---|---|---|
a31539dc8f | |||
eda4e42150
|
|||
54d629d504
|
|||
6486bbbdda
|
|||
b727832c8e
|
|||
7c4058884e
|
|||
a67bd37d11
|
|||
3f43ef2d20
|
|||
0b599f4038
|
|||
c02363c698
|
18
Cargo.lock
generated
18
Cargo.lock
generated
@ -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",
|
||||||
|
@ -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 = [
|
||||||
|
@ -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 ];
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
};
|
};
|
||||||
|
@ -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),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -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()
|
||||||
};
|
};
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -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! {
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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()?
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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!");
|
||||||
|
@ -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] {
|
||||||
|
Reference in New Issue
Block a user