Compare commits

..

No commits in common. "ee73509fc7ae26c6da549f3d63e3c97eb9c6e8f5" and "5da1900ae8410d370ab97b086a3789154790d1ea" have entirely different histories.

25 changed files with 18 additions and 305 deletions

View File

@ -140,13 +140,3 @@ 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
);

View File

@ -181,41 +181,6 @@ ORDER BY amount_seats DESC
Self::boats_to_details(db, boats).await
}
pub async fn all_for_boatshouse(db: &SqlitePool) -> Vec<BoatWithDetails> {
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<BoatWithDetails> {
if user.has_role(db, "admin").await {
return Self::all(db).await;

View File

@ -1,90 +0,0 @@
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<Self> {
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
}
}

View File

@ -9,7 +9,6 @@ use self::{
pub mod boat;
pub mod boatdamage;
pub mod boathouse;
pub mod family;
pub mod location;
pub mod log;

View File

@ -1,85 +0,0 @@
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<SqlitePool>,
admin: VorstandUser,
flash: Option<FlashMessage<'_>>,
) -> 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 = "<data>")]
async fn new<'r>(
db: &State<SqlitePool>,
data: Form<FormBoathouseToAdd>,
_admin: AdminUser,
) -> Flash<Redirect> {
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/<boathouse_id>/delete")]
async fn delete(db: &State<SqlitePool>, _admin: AdminUser, boathouse_id: i32) -> Flash<Redirect> {
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 = "<data>")]
//async fn create(
// db: &State<SqlitePool>,
// data: Form<BoatToAdd<'_>>,
// _admin: AdminUser,
//) -> Flash<Redirect> {
// 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<Route> {
routes![index, new, delete]
}

View File

@ -1,9 +0,0 @@
use rocket::Route;
pub mod boathouse;
pub fn routes() -> Vec<Route> {
let mut ret = Vec::new();
ret.append(&mut boathouse::routes());
ret
}

View File

@ -21,7 +21,6 @@ use crate::model::user::{User, UserWithRoles};
pub(crate) mod admin;
mod auth;
pub(crate) mod board;
mod boatdamage;
mod cox;
mod ergo;
@ -90,7 +89,6 @@ pub fn config(rocket: Rocket<Build>) -> Rocket<Build> {
.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])

View File

@ -1,9 +0,0 @@
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
);

View File

@ -2,6 +2,7 @@
{% 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 %}
<div class="max-w-screen-lg w-full">
<h1 class="h1">Boats</h1>
{{ boat::new() }}

View File

@ -1,6 +1,7 @@
{% 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 %}
<div class="max-w-screen-lg w-full">
<h1 class="h1">List</h1>
<form action="/admin/list" method="post">

View File

@ -1,6 +1,7 @@
{% 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 %}
<div class="max-w-screen-lg w-full">
<h1 class="h1">List - Result</h1>
<ol>

View File

@ -2,6 +2,7 @@
{% 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 %}
<div class="max-w-screen-lg w-full">
<h1 class="h1">Mail</h1>
<form action="/admin/mail" method="post" enctype="multipart/form-data">

View File

@ -2,6 +2,7 @@
{% extends "base" %}
{% block content %}
<div class="max-w-screen-lg w-full">
{% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}
<h1 class="h1">Schnupper Verwaltung</h1>
<div class="grid gap-3">
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"

View File

@ -2,6 +2,7 @@
{% extends "base" %}
{% block content %}
<div class="max-w-screen-lg w-full bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5">
{% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}
<h1 class="h1">Gebühren</h1>
<!-- START filterBar -->
<div class="search-wrapper">

View File

@ -2,6 +2,7 @@
{% extends "base" %}
{% block content %}
<div class="max-w-screen-lg w-full">
{% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}
<h1 class="h1">Users</h1>
{% if allowed_to_edit %}
<form action="/admin/user/new"

View File

@ -3,6 +3,7 @@
{% extends "base" %}
{% block content %}
<div class="max-w-screen-lg w-full bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5">
{% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}
<h1 class="h1">Scheckbücher</h1>
<!-- START filterBar -->
<div class="search-wrapper">

View File

@ -25,12 +25,7 @@
</div>
</header>
{% endif %}
<div class="flex flex-wrap min-h-screen {% if not loggedin_user and not show_kiosk_header %} items-center dark:bg-primary-900 {% else %} items-start {% endif %} justify-center px-4 py-12 sm:px-6 lg:px-8">
{% if flash and loggedin_user %}
<div class="max-w-screen-lg w-full mb-3">
{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}
</div>
{% endif %}
<div class="flex min-h-screen {% if not loggedin_user and not show_kiosk_header %} items-center dark:bg-primary-900 {% else %} items-start {% endif %} justify-center px-4 py-12 sm:px-6 lg:px-8">
{% block content %}
{% endblock content %}
</div>

View File

@ -1,54 +0,0 @@
{% 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) %}
<li class="truncate p-2 flex relative w-full">
{% set aisle = aisle_name ~ "-aisle" %}
{% set place = boathouse[aisle][side_name] %}
{% if place[level] %}
{{ place[level].1.name }} <a class="btn btn-primary absolute end-0" href="/board/boathouse/{{ place[level].0 }}/delete">X</a>
{% elif boats | length > 0 %}
<details>
<summary>Kein Boot</summary>
<form action="/board/boathouse" method="post" class="grid gap-3">
{{ macros::select(label="Boot", data=boats, name="boat_id", id="boat_id", display=["name", " (","amount_seats", " x)"], wrapper_class="col-span-4") }}
<input type="hidden" name="aisle" value="{{ aisle_name }}" />
<input type="hidden" name="side" value="{{ side_name }}" />
<input type="hidden" name="level" value="{{ level }}" />
<input type="submit"
class="btn btn-primary w-full col-span-4"
value="Boot eintragen" />
</form>
</details>
{% else %}
Kein Boot
{% endif %}
</li>
{% endmacro show_place %}
{% macro show_side(aisle_name, side_name) %}
<div class="{{ side_name }}-side">
<ol>
{{ 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) }}
</ol>
</div>
{% endmacro show_side %}
{% macro show_aisle(name, last=false) %}
<div id="{{ name }}-aisle" class="grid grid-cols-2 gap-4 {% if not last %}md:border-r{% endif %}">
{{ self::show_side(aisle_name = name, side_name = "mountain") }}
{{ self::show_side(aisle_name = name, side_name = "water") }}
</div>
{% endmacro show_aisle %}
{% block content %}
<div class="max-w-screen-lg w-full dark:text-white">
<h1 class="h1">Bootshaus</h1>
<div class="grid md:grid-cols-3 gap-4">
{{ self::show_aisle(name = "mountain") }}
{{ self::show_aisle(name = "middle") }}
{{ self::show_aisle(name = "water", last = true) }}
</div>
</div>
{% endblock content %}

View File

@ -4,6 +4,7 @@
{% block content %}
<div class="max-w-screen-lg w-full">
<h1 class="h1">Bootschäden</h1>
{% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3 mt-3") }}{% endif %}
<h2 class="text-md font-bold tracking-wide bg-primary-900 mt-3 p-3 text-white flex justify-between items-center rounded-md">
Neuen Schaden
<a href="#"

View File

@ -2,6 +2,7 @@
{% extends "base" %}
{% block content %}
<div class="max-w-screen-lg w-full">
{% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}
<h1 class="h1">Aktuelle Woche</h1>
<details>
<summary>Dirty Thirty</summary>

View File

@ -3,6 +3,7 @@
{% block content %}
<div class="max-w-screen-lg w-full">
<h1 class="h1">Ergo Challenges</h1>
{% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="my-3") }}{% endif %}
<div class="grid gap-3">
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"
role="alert">

View File

@ -2,6 +2,7 @@
{% extends "base" %}
{% block content %}
<div class="max-w-screen-lg w-full">
{% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}
<h1 class="h1">Ruderassistent</h1>
<div class="grid gap-3">
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"
@ -109,9 +110,6 @@
<li class="py-1">
<a href="/admin/user" class="link-primary">User</a>
</li>
<li class="py-1">
<a href="/board/boathouse" class="link-primary">Bootshaus</a>
</li>
</ul>
</div>
</div>

View File

@ -4,7 +4,7 @@
{% block content %}
<div class="w-full">
<h1 class="h1">Logbuch</h1>
{% if flash and not loggedin_user %}
{% if flash %}
<div class="pt-3 max-w-lg m-auto">
{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}
</div>

View File

@ -4,6 +4,9 @@
{% block content %}
<div class="w-full">
<h1 class="h1">Logbuch</h1>
{% if flash %}
<div class="w-full">{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}</div>
{% endif %}
<div class="w-full grid md:grid-cols-5 gap-3 mt-5">
<div class="bg-white dark:bg-primary-900 rounded-md hidden md:block shadow">
<h2 class="h2">Boote</h2>

View File

@ -3,6 +3,7 @@
{% extends "base" %}
{% block content %}
<div class="max-w-screen-xl w-full grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
{% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %}
{% if "scheckbuch" in loggedin_user.roles %}
<div class="grid gap-3 sm:col-span-2 lg:col-span-3">
<div class="bg-white dark:bg-primary-900 text-black dark:text-white rounded-md block shadow mt-5"