diff --git a/frontend/tests/cox.spec.ts b/frontend/tests/cox.spec.ts index d38dc96..d91c26b 100644 --- a/frontend/tests/cox.spec.ts +++ b/frontend/tests/cox.spec.ts @@ -18,7 +18,7 @@ test("cox can create and delete trip", async ({ page }) => { await expect(page.locator("body")).toContainText("18:00 Uhr (cox) Details"); await page.goto("/planned"); - await page.getByRole("link", { name: "Details" }).click(); + await page.getByRole('link', { name: 'Details' }).nth(1).click(); await page.getByRole("link", { name: "Termin löschen" }).click(); await expect(page.locator("body")).toContainText("Erfolgreich gelöscht!"); }); @@ -52,11 +52,11 @@ test.describe("cox can edit trips", () => { test("edit remarks", async () => { await sharedPage.goto("/planned"); - await sharedPage.getByRole("link", { name: "Details" }).click(); + await sharedPage.getByRole('link', { name: 'Details' }).nth(1).click(); await sharedPage.locator("#sidebar #notes").click(); await sharedPage.locator("#sidebar #notes").fill("Meine Anmerkung"); await sharedPage.getByRole("button", { name: "Speichern" }).click(); - await sharedPage.getByRole("link", { name: "Details" }).click(); + await sharedPage.getByRole("link", { name: "Details" }).nth(1).click(); await expect(sharedPage.locator("#sidebar")).toContainText( "Meine Anmerkung", ); @@ -68,14 +68,14 @@ test.describe("cox can edit trips", () => { test("add and remove guest", async () => { await sharedPage.goto("/planned"); - await sharedPage.getByRole("link", { name: "Details" }).click(); + await sharedPage.getByRole("link", { name: "Details" }).nth(1).click(); await sharedPage.locator("#sidebar #user_note").click(); await sharedPage.locator("#sidebar #user_note").fill("Mein Gast"); await sharedPage.getByRole("button", { name: "Gast hinzufügen" }).click(); await expect(sharedPage.locator("body")).toContainText( "Erfolgreich angemeldet!", ); - await sharedPage.getByRole("link", { name: "Details" }).click(); + await sharedPage.getByRole("link", { name: "Details" }).nth(1).click(); await expect(sharedPage.locator("#sidebar")).toContainText( "Freie Plätze: 4", ); @@ -90,7 +90,7 @@ test.describe("cox can edit trips", () => { await expect(sharedPage.locator("body")).toContainText( "Erfolgreich abgemeldet!", ); - await sharedPage.getByRole("link", { name: "Details" }).click(); + await sharedPage.getByRole("link", { name: "Details" }).nth(1).click(); await expect(sharedPage.locator("#sidebar")).toContainText( "Freie Plätze: 5", ); @@ -108,7 +108,7 @@ test.describe("cox can edit trips", () => { test("change amount rower", async () => { await sharedPage.goto("/planned"); - await sharedPage.getByRole("link", { name: "Details" }).click(); + await sharedPage.getByRole("link", { name: "Details" }).nth(1).click(); await expect(sharedPage.locator("#sidebar")).toContainText( "Freie Plätze: 5", ); @@ -122,7 +122,7 @@ test.describe("cox can edit trips", () => { test("call off trip", async () => { await sharedPage.goto("/planned"); - await sharedPage.getByRole("link", { name: "Details" }).click(); + await sharedPage.getByRole("link", { name: "Details" }).nth(1).click(); await expect(sharedPage.locator("#sidebar")).toContainText( "Freie Plätze: 3", ); @@ -137,7 +137,7 @@ test.describe("cox can edit trips", () => { test.afterAll(async () => { await sharedPage.goto("/planned"); - await sharedPage.getByRole("link", { name: "Details" }).click(); + await sharedPage.getByRole('link', { name: 'Details' }).nth(1).click(); await sharedPage.getByRole("link", { name: "Termin löschen" }).click(); await sharedPage.close(); }); diff --git a/seeds.sql b/seeds.sql index b413278..c88c422 100644 --- a/seeds.sql +++ b/seeds.sql @@ -45,10 +45,10 @@ 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, '1970-01-01', 'trip_details for a planned event'); +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, '1970-01-02', 'trip_details for trip from cox'); +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_type" (name, desc, question, icon) VALUES ('Regatta', 'Regatta!', 'Kein normales Event. Das ist eine Regatta! Willst du wirklich teilnehmen?', '🏅'); diff --git a/src/model/event.rs b/src/model/event.rs index 3a34ad4..b62811b 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -233,6 +233,7 @@ WHERE trip_details.id=? db: &SqlitePool, name: &str, planned_amount_cox: i32, + always_show: bool, trip_details: &TripDetails, ) { if trip_details.always_show { @@ -245,6 +246,10 @@ WHERE trip_details.id=? .await; } + if always_show && !trip_details.always_show { + trip_details.set_always_show(db, true).await; + } + sqlx::query!( "INSERT INTO planned_event(name, planned_amount_cox, trip_details_id) VALUES(?, ?, ?)", name, @@ -433,14 +438,14 @@ mod test { use crate::{model::tripdetails::TripDetails, testdb}; use super::Event; - use chrono::NaiveDate; + use chrono::Local; use sqlx::SqlitePool; #[sqlx::test] fn test_get_day() { let pool = testdb!(); - let res = Event::get_for_day(&pool, NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()).await; + let res = Event::get_for_day(&pool, Local::now().date_naive()).await; assert_eq!(res.len(), 1); } @@ -450,9 +455,9 @@ mod test { let trip_details = TripDetails::find_by_id(&pool, 1).await.unwrap(); - Event::create(&pool, "new-event".into(), 2, &trip_details).await; + Event::create(&pool, "new-event".into(), 2, false, &trip_details).await; - let res = Event::get_for_day(&pool, NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()).await; + let res = Event::get_for_day(&pool, Local::now().date_naive()).await; assert_eq!(res.len(), 2); } @@ -463,7 +468,7 @@ mod test { planned_event.delete(&pool).await.unwrap(); - let res = Event::get_for_day(&pool, NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()).await; + let res = Event::get_for_day(&pool, Local::now().date_naive()).await; assert_eq!(res.len(), 0); } @@ -471,7 +476,8 @@ mod test { fn test_ics() { let pool = testdb!(); + let today = Local::now().date_naive().format("%Y%m%d").to_string(); let actual = Event::get_ics_feed(&pool).await; - assert_eq!("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:ics-rs\r\nBEGIN:VEVENT\r\nUID:1@rudernlinz.at\r\nDTSTAMP:19900101T180000\r\nDTSTART:19700101T100000\r\nSUMMARY:test-planned-event \r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", actual); + assert_eq!(format!("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:ics-rs\r\nBEGIN:VEVENT\r\nUID:1@rudernlinz.at\r\nDTSTAMP:19900101T180000\r\nDTSTART:{today}T100000\r\nSUMMARY:test-planned-event \r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"), actual); } } diff --git a/src/model/logbook.rs b/src/model/logbook.rs index f52fb03..7c278dd 100644 --- a/src/model/logbook.rs +++ b/src/model/logbook.rs @@ -575,10 +575,8 @@ ORDER BY departure DESC return Err(LogbookUpdateError::ArrivalNotAfterDeparture); } - if !boat.external { - if boat.on_water_between(db, dep, arr).await { - return Err(LogbookUpdateError::BoatAlreadyOnWater); - }; + if !boat.external && boat.on_water_between(db, dep, arr).await { + return Err(LogbookUpdateError::BoatAlreadyOnWater); } let duration_in_mins = (arr.and_utc().timestamp() - dep.and_utc().timestamp()) / 60; @@ -594,14 +592,13 @@ ORDER BY departure DESC let today = Local::now().date_naive(); let day_diff = today - arr.date(); let day_diff = day_diff.num_days(); - if day_diff >= 7 { - if !user.has_role_tx(db, "admin").await - && !user - .has_role_tx(db, "allow-retroactive-logbookentries") - .await - { - return Err(LogbookUpdateError::OnlyAllowedToEndTripsEndingToday); - } + if day_diff >= 7 + && !user.has_role_tx(db, "admin").await + && !user + .has_role_tx(db, "allow-retroactive-logbookentries") + .await + { + return Err(LogbookUpdateError::OnlyAllowedToEndTripsEndingToday); } if day_diff < 0 && !user.has_role_tx(db, "admin").await { return Err(LogbookUpdateError::OnlyAllowedToEndTripsEndingToday); diff --git a/src/model/notification.rs b/src/model/notification.rs index 74b0f48..d27e894 100644 --- a/src/model/notification.rs +++ b/src/model/notification.rs @@ -203,6 +203,7 @@ mod test { testdb, }; + use chrono::Local; use sqlx::SqlitePool; #[sqlx::test] @@ -213,17 +214,16 @@ mod test { let add_tripdetails = TripDetailsToAdd { planned_starting_time: "10:00", max_people: 4, - day: "1970-02-01".into(), + day: Local::now().date_naive().format("%Y-%m-%d").to_string(), notes: None, trip_type: None, allow_guests: false, - always_show: false, }; let tripdetails_id = TripDetails::create(&pool, add_tripdetails).await; let trip_details = TripDetails::find_by_id(&pool, tripdetails_id) .await .unwrap(); - Event::create(&pool, "new-event".into(), 2, &trip_details).await; + Event::create(&pool, "new-event".into(), 2, false, &trip_details).await; let event = Event::find_by_trip_details(&pool, trip_details.id) .await .unwrap(); diff --git a/src/model/trip.rs b/src/model/trip.rs index 47d65d9..d9765f8 100644 --- a/src/model/trip.rs +++ b/src/model/trip.rs @@ -40,7 +40,6 @@ pub struct TripUpdate<'a> { pub max_people: i32, pub notes: Option<&'a str>, pub trip_type: Option, //TODO: Move to `TripType` - pub always_show: bool, pub is_locked: bool, } @@ -83,7 +82,7 @@ impl Trip { } // don't notify people who have cancelled their trip - if notify.cancelled(db) { + if notify.cancelled() { continue; } @@ -217,11 +216,10 @@ WHERE day=? let was_already_cancelled = tripdetails.max_people == 0; sqlx::query!( - "UPDATE trip_details SET max_people = ?, notes = ?, trip_type_id = ?, always_show = ?, is_locked = ? WHERE id = ?", + "UPDATE trip_details SET max_people = ?, notes = ?, trip_type_id = ?, is_locked = ? WHERE id = ?", update.max_people, update.notes, update.trip_type, - update.always_show, update.is_locked, trip_details_id ) @@ -345,6 +343,20 @@ WHERE day=? self.cox_id == user_id } + pub(crate) async fn toggle_always_show(&self, db: &SqlitePool) { + if let Some(trip_details) = self.trip_details_id { + let new_state = !self.always_show; + sqlx::query!( + "UPDATE trip_details SET always_show = ? WHERE id = ?", + new_state, + trip_details + ) + .execute(db) + .await + .unwrap(); + } + } + pub(crate) async fn get_pinned_for_day( db: &sqlx::Pool, day: NaiveDate, @@ -394,7 +406,7 @@ mod test { testdb, }; - use chrono::NaiveDate; + use chrono::Local; use sqlx::SqlitePool; use super::Trip; @@ -421,7 +433,8 @@ mod test { fn test_get_day_cox_trip() { let pool = testdb!(); - let res = Trip::get_for_day(&pool, NaiveDate::from_ymd_opt(1970, 1, 2).unwrap()).await; + let tomorrow = Local::now().date_naive() + chrono::Duration::days(1); + let res = Trip::get_for_day(&pool, tomorrow).await; assert_eq!(res.len(), 1); } @@ -477,7 +490,6 @@ mod test { max_people: 10, notes: None, trip_type: None, - always_show: false, is_locked: false, }; @@ -506,7 +518,6 @@ mod test { max_people: 10, notes: None, trip_type: Some(1), - always_show: false, is_locked: false, }; assert!(Trip::update_own(&pool, &update).await.is_ok()); @@ -535,7 +546,6 @@ mod test { max_people: 10, notes: None, trip_type: None, - always_show: false, is_locked: false, }; assert!(Trip::update_own(&pool, &update).await.is_err()); diff --git a/src/model/tripdetails.rs b/src/model/tripdetails.rs index 5e420ad..1dd59a5 100644 --- a/src/model/tripdetails.rs +++ b/src/model/tripdetails.rs @@ -1,5 +1,5 @@ use crate::model::user::User; -use chrono::NaiveDate; +use chrono::{Local, NaiveDate}; use rocket::FromForm; use serde::{Deserialize, Serialize}; use sqlx::{FromRow, SqlitePool}; @@ -33,7 +33,6 @@ pub struct TripDetailsToAdd<'r> { pub notes: Option<&'r str>, pub trip_type: Option, pub allow_guests: bool, - pub always_show: bool, } impl TripDetails { @@ -59,6 +58,24 @@ WHERE id like ? } } + pub fn date(&self) -> NaiveDate { + NaiveDate::parse_from_str(&self.day, "%Y-%m-%d").unwrap() + } + + pub(crate) async fn user_sees_trip(&self, db: &SqlitePool, user: &User) -> bool { + let today = Local::now().date_naive(); + let day_diff = self.date() - today; + let day_diff = day_diff.num_days(); + if day_diff < 0 { + // tripdetails is in past + return false; + } + if day_diff <= user.amount_days_to_show(db).await { + return true; + } + self.always_show + } + pub async fn find_by_startingdatetime( db: &SqlitePool, day: String, @@ -77,7 +94,7 @@ WHERE day = ? AND planned_starting_time = ? .await.unwrap() } - pub fn cancelled(&self, db: &SqlitePool) -> bool { + pub fn cancelled(&self) -> bool { self.max_people == 0 } @@ -89,7 +106,7 @@ WHERE day = ? AND planned_starting_time = ? return; } - if self.cancelled(db) { + if self.cancelled() { // Cox cancelled event, thus it's probably bad weather. Don't bother with sending // notifications return; @@ -146,14 +163,13 @@ WHERE day = ? AND planned_starting_time = ? /// Creates a new entry in `trip_details` and returns its id. pub async fn create(db: &SqlitePool, tripdetails: TripDetailsToAdd<'_>) -> i64 { let query = sqlx::query!( - "INSERT INTO trip_details(planned_starting_time, max_people, day, notes, allow_guests, trip_type_id, always_show) VALUES(?, ?, ?, ?, ?, ?, ?)" , + "INSERT INTO trip_details(planned_starting_time, max_people, day, notes, allow_guests, trip_type_id) VALUES(?, ?, ?, ?, ?, ?)" , tripdetails.planned_starting_time, tripdetails.max_people, tripdetails.day, tripdetails.notes, tripdetails.allow_guests, tripdetails.trip_type, - tripdetails.always_show ) .execute(db) .await @@ -161,6 +177,17 @@ WHERE day = ? AND planned_starting_time = ? query.last_insert_rowid() } + pub async fn set_always_show(&self, db: &SqlitePool, value: bool) { + sqlx::query!( + "UPDATE trip_details SET always_show = ? WHERE id = ?", + value, + self.id + ) + .execute(db) + .await + .unwrap(); //Okay, as planned_event can only be created with proper DB backing + } + pub async fn is_full(&self, db: &SqlitePool) -> bool { let amount_currently_registered = sqlx::query!( "SELECT COUNT(*) as count FROM user_trip WHERE trip_details_id = ?", @@ -309,7 +336,6 @@ mod test { notes: None, allow_guests: false, trip_type: None, - always_show: false } ) .await, @@ -325,7 +351,6 @@ mod test { notes: None, allow_guests: false, trip_type: None, - always_show: false } ) .await, diff --git a/src/model/user.rs b/src/model/user.rs index 49d828c..c310c59 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -450,6 +450,12 @@ ASKÖ Ruderverein Donau Linz", self.name), false } + pub async fn allowed_to_update_always_show_trip(&self, db: &SqlitePool) -> bool { + AllowedToUpdateTripToAlwaysBeShownUser::new(db, self.clone()) + .await + .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) @@ -879,18 +885,32 @@ ORDER BY last_access DESC days } - async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 { + pub(crate) async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 { if self.has_role(db, "cox").await { let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); //Ok, //december //has 31 //days - end_of_year + let days_left_in_year = end_of_year .signed_duration_since(Local::now().date_naive()) .num_days() - + 1 + + 1; + + if days_left_in_year <= 31 { + let end_of_next_year = + NaiveDate::from_ymd_opt(Local::now().year() + 1, 12, 31).unwrap(); //Ok, + //december + //has 31 + //days + end_of_next_year + .signed_duration_since(Local::now().date_naive()) + .num_days() + + 1 + } else { + days_left_in_year + } } else { - 6 + 10 } } @@ -1014,6 +1034,7 @@ special_user!(VorstandUser, +"Vorstand"); special_user!(EventUser, +"manage_events"); special_user!(AllowedToEditPaymentStatusUser, +"kassier", +"admin"); special_user!(ManageUserUser, +"admin", +"schriftfuehrer"); +special_user!(AllowedToUpdateTripToAlwaysBeShownUser, +"admin"); #[derive(FromRow, Serialize, Deserialize, Clone, Debug)] pub struct UserWithRolesAndMembershipPdf { diff --git a/src/model/usertrip.rs b/src/model/usertrip.rs index 1befabc..d0e38b6 100644 --- a/src/model/usertrip.rs +++ b/src/model/usertrip.rs @@ -24,6 +24,10 @@ impl UserTrip { return Err(UserTripError::GuestNotAllowedForThisEvent); } + if !trip_details.user_sees_trip(db, user).await { + return Err(UserTripError::NotVisibleToUser); + } + //TODO: Check if user sees the event (otherwise she could forge trip_details_id) let is_cox = trip_details.user_is_cox(db, user).await; @@ -96,6 +100,10 @@ impl UserTrip { return Err(UserTripDeleteError::DetailsLocked); } + if !trip_details.user_sees_trip(db, user).await { + return Err(UserTripDeleteError::NotVisibleToUser); + } + if let Some(name) = name { if !trip_details.user_allowed_to_change(db, user).await { return Err(UserTripDeleteError::NotAllowedToDeleteGuest); @@ -137,6 +145,7 @@ pub enum UserTripError { CantRegisterAtOwnEvent, GuestNotAllowedForThisEvent, NotAllowedToAddGuest, + NotVisibleToUser, } #[derive(Debug, PartialEq)] @@ -144,6 +153,7 @@ pub enum UserTripDeleteError { DetailsLocked, GuestNotParticipating, NotAllowedToDeleteGuest, + NotVisibleToUser, } #[cfg(test)] diff --git a/src/tera/admin/event.rs b/src/tera/admin/event.rs index 4644956..ef97fc6 100644 --- a/src/tera/admin/event.rs +++ b/src/tera/admin/event.rs @@ -18,6 +18,7 @@ use crate::model::{ struct AddEventForm<'r> { name: &'r str, planned_amount_cox: i32, + always_show: bool, tripdetails: TripDetailsToAdd<'r>, } @@ -34,7 +35,14 @@ async fn create( //just created //the object - Event::create(db, data.name, data.planned_amount_cox, &trip_details).await; + Event::create( + db, + data.name, + data.planned_amount_cox, + data.always_show, + &trip_details, + ) + .await; Flash::success(Redirect::to("/planned"), "Event hinzugefügt") } diff --git a/src/tera/admin/user.rs b/src/tera/admin/user.rs index afb2d1f..9731054 100644 --- a/src/tera/admin/user.rs +++ b/src/tera/admin/user.rs @@ -374,7 +374,7 @@ async fn create_scheckbuch( if mail.parse::
().is_err() { return Flash::error( Redirect::to("/admin/user/scheckbuch"), - format!("Keine gültige Mailadresse"), + "Keine gültige Mailadresse".to_string(), ); } @@ -383,9 +383,8 @@ async fn create_scheckbuch( if User::find_by_name(db, name).await.is_some() { return Flash::error( Redirect::to("/admin/user/scheckbuch"), - format!( - "Kann kein Scheckbuch erstellen, der Name wird bereits von einem User verwendet" - ), + "Kann kein Scheckbuch erstellen, der Name wird bereits von einem User verwendet" + .to_string(), ); } @@ -418,14 +417,14 @@ async fn schnupper_to_scheckbuch( let Some(user) = User::find_by_id(db, id).await else { return Flash::error( Redirect::to("/admin/schnupper"), - format!("user id not found"), + "user id not found".to_string(), ); }; if !user.has_role(db, "schnupperant").await { return Flash::error( Redirect::to("/admin/schnupper"), - format!("kein schnupperant..."), + "kein schnupperant...".to_string(), ); } diff --git a/src/tera/cox.rs b/src/tera/cox.rs index 6b47cf4..3166fc6 100644 --- a/src/tera/cox.rs +++ b/src/tera/cox.rs @@ -11,7 +11,7 @@ use crate::model::{ log::Log, trip::{self, CoxHelpError, Trip, TripDeleteError, TripHelpDeleteError, TripUpdateError}, tripdetails::{TripDetails, TripDetailsToAdd}, - user::CoxUser, + user::{AllowedToUpdateTripToAlwaysBeShownUser, CoxUser}, }; #[post("/trip", data = "")] @@ -42,7 +42,6 @@ struct EditTripForm<'r> { max_people: i32, notes: Option<&'r str>, trip_type: Option, - always_show: bool, is_locked: bool, } @@ -60,7 +59,6 @@ async fn update( max_people: data.max_people, notes: data.notes, trip_type: data.trip_type, - always_show: data.always_show, is_locked: data.is_locked, }; match Trip::update_own(db, &update).await { @@ -80,6 +78,23 @@ async fn update( } } +#[get("/trip//toggle-always-show")] +async fn toggle_always_show( + db: &State, + trip_id: i64, + _user: AllowedToUpdateTripToAlwaysBeShownUser, +) -> 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!", + ) + } else { + Flash::error(Redirect::to("/planned"), "Ausfahrt gibt's nicht") + } +} + #[get("/join/")] async fn join(db: &State, planned_event_id: i64, cox: CoxUser) -> Flash { if let Some(planned_event) = Event::find_by_id(db, planned_event_id).await { @@ -164,12 +179,19 @@ async fn remove(db: &State, planned_event_id: i64, cox: CoxUser) -> } pub fn routes() -> Vec { - routes![create, join, remove, remove_trip, update] + routes![ + create, + join, + remove, + remove_trip, + update, + toggle_always_show + ] } #[cfg(test)] mod test { - use chrono::NaiveDate; + use chrono::{Local, NaiveDate}; use rocket::{ http::{ContentType, Status}, local::asynchronous::Client, @@ -230,7 +252,9 @@ mod test { fn test_trip_update_succ() { let db = testdb!(); - let trip = &Trip::get_for_day(&db, NaiveDate::from_ymd_opt(1970, 01, 02).unwrap()).await[0]; + let tomorrow = Local::now().date_naive() + chrono::Duration::days(1); + println!("{tomorrow}"); + let trip = &Trip::get_for_day(&db, tomorrow).await[0]; assert_eq!(1, trip.trip.max_people); assert_eq!( "trip_details for trip from cox", @@ -266,7 +290,8 @@ mod test { "7:successAusfahrt erfolgreich aktualisiert." ); - let trip = &Trip::get_for_day(&db, NaiveDate::from_ymd_opt(1970, 01, 02).unwrap()).await[0]; + let tomorrow = Local::now().date_naive() + chrono::Duration::days(1); + let trip = &Trip::get_for_day(&db, tomorrow).await[0]; assert_eq!(12, trip.trip.max_people); assert_eq!("my-new-notes", &trip.trip.notes.clone().unwrap()); } @@ -306,7 +331,9 @@ mod test { fn test_trip_update_wrong_cox() { let db = testdb!(); - let trip = &Trip::get_for_day(&db, NaiveDate::from_ymd_opt(1970, 01, 02).unwrap()).await[0]; + let tomorrow = Local::now().date_naive() + chrono::Duration::days(1); + + let trip = &Trip::get_for_day(&db, tomorrow).await[0]; assert_eq!(1, trip.trip.max_people); assert_eq!( "trip_details for trip from cox", diff --git a/src/tera/mod.rs b/src/tera/mod.rs index 0f71aaa..9616a80 100644 --- a/src/tera/mod.rs +++ b/src/tera/mod.rs @@ -131,13 +131,13 @@ async fn new_blogpost( blogpost: Form>, config: &State, ) -> String { - if blogpost.pw == &config.wordpress_key { - let member = Role::find_by_name(&db, "Donau Linz").await.unwrap(); + 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"), - &format!("Neuer Blogpost"), + "Neuer Blogpost", Some(blogpost.article_url), None, ) @@ -160,9 +160,9 @@ async fn blogpost_unpublished( blogpost: Form>, config: &State, ) -> String { - if blogpost.pw == &config.wordpress_key { + if blogpost.pw == config.wordpress_key { Notification::delete_by_link( - &db, + db, &urlencoding::decode(blogpost.article_url).expect("UTF-8"), ) .await; diff --git a/src/tera/planned.rs b/src/tera/planned.rs index 7bf5f4d..0dab434 100644 --- a/src/tera/planned.rs +++ b/src/tera/planned.rs @@ -37,6 +37,10 @@ async fn index( context.insert("flash", &msg.into_inner()); } + context.insert( + "allowed_to_update_always_show_trip", + &user.allowed_to_update_always_show_trip(db).await, + ); context.insert("fee", &user.fee(db).await); context.insert("loggedin_user", &UserWithDetails::from_user(user, db).await); context.insert("days", &days); @@ -99,6 +103,10 @@ async fn join( Redirect::to("/planned"), "Du darfst keine Gäste hinzufügen.", ), + Err(UserTripError::NotVisibleToUser) => Flash::error( + Redirect::to("/planned"), + "Du kannst dich nicht registrieren, weil du die Ausfahrt gar nicht sehen solltest.", + ), Err(UserTripError::DetailsLocked) => Flash::error( Redirect::to("/planned"), "Die Bootseinteilung wurde bereits gemacht. Wenn du noch mitrudern möchtest, frag bitte bei einer angemeldeten Steuerperson nach, ob das noch möglich ist.", @@ -147,6 +155,10 @@ async fn remove_guest( Err(UserTripDeleteError::GuestNotParticipating) => { Flash::error(Redirect::to("/planned"), "Gast nicht angemeldet.") } + Err(UserTripDeleteError::NotVisibleToUser) => Flash::error( + Redirect::to("/planned"), + "Du kannst dich nicht abmelden, weil du die Ausfahrt gar nicht sehen solltest.", + ), Err(UserTripDeleteError::NotAllowedToDeleteGuest) => Flash::error( Redirect::to("/planned"), "Keine Berechtigung um den Gast zu entfernen.", @@ -191,6 +203,18 @@ async fn remove( Flash::error(Redirect::to("/planned"), "Das Boot ist bereits eingeteilt. Bitte kontaktiere den Schiffsführer (Nummern siehe Signalgruppe) falls du dich doch abmelden willst.") } + Err(UserTripDeleteError::NotVisibleToUser) => { + Log::create( + db, + format!( + "User {} tried to unregister for not-yet-seeable trip_details.id={}", + user.name, trip_details_id + ), + ) + .await; + + Flash::error(Redirect::to("/planned"), "Abmeldung nicht möglich, da du dieses Event eigentlich gar nicht sehen solltest...") + } Err(_) => { panic!("Not possible to be here"); } diff --git a/templates/forms/event.html.tera b/templates/forms/event.html.tera index 5803c7a..38a9fc8 100644 --- a/templates/forms/event.html.tera +++ b/templates/forms/event.html.tera @@ -10,7 +10,7 @@ {{ 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='Immer anzeigen', name='tripdetails.always_show') }} + {{ 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 27a3b86..0632e59 100644 --- a/templates/forms/trip.html.tera +++ b/templates/forms/trip.html.tera @@ -5,7 +5,6 @@ {{ 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='Immer anzeigen', name='always_show') }} {{ macros::input(label='Anmerkungen', name='notes', type='input') }} {{ macros::select(label='Typ', data=trip_types, name='trip_type', default='Reguläre Ausfahrt') }} diff --git a/templates/planned.html.tera b/templates/planned.html.tera index 0cccb5e..67ed223 100644 --- a/templates/planned.html.tera +++ b/templates/planned.html.tera @@ -67,7 +67,8 @@ {% endif %} {% endfor %} {% endif %} -
{{ macros::input(label='Anzahl Ruderer', name='max_people', type='number', required=true, value=trip.max_people, min=trip.rower | length) }} {{ macros::input(label='Anmerkungen', name='notes', type='input', value=trip.notes) }} - {{ macros::checkbox(label='Immer anzeigen', name='always_show', id=trip.id,checked=trip.always_show) }} {{ macros::checkbox(label='Gesperrt', name='is_locked', id=trip.id,checked=trip.is_locked) }} {{ macros::select(label='Typ', name='trip_type', data=trip_types, default='Reguläre Ausfahrt', selected_id=trip.trip_type_id) }} @@ -369,7 +369,6 @@
{{ macros::input(label='', name='max_people', type='hidden', value=0) }} {{ macros::input(label='Grund der Absage', name='notes', type='input', value='') }} - {{ macros::input(label='', name='always_show', type='hidden', value=trip.always_show) }} {{ macros::input(label='', name='is_locked', type='hidden', value=trip.is_locked) }} {{ macros::input(label='', name='trip_type', type='hidden', value=trip.trip_type_id) }} @@ -379,6 +378,20 @@ {% endif %} {% endif %} {# --- END Edit Form --- #} + {# --- START Admin Form --- #} + {% if allowed_to_update_always_show_trip %} +
+

Admin-Modus

+ + + +
+ {% endif %} + {# --- END Admin Form --- #}
{# --- END Sidebar Content --- #}