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
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
This commit is contained in:
@ -62,7 +62,6 @@ pub async fn get_songs(id: i32) -> Result<Vec<frontend::Song>, ServerFnError> {
|
||||
let db_con = &mut get_db_conn();
|
||||
|
||||
let song_list = if let Some(user) = user {
|
||||
let user_id = user.id.unwrap();
|
||||
let song_list: Vec<(
|
||||
backend::Album,
|
||||
Option<backend::Song>,
|
||||
@ -80,12 +79,12 @@ pub async fn get_songs(id: i32) -> Result<Vec<frontend::Song>, ServerFnError> {
|
||||
.left_join(
|
||||
song_likes::table.on(songs::id
|
||||
.eq(song_likes::song_id)
|
||||
.and(song_likes::user_id.eq(user_id))),
|
||||
.and(song_likes::user_id.eq(user.id))),
|
||||
)
|
||||
.left_join(
|
||||
song_dislikes::table.on(songs::id
|
||||
.eq(song_dislikes::song_id)
|
||||
.and(song_dislikes::user_id.eq(user_id))),
|
||||
.and(song_dislikes::user_id.eq(user.id))),
|
||||
)
|
||||
.select((
|
||||
albums::all_columns,
|
||||
|
@ -75,8 +75,7 @@ pub async fn top_songs_by_artist(
|
||||
.map_err(|e| {
|
||||
ServerFnError::ServerError::<NoCustomError>(format!("Error getting user: {e}"))
|
||||
})?
|
||||
.id
|
||||
.unwrap();
|
||||
.id;
|
||||
|
||||
let db = &mut get_db_conn();
|
||||
let song_play_counts: Vec<(i32, i64)> = if let Some(limit) = limit {
|
||||
|
@ -12,14 +12,14 @@ cfg_if! {
|
||||
}
|
||||
|
||||
use crate::api::users::UserCredentials;
|
||||
use crate::models::backend::User;
|
||||
use crate::models::backend::{NewUser, User};
|
||||
use crate::util::serverfn_client::Client;
|
||||
|
||||
/// Create a new user and log them in
|
||||
/// Takes in a NewUser struct, with the password in plaintext
|
||||
/// Returns a Result with the error message if the user could not be created
|
||||
#[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
|
||||
if std::env::var("LIBRETUNES_DISABLE_SIGNUP").is_ok_and(|v| v == "true") {
|
||||
return Err(ServerFnError::<NoCustomError>::ServerError(
|
||||
@ -30,8 +30,7 @@ pub async fn signup(new_user: User) -> Result<(), ServerFnError> {
|
||||
use crate::api::users::create_user;
|
||||
|
||||
// Ensure the user has no id, and is not a self-proclaimed admin
|
||||
let new_user = User {
|
||||
id: None,
|
||||
let new_user = NewUser {
|
||||
admin: false,
|
||||
..new_user
|
||||
};
|
||||
|
@ -47,10 +47,6 @@ pub async fn upload_picture(data: MultipartData) -> Result<(), ServerFnError> {
|
||||
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
|
||||
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}"))
|
||||
})?;
|
||||
|
||||
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);
|
||||
to_webp(&mut image_target, &image_source, &WEBPConfig::new()).map_err(|e| {
|
||||
ServerFnError::<NoCustomError>::ServerError(format!("Error converting image to webp: {e}"))
|
||||
@ -87,8 +83,7 @@ pub async fn recent_songs(
|
||||
.map_err(|e| {
|
||||
ServerFnError::<NoCustomError>::ServerError(format!("Error getting user: {e}"))
|
||||
})?
|
||||
.id
|
||||
.unwrap();
|
||||
.id;
|
||||
|
||||
let mut db_con = get_db_conn();
|
||||
|
||||
@ -211,8 +206,7 @@ pub async fn top_songs(
|
||||
.map_err(|e| {
|
||||
ServerFnError::<NoCustomError>::ServerError(format!("Error getting user: {e}"))
|
||||
})?
|
||||
.id
|
||||
.unwrap();
|
||||
.id;
|
||||
|
||||
let mut db_con = get_db_conn();
|
||||
|
||||
|
@ -163,7 +163,6 @@ pub async fn search_songs(
|
||||
let mut db_conn = get_db_conn();
|
||||
|
||||
let song_list = if let Some(user) = user {
|
||||
let user_id = user.id.unwrap();
|
||||
let song_list: Vec<(
|
||||
backend::Song,
|
||||
Option<backend::Album>,
|
||||
@ -184,12 +183,12 @@ pub async fn search_songs(
|
||||
.left_join(
|
||||
song_likes::table.on(songs::id
|
||||
.eq(song_likes::song_id)
|
||||
.and(song_likes::user_id.eq(user_id))),
|
||||
.and(song_likes::user_id.eq(user.id))),
|
||||
)
|
||||
.left_join(
|
||||
song_dislikes::table.on(songs::id
|
||||
.eq(song_dislikes::song_id)
|
||||
.and(song_dislikes::user_id.eq(user_id))),
|
||||
.and(song_dislikes::user_id.eq(user.id))),
|
||||
)
|
||||
.select((
|
||||
songs::all_columns,
|
||||
|
@ -76,8 +76,7 @@ pub async fn get_song_by_id(song_id: i32) -> Result<Option<frontend::Song>, Serv
|
||||
.map_err(|e| {
|
||||
ServerFnError::<NoCustomError>::ServerError(format!("Error getting user: {e}"))
|
||||
})?
|
||||
.id
|
||||
.unwrap();
|
||||
.id;
|
||||
|
||||
let db_con = &mut get_db_conn();
|
||||
|
||||
@ -174,8 +173,7 @@ pub async fn get_my_song_plays(song_id: i32) -> Result<i64, ServerFnError> {
|
||||
.map_err(|e| {
|
||||
ServerFnError::<NoCustomError>::ServerError(format!("Error getting user: {e}"))
|
||||
})?
|
||||
.id
|
||||
.unwrap();
|
||||
.id;
|
||||
|
||||
let db_con = &mut get_db_conn();
|
||||
|
||||
|
@ -10,6 +10,8 @@ cfg_if::cfg_if! {
|
||||
},
|
||||
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
|
||||
/// Returns an empty Result if successful, or an error if there was a problem
|
||||
#[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 leptos::server_fn::error::NoCustomError;
|
||||
|
||||
@ -92,7 +94,7 @@ pub async fn create_user(new_user: &User) -> Result<(), ServerFnError> {
|
||||
})?
|
||||
.to_string();
|
||||
|
||||
let new_user = User {
|
||||
let new_user = NewUser {
|
||||
password: Some(password_hash),
|
||||
..new_user.clone()
|
||||
};
|
||||
|
@ -35,8 +35,7 @@ pub fn Profile() -> impl IntoView {
|
||||
let user_profile_picture = move || {
|
||||
user.get().and_then(|user| {
|
||||
if let Some(user) = user {
|
||||
user.id?;
|
||||
Some(format!("/assets/images/profile/{}.webp", user.id.unwrap()))
|
||||
Some(format!("/assets/images/profile/{}.webp", user.id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -21,4 +21,5 @@ pub use playlist::NewPlaylist;
|
||||
pub use playlist::Playlist;
|
||||
pub use song::NewSong;
|
||||
pub use song::Song;
|
||||
pub use user::NewUser;
|
||||
pub use user::User;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use chrono::NaiveDateTime;
|
||||
use libretunes_macro::db_type;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
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
|
||||
/// Using `deserialize_as` makes Diesel use the specified type when deserializing from the database,
|
||||
/// and then call `.into()` to convert it into the Option
|
||||
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Insertable))]
|
||||
#[cfg_attr(feature = "ssr", diesel(table_name = crate::schema::users))]
|
||||
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[db_type(crate::schema::users)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct User {
|
||||
/// A unique id for the user
|
||||
#[cfg_attr(feature = "ssr", diesel(deserialize_as = i32))]
|
||||
// #[cfg_attr(feature = "ssr", diesel(skip_insertion))] // This feature is not yet released
|
||||
pub id: Option<i32>,
|
||||
#[omit_new]
|
||||
pub id: i32,
|
||||
/// The user's username
|
||||
pub username: String,
|
||||
/// The user's email
|
||||
@ -33,8 +31,8 @@ pub struct User {
|
||||
#[cfg_attr(feature = "ssr", diesel(deserialize_as = String))]
|
||||
pub password: Option<String>,
|
||||
/// The time the user was created
|
||||
#[cfg_attr(feature = "ssr", diesel(deserialize_as = NaiveDateTime))]
|
||||
pub created_at: Option<NaiveDateTime>,
|
||||
#[omit_new]
|
||||
pub created_at: NaiveDateTime,
|
||||
/// Whether the user is an admin
|
||||
pub admin: bool,
|
||||
}
|
||||
@ -64,18 +62,14 @@ impl User {
|
||||
) -> Result<Vec<HistoryEntry>, Box<dyn Error>> {
|
||||
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 {
|
||||
song_history
|
||||
.filter(user_id.eq(my_id))
|
||||
.filter(user_id.eq(self.id))
|
||||
.order(date.desc())
|
||||
.limit(limit)
|
||||
.load(conn)?
|
||||
} else {
|
||||
song_history.filter(user_id.eq(my_id)).load(conn)?
|
||||
song_history.filter(user_id.eq(self.id)).load(conn)?
|
||||
};
|
||||
|
||||
Ok(my_history)
|
||||
@ -106,14 +100,10 @@ impl User {
|
||||
use crate::schema::song_history::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 {
|
||||
song_history
|
||||
.inner_join(songs)
|
||||
.filter(user_id.eq(my_id))
|
||||
.filter(user_id.eq(self.id))
|
||||
.order(date.desc())
|
||||
.limit(limit)
|
||||
.select((date, songs::all_columns()))
|
||||
@ -121,7 +111,7 @@ impl User {
|
||||
} else {
|
||||
song_history
|
||||
.inner_join(songs)
|
||||
.filter(user_id.eq(my_id))
|
||||
.filter(user_id.eq(self.id))
|
||||
.order(date.desc())
|
||||
.select((date, songs::all_columns()))
|
||||
.load(conn)?
|
||||
@ -148,13 +138,9 @@ impl User {
|
||||
pub fn add_history(&self, song_id: i32, conn: &mut PgPooledConn) -> Result<(), Box<dyn Error>> {
|
||||
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)
|
||||
.values((
|
||||
song_history::user_id.eq(my_id),
|
||||
song_history::user_id.eq(self.id),
|
||||
song_history::song_id.eq(song_id),
|
||||
))
|
||||
.execute(conn)?;
|
||||
@ -177,15 +163,11 @@ impl User {
|
||||
use crate::schema::song_dislikes;
|
||||
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 {
|
||||
diesel::insert_into(song_likes::table)
|
||||
.values((
|
||||
song_likes::song_id.eq(song_id),
|
||||
song_likes::user_id.eq(my_id),
|
||||
song_likes::user_id.eq(self.id),
|
||||
))
|
||||
.execute(conn)?;
|
||||
|
||||
@ -194,7 +176,7 @@ impl User {
|
||||
song_dislikes::table.filter(
|
||||
song_dislikes::song_id
|
||||
.eq(song_id)
|
||||
.and(song_dislikes::user_id.eq(my_id)),
|
||||
.and(song_dislikes::user_id.eq(self.id)),
|
||||
),
|
||||
)
|
||||
.execute(conn)?;
|
||||
@ -203,7 +185,7 @@ impl User {
|
||||
song_likes::table.filter(
|
||||
song_likes::song_id
|
||||
.eq(song_id)
|
||||
.and(song_likes::user_id.eq(my_id)),
|
||||
.and(song_likes::user_id.eq(self.id)),
|
||||
),
|
||||
)
|
||||
.execute(conn)?;
|
||||
@ -221,15 +203,11 @@ impl User {
|
||||
) -> Result<bool, Box<dyn Error>> {
|
||||
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
|
||||
.filter(
|
||||
song_likes::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)
|
||||
.optional()?
|
||||
@ -253,15 +231,11 @@ impl User {
|
||||
use crate::schema::song_dislikes;
|
||||
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 {
|
||||
diesel::insert_into(song_dislikes::table)
|
||||
.values((
|
||||
song_dislikes::song_id.eq(song_id),
|
||||
song_dislikes::user_id.eq(my_id),
|
||||
song_dislikes::user_id.eq(self.id),
|
||||
))
|
||||
.execute(conn)?;
|
||||
|
||||
@ -270,7 +244,7 @@ impl User {
|
||||
song_likes::table.filter(
|
||||
song_likes::song_id
|
||||
.eq(song_id)
|
||||
.and(song_likes::user_id.eq(my_id)),
|
||||
.and(song_likes::user_id.eq(self.id)),
|
||||
),
|
||||
)
|
||||
.execute(conn)?;
|
||||
@ -279,7 +253,7 @@ impl User {
|
||||
song_dislikes::table.filter(
|
||||
song_dislikes::song_id
|
||||
.eq(song_id)
|
||||
.and(song_dislikes::user_id.eq(my_id)),
|
||||
.and(song_dislikes::user_id.eq(self.id)),
|
||||
),
|
||||
)
|
||||
.execute(conn)?;
|
||||
@ -297,15 +271,11 @@ impl User {
|
||||
) -> Result<bool, Box<dyn Error>> {
|
||||
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
|
||||
.filter(
|
||||
song_dislikes::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)
|
||||
.optional()?
|
||||
|
@ -67,7 +67,7 @@ fn OwnProfile() -> impl IntoView {
|
||||
{move || GlobalState::logged_in_user().get().map(|user| {
|
||||
match user {
|
||||
Some(user) => {
|
||||
let user_id = user.id.unwrap();
|
||||
let user_id = user.id;
|
||||
Either::Left(view! {
|
||||
<UserProfile user />
|
||||
<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
|
||||
#[component]
|
||||
fn UserProfile(user: User) -> impl IntoView {
|
||||
let user_id = user.id.unwrap();
|
||||
let profile_image_path = format!("/assets/images/profile/{user_id}.webp");
|
||||
let profile_image_path = format!("/assets/images/profile/{}.webp", user.id);
|
||||
|
||||
view! {
|
||||
<div class="flex">
|
||||
@ -152,11 +151,7 @@ fn UserProfile(user: User) -> impl IntoView {
|
||||
</div>
|
||||
<p class="m-2">
|
||||
{user.email}
|
||||
{
|
||||
user.created_at.map(|created_at| {
|
||||
format!(" • Joined {}", created_at.format("%B %Y"))
|
||||
})
|
||||
}
|
||||
{format!(" • Joined {}", user.created_at.format("%B %Y"))}
|
||||
{
|
||||
if user.admin {
|
||||
" • Admin"
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::api::auth::signup;
|
||||
use crate::components::fancy_input::*;
|
||||
use crate::components::loading::Loading;
|
||||
use crate::models::backend::User;
|
||||
use crate::models::backend::NewUser;
|
||||
use crate::util::state::GlobalState;
|
||||
use leptos::leptos_dom::*;
|
||||
use leptos::prelude::*;
|
||||
@ -19,12 +19,10 @@ pub fn Signup() -> impl IntoView {
|
||||
|
||||
let on_submit = move |ev: leptos::ev::SubmitEvent| {
|
||||
ev.prevent_default();
|
||||
let mut new_user = User {
|
||||
id: None,
|
||||
let new_user = NewUser {
|
||||
username: username.get_untracked(),
|
||||
email: email.get_untracked(),
|
||||
password: Some(password.get_untracked()),
|
||||
created_at: None,
|
||||
admin: false,
|
||||
};
|
||||
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
|
||||
user.refetch();
|
||||
} else {
|
||||
// Manually set the user to the new user, avoiding a refetch
|
||||
new_user.password = None;
|
||||
user.set(Some(Some(new_user)));
|
||||
user.refetch();
|
||||
|
||||
// Redirect to the login page
|
||||
log!("Signed up successfully!");
|
||||
|
@ -9,10 +9,8 @@ use async_trait::async_trait;
|
||||
impl AuthUser for User {
|
||||
type Id = i32;
|
||||
|
||||
// TODO: Ideally, we shouldn't have to unwrap here
|
||||
|
||||
fn id(&self) -> Self::Id {
|
||||
self.id.unwrap()
|
||||
self.id
|
||||
}
|
||||
|
||||
fn session_auth_hash(&self) -> &[u8] {
|
||||
|
Reference in New Issue
Block a user