Merge pull request 'single-user-edit-page' (#986) from single-user-edit-page into staging
Reviewed-on: #986
This commit is contained in:
commit
7083d27644
@ -4,4 +4,8 @@
|
|||||||
|
|
||||||
.h2 {
|
.h2 {
|
||||||
@apply font-bold uppercase tracking-wide text-center rounded-t-md text-primary-950 dark:text-white bg-gray-200 dark:bg-primary-950 bg-opacity-80 text-lg px-3 py-3;
|
@apply font-bold uppercase tracking-wide text-center rounded-t-md text-primary-950 dark:text-white bg-gray-200 dark:bg-primary-950 bg-opacity-80 text-lg px-3 py-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h3 {
|
||||||
|
@apply text-center text-xl uppercase tracking-wide font-bold text-primary-900 dark:text-white;
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
use super::user::User;
|
use super::{role::Role, user::User};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
||||||
@ -21,6 +21,7 @@ pub struct ActivityBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ActivityBuilder {
|
impl ActivityBuilder {
|
||||||
|
#[must_use]
|
||||||
pub fn new(text: &str) -> Self {
|
pub fn new(text: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
text: text.into(),
|
text: text.into(),
|
||||||
@ -29,6 +30,7 @@ impl ActivityBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
pub fn relevant_for_user(self, user: &User) -> Self {
|
pub fn relevant_for_user(self, user: &User) -> Self {
|
||||||
Self {
|
Self {
|
||||||
relevant_for: format!("{}user-{};", self.relevant_for, user.id),
|
relevant_for: format!("{}user-{};", self.relevant_for, user.id),
|
||||||
@ -36,6 +38,14 @@ impl ActivityBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn relevant_for_role(self, role: &Role) -> Self {
|
||||||
|
Self {
|
||||||
|
relevant_for: format!("{}role-{};", self.relevant_for, role.id),
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn save(self, db: &SqlitePool) {
|
pub async fn save(self, db: &SqlitePool) {
|
||||||
Activity::create(db, &self.text, &self.relevant_for, self.keep_until).await;
|
Activity::create(db, &self.text, &self.relevant_for, self.keep_until).await;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::{cmp::Ordering, fmt::Display, ops::DerefMut};
|
use std::{cmp::Ordering, fmt::Display, ops::DerefMut};
|
||||||
|
|
||||||
|
use super::{activity::ActivityBuilder, user::AdminUser};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
||||||
|
|
||||||
@ -134,6 +135,30 @@ WHERE name like ?
|
|||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update(
|
||||||
|
&self,
|
||||||
|
db: &SqlitePool,
|
||||||
|
updated_by: &AdminUser,
|
||||||
|
formatted_name: &str,
|
||||||
|
desc: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE role SET formatted_name=?, desc=? WHERE id=?",
|
||||||
|
formatted_name,
|
||||||
|
desc,
|
||||||
|
self.id
|
||||||
|
)
|
||||||
|
.execute(db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
ActivityBuilder::new(&format!(
|
||||||
|
"{updated_by} hat Rolle {self} von {self:#?} auf FORMATTED_NAME={formatted_name}, DESC={desc} aktualisiert."
|
||||||
|
)).relevant_for_role(self).save(db).await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn names_from_role(&self, db: &SqlitePool) -> Vec<String> {
|
pub async fn names_from_role(&self, db: &SqlitePool) -> Vec<String> {
|
||||||
let query = format!(
|
let query = format!(
|
||||||
"SELECT u.name
|
"SELECT u.name
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use super::{regular::ClubMember, ManageUserUser, User};
|
use super::{ManageUserUser, User, regular::ClubMember};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
NonEmptyString,
|
||||||
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
|
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
|
||||||
special_user, NonEmptyString,
|
special_user,
|
||||||
};
|
};
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use rocket::{async_trait, fs::TempFile};
|
use rocket::{async_trait, fs::TempFile};
|
||||||
|
@ -1,20 +1,21 @@
|
|||||||
use std::{fmt::Display, ops::DerefMut};
|
use std::{fmt::Display, ops::DerefMut};
|
||||||
|
|
||||||
use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
|
use argon2::{Argon2, PasswordHasher, password_hash::SaltString};
|
||||||
use chrono::{Datelike, Local, NaiveDate};
|
use chrono::{Datelike, Local, NaiveDate};
|
||||||
use log::info;
|
use log::info;
|
||||||
use rocket::async_trait;
|
use rocket::async_trait;
|
||||||
use rocket::{
|
use rocket::{
|
||||||
|
Request,
|
||||||
http::{Cookie, Status},
|
http::{Cookie, Status},
|
||||||
request::{FromRequest, Outcome},
|
request::{FromRequest, Outcome},
|
||||||
time::{Duration, OffsetDateTime},
|
time::{Duration, OffsetDateTime},
|
||||||
Request,
|
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
||||||
|
|
||||||
use super::activity::ActivityBuilder;
|
use super::activity::ActivityBuilder;
|
||||||
use super::{
|
use super::{
|
||||||
|
Day,
|
||||||
log::Log,
|
log::Log,
|
||||||
logbook::Logbook,
|
logbook::Logbook,
|
||||||
mail::Mail,
|
mail::Mail,
|
||||||
@ -23,7 +24,6 @@ use super::{
|
|||||||
role::Role,
|
role::Role,
|
||||||
stat::Stat,
|
stat::Stat,
|
||||||
tripdetails::TripDetails,
|
tripdetails::TripDetails,
|
||||||
Day,
|
|
||||||
};
|
};
|
||||||
use crate::AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD;
|
use crate::AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD;
|
||||||
use scheckbuch::ScheckbuchUser;
|
use scheckbuch::ScheckbuchUser;
|
||||||
@ -512,7 +512,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
|
|||||||
.save(db)
|
.save(db)
|
||||||
.await;
|
.await;
|
||||||
return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has
|
return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has
|
||||||
//been deleted
|
//been deleted
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(user_pw) = user.pw.as_ref() {
|
if let Some(user_pw) = user.pw.as_ref() {
|
||||||
@ -583,12 +583,12 @@ ASKÖ Ruderverein Donau Linz", self.name),
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete(&self, db: &SqlitePool) {
|
pub async fn delete(&self, db: &SqlitePool, deleted_by: &ManageUserUser) {
|
||||||
sqlx::query!("UPDATE user SET deleted=1 WHERE id=?", self.id)
|
sqlx::query!("UPDATE user SET deleted=1 WHERE id=?", self.id)
|
||||||
.execute(db)
|
.execute(db)
|
||||||
.await
|
.await
|
||||||
.unwrap(); //Okay, because we can only create a User of a valid id
|
.unwrap(); //Okay, because we can only create a User of a valid id
|
||||||
ActivityBuilder::new(&format!("User {self} wurde gelöscht."))
|
ActivityBuilder::new(&format!("User {self} wurde von {deleted_by} gelöscht."))
|
||||||
.relevant_for_user(self)
|
.relevant_for_user(self)
|
||||||
.save(db)
|
.save(db)
|
||||||
.await;
|
.await;
|
||||||
@ -622,9 +622,9 @@ ASKÖ Ruderverein Donau Linz", self.name),
|
|||||||
pub(crate) async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 {
|
pub(crate) async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 {
|
||||||
if self.allowed_to_steer(db).await {
|
if self.allowed_to_steer(db).await {
|
||||||
let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); //Ok,
|
let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); //Ok,
|
||||||
//december
|
//december
|
||||||
//has 31
|
//has 31
|
||||||
//days
|
//days
|
||||||
let days_left_in_year = end_of_year
|
let days_left_in_year = end_of_year
|
||||||
.signed_duration_since(Local::now().date_naive())
|
.signed_duration_since(Local::now().date_naive())
|
||||||
.num_days()
|
.num_days()
|
||||||
@ -633,9 +633,9 @@ ASKÖ Ruderverein Donau Linz", self.name),
|
|||||||
if days_left_in_year <= 31 {
|
if days_left_in_year <= 31 {
|
||||||
let end_of_next_year =
|
let end_of_next_year =
|
||||||
NaiveDate::from_ymd_opt(Local::now().year() + 1, 12, 31).unwrap(); //Ok,
|
NaiveDate::from_ymd_opt(Local::now().year() + 1, 12, 31).unwrap(); //Ok,
|
||||||
//december
|
//december
|
||||||
//has 31
|
//has 31
|
||||||
//days
|
//days
|
||||||
end_of_next_year
|
end_of_next_year
|
||||||
.signed_duration_since(Local::now().date_naive())
|
.signed_duration_since(Local::now().date_naive())
|
||||||
.num_days()
|
.num_days()
|
||||||
@ -867,8 +867,8 @@ special_user!(SteeringUser, +"cox", +"Bootsführer");
|
|||||||
special_user!(AdminUser, +"admin");
|
special_user!(AdminUser, +"admin");
|
||||||
special_user!(AllowedForPlannedTripsUser, +"Donau Linz", +"scheckbuch", +"Förderndes Mitglied");
|
special_user!(AllowedForPlannedTripsUser, +"Donau Linz", +"scheckbuch", +"Förderndes Mitglied");
|
||||||
special_user!(DonauLinzUser, +"Donau Linz", -"Unterstützend", -"Förderndes Mitglied"); // TODO:
|
special_user!(DonauLinzUser, +"Donau Linz", -"Unterstützend", -"Förderndes Mitglied"); // TODO:
|
||||||
// remove ->
|
// remove ->
|
||||||
// RegularUser
|
// RegularUser
|
||||||
special_user!(SchnupperBetreuerUser, +"schnupper-betreuer");
|
special_user!(SchnupperBetreuerUser, +"schnupper-betreuer");
|
||||||
special_user!(VorstandUser, +"admin", +"Vorstand");
|
special_user!(VorstandUser, +"admin", +"Vorstand");
|
||||||
special_user!(EventUser, +"manage_events");
|
special_user!(EventUser, +"manage_events");
|
||||||
@ -982,17 +982,21 @@ mod test {
|
|||||||
#[sqlx::test]
|
#[sqlx::test]
|
||||||
fn wrong_pw() {
|
fn wrong_pw() {
|
||||||
let pool = testdb!();
|
let pool = testdb!();
|
||||||
assert!(User::login(&pool, "admin".into(), "admi".into())
|
assert!(
|
||||||
.await
|
User::login(&pool, "admin".into(), "admi".into())
|
||||||
.is_err());
|
.await
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[sqlx::test]
|
#[sqlx::test]
|
||||||
fn wrong_username() {
|
fn wrong_username() {
|
||||||
let pool = testdb!();
|
let pool = testdb!();
|
||||||
assert!(User::login(&pool, "admi".into(), "admin".into())
|
assert!(
|
||||||
.await
|
User::login(&pool, "admi".into(), "admin".into())
|
||||||
.is_err());
|
.await
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[sqlx::test]
|
#[sqlx::test]
|
||||||
@ -1011,9 +1015,11 @@ mod test {
|
|||||||
let pool = testdb!();
|
let pool = testdb!();
|
||||||
let user = User::find_by_id(&pool, 1).await.unwrap();
|
let user = User::find_by_id(&pool, 1).await.unwrap();
|
||||||
|
|
||||||
assert!(User::login(&pool, "admin".into(), "abc".into())
|
assert!(
|
||||||
.await
|
User::login(&pool, "admin".into(), "abc".into())
|
||||||
.is_err());
|
.await
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
|
|
||||||
user.update_pw(&pool, "abc".into()).await;
|
user.update_pw(&pool, "abc".into()).await;
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use super::{ManageUserUser, User};
|
use super::{ManageUserUser, User};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
NonEmptyString,
|
||||||
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
|
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
|
||||||
special_user, NonEmptyString,
|
special_user,
|
||||||
};
|
};
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use rocket::{async_trait, fs::TempFile, tokio::io::AsyncReadExt};
|
use rocket::{async_trait, fs::TempFile, tokio::io::AsyncReadExt};
|
||||||
|
@ -2,12 +2,13 @@ use super::foerdernd::FoerderndUser;
|
|||||||
use super::regular::RegularUser;
|
use super::regular::RegularUser;
|
||||||
use super::unterstuetzend::UnterstuetzendUser;
|
use super::unterstuetzend::UnterstuetzendUser;
|
||||||
use super::{ManageUserUser, User};
|
use super::{ManageUserUser, User};
|
||||||
|
use crate::NonEmptyString;
|
||||||
use crate::model::activity::ActivityBuilder;
|
use crate::model::activity::ActivityBuilder;
|
||||||
use crate::model::role::Role;
|
use crate::model::role::Role;
|
||||||
use crate::NonEmptyString;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
SCHECKBUCH,
|
||||||
model::{mail::Mail, notification::Notification},
|
model::{mail::Mail, notification::Notification},
|
||||||
special_user, SCHECKBUCH,
|
special_user,
|
||||||
};
|
};
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use rocket::async_trait;
|
use rocket::async_trait;
|
||||||
|
@ -4,9 +4,9 @@ use super::scheckbuch::ScheckbuchUser;
|
|||||||
use super::schnupperinterest::SchnupperInterestUser;
|
use super::schnupperinterest::SchnupperInterestUser;
|
||||||
use super::unterstuetzend::UnterstuetzendUser;
|
use super::unterstuetzend::UnterstuetzendUser;
|
||||||
use super::{ManageUserUser, User};
|
use super::{ManageUserUser, User};
|
||||||
|
use crate::NonEmptyString;
|
||||||
use crate::model::activity::ActivityBuilder;
|
use crate::model::activity::ActivityBuilder;
|
||||||
use crate::model::role::Role;
|
use crate::model::role::Role;
|
||||||
use crate::NonEmptyString;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
model::{mail::Mail, notification::Notification},
|
model::{mail::Mail, notification::Notification},
|
||||||
special_user,
|
special_user,
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use super::scheckbuch::ScheckbuchUser;
|
use super::scheckbuch::ScheckbuchUser;
|
||||||
use super::schnupperant::SchnupperantUser;
|
use super::schnupperant::SchnupperantUser;
|
||||||
use super::{ManageUserUser, User};
|
use super::{ManageUserUser, User};
|
||||||
|
use crate::NonEmptyString;
|
||||||
use crate::model::activity::ActivityBuilder;
|
use crate::model::activity::ActivityBuilder;
|
||||||
use crate::model::role::Role;
|
use crate::model::role::Role;
|
||||||
use crate::NonEmptyString;
|
|
||||||
use crate::{model::notification::Notification, special_user};
|
use crate::{model::notification::Notification, special_user};
|
||||||
use rocket::async_trait;
|
use rocket::async_trait;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use super::{regular::ClubMember, ManageUserUser, User};
|
use super::{ManageUserUser, User, regular::ClubMember};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
NonEmptyString,
|
||||||
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
|
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
|
||||||
special_user, NonEmptyString,
|
special_user,
|
||||||
};
|
};
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use rocket::{async_trait, fs::TempFile};
|
use rocket::{async_trait, fs::TempFile};
|
||||||
|
@ -12,6 +12,7 @@ pub mod boat;
|
|||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod mail;
|
pub mod mail;
|
||||||
pub mod notification;
|
pub mod notification;
|
||||||
|
pub mod role;
|
||||||
pub mod schnupper;
|
pub mod schnupper;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
||||||
@ -81,6 +82,7 @@ pub fn routes() -> Vec<Route> {
|
|||||||
ret.append(&mut notification::routes());
|
ret.append(&mut notification::routes());
|
||||||
ret.append(&mut mail::routes());
|
ret.append(&mut mail::routes());
|
||||||
ret.append(&mut event::routes());
|
ret.append(&mut event::routes());
|
||||||
|
ret.append(&mut role::routes());
|
||||||
ret.append(&mut routes![rss, show_rss, show_list, list]);
|
ret.append(&mut routes![rss, show_rss, show_list, list]);
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
64
src/tera/admin/role.rs
Normal file
64
src/tera/admin/role.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
use crate::model::{
|
||||||
|
role::Role,
|
||||||
|
user::{AdminUser, UserWithDetails, VorstandUser},
|
||||||
|
};
|
||||||
|
use rocket::{
|
||||||
|
form::Form,
|
||||||
|
get, post,
|
||||||
|
request::FlashMessage,
|
||||||
|
response::{Flash, Redirect},
|
||||||
|
routes, FromForm, Route, State,
|
||||||
|
};
|
||||||
|
use rocket_dyn_templates::{tera::Context, Template};
|
||||||
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
|
#[get("/role")]
|
||||||
|
async fn index(
|
||||||
|
db: &State<SqlitePool>,
|
||||||
|
admin: VorstandUser,
|
||||||
|
flash: Option<FlashMessage<'_>>,
|
||||||
|
) -> Template {
|
||||||
|
let roles = Role::all(db).await;
|
||||||
|
|
||||||
|
let mut context = Context::new();
|
||||||
|
if let Some(msg) = flash {
|
||||||
|
context.insert("flash", &msg.into_inner());
|
||||||
|
}
|
||||||
|
context.insert("roles", &roles);
|
||||||
|
context.insert(
|
||||||
|
"loggedin_user",
|
||||||
|
&UserWithDetails::from_user(admin.user, db).await,
|
||||||
|
);
|
||||||
|
|
||||||
|
Template::render("admin/role", context.into_json())
|
||||||
|
}
|
||||||
|
#[derive(FromForm)]
|
||||||
|
pub struct RoleToUpdate<'r> {
|
||||||
|
pub formatted_name: &'r str,
|
||||||
|
pub desc: &'r str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/role/<role_id>", data = "<data>")]
|
||||||
|
async fn update(
|
||||||
|
db: &State<SqlitePool>,
|
||||||
|
data: Form<RoleToUpdate<'_>>,
|
||||||
|
role_id: i32,
|
||||||
|
admin: AdminUser,
|
||||||
|
) -> Flash<Redirect> {
|
||||||
|
let role = Role::find_by_id(db, role_id).await;
|
||||||
|
let Some(role) = role else {
|
||||||
|
return Flash::error(Redirect::to("/admin/role"), "Role does not exist!");
|
||||||
|
};
|
||||||
|
|
||||||
|
match role
|
||||||
|
.update(db, &admin, &data.formatted_name, &data.desc)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => Flash::success(Redirect::to("/admin/role"), "Rolle bearbeitet"),
|
||||||
|
Err(e) => Flash::error(Redirect::to("/admin/role"), e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routes() -> Vec<Route> {
|
||||||
|
routes![index, update]
|
||||||
|
}
|
@ -7,11 +7,11 @@ use crate::{
|
|||||||
mail::valid_mails,
|
mail::valid_mails,
|
||||||
role::Role,
|
role::Role,
|
||||||
user::{
|
user::{
|
||||||
|
AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails,
|
||||||
|
UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser,
|
||||||
clubmember::ClubMemberUser, foerdernd::FoerderndUser, member::Member,
|
clubmember::ClubMemberUser, foerdernd::FoerderndUser, member::Member,
|
||||||
regular::RegularUser, scheckbuch::ScheckbuchUser, schnupperant::SchnupperantUser,
|
regular::RegularUser, scheckbuch::ScheckbuchUser, schnupperant::SchnupperantUser,
|
||||||
schnupperinterest::SchnupperInterestUser, unterstuetzend::UnterstuetzendUser,
|
schnupperinterest::SchnupperInterestUser, unterstuetzend::UnterstuetzendUser,
|
||||||
AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails,
|
|
||||||
UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tera::Config,
|
tera::Config,
|
||||||
@ -19,6 +19,7 @@ use crate::{
|
|||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use rocket::{
|
use rocket::{
|
||||||
|
FromForm, Request, Route, State,
|
||||||
form::Form,
|
form::Form,
|
||||||
fs::TempFile,
|
fs::TempFile,
|
||||||
get,
|
get,
|
||||||
@ -26,9 +27,9 @@ use rocket::{
|
|||||||
post,
|
post,
|
||||||
request::{FlashMessage, FromRequest, Outcome},
|
request::{FlashMessage, FromRequest, Outcome},
|
||||||
response::{Flash, Redirect},
|
response::{Flash, Redirect},
|
||||||
routes, FromForm, Request, Route, State,
|
routes,
|
||||||
};
|
};
|
||||||
use rocket_dyn_templates::{tera::Context, Template};
|
use rocket_dyn_templates::{Template, tera::Context};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
// Custom request guard to extract the Referer header
|
// Custom request guard to extract the Referer header
|
||||||
@ -286,7 +287,7 @@ async fn delete(db: &State<SqlitePool>, admin: ManageUserUser, user: i32) -> Fla
|
|||||||
Log::create(db, format!("{} deleted user: {user:?}", admin.user.name)).await;
|
Log::create(db, format!("{} deleted user: {user:?}", admin.user.name)).await;
|
||||||
match user {
|
match user {
|
||||||
Some(user) => {
|
Some(user) => {
|
||||||
user.delete(db).await;
|
user.delete(db, &admin).await;
|
||||||
Flash::success(
|
Flash::success(
|
||||||
Redirect::to("/admin/user"),
|
Redirect::to("/admin/user"),
|
||||||
format!("Benutzer {} gelöscht", user.name),
|
format!("Benutzer {} gelöscht", user.name),
|
||||||
|
37
templates/admin/role.html.tera
Normal file
37
templates/admin/role.html.tera
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{% import "includes/macros" as macros %}
|
||||||
|
{% import "includes/forms/boat" as boat %}
|
||||||
|
{% extends "base" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="max-w-screen-lg w-full dark:text-white">
|
||||||
|
<h1 class="h1">Rolle</h1>
|
||||||
|
<div class="grid ">
|
||||||
|
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"
|
||||||
|
role="alert">
|
||||||
|
<h2 class="h2">Rolle</h2>
|
||||||
|
{% for role in roles %}
|
||||||
|
<div data-filterable="true"
|
||||||
|
data-filter="{{ role.name }}"
|
||||||
|
class="w-full border-t">
|
||||||
|
<form action="/admin/role/{{ role.id }}"
|
||||||
|
data-filterable="true"
|
||||||
|
method="post"
|
||||||
|
class="bg-white dark:bg-primary-900 p-4 w-full">
|
||||||
|
<div class="w-full">
|
||||||
|
<input type="hidden" name="id" value="{{ role.id }}" />
|
||||||
|
<div class="font-bold mb-1 text-black dark:text-white">
|
||||||
|
{{ role.name }}
|
||||||
|
<br />
|
||||||
|
</div>
|
||||||
|
<div class="grid md:grid-cols-3 gap-3">
|
||||||
|
{{ macros::input(label='Formatierter Name', name='formatted_name', type='text', value=role.formatted_name) }}
|
||||||
|
{{ macros::input(label='Beschreibung', name='desc', type='text', value=role.desc) }}
|
||||||
|
<input value="Ändern" type="submit" class="w-28 btn btn-primary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
@ -8,52 +8,121 @@
|
|||||||
<summary class="px-3 cursor-pointer text-md font-bold text-primary-950 dark:text-white">
|
<summary class="px-3 cursor-pointer text-md font-bold text-primary-950 dark:text-white">
|
||||||
Neue Person hinzufügen
|
Neue Person hinzufügen
|
||||||
</summary>
|
</summary>
|
||||||
<details class="mt-5 bg-gray-200 dark:bg-primary-600 p-3 rounded-md">
|
|
||||||
<summary class="px-3 cursor-pointer text-md font-bold text-primary-950 dark:text-white">Vereinsmitglied</summary>
|
<div class="grid sm:grid-cols-3 gap-3 mt-3">
|
||||||
<form action="/admin/user/new/clubmember"
|
<button type="button"
|
||||||
method="post"
|
onclick="document.getElementById('add-clubuser').showModal()"
|
||||||
enctype="multipart/form-data"
|
class="btn btn-primary">Vereinsmitglied</button>
|
||||||
class="grid gap-3">
|
<button type="button"
|
||||||
<div>
|
onclick="document.getElementById('add-scheckbuch').showModal()"
|
||||||
<label for="membertype" class="text-sm text-gray-600 dark:text-gray-100">Mitgliedstyp</label>
|
class="btn btn-dark">Scheckbuch</button>
|
||||||
<select name="membertype" id="membertype" class="input rounded-md ">
|
<button type="button"
|
||||||
<option selected="" value="regular">Reguläres Vereinsmitglied</option>
|
onclick="document.getElementById('add-schnupperkurs').showModal()"
|
||||||
<option value="unterstuetzend">Unterstützendes Vereinsmitglied</option>
|
class="btn btn-dark">Schnupperkurs</button>
|
||||||
<option value="foerdernd">Förderndes Vereinsmitglied</option>
|
|
||||||
</select>
|
|
||||||
|
</div>
|
||||||
|
<dialog id="add-clubuser"
|
||||||
|
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
|
||||||
|
onclick="document.getElementById('add-clubuser').close()">
|
||||||
|
<div onclick="event.stopPropagation();" class="p-3">
|
||||||
|
<button type="button"
|
||||||
|
onclick="document.getElementById('add-clubuser').close()"
|
||||||
|
title="Schließen"
|
||||||
|
class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3">
|
||||||
|
<svg class="inline h-5 w-5"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16">
|
||||||
|
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="mt-8">
|
||||||
|
<h2 class="h3 mb-3">Neues Vereinsmitglied</h2>
|
||||||
|
<form action="/admin/user/new/clubmember"
|
||||||
|
method="post"
|
||||||
|
enctype="multipart/form-data"
|
||||||
|
class="grid gap-3">
|
||||||
|
<div>
|
||||||
|
<label for="membertype" class="text-sm text-gray-600 dark:text-gray-100">Mitgliedstyp</label>
|
||||||
|
<select name="membertype" id="membertype" class="input rounded-md ">
|
||||||
|
<option selected="" value="regular">Reguläres Vereinsmitglied</option>
|
||||||
|
<option value="unterstuetzend">Unterstützendes Vereinsmitglied</option>
|
||||||
|
<option value="foerdernd">Förderndes Vereinsmitglied</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{{ macros::input(label='Name', name='name', type="text", required=true) }}
|
||||||
|
{{ macros::input(label='Mailadresse', name='mail', type="email", required=true, placeholder='user@mail.at') }}
|
||||||
|
{{ macros::select(label="Finanzielles", data=financial, name='financial_id', display=['name'], default="Keine Ermäßigung") }}
|
||||||
|
{{ macros::input(label='Mitglied seit', name='member_since', type="date", value=now() | date(), required=true) }}
|
||||||
|
{{ macros::input(label='Geburtsdatum', name='birthdate', type="date", required=true) }}
|
||||||
|
{{ macros::input(label='Telefonnummer', name='phone', type="text", required=true) }}
|
||||||
|
{{ macros::input(label='Adresse', name='address', type="text", required=true) }}
|
||||||
|
{{ macros::input(label='Beitrittserklärung', name='membership_pdf', type="file", accept='application/pdf', required=true) }}
|
||||||
|
<input value="Neues Vereinsmitglied anlegen"
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-primary" />
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{{ macros::input(label='Name', name='name', type="text", required=true) }}
|
</div>
|
||||||
{{ macros::input(label='Mailadresse', name='mail', type="email", required=true) }}
|
</dialog>
|
||||||
{{ macros::select(label="Finanzielles", data=financial, name='financial_id', display=['name'], default="Keine Ermäßigung") }}
|
|
||||||
{{ macros::input(label='Mitglied seit', name='member_since', type="date", value=now() | date(), required=true) }}
|
<dialog id="add-scheckbuch"
|
||||||
{{ macros::input(label='Geburtsdatum', name='birthdate', type="date", required=true) }}
|
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
|
||||||
{{ macros::input(label='Telefonnummer', name='phone', type="text", required=true) }}
|
onclick="document.getElementById('add-scheckbuch').close()">
|
||||||
{{ macros::input(label='Adresse', name='address', type="text", required=true) }}
|
<div onclick="event.stopPropagation();" class="p-3">
|
||||||
{{ macros::input(label='Beitrittserklärung', name='membership_pdf', type="file", accept='application/pdf', required=true) }}
|
<button type="button"
|
||||||
<input value="Neues Vereinsmitglied anlegen"
|
onclick="document.getElementById('add-scheckbuch').close()"
|
||||||
type="submit"
|
title="Schließen"
|
||||||
class="btn btn-primary" />
|
class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3">
|
||||||
</form>
|
<svg class="inline h-5 w-5"
|
||||||
</details>
|
width="16"
|
||||||
<details class="mt-5 bg-gray-200 dark:bg-primary-600 p-3 rounded-md">
|
height="16"
|
||||||
<summary class="px-3 cursor-pointer text-md font-bold text-primary-950 dark:text-white">Scheckbuch</summary>
|
fill="currentColor"
|
||||||
<form action="/admin/user/new/scheckbuch"
|
viewBox="0 0 16 16">
|
||||||
method="post"
|
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
|
||||||
enctype="multipart/form-data"
|
</svg>
|
||||||
class="grid gap-3">
|
</button>
|
||||||
{{ macros::input(label='Name', name='name', type="text", required=true) }}
|
<div class="mt-8">
|
||||||
{{ macros::input(label='Mailadresse', name='mail', type="email", required=true) }}
|
<h2 class="h3 mb-3">Neues Scheckbuch</h2>
|
||||||
<input value="Neues Scheckbuch anlegen"
|
<form action="/admin/user/new/scheckbuch"
|
||||||
type="submit"
|
method="post"
|
||||||
class="btn btn-primary" />
|
enctype="multipart/form-data"
|
||||||
</form>
|
class="grid gap-3">
|
||||||
</details>
|
{{ macros::input(label='Name', name='name', type="text", required=true) }}
|
||||||
<details class="mt-5 bg-gray-200 dark:bg-primary-600 p-3 rounded-md">
|
{{ macros::input(label='Mailadresse', name='mail', type="email", required=true, placeholder='user@mail.at') }}
|
||||||
<summary class="px-3 cursor-pointer text-md font-bold text-primary-950 dark:text-white">Schnupperkurs</summary>
|
<input value="Neues Scheckbuch anlegen"
|
||||||
<form action="/admin/user/new/schnupper"
|
type="submit"
|
||||||
|
class="btn btn-primary" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<dialog id="add-schnupperkurs"
|
||||||
|
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
|
||||||
|
onclick="document.getElementById('add-schnupperkurs').close()">
|
||||||
|
<div onclick="event.stopPropagation();" class="p-3">
|
||||||
|
<button type="button"
|
||||||
|
onclick="document.getElementById('add-schnupperkurs').close()"
|
||||||
|
title="Schließen"
|
||||||
|
class="sidebar-close border-0 bg-primary-100 focus:bg-primary-50 text-black flex items-center justify-center transform rotate-45 absolute right-0 mr-3">
|
||||||
|
<svg class="inline h-5 w-5"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 16 16">
|
||||||
|
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="mt-8">
|
||||||
|
<form action="/admin/user/new/schnupper"
|
||||||
method="post"
|
method="post"
|
||||||
enctype="multipart/form-data"
|
enctype="multipart/form-data"
|
||||||
class="grid gap-3">
|
class="grid gap-3">
|
||||||
|
<h2 class="h3 mb-3">Neuer Schnupperant</h2>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="schnupper_type" class="text-sm text-gray-600 dark:text-gray-100">Typ</label>
|
<label for="schnupper_type" class="text-sm text-gray-600 dark:text-gray-100">Typ</label>
|
||||||
<select name="schnupper_type" id="schnupper_type" class="input rounded-md ">
|
<select name="schnupper_type" id="schnupper_type" class="input rounded-md ">
|
||||||
@ -62,11 +131,13 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{{ macros::input(label='Name', name='name', type="text", required=true) }}
|
{{ macros::input(label='Name', name='name', type="text", required=true) }}
|
||||||
{{ macros::input(label='Mailadresse', name='mail', type="email", required=true) }}
|
{{ macros::input(label='Mailadresse', name='mail', type="email", required=true, placeholder='user@mail.at') }}
|
||||||
{{ macros::select(label="Finanzielles", data=financial, name='financial_id', display=['name'], default="Keine Ermäßigung") }}
|
{{ macros::select(label="Finanzielles", data=financial, name='financial_id', display=['name'], default="Keine Ermäßigung") }}
|
||||||
<input value="Hinzufügen" type="submit" class="btn btn-primary" />
|
<input value="Hinzufügen" type="submit" class="btn btn-primary" />
|
||||||
</form>
|
</form>
|
||||||
</details>
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
</details>
|
</details>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- START filterBar -->
|
<!-- START filterBar -->
|
||||||
|
@ -132,7 +132,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<dialog id="change-member-type"
|
<dialog id="change-member-type"
|
||||||
class="max-w-screen-sm w-full dark:bg-primary-600 dark:text-white rounded-md"
|
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
|
||||||
onclick="document.getElementById('change-member-type').close()">
|
onclick="document.getElementById('change-member-type').close()">
|
||||||
<div onclick="event.stopPropagation();" class="p-3">
|
<div onclick="event.stopPropagation();" class="p-3">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@ -235,7 +235,7 @@
|
|||||||
class="btn btn-primary">Zu Vereinsmitglied umwandeln</button>
|
class="btn btn-primary">Zu Vereinsmitglied umwandeln</button>
|
||||||
</div>
|
</div>
|
||||||
<dialog id="call-for-action"
|
<dialog id="call-for-action"
|
||||||
class="max-w-screen-sm w-full dark:bg-primary-600 dark:text-white rounded-md"
|
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
|
||||||
onclick="document.getElementById('call-for-action').close()">
|
onclick="document.getElementById('call-for-action').close()">
|
||||||
<div onclick="event.stopPropagation();" class="p-3">
|
<div onclick="event.stopPropagation();" class="p-3">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@ -318,7 +318,7 @@
|
|||||||
class="btn btn-primary w-full">Rolle hinzufügen</button>
|
class="btn btn-primary w-full">Rolle hinzufügen</button>
|
||||||
</div>
|
</div>
|
||||||
<dialog id="role-modal"
|
<dialog id="role-modal"
|
||||||
class="max-w-screen-sm w-full dark:bg-primary-600 dark:text-white rounded-md"
|
class="max-w-screen-sm w-full dark:bg-primary-900 dark:text-white rounded-md"
|
||||||
onclick="document.getElementById('role-modal').close()">
|
onclick="document.getElementById('role-modal').close()">
|
||||||
<div onclick="event.stopPropagation();" class="p-3">
|
<div onclick="event.stopPropagation();" class="p-3">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
|
@ -156,7 +156,7 @@ function setChoiceByLabel(choicesInstance, label) {
|
|||||||
</header>
|
</header>
|
||||||
<div class="h-8"></div>
|
<div class="h-8"></div>
|
||||||
{% endmacro header %}
|
{% endmacro header %}
|
||||||
{% macro input(label, name, type, required=false, class='rounded-md', value='', min='', hide_label=false, id='', autofocus=false, wrapper_class='', pattern='', readonly=false, accept='') %}
|
{% macro input(label, name, type, required=false, class='rounded-md', value='', min='', hide_label=false, id='', autofocus=false, wrapper_class='', pattern='', readonly=false, accept='', placeholder='') %}
|
||||||
<div class="{{ wrapper_class }}">
|
<div class="{{ wrapper_class }}">
|
||||||
<label for="{{ name }}"
|
<label for="{{ name }}"
|
||||||
class="{% if hide_label %} sr-only {% else %} text-sm text-gray-600 dark:text-white {% endif %}">
|
class="{% if hide_label %} sr-only {% else %} text-sm text-gray-600 dark:text-white {% endif %}">
|
||||||
@ -169,7 +169,7 @@ function setChoiceByLabel(choicesInstance, label) {
|
|||||||
{% if required %}required{% endif %}
|
{% if required %}required{% endif %}
|
||||||
value="{{ value }}"
|
value="{{ value }}"
|
||||||
class="input {{ class }}"
|
class="input {{ class }}"
|
||||||
placeholder="{% if hide_label %}{{ label }}{% endif %}"
|
placeholder="{% if hide_label %}{{ label }}{% endif %}{% if placeholder %}{{ placeholder }}{% endif %}"
|
||||||
{% if min is defined %}min="{{ min }}"{% endif %}
|
{% if min is defined %}min="{{ min }}"{% endif %}
|
||||||
{% if autofocus %}autofocus{% endif %}
|
{% if autofocus %}autofocus{% endif %}
|
||||||
{% if accept %}accept="{{ accept }}"{% endif %}
|
{% if accept %}accept="{{ accept }}"{% endif %}
|
||||||
|
@ -431,6 +431,9 @@
|
|||||||
<li class="py-1">
|
<li class="py-1">
|
||||||
<a href="/admin/rss" class="block w-100 py-2 hover:text-primary-600">Logs</a>
|
<a href="/admin/rss" class="block w-100 py-2 hover:text-primary-600">Logs</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="py-1">
|
||||||
|
<a href="/admin/role" class="block w-100 py-2 hover:text-primary-600">Rollen</a>
|
||||||
|
</li>
|
||||||
<li class="py-1">
|
<li class="py-1">
|
||||||
<a href="/admin/list" class="block w-100 py-2 hover:text-primary-600">Fingerabdruck-Liste überprüfen</a>
|
<a href="/admin/list" class="block w-100 py-2 hover:text-primary-600">Fingerabdruck-Liste überprüfen</a>
|
||||||
</li>
|
</li>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user