From 5cd1f4f73678c50af95cab146e460e342a787fac Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Wed, 2 Jul 2025 10:58:02 +0200 Subject: [PATCH] feat(gpodder): add signup link admin methods --- gpodder/src/models.rs | 6 ++++ gpodder/src/repository/admin.rs | 22 ++++++++++++- gpodder/src/repository/mod.rs | 2 +- gpodder/src/store.rs | 29 +++++++++++++++++ gpodder_sqlite/src/repository/auth.rs | 45 +++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 2 deletions(-) diff --git a/gpodder/src/models.rs b/gpodder/src/models.rs index c37f97f..9240af8 100644 --- a/gpodder/src/models.rs +++ b/gpodder/src/models.rs @@ -77,3 +77,9 @@ pub struct Page { pub struct UserFilter { pub username: Option, } + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SignupLink { + pub id: i64, + pub time_created: DateTime, +} diff --git a/gpodder/src/repository/admin.rs b/gpodder/src/repository/admin.rs index 2123026..495d982 100644 --- a/gpodder/src/repository/admin.rs +++ b/gpodder/src/repository/admin.rs @@ -1,9 +1,12 @@ +use chrono::Utc; +use rand::Rng; + use crate::{AuthErr, Page, models}; /// Admin view of the repository, providing methods only allowed by admins pub struct AdminRepository<'a> { pub(crate) store: &'a (dyn super::GpodderStore + Send + Sync), - pub(crate) user: &'a models::User, + pub(crate) _user: &'a models::User, } impl<'a> AdminRepository<'a> { @@ -14,4 +17,21 @@ impl<'a> AdminRepository<'a> { ) -> Result, AuthErr> { self.store.paginated_users(page, filter) } + + /// Generate a new unique signup link ID + pub fn generate_signup_link(&self) -> Result { + let link = models::SignupLink { + id: rand::thread_rng().r#gen(), + time_created: Utc::now(), + }; + + self.store.insert_signup_link(&link)?; + + Ok(link) + } + + /// Remove the signup link with the given ID, if it exists + pub fn remove_signup_link(&self, id: i64) -> Result { + self.store.remove_signup_link(id) + } } diff --git a/gpodder/src/repository/mod.rs b/gpodder/src/repository/mod.rs index ef87237..f850a05 100644 --- a/gpodder/src/repository/mod.rs +++ b/gpodder/src/repository/mod.rs @@ -47,7 +47,7 @@ impl GpodderRepository { if user.admin { Ok(admin::AdminRepository { store: self.store.as_ref(), - user, + _user: user, }) } else { Err(AuthErr::NotAnAdmin) diff --git a/gpodder/src/store.rs b/gpodder/src/store.rs index 7f32374..598fb41 100644 --- a/gpodder/src/store.rs +++ b/gpodder/src/store.rs @@ -67,6 +67,35 @@ pub trait GpodderAuthStore { /// Return the given page of users, ordered by username fn paginated_users(&self, page: Page, filter: &UserFilter) -> Result, AuthErr>; + + /// Insert the signup link into the database. + /// + /// # Errors + /// + /// If a database failure occurs + fn insert_signup_link(&self, link: &SignupLink) -> Result<(), AuthErr>; + + /// Get the signup link associated with the given ID + /// + /// # Returns + /// + /// Some(link) if the ID corresponds to an existing signup link; None otherwise + /// + /// # Errors + /// + /// If a database failure occurs + fn get_signup_link(&self, id: i64) -> Result, AuthErr>; + + /// Remove the signup link with the given ID. + /// + /// # Returns + /// + /// True if the ID existed in the database; false otherwise + /// + /// # Errors + /// + /// If a database failure occurs + fn remove_signup_link(&self, id: i64) -> Result; } pub trait GpodderDeviceStore { diff --git a/gpodder_sqlite/src/repository/auth.rs b/gpodder_sqlite/src/repository/auth.rs index 38af31e..1988a4c 100644 --- a/gpodder_sqlite/src/repository/auth.rs +++ b/gpodder_sqlite/src/repository/auth.rs @@ -7,6 +7,7 @@ use crate::{ DbError, models::{ session::Session, + signup_link::SignupLink, user::{NewUser, User}, }, schema::*, @@ -23,6 +24,15 @@ impl From for gpodder::User { } } +impl From for gpodder::SignupLink { + fn from(value: SignupLink) -> Self { + Self { + id: value.id, + time_created: DateTime::from_timestamp(value.created_at, 0).unwrap(), + } + } +} + impl gpodder::GpodderAuthStore for SqliteRepository { fn get_user(&self, username: &str) -> Result, AuthErr> { Ok(users::table @@ -170,4 +180,39 @@ impl gpodder::GpodderAuthStore for SqliteRepository { })() .map_err(AuthErr::from) } + + fn get_signup_link(&self, id: i64) -> Result, AuthErr> { + match signup_links::table + .find(id) + .select(SignupLink::as_select()) + .first(&mut self.pool.get().map_err(DbError::from)?) + .optional() + { + Ok(Some(link)) => Ok(Some(gpodder::SignupLink::from(link))), + Ok(None) => Ok(None), + Err(err) => Err(DbError::from(err).into()), + } + } + + fn insert_signup_link(&self, link: &gpodder::SignupLink) -> Result<(), AuthErr> { + diesel::insert_into(signup_links::table) + .values(SignupLink { + id: link.id, + created_at: link.time_created.timestamp(), + }) + .execute(&mut self.pool.get().map_err(DbError::from)?) + .map_err(DbError::from)?; + + Ok(()) + } + + fn remove_signup_link(&self, id: i64) -> Result { + match diesel::delete(signup_links::table.filter(signup_links::id.eq(id))) + .execute(&mut self.pool.get().map_err(DbError::from)?) + { + Ok(0) => Ok(false), + Ok(_) => Ok(true), + Err(err) => Err(DbError::from(err).into()), + } + } }