feat: WIP edit and delete location and area

This commit is contained in:
Max Hohlfeld 2024-11-04 23:12:00 +01:00
parent a1d86992ef
commit bdc57fd22c
10 changed files with 273 additions and 119 deletions

View File

@ -0,0 +1,18 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use sqlx::PgPool;
use crate::{endpoints::IdPath, models::User, utils::ApplicationError};
#[actix_web::delete("/locations/delete/{id}")]
pub async fn delete (
user: web::ReqData<User>,
pool: web::Data<PgPool>,
path: web::Path<IdPath>
) -> Result<impl Responder, ApplicationError> {
Ok(HttpResponse::Found()
.insert_header((LOCATION, "/locations"))
.insert_header(("HX-LOCATION", "/locations"))
.finish())
}

View File

@ -0,0 +1,38 @@
use actix_web::{web, HttpResponse, Responder};
use askama_actix::TemplateToResponse;
use sqlx::PgPool;
use crate::{
endpoints::{location::LocationTemplate, IdPath},
models::{Area, Location, Role, User},
utils::ApplicationError,
};
#[actix_web::get("/locations/edit/{id}")]
pub async fn get(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
path: web::Path<IdPath>,
) -> Result<impl Responder, ApplicationError> {
if user.role == Role::AreaManager && user.role == Role::Admin {
return Err(ApplicationError::Unauthorized);
}
let Some(location) = Location::read_by_id(pool.get_ref(), path.id).await? else {
return Ok(HttpResponse::NotFound().finish());
};
let mut areas = None;
if user.role == Role::Admin {
areas = Some(Area::read_all(pool.get_ref()).await?);
}
let template = LocationTemplate {
user: user.into_inner(),
areas,
location: Some(location),
};
Ok(template.to_response())
}

View File

@ -1,16 +1,8 @@
use actix_web::{web, HttpResponse, Responder}; use actix_web::{web, HttpResponse, Responder};
use askama::Template;
use askama_actix::TemplateToResponse; use askama_actix::TemplateToResponse;
use sqlx::PgPool; use sqlx::PgPool;
use crate::models::{Area, Role, User}; use crate::{endpoints::location::LocationTemplate, models::{Area, Role, User}};
#[derive(Template)]
#[template(path = "location/new.html")]
pub struct NewLocationTemplate {
user: User,
areas: Option<Vec<Area>>,
}
#[actix_web::get("/locations/new")] #[actix_web::get("/locations/new")]
pub async fn get(user: web::ReqData<User>, pool: web::Data<PgPool>) -> impl Responder { pub async fn get(user: web::ReqData<User>, pool: web::Data<PgPool>) -> impl Responder {
@ -21,9 +13,10 @@ pub async fn get(user: web::ReqData<User>, pool: web::Data<PgPool>) -> impl Resp
areas = Some(Area::read_all(pool.get_ref()).await.unwrap()); areas = Some(Area::read_all(pool.get_ref()).await.unwrap());
} }
let template = NewLocationTemplate { let template = LocationTemplate {
user: user.into_inner(), user: user.into_inner(),
areas, areas,
location: None
}; };
return template.to_response(); return template.to_response();

View File

@ -1,3 +1,26 @@
pub mod get_overview; use askama::Template;
use serde::Deserialize;
use crate::models::{Area, Location, Role, User};
use crate::filters;
pub mod get_new; pub mod get_new;
pub mod get_overview;
pub mod post_new; pub mod post_new;
pub mod get_edit;
pub mod post_edit;
pub mod delete;
#[derive(Template)]
#[template(path = "location/new_or_edit.html")]
pub struct LocationTemplate {
user: User,
areas: Option<Vec<Area>>,
location: Option<Location>,
}
#[derive(Deserialize)]
pub struct LocationForm {
name: String,
area: Option<i32>,
}

View File

@ -0,0 +1,33 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use sqlx::PgPool;
use crate::{
endpoints::{location::LocationForm, IdPath},
models::{Location, Role, User},
utils::ApplicationError,
};
#[actix_web::post("/locations/edit/{id}")]
pub async fn post(
user: web::ReqData<User>,
pool: web::Data<PgPool>,
form: web::Form<LocationForm>,
path: web::Path<IdPath>,
) -> Result<impl Responder, ApplicationError> {
if user.role == Role::AreaManager && user.role == Role::Admin {
return Err(ApplicationError::Unauthorized);
}
let area_id = if user.role == Role::Admin && form.area.is_some() {
form.area.unwrap()
} else {
user.area_id
};
Location::update(pool.get_ref(), path.id, &form.name, area_id).await?;
Ok(HttpResponse::Found()
.insert_header((LOCATION, "/locations"))
.insert_header(("HX-LOCATION", "/locations"))
.finish())
}

View File

@ -1,42 +1,38 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder}; use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use serde::Deserialize;
use sqlx::PgPool; use sqlx::PgPool;
use crate::models::{Location, Role, User}; use crate::{
endpoints::location::LocationForm,
#[derive(Deserialize)] models::{Location, Role, User}, utils::ApplicationError,
pub struct NewLocationForm { };
name: String,
area: Option<i32>,
}
#[actix_web::post("/locations/new")] #[actix_web::post("/locations/new")]
pub async fn post( pub async fn post(
user: web::ReqData<User>, user: web::ReqData<User>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
form: web::Form<NewLocationForm>, form: web::Form<LocationForm>,
) -> impl Responder { ) -> Result<impl Responder, ApplicationError> {
if user.role == Role::AreaManager && user.role == Role::Admin {
if user.role == Role::AreaManager || user.role == Role::Admin { return Err(ApplicationError::Unauthorized);
let mut area_id = user.area_id;
if user.role == Role::Admin && form.area.is_some() {
area_id = form.area.unwrap();
}
match Location::create(pool.get_ref(), &form.name, area_id).await {
Ok(_) => {
return HttpResponse::Found()
.insert_header((LOCATION, "/locations"))
.insert_header(("HX-LOCATION", "/locations"))
.finish()
}
Err(err) => {
println!("{}", err);
return HttpResponse::InternalServerError().finish();
}
}
} }
return HttpResponse::Unauthorized().finish(); let mut area_id = user.area_id;
if user.role == Role::Admin && form.area.is_some() {
area_id = form.area.unwrap();
}
// TODO: rework
match Location::create(pool.get_ref(), &form.name, area_id).await {
Ok(_) => {
return Ok(HttpResponse::Found()
.insert_header((LOCATION, "/locations"))
.insert_header(("HX-LOCATION", "/locations"))
.finish())
}
Err(err) => {
println!("{}", err);
return Ok(HttpResponse::InternalServerError().finish());
}
}
} }

View File

@ -41,9 +41,7 @@ impl Location {
} }
pub async fn read_all(pool: &PgPool) -> anyhow::Result<Vec<Location>> { pub async fn read_all(pool: &PgPool) -> anyhow::Result<Vec<Location>> {
let records = query!("SELECT * FROM location") let records = query!("SELECT * FROM location").fetch_all(pool).await?;
.fetch_all(pool)
.await?;
let locations = records let locations = records
.iter() .iter()
@ -78,4 +76,42 @@ impl Location {
Ok(locations) Ok(locations)
} }
pub async fn read_by_id(pool: &PgPool, id: i32) -> super::Result<Option<Location>> {
let record = query!("SELECT * FROM location WHERE id = $1;", id)
.fetch_optional(pool)
.await?;
let location = record.and_then(|r| {
Some(Location {
id: r.id,
name: r.name,
area_id: r.areaid,
area: None,
})
});
Ok(location)
}
pub async fn update(pool: &PgPool, id: i32, name: &str, area_id: i32) -> super::Result<()> {
query!(
"UPDATE location SET name = $1, areaid = $2 WHERE id = $3;",
name,
area_id,
id
)
.execute(pool)
.await?;
Ok(())
}
pub async fn delete(pool: &PgPool, id: i32) -> super::Result<()> {
sqlx::query!("DELETE FROM location WHERE id = $1;", id)
.execute(pool)
.await?;
Ok(())
}
} }

View File

@ -5,7 +5,7 @@
<div class="container"> <div class="container">
{% if area.is_some() %} {% if area.is_some() %}
<form method="post" action="/area/edit/{{ area.as_ref().unwrap().id }}"> <form method="post" action="/area/edit/{{ area.as_ref().unwrap().id }}">
<h1 class="title">Bereich '{{ area.as_ref().unwrap().name }}' anlegen</h1> <h1 class="title">Bereich '{{ area.as_ref().unwrap().name }}' bearbeiten</h1>
{% else %} {% else %}
<form method="post" action="/area/new"> <form method="post" action="/area/new">
<h1 class="title">Neuen Bereich anlegen</h1> <h1 class="title">Neuen Bereich anlegen</h1>

View File

@ -1,73 +0,0 @@
{% extends "nav.html" %}
{% block content %}
<section class="section">
<div class="container">
<form method="post" action="/locations/new">
<h1 class="title">Neuen Ort anlegen</h1>
<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" required placeholder="Zentralstadion" />
</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 required 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">
<button class="button is-success">
<svg class="icon">
<use href="/static/feather-sprite.svg#check-circle" />
</svg>
<span>Speichern</span>
</button>
</div>
<div class="control">
<a class="button is-link is-light" hx-boost="true" href="/locations">
<svg class="icon">
<use href="/static/feather-sprite.svg#arrow-left" />
</svg>
<span>Zurück</span>
</a>
</div>
</div>
</div>
</div>
</form>
</div>
</section>
<script>
</script>
{% endblock %}

View File

@ -0,0 +1,90 @@
{% extends "nav.html" %}
{% block content %}
<section class="section">
<div class="container">
{% if location.is_some() %}
<form method="post" action="/locations/edit/{{ location.as_ref().unwrap().id }}">
<h1 class="title">Veranstaltungsort '{{ location.as_ref().unwrap().name }}' bearbeiten</h1>
{% else %}
<form method="post" action="/locations/new">
<h1 class="title">Neuen Ort anlegen</h1>
{% endif %}
<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" required placeholder="Zentralstadion" {% if
location.is_some() -%} value="{{ location.as_ref().unwrap().name }}" {% endif -%} />
</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 required name="area">
{%- let areaid = None %}
{% if let Some(loc) = location %}
{% let areaid = loc.area_id %}
{% endif -%}
{% for area in areas.as_ref().unwrap() %}
<option {{ areaid|is_some_and_eq(area.id)|cond_show("selected") }} 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">
<button class="button is-success">
<svg class="icon">
<use href="/static/feather-sprite.svg#check-circle" />
</svg>
<span>
{% if location.is_some() %}
Speichern
{% else %}
Erstellen
{% endif %}
</span>
</button>
</div>
<div class="control">
<a class="button is-link is-light" hx-boost="true" href="/locations">
<svg class="icon">
<use href="/static/feather-sprite.svg#arrow-left" />
</svg>
<span>Zurück</span>
</a>
</div>
</div>
</div>
</div>
</form>
</div>
</section>
<script>
</script>
{% endblock %}