2025-04-08 20:18:50 +02:00

535 lines
18 KiB
Rust

use super::{CreateError, Team};
use crate::{admin::route::Route, admin::station::Station, err, partials::page, pl, succ};
use axum::{
extract::State,
response::{IntoResponse, Redirect},
routing::{get, post},
Form, Router,
};
use maud::{html, Markup, PreEscaped};
use serde::Deserialize;
use sqlx::SqlitePool;
use std::sync::Arc;
use tower_sessions::Session;
#[derive(Deserialize)]
struct CreateForm {
name: String,
route_id: i64,
}
async fn create(
State(db): State<Arc<SqlitePool>>,
session: Session,
Form(form): Form<CreateForm>,
) -> impl IntoResponse {
let Some(route) = Route::find_by_id(&db, form.route_id).await else {
err!(
session,
"Team mit {} konnte nicht erstellt werden, da keine Route mit ID {} existiert",
form.name,
form.route_id
);
return Redirect::to("/admin/team");
};
match Team::create(&db, &form.name, &route).await {
Ok(()) => succ!(session, "Team '{}' erfolgreich erstellt!", form.name),
Err(CreateError::DuplicateName(e)) => err!(
session,
"Team '{}' konnte _NICHT_ erstellt werden, da es bereits ein Team mit diesem Namen gibt ({e})!",
form.name
),
Err(CreateError::NoStationForRoute) => err!(
session,
"Team '{}' konnte _NICHT_ erstellt werden, da in der angegebenen Route '{}' noch keine Stationen vorkommen",
form.name,
route.name
),
}
Redirect::to("/admin/team")
}
async fn delete(
State(db): State<Arc<SqlitePool>>,
session: Session,
axum::extract::Path(id): axum::extract::Path<i64>,
) -> impl IntoResponse {
let Some(team) = Team::find_by_id(&db, id).await else {
err!(
session,
"Team mit ID {id} konnte nicht gelöscht werden, da sie nicht existiert"
);
return Redirect::to("/admin/team");
};
match team.delete(&db).await {
Ok(()) => succ!(session, "Team '{}' erfolgreich gelöscht!", team.name),
Err(e) => err!(
session,
"Team '{}' kann nicht gelöscht werden, da sie bereits verwendet wird. ({e})",
team.name
),
}
Redirect::to("/admin/team")
}
async fn view(
State(db): State<Arc<SqlitePool>>,
session: Session,
axum::extract::Path(id): axum::extract::Path<i64>,
) -> Result<Markup, impl IntoResponse> {
let Some(team) = Team::find_by_id(&db, id).await else {
err!(
session,
"Team mit ID {id} konnte nicht geöffnet werden, da sie nicht existiert"
);
return Err(Redirect::to("/admin/team"));
};
let first_station = team.first_station(&db).await;
let routes = Route::all(&db).await;
let stations = team.route(&db).await.stations(&db).await;
// maybe switch to maud-display impl of team
let content = html! {
h1 {
a href="/admin/team" { "↩️" }
"Team " (team.name)
}
article {
details {
summary { "Teamnamen bearbeiten ✏️" }
form action=(format!("/admin/team/{}/name", team.id)) method="post" {
input type="text" name="name" value=(team.name) required;
input type="submit" value="Speichern";
}
}
}
table {
tbody {
tr {
th scope="row" { "Notizen" };
td {
@match &team.notes {
Some(notes) => {
(notes)
details {
summary { "✏️" }
form action=(format!("/admin/team/{}/notes", team.id)) method="post" {
textarea name="notes" required rows="10" { (notes) };
input type="submit" value="Speichern";
}
}
},
None => details {
summary { "Neue Notiz hinzufügen" }
form action=(format!("/admin/team/{}/notes", team.id)) method="post" {
textarea name="notes" required rows="10" {};
input type="submit" value="Speichern";
}
}
}
}
}
tr {
th scope="row" { "Anzahl Teammitglieder" };
td {
@match team.amount_people {
Some(amount) => (amount),
None => "?",
}
details {
summary { "✏️" }
form action=(format!("/admin/team/{}/amount-people", team.id)) method="post" {
input type="number" name="amount_people" min="0" max="10";
input type="submit" value="Speichern";
}
a href=(format!("/admin/team/{}/amount-people-reset", team.id)) {
button class="error" {
em data-tooltip="Ich weiß noch nicht wv. Personen dieses Team beherbergt." {
"?"
}
}
}
}
}
}
tr {
th scope="row" { "Route" };
td {
a href=(format!("/admin/route/{}", &team.route(&db).await.id)) {
(&team.route(&db).await.name)
}
@if routes.len() > 1 {
details {
summary { "✏️" }
form action=(format!("/admin/team/{}/update-route", team.id)) method="post" {
select name="route_id" aria-label="Route auswählen" required {
@for route in &routes {
@if route.id != team.route(&db).await.id {
option value=(route.id) {
(route.name)
}
}
}
}
input type="submit" value="Team speichern";
}
}
}
}
}
tr {
th scope="row" {
"Erste Station"
article {
"Die erste Station wird beim Anlegen eines Team automatisch an diejenige Station vergeben, die aktuell am wenigsten Startteams hat. Diese Zuteilung kannst du hier auch manuell verändern."
}
};
td {
a href=(format!("/admin/station/{}", first_station.id)) {
(first_station.name)
}
@if stations.len() > 1 {
details {
summary { "✏️" }
form action=(format!("/admin/team/{}/update-first-station", team.id)) method="post" {
select name="first_station_id" aria-label="Station auswählen" required {
@for station in &stations {
@if station.id != first_station.id {
option value=(station.id) {
(station.name)
@let amount_start_teams = Team::all_with_first_station(&db, station).await.len();
@if amount_start_teams > 0 {
(format!(" (schon {amount_start_teams} {})", pl(amount_start_teams, "Startteam", "s")))
}
}
}
}
}
input type="submit" value="Station speichern";
}
}
}
}
}
}
}
};
Ok(page(content, session, true).await)
}
#[derive(Deserialize)]
struct UpdateNameForm {
name: String,
}
async fn update_name(
State(db): State<Arc<SqlitePool>>,
session: Session,
axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateNameForm>,
) -> impl IntoResponse {
let Some(team) = Team::find_by_id(&db, id).await else {
err!(
session,
"Team mit ID {id} konnte nicht bearbeitet werden, da sie nicht existiert"
);
return Redirect::to("/admin/team");
};
team.update_name(&db, &form.name).await;
succ!(
session,
"Team '{}' heißt ab sofort '{}'.",
team.name,
form.name
);
Redirect::to(&format!("/admin/team/{id}"))
}
#[derive(Deserialize)]
struct UpdateNotesForm {
notes: String,
}
async fn update_notes(
State(db): State<Arc<SqlitePool>>,
session: Session,
axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateNotesForm>,
) -> impl IntoResponse {
let Some(team) = Team::find_by_id(&db, id).await else {
err!(
session,
"Team mit ID {id} konnte nicht bearbeitet werden, da sie nicht existiert"
);
return Redirect::to("/admin/team");
};
team.update_notes(&db, &form.notes).await;
succ!(
session,
"Notizen für das Team '{}' wurden erfolgreich bearbeitet!",
team.name
);
Redirect::to(&format!("/admin/team/{id}"))
}
#[derive(Deserialize)]
struct UpdateAmountPeopleForm {
amount_people: i64,
}
async fn update_amount_people(
State(db): State<Arc<SqlitePool>>,
session: Session,
axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateAmountPeopleForm>,
) -> impl IntoResponse {
let Some(team) = Team::find_by_id(&db, id).await else {
err!(
session,
"Team mit ID {id} konnte nicht bearbeitet werden, da sie nicht existiert"
);
return Redirect::to("/admin/team");
};
team.update_amount_people(&db, form.amount_people).await;
succ!(
session,
"Anzahl an Mitglieder für das Team '{}' wurden erfolgreich bearbeitet!",
team.name
);
Redirect::to(&format!("/admin/team/{id}"))
}
#[derive(Deserialize)]
struct UpdateRouteForm {
route_id: i64,
}
async fn update_route(
State(db): State<Arc<SqlitePool>>,
session: Session,
axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateRouteForm>,
) -> impl IntoResponse {
// TODO: move sanity checks into mod.rs
let Some(team) = Team::find_by_id(&db, id).await else {
err!(
session,
"Team mit ID {id} konnte nicht bearbeitet werden, da sie nicht existiert"
);
return Redirect::to("/admin/team");
};
let Some(route) = Route::find_by_id(&db, form.route_id).await else {
err!(
session,
"Konnte Team mit ID {id} konnte nicht zur Route mit ID {} bearbeiten, da diese Route nicht existiert.",
form.route_id
);
return Redirect::to(&format!("/admin/team/{id}"));
};
match team.update_route(&db, &route).await {
Ok(new_first_station_name) => succ!(
session,
"Team '{}' läuft ab sofort die Route '{}'. Auch die erste Station hat sich dadurch auf {} verändert!!",
team.name,
route.name,
new_first_station_name
),
Err(()) => err!(
session,
"Konnte für das Team {} nicht die Route {} auswählen, da Route {} keine Stationen zugeteilt hat.",
team.name,
route.name,
route.name
),
}
Redirect::to(&format!("/admin/team/{id}"))
}
#[derive(Deserialize)]
struct UpdateFirstStationForm {
first_station_id: i64,
}
async fn update_first_station(
State(db): State<Arc<SqlitePool>>,
session: Session,
axum::extract::Path(id): axum::extract::Path<i64>,
Form(form): Form<UpdateFirstStationForm>,
) -> impl IntoResponse {
let Some(team) = Team::find_by_id(&db, id).await else {
err!(
session,
"Team mit ID {id} konnte nicht bearbeitet werden, da sie nicht existiert"
);
return Redirect::to("/admin/team");
};
let Some(station) = Station::find_by_id(&db, form.first_station_id).await else {
err!(
session,
"Konnte die erste Station (ID={}) des Teams mit ID {} nicht bearbeiten, da diese Station nicht existiert.",
form.first_station_id,
team.id
);
return Redirect::to(&format!("/admin/team/{id}"));
};
if !station.is_in_route(&db, &team.route(&db).await).await {
err!(
session,
"Konnte Station {} nicht dem Team {} hinzufügen, weil dieses Team bei Route {} und nicht bei Route {} mitläuft.",
station.name,
team.name,
team.route(&db).await.name,
team.name
);
return Redirect::to(&format!("/admin/team/{id}"));
}
team.update_first_station(&db, &station).await;
succ!(
session,
"Erste Station des Teams {} ist ab sofort {}",
team.name,
station.name
);
Redirect::to(&format!("/admin/team/{id}"))
}
async fn update_amount_people_reset(
State(db): State<Arc<SqlitePool>>,
session: Session,
axum::extract::Path(id): axum::extract::Path<i64>,
) -> impl IntoResponse {
let Some(team) = Team::find_by_id(&db, id).await else {
err!(
session,
"Team mit ID {id} konnte nicht bearbeitet werden, da sie nicht existiert"
);
return Redirect::to("/admin/team");
};
team.update_amount_people_reset(&db).await;
succ!(
session,
"Anzahl an Mitglieder für das Team '{}' wurden erfolgreich bearbeitet!",
team.name
);
Redirect::to(&format!("/admin/team/{id}"))
}
async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
let teams = Team::all(&db).await;
let routes = Route::all(&db).await;
let content = html! {
h1 {
a href="/admin/" { "↩️" }
"Teams"
}
article {
em { "Teams " }
"sind eine Menge an Personen, die verschiedene "
a href="/admin/station" { "Stationen" }
" ablaufen. Welche Stationen, entscheidet sich je nachdem, welcher "
a href="/admin/route" { "Route" }
" sie zugewiesen sind."
}
ol {
@for team in &teams{
li {
a href=(format!("/admin/team/{}", team.id)){
(team.name)
}
a href=(format!("/admin/team/{}/delete", team.id))
onclick="return confirm('Bist du sicher, dass das Team gelöscht werden soll? Das kann _NICHT_ mehr rückgängig gemacht werden.');" {
"🗑️"
}
}
}
}
@if teams.is_empty() {
article class="warning" {
"Es gibt noch keine Teams. "
@if !routes.is_empty() {
"Das kannst du hier ändern ⤵️"
}
}
}
h2 { "Neues Team" }
@if routes.is_empty() {
article class="error" {
(PreEscaped("Bevor du ein Team erstellen kannst, musst du zumindest eine Route erstellen, die das Team gehen kann &rarr; "))
a role="button" href="/admin/route" {
"Team erstellen"
}
}
} @else {
form action="/admin/team" method="post" {
@if routes.len() == 1 {
fieldset role="team" {
input type="text" name="name" placeholder="Teamnamen" required;
input type="hidden" name="route_id" value=(routes[0].id) ;
input type="submit" value="Neues Team";
}
} @else {
input type="text" name="name" placeholder="Teamnamen" required;
select name="route_id" aria-label="Route auswählen" required {
@for route in &routes {
option value=(route.id) {
(route.name)
}
}
}
input type="submit" value="Neues Team";
}
}
}
};
page(content, session, false).await
}
pub(super) fn routes() -> Router<Arc<SqlitePool>> {
Router::new()
.route("/", get(index))
.route("/", post(create))
.route("/{id}", get(view))
.route("/{id}/delete", get(delete))
.route("/{id}/name", post(update_name))
.route("/{id}/notes", post(update_notes))
.route("/{id}/amount-people", post(update_amount_people))
.route("/{id}/amount-people-reset", get(update_amount_people_reset))
.route("/{id}/update-route", post(update_route))
.route("/{id}/update-first-station", post(update_first_station))
}