Files
mnemosyne/src/logs.rs
2026-04-09 18:35:54 +02:00

169 lines
5.2 KiB
Rust

use rusqlite::Connection;
use serde::{Deserialize, Serialize};
use strum::IntoStaticStr;
use uuid::Uuid;
use crate::{database::DatabaseError, users::User};
#[derive(Debug)]
pub struct LogEntry {
pub id: Uuid,
pub actor: User,
pub data: LogAction,
}
impl LogEntry {
pub fn new(conn: &Connection, actor: User, data: LogAction) -> Result<LogEntry, DatabaseError> {
let log = LogEntry {
id: Uuid::now_v7(),
actor,
data,
};
let actiontype: &'static str = (&log.data).into();
let payload = serde_json::to_string(&log.data).unwrap();
conn.prepare(
"INSERT INTO logs(id, actor, target, actiontype, payload) VALUES (?1,?2,?3,?4,?5)",
)?
.execute((
&log.id,
&log.actor.id,
log.data.get_target_id(),
actiontype,
payload,
))?;
Ok(log)
}
pub fn total_count(conn: &Connection) -> Result<i64, DatabaseError> {
Ok(conn.query_row("SELECT COUNT(*) FROM logs", (), |r| r.get(0))?)
}
pub fn get_chronological_offset(
conn: &Connection,
offset: i64,
limit: i64,
) -> Result<Vec<LogEntry>, DatabaseError> {
Ok(conn
.prepare("SELECT id, actor, target, actiontype, payload FROM logs ORDER BY id DESC LIMIT ?1 OFFSET ?2")?
.query_map((limit, offset), |r| {
let payload: String = r.get(4)?;
Ok(LogEntry {
id: r.get(0)?,
actor: User::get_by_id(conn, r.get(1)?).unwrap(),
data: serde_json::from_str(&payload).unwrap(),
})
})?
.collect::<Result<Vec<LogEntry>, _>>()?)
}
}
// #[derive(Debug, thiserror::Error)]
// pub enum LogError {}
#[derive(Debug, IntoStaticStr, Serialize, Deserialize)]
pub enum LogAction {
Initialize,
RegenInfradmin,
CreateUser {
id: Uuid,
handle: String,
},
ManuallyChangeUsersPassword {
id: Uuid,
},
CreateTag {
id: Uuid,
name: String,
},
RenameTag {
id: Uuid,
on: String,
nn: String,
},
DeleteTag {
id: Uuid,
name: String,
},
CreatePerson {
id: Uuid,
pname: String,
},
ChangeUserHandle {
id: Uuid,
old: String,
new: String,
},
AddPersonName {
pid: Uuid, // person id
nid: Uuid, // name id
pn: String, // primary name
nn: String, // new name
},
SetPersonPrimaryName {
pid: Uuid, // person id
nid: Uuid, // name id
on: String, // old name
nn: String, // new name
},
CreateQuote {
id: Uuid,
},
ManuallyRevokeSession {
id: Uuid,
},
}
impl LogAction {
pub fn get_target_id(&self) -> Option<Uuid> {
match self {
Self::Initialize | Self::RegenInfradmin => None,
Self::CreateUser { id, .. }
| Self::CreateTag { id, .. }
| Self::CreatePerson { id, .. }
| Self::ChangeUserHandle { id, .. }
| Self::CreateQuote { id }
| Self::ManuallyRevokeSession { id }
| Self::RenameTag { id, .. }
| Self::DeleteTag { id, .. }
| Self::ManuallyChangeUsersPassword { id } => Some(*id),
Self::AddPersonName { pid, .. } | Self::SetPersonPrimaryName { pid, .. } => Some(*pid),
}
}
pub fn get_humanreadable_payload(&self) -> String {
match self {
LogAction::Initialize => format!("Initialized Mnemosyne."),
LogAction::RegenInfradmin => format!("Regenerated the Infradmin account."),
LogAction::CreateUser { id, handle } => {
format!("Created user @{handle} (uid: {id})")
}
LogAction::ManuallyChangeUsersPassword { id } => {
format!("Manually changed password of user with id: {id}")
}
LogAction::CreateTag { id, name } => {
format!("Created tag #{name} (id: {id})")
}
LogAction::RenameTag { id, on, nn } => {
format!("Renamed tag #{on} -> #{nn} (id: {id})")
}
LogAction::DeleteTag { id, name } => {
format!("Deleted tag #{name} (id: {id})")
}
LogAction::CreatePerson { id, pname } => {
format!("Created person ~{pname} (id: {id})")
}
LogAction::ChangeUserHandle { id, old, new } => {
format!("Changed user handle @{old} -> @{new} (uid: {id})")
}
LogAction::AddPersonName { pid, nid, pn, nn } => {
format!("Added name \"{nn}\" to ~{pn} (pid: {pid}; nid: {nid})")
}
LogAction::SetPersonPrimaryName { pid, nid, on, nn } => {
format!("~{on} now has primary name \"{nn}\" (pid: {pid}; nid: {nid})")
}
LogAction::CreateQuote { id } => {
format!("Created quote of ID {id}")
}
LogAction::ManuallyRevokeSession { id } => {
format!("Revoked session of ID {id}")
}
}
}
}