diff --git a/.gitea/workflows/action.yml b/.gitea/workflows/action.yml index 3d95e0a..605a0e0 100644 --- a/.gitea/workflows/action.yml +++ b/.gitea/workflows/action.yml @@ -35,49 +35,6 @@ jobs: # path: frontend/playwright-report/ # retention-days: 30 - deploy-staging: - runs-on: ubuntu-latest - container: git.hofer.link/philipp/ci-images:rust-latest - needs: [test] - if: github.ref == 'refs/heads/staging' - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Run Test DB Script - run: ./test_db.sh - - - name: Cache Cargo dependencies - uses: Swatinem/rust-cache@v2 - - - name: Build - run: | - cargo build --release --target $CARGO_TARGET - strip target/$CARGO_TARGET/release/rot - cd frontend && npm install && npm run build - - - name: Deploy to Staging - run: | - mkdir -p ~/.ssh - ssh-keyscan -H $SSH_HOST >> ~/.ssh/known_hosts - echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa - - scp -C target/$CARGO_TARGET/release/rot $SSH_USER@$SSH_HOST:/home/rowing-staging/rot-updating - - scp -C staging-diff.sql $SSH_USER@$SSH_HOST:/home/rowing-staging/ - scp -C -r static $SSH_USER@$SSH_HOST:/home/rowing-staging/ - scp -C -r templates $SSH_USER@$SSH_HOST:/home/rowing-staging/ - scp -C -r svelte $SSH_USER@$SSH_HOST:/home/rowing-staging/ - ssh $SSH_USER@$SSH_HOST 'sudo systemctl stop rotstaging' - ssh $SSH_USER@$SSH_HOST 'rm /home/rowing-staging/db.sqlite && cp /home/rowing/db.sqlite /home/rowing-staging/db.sqlite && mkdir -p /home/rowing-staging/svelte/build && mkdir -p /home/rowing-staging/data-ergo/thirty && mkdir -p /home/rowing-staging/data-ergo/dozen && sqlite3 /home/rowing-staging/db.sqlite < /home/rowing-staging/staging-diff.sql' - ssh $SSH_USER@$SSH_HOST 'mv /home/rowing-staging/rot-updating /home/rowing-staging/rot' - ssh $SSH_USER@$SSH_HOST 'sudo systemctl start rotstaging' - env: - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} - SSH_HOST: ${{ secrets.SSH_HOST }} - SSH_USER: ${{ secrets.SSH_USER }} - deploy-main: runs-on: ubuntu-latest container: git.hofer.link/philipp/ci-images:rust-latest @@ -99,21 +56,21 @@ jobs: strip target/$CARGO_TARGET/release/rot cd frontend && npm install && npm run build - - name: Deploy to production + - name: Deploy Normannen run: | mkdir -p ~/.ssh ssh-keyscan -H $SSH_HOST >> ~/.ssh/known_hosts echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa chmod 600 ~/.ssh/id_rsa - scp -C target/$CARGO_TARGET/release/rot $SSH_USER@$SSH_HOST:/home/rowing/rot-updating - scp -C -r static $SSH_USER@$SSH_HOST:/home/rowing/ - scp -C -r templates $SSH_USER@$SSH_HOST:/home/rowing/ - scp -C -r svelte $SSH_USER@$SSH_HOST:/home/rowing/ - ssh $SSH_USER@$SSH_HOST 'mkdir -p /home/rowing/svelte/build && mkdir -p /home/rowing/data-ergo/thirty && mkdir -p /home/rowing/data-ergo/dozen' - ssh $SSH_USER@$SSH_HOST 'sudo systemctl stop rot' - ssh $SSH_USER@$SSH_HOST 'mv /home/rowing/rot-updating /home/rowing/rot' - ssh $SSH_USER@$SSH_HOST 'sudo systemctl start rot' + scp -C target/$CARGO_TARGET/release/rot $SSH_USER@$SSH_HOST:/home/normannen/rot-updating + scp -C -r static $SSH_USER@$SSH_HOST:/home/normannen/ + scp -C -r templates $SSH_USER@$SSH_HOST:/home/normannen/ + scp -C -r svelte $SSH_USER@$SSH_HOST:/home/normannen/ + ssh $SSH_USER@$SSH_HOST 'mkdir -p /home/normannen/svelte/build' + ssh $SSH_USER@$SSH_HOST 'sudo systemctl stop normannen' + ssh $SSH_USER@$SSH_HOST 'mv /home/normannen/rot-updating /home/normannen/rot' + ssh $SSH_USER@$SSH_HOST 'sudo systemctl start normannen' env: SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} SSH_HOST: ${{ secrets.SSH_HOST }} diff --git a/demo_db.sh b/demo_db.sh deleted file mode 100755 index 4337804..0000000 --- a/demo_db.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -rm -f db.sqlite -touch db.sqlite -sqlite3 db.sqlite < migration.sql -sqlite3 db.sqlite < seeds_demo.sql diff --git a/migration.sql b/migration.sql index 365aa72..28a85fa 100644 --- a/migration.sql +++ b/migration.sql @@ -4,27 +4,9 @@ CREATE TABLE IF NOT EXISTS "user" ( "pw" text, "deleted" boolean NOT NULL DEFAULT FALSE, "last_access" DATETIME, - "dob" text, - "weight" text, - "sex" text, - "dirty_thirty" text, - "dirty_dozen" text, - "member_since_date" text, - "birthdate" text, - "mail" text, - "nickname" text, - "notes" text, - "phone" text, - "address" text, - "family_id" INTEGER REFERENCES family(id), - "membership_pdf" BLOB, "user_token" TEXT NOT NULL DEFAULT (lower(hex(randomblob(16)))) ); -CREATE TABLE IF NOT EXISTS "family" ( - "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT -); - CREATE TABLE IF NOT EXISTS "role" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" text NOT NULL UNIQUE, @@ -87,74 +69,12 @@ CREATE TABLE IF NOT EXISTS "log" ( "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -CREATE TABLE IF NOT EXISTS "location" ( - "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, - "name" text NOT NULL UNIQUE -); - -CREATE TABLE IF NOT EXISTS "boat" ( - "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, - "name" text NOT NULL UNIQUE, - "amount_seats" integer NOT NULL, - "location_id" INTEGER NOT NULL REFERENCES location(id) DEFAULT 1, - "owner" INTEGER REFERENCES user(id), -- null: club is owner - "year_built" INTEGER, - "boatbuilder" TEXT, - "default_shipmaster_only_steering" boolean default false not null, - "convert_handoperated_possible" boolean default false not null, - "default_destination" text, - "skull" boolean default true NOT NULL, -- false => riemen - "external" boolean default false NOT NULL, -- false => owned by different club - "deleted" boolean NOT NULL DEFAULT FALSE -); - -CREATE TABLE IF NOT EXISTS "logbook_type" ( - "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, - "name" text NOT NULL UNIQUE -- e.g. 'Wanderfahrt', 'Regatta' -); - -CREATE TABLE IF NOT EXISTS "logbook" ( - "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, - "boat_id" INTEGER NOT NULL REFERENCES boat(id), - "shipmaster" INTEGER NOT NULL REFERENCES user(id), - "steering_person" INTEGER NOT NULL REFERENCES user(id), - "shipmaster_only_steering" boolean not null, - "departure" datetime not null, - "arrival" datetime, -- None -> ship is on water - "destination" text, - "distance_in_km" integer, - "comments" text, - "logtype" INTEGER REFERENCES logbook_type(id) -); - CREATE TABLE IF NOT EXISTS "rower" ( "logbook_id" INTEGER NOT NULL REFERENCES logbook(id) ON DELETE CASCADE, "rower_id" INTEGER NOT NULL REFERENCES user(id), CONSTRAINT unq UNIQUE (logbook_id, rower_id) ); -CREATE TABLE IF NOT EXISTS "boat_damage" ( - "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, - "boat_id" INTEGER NOT NULL REFERENCES boat(id), - "desc" text not null, - "user_id_created" INTEGER NOT NULL REFERENCES user(id), - "created_at" datetime not null default CURRENT_TIMESTAMP, - "user_id_fixed" INTEGER REFERENCES user(id), -- none: not fixed yet - "fixed_at" datetime, - "user_id_verified" INTEGER REFERENCES user(id), - "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 11), - CONSTRAINT unq UNIQUE (aisle, side, level) -- only 1 boat allowed to rest at each space -); - CREATE TABLE IF NOT EXISTS "notification" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "user_id" INTEGER NOT NULL REFERENCES user(id), @@ -166,18 +86,6 @@ CREATE TABLE IF NOT EXISTS "notification" ( "link" TEXT ); -CREATE TABLE IF NOT EXISTS "boat_reservation" ( - "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, - "boat_id" INTEGER NOT NULL REFERENCES boat(id), - "start_date" DATE NOT NULL, - "end_date" DATE NOT NULL, - "time_desc" TEXT NOT NULL, - "usage" TEXT NOT NULL, - "user_id_applicant" INTEGER NOT NULL REFERENCES user(id), - "user_id_confirmation" INTEGER REFERENCES user(id), - "created_at" datetime not null default CURRENT_TIMESTAMP -); - CREATE TABLE IF NOT EXISTS "waterlevel" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "day" DATE NOT NULL, @@ -197,44 +105,3 @@ CREATE TABLE IF NOT EXISTS "weather" ( "wind_gust" FLOAT NOT NULL, "rain_mm" FLOAT NOT NULL ); - -CREATE TABLE IF NOT EXISTS "trailer" ( - "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, - "name" text NOT NULL UNIQUE -); - -CREATE TABLE IF NOT EXISTS "trailer_reservation" ( - "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, - "trailer_id" INTEGER NOT NULL REFERENCES trailer(id), - "start_date" DATE NOT NULL, - "end_date" DATE NOT NULL, - "time_desc" TEXT NOT NULL, - "usage" TEXT NOT NULL, - "user_id_applicant" INTEGER NOT NULL REFERENCES user(id), - "user_id_confirmation" INTEGER REFERENCES user(id), - "created_at" datetime not null default CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS "distance" ( - "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, - "destination" text NOT NULL, - "distance_in_km" integer NOT NULL -); - - -CREATE TRIGGER IF NOT EXISTS prevent_multiple_roles_same_cluster -BEFORE INSERT ON user_role -BEGIN - SELECT CASE - WHEN EXISTS ( - SELECT 1 - FROM user_role ur - JOIN role r1 ON ur.role_id = r1.id - JOIN role r2 ON r1."cluster" = r2."cluster" - WHERE ur.user_id = NEW.user_id - AND r2.id = NEW.role_id - AND r1.id != NEW.role_id - ) - THEN RAISE(ABORT, 'User already has a role in this cluster') - END; -END; diff --git a/rot.service b/normannen.service similarity index 63% rename from rot.service rename to normannen.service index bdb2d95..e11f3a5 100644 --- a/rot.service +++ b/normannen.service @@ -1,15 +1,15 @@ [Unit] -Description=Rot +Description=Normannen [Service] User=root Group=root -WorkingDirectory=/home/rowing +WorkingDirectory=/home/normannen Environment="ROCKET_ENV=prod" Environment="ROCKET_ADDRESS=127.0.0.1" -Environment="ROCKET_PORT=8001" +Environment="ROCKET_PORT=9001" Environment="RUST_LOG=info" -ExecStart=/home/rowing/rot +ExecStart=/home/normannen/rot Restart=always RestartSec=10 diff --git a/rotstaging.service b/rotstaging.service deleted file mode 100644 index 5a76e86..0000000 --- a/rotstaging.service +++ /dev/null @@ -1,17 +0,0 @@ -[Unit] -Description=Rot Staging - -[Service] -User=root -Group=root -WorkingDirectory=/home/rowing-staging -Environment="ROCKET_ENV=prod" -Environment="ROCKET_ADDRESS=127.0.0.1" -Environment="ROCKET_PORT=7999" -Environment="ROCKET_LOG=info" -ExecStart=/home/rowing-staging/rot -Restart=always -RestartSec=10 - -[Install] -WantedBy=multi-user.target diff --git a/seeds.sql b/seeds.sql index 24129af..1714844 100644 --- a/seeds.sql +++ b/seeds.sql @@ -1,82 +1,22 @@ INSERT INTO "role" (name) VALUES ('admin'); INSERT INTO "role" (name) VALUES ('cox'); INSERT INTO "role" (name) VALUES ('scheckbuch'); -INSERT INTO "role" (name) VALUES ('tech'); -INSERT INTO "role" (name) VALUES ('Donau Linz'); INSERT INTO "role" (name) VALUES ('manage_events'); -INSERT INTO "role" (name) VALUES ('Rennrudern'); -INSERT INTO "role" (name) VALUES ('paid'); -INSERT INTO "role" (name) VALUES ('Vorstand'); -INSERT INTO "role" (name) VALUES ('Bootsführer'); -INSERT INTO "role" (name) VALUES ('schnupperant'); -INSERT INTO "role" (name) VALUES ('kassier'); -INSERT INTO "role" (name) VALUES ('schriftfuehrer'); -INSERT INTO "role" (name) VALUES ('no-einschreibgebuehr'); -INSERT INTO "role" (name) VALUES ('schnupper-betreuer'); -INSERT INTO "role" (name) VALUES ('allow_website_login'); -INSERT INTO "user" (name, pw) VALUES('admin', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$4P4NCw4Ukhv80/eQYTsarHhnw61JuL1KMx/L9dm82YM'); + +INSERT INTO "user" (name) VALUES('admin'); INSERT INTO "user_role" (user_id, role_id) VALUES(1,1); INSERT INTO "user_role" (user_id, role_id) VALUES(1,2); -INSERT INTO "user_role" (user_id, role_id) VALUES(1,5); -INSERT INTO "user_role" (user_id, role_id) VALUES(1,6); -INSERT INTO "user" (name, pw) VALUES('rower', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$jWKzDmI0jqT2dqINFt6/1NjVF4Dx15n07PL1ZMBmFsY'); -INSERT INTO "user_role" (user_id, role_id) VALUES(2,5); -INSERT INTO "user" (name, pw) VALUES('guest', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$GF6gizbI79Bh0zA9its8S0gram956v+YIV8w8VpwJnQ'); -INSERT INTO "user_role" (user_id, role_id) VALUES(3,5); +INSERT INTO "user_role" (user_id, role_id) VALUES(1,4); +INSERT INTO "user" (name) VALUES('steuerperson'); +INSERT INTO "user_role" (user_id, role_id) VALUES(2,2); +INSERT INTO "user" (name) VALUES('anfaenger'); INSERT INTO "user_role" (user_id, role_id) VALUES(3,3); -INSERT INTO "user" (name, pw) VALUES('cox', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$lnWzHx3DdqS9GQyWYel82kIotZuK2wk9EyfhPFtjNzs'); -INSERT INTO "user_role" (user_id, role_id) VALUES(4,5); -INSERT INTO "user_role" (user_id, role_id) VALUES(4,2); -INSERT INTO "user_role" (user_id, role_id) VALUES(4,8); -INSERT INTO "user" (name) VALUES('new'); -INSERT INTO "user_role" (user_id, role_id) VALUES(5,5); -INSERT INTO "user" (name, pw) VALUES('cox2', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$lnWzHx3DdqS9GQyWYel82kIotZuK2wk9EyfhPFtjNzs'); -INSERT INTO "user_role" (user_id, role_id) VALUES(6,5); -INSERT INTO "user_role" (user_id, role_id) VALUES(6,2); -INSERT INTO "user" (name, pw) VALUES('rower2', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$jWKzDmI0jqT2dqINFt6/1NjVF4Dx15n07PL1ZMBmFsY'); -INSERT INTO "user_role" (user_id, role_id) VALUES(7,5); -INSERT INTO "user" (name, pw) VALUES('teen', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$jWKzDmI0jqT2dqINFt6/1NjVF4Dx15n07PL1ZMBmFsY'); -INSERT INTO "user_role" (user_id, role_id) VALUES(8,5); -INSERT INTO "user_role" (user_id, role_id) VALUES(8,7); -INSERT INTO "user" (name, pw) VALUES('Vorstandsmitglied', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$jWKzDmI0jqT2dqINFt6/1NjVF4Dx15n07PL1ZMBmFsY'); -INSERT INTO "user_role" (user_id, role_id) VALUES(9,5); -INSERT INTO "user" (name, pw) VALUES('main', '$argon2id$v=19$m=19456,t=2,p=1$dS/X5/sPEKTj4Rzs/CuvzQ$4P4NCw4Ukhv80/eQYTsarHhnw61JuL1KMx/L9dm82YM'); -INSERT INTO "user_role" (user_id, role_id) VALUES(10,1); -INSERT INTO "user_role" (user_id, role_id) VALUES(10,2); -INSERT INTO "user_role" (user_id, role_id) VALUES(10,5); -INSERT INTO "user_role" (user_id, role_id) VALUES(10,6); -INSERT INTO "user_role" (user_id, role_id) VALUES(10,9); - -INSERT INTO "trip_details" (planned_starting_time, max_people, day, notes) VALUES('10:00', 2, date('now'), 'trip_details for a planned event'); -INSERT INTO "planned_event" (name, planned_amount_cox, trip_details_id) VALUES('test-planned-event', 2, 1); - -INSERT INTO "trip_details" (planned_starting_time, max_people, day, notes) VALUES('11:00', 1, date('now', '+1 day'), 'trip_details for trip from cox'); -INSERT INTO "trip" (cox_id, trip_details_id) VALUES(4, 2); - -INSERT INTO "trip_details" (planned_starting_time, max_people, day, notes) VALUES('10:00', 2, date('now'), 'same trip_details as id=1'); -INSERT INTO "trip_type" (name, desc, question, icon) VALUES ('Regatta', 'Regatta!', 'Kein normales Event. Das ist eine Regatta! Willst du wirklich teilnehmen?', '🏅'); -INSERT INTO "trip_type" (name, desc, question, icon) VALUES ('Lange Ausfahrt', 'Lange Ausfahrt!', 'Das ist eine lange Ausfahrt! Willst du wirklich teilnehmen?', '💪'); -INSERT INTO "trip_type" (name, desc, question, icon) VALUES ('Wanderfahrt', 'Wanderfahrt!', 'Kein normales Event. Das ist eine Wanderfahrt! Bitte überprüfe ob du alle Anforderungen erfüllst. Willst du wirklich teilnehmen?', '⛱'); -INSERT INTO "location" (name) VALUES ('Linz'); -INSERT INTO "location" (name) VALUES ('Ottensheim'); -INSERT INTO "boat" (name, amount_seats, location_id) VALUES ('Haichenbach', 1, 1); -INSERT INTO "boat" (name, amount_seats, location_id, owner) VALUES ('private_boat_from_rower', 1, 1, 2); -INSERT INTO "boat" (name, amount_seats, location_id) VALUES ('Joe', 2, 1); -INSERT INTO "boat" (name, amount_seats, location_id) VALUES ('Kaputtes Boot :-(', 7, 1); -INSERT INTO "boat" (name, amount_seats, location_id) VALUES ('Sehr kaputtes Boot :-((', 7, 1); -INSERT INTO "boat" (name, amount_seats, location_id) VALUES ('Ottensheim Boot', 7, 2); -INSERT INTO "boat" (name, amount_seats, location_id, owner) VALUES ('second_private_boat_from_rower', 1, 1, 2); -INSERT INTO "boat" (name, amount_seats, location_id, default_shipmaster_only_steering) VALUES ('cox_only_steering_boat', 3, 1, true); -INSERT INTO "logbook_type" (name) VALUES ('Wanderfahrt'); -INSERT INTO "logbook_type" (name) VALUES ('Regatta'); -INSERT INTO "logbook" (boat_id, shipmaster,steering_person, shipmaster_only_steering, departure) VALUES (2, 2, 2, false, strftime('%Y', 'now') || '-12-24 10:00'); -INSERT INTO "logbook" (boat_id, shipmaster, steering_person, shipmaster_only_steering, departure, arrival, destination, distance_in_km) VALUES (1, 4, 4, false, strftime('%Y', 'now') || '-12-24 10:00', strftime('%Y', 'now') || '-12-24 15:00', 'Ottensheim', 25); -INSERT INTO "logbook" (boat_id, shipmaster, steering_person, shipmaster_only_steering, departure, arrival, destination, distance_in_km) VALUES (3, 4, 4, false, strftime('%Y', 'now') || '-12-24 10:00', strftime('%Y', 'now') || '-12-24 11:30', 'Ottensheim + Regattastrecke', 29); -INSERT INTO "rower" (logbook_id, rower_id) VALUES(3,3); -INSERT INTO "boat_damage" (boat_id, desc, user_id_created, created_at) VALUES(4,'Dolle bei Position 2 fehlt', 5, '2142-12-24 15:02'); -INSERT INTO "boat_damage" (boat_id, desc, user_id_created, created_at, lock_boat) VALUES(5, 'TOHT', 5, '2142-12-24 15:02', 1); -INSERT INTO "notification" (user_id, message, category) VALUES (1, 'This is a test notification', 'test-cat'); -INSERT INTO "trailer" (name) VALUES('Großer Hänger'); -INSERT INTO "trailer" (name) VALUES('Kleiner Hänger'); -insert into distance(destination, distance_in_km) values('Ottensheim', 25); +INSERT INTO trip_type VALUES(1,'Regatta','Regatta!','Kein normales Event. Das ist eine Regatta! Willst du wirklich teilnehmen?','🏅'); +INSERT INTO trip_type VALUES(2,'Lange Ausfahrt','Lange Ausfahrt!','Das ist eine lange Ausfahrt! Willst du wirklich teilnehmen?','💪'); +INSERT INTO trip_type VALUES(3,'Wanderfahrt','Wanderfahrt!','Kein normales Event. Das ist eine Wanderfahrt! Bitte überprüfe ob du alle Anforderungen erfüllst. Willst du wirklich teilnehmen?','⛱'); +INSERT INTO trip_type VALUES(4,'Ergo','Ergo-Fahrt im Bootshaus','Das ist keine Fahrt auf der Donau, sondern eine tolle Ergo-Einheit im Bootshaus. Willst du teilnehmen?','🏠'); +INSERT INTO trip_type VALUES(5,'Ruderbecken','Ruderbecken-Training','Das ist ein Training im Ruderbecken. Willst du teilnehmen?','🏠'); +INSERT INTO trip_type VALUES(6,'Theorie','Theorie','Das ist keine Ausfahrt. Stattdessen wirst du mit zusätzlichem Wissen belohnt. Willst du teilnehmen?','📚'); +INSERT INTO trip_type VALUES(7,'Arbeitspartie','Arbeitspartie','Keine Ausfahrt, sondern eine Arbeitspartie im Bootshaus. Willst du teilnehmen?','🧹'); +INSERT INTO trip_type VALUES(8,'Einer-Ausfahrt','1x Ausfahrt','Das ist eine Ausfahrt in Einer-Booten (1x). Willst du teilnehmen?','1️⃣'); diff --git a/src/lib.rs b/src/lib.rs index 61615f9..25b1b07 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,16 +11,6 @@ pub mod rest; pub mod scheduled; pub(crate) const AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD: i64 = 10; -pub(crate) const RENNRUDERBEITRAG: i64 = 11000; -pub(crate) const BOAT_STORAGE: i64 = 4500; -pub(crate) const FAMILY_TWO: i64 = 30000; -pub(crate) const FAMILY_THREE_OR_MORE: i64 = 35000; -pub(crate) const STUDENT_OR_PUPIL: i64 = 8000; -pub(crate) const REGULAR: i64 = 22000; -pub(crate) const UNTERSTUETZEND: i64 = 2500; -pub(crate) const FOERDERND: i64 = 8500; -pub(crate) const SCHECKBUCH: i64 = 3000; -pub(crate) const EINSCHREIBGEBUEHR: i64 = 3000; #[cfg(test)] #[macro_export] diff --git a/src/model/boat.rs b/src/model/boat.rs deleted file mode 100644 index 4776cf9..0000000 --- a/src/model/boat.rs +++ /dev/null @@ -1,651 +0,0 @@ -use std::ops::DerefMut; - -use chrono::NaiveDateTime; -use itertools::Itertools; -use rocket::serde::{Deserialize, Serialize}; -use rocket::FromForm; -use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; - -use crate::model::boathouse::Boathouse; - -use super::location::Location; -use super::user::User; - -#[derive(FromRow, Debug, Serialize, Deserialize, Eq, Hash, PartialEq, Clone)] -pub struct Boat { - pub id: i64, - pub name: String, - pub amount_seats: i64, - pub location_id: i64, - pub owner: Option, - pub year_built: Option, - pub boatbuilder: Option, - pub default_destination: Option, - #[serde(default = "bool::default")] - pub convert_handoperated_possible: bool, - #[serde(default = "bool::default")] - pub default_shipmaster_only_steering: bool, - #[serde(default = "bool::default")] - skull: bool, - #[serde(default = "bool::default")] - pub external: bool, - pub deleted: bool, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "lowercase")] -pub enum BoatDamage { - None, - Light, - Locked, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct BoatWithDetails { - #[serde(flatten)] - pub(crate) boat: Boat, - damage: BoatDamage, - on_water: bool, - reserved_today: bool, - cat: String, -} - -#[derive(FromForm)] -pub struct BoatToAdd<'r> { - pub name: &'r str, - pub amount_seats: i64, - pub year_built: Option, - pub boatbuilder: Option<&'r str>, - pub default_shipmaster_only_steering: bool, - pub convert_handoperated_possible: bool, - pub default_destination: Option<&'r str>, - pub skull: bool, - pub external: bool, - pub location_id: Option, - pub owner: Option, -} - -#[derive(FromForm)] -pub struct BoatToUpdate<'r> { - pub name: &'r str, - pub amount_seats: i64, - pub year_built: Option, - pub boatbuilder: Option<&'r str>, - pub default_shipmaster_only_steering: bool, - pub default_destination: Option<&'r str>, - pub skull: bool, - pub convert_handoperated_possible: bool, - pub external: bool, - pub location_id: i64, - pub owner: Option, -} - -impl Boat { - pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option { - sqlx::query_as!(Self, "SELECT id, name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, convert_handoperated_possible, default_destination, skull, external, deleted FROM boat WHERE id like ?", id) - .fetch_one(db) - .await - .ok() - } - pub async fn find_by_id_tx(db: &mut Transaction<'_, Sqlite>, id: i32) -> Option { - sqlx::query_as!(Self, "SELECT id, name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, convert_handoperated_possible, default_destination, skull, external, deleted FROM boat WHERE id like ?", id) - .fetch_one(db.deref_mut()) - .await - .ok() - } - - pub async fn find_by_name(db: &SqlitePool, name: String) -> Option { - sqlx::query_as!(Self, "SELECT id, name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, convert_handoperated_possible, default_destination, skull, external, deleted FROM boat WHERE name like ?", name) - .fetch_one(db) - .await - .ok() - } - - pub async fn shipmaster_allowed(&self, db: &SqlitePool, user: &User) -> bool { - if let Some(owner_id) = self.owner { - return owner_id == user.id; - } - - if user.has_role(db, "Rennrudern").await { - let ottensheim = Location::find_by_name(db, "Ottensheim".into()) - .await - .unwrap(); - if self.location_id == ottensheim.id { - return true; - } - } - - if self.amount_seats == 1 { - return true; - } - - user.allowed_to_steer(db).await - } - - pub async fn shipmaster_allowed_tx( - &self, - db: &mut Transaction<'_, Sqlite>, - user: &User, - ) -> bool { - if let Some(owner_id) = self.owner { - return owner_id == user.id; - } - - if self.amount_seats == 1 { - return true; - } - - user.allowed_to_steer_tx(db).await - } - - pub async fn is_locked(&self, db: &SqlitePool) -> bool { - sqlx::query!("SELECT * FROM boat_damage WHERE boat_id=? AND lock_boat=true AND user_id_verified is null", self.id).fetch_optional(db).await.unwrap().is_some() - } - - pub async fn has_minor_damage(&self, db: &SqlitePool) -> bool { - sqlx::query!("SELECT * FROM boat_damage WHERE boat_id=? AND lock_boat=false AND user_id_verified is null", self.id).fetch_optional(db).await.unwrap().is_some() - } - - pub async fn reserved_today(&self, db: &SqlitePool) -> bool { - sqlx::query!( - "SELECT * -FROM boat_reservation -WHERE boat_id =? -AND date('now') BETWEEN start_date AND end_date;", - self.id - ) - .fetch_optional(db) - .await - .unwrap() - .is_some() - } - - pub async fn on_water(&self, db: &SqlitePool) -> bool { - sqlx::query!( - "SELECT * FROM logbook WHERE boat_id=? AND arrival is null", - self.id - ) - .fetch_optional(db) - .await - .unwrap() - .is_some() - } - - pub(crate) fn cat(&self) -> String { - if self.external { - "Vereinsfremde Boote".to_string() - } else if self.default_shipmaster_only_steering { - format!("{}+", self.amount_seats - 1) - } else { - format!("{}x", self.amount_seats) - } - } - - async fn boats_to_details(db: &SqlitePool, boats: Vec) -> Vec { - let mut res = Vec::new(); - for boat in boats { - let mut damage = BoatDamage::None; - if boat.has_minor_damage(db).await { - damage = BoatDamage::Light; - } - if boat.is_locked(db).await { - damage = BoatDamage::Locked; - } - let cat = boat.cat(); - - res.push(BoatWithDetails { - damage, - on_water: boat.on_water(db).await, - reserved_today: boat.reserved_today(db).await, - boat, - cat, - }); - } - res - } - - pub async fn all(db: &SqlitePool) -> Vec { - let boats = sqlx::query_as!( - Boat, - " -SELECT id, name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, default_destination, skull, external, deleted, convert_handoperated_possible -FROM boat -WHERE deleted=false -ORDER BY amount_seats DESC - " - ) - .fetch_all(db) - .await - .unwrap(); //TODO: fixme - - 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, - b.deleted, - b.convert_handoperated_possible -FROM - boat AS b -WHERE - b.external = false - AND b.location_id = (SELECT id FROM location WHERE name = 'Linz') - AND b.deleted = false -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; - } - let mut boats = if user.allowed_to_steer(db).await { - sqlx::query_as!( - Boat, - " -SELECT id, name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, default_destination, skull, external, deleted, convert_handoperated_possible -FROM boat -WHERE (owner is null or owner = ?) AND deleted = 0 -ORDER BY amount_seats DESC - ", - user.id - ) - .fetch_all(db) - .await - .unwrap() //TODO: fixme - } else { - sqlx::query_as!( - Boat, - " -SELECT id, name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, default_destination, skull, external, deleted, convert_handoperated_possible -FROM boat -WHERE (owner = ? OR (owner is null and amount_seats = 1)) AND deleted = 0 -ORDER BY amount_seats DESC - ", - user.id - ) - .fetch_all(db) - .await - .unwrap() //TODO: fixme - }; - - if user.has_role(db, "Rennrudern").await { - let ottensheim = Location::find_by_name(db, "Ottensheim".into()) - .await - .unwrap(); - let boats_in_ottensheim = sqlx::query_as!( - Boat, - "SELECT id, name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, default_destination, skull, external, deleted, convert_handoperated_possible -FROM boat -WHERE (owner is null and location_id = ?) AND deleted = 0 -ORDER BY amount_seats DESC - ",ottensheim.id) - .fetch_all(db) - .await - .unwrap(); //TODO: fixme - boats.extend(boats_in_ottensheim.into_iter()); - } - let boats = boats.into_iter().unique().collect(); - - Self::boats_to_details(db, boats).await - } - - pub async fn all_at_location(db: &SqlitePool, location: String) -> Vec { - let boats = sqlx::query_as!( - Boat, - " -SELECT boat.id, boat.name, amount_seats, location_id, owner, year_built, boatbuilder, default_shipmaster_only_steering, default_destination, skull, external, deleted, convert_handoperated_possible -FROM boat -INNER JOIN location ON boat.location_id = location.id -WHERE location.name=? AND deleted = 0 -ORDER BY amount_seats DESC - ", - location - ) - .fetch_all(db) - .await - .unwrap(); //TODO: fixme - - Self::boats_to_details(db, boats).await - } - - pub async fn create(db: &SqlitePool, boat: BoatToAdd<'_>) -> Result<(), String> { - sqlx::query!( - "INSERT INTO boat(name, amount_seats, year_built, boatbuilder, default_shipmaster_only_steering, default_destination, skull, external, location_id, owner, convert_handoperated_possible) VALUES (?,?,?,?,?,?,?,?,?,?,?)", - boat.name, - boat.amount_seats, - boat.year_built, - boat.boatbuilder, - boat.default_shipmaster_only_steering, - boat.default_destination, - boat.skull, - boat.external, - boat.location_id, - boat.owner, - boat.convert_handoperated_possible - ) - .execute(db) - .await.map_err(|e| e.to_string())?; - Ok(()) - } - - pub async fn update(&self, db: &SqlitePool, boat: BoatToUpdate<'_>) -> Result<(), String> { - sqlx::query!( - "UPDATE boat SET name=?, amount_seats=?, year_built=?, boatbuilder=?, default_shipmaster_only_steering=?, default_destination=?, skull=?, external=?, location_id=?, owner=?, convert_handoperated_possible=? WHERE id=?", - boat.name, - boat.amount_seats, - boat.year_built, - boat.boatbuilder, - boat.default_shipmaster_only_steering, - boat.default_destination, - boat.skull, - boat.external, - boat.location_id, - boat.owner, - boat.convert_handoperated_possible, - self.id - ) - .execute(db) - .await.map_err(|e| e.to_string())?; - Ok(()) - } - - pub async fn owner(&self, db: &SqlitePool) -> Option { - if let Some(owner_id) = self.owner { - Some(User::find_by_id(db, owner_id as i32).await.unwrap()) - } else { - None - } - } - - pub async fn delete(&self, db: &SqlitePool) { - sqlx::query!("UPDATE boat SET deleted=1 WHERE id=?", self.id) - .execute(db) - .await - .unwrap(); //Okay, because we can only create a Boat of a valid id - } - - pub async fn boathouse(&self, db: &SqlitePool) -> Option { - sqlx::query_as!( - Boathouse, - "SELECT * FROM boathouse WHERE boat_id like ?", - self.id - ) - .fetch_one(db) - .await - .ok() - } - - pub async fn on_water_between( - &self, - db: &mut Transaction<'_, Sqlite>, - dep: NaiveDateTime, - arr: NaiveDateTime, - ) -> bool { - let dep = dep.format("%Y-%m-%dT%H:%M").to_string(); - let arr = arr.format("%Y-%m-%dT%H:%M").to_string(); - - sqlx::query!( - "SELECT COUNT(*) AS overlap_count -FROM logbook -WHERE boat_id = ? - AND ( - (departure <= ? AND arrival >= ?) -- Existing entry covers the entire new period - OR (departure >= ? AND departure < ?) -- Existing entry starts during the new period - OR (arrival > ? AND arrival <= ?) -- Existing entry ends during the new period - );", - self.id, - arr, - arr, - dep, - dep, - dep, - arr - ) - .fetch_one(db.deref_mut()) - .await - .unwrap() - .overlap_count - > 0 - } -} - -#[cfg(test)] -mod test { - use crate::{ - model::boat::{Boat, BoatToAdd}, - testdb, - }; - - use sqlx::SqlitePool; - - use super::BoatToUpdate; - - #[sqlx::test] - fn test_find_correct_id() { - let pool = testdb!(); - let boat = Boat::find_by_id(&pool, 1).await.unwrap(); - assert_eq!(boat.id, 1); - } - - #[sqlx::test] - fn test_find_wrong_id() { - let pool = testdb!(); - let boat = Boat::find_by_id(&pool, 1337).await; - assert!(boat.is_none()); - } - - #[sqlx::test] - fn test_all() { - let pool = testdb!(); - let res = Boat::all(&pool).await; - assert!(res.len() > 3); - } - - #[sqlx::test] - fn test_succ_create() { - let pool = testdb!(); - - assert_eq!( - Boat::create( - &pool, - BoatToAdd { - name: "new-boat-name".into(), - amount_seats: 42, - year_built: None, - boatbuilder: "Best Boatbuilder".into(), - default_shipmaster_only_steering: true, - convert_handoperated_possible: false, - skull: true, - external: false, - location_id: Some(1), - owner: None, - default_destination: None - } - ) - .await, - Ok(()) - ); - } - - #[sqlx::test] - fn test_duplicate_name_create() { - let pool = testdb!(); - - assert_eq!( - Boat::create( - &pool, - BoatToAdd { - name: "Haichenbach".into(), - amount_seats: 42, - year_built: None, - boatbuilder: "Best Boatbuilder".into(), - default_shipmaster_only_steering: true, - convert_handoperated_possible: false, - skull: true, - external: false, - location_id: Some(1), - owner: None, - default_destination: None - } - ) - .await, - Err( - "error returned from database: (code: 2067) UNIQUE constraint failed: boat.name" - .into() - ) - ); - } - - #[sqlx::test] - fn test_is_locked() { - let pool = testdb!(); - let res = Boat::find_by_id(&pool, 5) - .await - .unwrap() - .is_locked(&pool) - .await; - assert_eq!(res, true); - } - - #[sqlx::test] - fn test_is_not_locked() { - let pool = testdb!(); - let res = Boat::find_by_id(&pool, 4) - .await - .unwrap() - .is_locked(&pool) - .await; - assert_eq!(res, false); - } - - #[sqlx::test] - fn test_is_not_locked_no_damage() { - let pool = testdb!(); - let res = Boat::find_by_id(&pool, 3) - .await - .unwrap() - .is_locked(&pool) - .await; - assert_eq!(res, false); - } - - #[sqlx::test] - fn test_has_minor_damage() { - let pool = testdb!(); - let res = Boat::find_by_id(&pool, 4) - .await - .unwrap() - .has_minor_damage(&pool) - .await; - assert_eq!(res, true); - } - - #[sqlx::test] - fn test_has_no_minor_damage() { - let pool = testdb!(); - let res = Boat::find_by_id(&pool, 5) - .await - .unwrap() - .has_minor_damage(&pool) - .await; - assert_eq!(res, false); - } - - #[sqlx::test] - fn test_on_water() { - let pool = testdb!(); - let res = Boat::find_by_id(&pool, 2) - .await - .unwrap() - .on_water(&pool) - .await; - assert_eq!(res, true); - } - - #[sqlx::test] - fn test_not_on_water() { - let pool = testdb!(); - let res = Boat::find_by_id(&pool, 4) - .await - .unwrap() - .on_water(&pool) - .await; - assert_eq!(res, false); - } - - #[sqlx::test] - fn test_succ_update() { - let pool = testdb!(); - let boat = Boat::find_by_id(&pool, 1).await.unwrap(); - let update = BoatToUpdate { - name: "my-new-boat-name", - amount_seats: 3, - year_built: None, - boatbuilder: None, - default_shipmaster_only_steering: false, - convert_handoperated_possible: false, - skull: true, - external: false, - location_id: 1, - owner: None, - default_destination: None, - }; - - boat.update(&pool, update).await.unwrap(); - - let boat = Boat::find_by_id(&pool, 1).await.unwrap(); - assert_eq!(boat.name, "my-new-boat-name"); - } - - #[sqlx::test] - fn test_failed_update() { - let pool = testdb!(); - let boat = Boat::find_by_id(&pool, 1).await.unwrap(); - let update = BoatToUpdate { - name: "my-new-boat-name", - amount_seats: 3, - year_built: None, - boatbuilder: None, - default_shipmaster_only_steering: false, - convert_handoperated_possible: false, - skull: true, - external: false, - location_id: 999, - owner: None, - default_destination: None, - }; - - match boat.update(&pool, update).await { - Ok(_) => panic!("Update with invalid location should not succeed"), - Err(e) => assert_eq!( - e, - "error returned from database: (code: 787) FOREIGN KEY constraint failed" - ), - }; - - let boat = Boat::find_by_id(&pool, 1).await.unwrap(); - assert_eq!(boat.name, "Haichenbach"); - } -} diff --git a/src/model/boatdamage.rs b/src/model/boatdamage.rs deleted file mode 100644 index bcdb377..0000000 --- a/src/model/boatdamage.rs +++ /dev/null @@ -1,350 +0,0 @@ -use crate::model::{boat::Boat, user::User}; -use chrono::NaiveDateTime; -use rocket::serde::{Deserialize, Serialize}; -use rocket::FromForm; -use sqlx::{FromRow, SqlitePool}; - -use super::log::Log; -use super::notification::Notification; -use super::role::Role; - -#[derive(FromRow, Debug, Serialize, Deserialize)] -pub struct BoatDamage { - pub id: i64, - pub boat_id: i64, - pub desc: String, - pub user_id_created: i64, - pub created_at: NaiveDateTime, - pub user_id_fixed: Option, - pub fixed_at: Option, - pub user_id_verified: Option, - pub verified_at: Option, - pub lock_boat: bool, -} - -#[derive(FromRow, Debug, Serialize, Deserialize)] -pub struct BoatDamageWithDetails { - #[serde(flatten)] - boat_damage: BoatDamage, - user_created: User, - user_fixed: Option, - user_verified: Option, - boat: Boat, - verified: bool, -} - -#[derive(Debug)] -pub struct BoatDamageToAdd<'r> { - pub boat_id: i64, - pub desc: &'r str, - pub user_id_created: i32, - pub lock_boat: bool, -} - -#[derive(FromForm, Debug)] -pub struct BoatDamageFixed<'r> { - pub desc: &'r str, - pub user_id_fixed: i32, -} - -#[derive(FromForm, Debug)] -pub struct BoatDamageVerified<'r> { - pub desc: &'r str, - pub user_id_verified: i32, -} - -impl BoatDamage { - pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option { - sqlx::query_as!( - Self, - "SELECT id, boat_id, desc, user_id_created, created_at, user_id_fixed, fixed_at, user_id_verified, verified_at, lock_boat - FROM boat_damage - WHERE id like ?", - id - ) - .fetch_one(db) - .await - .ok() - } - - pub async fn all(db: &SqlitePool) -> Vec { - let boatdamages = sqlx::query_as!( - BoatDamage, - " -SELECT id, boat_id, desc, user_id_created, created_at, user_id_fixed, fixed_at, user_id_verified, verified_at, lock_boat -FROM boat_damage -WHERE ( - verified_at IS NULL - OR verified_at >= datetime('now', '-30 days') - ) -ORDER BY created_at DESC - " - ) - .fetch_all(db) - .await - .unwrap(); //TODO: fixme - - let mut res = Vec::new(); - for boat_damage in boatdamages { - let user_fixed = match boat_damage.user_id_fixed { - Some(id) => { - let user = User::find_by_id(db, id as i32).await; - Some(user.unwrap()) - } - None => None, - }; - let user_verified = match boat_damage.user_id_verified { - Some(id) => { - let user = User::find_by_id(db, id as i32).await; - Some(user.unwrap()) - } - None => None, - }; - - res.push(BoatDamageWithDetails { - boat: Boat::find_by_id(db, boat_damage.boat_id as i32) - .await - .unwrap(), - user_created: User::find_by_id(db, boat_damage.user_id_created as i32) - .await - .unwrap(), - user_fixed, - verified: user_verified.is_some(), - user_verified, - boat_damage, - }); - } - res - } - - pub async fn create(db: &SqlitePool, boatdamage: BoatDamageToAdd<'_>) -> Result<(), String> { - Log::create(db, format!("New boat damage: {boatdamage:?}")).await; - let Some(boat) = Boat::find_by_id(db, boatdamage.boat_id as i32).await else { - return Err("Boot gibt's ned".into()); - }; - let was_unusable_before = boat.is_locked(db).await; - - sqlx::query!( - "INSERT INTO boat_damage(boat_id, desc, user_id_created, lock_boat) VALUES (?,?,?, ?)", - boatdamage.boat_id, - boatdamage.desc, - boatdamage.user_id_created, - boatdamage.lock_boat - ) - .execute(db) - .await - .map_err(|e| e.to_string())?; - - if !was_unusable_before && boat.is_locked(db).await { - Notification::create_for_steering_people(db, &format!("Liebe Steuerberechtigte, bitte beachten, dass {} bis auf weiteres aufgrund von Reparaturarbeiten gesperrt ist.", boat.name), "Boot gesperrt", None, None).await; - } - - let technicals = - User::all_with_role(db, &Role::find_by_name(db, "tech").await.unwrap()).await; - for technical in technicals { - if technical.id as i32 != boatdamage.user_id_created { - Notification::create( - db, - &technical, - &format!( - "{} hat einen neuen Bootschaden für Boot '{}' angelegt: {}", - User::find_by_id(db, boatdamage.user_id_created) - .await - .unwrap() - .name, - boat.name, - boatdamage.desc - ), - "Neuer Bootsschaden angelegt", - None, - None, - ) - .await; - } - } - - Notification::create( - db, - &User::find_by_id(db, boatdamage.user_id_created) - .await - .unwrap(), - &format!( - "Du hat einen neuen Bootschaden für Boot '{}' angelegt: {}", - Boat::find_by_id(db, boatdamage.boat_id as i32) - .await - .unwrap() - .name, - boatdamage.desc - ), - "Neuer Bootsschaden angelegt", - None, - None, - ) - .await; - - Ok(()) - } - - pub async fn fixed( - &self, - db: &SqlitePool, - boat_damage: BoatDamageFixed<'_>, - ) -> Result<(), String> { - Log::create(db, format!("Fixed boat damage: {boat_damage:?}")).await; - - let boat = Boat::find_by_id(db, self.boat_id as i32).await.unwrap(); - - sqlx::query!( - "UPDATE boat_damage SET desc=?, user_id_fixed=?, fixed_at=CURRENT_TIMESTAMP WHERE id=?", - boat_damage.desc, - boat_damage.user_id_fixed, - self.id - ) - .execute(db) - .await - .map_err(|e| e.to_string())?; - - let user = User::find_by_id(db, boat_damage.user_id_fixed) - .await - .unwrap(); - if user.has_role(db, "tech").await { - return self - .verified( - db, - BoatDamageVerified { - desc: boat_damage.desc, - user_id_verified: user.id as i32, - }, - ) - .await; - } - - let technicals = - User::all_with_role(db, &Role::find_by_name(db, "tech").await.unwrap()).await; - for technical in technicals { - if technical.id as i32 != boat_damage.user_id_fixed { - Notification::create( - db, - &technical, - &format!( - "{} hat den Bootschaden '{}' beim Boot '{}' repariert. Könntest du das bei Gelegenheit verifizieren?", - User::find_by_id(db, boat_damage.user_id_fixed) - .await - .unwrap() - .name, - boat_damage.desc, - boat.name, - ), - "Bootsschaden repariert", - None,None - ) - .await; - } - } - - if boat_damage.user_id_fixed != self.user_id_created as i32 { - let user_fixed = User::find_by_id(db, boat_damage.user_id_fixed) - .await - .unwrap(); - let user_created = User::find_by_id(db, self.user_id_created as i32) - .await - .unwrap(); - - // Boatdamage is also directly verified, if a tech has repaired it. We don't want to - // send 2 notifications. - if !user_fixed.has_role(db, "tech").await { - Notification::create( - db, - &user_created, - &format!( - "{} hat den von dir eingetragenen Bootschaden '{}' beim Boot '{}' repariert. Dieser muss nun noch von unseren Bootswarten bestätigt werden.", - user_fixed.name, - boat_damage.desc, boat.name, - ), - "Bootsschaden repariert", - None,None - ) - .await; - } - } - - Ok(()) - } - - pub async fn verified( - &self, - db: &SqlitePool, - boat_form: BoatDamageVerified<'_>, - ) -> Result<(), String> { - if let Some(verifier) = User::find_by_id(db, boat_form.user_id_verified).await { - if !verifier.has_role(db, "tech").await { - Log::create(db, format!("User {verifier:?} tried to verify boat {boat_form:?}. The user is no tech. Manually craftted request?")).await; - return Err("You are not allowed to verify the boat!".into()); - } - } else { - Log::create(db, format!("Someone tried to verify the boat {boat_form:?} with user_id={} which does not exist. Manually craftted request?", boat_form.user_id_verified)).await; - return Err("Could not find user".into()); - } - - let Some(boat) = Boat::find_by_id(db, self.boat_id as i32).await else { - return Err("Boot gibt's ned".into()); - }; - let was_unusable_before = boat.is_locked(db).await; - - Log::create(db, format!("Verified boat damage: {boat_form:?}")).await; - - sqlx::query!( - "UPDATE boat_damage SET desc=?, user_id_verified=?, verified_at=CURRENT_TIMESTAMP WHERE id=?", - boat_form.desc, - boat_form.user_id_verified, - self.id - ) - .execute(db) - .await.map_err(|e| e.to_string())?; - - if boat_form.user_id_verified != self.user_id_created as i32 { - let user_verified = User::find_by_id(db, boat_form.user_id_verified) - .await - .unwrap(); - let user_created = User::find_by_id(db, self.user_id_created as i32) - .await - .unwrap(); - - if user_verified.id == self.user_id_fixed.unwrap() { - Notification::create( - db, - &user_created, - &format!( - "{} hat den von dir eingetragenen Bootschaden '{}' beim Boot '{}' repariert und verifiziert.", - user_verified.name, - self.desc, boat.name, - ), - "Bootsschaden repariert & verifiziert", - None, - None - ) - .await; - } else { - Notification::create( - db, - &user_created, - &format!( - "{} hat verifiziert, dass der von dir eingetragenen Bootschaden '{}' beim Boot '{}' korrekt repariert wurde.", - user_verified.name, - self.desc, boat.name, - ), - "Bootsschaden verifiziert", - None, - None - ).await; - } - } - - if was_unusable_before && !boat.is_locked(db).await { - let cox = Role::find_by_name(db, "cox").await.unwrap(); - Notification::create_for_role(db, &cox, &format!("Liebe Steuerberechtigte, {} wurde repariert und freut sich ab sofort wieder gerudert zu werden :-)", boat.name), "Boot repariert", None, None).await; - } - - Ok(()) - } -} diff --git a/src/model/distance.rs b/src/model/distance.rs deleted file mode 100644 index 05e2caa..0000000 --- a/src/model/distance.rs +++ /dev/null @@ -1,33 +0,0 @@ -use serde::Serialize; -use sqlx::{FromRow, SqlitePool}; - -#[derive(FromRow, Serialize, Clone, Debug)] -pub struct Distance { - pub id: i64, - pub destination: String, - pub distance_in_km: i64, -} - -impl Distance { - /// Return all default `distance`s, ordered by usage in logbook entries - pub async fn all(db: &SqlitePool) -> Vec { - sqlx::query_as!( - Self, - "SELECT - d.id, - d.destination, - d.distance_in_km -FROM - distance d -LEFT JOIN - logbook l ON d.destination = l.destination AND d.distance_in_km = l.distance_in_km -GROUP BY - d.id, d.destination, d.distance_in_km -ORDER BY - COUNT(l.id) DESC, d.destination ASC;" - ) - .fetch_all(db) - .await - .unwrap() - } -} diff --git a/src/model/location.rs b/src/model/location.rs deleted file mode 100644 index 7d174ec..0000000 --- a/src/model/location.rs +++ /dev/null @@ -1,103 +0,0 @@ -use serde::{Deserialize, Serialize}; -use sqlx::{FromRow, SqlitePool}; - -#[derive(FromRow, Debug, Serialize, Deserialize)] -pub struct Location { - pub id: i64, - pub name: String, -} - -impl Location { - pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option { - sqlx::query_as!( - Self, - " - SELECT id, name - FROM location - WHERE id like ? - ", - id - ) - .fetch_one(db) - .await - .ok() - } - - pub async fn find_by_name(db: &SqlitePool, name: String) -> Option { - sqlx::query_as!( - Self, - " - SELECT id, name - FROM location - WHERE name=? - ", - name - ) - .fetch_one(db) - .await - .ok() - } - - pub async fn all(db: &SqlitePool) -> Vec { - sqlx::query_as!(Self, "SELECT id, name FROM location") - .fetch_all(db) - .await - .unwrap() //TODO: fixme - } - - pub async fn create(db: &SqlitePool, name: &str) -> bool { - sqlx::query!("INSERT INTO location(name) VALUES (?)", name) - .execute(db) - .await - .is_ok() - } - - pub async fn delete(&self, db: &SqlitePool) { - sqlx::query!("DELETE FROM location WHERE id=?", self.id) - .execute(db) - .await - .unwrap(); //Okay, because we can only create a Location of a valid id - } -} - -#[cfg(test)] -mod test { - use crate::{model::location::Location, testdb}; - - use sqlx::SqlitePool; - - #[sqlx::test] - fn test_find_correct_id() { - let pool = testdb!(); - let location = Location::find_by_id(&pool, 1).await.unwrap(); - assert_eq!(location.id, 1); - } - - #[sqlx::test] - fn test_find_wrong_id() { - let pool = testdb!(); - let location = Location::find_by_id(&pool, 1337).await; - assert!(location.is_none()); - } - - #[sqlx::test] - fn test_all() { - let pool = testdb!(); - let res = Location::all(&pool).await; - assert!(res.len() > 1); - } - - #[sqlx::test] - fn test_succ_create() { - let pool = testdb!(); - - assert_eq!(Location::create(&pool, "new-loc-name".into(),).await, true); - } - - #[sqlx::test] - fn test_duplicate_name_create() { - let pool = testdb!(); - - assert_eq!(Location::create(&pool, "Linz".into(),).await, false); - } -} diff --git a/src/model/logtype.rs b/src/model/logtype.rs deleted file mode 100644 index 03d1b05..0000000 --- a/src/model/logtype.rs +++ /dev/null @@ -1,52 +0,0 @@ -use serde::{Deserialize, Serialize}; -use sqlx::{FromRow, SqlitePool}; - -#[derive(FromRow, Debug, Serialize, Deserialize, Clone)] -pub struct LogType { - pub id: i64, - name: String, -} - -impl LogType { - pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option { - sqlx::query_as!( - Self, - " -SELECT id, name -FROM logbook_type -WHERE id like ? - ", - id - ) - .fetch_one(db) - .await - .ok() - } - - pub async fn all(db: &SqlitePool) -> Vec { - sqlx::query_as!( - Self, - " -SELECT id, name -FROM logbook_type - " - ) - .fetch_all(db) - .await - .unwrap() //TODO: fixme - } -} - -#[cfg(test)] -mod test { - use crate::testdb; - - use sqlx::SqlitePool; - - #[sqlx::test] - fn test_find_true() { - let _ = testdb!(); - } - - //TODO: write tests -} diff --git a/src/model/mod.rs b/src/model/mod.rs index 3a76436..80b42ac 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -14,25 +14,11 @@ use self::{ use boatreservation::{BoatReservation, BoatReservationWithDetails}; use std::collections::HashMap; -pub mod boat; -pub mod boatdamage; -pub mod boathouse; -pub mod boatreservation; -pub mod distance; pub mod event; -pub mod family; -pub mod location; pub mod log; -pub mod logbook; -pub mod logtype; -pub mod mail; pub mod notification; pub mod personal; pub mod role; -pub mod rower; -pub mod stat; -pub mod trailer; -pub mod trailerreservation; pub mod trip; pub mod tripdetails; pub mod triptype; diff --git a/src/model/notification.rs b/src/model/notification.rs index 8a49f37..41ad747 100644 --- a/src/model/notification.rs +++ b/src/model/notification.rs @@ -212,14 +212,6 @@ ORDER BY read_at DESC, created_at DESC; .await .unwrap(); } - - pub(crate) async fn delete_by_link(db: &sqlx::Pool, link: &str) { - let link = Some(link); - sqlx::query!("DELETE FROM notification WHERE link=?", link) - .execute(db) - .await - .unwrap(); - } } #[cfg(test)] diff --git a/src/model/personal/mod.rs b/src/model/personal/mod.rs index 40a93c9..fb54777 100644 --- a/src/model/personal/mod.rs +++ b/src/model/personal/mod.rs @@ -1,52 +1 @@ -use chrono::{Datelike, Local}; -use equatorprice::Level; -use serde::Serialize; -use sqlx::SqlitePool; - -use super::{logbook::Logbook, stat::Stat, user::User}; - pub(crate) mod cal; -pub(crate) mod equatorprice; -pub(crate) mod rowingbadge; - -#[derive(Serialize)] -pub(crate) struct Achievements { - pub(crate) equatorprice: equatorprice::Next, - pub(crate) curr_equatorprice_name: String, - pub(crate) new_equatorprice_this_season: bool, - pub(crate) rowingbadge: Option, - pub(crate) all_time_km: i32, - pub(crate) year_first_mentioned: Option, - pub(crate) year_last_mentioned: Option, -} - -impl Achievements { - pub(crate) async fn for_user(db: &SqlitePool, user: &User) -> Self { - let rowed_km = Stat::total_km(db, user).await.rowed_km; - let rowed_km_this_season = if Local::now().month() == 1 { - Stat::person(db, Some(Local::now().year() - 1), user) - .await - .rowed_km - + Stat::person(db, Some(Local::now().year()), user) - .await - .rowed_km - } else { - Stat::person(db, Some(Local::now().year()), user) - .await - .rowed_km - }; - - let new_equatorprice_this_season = - Level::curr_level(rowed_km) != Level::curr_level(rowed_km - rowed_km_this_season); - - Self { - equatorprice: equatorprice::Next::new(rowed_km), - curr_equatorprice_name: equatorprice::Level::curr_level(rowed_km).desc().to_string(), - new_equatorprice_this_season, - rowingbadge: rowingbadge::Status::for_user(db, user).await, - all_time_km: rowed_km, - year_first_mentioned: Logbook::year_first_logbook_entry(db, user).await, - year_last_mentioned: Logbook::year_last_logbook_entry(db, user).await, - } - } -} diff --git a/src/model/rower.rs b/src/model/rower.rs deleted file mode 100644 index 48ae522..0000000 --- a/src/model/rower.rs +++ /dev/null @@ -1,102 +0,0 @@ -use std::ops::DerefMut; - -use serde::{Deserialize, Serialize}; -use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; - -use super::{logbook::Logbook, user::User}; - -#[derive(FromRow, Debug, Serialize, Deserialize)] -pub struct Rower { - pub logbook_id: i64, - pub rower_id: i64, -} - -impl Rower { - pub async fn for_log(db: &SqlitePool, log: &Logbook) -> Vec { - let mut tx = db.begin().await.unwrap(); - let ret = Self::for_log_tx(&mut tx, log).await; - tx.commit().await.unwrap(); - ret - } - - pub async fn for_log_tx(db: &mut Transaction<'_, Sqlite>, log: &Logbook) -> Vec { - sqlx::query_as!( - User, - " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token -FROM user -WHERE id in (SELECT rower_id FROM rower WHERE logbook_id=?) - ", - log.id - ) - .fetch_all(db.deref_mut()) - .await - .unwrap() - } - - pub async fn create( - db: &mut Transaction<'_, Sqlite>, - logbook_id: i64, - rower_id: i64, - ) -> Result<(), String> { - //TODO: Check if rower is allowed to row - - sqlx::query!( - "INSERT INTO rower(logbook_id, rower_id) VALUES (?,?);", - logbook_id, - rower_id - ) - .execute(db.deref_mut()) - .await - .map_err(|e| e.to_string())?; - - Ok(()) - } -} - -#[cfg(test)] -mod test { - use sqlx::SqlitePool; - - use super::Logbook; - use crate::model::{rower::Rower, user::User}; - use crate::testdb; - - #[sqlx::test] - fn test_for_log() { - let pool = testdb!(); - - let logbook = Logbook::find_by_id(&pool, 3).await.unwrap(); - let rowers = Rower::for_log(&pool, &logbook).await; - let expected = User::find_by_id(&pool, 3).await.unwrap(); - assert_eq!(rowers, vec![expected]); - } - - #[sqlx::test] - fn test_for_log_none() { - let pool = testdb!(); - - let logbook = Logbook::find_by_id(&pool, 2).await.unwrap(); - let rowers = Rower::for_log(&pool, &logbook).await; - assert_eq!(rowers, vec![]); - } - - #[sqlx::test] - fn test_create() { - let pool = testdb!(); - - let logbook = Logbook::find_by_id(&pool, 3).await.unwrap(); - - let mut tx = pool.begin().await.unwrap(); - Rower::create(&mut tx, logbook.id, 2).await.unwrap(); - tx.commit().await.unwrap(); - let rowers = Rower::for_log(&pool, &logbook).await; - assert_eq!( - rowers, - vec![ - User::find_by_id(&pool, 2).await.unwrap(), - User::find_by_id(&pool, 3).await.unwrap() - ] - ); - } -} diff --git a/src/model/trailer.rs b/src/model/trailer.rs deleted file mode 100644 index 658fc14..0000000 --- a/src/model/trailer.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::ops::DerefMut; - -use rocket::serde::{Deserialize, Serialize}; -use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; - -#[derive(FromRow, Debug, Serialize, Deserialize, Eq, Hash, PartialEq, Clone)] -pub struct Trailer { - pub id: i64, - pub name: String, -} - -impl Trailer { - pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option { - sqlx::query_as!(Self, "SELECT id, name FROM trailer WHERE id like ?", id) - .fetch_one(db) - .await - .ok() - } - pub async fn find_by_id_tx(db: &mut Transaction<'_, Sqlite>, id: i32) -> Option { - sqlx::query_as!(Self, "SELECT id, name FROM trailer WHERE id like ?", id) - .fetch_one(db.deref_mut()) - .await - .ok() - } - pub async fn all(db: &SqlitePool) -> Vec { - sqlx::query_as!(Self, "SELECT id, name FROM trailer") - .fetch_all(db) - .await - .unwrap() - } -} diff --git a/src/model/trailerreservation.rs b/src/model/trailerreservation.rs deleted file mode 100644 index 0bc45a1..0000000 --- a/src/model/trailerreservation.rs +++ /dev/null @@ -1,233 +0,0 @@ -use std::collections::HashMap; - -use chrono::NaiveDate; -use chrono::NaiveDateTime; -use rocket::serde::{Deserialize, Serialize}; -use sqlx::{FromRow, SqlitePool}; - -use super::log::Log; -use super::notification::Notification; -use super::role::Role; -use super::trailer::Trailer; -use super::user::User; -use crate::tera::trailerreservation::ReservationEditForm; - -#[derive(FromRow, Debug, Serialize, Deserialize)] -pub struct TrailerReservation { - pub id: i64, - pub trailer_id: i64, - pub start_date: NaiveDate, - pub end_date: NaiveDate, - pub time_desc: String, - pub usage: String, - pub user_id_applicant: i64, - pub user_id_confirmation: Option, - pub created_at: NaiveDateTime, -} - -#[derive(FromRow, Debug, Serialize, Deserialize)] -pub struct TrailerReservationWithDetails { - #[serde(flatten)] - reservation: TrailerReservation, - trailer: Trailer, - user_applicant: User, - user_confirmation: Option, -} - -#[derive(Debug)] -pub struct TrailerReservationToAdd<'r> { - pub trailer: &'r Trailer, - pub start_date: NaiveDate, - pub end_date: NaiveDate, - pub time_desc: &'r str, - pub usage: &'r str, - pub user_applicant: &'r User, -} - -impl TrailerReservation { - pub async fn find_by_id(db: &SqlitePool, id: i32) -> Option { - sqlx::query_as!( - Self, - "SELECT id, trailer_id, start_date, end_date, time_desc, usage, user_id_applicant, user_id_confirmation, created_at - FROM trailer_reservation - WHERE id like ?", - id - ) - .fetch_one(db) - .await - .ok() - } - - pub async fn all_future(db: &SqlitePool) -> Vec { - let trailerreservations = sqlx::query_as!( - Self, - " -SELECT id, trailer_id, start_date, end_date, time_desc, usage, user_id_applicant, user_id_confirmation, created_at -FROM trailer_reservation -WHERE end_date >= CURRENT_DATE ORDER BY end_date - " - ) - .fetch_all(db) - .await - .unwrap(); //TODO: fixme - - let mut res = Vec::new(); - for reservation in trailerreservations { - let user_confirmation = match reservation.user_id_confirmation { - Some(id) => { - let user = User::find_by_id(db, id as i32).await; - Some(user.unwrap()) - } - None => None, - }; - let user_applicant = User::find_by_id(db, reservation.user_id_applicant as i32) - .await - .unwrap(); - let trailer = Trailer::find_by_id(db, reservation.trailer_id as i32) - .await - .unwrap(); - - res.push(TrailerReservationWithDetails { - reservation, - trailer, - user_applicant, - user_confirmation, - }); - } - res - } - pub async fn all_future_with_groups( - db: &SqlitePool, - ) -> HashMap> { - let mut grouped_reservations: HashMap> = - HashMap::new(); - - let reservations = Self::all_future(db).await; - for reservation in reservations { - let key = format!( - "{}-{}-{}-{}-{}", - reservation.reservation.start_date, - reservation.reservation.end_date, - reservation.reservation.time_desc, - reservation.reservation.usage, - reservation.user_applicant.name - ); - - grouped_reservations - .entry(key) - .or_default() - .push(reservation); - } - - grouped_reservations - } - - pub async fn create( - db: &SqlitePool, - trailerreservation: TrailerReservationToAdd<'_>, - ) -> Result<(), String> { - if Self::trailer_reserved_between_dates( - db, - trailerreservation.trailer, - &trailerreservation.start_date, - &trailerreservation.end_date, - ) - .await - { - return Err("Hänger in diesem Zeitraum bereits reserviert.".into()); - } - - Log::create( - db, - format!("New trailer reservation: {trailerreservation:?}"), - ) - .await; - - sqlx::query!( - "INSERT INTO trailer_reservation(trailer_id, start_date, end_date, time_desc, usage, user_id_applicant) VALUES (?,?,?,?,?,?)", - trailerreservation.trailer.id, - trailerreservation.start_date, - trailerreservation.end_date, - trailerreservation.time_desc, - trailerreservation.usage, - trailerreservation.user_applicant.id, - ) - .execute(db) - .await - .map_err(|e| e.to_string())?; - - let board = - User::all_with_role(db, &Role::find_by_name(db, "Vorstand").await.unwrap()).await; - for user in board { - let date = if trailerreservation.start_date == trailerreservation.end_date { - format!("am {}", trailerreservation.start_date) - } else { - format!( - "von {} bis {}", - trailerreservation.start_date, trailerreservation.end_date - ) - }; - - Notification::create( - db, - &user, - &format!( - "{} hat eine neue Hängerreservierung für Hänger '{}' {} angelegt. Zeit: {}; Zweck: {}", - trailerreservation.user_applicant.name, - trailerreservation.trailer.name, - date, - trailerreservation.time_desc, - trailerreservation.usage - ), - "Neue Hängerreservierung", - None,None - ) - .await; - } - - Ok(()) - } - - pub async fn trailer_reserved_between_dates( - db: &SqlitePool, - trailer: &Trailer, - start_date: &NaiveDate, - end_date: &NaiveDate, - ) -> bool { - sqlx::query!( - "SELECT COUNT(*) AS reservation_count -FROM trailer_reservation -WHERE trailer_id = ? -AND start_date <= ? AND end_date >= ?;", - trailer.id, - end_date, - start_date - ) - .fetch_one(db) - .await - .unwrap() - .reservation_count - > 0 - } - - pub async fn update(&self, db: &SqlitePool, data: ReservationEditForm) { - let time_desc = data.time_desc.trim(); - let usage = data.usage.trim(); - sqlx::query!( - "UPDATE trailer_reservation SET time_desc = ?, usage = ? where id = ?", - time_desc, - usage, - self.id - ) - .execute(db) - .await - .unwrap(); //Okay, because we can only create a User of a valid id - } - - pub async fn delete(&self, db: &SqlitePool) { - sqlx::query!("DELETE FROM trailer_reservation 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/trip.rs b/src/model/trip.rs index dd817a4..740ad2c 100644 --- a/src/model/trip.rs +++ b/src/model/trip.rs @@ -9,7 +9,7 @@ use super::{ notification::Notification, tripdetails::TripDetails, triptype::TripType, - user::{ErgoUser, SteeringUser, User}, + user::{SteeringUser, User}, usertrip::UserTrip, }; @@ -66,16 +66,6 @@ impl Trip { Self::perform_new(db, &cox.user, trip_details).await } - pub async fn new_own_ergo(db: &SqlitePool, ergo: &ErgoUser, trip_details: TripDetails) { - let typ = trip_details.triptype(db).await; - if let Some(typ) = typ { - let allowed_type = TripType::find_by_id(db, 4).await.unwrap(); - if typ == allowed_type { - Self::perform_new(db, &ergo.user, trip_details).await; - } - } - } - async fn perform_new(db: &SqlitePool, user: &User, trip_details: TripDetails) { let _ = sqlx::query!( "INSERT INTO trip (cox_id, trip_details_id) VALUES(?, ?)", diff --git a/src/model/user/mod.rs b/src/model/user/mod.rs index 38862e3..491b835 100644 --- a/src/model/user/mod.rs +++ b/src/model/user/mod.rs @@ -6,53 +6,24 @@ use log::info; use rocket::{ async_trait, http::{Cookie, Status}, - request::{self, FromRequest, Outcome}, + request, + request::{FromRequest, Outcome}, time::{Duration, OffsetDateTime}, - tokio::io::AsyncReadExt, Request, }; use serde::{Deserialize, Serialize}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; -use super::{ - family::Family, - log::Log, - logbook::Logbook, - mail::Mail, - notification::Notification, - personal::{equatorprice, rowingbadge}, - role::Role, - stat::Stat, - tripdetails::TripDetails, - Day, -}; -use crate::{ - tera::admin::user::UserEditForm, AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD, BOAT_STORAGE, - EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE, FAMILY_TWO, FOERDERND, REGULAR, RENNRUDERBEITRAG, - SCHECKBUCH, STUDENT_OR_PUPIL, UNTERSTUETZEND, -}; -use fee::Fee; - -mod fee; +use super::{log::Log, role::Role, tripdetails::TripDetails, Day}; +use crate::{tera::admin::user::UserEditForm, AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD}; #[derive(FromRow, Serialize, Deserialize, Clone, Debug, Eq, Hash, PartialEq)] pub struct User { pub id: i64, pub name: String, pub pw: Option, - pub dob: Option, - pub weight: Option, - pub sex: Option, pub deleted: bool, pub last_access: Option, - pub member_since_date: Option, - pub birthdate: Option, - pub mail: Option, - pub nickname: Option, - pub notes: Option, - pub phone: Option, - pub address: Option, - pub family_id: Option, pub user_token: String, } @@ -62,7 +33,6 @@ pub struct UserWithDetails { pub user: User, pub amount_unread_notifications: i64, pub allowed_to_steer: bool, - pub on_water: bool, pub roles: Vec, } @@ -71,7 +41,6 @@ impl UserWithDetails { let allowed_to_steer = user.allowed_to_steer(db).await; Self { - on_water: user.on_water(db).await, roles: user.roles(db).await, amount_unread_notifications: user.amount_unread_notifications(db).await, allowed_to_steer, @@ -103,321 +72,7 @@ impl User { self.has_role_tx(db, "cox").await || self.has_role_tx(db, "Bootsführer").await } - pub async fn send_welcome_email(&self, db: &SqlitePool, smtp_pw: &str) -> Result<(), String> { - let Some(mail) = &self.mail else { - return Err(format!( - "Could not send welcome mail, because user {} has no email address", - self.name - )); - }; - - if self.has_role(db, "Donau Linz").await { - self.send_welcome_mail_full_member(db, mail, smtp_pw) - .await?; - } else if self.has_role(db, "scheckbuch").await { - self.send_welcome_mail_scheckbuch(db, mail, smtp_pw).await?; - } else if self.has_role(db, "schnupperant").await { - self.send_welcome_mail_schnupper(db, mail, smtp_pw).await?; - } else { - return Err(format!( - "Could not send welcome mail, because user {} is not in Donau Linz or scheckbuch or schnupperant group", - self.name - )); - } - - Log::create( - db, - format!("Willkommensemail wurde an {} versandt", self.name), - ) - .await; - - Ok(()) - } - - async fn send_welcome_mail_schnupper( - &self, - db: &SqlitePool, - mail: &str, - smtp_pw: &str, - ) -> Result<(), String> { - // 2 things to do: - // 1. Send mail to user - Mail::send_single( - db, - mail, - "Schnupperrudern beim ASKÖ Ruderverein Donau Linz", - format!( -"Hallo {0}, - -es freut uns sehr, dich bei unserem Schnupperkurs willkommen heißen zu dürfen. Detaillierte Informationen folgen noch, ich werde sie dir ein paar Tage vor dem Termin zusenden. - -Riemen- & Dollenbruch, -ASKÖ Ruderverein Donau Linz", self.name), - smtp_pw, - ).await?; - - // 2. Notify all coxes - let coxes = Role::find_by_name(db, "schnupper-betreuer").await.unwrap(); - Notification::create_for_role( - db, - &coxes, - &format!( - "Liebe Schnupper-Betreuer, {} nimmt am Schnupperkurs teil.", - self.name - ), - "Neue(r) Schnupperteilnehmer:in ", - None, - None, - ) - .await; - - Ok(()) - } - - async fn send_welcome_mail_scheckbuch( - &self, - db: &SqlitePool, - mail: &str, - smtp_pw: &str, - ) -> Result<(), String> { - // 2 things to do: - // 1. Send mail to user - Mail::send_single( - db, - mail, - "ASKÖ Ruderverein Donau Linz | Dein Scheckbuch wartet auf Dich", - format!( -"Hallo {0}, - -herzlich willkommen beim ASKÖ Ruderverein Donau Linz! Wir freuen uns sehr, dass Du Dich entschieden hast, das Rudern bei uns auszuprobieren. Mit Deinem Scheckbuch kannst Du jetzt an fünf Ausfahrten teilnehmen und so diesen Sport in seiner vollen Vielfalt erleben. Falls du die {1} € noch nicht bezahlt hast, nimm diese bitte zur nächsten Ausfahrt mit (oder überweise sie auf unser Bankkonto [dieses findest du auf https://rudernlinz.at]). - -Für die Organisation unserer Ausfahrten nutzen wir app.rudernlinz.at. Logge Dich bitte mit Deinem Namen ('{0}', ohne Anführungszeichen) ein. Beim ersten Mal kannst Du das Passwortfeld leer lassen. Unter 'Geplante Ausfahrten' kannst Du Dich jederzeit für eine Ausfahrt anmelden. Wir bieten mindestens einmal pro Woche Ausfahrten an, sowohl für Anfänger als auch für Fortgeschrittene (A+F Rudern). Zusätzliche Ausfahrten werden von unseren Steuerleuten ausgeschrieben, öfters reinschauen kann sich also lohnen :-) - -Nach deinen 5 Ausfahrten würden wir uns freuen, dich als Mitglied in unserem Verein begrüßen zu dürfen. - -Wir freuen uns darauf, Dich bald am Wasser zu sehen und gemeinsam tolle Erfahrungen zu sammeln! - -Riemen- & Dollenbruch, -ASKÖ Ruderverein Donau Linz", self.name, SCHECKBUCH/100), - smtp_pw, - ).await?; - - // 2. Notify all coxes - Notification::create_for_steering_people( - db, - &format!( - "Liebe Steuerberechtigte, {} hat nun ein Scheckbuch. Wie immer, freuen wir uns wenn du uns beim A+F Rudern unterstützt oder selber Ausfahrten ausschreibst. Bitte beachte, dass Scheckbuch-Personen nur Ausfahrten sehen, bei denen 'Scheckbuch-Anmeldungen erlauben' ausgewählt wurde.", - self.name - ), - "Neues Scheckbuch", - None,None - ) - .await; - - Ok(()) - } - - async fn send_end_mail_scheckbuch( - &self, - db: &mut Transaction<'_, Sqlite>, - mail: &str, - smtp_pw: &str, - ) -> Result<(), String> { - Mail::send_single_tx( - db, - mail, - "ASKÖ Ruderverein Donau Linz | Deine Mitgliedschaft wartet auf Dich", - format!( -"Hallo {0}, - -herzlichen Glückwunsch---Du hast Deine fünf Scheckbuch-Ausfahrten erfolgreich absolviert! Wir hoffen, dass Du das Rudern bei uns genauso genossen hast wie wir es genossen haben, Dich auf dem Wasser zu begleiten. - -Wir würden uns sehr freuen, Dich als festes Mitglied in unserem Verein willkommen zu heißen! Als Mitglied stehen Dir dann alle unsere Ausfahrten offen, die von unseren Steuerleuten organisiert werden. Im Sommer erwarten Dich zusätzlich spannende Events: Wanderfahrten, Sternfahrten, Fetzenfahrt, .... Im Winter bieten wir Indoor-Ergo-Challenges an, bei denen Du Deine Fitness auf dem Ruderergometer unter Beweis stellen kannst. Alle Details zu diesen Aktionen erfährst Du, sobald Du Teil unseres Vereins bist! :-) - -Alle Informationen zu den Mitgliedsbeiträgen findest Du unter https://rudernlinz.at/unser-verein/gebuhren/ Falls Du Dich entscheidest, unserem Verein beizutreten, fülle bitte unser Beitrittsformular auf https://rudernlinz.at/unser-verein/downloads/ aus und sende es an info@rudernlinz.at. - -Wir freuen uns, Dich bald wieder auf dem Wasser zu sehen. - -Riemen- & Dollenbruch, -ASKÖ Ruderverein Donau Linz", self.name), - smtp_pw, - ).await?; - - Ok(()) - } - - async fn send_welcome_mail_full_member( - &self, - db: &SqlitePool, - mail: &str, - smtp_pw: &str, - ) -> Result<(), String> { - // 2 things to do: - // 1. Send mail to user - Mail::send_single( - db, - mail, - "Willkommen im ASKÖ Ruderverein Donau Linz!", - format!( -"Hallo {0}, - -herzlich willkommen im ASKÖ Ruderverein Donau Linz! Wir freuen uns sehr, dich als neues Mitglied in unserem Verein begrüßen zu dürfen. - -Um dir den Einstieg zu erleichtern, findest du in unserem Handbuch alle wichtigen Informationen über unseren Verein: https://rudernlinz.at/book. Bei weiteren Fragen stehen dir die Adressen info@rudernlinz.at (für allgemeine Fragen) und it@rudernlinz.at (bei technischen Fragen) jederzeit zur Verfügung. - -Du kannst auch gerne unserer Signal-Gruppe beitreten, um auf dem Laufenden zu bleiben und dich mit anderen Mitgliedern auszutauschen: https://signal.group/#CjQKICFrq6zSsRHxrucS3jEcQn6lknEXacAykwwLV3vNLKxPEhA17jxz7cpjfu3JZokLq1TH - -Für die Organisation unserer Ausfahrten nutzen wir app.rudernlinz.at. Logge dich einfach mit deinem Namen ('{0}' ohne Anführungszeichen) ein, beim ersten Mal kannst du das Passwortfeld leer lassen. Unter 'Geplante Ausfahrten' kannst du dich jederzeit zu den Ausfahrten anmelden. - -Beim nächsten Treffen im Verein, erinnere jemand vom Vorstand (https://rudernlinz.at/unser-verein/vorstand/) bitte daran, deinen Fingerabdruck zu registrieren, damit du Zugang zum Bootshaus erhältst. - -Damit du dich noch mehr verbunden fühlst (:-)), haben wir im Bootshaus ein WLAN für Vereinsmitglieder 'ASKÖ Ruderverein Donau Linz' eingerichtet. Das Passwort dafür lautet 'donau1921' (ohne Anführungszeichen). Bitte gib das Passwort an keine vereinsfremden Personen weiter. - -Wir freuen uns darauf, dich bald am Wasser zu sehen und gemeinsam tolle Erfahrungen zu sammeln! - -Riemen- & Dollenbruch -ASKÖ Ruderverein Donau Linz", self.name), - smtp_pw, - ).await?; - - // 2. Notify all coxes - Notification::create_for_steering_people( - db, - &format!( - "Liebe Steuerberechtigte, seit {} gibt es ein neues Mitglied: {}", - self.member_since_date.clone().unwrap(), - self.name - ), - "Neues Vereinsmitglied", - None, - None, - ) - .await; - - Ok(()) - } - - pub async fn fee(&self, db: &SqlitePool) -> Option { - if !self.has_role(db, "Donau Linz").await - && !self.has_role(db, "Unterstützend").await - && !self.has_role(db, "Förderndes Mitglied").await - { - return None; - } - if self.deleted { - return None; - } - - let mut fee = Fee::new(); - - if let Some(family) = Family::find_by_opt_id(db, self.family_id).await { - for member in family.members(db).await { - fee.add_person(&member); - if member.has_role(db, "paid").await { - fee.paid(); - } - fee.merge(member.fee_without_families(db).await); - } - if family.amount_family_members(db).await > 2 { - fee.add("Familie 3+ Personen".into(), FAMILY_THREE_OR_MORE); - } else { - fee.add("Familie 2 Personen".into(), FAMILY_TWO); - } - } else { - fee.add_person(self); - if self.has_role(db, "paid").await { - fee.paid(); - } - fee.merge(self.fee_without_families(db).await); - } - - Some(fee) - } - - async fn fee_without_families(&self, db: &SqlitePool) -> Fee { - let mut fee = Fee::new(); - - if !self.has_role(db, "Donau Linz").await - && !self.has_role(db, "Unterstützend").await - && !self.has_role(db, "Förderndes Mitglied").await - { - return fee; - } - if self.has_role(db, "Rennrudern").await { - if self.has_role(db, "half-rennrudern").await { - fee.add("Rennruderbeitrag (1/2 Preis) ".into(), RENNRUDERBEITRAG / 2); - } else if !self.has_role(db, "renntrainer").await { - fee.add("Rennruderbeitrag".into(), RENNRUDERBEITRAG); - } - } - - let amount_boats = self.amount_boats(db).await; - if amount_boats > 0 { - fee.add( - format!("{}x Bootsplatz", amount_boats), - amount_boats * BOAT_STORAGE, - ); - } - - if let Some(member_since_date) = &self.member_since_date { - if let Ok(member_since_date) = NaiveDate::parse_from_str(member_since_date, "%Y-%m-%d") - { - if member_since_date.year() == Local::now().year() - && !self.has_role(db, "no-einschreibgebuehr").await - { - fee.add("Einschreibgebühr".into(), EINSCHREIBGEBUEHR); - } - } - } - - let halfprice = if let Some(member_since_date) = &self.member_since_date { - if let Ok(member_since_date) = NaiveDate::parse_from_str(member_since_date, "%Y-%m-%d") - { - let halfprice_startdate = - NaiveDate::from_ymd_opt(Local::now().year(), 7, 1).unwrap(); - member_since_date >= halfprice_startdate - } else { - false - } - } else { - false - }; - - if self.has_role(db, "Unterstützend").await { - fee.add("Unterstützendes Mitglied".into(), UNTERSTUETZEND); - } else if self.has_role(db, "Förderndes Mitglied").await { - fee.add("Förderndes Mitglied".into(), FOERDERND); - } else if Family::find_by_opt_id(db, self.family_id).await.is_none() { - if self.has_role(db, "Student").await || self.has_role(db, "Schüler").await { - if halfprice { - fee.add("Schüler/Student (Halbpreis)".into(), STUDENT_OR_PUPIL / 2); - } else { - fee.add("Schüler/Student".into(), STUDENT_OR_PUPIL); - } - } else if self.has_role(db, "Ehrenmitglied").await { - fee.add("Ehrenmitglied".into(), 0); - } else if halfprice { - fee.add("Mitgliedsbeitrag (Halbpreis)".into(), REGULAR / 2); - } else { - fee.add("Mitgliedsbeitrag".into(), REGULAR); - } - } - - fee - } - - pub async fn amount_boats(&self, db: &SqlitePool) -> i64 { - sqlx::query!( - "SELECT COUNT(*) as count FROM boat WHERE owner = ?", - self.id - ) - .fetch_one(db) - .await - .unwrap() - .count - } - - pub async fn amount_unread_notifications(&self, db: &SqlitePool) -> i64 { + pub async fn amount_unread_notifications(&self, db: &SqlitePool) -> i32 { sqlx::query!( "SELECT COUNT(*) as count FROM notification WHERE user_id = ? AND read_at IS NULL", self.id @@ -451,29 +106,6 @@ ASKÖ Ruderverein Donau Linz", self.name), .is_some() } - pub async fn has_membership_pdf(&self, db: &SqlitePool) -> bool { - match sqlx::query_scalar!("SELECT membership_pdf FROM user WHERE id = ?", self.id) - .fetch_one(db) - .await - .unwrap() - { - Some(a) if a.is_empty() => false, - None => false, - _ => true, - } - } - pub async fn has_membership_pdf_tx(&self, db: &mut Transaction<'_, Sqlite>) -> bool { - match sqlx::query_scalar!("SELECT membership_pdf FROM user WHERE id = ?", self.id) - .fetch_one(db.deref_mut()) - .await - .unwrap() - { - Some(a) if a.is_empty() => false, - None => false, - _ => true, - } - } - pub async fn roles(&self, db: &SqlitePool) -> Vec { sqlx::query!( "SELECT r.name FROM role r JOIN user_role ur ON r.id = ur.role_id JOIN user u ON u.id = ur.user_id WHERE ur.user_id = ? AND u.deleted = 0;", @@ -521,7 +153,7 @@ ASKÖ Ruderverein Donau Linz", self.name), sqlx::query_as!( Self, " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token +SELECT id, name, pw, deleted, last_access, user_token FROM user WHERE id like ? ", @@ -536,7 +168,7 @@ WHERE id like ? sqlx::query_as!( Self, " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token +SELECT id, name, pw, deleted, last_access, user_token FROM user WHERE id like ? ", @@ -553,7 +185,7 @@ WHERE id like ? sqlx::query_as!( Self, " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token +SELECT id, name, pw, deleted, last_access, user_token FROM user WHERE lower(name)=? ", @@ -564,39 +196,22 @@ WHERE lower(name)=? .ok() } - pub async fn on_water(&self, db: &SqlitePool) -> bool { - if sqlx::query!( - "SELECT * FROM logbook WHERE shipmaster=? AND arrival is null", - self.id - ) - .fetch_optional(db) - .await - .unwrap() - .is_some() - { - return true; - } - if sqlx::query!( - "SELECT * FROM logbook JOIN rower ON rower.logbook_id=logbook.id WHERE rower_id=? AND arrival is null", - self.id - ) - .fetch_optional(db) - .await - .unwrap() - .is_some() - { - return true; - } - - false - } - pub async fn all(db: &SqlitePool) -> Vec { +<<<<<<< HEAD:src/model/user/mod.rs Self::all_with_order(db, "last_access", false).await } pub async fn all_with_order(db: &SqlitePool, sort: &str, asc: bool) -> Vec { let mut query = format!( +======= + sqlx::query_as!( + Self, + " +SELECT id, name, pw, deleted, last_access, user_token +FROM user +WHERE deleted = 0 +ORDER BY last_access DESC +>>>>>>> caeb9dd (first draft of normannen deployment):src/model/user.rs " SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token FROM user @@ -626,48 +241,24 @@ WHERE lower(name)=? sqlx::query_as!( Self, " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token +SELECT id, name, pw, deleted, last_access, user_token FROM user u JOIN user_role ur ON u.id = ur.user_id WHERE ur.role_id = ? AND deleted = 0 ORDER BY name; - ", role.id + ", + role.id ) .fetch_all(db.deref_mut()) .await .unwrap() } - pub async fn all_payer_groups(db: &SqlitePool) -> Vec { - sqlx::query_as!( - Self, - " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token FROM user -WHERE family_id IS NOT NULL -GROUP BY family_id - -UNION - --- Select users with a null family_id, without grouping -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token FROM user -WHERE family_id IS NULL; - " - ) - .fetch_all(db) - .await - .unwrap() - } - - pub async fn ergo(db: &SqlitePool) -> Vec { - let ergo = Role::find_by_name(db, "ergo").await.unwrap(); - Self::all_with_role(db, &ergo).await - } - pub async fn cox(db: &SqlitePool) -> Vec { sqlx::query_as!( Self, " -SELECT id, name, pw, deleted, last_access, dob, weight, sex, member_since_date, birthdate, mail, nickname, notes, phone, address, family_id, user_token +SELECT id, name, pw, deleted, last_access, user_token FROM user WHERE deleted = 0 AND (SELECT COUNT(*) FROM user_role WHERE user_id=user.id AND role_id = (SELECT id FROM role WHERE name = 'cox')) > 0 ORDER BY last_access DESC @@ -686,70 +277,13 @@ ORDER BY last_access DESC .is_ok() } - pub async fn create_with_mail(db: &SqlitePool, name: &str, mail: &str) -> bool { - let name = name.trim(); - sqlx::query!("INSERT INTO USER(name, mail) VALUES (?, ?)", name, mail) - .execute(db) - .await - .is_ok() - } - - pub async fn update_ergo(&self, db: &SqlitePool, dob: i32, weight: i64, sex: &str) { - sqlx::query!( - "UPDATE user SET dob = ?, weight = ?, sex = ? where id = ?", - dob, - weight, - sex, - self.id - ) - .execute(db) - .await - .unwrap(); //Okay, because we can only create a User of a valid id - } - - pub async fn update(&self, db: &SqlitePool, data: UserEditForm<'_>) -> Result<(), String> { + pub async fn update(&self, db: &SqlitePool, data: UserEditForm) -> Result<(), String> { let mut db = db.begin().await.map_err(|e| e.to_string())?; - let mut family_id = data.family_id; - - if family_id.is_some_and(|x| x == -1) { - family_id = Some(Family::insert_tx(&mut db).await) - } - - if !self.has_membership_pdf_tx(&mut db).await { - if let Some(membership_pdf) = data.membership_pdf { - let mut stream = membership_pdf.open().await.unwrap(); - let mut buffer = Vec::new(); - stream.read_to_end(&mut buffer).await.unwrap(); - sqlx::query!( - "UPDATE user SET membership_pdf = ? where id = ?", - buffer, - self.id - ) - .execute(db.deref_mut()) - .await - .unwrap(); //Okay, because we can only create a User of a valid id - } - } - - sqlx::query!( - "UPDATE user SET dob = ?, weight = ?, sex = ?, member_since_date=?, birthdate=?, mail=?, nickname=?, notes=?, phone=?, address=?, family_id = ? where id = ?", - data.dob, - data.weight, - data.sex, - data.member_since_date, - data.birthdate, - data.mail, - data.nickname, - data.notes, - data.phone, - data.address, - family_id, - self.id - ) - .execute(db.deref_mut()) - .await - .unwrap(); //Okay, because we can only create a User of a valid id + sqlx::query!("UPDATE user SET name = ? where id = ?", data.name, self.id) + .execute(db.deref_mut()) + .await + .unwrap(); //Okay, because we can only create a User of a valid id // handle roles sqlx::query!("DELETE FROM user_role WHERE user_id = ?", self.id) @@ -827,41 +361,7 @@ ORDER BY last_access DESC pub async fn login(db: &SqlitePool, name: &str, pw: &str) -> Result { let name = name.trim().to_lowercase(); // just to make sure... let Some(user) = User::find_by_name(db, &name).await else { - if ![ - "n-sageder", - "p-hofer", - "marie-birner", - "daniel-kortschak", - "rudernlinz", - "m-birner", - "s-sollberger", - "d-kortschak", - "wwwadmin", - "wadminw", - "admin", - "m sageder", - "d kortschak", - "a almousa", - "p hofer", - "s sollberger", - "n sageder", - "wp-system", - "s.sollberger", - "m.birner", - "m-sageder", - "a-almousa", - "m.sageder", - "n.sageder", - "a.almousa", - "p.hofer", - "philipp-hofer", - "d.kortschak", - "[login]", - ] - .contains(&name.as_str()) - { - Log::create(db, format!("Username ({name}) not found (tried to login)")).await; - } + Log::create(db, format!("Username ({name}) not found (tried to login)")).await; return Err(LoginError::InvalidAuthenticationCombo); // Username not found }; @@ -982,6 +482,7 @@ ORDER BY last_access DESC AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD } } +<<<<<<< HEAD:src/model/user/mod.rs pub(crate) async fn close_thousands_trip(&self, db: &SqlitePool) -> Option { let rowed_km = Stat::person(db, None, self).await.rowed_km; @@ -1073,6 +574,8 @@ ORDER BY last_access DESC Notification::create_with_tx(db, self, &format!("Mit deiner letzten Ausfahrt erfüllst du nun alle Anforderungen für den Äquatorpreis in {level}. Gratuliere! 🎉"), "Äquatorpreis", None, None).await; } } +======= +>>>>>>> caeb9dd (first draft of normannen deployment):src/model/user.rs } #[async_trait] @@ -1171,62 +674,12 @@ macro_rules! special_user { }; } -special_user!(TechUser, +"tech"); -special_user!(ErgoUser, +"ergo"); -special_user!(SteeringUser, +"cox", +"Bootsführer"); +special_user!(SteeringUser, +"cox"); special_user!(AdminUser, +"admin"); -special_user!(AllowedForPlannedTripsUser, +"Donau Linz", +"scheckbuch"); -special_user!(DonauLinzUser, +"Donau Linz", -"Unterstützend", -"Förderndes Mitglied"); -special_user!(SchnupperBetreuerUser, +"schnupper-betreuer"); -special_user!(VorstandUser, +"admin", +"Vorstand"); special_user!(EventUser, +"manage_events"); -special_user!(AllowedToEditPaymentStatusUser, +"kassier", +"admin"); -special_user!(ManageUserUser, +"admin", +"schriftfuehrer"); +special_user!(ManageUserUser, +"admin"); special_user!(AllowedToUpdateTripToAlwaysBeShownUser, +"admin"); -#[derive(FromRow, Serialize, Deserialize, Clone, Debug)] -pub struct UserWithRolesAndMembershipPdf { - #[serde(flatten)] - pub user: User, - pub membership_pdf: bool, - pub roles: Vec, -} - -impl UserWithRolesAndMembershipPdf { - pub(crate) async fn from_user(db: &SqlitePool, user: User) -> Self { - let membership_pdf = user.has_membership_pdf(db).await; - - Self { - roles: user.roles(db).await, - user, - membership_pdf, - } - } -} - -#[derive(FromRow, Serialize, Deserialize, Clone, Debug)] -pub struct UserWithMembershipPdf { - #[serde(flatten)] - pub user: User, - pub membership_pdf: Option>, -} - -impl UserWithMembershipPdf { - pub(crate) async fn from(db: &SqlitePool, user: User) -> Self { - let membership_pdf: Option> = - sqlx::query_scalar!("SELECT membership_pdf FROM user WHERE id = $1", user.id) - .fetch_optional(db) - .await - .unwrap() - .unwrap(); - - Self { - user, - membership_pdf, - } - } -} - #[cfg(test)] mod test { use std::collections::HashMap; diff --git a/src/tera/admin/event.rs b/src/tera/admin/event.rs index e78c465..cad081a 100644 --- a/src/tera/admin/event.rs +++ b/src/tera/admin/event.rs @@ -45,7 +45,7 @@ async fn create( ) .await; - Flash::success(Redirect::to("/planned"), "Event hinzugefügt") + Flash::success(Redirect::to("/"), "Event hinzugefügt") } //TODO: add constraints (e.g. planned_amount_cox > 0) @@ -79,21 +79,21 @@ async fn update( match Event::find_by_id(db, data.id).await { Some(planned_event) => { planned_event.update(db, &update).await; - Flash::success(Redirect::to("/planned"), "Event erfolgreich bearbeitet") + Flash::success(Redirect::to("/"), "Event erfolgreich bearbeitet") } - None => Flash::error(Redirect::to("/planned"), "Planned event id not found"), + None => Flash::error(Redirect::to("/"), "Planned event id not found"), } } #[get("/planned-event//delete")] async fn delete(db: &State, id: i64, _admin: EventUser) -> Flash { let Some(event) = Event::find_by_id(db, id).await else { - return Flash::error(Redirect::to("/planned"), "Event does not exist"); + return Flash::error(Redirect::to("/"), "Event does not exist"); }; match event.delete(db).await { - Ok(()) => Flash::success(Redirect::to("/planned"), "Event gelöscht"), - Err(e) => Flash::error(Redirect::to("/planned"), e), + Ok(()) => Flash::success(Redirect::to("/"), "Event gelöscht"), + Err(e) => Flash::error(Redirect::to("/"), e), } } @@ -132,7 +132,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -163,7 +163,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -199,7 +199,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -238,7 +238,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -269,7 +269,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() diff --git a/src/tera/admin/mail.rs b/src/tera/admin/mail.rs deleted file mode 100644 index dd9c8d3..0000000 --- a/src/tera/admin/mail.rs +++ /dev/null @@ -1,86 +0,0 @@ -use rocket::form::Form; -use rocket::fs::TempFile; -use rocket::response::{Flash, Redirect}; -use rocket::{get, request::FlashMessage, routes, Route, State}; -use rocket::{post, FromForm}; -use rocket_dyn_templates::{tera::Context, Template}; -use sqlx::SqlitePool; - -use crate::model::log::Log; -use crate::model::mail::Mail; -use crate::model::role::Role; -use crate::model::user::UserWithDetails; -use crate::model::user::{AdminUser, VorstandUser}; -use crate::tera::Config; - -#[get("/mail")] -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 roles = Role::all(db).await; - - context.insert( - "loggedin_user", - &UserWithDetails::from_user(admin.user, db).await, - ); - context.insert("roles", &roles); - - Template::render("admin/mail", context.into_json()) -} - -#[get("/mail/fee")] -async fn fee(db: &State, admin: AdminUser, config: &State) -> &'static str { - Log::create(db, format!("{admin:?} trying to send fee")).await; - Mail::fees(db, config.smtp_pw.clone()).await; - "SUCC" -} - -#[get("/mail/fee-final")] -async fn fee_final( - db: &State, - admin: AdminUser, - config: &State, -) -> &'static str { - Log::create(db, format!("{admin:?} trying to send fee_final")).await; - Mail::fees_final(db, config.smtp_pw.clone()).await; - "SUCC" -} - -#[derive(FromForm, Debug)] -pub struct MailToSend<'a> { - pub(crate) role_id: i32, - pub(crate) subject: String, - pub(crate) body: String, - pub(crate) files: Vec>, -} - -#[post("/mail", data = "")] -async fn update( - db: &State, - data: Form>, - config: &State, - admin: VorstandUser, -) -> Flash { - let d = data.into_inner(); - Log::create(db, format!("{admin:?} trying to send this mail: {d:?}")).await; - if Mail::send(db, d, config.smtp_pw.clone()).await { - Log::create(db, "Mail successfully sent".into()).await; - Flash::success(Redirect::to("/admin/mail"), "Mail versendet") - } else { - Log::create(db, "Error sending the mail".into()).await; - Flash::error(Redirect::to("/admin/mail"), "Fehler") - } -} - -pub fn routes() -> Vec { - routes![index, update, fee, fee_final] -} - -#[cfg(test)] -mod test {} diff --git a/src/tera/admin/mod.rs b/src/tera/admin/mod.rs index 1690f45..8424194 100644 --- a/src/tera/admin/mod.rs +++ b/src/tera/admin/mod.rs @@ -3,16 +3,13 @@ use rocket::{form::Form, get, post, routes, FromForm, Route, State}; use rocket_dyn_templates::{context, Template}; use sqlx::SqlitePool; +use super::notification; use crate::{ model::{log::Log, role::Role, user::AdminUser}, tera::Config, }; -pub mod boat; pub mod event; -pub mod mail; -pub mod notification; -pub mod schnupper; pub mod user; #[get("/rss?")] @@ -76,10 +73,7 @@ async fn list(db: &State, _admin: AdminUser, list_form: Form Vec { let mut ret = Vec::new(); ret.append(&mut user::routes()); - ret.append(&mut schnupper::routes()); - ret.append(&mut boat::routes()); ret.append(&mut notification::routes()); - ret.append(&mut mail::routes()); ret.append(&mut event::routes()); ret.append(&mut routes![rss, show_rss, show_list, list]); ret diff --git a/src/tera/admin/notification.rs b/src/tera/admin/notification.rs deleted file mode 100644 index fe6578b..0000000 --- a/src/tera/admin/notification.rs +++ /dev/null @@ -1,116 +0,0 @@ -use crate::model::{ - log::Log, - notification::Notification, - role::Role, - user::{User, UserWithDetails, VorstandUser}, -}; -use itertools::Itertools; -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("/notification")] -async fn index( - db: &State, - user: VorstandUser, - flash: Option>, -) -> Template { - let mut context = Context::new(); - if let Some(msg) = flash { - context.insert("flash", &msg.into_inner()); - } - context.insert( - "loggedin_user", - &UserWithDetails::from_user(user.user, db).await, - ); - - let users: Vec = User::all(db) - .await - .into_iter() - .filter(|u| u.last_access.is_some()) // Not useful to send notifications to people who are - // not logging in - .sorted_by_key(|u| u.name.clone()) - .collect(); - - context.insert("roles", &Role::all(db).await); - context.insert("users", &users); - - Template::render("admin/notification", context.into_json()) -} - -#[derive(FromForm, Debug)] -pub struct NotificationToSendGroup { - pub(crate) role_id: i32, - pub(crate) category: String, - pub(crate) message: String, -} - -#[derive(FromForm, Debug)] -pub struct NotificationToSendUser { - pub(crate) user_id: i32, - pub(crate) category: String, - pub(crate) message: String, -} - -#[post("/notification/group", data = "")] -async fn send_group( - db: &State, - data: Form, - admin: VorstandUser, -) -> Flash { - let d = data.into_inner(); - Log::create( - db, - format!("{admin:?} trying to send this notification: {d:?}"), - ) - .await; - - let Some(role) = Role::find_by_id(db, d.role_id).await else { - return Flash::error(Redirect::to("/admin/notification"), "Rolle gibt's ned"); - }; - - for user in User::all_with_role(db, &role).await { - Notification::create(db, &user, &d.message, &d.category, None, None).await; - } - Log::create(db, "Notification successfully sent".into()).await; - Flash::success( - Redirect::to("/admin/notification"), - "Nachricht ausgeschickt", - ) -} - -#[post("/notification/user", data = "")] -async fn send_user( - db: &State, - data: Form, - admin: VorstandUser, -) -> Flash { - let d = data.into_inner(); - Log::create( - db, - format!("{admin:?} trying to send this notification: {d:?}"), - ) - .await; - - let Some(user) = User::find_by_id(db, d.user_id).await else { - return Flash::error(Redirect::to("/admin/notification"), "User gibt's ned"); - }; - - Notification::create(db, &user, &d.message, &d.category, None, None).await; - - Log::create(db, "Notification successfully sent".into()).await; - Flash::success( - Redirect::to("/admin/notification"), - "Nachricht ausgeschickt", - ) -} - -pub fn routes() -> Vec { - routes![index, send_user, send_group] -} diff --git a/src/tera/admin/schnupper.rs b/src/tera/admin/schnupper.rs deleted file mode 100644 index 9a751e1..0000000 --- a/src/tera/admin/schnupper.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::model::{ - role::Role, - user::{SchnupperBetreuerUser, User, UserWithDetails}, -}; -use futures::future::join_all; -use rocket::{get, request::FlashMessage, routes, Route, State}; -use rocket_dyn_templates::{tera::Context, Template}; -use sqlx::SqlitePool; - -#[get("/schnupper")] -async fn index( - db: &State, - user: SchnupperBetreuerUser, - flash: Option>, -) -> Template { - let schnupperant = Role::find_by_name(db, "schnupperant").await.unwrap(); - - let user_futures: Vec<_> = User::all_with_role(db, &schnupperant) - .await - .into_iter() - .map(|u| async move { UserWithDetails::from_user(u, db).await }) - .collect(); - let users: Vec = join_all(user_futures).await; - - let mut context = Context::new(); - if let Some(msg) = flash { - context.insert("flash", &msg.into_inner()); - } - context.insert("schnupperanten", &users); - context.insert( - "loggedin_user", - &UserWithDetails::from_user(user.user, db).await, - ); - - Template::render("admin/schnupper/index", context.into_json()) -} - -pub fn routes() -> Vec { - routes![index] -} diff --git a/src/tera/admin/user.rs b/src/tera/admin/user.rs index 192560b..77958e0 100644 --- a/src/tera/admin/user.rs +++ b/src/tera/admin/user.rs @@ -1,25 +1,15 @@ use std::collections::HashMap; -use crate::{ - model::{ - family::Family, - log::Log, - logbook::Logbook, - role::Role, - user::{ - AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, SchnupperBetreuerUser, User, - UserWithDetails, UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser, - }, - }, - tera::Config, +use crate::model::{ + log::Log, + role::Role, + user::{AdminUser, ManageUserUser, User, UserWithDetails}, }; use futures::future::join_all; -use lettre::Address; use rocket::{ form::Form, - fs::TempFile, get, - http::{ContentType, Status}, + http::Status, post, request::{FlashMessage, FromRequest, Outcome}, response::{Flash, Redirect}, @@ -46,7 +36,7 @@ impl<'r> FromRequest<'r> for Referer { #[get("/user?&")] async fn index( db: &State, - user: VorstandUser, + user: ManageUserUser, flash: Option>, sort: Option, asc: bool, @@ -56,16 +46,15 @@ async fn index( let user_futures: Vec<_> = User::all_with_order(db, &sort_column, asc) .await .into_iter() - .map(|u| async move { UserWithRolesAndMembershipPdf::from_user(db, u).await }) + .map(|u| async move { UserWithDetails::from_user(u, db).await }) .collect(); let user: User = user.into_inner(); let allowed_to_edit = ManageUserUser::new(db, user.clone()).await.is_some(); - let users: Vec = join_all(user_futures).await; + let users: Vec = join_all(user_futures).await; let roles = Role::all(db).await; - let families = Family::all_with_members(db).await; let mut context = Context::new(); if let Some(msg) = flash { @@ -74,7 +63,6 @@ async fn index( context.insert("allowed_to_edit", &allowed_to_edit); context.insert("users", &users); context.insert("roles", &roles); - context.insert("families", &families); context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await); Template::render("admin/user/index", context.into_json()) @@ -89,15 +77,14 @@ async fn index_admin( let user_futures: Vec<_> = User::all(db) .await .into_iter() - .map(|u| async move { UserWithRolesAndMembershipPdf::from_user(db, u).await }) + .map(|u| async move { UserWithDetails::from_user(u, db).await }) .collect(); - let users: Vec = join_all(user_futures).await; + let users: Vec = join_all(user_futures).await; let user: User = user.user; let allowed_to_edit = ManageUserUser::new(db, user.clone()).await.is_some(); let roles = Role::all(db).await; - let families = Family::all_with_members(db).await; let mut context = Context::new(); if let Some(msg) = flash { @@ -106,137 +93,11 @@ async fn index_admin( context.insert("allowed_to_edit", &allowed_to_edit); context.insert("users", &users); context.insert("roles", &roles); - context.insert("families", &families); context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await); Template::render("admin/user/index", context.into_json()) } -#[get("/user/fees")] -async fn fees( - db: &State, - user: VorstandUser, - flash: Option>, -) -> Template { - let mut context = Context::new(); - - let users = User::all_payer_groups(db).await; - let mut fees = Vec::new(); - for user in users { - if let Some(fee) = user.fee(db).await { - fees.push(fee); - } - } - - context.insert("fees", &fees); - - if let Some(msg) = flash { - context.insert("flash", &msg.into_inner()); - } - context.insert( - "loggedin_user", - &UserWithDetails::from_user(user.into_inner(), db).await, - ); - - Template::render("admin/user/fees", context.into_json()) -} - -#[get("/user/scheckbuch")] -async fn scheckbuch( - db: &State, - user: VorstandUser, - flash: Option>, -) -> Template { - let mut context = Context::new(); - - let scheckbooks = Role::find_by_name(db, "scheckbuch").await.unwrap(); - let scheckbooks = User::all_with_role(db, &scheckbooks).await; - let mut scheckbooks_with_roles = Vec::new(); - for s in scheckbooks { - scheckbooks_with_roles.push(( - Logbook::completed_with_user(db, &s).await, - UserWithDetails::from_user(s, db).await, - )) - } - - context.insert("scheckbooks", &scheckbooks_with_roles); - - if let Some(msg) = flash { - context.insert("flash", &msg.into_inner()); - } - context.insert( - "loggedin_user", - &UserWithDetails::from_user(user.into_inner(), db).await, - ); - - Template::render("admin/user/scheckbuch", context.into_json()) -} - -#[get("/user/fees/paid?")] -async fn fees_paid( - db: &State, - calling_user: AllowedToEditPaymentStatusUser, - user_ids: Vec, - referer: Referer, -) -> Flash { - let mut res = String::new(); - for user_id in user_ids { - let user = User::find_by_id(db, user_id).await.unwrap(); - res.push_str(&format!("{} + ", user.name)); - if user.has_role(db, "paid").await { - Log::create( - db, - format!( - "{} set fees NOT paid for '{}'", - calling_user.user.name, user.name - ), - ) - .await; - user.remove_role(db, &Role::find_by_name(db, "paid").await.unwrap()) - .await; - } else { - Log::create( - db, - format!( - "{} set fees paid for '{}'", - calling_user.user.name, user.name - ), - ) - .await; - user.add_role(db, &Role::find_by_name(db, "paid").await.unwrap()) - .await - .expect("paid role has no group"); - } - } - - res.truncate(res.len() - 3); // remove ' + ' from the end - - Flash::success( - Redirect::to(referer.0), - format!("Zahlungsstatus von {} erfolgreich geändert", res), - ) -} - -#[get("/user//send-welcome-mail")] -async fn send_welcome_mail( - db: &State, - _admin: ManageUserUser, - config: &State, - user: i32, -) -> Flash { - let Some(user) = User::find_by_id(db, user).await else { - return Flash::error(Redirect::to("/admin/user"), "User does not exist"); - }; - - match user.send_welcome_email(db, &config.smtp_pw).await { - Ok(()) => Flash::success( - Redirect::to("/admin/user"), - format!("Willkommens-Email wurde an {} versandt.", user.name), - ), - Err(e) => Flash::error(Redirect::to("/admin/user"), e), - } -} - #[get("/user//reset-pw")] async fn resetpw(db: &State, admin: ManageUserUser, user: i32) -> Flash { let user = User::find_by_id(db, user).await; @@ -274,27 +135,16 @@ async fn delete(db: &State, admin: ManageUserUser, user: i32) -> Fla } #[derive(FromForm, Debug)] -pub struct UserEditForm<'a> { +pub struct UserEditForm { pub(crate) id: i32, - pub(crate) dob: Option, - pub(crate) weight: Option, - pub(crate) sex: Option, + pub(crate) name: String, pub(crate) roles: HashMap, - pub(crate) member_since_date: Option, - pub(crate) birthdate: Option, - pub(crate) mail: Option, - pub(crate) nickname: Option, - pub(crate) notes: Option, - pub(crate) phone: Option, - pub(crate) address: Option, - pub(crate) family_id: Option, - pub(crate) membership_pdf: Option>, } #[post("/user", data = "", format = "multipart/form-data")] async fn update( db: &State, - data: Form>, + data: Form, admin: ManageUserUser, ) -> Flash { let user = User::find_by_id(db, data.id).await; @@ -311,31 +161,14 @@ async fn update( }; match user.update(db, data.into_inner()).await { - Ok(_) => Flash::success(Redirect::to("/admin/user"), "Successfully updated user"), + Ok(_) => Flash::success( + Redirect::to("/admin/user"), + "Mitglied erfolgreich geändert!", + ), Err(e) => Flash::error(Redirect::to("/admin/user"), e), } } -#[get("/user//membership")] -async fn download_membership_pdf( - db: &State, - admin: ManageUserUser, - user: i32, -) -> (ContentType, Vec) { - let user = User::find_by_id(db, user).await.unwrap(); - let user = UserWithMembershipPdf::from(db, user).await; - Log::create( - db, - format!( - "{} downloaded membership application for user: {}", - admin.user.name, user.user.name - ), - ) - .await; - - (ContentType::PDF, user.membership_pdf.unwrap()) -} - #[derive(FromForm, Debug)] struct UserAddForm<'r> { name: &'r str, @@ -353,132 +186,18 @@ async fn create( format!("{} created new user: {data:?}", admin.user.name), ) .await; - Flash::success(Redirect::to("/admin/user"), "Successfully created user") + Flash::success( + Redirect::to("/admin/user"), + "Mitglied erfolgreich angelegt!", + ) } else { Flash::error( Redirect::to("/admin/user"), - format!("User {} already exists", data.name), + format!("Mitlgied {} gibt/gab es bereits", data.name), ) } } -#[derive(FromForm, Debug)] -struct UserAddScheckbuchForm<'r> { - name: &'r str, - mail: &'r str, -} - -#[post("/user/new/scheckbuch", data = "")] -async fn create_scheckbuch( - db: &State, - data: Form>, - admin: VorstandUser, - config: &State, -) -> Flash { - // 1. Check mail adress - let mail = data.mail.trim(); - if mail.parse::
().is_err() { - return Flash::error( - Redirect::to("/admin/user/scheckbuch"), - "Keine gültige Mailadresse".to_string(), - ); - } - - // 2. Check name - let name = data.name.trim(); - if User::find_by_name(db, name).await.is_some() { - return Flash::error( - Redirect::to("/admin/user/scheckbuch"), - "Kann kein Scheckbuch erstellen, der Name wird bereits von einem User verwendet" - .to_string(), - ); - } - - // 3. Create user - User::create_with_mail(db, name, mail).await; - let user = User::find_by_name(db, name).await.unwrap(); - - // 4. Add 'scheckbuch' role - let scheckbuch = Role::find_by_name(db, "scheckbuch").await.unwrap(); - user.add_role(db, &scheckbuch) - .await - .expect("new user has no roles yet"); - - // 4. Send welcome mail (+ notification) - user.send_welcome_email(db, &config.smtp_pw).await.unwrap(); - - Log::create( - db, - format!("{} created new scheckbuch: {data:?}", admin.name), - ) - .await; - Flash::success(Redirect::to("/admin/user/scheckbuch"), format!("Scheckbuch erfolgreich erstellt. Eine E-Mail in der alles erklärt wird, wurde an {mail} verschickt.")) -} - -#[get("/user/move/schnupperant//to/scheckbuch")] -async fn schnupper_to_scheckbuch( - db: &State, - id: i32, - admin: SchnupperBetreuerUser, - config: &State, -) -> Flash { - let Some(user) = User::find_by_id(db, id).await else { - return Flash::error( - Redirect::to("/admin/schnupper"), - "user id not found".to_string(), - ); - }; - - if !user.has_role(db, "schnupperant").await { - return Flash::error( - Redirect::to("/admin/schnupper"), - "kein schnupperant...".to_string(), - ); - } - - let schnupperant = Role::find_by_name(db, "schnupperant").await.unwrap(); - let paid = Role::find_by_name(db, "paid").await.unwrap(); - user.remove_role(db, &schnupperant).await; - user.remove_role(db, &paid).await; - - let scheckbuch = Role::find_by_name(db, "scheckbuch").await.unwrap(); - user.add_role(db, &scheckbuch) - .await - .expect("just removed 'schnupperant' thus can't have a role with that group"); - - if let Some(no_einschreibgebuehr) = Role::find_by_name(db, "no-einschreibgebuehr").await { - user.add_role(db, &no_einschreibgebuehr) - .await - .expect("role doesn't have a group"); - } - - user.send_welcome_email(db, &config.smtp_pw).await.unwrap(); - - Log::create( - db, - format!( - "{} created new scheckbuch (from schnupperant): {}", - admin.name, user.name - ), - ) - .await; - Flash::success(Redirect::to("/admin/schnupper"), format!("Scheckbuch erfolgreich erstellt. Eine E-Mail in der alles erklärt wird, wurde an {} verschickt.", user.mail.unwrap())) -} - pub fn routes() -> Vec { - routes![ - index, - index_admin, - resetpw, - update, - create, - create_scheckbuch, - schnupper_to_scheckbuch, - delete, - fees, - fees_paid, - scheckbuch, - download_membership_pdf, - send_welcome_mail - ] + routes![index, index_admin, resetpw, update, create, delete] } diff --git a/src/tera/auth.rs b/src/tera/auth.rs index 511c6b9..1797fc1 100644 --- a/src/tera/auth.rs +++ b/src/tera/auth.rs @@ -73,7 +73,7 @@ async fn login( ); } Err(_) => { - return Flash::error(Redirect::to("/auth"), "Falscher Benutzername/Passwort. Du bist Vereinsmitglied und der Login klappt nicht? Kontaktiere unseren Schriftführer oder schreibe eine Mail an info@rudernlinz.at!"); + return Flash::error(Redirect::to("/auth"), "Falscher Benutzername/Passwort. Du bist Vereinsmitglied und der Login klappt nicht? Melde dich bitte unter it@verein.tld!"); } }; @@ -88,15 +88,7 @@ async fn login( ) .await; - // Check for redirect_url cookie and redirect accordingly - match cookies.get_private("redirect_url") { - Some(redirect_cookie) => { - let redirect_url = redirect_cookie.value().to_string(); - cookies.remove_private(redirect_cookie); // Remove the cookie after using it - Flash::success(Redirect::to(redirect_url), "Login erfolgreich") - } - None => Flash::success(Redirect::to("/"), "Login erfolgreich"), - } + Flash::success(Redirect::to("/"), "Login erfolgreich") } #[get("/set-pw/")] diff --git a/src/tera/board/boathouse.rs b/src/tera/board/boathouse.rs deleted file mode 100644 index 1c10408..0000000 --- a/src/tera/board/boathouse.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::model::{ - boat::Boat, - boathouse::Boathouse, - user::{AdminUser, UserWithDetails, 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; - let mut final_boats = Vec::new(); - for boat in boats { - if boat.boat.boathouse(db).await.is_none() && !boat.boat.external { - final_boats.push(boat); - } - } - - context.insert("boats", &final_boats); - - let boathouse = Boathouse::get(db).await; - context.insert("boathouse", &boathouse); - - context.insert( - "loggedin_user", - &UserWithDetails::from_user(admin.into_inner(), 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 deleted file mode 100644 index 8eff9d3..0000000 --- a/src/tera/board/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -use rocket::Route; - -pub mod achievement; -pub mod boathouse; - -pub fn routes() -> Vec { - let mut ret = Vec::new(); - ret.append(&mut boathouse::routes()); - ret.append(&mut achievement::routes()); - ret -} diff --git a/src/tera/boatreservation.rs b/src/tera/boatreservation.rs deleted file mode 100644 index a739dfc..0000000 --- a/src/tera/boatreservation.rs +++ /dev/null @@ -1,223 +0,0 @@ -use chrono::NaiveDate; -use rocket::{ - form::Form, - get, post, - request::FlashMessage, - response::{Flash, Redirect}, - routes, FromForm, Route, State, -}; -use rocket_dyn_templates::Template; -use sqlx::SqlitePool; -use tera::Context; - -use crate::{ - model::{ - boat::Boat, - boatreservation::{BoatReservation, BoatReservationToAdd}, - log::Log, - user::{DonauLinzUser, User, UserWithDetails}, - }, - tera::log::KioskCookie, -}; - -#[get("/")] -async fn index_kiosk( - db: &State, - flash: Option>, - _kiosk: KioskCookie, -) -> Template { - let boatreservations = BoatReservation::all_future(db).await; - - let mut context = Context::new(); - if let Some(msg) = flash { - context.insert("flash", &msg.into_inner()); - } - - let linz_boats = Boat::all_for_boatshouse(db).await; - let mut boats = Vec::new(); - for boat in linz_boats { - if boat.boat.owner.is_none() { - boats.push(boat); - } - } - - context.insert("boatreservations", &boatreservations); - context.insert("boats", &boats); - context.insert("user", &User::all(db).await); - context.insert("show_kiosk_header", &true); - - Template::render("boatreservations", context.into_json()) -} - -#[get("/", rank = 2)] -async fn index( - db: &State, - flash: Option>, - user: DonauLinzUser, -) -> Template { - let boatreservations = BoatReservation::all_future(db).await; - - let mut context = Context::new(); - if let Some(msg) = flash { - context.insert("flash", &msg.into_inner()); - } - - let linz_boats = Boat::all_for_boatshouse(db).await; - let mut boats = Vec::new(); - for boat in linz_boats { - if boat.boat.owner.is_none() { - boats.push(boat); - } - } - - context.insert("boatreservations", &boatreservations); - context.insert("boats", &boats); - context.insert("user", &User::all(db).await); - context.insert( - "loggedin_user", - &UserWithDetails::from_user(user.into_inner(), db).await, - ); - - Template::render("boatreservations", context.into_json()) -} - -#[derive(Debug, FromForm)] -pub struct FormBoatReservationToAdd<'r> { - pub boat_id: i64, - pub start_date: &'r str, - pub end_date: &'r str, - pub time_desc: &'r str, - pub usage: &'r str, - pub user_id_applicant: Option, -} - -#[post("/new", data = "", rank = 2)] -async fn create<'r>( - db: &State, - data: Form>, - user: DonauLinzUser, -) -> Flash { - let user_applicant: User = user.into_inner(); - let boat = Boat::find_by_id(db, data.boat_id as i32).await.unwrap(); - let boatreservation_to_add = BoatReservationToAdd { - boat: &boat, - start_date: NaiveDate::parse_from_str(data.start_date, "%Y-%m-%d").unwrap(), - end_date: NaiveDate::parse_from_str(data.end_date, "%Y-%m-%d").unwrap(), - time_desc: data.time_desc, - usage: data.usage, - user_applicant: &user_applicant, - }; - match BoatReservation::create(db, boatreservation_to_add).await { - Ok(_) => Flash::success( - Redirect::to("/boatreservation"), - "Reservierung erfolgreich hinzugefügt", - ), - Err(e) => Flash::error(Redirect::to("/boatreservation"), format!("Fehler: {e}")), - } -} - -#[post("/new", data = "")] -async fn create_from_kiosk<'r>( - db: &State, - data: Form>, - _kiosk: KioskCookie, -) -> Flash { - let user_applicant: User = User::find_by_id(db, data.user_id_applicant.unwrap() as i32) - .await - .unwrap(); - let boat = Boat::find_by_id(db, data.boat_id as i32).await.unwrap(); - let boatreservation_to_add = BoatReservationToAdd { - boat: &boat, - start_date: NaiveDate::parse_from_str(data.start_date, "%Y-%m-%d").unwrap(), - end_date: NaiveDate::parse_from_str(data.end_date, "%Y-%m-%d").unwrap(), - time_desc: data.time_desc, - usage: data.usage, - user_applicant: &user_applicant, - }; - match BoatReservation::create(db, boatreservation_to_add).await { - Ok(_) => Flash::success( - Redirect::to("/boatreservation"), - "Reservierung erfolgreich hinzugefügt", - ), - Err(e) => Flash::error(Redirect::to("/boatreservation"), format!("Fehler: {e}")), - } -} - -#[derive(FromForm, Debug)] -pub struct ReservationEditForm { - pub(crate) id: i32, - pub(crate) time_desc: String, - pub(crate) usage: String, -} - -#[post("/", data = "")] -async fn update( - db: &State, - data: Form, - user: User, -) -> Flash { - let Some(reservation) = BoatReservation::find_by_id(db, data.id).await else { - return Flash::error( - Redirect::to("/boatreservation"), - format!("Reservation with ID {} does not exist!", data.id), - ); - }; - - if user.id != reservation.user_id_applicant && !user.has_role(db, "admin").await { - return Flash::error( - Redirect::to("/boatreservation"), - "Not allowed to update reservation (only admins + creator do so).".to_string(), - ); - } - - Log::create( - db, - format!( - "{} updated reservation from {reservation:?} to {data:?}", - user.name - ), - ) - .await; - - reservation.update(db, data.into_inner()).await; - - Flash::success( - Redirect::to("/boatreservation"), - "Reservierung erfolgreich bearbeitet", - ) -} - -#[get("//delete")] -async fn delete<'r>( - db: &State, - reservation_id: i32, - user: DonauLinzUser, -) -> Flash { - let reservation = BoatReservation::find_by_id(db, reservation_id) - .await - .unwrap(); - - if user.id == reservation.user_id_applicant || user.has_role(db, "admin").await { - reservation.delete(db).await; - Flash::success( - Redirect::to("/boatreservation"), - "Reservierung erfolgreich gelöscht", - ) - } else { - Flash::error( - Redirect::to("/boatreservation"), - "Nur der Reservierer darf die Reservierung löschen.".to_string(), - ) - } -} - -pub fn routes() -> Vec { - routes![ - index, - index_kiosk, - create, - create_from_kiosk, - delete, - update - ] -} diff --git a/src/tera/cox.rs b/src/tera/cox.rs index 31c791c..a89a865 100644 --- a/src/tera/cox.rs +++ b/src/tera/cox.rs @@ -11,32 +11,9 @@ use crate::model::{ log::Log, trip::{self, CoxHelpError, Trip, TripDeleteError, TripHelpDeleteError, TripUpdateError}, tripdetails::{TripDetails, TripDetailsToAdd}, - user::{AllowedToUpdateTripToAlwaysBeShownUser, ErgoUser, SteeringUser, User}, + user::{AllowedToUpdateTripToAlwaysBeShownUser, SteeringUser, User}, }; -#[post("/trip", data = "", rank = 2)] -async fn create_ergo( - db: &State, - data: Form>, - cox: ErgoUser, -) -> Flash { - let trip_details_id = TripDetails::create(db, data.into_inner()).await; - let trip_details = TripDetails::find_by_id(db, trip_details_id).await.unwrap(); //Okay, bc just - //created - Trip::new_own_ergo(db, &cox, trip_details).await; //TODO: fix - - //Log::create( - // db, - // format!( - // "Cox {} created trip on {} @ {} for {} rower", - // cox.name, trip_details.day, trip_details.planned_starting_time, trip_details.max_people, - // ), - //) - //.await; - - Flash::success(Redirect::to("/planned"), "Ausfahrt erfolgreich erstellt.") -} - #[post("/trip", data = "")] async fn create( db: &State, @@ -57,7 +34,7 @@ async fn create( //) //.await; - Flash::success(Redirect::to("/planned"), "Ausfahrt erfolgreich erstellt.") + Flash::success(Redirect::to("/"), "Ausfahrt erfolgreich erstellt.") } #[derive(FromForm)] @@ -85,23 +62,19 @@ async fn update( is_locked: data.is_locked, }; match Trip::update_own(db, &update).await { - Ok(_) => Flash::success( - Redirect::to("/planned"), - "Ausfahrt erfolgreich aktualisiert.", - ), + Ok(_) => Flash::success(Redirect::to("/"), "Ausfahrt erfolgreich aktualisiert."), Err(TripUpdateError::NotYourTrip) => { - Flash::error(Redirect::to("/planned"), "Nicht deine Ausfahrt!") + Flash::error(Redirect::to("/"), "Nicht deine Ausfahrt!") + } + Err(TripUpdateError::TripTypeNotAllowed) => { + Flash::error(Redirect::to("/"), "Du darfst nur Ergo-Events erstellen") } - Err(TripUpdateError::TripTypeNotAllowed) => Flash::error( - Redirect::to("/planned"), - "Du darfst nur Ergo-Events erstellen", - ), Err(TripUpdateError::TripDetailsDoesNotExist) => { - Flash::error(Redirect::to("/planned"), "Ausfahrt gibt's nicht") + Flash::error(Redirect::to("/"), "Ausfahrt gibt's nicht") } } } else { - Flash::error(Redirect::to("/planned"), "Ausfahrt gibt's nicht") + Flash::error(Redirect::to("/"), "Ausfahrt gibt's nicht") } } @@ -113,12 +86,9 @@ async fn toggle_always_show( ) -> Flash { if let Some(trip) = Trip::find_by_id(db, trip_id).await { trip.toggle_always_show(db).await; - Flash::success( - Redirect::to("/planned"), - "'Immer anzeigen' erfolgreich gesetzt!", - ) + Flash::success(Redirect::to("/"), "'Immer anzeigen' erfolgreich gesetzt!") } else { - Flash::error(Redirect::to("/planned"), "Ausfahrt gibt's nicht") + Flash::error(Redirect::to("/"), "Ausfahrt gibt's nicht") } } @@ -135,24 +105,24 @@ async fn join(db: &State, planned_event_id: i64, cox: SteeringUser) ), ) .await; - Flash::success(Redirect::to("/planned"), "Danke für's helfen!") + Flash::success(Redirect::to("/"), "Danke für's helfen!") } Err(CoxHelpError::CanceledEvent) => { - Flash::error(Redirect::to("/planned"), "Die Ausfahrt wurde leider abgesagt...") + Flash::error(Redirect::to("/"), "Die Ausfahrt wurde leider abgesagt...") } Err(CoxHelpError::AlreadyRegisteredAsCox) => { - Flash::error(Redirect::to("/planned"), "Du hilfst bereits aus!") + Flash::error(Redirect::to("/"), "Du hilfst bereits aus!") } Err(CoxHelpError::AlreadyRegisteredAsRower) => Flash::error( - Redirect::to("/planned"), + Redirect::to("/"), "Du hast dich bereits als Ruderer angemeldet!", ), Err(CoxHelpError::DetailsLocked) => { - Flash::error(Redirect::to("/planned"), "Die Bootseinteilung wurde bereits gemacht. Wenn du noch steuern möchtest, frag bitte bei einer bereits angemeldeten Steuerperson nach, ob das noch möglich ist.") + Flash::error(Redirect::to("/"), "Die Bootseinteilung wurde bereits gemacht. Wenn du noch steuern möchtest, frag bitte bei einer bereits angemeldeten Steuerperson nach, ob das noch möglich ist.") } } } else { - Flash::error(Redirect::to("/planned"), "Event gibt's nicht") + Flash::error(Redirect::to("/"), "Event gibt's nicht") } } @@ -160,18 +130,18 @@ async fn join(db: &State, planned_event_id: i64, cox: SteeringUser) async fn remove_trip(db: &State, trip_id: i64, cox: User) -> Flash { let trip = Trip::find_by_id(db, trip_id).await; match trip { - None => Flash::error(Redirect::to("/planned"), "Trip gibt's nicht!"), + None => Flash::error(Redirect::to("/"), "Trip gibt's nicht!"), Some(trip) => match trip.delete(db, &cox).await { Ok(_) => { Log::create(db, format!("Cox {} deleted trip.id={}", cox.name, trip_id)).await; - Flash::success(Redirect::to("/planned"), "Erfolgreich gelöscht!") + Flash::success(Redirect::to("/"), "Erfolgreich gelöscht!") } Err(TripDeleteError::SomebodyAlreadyRegistered) => Flash::error( - Redirect::to("/planned"), + Redirect::to("/"), "Ausfahrt kann nicht gelöscht werden, da bereits jemand registriert ist!", ), Err(TripDeleteError::NotYourTrip) => { - Flash::error(Redirect::to("/planned"), "Nicht deine Ausfahrt!") + Flash::error(Redirect::to("/"), "Nicht deine Ausfahrt!") } }, } @@ -195,24 +165,23 @@ async fn remove( ) .await; - Flash::success(Redirect::to("/planned"), "Erfolgreich abgemeldet!") + Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!") } Err(TripHelpDeleteError::DetailsLocked) => { - Flash::error(Redirect::to("/planned"), "Die Bootseinteilung wurde bereits gemacht. Wenn du doch nicht steuern kannst, melde dich bitte unbedingt schnellstmöglich bei einer anderen Steuerperson!") + Flash::error(Redirect::to("/"), "Die Bootseinteilung wurde bereits gemacht. Wenn du doch nicht steuern kannst, melde dich bitte unbedingt schnellstmöglich bei einer anderen Steuerperson!") } Err(TripHelpDeleteError::CoxNotHelping) => { - Flash::error(Redirect::to("/planned"), "Steuermann hilft nicht aus...") + Flash::error(Redirect::to("/"), "Steuermann hilft nicht aus...") } } } else { - Flash::error(Redirect::to("/planned"), "Planned_event does not exist.") + Flash::error(Redirect::to("/"), "Planned_event does not exist.") } } pub fn routes() -> Vec { routes![ create, - create_ergo, join, remove, remove_trip, @@ -260,7 +229,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -310,7 +279,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -349,7 +318,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -389,7 +358,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -417,7 +386,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -430,7 +399,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -454,14 +423,14 @@ mod test { .body("name=cox&password=cox"); // Add the form data to the request body; login.dispatch().await; - let req = client.get("/planned/join/1"); + let req = client.get("/join/1"); let _ = req.dispatch().await; let req = client.get("/cox/join/1"); let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -492,7 +461,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -533,7 +502,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -561,7 +530,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -589,7 +558,7 @@ mod test { let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() diff --git a/src/tera/mod.rs b/src/tera/mod.rs index 486661e..c951b81 100644 --- a/src/tera/mod.rs +++ b/src/tera/mod.rs @@ -1,14 +1,12 @@ use std::{fs::OpenOptions, io::Write}; -use chrono::{Datelike, Local}; +use chrono::Local; use rocket::{ catch, catchers, fairing::{AdHoc, Fairing, Info, Kind}, - form::Form, fs::FileServer, get, http::Cookie, - post, request::FlashMessage, response::{Flash, Redirect}, routes, @@ -20,30 +18,17 @@ use serde::Deserialize; use sqlx::SqlitePool; use tera::Context; -use crate::{ - model::{ - logbook::Logbook, - notification::Notification, - personal::Achievements, - role::Role, - user::{User, UserWithDetails}, - }, - SCHECKBUCH, +use crate::model::{ + role::Role, + user::{User, UserWithDetails}, }; pub(crate) mod admin; mod auth; -pub(crate) mod board; -mod boatdamage; -pub(crate) mod boatreservation; mod cox; -mod ergo; -mod log; mod misc; mod notification; mod planned; -mod stat; -pub(crate) mod trailerreservation; #[derive(FromForm, Debug)] struct LoginForm<'r> { @@ -51,31 +36,6 @@ struct LoginForm<'r> { password: &'r str, } -#[get("/")] -async fn index(db: &State, user: User, flash: Option>) -> Template { - let mut context = Context::new(); - if let Some(msg) = flash { - context.insert("flash", &msg.into_inner()); - } - - if user.has_role(db, "scheckbuch").await { - let last_trips = Logbook::completed_with_user(db, &user).await; - context.insert("last_trips", &last_trips); - } - - let date = chrono::Utc::now(); - if date.month() <= 3 || date.month() >= 10 { - context.insert("show_quick_ergo_button", "yes"); - } - - context.insert("achievements", &Achievements::for_user(db, &user).await); - context.insert("notifications", &Notification::for_user(db, &user).await); - context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await); - context.insert("costs_scheckbuch", &SCHECKBUCH); - - Template::render("index", context.into_json()) -} - #[get("/impressum")] async fn impressum(db: &State, user: Option) -> Template { let mut context = Context::new(); @@ -107,22 +67,6 @@ async fn steering(db: &State, user: User, flash: Option, login: Form>) -> String { - if let Ok(user) = User::login(db, login.name, login.password).await { - if user.has_role(db, "allow_website_login").await { - return String::from("SUCC"); - } - if user.has_role(db, "admin").await { - return String::from("SUCC"); - } - if user.has_role(db, "Vorstand").await { - return String::from("SUCC"); - } - } - "FAIL".into() -} - #[catch(401)] //Unauthorized fn unauthorized_error(req: &Request) -> Redirect { // Save the URL the user tried to access, to be able to go there once logged in @@ -134,60 +78,6 @@ fn unauthorized_error(req: &Request) -> Redirect { Redirect::to("/auth") } -#[derive(FromForm, Debug)] -struct NewBlogpostForm<'r> { - article_url: &'r str, - article_title: &'r str, - pw: &'r str, -} - -#[post("/", data = "")] -async fn new_blogpost( - db: &State, - blogpost: Form>, - config: &State, -) -> String { - if blogpost.pw == config.wordpress_key { - let member = Role::find_by_name(db, "Donau Linz").await.unwrap(); - Notification::create_for_role( - db, - &member, - &urlencoding::decode(blogpost.article_title).expect("UTF-8"), - "Neuer Blogpost", - Some(blogpost.article_url), - None, - ) - .await; - "ACK".into() - } else { - "WRONG pw".into() - } -} - -#[derive(FromForm, Debug)] -struct BlogpostUnpublishedForm<'r> { - article_url: &'r str, - pw: &'r str, -} - -#[post("/", data = "")] -async fn blogpost_unpublished( - db: &State, - blogpost: Form>, - config: &State, -) -> String { - if blogpost.pw == config.wordpress_key { - Notification::delete_by_link( - db, - &urlencoding::decode(blogpost.article_url).expect("UTF-8"), - ) - .await; - "ACK".into() - } else { - "WRONG pw".into() - } -} - #[catch(403)] //forbidden fn forbidden_error() -> Flash { Flash::error(Redirect::to("/"), "Keine Berechtigung für diese Aktion. Wenn du der Meinung bist, dass du das machen darfst, melde dich bitte bei it@rudernlinz.at.") @@ -262,22 +152,12 @@ pub struct Config { pub fn config(rocket: Rocket) -> Rocket { rocket - .mount("/", routes![index, steering, impressum]) + .mount("/", routes![steering, impressum]) .mount("/auth", auth::routes()) - .mount("/wikiauth", routes![wikiauth]) - .mount("/new-blogpost", routes![new_blogpost]) - .mount("/blogpost-unpublished", routes![blogpost_unpublished]) - .mount("/log", log::routes()) - .mount("/planned", planned::routes()) - .mount("/ergo", ergo::routes()) + .mount("/", planned::routes()) .mount("/notification", notification::routes()) - .mount("/stat", stat::routes()) - .mount("/boatdamage", boatdamage::routes()) - .mount("/boatreservation", boatreservation::routes()) - .mount("/trailerreservation", trailerreservation::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/src/tera/planned.rs b/src/tera/planned.rs index 3102818..fa77f18 100644 --- a/src/tera/planned.rs +++ b/src/tera/planned.rs @@ -13,20 +13,14 @@ use crate::{ log::Log, tripdetails::TripDetails, triptype::TripType, - user::{AllowedForPlannedTripsUser, User, UserWithDetails}, + user::{User, UserWithDetails}, usertrip::{UserTrip, UserTripDeleteError, UserTripError}, }, AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD, }; #[get("/")] -async fn index( - db: &State, - user: AllowedForPlannedTripsUser, - flash: Option>, -) -> Template { - let user: User = user.into_inner(); - +async fn index(db: &State, user: User, flash: Option>) -> Template { let mut context = Context::new(); if user.allowed_to_steer(db).await @@ -47,25 +41,22 @@ async fn index( "allowed_to_update_always_show_trip", &user.allowed_to_update_always_show_trip(db).await, ); - context.insert("fee", &user.fee(db).await); context.insert( "amount_days_to_show_trips_ahead", &AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD, ); context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await); context.insert("days", &days); - Template::render("planned", context.into_json()) + Template::render("index", context.into_json()) } #[get("/join/?")] async fn join( db: &State, trip_details_id: i64, - user: AllowedForPlannedTripsUser, + user: User, user_note: Option, ) -> Flash { - let user: User = user.into_inner(); - let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { return Flash::error(Redirect::to("/"), "Trip_details do not exist."); }; @@ -90,35 +81,35 @@ async fn join( ), ).await; } - Flash::success(Redirect::to("/planned"), "Erfolgreich angemeldet!") + Flash::success(Redirect::to("/"), "Erfolgreich angemeldet!") } Err(UserTripError::EventAlreadyFull) => { - Flash::error(Redirect::to("/planned"), "Event bereits ausgebucht!") + Flash::error(Redirect::to("/"), "Event bereits ausgebucht!") } Err(UserTripError::AlreadyRegistered) => { - Flash::error(Redirect::to("/planned"), "Du nimmst bereits teil!") + Flash::error(Redirect::to("/"), "Du nimmst bereits teil!") } Err(UserTripError::AlreadyRegisteredAsCox) => { - Flash::error(Redirect::to("/planned"), "Du hilfst bereits als Steuerperson aus!") + Flash::error(Redirect::to("/"), "Du hilfst bereits als Steuerperson aus!") } Err(UserTripError::CantRegisterAtOwnEvent) => Flash::error( - Redirect::to("/planned"), + Redirect::to("/"), "Du kannst bei einer selbst ausgeschriebenen Fahrt nicht mitrudern ;)", ), Err(UserTripError::GuestNotAllowedForThisEvent) => Flash::error( - Redirect::to("/planned"), + Redirect::to("/"), "Bei dieser Ausfahrt können leider keine Gäste mitfahren.", ), Err(UserTripError::NotAllowedToAddGuest) => Flash::error( - Redirect::to("/planned"), + Redirect::to("/"), "Du darfst keine Gäste hinzufügen.", ), Err(UserTripError::NotVisibleToUser) => Flash::error( - Redirect::to("/planned"), + Redirect::to("/"), "Du kannst dich nicht registrieren, weil du die Ausfahrt gar nicht sehen solltest.", ), Err(UserTripError::DetailsLocked) => Flash::error( - Redirect::to("/planned"), + Redirect::to("/"), "Die Bootseinteilung wurde bereits gemacht. Wenn du noch mitrudern möchtest, frag bitte bei einer angemeldeten Steuerperson nach, ob das noch möglich ist.", ), } @@ -128,13 +119,11 @@ async fn join( async fn remove_guest( db: &State, trip_details_id: i64, - user: AllowedForPlannedTripsUser, + user: User, name: String, ) -> Flash { - let user: User = user.into_inner(); - let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { - return Flash::error(Redirect::to("/planned"), "TripDetailsId does not exist"); + return Flash::error(Redirect::to("/"), "TripDetailsId does not exist"); }; match UserTrip::delete(db, &user, &trip_details, Some(name)).await { @@ -148,7 +137,7 @@ async fn remove_guest( ) .await; - Flash::success(Redirect::to("/planned"), "Erfolgreich abgemeldet!") + Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!") } Err(UserTripDeleteError::DetailsLocked) => { Log::create( @@ -160,32 +149,26 @@ async fn remove_guest( ) .await; - Flash::error(Redirect::to("/planned"), "Die Bootseinteilung wurde bereits gemacht. Wenn du doch nicht mitrudern kannst, melde dich bitte unbedingt schnellstmöglich bei einer angemeldeten Steuerperson!") + Flash::error(Redirect::to("/"), "Die Bootseinteilung wurde bereits gemacht. Wenn du doch nicht mitrudern kannst, melde dich bitte unbedingt schnellstmöglich bei einer angemeldeten Steuerperson!") } Err(UserTripDeleteError::GuestNotParticipating) => { - Flash::error(Redirect::to("/planned"), "Gast nicht angemeldet.") + Flash::error(Redirect::to("/"), "Gast nicht angemeldet.") } Err(UserTripDeleteError::NotVisibleToUser) => Flash::error( - Redirect::to("/planned"), + Redirect::to("/"), "Du kannst dich nicht abmelden, weil du die Ausfahrt gar nicht sehen solltest.", ), Err(UserTripDeleteError::NotAllowedToDeleteGuest) => Flash::error( - Redirect::to("/planned"), + Redirect::to("/"), "Keine Berechtigung um den Gast zu entfernen.", ), } } #[get("/remove/")] -async fn remove( - db: &State, - trip_details_id: i64, - user: AllowedForPlannedTripsUser, -) -> Flash { - let user: User = user.into_inner(); - +async fn remove(db: &State, trip_details_id: i64, user: User) -> Flash { let Some(trip_details) = TripDetails::find_by_id(db, trip_details_id).await else { - return Flash::error(Redirect::to("/planned"), "TripDetailsId does not exist"); + return Flash::error(Redirect::to("/"), "TripDetailsId does not exist"); }; match UserTrip::delete(db, &user, &trip_details, None).await { @@ -199,7 +182,7 @@ async fn remove( ) .await; - Flash::success(Redirect::to("/planned"), "Erfolgreich abgemeldet!") + Flash::success(Redirect::to("/"), "Erfolgreich abgemeldet!") } Err(UserTripDeleteError::DetailsLocked) => { Log::create( @@ -211,7 +194,7 @@ async fn remove( ) .await; - Flash::error(Redirect::to("/planned"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.") + Flash::error(Redirect::to("/"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.") } Err(UserTripDeleteError::NotVisibleToUser) => { Log::create( @@ -223,7 +206,7 @@ async fn remove( ) .await; - Flash::error(Redirect::to("/planned"), "Abmeldung nicht möglich, da du dieses Event eigentlich gar nicht sehen solltest...") + Flash::error(Redirect::to("/"), "Abmeldung nicht möglich, da du dieses Event eigentlich gar nicht sehen solltest...") } Err(_) => { panic!("Not possible to be here"); @@ -259,11 +242,11 @@ mod test { .body("name=rower&password=rower"); // Add the form data to the request body; login.dispatch().await; - let req = client.get("/planned/join/1"); + let req = client.get("/join/1"); let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -272,11 +255,11 @@ mod test { assert_eq!(flash_cookie.value(), "7:successErfolgreich angemeldet!"); - let req = client.get("/planned/remove/1"); + let req = client.get("/remove/1"); let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); - assert_eq!(response.headers().get("Location").next(), Some("/planned")); + assert_eq!(response.headers().get("Location").next(), Some("/")); let flash_cookie = response .cookies() @@ -300,7 +283,7 @@ mod test { .body("name=rower&password=rower"); // Add the form data to the request body; login.dispatch().await; - let req = client.get("/planned/join/9999"); + let req = client.get("/join/9999"); let response = req.dispatch().await; assert_eq!(response.status(), Status::SeeOther); diff --git a/src/tera/trailerreservation.rs b/src/tera/trailerreservation.rs deleted file mode 100644 index 4746ec9..0000000 --- a/src/tera/trailerreservation.rs +++ /dev/null @@ -1,211 +0,0 @@ -use chrono::NaiveDate; -use rocket::{ - form::Form, - get, post, - request::FlashMessage, - response::{Flash, Redirect}, - routes, FromForm, Route, State, -}; -use rocket_dyn_templates::Template; -use sqlx::SqlitePool; -use tera::Context; - -use crate::{ - model::{ - log::Log, - trailer::Trailer, - trailerreservation::{TrailerReservation, TrailerReservationToAdd}, - user::{DonauLinzUser, User, UserWithDetails}, - }, - tera::log::KioskCookie, -}; - -#[get("/")] -async fn index_kiosk( - db: &State, - flash: Option>, - _kiosk: KioskCookie, -) -> Template { - let trailerreservations = TrailerReservation::all_future(db).await; - - let mut context = Context::new(); - if let Some(msg) = flash { - context.insert("flash", &msg.into_inner()); - } - - context.insert("trailerreservations", &trailerreservations); - context.insert("trailers", &Trailer::all(db).await); - context.insert("user", &User::all(db).await); - context.insert("show_kiosk_header", &true); - - Template::render("trailerreservations", context.into_json()) -} - -#[get("/", rank = 2)] -async fn index( - db: &State, - flash: Option>, - user: DonauLinzUser, -) -> Template { - let trailerreservations = TrailerReservation::all_future(db).await; - - let mut context = Context::new(); - if let Some(msg) = flash { - context.insert("flash", &msg.into_inner()); - } - - context.insert("trailerreservations", &trailerreservations); - context.insert("trailers", &Trailer::all(db).await); - context.insert("user", &User::all(db).await); - context.insert( - "loggedin_user", - &UserWithDetails::from_user(user.into_inner(), db).await, - ); - - Template::render("trailerreservations", context.into_json()) -} - -#[derive(Debug, FromForm)] -pub struct FormTrailerReservationToAdd<'r> { - pub trailer_id: i64, - pub start_date: &'r str, - pub end_date: &'r str, - pub time_desc: &'r str, - pub usage: &'r str, - pub user_id_applicant: Option, -} - -#[post("/new", data = "", rank = 2)] -async fn create<'r>( - db: &State, - data: Form>, - user: DonauLinzUser, -) -> Flash { - let user_applicant: User = user.into_inner(); - let trailer = Trailer::find_by_id(db, data.trailer_id as i32) - .await - .unwrap(); - let trailerreservation_to_add = TrailerReservationToAdd { - trailer: &trailer, - start_date: NaiveDate::parse_from_str(data.start_date, "%Y-%m-%d").unwrap(), - end_date: NaiveDate::parse_from_str(data.end_date, "%Y-%m-%d").unwrap(), - time_desc: data.time_desc, - usage: data.usage, - user_applicant: &user_applicant, - }; - match TrailerReservation::create(db, trailerreservation_to_add).await { - Ok(_) => Flash::success( - Redirect::to("/trailerreservation"), - "Reservierung erfolgreich hinzugefügt", - ), - Err(e) => Flash::error(Redirect::to("/trailerreservation"), format!("Fehler: {e}")), - } -} - -#[post("/new", data = "")] -async fn create_from_kiosk<'r>( - db: &State, - data: Form>, - _kiosk: KioskCookie, -) -> Flash { - let user_applicant: User = User::find_by_id(db, data.user_id_applicant.unwrap() as i32) - .await - .unwrap(); - let trailer = Trailer::find_by_id(db, data.trailer_id as i32) - .await - .unwrap(); - let trailerreservation_to_add = TrailerReservationToAdd { - trailer: &trailer, - start_date: NaiveDate::parse_from_str(data.start_date, "%Y-%m-%d").unwrap(), - end_date: NaiveDate::parse_from_str(data.end_date, "%Y-%m-%d").unwrap(), - time_desc: data.time_desc, - usage: data.usage, - user_applicant: &user_applicant, - }; - match TrailerReservation::create(db, trailerreservation_to_add).await { - Ok(_) => Flash::success( - Redirect::to("/trailerreservation"), - "Reservierung erfolgreich hinzugefügt", - ), - Err(e) => Flash::error(Redirect::to("/trailerreservation"), format!("Fehler: {e}")), - } -} - -#[derive(FromForm, Debug)] -pub struct ReservationEditForm { - pub(crate) id: i32, - pub(crate) time_desc: String, - pub(crate) usage: String, -} - -#[post("/", data = "")] -async fn update( - db: &State, - data: Form, - user: User, -) -> Flash { - let Some(reservation) = TrailerReservation::find_by_id(db, data.id).await else { - return Flash::error( - Redirect::to("/trailerreservation"), - format!("Reservation with ID {} does not exist!", data.id), - ); - }; - - if user.id != reservation.user_id_applicant && !user.has_role(db, "admin").await { - return Flash::error( - Redirect::to("/trailerreservation"), - "Not allowed to update reservation (only admins + creator do so).".to_string(), - ); - } - - Log::create( - db, - format!( - "{} updated reservation from {reservation:?} to {data:?}", - user.name - ), - ) - .await; - - reservation.update(db, data.into_inner()).await; - - Flash::success( - Redirect::to("/trailerreservation"), - "Reservierung erfolgreich bearbeitet", - ) -} - -#[get("//delete")] -async fn delete<'r>( - db: &State, - reservation_id: i32, - user: DonauLinzUser, -) -> Flash { - let reservation = TrailerReservation::find_by_id(db, reservation_id) - .await - .unwrap(); - - if user.id == reservation.user_id_applicant || user.has_role(db, "admin").await { - reservation.delete(db).await; - Flash::success( - Redirect::to("/trailerreservation"), - "Reservierung erfolgreich gelöscht", - ) - } else { - Flash::error( - Redirect::to("/trailerreservation"), - "Nur der Reservierer darf die Reservierung löschen.".to_string(), - ) - } -} - -pub fn routes() -> Vec { - routes![ - index, - index_kiosk, - create, - create_from_kiosk, - delete, - update - ] -} diff --git a/staging-diff.sql b/staging-diff.sql deleted file mode 100644 index 6fb21fc..0000000 --- a/staging-diff.sql +++ /dev/null @@ -1,5 +0,0 @@ --- test user -INSERT INTO user(name) VALUES('Marie'); -INSERT INTO "user_role" (user_id, role_id) VALUES((SELECT id from user where name = 'Marie'),(SELECT id FROM role where name = 'Donau Linz')); -INSERT INTO user(name) VALUES('Philipp'); -INSERT INTO "user_role" (user_id, role_id) VALUES((SELECT id from user where name = 'Philipp'),(SELECT id FROM role where name = 'Donau Linz')); diff --git a/templates/achievement.html.tera b/templates/achievement.html.tera deleted file mode 100644 index fea7399..0000000 --- a/templates/achievement.html.tera +++ /dev/null @@ -1,100 +0,0 @@ -{% import "includes/macros" as macros %} -{% import "includes/forms/log" as log %} -{% extends "base" %} -{% block content %} - -
-

Abzeichen für {{ rowingbadge_year }}

-
- - - - - - - - - - - - - - - - - - {% for person in people %} - {% set user = person.0 %} - {% set achievement = person.1 %} - - - - - - - - {% if achievement.rowingbadge %} - {% set badge = achievement.rowingbadge %} - - - - - - {% else %} - - - - - - {% endif %} - - {% endfor %} - -
NameErster LogLetzter LogGesamt-KMÄquatorpreis (ÄP) - ÄP diese -
- Saison bekommen -
- Fahrtenabzeichen (FA) -
- geschafft -
FA - KMFA - fehlende KMEintagesausfahrtenMehrtagesausfahrten
{{ user.name }} - {% if achievement.year_first_mentioned %}{{ achievement.year_first_mentioned }}{% endif %} - - {% if achievement.year_last_mentioned %}{{ achievement.year_last_mentioned }}{% endif %} - {{ achievement.all_time_km }}{{ achievement.curr_equatorprice_name }} - {% if achievement.new_equatorprice_this_season %} - 🎉 - {% else %} - - - {% endif %} - - {% if badge.achieved %} - ja - {% else %} - nein - {% endif %} - {{ badge.rowed_km }} / {{ badge.required_km }}{{ badge.missing_km }} -
- - > {{ badge.single_day_trips_required_distance }} km: {{ badge.single_day_trips_over_required_distance | length }} / 2 - - {% for log in badge.single_day_trips_over_required_distance %} - {{ log::show_old(log=log, state="completed", only_ones=false, index=loop.index, hide_type=true) }} - {% endfor %} -
-
-
- - > {{ badge.multi_day_trips_required_distance }} km: {{ badge.multi_day_trips_over_required_distance | length }} / 1 - - {% for log in badge.multi_day_trips_over_required_distance %} - {{ log::show_old(log=log, state="completed", only_ones=false, index=loop.index, hide_type=true) }} - {% endfor %} -
-
Geb.datum fehlt 👻Geb.datum fehlt 👻Geb.datum fehlt 👻Geb.datum fehlt 👻Geb.datum fehlt 👻
-
-
- - -{% endblock content %} diff --git a/templates/admin/boat/index.html.tera b/templates/admin/boat/index.html.tera deleted file mode 100644 index a79cbbd..0000000 --- a/templates/admin/boat/index.html.tera +++ /dev/null @@ -1,10 +0,0 @@ -{% import "includes/macros" as macros %} -{% import "includes/forms/boat" as boat %} -{% extends "base" %} -{% block content %} -
-

Boats

- {{ boat::new() }} - {% for boat in boats %}{{ boat::edit(boat=boat, uuid=loop.index) }}{% endfor %} -
-{% endblock content %} diff --git a/templates/admin/list/index.html.tera b/templates/admin/list/index.html.tera deleted file mode 100644 index a020b1f..0000000 --- a/templates/admin/list/index.html.tera +++ /dev/null @@ -1,11 +0,0 @@ -{% import "includes/macros" as macros %} -{% extends "base" %} -{% block content %} -
-

List

-
- - -
-
-{% endblock content %} diff --git a/templates/admin/list/result.html.tera b/templates/admin/list/result.html.tera deleted file mode 100644 index a5863c2..0000000 --- a/templates/admin/list/result.html.tera +++ /dev/null @@ -1,10 +0,0 @@ -{% import "includes/macros" as macros %} -{% extends "base" %} -{% block content %} -
-

List - Result

-
    - {% for person in result %}
  1. {{ person }}
  2. {% endfor %} -
-
-{% endblock content %} diff --git a/templates/admin/mail.html.tera b/templates/admin/mail.html.tera deleted file mode 100644 index 6c97d4c..0000000 --- a/templates/admin/mail.html.tera +++ /dev/null @@ -1,27 +0,0 @@ -{% import "includes/macros" as macros %} -{% import "includes/forms/boat" as boat %} -{% extends "base" %} -{% block content %} -
-

Mail

-
- -
-
-{% endblock content %} diff --git a/templates/admin/schnupper/index.html.tera b/templates/admin/schnupper/index.html.tera deleted file mode 100644 index b1fece6..0000000 --- a/templates/admin/schnupper/index.html.tera +++ /dev/null @@ -1,38 +0,0 @@ -{% import "includes/macros" as macros %} -{% extends "base" %} -{% block content %} -
-

Schnupper Verwaltung

-
-
-

Angemeldete Personen: {{ schnupperanten | length }}

-
    - {% for user in schnupperanten %} -
  1. - - -  {{ user.name }} ({{ user.mail }} - {%- if user.notes %} | {{ user.notes }} - {% endif -%} - ) - - Zu Scheckbuch umwandeln - -
  2. - {% endfor %} -
-
-
-

Legende

-
- Bezahlt - Juhuuu! -
-
- Noch nicht bezahlt -
-
-
-
-{% endblock content %} diff --git a/templates/admin/user/fees.html.tera b/templates/admin/user/fees.html.tera deleted file mode 100644 index 1a75b25..0000000 --- a/templates/admin/user/fees.html.tera +++ /dev/null @@ -1,43 +0,0 @@ -{% import "includes/macros" as macros %} -{% extends "base" %} -{% block content %} -
-

Gebühren

-
- - -
-
-
- {% for fee in fees | sort(attribute="name") %} -
-
-
- {{ fee.name }} - {{ fee.sum_in_cents / 100 }}€ -
-
- {% for p in fee.parts %} - {{ p.0 }} ({{ p.1 / 100 }}€) - {% if not loop.last %}+{% endif %} - {% endfor %} -
- {% if "admin" in loggedin_user.roles or "kassier" in loggedin_user.roles %} - - {% endif %} -
-
- {% endfor %} -
-
-{% endblock content %} diff --git a/templates/admin/user/index.html.tera b/templates/admin/user/index.html.tera index 69e0121..755e301 100644 --- a/templates/admin/user/index.html.tera +++ b/templates/admin/user/index.html.tera @@ -2,7 +2,7 @@ {% extends "base" %} {% block content %}
-

Users

+

Mitgliederverwaltung

{% if allowed_to_edit %}
Neue Person hinzufügen @@ -11,12 +11,15 @@ method="post" class="flex mt-4 rounded-md sm:flex items-end justify-between">
-
- - +

Neues Mitglied hinzufügen

+
+
+ + +
@@ -62,7 +65,7 @@
{% for user in users %}
@@ -75,15 +78,14 @@ enctype="multipart/form-data" class="inline"> • Willkommensmail verschicken + onclick="return confirm('Demo aktiv, nun würde der Benutzer eine Mail bekommen mit der Info wie er ruad.at verwendet.');">Willkommensmail verschicken {% endif %} - {% if user.last_access %}• ⏳ {{ user.last_access | date }}{% endif %} + {% if user.last_access %}• Zuletzt eingeloggt:  {{ user.last_access | date() }}{% endif %} {% for role in user.roles -%} - {{ role }} + {{ macros::fancy_role_name(name=role) }} {%- if not loop.last %}, {% endif -%} {% endfor %} @@ -102,59 +104,14 @@
- {% for cluster, cluster_roles in roles | group_by(attribute="cluster") %} - - {# Determine the initially selected role within the cluster #} - {% set_global selected_role_id = "none" %} - {% for role in cluster_roles %} - {% if selected_role_id == "none" and role.name in user.roles %} - {% set_global selected_role_id = role.id %} - {% endif %} - {% endfor %} - {# Set default name to the selected role ID or first role if none selected #} - - {% endfor %} {% for role in roles %} {% if not role.cluster %} {{ macros::checkbox(label=role.name, name="roles[" ~ role.id ~ "]", id=loop.index , checked=role.name in user.roles, disabled=allowed_to_edit == false) }} {% endif %} {% endfor %}
- {% if user.membership_pdf %} - Beitrittserklärung herunterladen - {% else %} - {{ macros::input(label='Beitrittserklärung', name='membership_pdf', id=loop.index, type="file", readonly=allowed_to_edit == false, accept='application/pdf') }} - {% endif %} - {{ macros::input(label='DOB', name='dob', id=loop.index, type="text", value=user.dob, readonly=allowed_to_edit == false) }} - {{ macros::input(label='Weight (kg)', name='weight', id=loop.index, type="text", value=user.weight, readonly=allowed_to_edit == false) }} - {{ macros::input(label='Sex', name='sex', id=loop.index, type="text", value=user.sex, readonly=allowed_to_edit == false) }} - {{ macros::input(label='Mitglied seit', name='member_since_date', id=loop.index, type="text", value=user.member_since_date, readonly=allowed_to_edit == false) }} - {{ macros::input(label='Geburtsdatum', name='birthdate', id=loop.index, type="text", value=user.birthdate, readonly=allowed_to_edit == false) }} - {{ macros::input(label='Mail', name='mail', id=loop.index, type="text", value=user.mail, readonly=allowed_to_edit == false) }} - {{ macros::input(label='Nickname', name='nickname', id=loop.index, type="text", value=user.nickname, readonly=allowed_to_edit == false) }} - {{ macros::input(label='Notizen', name='notes', id=loop.index, type="text", value=user.notes, readonly=allowed_to_edit == false) }} - {{ macros::input(label='Telefon', name='phone', id=loop.index, type="text", value=user.phone, readonly=allowed_to_edit == false) }} - {{ macros::input(label='Adresse', name='address', id=loop.index, type="text", value=user.address, readonly=allowed_to_edit == false) }} - {% if allowed_to_edit %} - {{ macros::select(label="Familie", data=families, name='family_id', selected_id=user.family_id, display=['names'], default="Keine Familie", new_last_entry='Neue Familie anlegen') }} - {% endif %} + {{ macros::input(label='Name', name='name', id=loop.index, type="text", value=user.name) }} + {{ macros::input(label='Mail', name='mail', id=loop.index, type="text", value="Demo Version: Mails deaktiviert", readonly=true) }}
{% if allowed_to_edit %} diff --git a/templates/admin/user/scheckbuch.html.tera b/templates/admin/user/scheckbuch.html.tera deleted file mode 100644 index 249ce1d..0000000 --- a/templates/admin/user/scheckbuch.html.tera +++ /dev/null @@ -1,70 +0,0 @@ -{% import "includes/macros" as macros %} -{% import "includes/forms/log" as log %} -{% extends "base" %} -{% block content %} -
-

Scheckbücher

-
-
-

Neues Scheckbuch hinzufügen

-
-
- - -
-
- - -
-
-
-
- -
-
- -
- - -
- -
-
- {% for scheckbook in scheckbooks %} - {% set user = scheckbook.1 %} - {% set trips = scheckbook.0 %} -
-
-
- {{ user.name }} - Ausfahrten: {{ trips | length }} - {% for trip in trips %} -
  • {{ log::show_old(log=trip, state="completed", only_ones=false, index=loop.index) }}
  • - {% endfor %} -
    - {% if "admin" in loggedin_user.roles or "kassier" in loggedin_user.roles %} - Zahlungsstatus ändern - {% endif %} -
    -
    - {% endfor %} -
    -
    -{% endblock content %} diff --git a/templates/auth/login.html.tera b/templates/auth/login.html.tera index a57c505..e5cdd4f 100644 --- a/templates/auth/login.html.tera +++ b/templates/auth/login.html.tera @@ -2,9 +2,6 @@ {% block content %}
    - Logo Ruderassistent

    Ruderassistent

    {% if flash %}{{ macros::alert(message=flash.1, type=flash.0, class="sm:col-span-2 lg:col-span-3") }}{% endif %} diff --git a/templates/boatdamages.html.tera b/templates/boatdamages.html.tera deleted file mode 100644 index 63751d1..0000000 --- a/templates/boatdamages.html.tera +++ /dev/null @@ -1,106 +0,0 @@ -{% import "includes/macros" as macros %} -{% import "includes/forms/log" as log %} -{% extends "base" %} -{% block content %} -
    -

    Bootschäden

    -

    - Neuen Schaden - - {% include "includes/plus-icon" %} - Neuen Schaden eintragen - -

    - -
    - - -
    -
    - {% for boatdamage in boatdamages | sort(attribute="verified") %} -
    -
    - {{ boatdamage.created_at | date(format='%d.%m.%Y') }} ({{ boatdamage.boat.name }}) - {% if boatdamage.boat.damage %} - (Boot gesperrt) - {% endif %} -
    {{ boatdamage.desc }}
    - - Schaden eingetragen von {{ boatdamage.user_created.name }} am/um {{ boatdamage.created_at | date(format='%d.%m.%Y (%H:%M)') }} - - {% if boatdamage.fixed_at %} - Repariert von {{ boatdamage.user_fixed.name }} am/um {{ boatdamage.fixed_at | date(format='%d.%m.%Y (%H:%M)') }} - {% else %} - {% if loggedin_user and loggedin_user.allowed_to_steer %} -
    - - {% if loggedin_user and "tech" in loggedin_user.roles %} - - {% else %} - - {% endif %} -
    - {% endif %} - {% endif %} - {% if boatdamage.verified_at %} - Verifiziert von {{ boatdamage.user_verified.name }} am/um {{ boatdamage.verified_at | date(format='%d.%m.%Y (%H:%M)') }} - {% else %} - {% if loggedin_user and "tech" in loggedin_user.roles and boatdamage.fixed_at %} -
    - - -
    - {% endif %} - {% endif %} -
    -
    - {% endfor %} -
    -{% endblock content %} diff --git a/templates/boatreservations.html.tera b/templates/boatreservations.html.tera deleted file mode 100644 index ffd0b7d..0000000 --- a/templates/boatreservations.html.tera +++ /dev/null @@ -1,98 +0,0 @@ -{% import "includes/macros" as macros %} -{% import "includes/forms/log" as log %} -{% extends "base" %} -{% block content %} -
    -

    Bootsreservierungen

    -

    - Neue Reservierung - - {% include "includes/plus-icon" %} - Neue Reservierung eintragen - -

    - -
    - - -
    -
    - {% for reservation in boatreservations %} - {% set allowed_to_edit = false %} - {% if loggedin_user %} - {% if loggedin_user.id == reservation.user_applicant.id or "admin" in loggedin_user.roles %} - {% set allowed_to_edit = true %} - {% endif %} - {% endif %} -
    -
    - Boot: - {{ reservation.boat.name }} -
    - Reservierung: - {{ reservation.user_applicant.name }} -
    - Datum: - {{ reservation.start_date }} - {% if reservation.end_date != reservation.start_date %} - - - {{ reservation.end_date }} - {% endif %} -
    - {% if not allowed_to_edit %} - Uhrzeit: - {{ reservation.time_desc }} -
    - Zweck: - {{ reservation.usage }} - {% endif %} - {% if allowed_to_edit %} -
    -
    - - {{ macros::input(label='Uhrzeit', name='time_desc', id=loop.index, type="text", value=reservation.time_desc, readonly=false) }} - {{ macros::input(label='Zweck', name='usage', id=loop.index, type="text", value=reservation.usage, readonly=false) }} -
    - -
    - {% endif %} -
    -
    - {% endfor %} -
    -{% endblock content %} diff --git a/templates/ergo/final.html.tera b/templates/ergo/final.html.tera deleted file mode 100644 index 8dc4731..0000000 --- a/templates/ergo/final.html.tera +++ /dev/null @@ -1,39 +0,0 @@ -{% import "includes/macros" as macros %} -{% extends "base" %} -{% block content %} -
    -

    Aktuelle Woche

    -
    - Dirty Thirty -

    -

    - -
    -

    -
    -
    - Dirty Dozen -

    -

    - -
    -

    -
    -
    -{% endblock content %} diff --git a/templates/ergo/missing-data.html.tera b/templates/ergo/missing-data.html.tera deleted file mode 100644 index 22e3992..0000000 --- a/templates/ergo/missing-data.html.tera +++ /dev/null @@ -1,37 +0,0 @@ -{% import "includes/macros" as macros %} -{% import "includes/forms/boat" as boat %} -{% extends "base" %} -{% block content %} -
    -

    Ergo Challenge

    -
    - -
    -
    -{% endblock content %} diff --git a/templates/forms/event.html.tera b/templates/forms/event.html.tera index 38a9fc8..d802a2a 100644 --- a/templates/forms/event.html.tera +++ b/templates/forms/event.html.tera @@ -9,7 +9,7 @@ {{ macros::input(label='Startzeit', name='tripdetails.planned_starting_time', type='time', required=true) }} {{ macros::input(label='Anzahl Steuerleute', name='planned_amount_cox', type='number', required=true, min='0') }} {{ macros::input(label='Anzahl Ruderer (ohne Steuerperson)', name='tripdetails.max_people', type='number', required=true, min='0') }} - {{ macros::checkbox(label='Scheckbuch-Anmeldungen erlauben', name='tripdetails.allow_guests') }} + {{ macros::checkbox(label='Anfänger-Anmeldungen erlauben', name='tripdetails.allow_guests') }} {{ macros::checkbox(label='Immer anzeigen', name='always_show') }} {{ macros::input(label='Anmerkungen', name='tripdetails.notes', type='input') }} {{ macros::select(label='Typ', data=trip_types, name='tripdetails.trip_type', default='Reguläre Ausfahrt') }} diff --git a/templates/forms/trip.html.tera b/templates/forms/trip.html.tera index 5cc8dea..7d4f004 100644 --- a/templates/forms/trip.html.tera +++ b/templates/forms/trip.html.tera @@ -4,7 +4,7 @@ {{ macros::input(label='Startzeit (zB "10:00")', name='planned_starting_time', type='time', required=true) }} {{ macros::input(label='Anzahl Ruderer (ohne Steuerperson)', name='max_people', type='number', required=true, min='0') }} - {{ macros::checkbox(label='Scheckbuch-Anmeldungen erlauben', name='allow_guests') }} + {{ macros::checkbox(label='Anfänger-Anmeldungen erlauben', name='allow_guests') }} {{ macros::input(label='Anmerkungen', name='notes', type='input') }} {% if loggedin_user.allowed_to_steer %} {{ macros::select(label='Typ', data=trip_types, name='trip_type', default='Reguläre Ausfahrt') }} diff --git a/templates/includes/macros.html.tera b/templates/includes/macros.html.tera index 430cf6a..3e7486b 100644 --- a/templates/includes/macros.html.tera +++ b/templates/includes/macros.html.tera @@ -106,31 +106,10 @@ function setChoiceByLabel(choicesInstance, label) {