more-activities #1036

Merged
philipp merged 2 commits from more-activities into main 2025-05-17 09:50:11 +02:00
5 changed files with 94 additions and 59 deletions
Showing only changes of commit f7bb394236 - Show all commits

View File

@ -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,

View File

@ -13,7 +13,7 @@ use rocket::{
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
use super::activity::ActivityBuilder;
use super::activity::{ActivityBuilder, ReasonAuth};
use super::{
log::Log,
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> {
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
};
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)
.save(db)
.await;
),
"Fehlgeschlagener Login",
None,
None,
)
.await;
}
ActivityBuilder::from(ReasonAuth::DeletedUserLogin(&user))
.save(db)
.await;
return Err(LoginError::InvalidAuthenticationCombo); //User existed sometime ago; has
//been deleted
}
@ -519,12 +495,9 @@ 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)
.save(db)
.await;
ActivityBuilder::from(ReasonAuth::WrongPw(&user))
.save(db)
.await;
Err(LoginError::InvalidAuthenticationCombo)
} else {
info!("User {name} has no PW set");

View File

@ -1,6 +1,6 @@
use crate::{
model::{
activity::Activity,
activity::{Activity, ActivityWithDetails},
family::Family,
log::Log,
logbook::Logbook,
@ -141,7 +141,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;

View File

@ -14,7 +14,7 @@ use rocket_dyn_templates::{context, tera, Template};
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;

View File

@ -411,7 +411,11 @@
<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>