diff --git a/assets/images/placeholders/MusicPlaceholder.svg b/assets/images/placeholders/MusicPlaceholder.svg
new file mode 100644
index 0000000..4a3917b
--- /dev/null
+++ b/assets/images/placeholders/MusicPlaceholder.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/migrations/2024-05-18-231505_add_user_admin/down.sql b/migrations/2024-05-18-231505_add_user_admin/down.sql
new file mode 100644
index 0000000..8c2de0f
--- /dev/null
+++ b/migrations/2024-05-18-231505_add_user_admin/down.sql
@@ -0,0 +1 @@
+ALTER TABLE users DROP COLUMN admin;
diff --git a/migrations/2024-05-18-231505_add_user_admin/up.sql b/migrations/2024-05-18-231505_add_user_admin/up.sql
new file mode 100644
index 0000000..58a6e49
--- /dev/null
+++ b/migrations/2024-05-18-231505_add_user_admin/up.sql
@@ -0,0 +1 @@
+ALTER TABLE users ADD COLUMN admin BOOLEAN DEFAULT FALSE NOT NULL;
diff --git a/src/auth.rs b/src/auth.rs
index 58ba6f4..37f861f 100644
--- a/src/auth.rs
+++ b/src/auth.rs
@@ -21,9 +21,10 @@ use crate::users::UserCredentials;
pub async fn signup(new_user: User) -> Result<(), ServerFnError> {
use crate::users::create_user;
- // Ensure the user has no id
+ // Ensure the user has no id, and is not a self-proclaimed admin
let new_user = User {
id: None,
+ admin: false,
..new_user
};
@@ -143,3 +144,37 @@ pub async fn get_user() -> Result {
auth_session.user.ok_or(ServerFnError::::ServerError("User not logged in".to_string()))
}
+
+/// Check if a user is an admin
+/// Returns a Result with a boolean indicating if the user is logged in and an admin
+#[server(endpoint = "check_admin")]
+pub async fn check_admin() -> Result {
+ let auth_session = extract::>().await
+ .map_err(|e| ServerFnError::::ServerError(format!("Error getting auth session: {}", e)))?;
+
+ Ok(auth_session.user.as_ref().map(|u| u.admin).unwrap_or(false))
+}
+
+/// Require that a user is logged in and an admin
+/// Returns a Result with the error message if the user is not logged in or is not an admin
+/// Intended to be used at the start of a protected route, to ensure the user is logged in and an admin:
+/// ```rust
+/// use leptos::*;
+/// use libretunes::auth::require_admin;
+/// #[server(endpoint = "protected_admin_route")]
+/// pub async fn protected_admin_route() -> Result<(), ServerFnError> {
+/// require_admin().await?;
+/// // Continue with protected route
+/// Ok(())
+/// }
+/// ```
+#[cfg(feature = "ssr")]
+pub async fn require_admin() -> Result<(), ServerFnError> {
+ check_admin().await.and_then(|is_admin| {
+ if is_admin {
+ Ok(())
+ } else {
+ Err(ServerFnError::::ServerError(format!("Unauthorized")))
+ }
+ })
+}
diff --git a/src/models.rs b/src/models.rs
index f0665ba..83b716d 100644
--- a/src/models.rs
+++ b/src/models.rs
@@ -41,6 +41,8 @@ pub struct User {
/// The time the user was created
#[cfg_attr(feature = "ssr", diesel(deserialize_as = SystemTime))]
pub created_at: Option,
+ /// Whether the user is an admin
+ pub admin: bool,
}
impl User {
@@ -304,6 +306,26 @@ impl Artist {
Ok(my_songs)
}
+
+ /// Display a list of artists as a string.
+ ///
+ /// For one artist, displays [artist1]. For two artists, displays [artist1] & [artist2].
+ /// For three or more artists, displays [artist1], [artist2], & [artist3].
+ pub fn display_list(artists: &Vec) -> String {
+ let mut artist_list = String::new();
+
+ for (i, artist) in artists.iter().enumerate() {
+ if i == 0 {
+ artist_list.push_str(&artist.name);
+ } else if i == artists.len() - 1 {
+ artist_list.push_str(&format!(" & {}", artist.name));
+ } else {
+ artist_list.push_str(&format!(", {}", artist.name));
+ }
+ }
+
+ artist_list
+ }
}
/// Model for an album
@@ -433,6 +455,32 @@ impl Song {
Ok(my_artists)
}
+
+ /// Get the album for this song from the database
+ ///
+ /// # Arguments
+ ///
+ /// * `conn` - A mutable reference to a database connection
+ ///
+ /// # Returns
+ ///
+ /// * `Result