From 3deb1e40fc78e263cc3c3eb81b30ce99b118c374 Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 8 Mar 2024 13:13:20 +0100 Subject: [PATCH 1/5] boatshouse functionality, fixes #183 --- migration.sql | 10 ++++ src/model/boat.rs | 35 +++++++++++ src/model/boathouse.rs | 90 +++++++++++++++++++++++++++++ src/model/mod.rs | 1 + src/tera/board/boathouse.rs | 85 +++++++++++++++++++++++++++ src/tera/board/mod.rs | 9 +++ src/tera/mod.rs | 2 + staging-diff.sql | 9 +++ templates/board/boathouse.html.tera | 48 +++++++++++++++ templates/index.html.tera | 3 + 10 files changed, 292 insertions(+) create mode 100644 src/model/boathouse.rs create mode 100644 src/tera/board/boathouse.rs create mode 100644 src/tera/board/mod.rs create mode 100644 templates/board/boathouse.html.tera diff --git a/migration.sql b/migration.sql index 2bbf1c9..2df76ef 100644 --- a/migration.sql +++ b/migration.sql @@ -140,3 +140,13 @@ CREATE TABLE IF NOT EXISTS "boat_damage" ( "verified_at" datetime, "lock_boat" boolean not null default false -- if true: noone can use the boat ); + +CREATE TABLE IF NOT EXISTS "boathouse" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "boat_id" INTEGER NOT NULL REFERENCES boat(id), + "aisle" TEXT NOT NULL CHECK (aisle in ('water', 'middle', 'mountain')), + "side" TEXT NOT NULL CHECK(side IN ('mountain', 'water')), + "level" INTEGER NOT NULL CHECK(level BETWEEN 0 AND 3), + CONSTRAINT unq UNIQUE (aisle, side, level) -- only 1 boat allowed to rest at each space +); + diff --git a/src/model/boat.rs b/src/model/boat.rs index d40a50a..4354de8 100644 --- a/src/model/boat.rs +++ b/src/model/boat.rs @@ -181,6 +181,41 @@ ORDER BY amount_seats DESC Self::boats_to_details(db, boats).await } + pub async fn all_for_boatshouse(db: &SqlitePool) -> Vec { + let boats = sqlx::query_as!( + Boat, + " +SELECT + b.id, + b.name, + b.amount_seats, + b.location_id, + b.owner, + b.year_built, + b.boatbuilder, + b.default_shipmaster_only_steering, + b.default_destination, + b.skull, + b.external +FROM + boat AS b +LEFT JOIN + boathouse AS bh ON b.id = bh.boat_id +WHERE + b.external = false + AND b.location_id = (SELECT id FROM location WHERE name = 'Linz') + AND bh.id IS NULL -- This ensures the boat does not have an entry in the boathouse table +ORDER BY + b.name DESC; + " + ) + .fetch_all(db) + .await + .unwrap(); //TODO: fixme + + Self::boats_to_details(db, boats).await + } + pub async fn for_user(db: &SqlitePool, user: &User) -> Vec { if user.has_role(db, "admin").await { return Self::all(db).await; diff --git a/src/model/boathouse.rs b/src/model/boathouse.rs new file mode 100644 index 0000000..a3ae022 --- /dev/null +++ b/src/model/boathouse.rs @@ -0,0 +1,90 @@ +use std::collections::HashMap; + +use rocket::serde::{Deserialize, Serialize}; +use sqlx::{FromRow, SqlitePool}; + +use crate::tera::board::boathouse::FormBoathouseToAdd; + +use super::boat::Boat; + +#[derive(FromRow, Debug, Serialize, Deserialize)] +pub struct Boathouse { + pub id: i64, + pub boat_id: i64, + pub aisle: String, + pub side: String, + pub level: i64, +} + +impl Boathouse { + pub async fn get(db: &SqlitePool) -> HashMap<&str, HashMap<&str, [Option<(i64, Boat)>; 4]>> { + let mut ret: HashMap<&str, HashMap<&str, [Option<(i64, Boat)>; 4]>> = HashMap::new(); + + let mut mountain = HashMap::new(); + mountain.insert("mountain", [None, None, None, None]); + mountain.insert("water", [None, None, None, None]); + ret.insert("mountain-aisle", mountain); + + let mut middle = HashMap::new(); + middle.insert("mountain", [None, None, None, None]); + middle.insert("water", [None, None, None, None]); + ret.insert("middle-aisle", middle); + + let mut water = HashMap::new(); + water.insert("mountain", [None, None, None, None]); + water.insert("water", [None, None, None, None]); + ret.insert("water-aisle", water); + + let boathouses = sqlx::query_as!( + Boathouse, + "SELECT id, boat_id, aisle, side, level FROM boathouse" + ) + .fetch_all(db) + .await + .unwrap(); //TODO: fixme + + for boathouse in boathouses { + let aisle = ret + .get_mut(format!("{}-aisle", boathouse.aisle).as_str()) + .unwrap(); + let side = aisle.get_mut(boathouse.side.as_str()).unwrap(); + + side[boathouse.level as usize] = Some(( + boathouse.id, + Boat::find_by_id(db, boathouse.boat_id as i32) + .await + .unwrap(), + )); + } + + ret + } + + pub async fn create(db: &SqlitePool, data: FormBoathouseToAdd) -> Result<(), String> { + sqlx::query!( + "INSERT INTO boathouse(boat_id, aisle, side, level) VALUES (?,?,?,?)", + data.boat_id, + data.aisle, + data.side, + data.level + ) + .execute(db) + .await + .map_err(|e| e.to_string())?; + Ok(()) + } + + pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option { + sqlx::query_as!(Self, "SELECT * FROM boathouse WHERE id like ?", id) + .fetch_one(db) + .await + .ok() + } + + pub async fn delete(&self, db: &SqlitePool) { + sqlx::query!("DELETE FROM boathouse WHERE id=?", self.id) + .execute(db) + .await + .unwrap(); //Okay, because we can only create a Boat of a valid id + } +} diff --git a/src/model/mod.rs b/src/model/mod.rs index 44f1df9..6423379 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -9,6 +9,7 @@ use self::{ pub mod boat; pub mod boatdamage; +pub mod boathouse; pub mod family; pub mod location; pub mod log; diff --git a/src/tera/board/boathouse.rs b/src/tera/board/boathouse.rs new file mode 100644 index 0000000..94119ba --- /dev/null +++ b/src/tera/board/boathouse.rs @@ -0,0 +1,85 @@ +use crate::model::{ + boat::Boat, + boathouse::Boathouse, + user::{AdminUser, UserWithRoles, VorstandUser}, +}; +use rocket::{ + form::Form, + get, post, + request::FlashMessage, + response::{Flash, Redirect}, + routes, FromForm, Route, State, +}; +use rocket_dyn_templates::{tera::Context, Template}; +use sqlx::SqlitePool; + +#[get("/boathouse")] +async fn index( + db: &State, + admin: VorstandUser, + flash: Option>, +) -> Template { + let mut context = Context::new(); + if let Some(msg) = flash { + context.insert("flash", &msg.into_inner()); + } + + let boats = Boat::all_for_boatshouse(db).await; + context.insert("boats", &boats); + + let boathouse = Boathouse::get(db).await; + context.insert("boathouse", &boathouse); + + context.insert( + "loggedin_user", + &UserWithRoles::from_user(admin.into(), db).await, + ); + + Template::render("board/boathouse", context.into_json()) +} + +#[derive(FromForm)] +pub struct FormBoathouseToAdd { + pub boat_id: i32, + pub aisle: String, + pub side: String, + pub level: i32, +} +#[post("/boathouse", data = "")] +async fn new<'r>( + db: &State, + data: Form, + _admin: AdminUser, +) -> Flash { + match Boathouse::create(db, data.into_inner()).await { + Ok(_) => Flash::success(Redirect::to("/board/boathouse"), "Boot hinzugefügt"), + Err(e) => Flash::error(Redirect::to("/board/boathouse"), e), + } +} + +#[get("/boathouse//delete")] +async fn delete(db: &State, _admin: AdminUser, boathouse_id: i32) -> Flash { + let boat = Boathouse::find_by_id(db, boathouse_id).await; + match boat { + Some(boat) => { + boat.delete(db).await; + Flash::success(Redirect::to("/board/boathouse"), "Bootsplatz gelöscht") + } + None => Flash::error(Redirect::to("/board/boathouse"), "Boatplace does not exist"), + } +} +//#[post("/boat/new", data = "")] +//async fn create( +// db: &State, +// data: Form>, +// _admin: AdminUser, +//) -> Flash { +// match Boat::create(db, data.into_inner()).await { +// Ok(_) => Flash::success(Redirect::to("/admin/boat"), "Boot hinzugefügt"), +// Err(e) => Flash::error(Redirect::to("/admin/boat"), e), +// } +//} + +pub fn routes() -> Vec { + routes![index, new, delete] +} diff --git a/src/tera/board/mod.rs b/src/tera/board/mod.rs new file mode 100644 index 0000000..4800fb8 --- /dev/null +++ b/src/tera/board/mod.rs @@ -0,0 +1,9 @@ +use rocket::Route; + +pub mod boathouse; + +pub fn routes() -> Vec { + let mut ret = Vec::new(); + ret.append(&mut boathouse::routes()); + ret +} diff --git a/src/tera/mod.rs b/src/tera/mod.rs index f4ec85a..ae52b07 100644 --- a/src/tera/mod.rs +++ b/src/tera/mod.rs @@ -21,6 +21,7 @@ use crate::model::user::{User, UserWithRoles}; pub(crate) mod admin; mod auth; +pub(crate) mod board; mod boatdamage; mod cox; mod ergo; @@ -89,6 +90,7 @@ pub fn config(rocket: Rocket) -> Rocket { .mount("/boatdamage", boatdamage::routes()) .mount("/cox", cox::routes()) .mount("/admin", admin::routes()) + .mount("/board", board::routes()) .mount("/", misc::routes()) .mount("/public", FileServer::from("static/")) .register("/", catchers![unauthorized_error, forbidden_error]) diff --git a/staging-diff.sql b/staging-diff.sql index e69de29..fe85d50 100644 --- a/staging-diff.sql +++ b/staging-diff.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS "boathouse" ( + "id" INTEGER PRIMARY KEY AUTOINCREMENT, + "boat_id" INTEGER NOT NULL REFERENCES boat(id), + "aisle" TEXT NOT NULL CHECK (aisle in ('water', 'middle', 'mountain')), + "side" TEXT NOT NULL CHECK(side IN ('mountain', 'water')), + "level" INTEGER NOT NULL CHECK(level BETWEEN 0 AND 3), + CONSTRAINT unq UNIQUE (aisle, side, level) -- only 1 boat allowed to rest at each space +); + diff --git a/templates/board/boathouse.html.tera b/templates/board/boathouse.html.tera new file mode 100644 index 0000000..c70cb17 --- /dev/null +++ b/templates/board/boathouse.html.tera @@ -0,0 +1,48 @@ +{% import "includes/macros" as macros %} +{% import "includes/forms/log" as log %} +{% import "includes/forms/boat" as boat %} +{% extends "base" %} +{% macro show_place(aisle_name, side_name, level) %} +
  • + {% set aisle = aisle_name ~ "-aisle" %} + {% set place = boathouse[aisle][side_name] %} + {% if place[level] %} + {{ place[level].1.name }} X + {% else %} +
    + {{ macros::select(label="Boot", data=boats, name="boat_id", id="boat_id", display=["name", " (","amount_seats", " x)"], wrapper_class="col-span-4") }} + + + + +
    + {% endif %} +
  • +{% endmacro show_place %} +{% macro show_side(aisle_name, side_name) %} +
    +
      + {{ self::show_place(aisle_name = aisle_name, side_name = side_name, level = 0) }} + {{ self::show_place(aisle_name = aisle_name, side_name = side_name, level = 1) }} + {{ self::show_place(aisle_name = aisle_name, side_name = side_name, level = 2) }} + {{ self::show_place(aisle_name = aisle_name, side_name = side_name, level = 3) }} +
    +
    +{% endmacro show_side %} +{% macro show_aisle(name) %} +
    + {{ self::show_side(aisle_name = name, side_name = "mountain") }} + {{ self::show_side(aisle_name = name, side_name = "water") }} +
    +{% endmacro show_aisle %} +{% block content %} + {% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %} +
    +

    Bootshaus

    + {{ self::show_aisle(name = "mountain") }} + {{ self::show_aisle(name = "middle") }} + {{ self::show_aisle(name = "water") }} +
    +{% endblock content %} diff --git a/templates/index.html.tera b/templates/index.html.tera index 06c03e7..95f7932 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -110,6 +110,9 @@
  • User
  • +
  • + Bootshaus +
  • -- 2.45.2 From 69edb63dddc9d8fee3c955e7848db74a39b0c7e2 Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 8 Mar 2024 13:46:00 +0100 Subject: [PATCH 2/5] styling --- templates/board/boathouse.html.tera | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/templates/board/boathouse.html.tera b/templates/board/boathouse.html.tera index c70cb17..a92fbd4 100644 --- a/templates/board/boathouse.html.tera +++ b/templates/board/boathouse.html.tera @@ -8,16 +8,21 @@ {% set place = boathouse[aisle][side_name] %} {% if place[level] %} {{ place[level].1.name }} X + {% elif boats | length > 0 %} +
    + Kein Boot +
    + {{ macros::select(label="Boot", data=boats, name="boat_id", id="boat_id", display=["name", " (","amount_seats", " x)"], wrapper_class="col-span-4") }} + + + + +
    +
    {% else %} -
    - {{ macros::select(label="Boot", data=boats, name="boat_id", id="boat_id", display=["name", " (","amount_seats", " x)"], wrapper_class="col-span-4") }} - - - - -
    + Kein Boot {% endif %} {% endmacro show_place %} -- 2.45.2 From 1e96c113a9b710726ce315b1d54f8286a1570d4d Mon Sep 17 00:00:00 2001 From: philipp Date: Sat, 9 Mar 2024 16:15:47 +0100 Subject: [PATCH 3/5] single location for flash message --- templates/admin/boat/index.html.tera | 1 - templates/admin/list/index.html.tera | 1 - templates/admin/list/result.html.tera | 1 - templates/admin/mail.html.tera | 1 - templates/admin/schnupper/index.html.tera | 1 - templates/admin/user/fees.html.tera | 1 - templates/admin/user/index.html.tera | 1 - templates/admin/user/scheckbuch.html.tera | 1 - templates/base.html.tera | 7 ++++++- templates/board/boathouse.html.tera | 13 +++++++------ templates/boatdamages.html.tera | 1 - templates/ergo.final.html.tera | 1 - templates/ergo.html.tera | 1 - templates/index.html.tera | 1 - templates/kiosk.html.tera | 5 ----- templates/log.html.tera | 3 --- templates/planned.html.tera | 1 - 17 files changed, 13 insertions(+), 28 deletions(-) diff --git a/templates/admin/boat/index.html.tera b/templates/admin/boat/index.html.tera index 33127e1..a79cbbd 100644 --- a/templates/admin/boat/index.html.tera +++ b/templates/admin/boat/index.html.tera @@ -2,7 +2,6 @@ {% import "includes/forms/boat" as boat %} {% extends "base" %} {% block content %} - {% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}

    Boats

    {{ boat::new() }} diff --git a/templates/admin/list/index.html.tera b/templates/admin/list/index.html.tera index 6fe0e31..a020b1f 100644 --- a/templates/admin/list/index.html.tera +++ b/templates/admin/list/index.html.tera @@ -1,7 +1,6 @@ {% import "includes/macros" as macros %} {% extends "base" %} {% block content %} - {% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}

    List

    diff --git a/templates/admin/list/result.html.tera b/templates/admin/list/result.html.tera index ae43182..a5863c2 100644 --- a/templates/admin/list/result.html.tera +++ b/templates/admin/list/result.html.tera @@ -1,7 +1,6 @@ {% import "includes/macros" as macros %} {% extends "base" %} {% block content %} - {% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}

    List - Result

      diff --git a/templates/admin/mail.html.tera b/templates/admin/mail.html.tera index 05a0b8f..1f80747 100644 --- a/templates/admin/mail.html.tera +++ b/templates/admin/mail.html.tera @@ -2,7 +2,6 @@ {% import "includes/forms/boat" as boat %} {% extends "base" %} {% block content %} - {% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}

      Mail

      diff --git a/templates/admin/schnupper/index.html.tera b/templates/admin/schnupper/index.html.tera index a3e9d5a..b2520b8 100644 --- a/templates/admin/schnupper/index.html.tera +++ b/templates/admin/schnupper/index.html.tera @@ -2,7 +2,6 @@ {% extends "base" %} {% block content %}
      - {% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}

      Schnupper Verwaltung

      - {% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}

      Gebühren

      diff --git a/templates/admin/user/index.html.tera b/templates/admin/user/index.html.tera index 932975d..068ce64 100644 --- a/templates/admin/user/index.html.tera +++ b/templates/admin/user/index.html.tera @@ -2,7 +2,6 @@ {% extends "base" %} {% block content %}
      - {% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}

      Users

      {% if allowed_to_edit %} - {% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}

      Scheckbücher

      diff --git a/templates/base.html.tera b/templates/base.html.tera index e9c6eb3..dbb98d5 100644 --- a/templates/base.html.tera +++ b/templates/base.html.tera @@ -25,7 +25,12 @@
      {% endif %} -
      +
      + {% if flash and loggedin_user %} +
      + {{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }} +
      + {% endif %} {% block content %} {% endblock content %}
      diff --git a/templates/board/boathouse.html.tera b/templates/board/boathouse.html.tera index a92fbd4..d07f520 100644 --- a/templates/board/boathouse.html.tera +++ b/templates/board/boathouse.html.tera @@ -37,17 +37,18 @@
      {% endmacro show_side %} {% macro show_aisle(name) %} -
      +
      {{ self::show_side(aisle_name = name, side_name = "mountain") }} {{ self::show_side(aisle_name = name, side_name = "water") }}
      {% endmacro show_aisle %} {% block content %} - {% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %} -
      +

      Bootshaus

      - {{ self::show_aisle(name = "mountain") }} - {{ self::show_aisle(name = "middle") }} - {{ self::show_aisle(name = "water") }} +
      + {{ self::show_aisle(name = "mountain") }} + {{ self::show_aisle(name = "middle") }} + {{ self::show_aisle(name = "water") }} +
      {% endblock content %} diff --git a/templates/boatdamages.html.tera b/templates/boatdamages.html.tera index d863c49..74908a4 100644 --- a/templates/boatdamages.html.tera +++ b/templates/boatdamages.html.tera @@ -4,7 +4,6 @@ {% block content %}

      Bootschäden

      - {% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3 mt-3") }}{% endif %}

      Neuen Schaden - {% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}

      Aktuelle Woche

      Dirty Thirty diff --git a/templates/ergo.html.tera b/templates/ergo.html.tera index 1487cdd..dff7d8a 100644 --- a/templates/ergo.html.tera +++ b/templates/ergo.html.tera @@ -3,7 +3,6 @@ {% block content %}

      Ergo Challenges

      - {% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="my-3") }}{% endif %}

    {% endmacro show_side %} -{% macro show_aisle(name) %} -
    +{% macro show_aisle(name, last=false) %} +
    {{ self::show_side(aisle_name = name, side_name = "mountain") }} {{ self::show_side(aisle_name = name, side_name = "water") }}
    @@ -45,10 +45,10 @@ {% block content %}

    Bootshaus

    -
    +
    {{ self::show_aisle(name = "mountain") }} {{ self::show_aisle(name = "middle") }} - {{ self::show_aisle(name = "water") }} + {{ self::show_aisle(name = "water", last = true) }}
    {% endblock content %} -- 2.45.2 From ad2f2241aaba13efab7119740fa120afca6bdefd Mon Sep 17 00:00:00 2001 From: philipp Date: Sat, 9 Mar 2024 16:56:32 +0100 Subject: [PATCH 5/5] show messages in kiosk mode --- templates/kiosk.html.tera | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/templates/kiosk.html.tera b/templates/kiosk.html.tera index f297fe6..149d157 100644 --- a/templates/kiosk.html.tera +++ b/templates/kiosk.html.tera @@ -4,6 +4,11 @@ {% block content %}

    Logbuch

    + {% if flash and not loggedin_user %} +
    + {{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }} +
    + {% endif %}