236 lines
6.7 KiB
Rust
236 lines
6.7 KiB
Rust
use std::ops::DerefMut;
|
|
|
|
use super::{
|
|
role::Role,
|
|
user::{ManageUserUser, User},
|
|
};
|
|
use chrono::{DateTime, Duration, Local, NaiveDateTime, TimeZone, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use sqlx::{FromRow, Sqlite, SqlitePool, Transaction};
|
|
|
|
#[derive(FromRow, Debug, Serialize, Deserialize, Clone)]
|
|
pub struct Activity {
|
|
pub id: i64,
|
|
pub created_at: NaiveDateTime,
|
|
pub text: String,
|
|
pub relevant_for: String,
|
|
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> {
|
|
Auth(ReasonAuth<'a>),
|
|
// `User` changed the data of `User`, explanation in `String`
|
|
UserDataChange(&'a ManageUserUser, &'a User, String),
|
|
// New Note for User
|
|
NewUserNote(&'a ManageUserUser, &'a User, String),
|
|
}
|
|
|
|
impl From<Reason<'_>> for ActivityBuilder {
|
|
fn from(value: Reason<'_>) -> Self {
|
|
match value {
|
|
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}"
|
|
))
|
|
.relevant_for_user(changed_user),
|
|
Reason::NewUserNote(changed_by, user, explanation) => {
|
|
Self::new(&format!("({changed_by}) {explanation}")).relevant_for_user(user)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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,
|
|
keep_until: Option<NaiveDateTime>,
|
|
}
|
|
|
|
impl ActivityBuilder {
|
|
/// TODO: maybe make this private, and only allow specific acitivites defined in `Reason`
|
|
#[must_use]
|
|
pub fn new(text: &str) -> Self {
|
|
Self {
|
|
text: text.into(),
|
|
relevant_for: String::new(),
|
|
keep_until: None,
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn relevant_for_user(self, user: &User) -> Self {
|
|
Self {
|
|
relevant_for: format!("{}user-{};", self.relevant_for, user.id),
|
|
..self
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn relevant_for_role(self, role: &Role) -> Self {
|
|
Self {
|
|
relevant_for: format!("{}role-{};", self.relevant_for, role.id),
|
|
..self
|
|
}
|
|
}
|
|
|
|
#[must_use]
|
|
pub fn keep_until_days(self, days: i64) -> Self {
|
|
let now = Utc::now().naive_utc();
|
|
Self {
|
|
keep_until: Some(now + Duration::days(days)),
|
|
..self
|
|
}
|
|
}
|
|
|
|
pub async fn save(self, db: &SqlitePool) {
|
|
Activity::create(db, &self.text, &self.relevant_for, self.keep_until).await;
|
|
}
|
|
|
|
pub async fn save_tx(self, db: &mut Transaction<'_, Sqlite>) {
|
|
Activity::create_with_tx(db, &self.text, &self.relevant_for, self.keep_until).await;
|
|
}
|
|
}
|
|
|
|
impl Activity {
|
|
pub async fn find_by_id(db: &SqlitePool, id: i64) -> Option<Self> {
|
|
sqlx::query_as!(
|
|
Self,
|
|
"SELECT id, created_at, text, relevant_for, keep_until FROM activity WHERE id like ?",
|
|
id
|
|
)
|
|
.fetch_one(db)
|
|
.await
|
|
.ok()
|
|
}
|
|
pub(super) async fn create_with_tx(
|
|
db: &mut Transaction<'_, Sqlite>,
|
|
text: &str,
|
|
relevant_for: &str,
|
|
keep_until: Option<NaiveDateTime>,
|
|
) {
|
|
sqlx::query!(
|
|
"INSERT INTO activity(text, relevant_for, keep_until) VALUES (?, ?, ?)",
|
|
text,
|
|
relevant_for,
|
|
keep_until
|
|
)
|
|
.execute(db.deref_mut())
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
pub(super) async fn create(
|
|
db: &SqlitePool,
|
|
text: &str,
|
|
relevant_for: &str,
|
|
keep_until: Option<NaiveDateTime>,
|
|
) {
|
|
let mut tx = db.begin().await.unwrap();
|
|
Self::create_with_tx(&mut tx, text, relevant_for, keep_until).await;
|
|
tx.commit().await.unwrap();
|
|
}
|
|
|
|
pub async fn for_user(db: &SqlitePool, user: &User) -> Vec<Activity> {
|
|
let user_str = format!("user-{};", user.id);
|
|
sqlx::query_as!(
|
|
Self,
|
|
"
|
|
SELECT id, created_at, text, relevant_for, keep_until FROM activity
|
|
WHERE
|
|
relevant_for like CONCAT('%', ?, '%')
|
|
ORDER BY created_at DESC;
|
|
",
|
|
user_str
|
|
)
|
|
.fetch_all(db)
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
async fn last(db: &SqlitePool) -> Vec<Self> {
|
|
sqlx::query_as!(
|
|
Self,
|
|
"
|
|
SELECT id, created_at, text, relevant_for, keep_until FROM activity
|
|
ORDER BY id DESC
|
|
LIMIT 1000
|
|
"
|
|
)
|
|
.fetch_all(db)
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
pub async fn show(db: &SqlitePool) -> String {
|
|
let mut ret = String::new();
|
|
|
|
for log in Self::last(db).await {
|
|
let utc_time: DateTime<Utc> = Utc::from_utc_datetime(&Utc, &log.created_at);
|
|
let local_time = utc_time.with_timezone(&Local);
|
|
ret.push_str(&format!("- {local_time}: {}\n", log.text));
|
|
}
|
|
|
|
ret
|
|
}
|
|
}
|