feat: set functions for user

This commit is contained in:
Max Hohlfeld 2025-03-23 18:10:20 +01:00
parent c830704fa4
commit 736b69d2c3
6 changed files with 93 additions and 54 deletions

View File

@ -8,6 +8,7 @@ use crate::{
};
use actix_web::HttpResponse;
use rinja::Template;
use serde::Deserialize;
use sqlx::PgPool;
use zxcvbn::{zxcvbn, Score};
@ -26,9 +27,9 @@ pub mod post_edit;
pub mod post_login;
pub mod post_new;
pub mod post_register;
pub mod post_resend_registration;
pub mod post_reset;
pub mod post_toggle;
pub mod post_resend_registration;
#[derive(Template)]
#[template(path = "user/new_or_edit.html")]
@ -45,6 +46,21 @@ pub struct NewOrEditUserTemplate {
area_id: Option<i32>,
}
#[derive(Deserialize)]
#[cfg_attr(test, derive(serde::Serialize))]
struct NewOrEditUserForm {
email: String,
name: String,
role: u8,
#[serde(rename(deserialize = "is-posten"))]
is_posten: Option<bool>,
#[serde(rename(deserialize = "is-wachhabender"))]
is_wachhabender: Option<bool>,
#[serde(rename(deserialize = "is-fuehrungsassistent"))]
is_fuehrungsassistent: Option<bool>,
area: Option<i32>,
}
#[derive(Template)]
#[template(path = "user/change_password.html")]
struct ResetPasswordTemplate<'a> {

View File

@ -1,30 +1,19 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use garde::Validate;
use serde::Deserialize;
use sqlx::PgPool;
use crate::{
endpoints::IdPath,
models::{Area, Role, User, UserChangeset},
endpoints::{user::NewOrEditUserForm, IdPath},
models::{Area, Function, Role, User, UserChangeset},
utils::ApplicationError,
};
#[derive(Deserialize)]
#[cfg_attr(test, derive(serde::Serialize))]
pub struct EditUserForm {
email: String,
name: String,
role: u8,
function: u8,
area: Option<i32>,
}
#[actix_web::post("/users/edit/{id}")]
pub async fn post_edit(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
path: web::Path<IdPath>,
form: web::Form<EditUserForm>,
form: web::Form<NewOrEditUserForm>,
) -> Result<impl Responder, ApplicationError> {
if user.role != Role::AreaManager && user.role != Role::Admin {
return Err(ApplicationError::Unauthorized);
@ -47,11 +36,25 @@ pub async fn post_edit(
return Err(ApplicationError::Unauthorized);
}
let mut functions = Vec::with_capacity(3);
if form.is_posten.unwrap_or(false) {
functions.push(Function::Posten);
}
if form.is_wachhabender.unwrap_or(false) {
functions.push(Function::Wachhabender);
}
if form.is_fuehrungsassistent.unwrap_or(false) {
functions.push(Function::Fuehrungsassistent);
}
let changeset = UserChangeset {
name: form.name.clone(),
email: form.email.clone(),
role: form.role.try_into()?,
function: form.function.try_into()?,
functions,
area_id,
};
@ -69,8 +72,7 @@ pub async fn post_edit(
#[cfg(test)]
mod tests {
use super::*;
use crate::{models::*, utils::test_helper::*};
use crate::{endpoints::user::NewOrEditUserForm, models::*, utils::test_helper::*};
use brass_macros::db_test;
use fake::{
faker::{internet::en::SafeEmail, name::en::Name},
@ -94,11 +96,13 @@ mod tests {
let new_name: String = Name().fake();
let new_mail: String = SafeEmail().fake();
let form = EditUserForm {
let form = NewOrEditUserForm {
name: new_name.clone(),
email: new_mail.clone(),
role: Role::AreaManager as u8,
function: Function::Fuehrungsassistent as u8,
is_posten: None,
is_wachhabender: None,
is_fuehrungsassistent: Some(true),
area: Some(2),
};
@ -127,11 +131,13 @@ mod tests {
user_area: 1,
};
let form = EditUserForm {
let form = NewOrEditUserForm {
name: "".to_string(),
email: "".to_string(),
role: Role::AreaManager as u8,
function: Function::Fuehrungsassistent as u8,
is_posten: None,
is_wachhabender: None,
is_fuehrungsassistent: Some(true),
area: Some(1),
};

View File

@ -1,28 +1,19 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use garde::Validate;
use serde::Deserialize;
use sqlx::PgPool;
use crate::{
endpoints::user::NewOrEditUserForm,
mail::Mailer,
models::{Function, Registration, Role, User, UserChangeset},
utils::ApplicationError,
};
#[derive(Deserialize)]
pub struct NewUserForm {
email: String,
name: String,
role: u8,
function: u8,
area: Option<i32>,
}
#[actix_web::post("/users/new")]
pub async fn post_new(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
form: web::Form<NewUserForm>,
form: web::Form<NewOrEditUserForm>,
mailer: web::Data<Mailer>,
) -> Result<impl Responder, ApplicationError> {
if user.role != Role::AreaManager && user.role != Role::Admin {
@ -36,29 +27,39 @@ pub async fn post_new(
}
let role = Role::try_from(form.role)?;
let function = Function::try_from(form.function)?;
let mut functions = Vec::with_capacity(3);
if form.is_posten.unwrap_or(false) {
functions.push(Function::Posten);
}
if form.is_wachhabender.unwrap_or(false) {
functions.push(Function::Wachhabender);
}
if form.is_fuehrungsassistent.unwrap_or(false) {
functions.push(Function::Fuehrungsassistent);
}
let changeset = UserChangeset {
name: form.name.clone(),
email: form.email.clone(),
role,
function,
area_id
functions,
area_id,
};
if let Err(e) = changeset.validate() {
return Ok(HttpResponse::BadRequest().body(e.to_string()));
};
let id = User::create(
pool.get_ref(),
changeset
)
.await?;
let id = User::create(pool.get_ref(), changeset).await?;
let registration = Registration::insert_new_for_user(pool.get_ref(), id).await?;
let new_user = User::read_by_id(pool.get_ref(), id).await?.unwrap();
mailer.send_registration_mail(&new_user, &registration.token).await?;
mailer
.send_registration_mail(&new_user, &registration.token)
.await?;
Ok(HttpResponse::Found()
.insert_header((LOCATION, "/users"))

View File

@ -30,7 +30,7 @@ impl User {
changeset.name,
changeset.email,
changeset.role as Role,
changeset.function as Function,
changeset.functions.as_slice() as &[Function],
changeset.area_id
)
.fetch_one(pool)
@ -285,7 +285,7 @@ impl User {
changeset.name,
changeset.email,
changeset.role as Role,
changeset.function as Function,
changeset.functions.as_slice() as &[Function],
changeset.area_id,
id
)

View File

@ -5,7 +5,7 @@ use garde::Validate;
use super::{Function, Role};
#[derive(Validate)]
#[derive(Debug, Validate)]
#[cfg_attr(test, derive(Dummy))]
#[garde(allow_unvalidated)]
pub struct UserChangeset {
@ -16,8 +16,8 @@ pub struct UserChangeset {
pub email: String,
#[cfg_attr(test, dummy(expr = "Role::Staff"))]
pub role: Role,
#[cfg_attr(test, dummy(expr = "Function::Posten"))]
pub function: Function,
#[cfg_attr(test, dummy(expr = "vec![Function::Posten]"))]
pub functions: Vec<Function>,
/// check before: must exist and user can create other user for this area
#[cfg_attr(test, dummy(expr = "1"))]
pub area_id: i32,

View File

@ -64,12 +64,28 @@
<label class="label">Funktion</label>
</div>
<div class="field-body">
<div class="field is-narrow">
<div class="field is-grouped">
<div class="control">
<div class="select is-fullwidth">
<select name="function">
</select>
</div>
<label class="checkbox">
<input id="is-posten" type="checkbox" name="is-posten" value="true" {{
is_posten.unwrap_or(false)|ref|cond_show("checked") }} />
Posten
</label>
</div>
<div class="control">
<label class="checkbox">
<input type="checkbox" name="is-wachhabender" value="true" {{
is_wachhabender.unwrap_or(false)|ref|cond_show("checked") }}
_="on change if me.checked then set checked of previous <input/> to true" />
Wachhabender
</label>
</div>
<div class="control">
<label class="checkbox">
<input type="checkbox" name="is-fuehrungsassistent" value="true" {{
is_fuehrungsassistent.unwrap_or(false)|ref|cond_show("checked") }} />
Führungsassistent
</label>
</div>
</div>
</div>
@ -87,8 +103,8 @@
<select name="area">
{% for area in areas.as_ref().unwrap() %}
<option value="{{ area.id }}" {{ area_id|is_some_and_eq(area.id)|ref|cond_show("selected") }}>{{
area.name }}</option>
{% endfor %}
area.name }}</input>
{% endfor %}
</select>
</div>
</div>