Files
rowt/src/model/activity.rs
Philipp Hofer ed6d05eb9e
Some checks failed
CI/CD Pipeline / deploy-staging (push) Blocked by required conditions
CI/CD Pipeline / deploy-main (push) Blocked by required conditions
CI/CD Pipeline / test (push) Has been cancelled
not so much clutter when addding notes
2025-05-14 08:04:56 +02:00

182 lines
5.1 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>,
}
// 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),
// `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::SuccLogin(user, agent) => {
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!(
"{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 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
}
}