feat: create and view edit users

This commit is contained in:
Max Hohlfeld 2024-03-02 12:57:18 +01:00
parent 6b35eda326
commit e107687f71
15 changed files with 552 additions and 60 deletions

View File

@ -1,4 +1,4 @@
CREATE TYPE role AS ENUM ('staff', 'area manager', 'admin');
CREATE TYPE role AS ENUM ('staff', 'areamanager', 'admin');
CREATE TYPE function AS ENUM ('posten', 'wachhabender');
CREATE TABLE area

View File

@ -33,10 +33,10 @@ async fn route(
let result = User::create(
pool.get_ref(),
form.name,
form.email,
hash,
salt,
&form.name,
&form.email,
&hash,
&salt,
role,
function,
form.area_id,

View File

@ -1,10 +1,19 @@
use actix_web::web::ServiceConfig;
use serde::Deserialize;
mod location;
mod user;
#[derive(Deserialize)]
pub struct IdPath {
pub id: i32
}
pub fn init(cfg: &mut ServiceConfig) {
cfg.service(location::get);
cfg.service(user::get);
cfg.service(user::get_overview::get_overview);
cfg.service(user::get_new::get_new);
cfg.service(user::post_new::post_new);
cfg.service(user::get_edit::get_edit);
}

View File

@ -0,0 +1,37 @@
use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder};
use askama::Template;
use askama_actix::TemplateToResponse;
use sqlx::PgPool;
use crate::{endpoints::IdPath, models::{Area, Role, User}};
#[derive(Template)]
#[template(path = "user/edit.html")]
pub struct EditUserTemplate {
user: User,
areas: Option<Vec<Area>>,
email: String,
name: String,
role: u8,
function: u8,
area_id: i32
}
#[actix_web::get("/users/edit/{id}")]
pub async fn get_edit(user: Identity, pool: web::Data<PgPool>, path: web::Path<IdPath>) -> impl Responder {
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap()).await.unwrap();
let mut areas = None;
if current_user.role == Role::Admin {
areas = Some(Area::read_all(pool.get_ref()).await.unwrap());
}
if let Ok(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await {
let template = EditUserTemplate { user: current_user, areas, email: user_in_db.email, name: user_in_db.name, role: user_in_db.role as u8, function: user_in_db.function as u8, area_id: user_in_db.area_id };
return template.to_response();
}
HttpResponse::BadRequest().body("Fehler beim Bearbeiten des Nutzers")
}

View File

@ -0,0 +1,28 @@
use actix_identity::Identity;
use actix_web::{web, Responder};
use askama::Template;
use askama_actix::TemplateToResponse;
use sqlx::PgPool;
use crate::models::{Area, Role, User};
#[derive(Template)]
#[template(path = "user/new.html")]
pub struct NewUserTemplate {
user: User,
areas: Option<Vec<Area>>
}
#[actix_web::get("/users/new")]
pub async fn get_new(user: Identity, pool: web::Data<PgPool>) -> impl Responder {
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap()).await.unwrap();
let mut areas: Option<Vec<Area>> = None;
if current_user.role == Role::Admin {
areas = Some(Area::read_all(pool.get_ref()).await.unwrap())
}
let template = NewUserTemplate { user: current_user, areas };
template.to_response()
}

View File

@ -0,0 +1,49 @@
use crate::models::{Area, Role, User, Function};
use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder};
use askama::Template;
use askama_actix::TemplateToResponse;
use sqlx::PgPool;
#[derive(Template)]
#[template(path = "user/overview.html")]
pub struct UsersTemplate {
user: User,
area: Option<Area>,
users: Vec<User>,
}
#[actix_web::get("/users")]
pub async fn get_overview(user: Identity, pool: web::Data<PgPool>) -> impl Responder {
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap())
.await
.unwrap();
if current_user.role == Role::AreaManager || current_user.role == Role::Admin {
let mut area = None;
let users;
if current_user.role == Role::AreaManager {
area = Some(
Area::read_by_id(pool.get_ref(), current_user.area_id)
.await
.unwrap(),
);
users = User::read_all_by_area(pool.get_ref(), current_user.area_id)
.await
.unwrap();
} else {
users = User::read_all_including_area(pool.get_ref()).await.unwrap();
}
let template = UsersTemplate {
user: current_user,
area,
users,
};
return template.to_response();
}
return HttpResponse::BadRequest().body("Fehler beim Zugriff auf die Nutzerverwaltung!");
}

View File

@ -1,38 +1,5 @@
use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder};
use askama::Template;
use askama_actix::TemplateToResponse;
use sqlx::PgPool;
use crate::models::{Area, Role, User};
#[derive(Template)]
#[template(path = "user/overview.html")]
pub struct UsersTemplate {
user: User,
area: Option<Area>,
users: Vec<User>
}
#[actix_web::get("/users")]
pub async fn get(user: Identity, pool: web::Data<PgPool>) -> impl Responder {
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap()).await.unwrap();
if current_user.role == Role::AreaManager || current_user.role == Role::Admin {
let mut area = None;
let users;
if current_user.role == Role::AreaManager {
area = Some(Area::read_by_id(pool.get_ref(), current_user.area_id).await.unwrap());
users = User::read_all_by_area(pool.get_ref(), current_user.area_id).await.unwrap();
} else {
users = User::read_all(pool.get_ref()).await.unwrap();
}
let template = UsersTemplate { user: current_user, area, users};
return template.to_response()
}
return HttpResponse::BadRequest().body("Fehler beim Zugriff auf die Nutzerverwaltung!");
}
pub mod get_overview;
pub mod get_new;
pub mod post_new;
pub mod get_edit;
pub mod put_edit;

View File

@ -0,0 +1,41 @@
use actix_identity::Identity;
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use serde::Deserialize;
use sqlx::PgPool;
use crate::{auth::utils::generate_salt_and_hash_plain_password, models::{Function, Role, User}};
#[derive(Deserialize)]
pub struct NewUserForm {
email: String,
name: String,
password: String,
role: u8,
function: u8,
area: Option<i32>
}
#[actix_web::post("/users/new")]
pub async fn post_new(user: Identity, pool: web::Data<PgPool>, form: web::Form<NewUserForm>) -> impl Responder {
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap()).await.unwrap();
let mut area_id = current_user.area_id;
if current_user.role == Role::Admin {
if let Some(id) = form.area {
area_id = id;
}
}
if let Ok((hash, salt)) = generate_salt_and_hash_plain_password(&form.password) {
if let Ok(role) = Role::try_from(form.role) {
if let Ok(function) = Function::try_from(form.function) {
match User::create(pool.get_ref(), &form.name, &form.email, &hash, &salt, role, function, area_id).await {
Ok(_) => return HttpResponse::Found().insert_header((LOCATION, "/users")).finish(),
Err(err) => println!("{}", err)
}
}
}
}
return HttpResponse::BadRequest().body("Fehler beim Erstellen des Nutzers");
}

View File

View File

@ -1,5 +1,6 @@
use sqlx::{query, query_as, PgPool};
#[derive(Clone)]
pub struct Area {
pub id: i32,
pub name: String

View File

@ -102,6 +102,7 @@ impl Availabillity {
role: r.role.clone(),
function: r.function.clone(),
area_id: r.areaid,
area: None,
locked: r.locked,
last_login: r.lastlogin,
receive_notifications: r.receivenotifications,

View File

@ -1,7 +1,7 @@
use chrono::{DateTime, Utc};
use sqlx::PgPool;
use super::{Function, Role};
use super::{Area, Function, Role};
#[derive(Clone)]
pub struct User {
@ -13,6 +13,7 @@ pub struct User {
pub role: Role,
pub function: Function,
pub area_id: i32,
pub area: Option<Area>,
pub locked: bool,
pub last_login: Option<DateTime<Utc>>,
pub receive_notifications: bool,
@ -21,10 +22,10 @@ pub struct User {
impl User {
pub async fn create(
pool: &PgPool,
name: String,
email: String,
password: String,
salt: String,
name: &str,
email: &str,
password: &str,
salt: &str,
role: Role,
function: Function,
area_id: i32,
@ -83,6 +84,7 @@ impl User {
role: record.role,
function: record.function,
area_id: record.areaid,
area: None,
locked: record.locked,
last_login: record.lastlogin,
receive_notifications: record.receivenotifications,
@ -123,6 +125,7 @@ impl User {
role: record.role,
function: record.function,
area_id: record.areaid,
area: None,
locked: record.locked,
last_login: record.lastlogin,
receive_notifications: record.receivenotifications,
@ -164,6 +167,7 @@ impl User {
role: record.role.clone(),
function: record.function.clone(),
area_id: record.areaid,
area: None,
locked: record.locked,
last_login: record.lastlogin,
receive_notifications: record.receivenotifications,
@ -173,6 +177,54 @@ impl User {
Ok(result)
}
pub async fn read_all_including_area(pool: &PgPool) -> anyhow::Result<Vec<User>> {
let records = sqlx::query!(
r#"
SELECT
user_.id AS userId,
user_.name,
user_.email,
user_.password,
user_.salt,
user_.role AS "role: Role",
user_.function AS "function: Function",
user_.areaId,
user_.locked,
user_.lastLogin,
user_.receiveNotifications,
area.id,
area.name AS areaName
FROM user_
JOIN area ON user_.areaId = area.id
"#
)
.fetch_all(pool)
.await?;
let results = records
.iter()
.map(|record| User {
id: record.userid,
name: record.name.clone(),
email: record.email.clone(),
password: record.password.clone(),
salt: record.salt.clone(),
role: record.role.clone(),
function: record.function.clone(),
area_id: record.areaid,
area: Some(Area {
id: record.areaid,
name: record.areaname.clone(),
}),
locked: record.locked,
last_login: record.lastlogin,
receive_notifications: record.receivenotifications,
})
.collect();
Ok(results)
}
pub async fn read_all_by_area(pool: &PgPool, area_id: i32) -> anyhow::Result<Vec<User>> {
let records = sqlx::query!(
r#"
@ -206,6 +258,7 @@ impl User {
role: record.role.clone(),
function: record.function.clone(),
area_id: record.areaid,
area: None,
locked: record.locked,
last_login: record.lastlogin,
receive_notifications: record.receivenotifications,
@ -215,11 +268,11 @@ impl User {
Ok(result)
}
pub async fn update(pool: &PgPool, id: i32, updated_user: User) -> Option<User> {
todo!()
}
// pub async fn update(pool: &PgPool, id: i32, updated_user: User) -> Option<User> {
// todo!()
// }
pub async fn delete(pool: &PgPool, id: i32) -> anyhow::Result<bool> {
todo!()
}
// pub async fn delete(pool: &PgPool, id: i32) -> anyhow::Result<bool> {
// todo!()
// }
}

126
templates/user/edit.html Normal file
View File

@ -0,0 +1,126 @@
{% extends "nav.html" %}
{% block content %}
<section class="section">
<div class="container">
<form method="post" action="/users/new">
<h1 class="title">Nutzer {{ name }} bearbeiten</h1>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">E-Mail</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" type="text" name="email" placeholder="max.mustermann@brasiwa-leipzig.de" value="{{ email }}"/>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Name</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" type="text" name="name" placeholder="Max Mustermann" value="{{ name }}"/>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Password</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" type="password" name="password" placeholder="**********"/>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Rolle</label>
</div>
<div class="field-body">
<div class="field is-narrow">
<div class="control">
<div class="select is-fullwidth">
<select name="role">
<option value="1" {% if role == 1%}selected{% endif %}>Personal</option>
<option value="10" {% if role == 10%}selected{% endif %}>Bereichsleiter</option>
<option value="100" {% if role == 100%}selected{% endif %}>Admin</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Funktion</label>
</div>
<div class="field-body">
<div class="field is-narrow">
<div class="control">
<div class="select is-fullwidth">
<select name="function">
<option value="1" {% if function == 1%}selected{% endif %}>Posten</option>
<option value="10" {% if function == 10%}selected{% endif %}>Wachhabender</option>
</select>
</div>
</div>
</div>
</div>
</div>
{% if user.role == Role::Admin %}
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Bereich</label>
</div>
<div class="field-body">
<div class="field is-narrow">
<div class="control">
<div class="select is-fullwidth">
<select name="area">
{% for area in areas.as_ref().unwrap() %}
<option value="{{ area.id }}" {% if area.id == area_id%}selected{% endif %}>{{ area.name }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
</div>
{% endif %}
<div class="field is-horizontal">
<div class="field-label"></div>
<div class="field-body">
<div class="field is-grouped">
<div class="control">
<input class="button is-link" type="submit" value="Erstellen">
</div>
<div class="control">
<a class="button is-link is-light" href="/users">Zurück</a>
</div>
</div>
</div>
</div>
</form>
</div>
</section>
<script>
</script>
{% endblock %}

126
templates/user/new.html Normal file
View File

@ -0,0 +1,126 @@
{% extends "nav.html" %}
{% block content %}
<section class="section">
<div class="container">
<form method="post" action="/users/new">
<h1 class="title">Neuen Nutzer anlegen</h1>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">E-Mail</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" type="text" name="email" placeholder="max.mustermann@brasiwa-leipzig.de"/>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Name</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" type="text" name="name" placeholder="Max Mustermann"/>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Password</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" type="password" name="password" placeholder="**********"/>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Rolle</label>
</div>
<div class="field-body">
<div class="field is-narrow">
<div class="control">
<div class="select is-fullwidth">
<select name="role">
<option value="1">Personal</option>
<option value="10">Bereichsleiter</option>
<option value="100">Admin</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Funktion</label>
</div>
<div class="field-body">
<div class="field is-narrow">
<div class="control">
<div class="select is-fullwidth">
<select name="function">
<option value="1">Posten</option>
<option value="10">Wachhabender</option>
</select>
</div>
</div>
</div>
</div>
</div>
{% if user.role == Role::Admin %}
<div class="field is-horizontal">
<div class="field-label">
<label class="label">Bereich</label>
</div>
<div class="field-body">
<div class="field is-narrow">
<div class="control">
<div class="select is-fullwidth">
<select name="area">
{% for area in areas.as_ref().unwrap() %}
<option value="{{ area.id }}">{{ area.name }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
</div>
{% endif %}
<div class="field is-horizontal">
<div class="field-label"></div>
<div class="field-body">
<div class="field is-grouped">
<div class="control">
<input class="button is-link" type="submit" value="Erstellen">
</div>
<div class="control">
<a class="button is-link is-light" href="/users">Zurück</a>
</div>
</div>
</div>
</div>
</form>
</div>
</section>
<script>
</script>
{% endblock %}

View File

@ -6,7 +6,7 @@
<div class="level">
<div class="level-left">
<h3 class="title is-3">
Nutzer
Nutzer {% if user.role == Role::AreaManager %}für Bereich {{ area.as_ref().unwrap().name }}{% endif %}
</h3>
</div>
<div class="level-right">
@ -16,14 +16,68 @@
{% if users.len() == 0 %}
<div class="box">
<h5 class="title is-5">keine Orte vorhanden</h5>
<h5 class="title is-5">keine Nutzer vorhanden</h5>
</div>
{% else %}
{% for u in users %}
<div class="box">
<h5 class="title is-5">{{ u.email }}</h5>
<table class="table is-fullwidth">
<thead>
<tr>
<th>E-Mail</th>
<th>Name</th>
<th>Rolle</th>
<th>Funktion</th>
{% if user.role == Role::Admin %}
<th>Bereich</th>
{% endif %}
<th></th>
</tr>
</thead>
<tbody>
{% for u in users %}
<tr>
<td>
{{ u.email }}
</td>
<td>
{{ u.name }}
</td>
<td>
{% match u.role %}
{% when Role::Staff %}
<span class="tag is-info is-light">Nutzer</span>
{% when Role::AreaManager %}
<span class="tag is-info is-light">Bereichsleiter</span>
{% when Role::Admin %}
<span class="tag is-info">Admin</span>
{% else %}
{% endmatch %}
</td>
<td>
{% match u.function %}
{% when Function::Posten %}
<span class="tag is-info is-light">Posten</span>
{% when Function::Wachhabender %}
<span class="tag is-info">Wachhabender</span>
{% else %}
{% endmatch %}
</td>
{% if user.role == Role::Admin %}
<td>
{{ u.area.as_ref().unwrap().name }}
</td>
{% endif %}
<td>
<div class="buttons is-right">
<a class="button is-link" href="/users/edit/{{ u.id }}">Bearbeiten</a>
<button class="button is-danger" name="delete-availabillity">Löschen</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</section>