Compare commits

...

2 Commits

11 changed files with 137 additions and 3 deletions

View File

@ -77,3 +77,9 @@ pub struct Page {
pub struct UserFilter { pub struct UserFilter {
pub username: Option<String>, pub username: Option<String>,
} }
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SignupLink {
pub id: i64,
pub time_created: DateTime<Utc>,
}

View File

@ -1,9 +1,12 @@
use chrono::Utc;
use rand::Rng;
use crate::{AuthErr, Page, models}; use crate::{AuthErr, Page, models};
/// Admin view of the repository, providing methods only allowed by admins /// Admin view of the repository, providing methods only allowed by admins
pub struct AdminRepository<'a> { pub struct AdminRepository<'a> {
pub(crate) store: &'a (dyn super::GpodderStore + Send + Sync), 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> { impl<'a> AdminRepository<'a> {
@ -14,4 +17,21 @@ impl<'a> AdminRepository<'a> {
) -> Result<Vec<models::User>, AuthErr> { ) -> Result<Vec<models::User>, AuthErr> {
self.store.paginated_users(page, filter) self.store.paginated_users(page, filter)
} }
/// Generate a new unique signup link ID
pub fn generate_signup_link(&self) -> Result<models::SignupLink, AuthErr> {
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<bool, AuthErr> {
self.store.remove_signup_link(id)
}
} }

View File

@ -47,7 +47,7 @@ impl GpodderRepository {
if user.admin { if user.admin {
Ok(admin::AdminRepository { Ok(admin::AdminRepository {
store: self.store.as_ref(), store: self.store.as_ref(),
user, _user: user,
}) })
} else { } else {
Err(AuthErr::NotAnAdmin) Err(AuthErr::NotAnAdmin)

View File

@ -67,6 +67,35 @@ pub trait GpodderAuthStore {
/// Return the given page of users, ordered by username /// Return the given page of users, ordered by username
fn paginated_users(&self, page: Page, filter: &UserFilter) -> Result<Vec<User>, AuthErr>; fn paginated_users(&self, page: Page, filter: &UserFilter) -> Result<Vec<User>, 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<Option<SignupLink>, 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<bool, AuthErr>;
} }
pub trait GpodderDeviceStore { pub trait GpodderDeviceStore {

View File

@ -15,8 +15,15 @@ tracing = { workspace = true }
chrono = { workspace = true, features = ["serde"] } chrono = { workspace = true, features = ["serde"] }
libsqlite3-sys = { version = "0.31.0", features = ["bundled"] } libsqlite3-sys = { version = "0.31.0", features = ["bundled"] }
diesel = { version = "2.2.7", features = ["r2d2", "sqlite", "returning_clauses_for_sqlite_3_35"] }
diesel_migrations = { version = "2.2.0", features = ["sqlite"] } diesel_migrations = { version = "2.2.0", features = ["sqlite"] }
[dependencies.diesel]
version = "2.2.7"
features = [
"r2d2",
"sqlite",
"returning_clauses_for_sqlite_3_35",
]
[dev-dependencies] [dev-dependencies]
criterion = "0.5.1" criterion = "0.5.1"

View File

@ -0,0 +1,2 @@
-- This file should undo anything in `up.sql`
drop table signup_links;

View File

@ -0,0 +1,5 @@
-- Your SQL goes here
create table signup_links (
id bigint primary key not null,
created_at bigint not null
);

View File

@ -2,5 +2,6 @@ pub mod device;
pub mod device_subscription; pub mod device_subscription;
pub mod episode_action; pub mod episode_action;
pub mod session; pub mod session;
pub mod signup_link;
pub mod sync_group; pub mod sync_group;
pub mod user; pub mod user;

View File

@ -0,0 +1,11 @@
use diesel::prelude::*;
use crate::schema::*;
#[derive(Clone, Queryable, Selectable, Insertable)]
#[diesel(table_name = signup_links)]
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
pub struct SignupLink {
pub id: i64,
pub created_at: i64,
}

View File

@ -7,6 +7,7 @@ use crate::{
DbError, DbError,
models::{ models::{
session::Session, session::Session,
signup_link::SignupLink,
user::{NewUser, User}, user::{NewUser, User},
}, },
schema::*, schema::*,
@ -23,6 +24,15 @@ impl From<User> for gpodder::User {
} }
} }
impl From<SignupLink> 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 { impl gpodder::GpodderAuthStore for SqliteRepository {
fn get_user(&self, username: &str) -> Result<Option<gpodder::models::User>, AuthErr> { fn get_user(&self, username: &str) -> Result<Option<gpodder::models::User>, AuthErr> {
Ok(users::table Ok(users::table
@ -170,4 +180,39 @@ impl gpodder::GpodderAuthStore for SqliteRepository {
})() })()
.map_err(AuthErr::from) .map_err(AuthErr::from)
} }
fn get_signup_link(&self, id: i64) -> Result<Option<gpodder::SignupLink>, 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<bool, AuthErr> {
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()),
}
}
} }

View File

@ -47,6 +47,13 @@ diesel::table! {
} }
} }
diesel::table! {
signup_links (id) {
id -> BigInt,
created_at -> BigInt,
}
}
diesel::table! { diesel::table! {
sync_groups (id) { sync_groups (id) {
id -> BigInt, id -> BigInt,
@ -74,6 +81,7 @@ diesel::allow_tables_to_appear_in_same_query!(
devices, devices,
episode_actions, episode_actions,
sessions, sessions,
signup_links,
sync_groups, sync_groups,
users, users,
); );