feat: locking and deletion of user

This commit is contained in:
Max Hohlfeld 2024-03-06 23:28:58 +01:00
parent 39a28038ca
commit 3d3e92cf38
11 changed files with 200 additions and 26 deletions

39
Cargo.lock generated
View File

@ -395,7 +395,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
"syn 2.0.18",
"syn 2.0.52",
]
[[package]]
@ -532,7 +532,7 @@ checksum = "7b2d0f03b3640e3a630367e40c468cb7f309529c708ed1d88597047b0e7c6ef7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.52",
]
[[package]]
@ -650,6 +650,7 @@ dependencies = [
"dotenv",
"futures-util",
"serde",
"serde_json",
"sqlx",
]
@ -1040,7 +1041,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.52",
]
[[package]]
@ -1670,18 +1671,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.60"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.28"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@ -1860,29 +1861,29 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
[[package]]
name = "serde"
version = "1.0.164"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.164"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.52",
]
[[package]]
name = "serde_json"
version = "1.0.96"
version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
dependencies = [
"itoa",
"ryu",
@ -2105,9 +2106,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.18"
version = "2.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
dependencies = [
"proc-macro2",
"quote",
@ -2131,7 +2132,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.52",
]
[[package]]
@ -2354,7 +2355,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.52",
"wasm-bindgen-shared",
]
@ -2388,7 +2389,7 @@ checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.52",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@ -19,3 +19,4 @@ chrono = { version = "0.4.33", features = ["serde"] }
actix-files = "0.6.5"
askama_actix = "0.14.0"
futures-util = "0.3.30"
serde_json = "1.0.114"

View File

@ -17,4 +17,6 @@ pub fn init(cfg: &mut ServiceConfig) {
cfg.service(user::post_new::post_new);
cfg.service(user::get_edit::get_edit);
cfg.service(user::post_edit::post_edit);
cfg.service(user::patch::patch);
cfg.service(user::delete::delete);
}

View File

@ -0,0 +1,30 @@
use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder};
use sqlx::PgPool;
use crate::{endpoints::IdPath, models::{Role, User}};
#[actix_web::delete("/users/delete/{id}")]
pub async fn delete(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();
if current_user.role != Role::AreaManager && current_user.role != Role::Admin {
return HttpResponse::Unauthorized().finish();
}
if let Ok(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await {
if current_user.role == Role::AreaManager && current_user.area_id != user_in_db.area_id {
return HttpResponse::Unauthorized().finish();
}
if user_in_db.locked {
if let Ok(_) = User::delete(pool.get_ref(), user_in_db.id).await {
return HttpResponse::NoContent().finish();
}
}
}
HttpResponse::BadRequest().finish()
}

View File

@ -3,3 +3,5 @@ pub mod get_new;
pub mod post_new;
pub mod get_edit;
pub mod post_edit;
pub mod patch;
pub mod delete;

View File

@ -0,0 +1,69 @@
use actix_identity::Identity;
use actix_web::{web, HttpResponse, Responder};
use serde::Deserialize;
use serde_json::value::Value;
use sqlx::PgPool;
use crate::{
endpoints::IdPath,
models::{Role, User},
};
#[derive(Deserialize)]
pub struct JsonPatchDoc {
op: String,
path: String,
value: Value
}
#[actix_web::patch("/users/edit/{id}")]
pub async fn patch(
user: Identity,
pool: web::Data<PgPool>,
path: web::Path<IdPath>,
patch_docs: web::Json<Vec<JsonPatchDoc>>,
) -> 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 {
return HttpResponse::Unauthorized().finish();
}
if let Ok(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await {
if current_user.role == Role::AreaManager && current_user.area_id != user_in_db.area_id {
return HttpResponse::Unauthorized().finish();
}
let mut changed = false;
let mut locked: Option<bool> = None;
for doc in patch_docs.iter() {
if doc.op.as_str() != "replace" {
continue;
}
match doc.path.as_str() {
"/locked" => {
changed = true;
if let Value::Bool(b) = doc.value {
locked = Some(b);
}
},
_ => panic!("other patch paths are not supported!"),
};
}
if changed {
if let Ok(_) = User::update(pool.get_ref(), path.id, None, None, None, None, None, locked).await {
return HttpResponse::Ok().body("");
}
} else {
return HttpResponse::Ok().body("");
}
}
HttpResponse::BadRequest().body("Fehler bei User PATCH")
}

View File

@ -33,6 +33,10 @@ pub async fn post_edit(
}
if let Ok(user_in_db) = User::read_by_id(pool.get_ref(), path.id).await {
if current_user.role == Role::AreaManager && current_user.area_id != user_in_db.area_id {
return HttpResponse::Unauthorized().finish();
}
let mut changed = false;
let email = if user_in_db.email != form.email {
@ -79,7 +83,7 @@ pub async fn post_edit(
};
if changed {
match User::update(pool.get_ref(), path.id, email, name, role, function, area).await {
match User::update(pool.get_ref(), path.id, email, name, role, function, area, None).await {
Ok(_) => return HttpResponse::Found().insert_header((LOCATION, "/users")).finish(),
Err(err) => println!("{}", err)
}

View File

@ -26,6 +26,7 @@ async fn main() -> anyhow::Result<()> {
.app_data(web::Data::new(pool.clone()))
.configure(auth::init)
.configure(calendar::init)
.configure(endpoints::init)
.wrap(redirect::CheckLogin)
.wrap(IdentityMiddleware::default())
.wrap(SessionMiddleware::new(

View File

@ -276,6 +276,7 @@ impl User {
role: Option<Role>,
function: Option<Function>,
area_id: Option<i32>,
locked: Option<bool>
) -> anyhow::Result<()> {
let mut query_builder = sqlx::QueryBuilder::new("UPDATE user_ SET ");
let mut separated = query_builder.separated(", ");
@ -305,6 +306,11 @@ impl User {
separated.push_bind_unseparated(area_id);
}
if let Some(locked) = locked {
separated.push("locked = ");
separated.push_bind_unseparated(locked);
}
query_builder.push(" WHERE id = ");
query_builder.push_bind(id);
query_builder.push(";");
@ -316,7 +322,10 @@ impl User {
Ok(())
}
// pub async fn delete(pool: &PgPool, id: i32) -> anyhow::Result<bool> {
// todo!()
// }
pub async fn delete(pool: &PgPool, id: i32) -> anyhow::Result<()> {
sqlx::query!("DELETE FROM user_ WHERE id = $1", id)
.execute(pool)
.await?;
Ok(())
}
}

View File

@ -4,7 +4,7 @@
<section class="section">
<div class="container">
<form method="post" action="/users/edit/{{ id }}">
<h1 class="title">Nutzer {{ name }} bearbeiten</h1>
<h1 class="title">Nutzer '{{ name }}' bearbeiten</h1>
<div class="field is-horizontal">
<div class="field-label">

View File

@ -37,7 +37,7 @@
</thead>
<tbody>
{% for u in users %}
<tr>
<tr id="user-{{ u.id }}">
<td>
{{ u.email }}
</td>
@ -85,8 +85,9 @@
</td>
<td>
<div class="buttons is-right">
<button class="button is-link is-light" name="toggle-lock-user">{% if u.locked %}Entsperren{% else %}Sperren{% endif %}</button>
<a class="button is-link" href="/users/edit/{{ u.id }}">Bearbeiten</a>
<button class="button is-danger" name="delete-availabillity">Löschen</button>
<button class="button is-danger" {% if !u.locked %}disabled{% endif %} name="delete-user">Löschen</button>
</div>
</td>
</tr>
@ -100,5 +101,59 @@
</section>
<script>
document.getElementsByName("delete-user")
.forEach(ele => ele.addEventListener("click", (event) => {
const id = event.target.closest("tr").id.split('-')[1];
event.target.classList.add("is-loading");
fetch(`/users/delete/${id}`, { method: "DELETE"})
.then(response => {
if (response.status == 204) {
document.getElementById(`user-${id}`).remove()
} else {
event.target.classList.remove("is-loading");
console.log("Fehler beim Löschen.")
}
});
}));
document.getElementsByName("toggle-lock-user")
.forEach(ele => ele.addEventListener("click", (event) => {
const id = event.target.closest("tr").id.split('-')[1];
event.target.classList.add("is-loading");
const locked = event.target.innerHTML == "Sperren" ? false : true;
fetch(`/users/edit/${id}`, {
method: "PATCH",
headers: new Headers({
"Content-Type": "application/json-patch+json",
}),
body: JSON.stringify([
{
"op": "replace",
"path": "/locked",
"value": !locked
}
])
})
.then(response => {
if (response.status == 200) {
event.target.classList.remove("is-loading");
if (locked) {
event.target.innerHTML = "Sperren";
event.target.closest("tr").querySelector("td:nth-last-child(2)").innerHTML = "nein"
event.target.closest("div.buttons").querySelector("button:nth-last-child(1)").setAttribute("disabled", "");
} else {
event.target.innerHTML = "Entsperren";
event.target.closest("tr").querySelector("td:nth-last-child(2)").innerHTML = "ja"
event.target.closest("div.buttons").querySelector("button:nth-last-child(1)").removeAttribute("disabled");
}
} else {
event.target.classList.remove("is-loading");
console.log("Fehler beim PATCH.")
}
});
}));
</script>
{% endblock %}