Merge pull request 'more-activities' (#1035) from more-activities into staging
Reviewed-on: #1035
This commit is contained in:
commit
a63d29a42a
@ -17,10 +17,31 @@ pub struct Activity {
|
||||
pub keep_until: Option<NaiveDateTime>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ActivityWithDetails {
|
||||
#[serde(flatten)]
|
||||
pub(crate) activity: Activity,
|
||||
keep_until_days: Option<i64>,
|
||||
}
|
||||
|
||||
impl From<Activity> for ActivityWithDetails {
|
||||
fn from(activity: Activity) -> Self {
|
||||
let keep_until_days = activity.keep_until.map(|keep_until| {
|
||||
let now = Utc::now().naive_utc();
|
||||
let duration = keep_until.signed_duration_since(now);
|
||||
duration.num_days()
|
||||
});
|
||||
|
||||
Self {
|
||||
keep_until_days,
|
||||
activity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add `reason` as additional db field, to be able to query and show this to the users
|
||||
pub enum Reason<'a> {
|
||||
// `User` tried to login with `String` as UserAgent
|
||||
SuccLogin(&'a User, String),
|
||||
Auth(ReasonAuth<'a>),
|
||||
// `User` changed the data of `User`, explanation in `String`
|
||||
UserDataChange(&'a ManageUserUser, &'a User, String),
|
||||
// New Note for User
|
||||
@ -30,11 +51,7 @@ pub enum Reason<'a> {
|
||||
impl From<Reason<'_>> for ActivityBuilder {
|
||||
fn from(value: Reason<'_>) -> Self {
|
||||
match value {
|
||||
Reason::SuccLogin(user, agent) => {
|
||||
Self::new(&format!("{user} hat sich eingeloggt (User-Agent: {agent})"))
|
||||
.relevant_for_user(user)
|
||||
.keep_until_days(7)
|
||||
}
|
||||
Reason::Auth(auth) => auth.into(),
|
||||
Reason::UserDataChange(changed_by, changed_user, explanation) => Self::new(&format!(
|
||||
"{changed_by} hat die Daten von {changed_user} aktualisiert: {explanation}"
|
||||
))
|
||||
@ -46,6 +63,43 @@ impl From<Reason<'_>> for ActivityBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ReasonAuth<'a> {
|
||||
// `User` tried to login with `String` as UserAgent
|
||||
SuccLogin(&'a User, String),
|
||||
// `User` tried to login which was already deleted
|
||||
DeletedUserLogin(&'a User),
|
||||
// `User` tried to login, supplied wrong PW
|
||||
WrongPw(&'a User),
|
||||
}
|
||||
|
||||
impl<'a> From<ReasonAuth<'a>> for Reason<'a> {
|
||||
fn from(auth_reason: ReasonAuth<'a>) -> Self {
|
||||
Reason::Auth(auth_reason)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReasonAuth<'_>> for ActivityBuilder {
|
||||
fn from(value: ReasonAuth<'_>) -> Self {
|
||||
match value {
|
||||
ReasonAuth::SuccLogin(user, agent) => {
|
||||
Self::new(&format!("{user} hat sich eingeloggt (User-Agent: {agent})"))
|
||||
.relevant_for_user(user)
|
||||
.keep_until_days(7)
|
||||
}
|
||||
ReasonAuth::DeletedUserLogin(user) => Self::new(&format!(
|
||||
"User {user} wollte sich einloggen, klappte jedoch nicht weil er gelöscht wurde."
|
||||
))
|
||||
.relevant_for_user(user)
|
||||
.keep_until_days(30),
|
||||
ReasonAuth::WrongPw(user) => Self::new(&format!(
|
||||
"User {user} wollte sich einloggen, hat jedoch das falsche Passwort angegeben."
|
||||
))
|
||||
.relevant_for_user(user)
|
||||
.keep_until_days(7),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ActivityBuilder {
|
||||
text: String,
|
||||
relevant_for: String,
|
||||
|
@ -1,8 +1,8 @@
|
||||
use std::ops::DerefMut;
|
||||
|
||||
use chrono::NaiveDateTime;
|
||||
use rocket::serde::{Deserialize, Serialize};
|
||||
use rocket::FromForm;
|
||||
use rocket::serde::{Deserialize, Serialize};
|
||||
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
||||
|
||||
use crate::model::boathouse::Boathouse;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::ops::DerefMut;
|
||||
|
||||
use serde::Serialize;
|
||||
use sqlx::{sqlite::SqliteQueryResult, FromRow, Sqlite, SqlitePool, Transaction};
|
||||
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction, sqlite::SqliteQueryResult};
|
||||
|
||||
use super::user::User;
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
use std::{error::Error, fs};
|
||||
|
||||
use lettre::{
|
||||
message::{header::ContentType, Attachment, MultiPart, SinglePart},
|
||||
transport::smtp::authentication::Credentials,
|
||||
Address, Message, SmtpTransport, Transport,
|
||||
message::{Attachment, MultiPart, SinglePart, header::ContentType},
|
||||
transport::smtp::authentication::Credentials,
|
||||
};
|
||||
use sqlx::{Sqlite, SqlitePool, Transaction};
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
use super::User;
|
||||
use crate::{
|
||||
model::family::Family, BOAT_STORAGE, DUAL_MEMBERSHIP, EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE,
|
||||
FAMILY_TWO, FOERDERND, REGULAR, RENNRUDERBEITRAG, STUDENT_OR_PUPIL, TRIAL_ROWING,
|
||||
TRIAL_ROWING_REDUCED, UNTERSTUETZEND,
|
||||
BOAT_STORAGE, DUAL_MEMBERSHIP, EINSCHREIBGEBUEHR, FAMILY_THREE_OR_MORE, FAMILY_TWO, FOERDERND,
|
||||
REGULAR, RENNRUDERBEITRAG, STUDENT_OR_PUPIL, TRIAL_ROWING, TRIAL_ROWING_REDUCED,
|
||||
UNTERSTUETZEND, model::family::Family,
|
||||
};
|
||||
use chrono::{Datelike, Local, NaiveDate};
|
||||
use serde::Serialize;
|
||||
|
@ -1,20 +1,21 @@
|
||||
use std::{fmt::Display, ops::DerefMut};
|
||||
|
||||
use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
|
||||
use argon2::{Argon2, PasswordHasher, password_hash::SaltString};
|
||||
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::activity::{ActivityBuilder, ReasonAuth};
|
||||
use super::{
|
||||
Day,
|
||||
log::Log,
|
||||
logbook::Logbook,
|
||||
mail::Mail,
|
||||
@ -23,7 +24,6 @@ use super::{
|
||||
role::Role,
|
||||
stat::Stat,
|
||||
tripdetails::TripDetails,
|
||||
Day,
|
||||
};
|
||||
use crate::AMOUNT_DAYS_TO_SHOW_TRIPS_AHEAD;
|
||||
use scheckbuch::ScheckbuchUser;
|
||||
@ -465,49 +465,25 @@ ASKÖ Ruderverein Donau Linz", self.name),
|
||||
pub async fn login(db: &SqlitePool, name: &str, pw: &str) -> Result<Self, LoginError> {
|
||||
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;
|
||||
}
|
||||
return Err(LoginError::InvalidAuthenticationCombo); // Username not found
|
||||
};
|
||||
|
||||
if user.deleted {
|
||||
ActivityBuilder::new(&format!(
|
||||
if let Some(board) = Role::find_by_name(db, "Vorstand").await {
|
||||
Notification::create_for_role(
|
||||
db,
|
||||
&board,
|
||||
&format!(
|
||||
"User {user} wollte sich einloggen, klappte jedoch nicht weil er gelöscht wurde."
|
||||
))
|
||||
.relevant_for_user(&user)
|
||||
),
|
||||
"Fehlgeschlagener Login",
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
ActivityBuilder::from(ReasonAuth::DeletedUserLogin(&user))
|
||||
.save(db)
|
||||
.await;
|
||||
return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has
|
||||
@ -519,10 +495,7 @@ ASKÖ Ruderverein Donau Linz", self.name),
|
||||
if password_hash == user_pw {
|
||||
return Ok(user);
|
||||
}
|
||||
ActivityBuilder::new(&format!(
|
||||
"User {user} wollte sich einloggen, hat jedoch das falsche Passwort angegeben."
|
||||
))
|
||||
.relevant_for_user(&user)
|
||||
ActivityBuilder::from(ReasonAuth::WrongPw(&user))
|
||||
.save(db)
|
||||
.await;
|
||||
Err(LoginError::InvalidAuthenticationCombo)
|
||||
@ -862,8 +835,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");
|
||||
@ -977,17 +950,21 @@ mod test {
|
||||
#[sqlx::test]
|
||||
fn wrong_pw() {
|
||||
let pool = testdb!();
|
||||
assert!(User::login(&pool, "admin".into(), "admi".into())
|
||||
assert!(
|
||||
User::login(&pool, "admin".into(), "admi".into())
|
||||
.await
|
||||
.is_err());
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
fn wrong_username() {
|
||||
let pool = testdb!();
|
||||
assert!(User::login(&pool, "admi".into(), "admin".into())
|
||||
assert!(
|
||||
User::login(&pool, "admi".into(), "admin".into())
|
||||
.await
|
||||
.is_err());
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
|
||||
#[sqlx::test]
|
||||
@ -1007,9 +984,11 @@ mod test {
|
||||
let pool = testdb!();
|
||||
let user = User::find_by_id(&pool, 1).await.unwrap();
|
||||
|
||||
assert!(User::login(&pool, "admin".into(), "abc".into())
|
||||
assert!(
|
||||
User::login(&pool, "admin".into(), "abc".into())
|
||||
.await
|
||||
.is_err());
|
||||
.is_err()
|
||||
);
|
||||
|
||||
user.update_pw(&pool, "abc".into()).await;
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
use super::{ManageUserUser, User};
|
||||
use crate::{
|
||||
NonEmptyString,
|
||||
model::{activity::ActivityBuilder, mail::Mail, notification::Notification, role::Role},
|
||||
special_user, NonEmptyString,
|
||||
special_user,
|
||||
};
|
||||
use chrono::NaiveDate;
|
||||
use rocket::{async_trait, fs::TempFile, tokio::io::AsyncReadExt};
|
||||
|
@ -2,12 +2,13 @@ use super::foerdernd::FoerderndUser;
|
||||
use super::regular::RegularUser;
|
||||
use super::unterstuetzend::UnterstuetzendUser;
|
||||
use super::{ManageUserUser, User};
|
||||
use crate::NonEmptyString;
|
||||
use crate::model::activity::ActivityBuilder;
|
||||
use crate::model::role::Role;
|
||||
use crate::NonEmptyString;
|
||||
use crate::{
|
||||
SCHECKBUCH,
|
||||
model::{mail::Mail, notification::Notification},
|
||||
special_user, SCHECKBUCH,
|
||||
special_user,
|
||||
};
|
||||
use chrono::NaiveDate;
|
||||
use rocket::async_trait;
|
||||
|
@ -4,9 +4,9 @@ use super::scheckbuch::ScheckbuchUser;
|
||||
use super::schnupperinterest::SchnupperInterestUser;
|
||||
use super::unterstuetzend::UnterstuetzendUser;
|
||||
use super::{ManageUserUser, User};
|
||||
use crate::NonEmptyString;
|
||||
use crate::model::activity::ActivityBuilder;
|
||||
use crate::model::role::Role;
|
||||
use crate::NonEmptyString;
|
||||
use crate::{
|
||||
model::{mail::Mail, notification::Notification},
|
||||
special_user,
|
||||
|
@ -1,6 +1,6 @@
|
||||
use csv::ReaderBuilder;
|
||||
use rocket::{form::Form, get, post, routes, FromForm, Route, State};
|
||||
use rocket_dyn_templates::{context, Template};
|
||||
use rocket::{FromForm, Route, State, form::Form, get, post, routes};
|
||||
use rocket_dyn_templates::{Template, context};
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
use crate::model::{activity::Activity, role::Role, user::AdminUser};
|
||||
|
@ -1,17 +1,17 @@
|
||||
use crate::{
|
||||
model::{
|
||||
activity::Activity,
|
||||
activity::{Activity, ActivityWithDetails},
|
||||
family::Family,
|
||||
log::Log,
|
||||
logbook::Logbook,
|
||||
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,6 +19,7 @@ use crate::{
|
||||
use chrono::NaiveDate;
|
||||
use futures::future::join_all;
|
||||
use rocket::{
|
||||
FromForm, Request, Route, State,
|
||||
form::Form,
|
||||
fs::TempFile,
|
||||
get,
|
||||
@ -26,9 +27,9 @@ use rocket::{
|
||||
post,
|
||||
request::{FlashMessage, FromRequest, Outcome},
|
||||
response::{Flash, Redirect},
|
||||
routes, FromForm, Request, Route, State,
|
||||
routes,
|
||||
};
|
||||
use rocket_dyn_templates::{tera::Context, Template};
|
||||
use rocket_dyn_templates::{Template, tera::Context};
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
// Custom request guard to extract the Referer header
|
||||
@ -141,7 +142,11 @@ async fn view(
|
||||
|
||||
let member = Member::from(db, user.clone()).await;
|
||||
let fee = user.fee(db).await;
|
||||
let activities = Activity::for_user(db, &user).await;
|
||||
let activities: Vec<ActivityWithDetails> = Activity::for_user(db, &user)
|
||||
.await
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
let financial = Role::all_cluster(db, "financial").await;
|
||||
let user_financial = user.financial(db).await;
|
||||
let skill = Role::all_cluster(db, "skill").await;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use rocket::{
|
||||
FromForm, Request, Route, State,
|
||||
form::Form,
|
||||
get,
|
||||
http::{Cookie, CookieJar},
|
||||
@ -8,13 +9,12 @@ use rocket::{
|
||||
response::{Flash, Redirect},
|
||||
routes,
|
||||
time::{Duration, OffsetDateTime},
|
||||
FromForm, Request, Route, State,
|
||||
};
|
||||
use rocket_dyn_templates::{context, tera, Template};
|
||||
use rocket_dyn_templates::{Template, context, tera};
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
use crate::model::{
|
||||
activity::{self, ActivityBuilder},
|
||||
activity::{self, ActivityBuilder, ReasonAuth},
|
||||
log::Log,
|
||||
user::{LoginError, User},
|
||||
};
|
||||
@ -83,7 +83,7 @@ async fn login(
|
||||
|
||||
cookies.add_private(Cookie::new("loggedin_user", format!("{}", user.id)));
|
||||
|
||||
ActivityBuilder::from(activity::Reason::SuccLogin(&user, agent.0))
|
||||
ActivityBuilder::from(ReasonAuth::SuccLogin(&user, agent.0))
|
||||
.save(db)
|
||||
.await;
|
||||
|
||||
|
@ -411,7 +411,9 @@
|
||||
<ul class="list-disc ms-4">
|
||||
{% for activity in activities %}
|
||||
<li>
|
||||
<strong>{{ activity.created_at | date(format="%d. %m. %Y") }}:</strong> <small>{{ activity.text }}</small>
|
||||
<strong>{{ activity.created_at | date(format="%d. %m. %Y") }}:</strong> <small>{{ activity.text }}
|
||||
{% if activity.keep_until_days %}(⏳ {{ activity.keep_until_days }} Tage){% endif %}
|
||||
</small>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>Noch keine Aktivität... Stay tuned 😆</li>
|
||||
|
Loading…
x
Reference in New Issue
Block a user