diff --git a/frontend/scss/components/_links.scss b/frontend/scss/components/_links.scss index ccf00a0..2643d63 100644 --- a/frontend/scss/components/_links.scss +++ b/frontend/scss/components/_links.scss @@ -11,6 +11,10 @@ @apply text-white hover:text-primary-100 underline; } + &-black { + @apply text-black hover:text-primary-950 dark:text-white hover:dark:text-primary-300 underline; + } + &-no-underline { @apply no-underline; } diff --git a/frontend/tests/log.spec.ts b/frontend/tests/log.spec.ts index efa53f0..626a597 100644 --- a/frontend/tests/log.spec.ts +++ b/frontend/tests/log.spec.ts @@ -115,7 +115,7 @@ test("Cox can start and finish trip", async ({ page }, testInfo) => { await page.getByPlaceholder("Passwort").press("Enter"); await page.goto("/log/show"); - await page.getByText('(cox2)').click(); + await page.getByRole('link', { name: 'Joe' }).nth(1).click(); page.once("dialog", (dialog) => { dialog.accept().catch(() => {}); }); @@ -208,7 +208,6 @@ test("Kiosk can start and finish trip", async ({ page }, testInfo) => { await page.getByRole('link', { name: 'Logbuch' }).click(); await expect(page.locator('body')).toContainText('Joe'); - await expect(page.locator('body')).toContainText('(cox2)'); await expect(page.locator('body')).toContainText('Ottensheim (25 km)'); await expect(page.locator('body')).toContainText('Ruderer: cox2, rower2'); @@ -225,7 +224,7 @@ test("Kiosk can start and finish trip", async ({ page }, testInfo) => { await page.getByPlaceholder("Passwort").press("Enter"); await page.goto("/log/show"); - await page.getByText('(cox2)').click(); + await page.getByRole('link', { name: 'Joe' }).nth(1).click(); page.once("dialog", (dialog) => { dialog.accept().catch(() => {}); }); @@ -286,7 +285,6 @@ test("Cox can start and finish trip with cox steering only", async ({ page }, te await page.goto('/log/show'); await expect(page.locator('body')).toContainText('cox_only_steering_boat'); - await expect(page.locator('body')).toContainText('(cox2 - handgesteuert)'); await expect(page.locator('body')).toContainText('Ottensheim (25 km)'); @@ -302,7 +300,7 @@ test("Cox can start and finish trip with cox steering only", async ({ page }, te await page.getByPlaceholder("Passwort").press("Enter"); await page.goto("/log/show"); - await page.getByText('(cox2 - handgesteuert)').click(); + await page.getByRole("link", { name: "cox_only_steering_boat" }).click(); page.once("dialog", (dialog) => { dialog.accept().catch(() => {}); }); @@ -371,7 +369,7 @@ test("Kiosk can start and finish trip in one stop", async ({ page }, testInfo) = await page.getByPlaceholder("Passwort").press("Enter"); await page.goto("/log/show"); - await page.getByText('(cox2)').click(); + await page.getByRole('link', { name: 'Joe' }).nth(1).click(); page.once("dialog", (dialog) => { dialog.accept().catch(() => {}); }); diff --git a/src/model/user/basic.rs b/src/model/user/basic.rs index dfcb739..b593c67 100644 --- a/src/model/user/basic.rs +++ b/src/model/user/basic.rs @@ -414,12 +414,14 @@ impl User { .await .unwrap(); - ActivityBuilder::new(&format!( - "{updated_by} hat die Rolle {role} von {self} entfernt." - )) - .relevant_for_user(self) - .save(db) - .await; + if !role.hide_in_lists && role.cluster.is_none() { + ActivityBuilder::new(&format!( + "{updated_by} hat die Rolle {role} von {self} entfernt." + )) + .relevant_for_user(self) + .save(db) + .await; + } Ok(()) } @@ -499,7 +501,7 @@ impl User { ) })?; - if !role.hide_in_lists { + if !role.hide_in_lists && role.cluster.is_none() { ActivityBuilder::new(&format!( "{updated_by} hat die Rolle '{role}' dem Benutzer {self} hinzugefügt." )) diff --git a/src/model/user/mod.rs b/src/model/user/mod.rs index 0e71ca8..5aa18a1 100644 --- a/src/model/user/mod.rs +++ b/src/model/user/mod.rs @@ -1,21 +1,20 @@ use std::{fmt::Display, ops::DerefMut}; -use argon2::{Argon2, PasswordHasher, password_hash::SaltString}; +use argon2::{password_hash::SaltString, Argon2, PasswordHasher}; use chrono::{Datelike, Local, NaiveDate}; use log::info; use rocket::async_trait; use rocket::{ - Request, http::{Cookie, Status}, request::{FromRequest, Outcome}, time::{Duration, OffsetDateTime}, + Request, }; use serde::{Deserialize, Serialize}; use sqlx::{FromRow, Sqlite, SqlitePool, Transaction}; use super::activity::ActivityBuilder; use super::{ - Day, log::Log, logbook::Logbook, mail::Mail, @@ -24,6 +23,7 @@ use super::{ role::Role, stat::Stat, tripdetails::TripDetails, + Day, }; use crate::AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD; use scheckbuch::ScheckbuchUser; @@ -512,7 +512,7 @@ ASKÖ Ruderverein Donau Linz", self.name), .save(db) .await; return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has - //been deleted + //been deleted } if let Some(user_pw) = user.pw.as_ref() { @@ -577,10 +577,6 @@ ASKÖ Ruderverein Donau Linz", self.name), .execute(db) .await .unwrap(); //Okay, because we can only create a User of a valid id - ActivityBuilder::new(&format!("User {self} hat sich eingeloggt.")) - .relevant_for_user(self) - .save(db) - .await; } pub async fn delete(&self, db: &SqlitePool, deleted_by: &ManageUserUser) { @@ -622,9 +618,9 @@ ASKÖ Ruderverein Donau Linz", self.name), pub(crate) async fn amount_days_to_show(&self, db: &SqlitePool) -> i64 { if self.allowed_to_steer(db).await { let end_of_year = NaiveDate::from_ymd_opt(Local::now().year(), 12, 31).unwrap(); //Ok, - //december - //has 31 - //days + //december + //has 31 + //days let days_left_in_year = end_of_year .signed_duration_since(Local::now().date_naive()) .num_days() @@ -633,9 +629,9 @@ ASKÖ Ruderverein Donau Linz", self.name), 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 + //december + //has 31 + //days end_of_next_year .signed_duration_since(Local::now().date_naive()) .num_days() @@ -867,8 +863,8 @@ special_user!(SteeringUser, +"cox", +"Bootsführer"); special_user!(AdminUser, +"admin"); special_user!(AllowedForPlannedTripsUser, +"Donau Linz", +"scheckbuch", +"Förderndes Mitglied"); special_user!(DonauLinzUser, +"Donau Linz", -"Unterstützend", -"Förderndes Mitglied"); // TODO: -// remove -> -// RegularUser + // remove -> + // RegularUser special_user!(SchnupperBetreuerUser, +"schnupper-betreuer"); special_user!(VorstandUser, +"admin", +"Vorstand"); special_user!(EventUser, +"manage_events"); @@ -982,21 +978,17 @@ mod test { #[sqlx::test] fn wrong_pw() { let pool = testdb!(); - assert!( - User::login(&pool, "admin".into(), "admi".into()) - .await - .is_err() - ); + assert!(User::login(&pool, "admin".into(), "admi".into()) + .await + .is_err()); } #[sqlx::test] fn wrong_username() { let pool = testdb!(); - assert!( - User::login(&pool, "admi".into(), "admin".into()) - .await - .is_err() - ); + assert!(User::login(&pool, "admi".into(), "admin".into()) + .await + .is_err()); } #[sqlx::test] @@ -1015,11 +1007,9 @@ mod test { let pool = testdb!(); let user = User::find_by_id(&pool, 1).await.unwrap(); - assert!( - User::login(&pool, "admin".into(), "abc".into()) - .await - .is_err() - ); + assert!(User::login(&pool, "admin".into(), "abc".into()) + .await + .is_err()); user.update_pw(&pool, "abc".into()).await; diff --git a/src/tera/admin/user.rs b/src/tera/admin/user.rs index 69ec070..95ec6fc 100644 --- a/src/tera/admin/user.rs +++ b/src/tera/admin/user.rs @@ -7,11 +7,11 @@ use crate::{ mail::valid_mails, role::Role, user::{ - AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails, - UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser, clubmember::ClubMemberUser, foerdernd::FoerderndUser, member::Member, regular::RegularUser, scheckbuch::ScheckbuchUser, schnupperant::SchnupperantUser, schnupperinterest::SchnupperInterestUser, unterstuetzend::UnterstuetzendUser, + AdminUser, AllowedToEditPaymentStatusUser, ManageUserUser, User, UserWithDetails, + UserWithMembershipPdf, UserWithRolesAndMembershipPdf, VorstandUser, }, }, tera::Config, @@ -19,7 +19,6 @@ use crate::{ use chrono::NaiveDate; use futures::future::join_all; use rocket::{ - FromForm, Request, Route, State, form::Form, fs::TempFile, get, @@ -27,9 +26,9 @@ use rocket::{ post, request::{FlashMessage, FromRequest, Outcome}, response::{Flash, Redirect}, - routes, + routes, FromForm, Request, Route, State, }; -use rocket_dyn_templates::{Template, tera::Context}; +use rocket_dyn_templates::{tera::Context, Template}; use sqlx::SqlitePool; // Custom request guard to extract the Referer header @@ -133,6 +132,12 @@ async fn view( format!("User mit ID {} gibts ned", user), )); }; + if user.name == "Externe Steuerperson" { + return Err(Flash::error( + Redirect::to("/admin/user"), + "Diese besondere Person kannst du dir leider nicht anschauen, mein lieber neugieriger Ruderant!" + )); + } let member = Member::from(db, user.clone()).await; let fee = user.fee(db).await; diff --git a/src/tera/auth.rs b/src/tera/auth.rs index d9f3317..d820d7b 100644 --- a/src/tera/auth.rs +++ b/src/tera/auth.rs @@ -1,5 +1,4 @@ use rocket::{ - FromForm, Request, Route, State, form::Form, get, http::{Cookie, CookieJar}, @@ -9,11 +8,13 @@ use rocket::{ response::{Flash, Redirect}, routes, time::{Duration, OffsetDateTime}, + FromForm, Request, Route, State, }; -use rocket_dyn_templates::{Template, context, tera}; +use rocket_dyn_templates::{context, tera, Template}; use sqlx::SqlitePool; use crate::model::{ + activity::ActivityBuilder, log::Log, user::{LoginError, User}, }; @@ -82,13 +83,12 @@ async fn login( cookies.add_private(Cookie::new("loggedin_user", format!("{}", user.id))); - Log::create( - db, - format!( - "Succ login of {} with this useragent: {}", - login.name, agent.0 - ), - ) + ActivityBuilder::new(&format!( + "{user} hat sich eingeloggt (User-Agent: {})", + agent.0 + )) + .relevant_for_user(&user) + .save(db) .await; // Check for redirect_url cookie and redirect accordingly diff --git a/src/tera/log.rs b/src/tera/log.rs index 390e285..ad8cfa6 100644 --- a/src/tera/log.rs +++ b/src/tera/log.rs @@ -1,7 +1,6 @@ use std::net::IpAddr; use rocket::{ - Request, Route, State, form::Form, get, http::{Cookie, CookieJar}, @@ -10,8 +9,9 @@ use rocket::{ response::{Flash, Redirect}, routes, time::{Duration, OffsetDateTime}, + Request, Route, State, }; -use rocket_dyn_templates::{Template, context}; +use rocket_dyn_templates::{context, Template}; use sqlx::SqlitePool; use tera::Context; @@ -110,10 +110,13 @@ async fn index( #[get("/show", rank = 3)] async fn show(db: &State, user: DonauLinzUser) -> Template { let logs = Logbook::completed(db).await; + let boats = Boat::all(db).await; + let users = User::all(db).await; + let logtypes = LogType::all(db).await; Template::render( "log.completed", - context!(logs, loggedin_user: &UserWithDetails::from_user(user.into_inner(), db).await), + context!(logs, boats, users, logtypes, loggedin_user: &UserWithDetails::from_user(user.into_inner(), db).await), ) } @@ -582,7 +585,7 @@ mod test { use sqlx::SqlitePool; use crate::model::logbook::Logbook; - use crate::tera::{User, log::Boat}; + use crate::tera::{log::Boat, User}; use crate::testdb; #[sqlx::test] diff --git a/templates/admin/user/index.html.tera b/templates/admin/user/index.html.tera index d889e2f..05b1bd1 100644 --- a/templates/admin/user/index.html.tera +++ b/templates/admin/user/index.html.tera @@ -12,13 +12,13 @@
+ class="btn btn-primary">🥳 Vereinsmitglied + class="btn btn-dark">🧑‍🏫 Scheckbuch + class="btn btn-dark">👨‍🎓 Schnupperkurs
diff --git a/templates/admin/user/view.html.tera b/templates/admin/user/view.html.tera index e6a61bb..0c6e782 100644 --- a/templates/admin/user/view.html.tera +++ b/templates/admin/user/view.html.tera @@ -4,11 +4,13 @@ {% block content %}
{% if "admin" in loggedin_user.roles or "Vorstand" in loggedin_user.roles %} - ← Userverwaltung + {% endif %}

{{ user.name }}

-
-
+
+

Grunddaten
@@ -53,7 +55,7 @@

-
+

Mitgliedschaft
@@ -119,12 +121,12 @@

{% if allowed_to_edit %}
- {% if is_clubmember %} -
+

Rollen

    @@ -363,7 +365,7 @@
{% endif %} {% if supposed_to_pay %} -
+

💸-Beitrag

@@ -400,13 +402,13 @@
{% endif %} -
+

Aktivitäten

-
+
    {% for activity in activities %} -
  • {{ activity.created_at | date(format="%d. %m. %Y") }}: {{ activity.text }}
  • +
  • {{ activity.created_at | date(format="%d. %m. %Y") }}: {{ activity.text }}
  • {% else %}
  • Noch keine Aktivität... Stay tuned 😆
  • {% endfor %} @@ -414,13 +416,13 @@
-
+

Ergo-Challenge

-
-
- {{ macros::input(label='DOB', name='dob', type="text", value=user.dob, readonly=allowed_to_edit == false) }} - {{ macros::input(label='Weight (kg)', name='weight', type="text", value=user.weight, readonly=allowed_to_edit == false) }} - {{ macros::input(label='Sex', name='sex', type="text", value=user.sex, readonly=allowed_to_edit == false) }} +
+
+ {{ macros::inputgroup(label='DOB', name='dob', type="text", value=user.dob, readonly=allowed_to_edit == false) }} + {{ macros::inputgroup(label='Weight (kg)', name='weight', type="text", value=user.weight, readonly=allowed_to_edit == false) }} + {{ macros::inputgroup(label='Sex', name='sex', type="text", value=user.sex, readonly=allowed_to_edit == false) }}
diff --git a/templates/includes/forms/log.html.tera b/templates/includes/forms/log.html.tera index 04a3b6e..66e6f50 100644 --- a/templates/includes/forms/log.html.tera +++ b/templates/includes/forms/log.html.tera @@ -183,8 +183,6 @@
+
+ + {% endif %}
{% endmacro show_old %} {% macro home(log) %} diff --git a/templates/log.completed.html.tera b/templates/log.completed.html.tera index 622a34b..6ababff 100644 --- a/templates/log.completed.html.tera +++ b/templates/log.completed.html.tera @@ -26,7 +26,7 @@ {% for log in logs %} {% set_global allowed_to_edit = false %} {% if loggedin_user %} - {% if "Vorstand" in loggedin_user.roles %} + {% if "Vorstand" in loggedin_user.roles or "admin" in loggedin_user.roles %} {% set_global allowed_to_edit = true %} {% endif %} {% endif %}