more-activities #1035
@@ -17,10 +17,31 @@ pub struct Activity {
 | 
				
			|||||||
    pub keep_until: Option<NaiveDateTime>,
 | 
					    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
 | 
					// TODO: add `reason` as additional db field, to be able to query and show this to the users
 | 
				
			||||||
pub enum Reason<'a> {
 | 
					pub enum Reason<'a> {
 | 
				
			||||||
    // `User` tried to login with `String` as UserAgent
 | 
					    Auth(ReasonAuth<'a>),
 | 
				
			||||||
    SuccLogin(&'a User, String),
 | 
					 | 
				
			||||||
    // `User` changed the data of `User`, explanation in `String`
 | 
					    // `User` changed the data of `User`, explanation in `String`
 | 
				
			||||||
    UserDataChange(&'a ManageUserUser, &'a User, String),
 | 
					    UserDataChange(&'a ManageUserUser, &'a User, String),
 | 
				
			||||||
    // New Note for User
 | 
					    // New Note for User
 | 
				
			||||||
@@ -30,11 +51,7 @@ pub enum Reason<'a> {
 | 
				
			|||||||
impl From<Reason<'_>> for ActivityBuilder {
 | 
					impl From<Reason<'_>> for ActivityBuilder {
 | 
				
			||||||
    fn from(value: Reason<'_>) -> Self {
 | 
					    fn from(value: Reason<'_>) -> Self {
 | 
				
			||||||
        match value {
 | 
					        match value {
 | 
				
			||||||
            Reason::SuccLogin(user, agent) => {
 | 
					            Reason::Auth(auth) => auth.into(),
 | 
				
			||||||
                Self::new(&format!("{user} hat sich eingeloggt (User-Agent: {agent})"))
 | 
					 | 
				
			||||||
                    .relevant_for_user(user)
 | 
					 | 
				
			||||||
                    .keep_until_days(7)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            Reason::UserDataChange(changed_by, changed_user, explanation) => Self::new(&format!(
 | 
					            Reason::UserDataChange(changed_by, changed_user, explanation) => Self::new(&format!(
 | 
				
			||||||
                "{changed_by} hat die Daten von {changed_user} aktualisiert: {explanation}"
 | 
					                "{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 {
 | 
					pub struct ActivityBuilder {
 | 
				
			||||||
    text: String,
 | 
					    text: String,
 | 
				
			||||||
    relevant_for: String,
 | 
					    relevant_for: String,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ use rocket::{
 | 
				
			|||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
 | 
					use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::activity::ActivityBuilder;
 | 
					use super::activity::{ActivityBuilder, ReasonAuth};
 | 
				
			||||||
use super::{
 | 
					use super::{
 | 
				
			||||||
    log::Log,
 | 
					    log::Log,
 | 
				
			||||||
    logbook::Logbook,
 | 
					    logbook::Logbook,
 | 
				
			||||||
@@ -465,51 +465,27 @@ ASKÖ Ruderverein Donau Linz", self.name),
 | 
				
			|||||||
    pub async fn login(db: &SqlitePool, name: &str, pw: &str) -> Result<Self, LoginError> {
 | 
					    pub async fn login(db: &SqlitePool, name: &str, pw: &str) -> Result<Self, LoginError> {
 | 
				
			||||||
        let name = name.trim().to_lowercase(); // just to make sure...
 | 
					        let name = name.trim().to_lowercase(); // just to make sure...
 | 
				
			||||||
        let Some(user) = User::find_by_name(db, &name).await else {
 | 
					        let Some(user) = User::find_by_name(db, &name).await else {
 | 
				
			||||||
            if ![
 | 
					            Log::create(db, format!("Username ({name}) not found (tried to login)")).await;
 | 
				
			||||||
                "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
 | 
					            return Err(LoginError::InvalidAuthenticationCombo); // Username not found
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if user.deleted {
 | 
					        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."
 | 
					                "User {user} wollte sich einloggen, klappte jedoch nicht weil er gelöscht wurde."
 | 
				
			||||||
            ))
 | 
					            ),
 | 
				
			||||||
            .relevant_for_user(&user)
 | 
					                    "Fehlgeschlagener Login",
 | 
				
			||||||
            .save(db)
 | 
					                    None,
 | 
				
			||||||
            .await;
 | 
					                    None,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .await;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ActivityBuilder::from(ReasonAuth::DeletedUserLogin(&user))
 | 
				
			||||||
 | 
					                .save(db)
 | 
				
			||||||
 | 
					                .await;
 | 
				
			||||||
            return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has
 | 
					            return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has
 | 
				
			||||||
                                                                //been deleted
 | 
					                                                                //been deleted
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -519,12 +495,9 @@ ASKÖ Ruderverein Donau Linz", self.name),
 | 
				
			|||||||
            if password_hash == user_pw {
 | 
					            if password_hash == user_pw {
 | 
				
			||||||
                return Ok(user);
 | 
					                return Ok(user);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ActivityBuilder::new(&format!(
 | 
					            ActivityBuilder::from(ReasonAuth::WrongPw(&user))
 | 
				
			||||||
                "User {user} wollte sich einloggen, hat jedoch das falsche Passwort angegeben."
 | 
					                .save(db)
 | 
				
			||||||
            ))
 | 
					                .await;
 | 
				
			||||||
            .relevant_for_user(&user)
 | 
					 | 
				
			||||||
            .save(db)
 | 
					 | 
				
			||||||
            .await;
 | 
					 | 
				
			||||||
            Err(LoginError::InvalidAuthenticationCombo)
 | 
					            Err(LoginError::InvalidAuthenticationCombo)
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            info!("User {name} has no PW set");
 | 
					            info!("User {name} has no PW set");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    model::{
 | 
					    model::{
 | 
				
			||||||
        activity::Activity,
 | 
					        activity::{Activity, ActivityWithDetails},
 | 
				
			||||||
        family::Family,
 | 
					        family::Family,
 | 
				
			||||||
        log::Log,
 | 
					        log::Log,
 | 
				
			||||||
        logbook::Logbook,
 | 
					        logbook::Logbook,
 | 
				
			||||||
@@ -141,7 +141,11 @@ async fn view(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let member = Member::from(db, user.clone()).await;
 | 
					    let member = Member::from(db, user.clone()).await;
 | 
				
			||||||
    let fee = user.fee(db).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 financial = Role::all_cluster(db, "financial").await;
 | 
				
			||||||
    let user_financial = user.financial(db).await;
 | 
					    let user_financial = user.financial(db).await;
 | 
				
			||||||
    let skill = Role::all_cluster(db, "skill").await;
 | 
					    let skill = Role::all_cluster(db, "skill").await;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,7 @@ use rocket_dyn_templates::{context, tera, Template};
 | 
				
			|||||||
use sqlx::SqlitePool;
 | 
					use sqlx::SqlitePool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::model::{
 | 
					use crate::model::{
 | 
				
			||||||
    activity::{self, ActivityBuilder},
 | 
					    activity::{self, ActivityBuilder, ReasonAuth},
 | 
				
			||||||
    log::Log,
 | 
					    log::Log,
 | 
				
			||||||
    user::{LoginError, User},
 | 
					    user::{LoginError, User},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -83,7 +83,7 @@ async fn login(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    cookies.add_private(Cookie::new("loggedin_user", format!("{}", user.id)));
 | 
					    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)
 | 
					        .save(db)
 | 
				
			||||||
        .await;
 | 
					        .await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -411,7 +411,11 @@
 | 
				
			|||||||
                        <ul class="list-disc ms-4">
 | 
					                        <ul class="list-disc ms-4">
 | 
				
			||||||
                            {% for activity in activities %}
 | 
					                            {% for activity in activities %}
 | 
				
			||||||
                                <li>
 | 
					                                <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>
 | 
					                                </li>
 | 
				
			||||||
                            {% else %}
 | 
					                            {% else %}
 | 
				
			||||||
                                <li>Noch keine Aktivität... Stay tuned 😆</li>
 | 
					                                <li>Noch keine Aktivität... Stay tuned 😆</li>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user