From d8fb561bca7c6446448c07de52f56e63aa044254 Mon Sep 17 00:00:00 2001 From: jakubmanczak Date: Sat, 4 Apr 2026 02:44:17 +0200 Subject: [PATCH] log everything --- src/api/persons.rs | 59 ++++++++++++++++++++----- src/api/quotes.rs | 12 +++-- src/api/sessions.rs | 18 +++++--- src/api/tags.rs | 57 +++++++++++++++++++----- src/api/users.rs | 67 ++++++++++++++++++++++------ src/logs.rs | 82 ++++++++++++++++++++++++++++++++--- src/web/pages/usersettings.rs | 3 +- 7 files changed, 246 insertions(+), 52 deletions(-) diff --git a/src/api/persons.rs b/src/api/persons.rs index 78e8378..b6c6b00 100644 --- a/src/api/persons.rs +++ b/src/api/persons.rs @@ -9,7 +9,8 @@ use uuid::Uuid; use crate::{ api::CompositeError, - database, + database::{self, DatabaseError}, + logs::{LogAction, LogEntry}, persons::{Name, Person}, users::{ User, @@ -52,8 +53,19 @@ pub async fn create( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let conn = database::conn()?; - let p = Person::create(&conn, form.name, u.id)?; + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + + let p = Person::create(&tx, form.name, u.id)?; + LogEntry::new( + &tx, + u, + LogAction::CreatePerson { + id: p.id, + pname: p.primary_name.as_str().to_string(), + }, + )?; + tx.commit().map_err(DatabaseError::from)?; Ok((StatusCode::CREATED, Json(p)).into_response()) } pub async fn add_name( @@ -62,10 +74,22 @@ pub async fn add_name( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let conn = database::conn()?; - let p = Person::get_by_id(&conn, id)?; - let n = p.add_name(&conn, form.name, u.id)?; + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + let p = Person::get_by_id(&tx, id)?; + let n = p.add_name(&tx, form.name, u.id)?; + LogEntry::new( + &tx, + u, + LogAction::AddPersonName { + pid: p.id, + nid: n.id, + pn: p.primary_name, + nn: n.name.clone(), + }, + )?; + tx.commit().map_err(DatabaseError::from)?; Ok((StatusCode::CREATED, Json(n)).into_response()) } @@ -79,13 +103,28 @@ pub async fn n_setprimary( headers: HeaderMap, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let conn = database::conn()?; - if !u.has_permission(&conn, Permission::ChangePersonPrimaryName)? { + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + + if !u.has_permission(&tx, Permission::ChangePersonPrimaryName)? { return Ok((StatusCode::FORBIDDEN, CANT_SET_PRIMARYNAME).into_response()); } - let mut n = Name::get_by_id(&conn, id)?; - n.set_primary(&conn)?; + let mut n = Name::get_by_id(&tx, id)?; + let p = Person::get_by_id(&tx, n.person_id)?; + n.set_primary(&tx)?; n.is_primary = true; + LogEntry::new( + &tx, + u, + LogAction::SetPersonPrimaryName { + pid: p.id, + nid: n.id, + on: p.primary_name, + nn: n.name.clone(), + }, + )?; + tx.commit().map_err(DatabaseError::from)?; + Ok(Json(n).into_response()) } diff --git a/src/api/quotes.rs b/src/api/quotes.rs index 3842484..997a761 100644 --- a/src/api/quotes.rs +++ b/src/api/quotes.rs @@ -10,7 +10,8 @@ use uuid::Uuid; use crate::{ api::CompositeError, - database, + database::{self, DatabaseError}, + logs::{LogAction, LogEntry}, persons::Name, quotes::Quote, users::{ @@ -48,16 +49,17 @@ pub async fn create( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let conn = database::conn()?; + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; let lines = form .lines .into_iter() - .map(|l| Ok((l.content, Name::get_by_id(&conn, l.name_id)?))) + .map(|l| Ok((l.content, Name::get_by_id(&tx, l.name_id)?))) .collect::, CompositeError>>()?; let q = Quote::create( - &conn, + &tx, lines, form.timestamp, form.context, @@ -66,5 +68,7 @@ pub async fn create( form.public, )?; + LogEntry::new(&tx, u, LogAction::CreateQuote { id: q.id })?; + tx.commit().map_err(DatabaseError::from)?; Ok((StatusCode::CREATED, Json(q)).into_response()) } diff --git a/src/api/sessions.rs b/src/api/sessions.rs index 7c983f0..1c2a4db 100644 --- a/src/api/sessions.rs +++ b/src/api/sessions.rs @@ -8,7 +8,8 @@ use uuid::Uuid; use crate::{ api::CompositeError, - database, + database::{self, DatabaseError}, + logs::{LogAction, LogEntry}, users::{ User, auth::{UserAuthRequired, UserAuthenticate}, @@ -17,7 +18,7 @@ use crate::{ }, }; -const CANT_REVOKE: &str = "You don't have permission to change this user's password."; +const CANT_REVOKE: &str = "You don't have permission to revoke this user's sessions."; pub async fn get_by_id( Path(id): Path, @@ -41,18 +42,21 @@ pub async fn revoke_by_id( headers: HeaderMap, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let conn = database::conn()?; - let mut s = Session::get_by_id(&conn, id)?; + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + let mut s = Session::get_by_id(&tx, id)?; match s.user_id == u.id - || u.has_permission(&conn, Permission::RevokeOthersSessions) + || u.has_permission(&tx, Permission::RevokeOthersSessions) .is_ok_and(|v| v) { true => { - s.revoke(&conn, Some(&u))?; + s.revoke(&tx, Some(&u))?; + LogEntry::new(&tx, u, LogAction::ManuallyRevokeSession { id })?; + tx.commit().map_err(DatabaseError::from)?; Ok(Json(s).into_response()) } - false => match u.has_permission(&conn, Permission::ListOthersSessions)? { + false => match u.has_permission(&tx, Permission::ListOthersSessions)? { true => Ok((StatusCode::FORBIDDEN, CANT_REVOKE).into_response()), false => Err(SessionError::NoSessionWithId(id))?, }, diff --git a/src/api/tags.rs b/src/api/tags.rs index 61345b5..21fd116 100644 --- a/src/api/tags.rs +++ b/src/api/tags.rs @@ -9,7 +9,8 @@ use uuid::Uuid; use crate::{ api::CompositeError, - database, + database::{self, DatabaseError}, + logs::{LogAction, LogEntry}, tags::{Tag, TagName}, users::{ User, @@ -56,11 +57,24 @@ pub async fn create( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let conn = database::conn()?; - if !u.has_permission(&conn, Permission::CreateTags)? { + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + + if !u.has_permission(&tx, Permission::CreateTags)? { return Ok((StatusCode::FORBIDDEN, CANT_MAKE_TAGS).into_response()); } - Ok(Json(Tag::create(&conn, form.name)?).into_response()) + + let t = Tag::create(&tx, form.name)?; + LogEntry::new( + &tx, + u, + LogAction::CreateTag { + id: t.id, + name: t.name.as_str().to_string(), + }, + )?; + tx.commit().map_err(DatabaseError::from)?; + Ok(Json(t).into_response()) } pub async fn rename( @@ -69,21 +83,42 @@ pub async fn rename( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let conn = database::conn()?; - if !u.has_permission(&conn, Permission::RenameTags)? { + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + + if !u.has_permission(&tx, Permission::RenameTags)? { return Ok((StatusCode::FORBIDDEN, CANT_RENAME_TAGS).into_response()); } - let mut tag = Tag::get_by_id(&conn, id)?; - tag.rename(&conn, form.name)?; + let mut tag = Tag::get_by_id(&tx, id)?; + let on = tag.name.as_str().to_string(); + tag.rename(&tx, form.name)?; + LogEntry::new( + &tx, + u, + LogAction::RenameTag { + id, + on, + nn: tag.name.as_str().to_string(), + }, + )?; + tx.commit().map_err(DatabaseError::from)?; + Ok(Json(tag).into_response()) } pub async fn delete(Path(id): Path, headers: HeaderMap) -> Result { let u = User::authenticate(&headers)?.required()?; - let conn = database::conn()?; - if !u.has_permission(&conn, Permission::DeleteTags)? { + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + + if !u.has_permission(&tx, Permission::DeleteTags)? { return Ok((StatusCode::FORBIDDEN, CANT_DEL_TAGS).into_response()); } - Tag::get_by_id(&conn, id)?.delete(&conn)?; + let t = Tag::get_by_id(&tx, id)?; + let name = t.name.as_str().to_string(); + t.delete(&tx)?; + LogEntry::new(&tx, u, LogAction::DeleteTag { id, name })?; + tx.commit().map_err(DatabaseError::from)?; + Ok((StatusCode::OK, TAG_DELETED).into_response()) } diff --git a/src/api/users.rs b/src/api/users.rs index eac1cd3..8cdb025 100644 --- a/src/api/users.rs +++ b/src/api/users.rs @@ -9,7 +9,8 @@ use uuid::Uuid; use crate::{ api::CompositeError, - database, + database::{self, DatabaseError}, + logs::{LogAction, LogEntry}, users::{ User, auth::{UserAuthRequired, UserAuthenticate}, @@ -61,11 +62,25 @@ pub async fn create( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let conn = database::conn()?; - if !u.has_permission(&conn, Permission::ManuallyCreateUsers)? { + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + + if !u.has_permission(&tx, Permission::ManuallyCreateUsers)? { return Ok((StatusCode::FORBIDDEN, CANT_MANUALLY_MAKE_USERS).into_response()); } - Ok(Json(User::create(&conn, form.handle)?).into_response()) + + let nu = User::create(&tx, form.handle)?; + LogEntry::new( + &tx, + u, + LogAction::CreateUser { + id: nu.id, + handle: nu.handle.as_str().to_string(), + }, + )?; + tx.commit().map_err(DatabaseError::from)?; + + Ok(Json(nu).into_response()) } pub async fn change_handle( Path(id): Path, @@ -73,17 +88,31 @@ pub async fn change_handle( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let conn = database::conn()?; + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; let mut target = if u.id == id { - u + u.clone() } else { - if !u.has_permission(&conn, Permission::ChangeOthersHandles)? { + if !u.has_permission(&tx, Permission::ChangeOthersHandles)? { return Ok((StatusCode::FORBIDDEN, CANT_CHANGE_OTHERS_HANDLE).into_response()); } - User::get_by_id(&conn, id)? + User::get_by_id(&tx, id)? }; - target.set_handle(&conn, form.handle)?; + + let old_handle = target.handle.as_str().to_string(); + target.set_handle(&tx, form.handle)?; + LogEntry::new( + &tx, + u, + LogAction::ChangeUserHandle { + id: target.id, + old: old_handle, + new: target.handle.as_str().to_string(), + }, + )?; + tx.commit().map_err(DatabaseError::from)?; + Ok(HANDLE_CHANGED_SUCCESS.into_response()) } @@ -97,15 +126,25 @@ pub async fn change_password( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let conn = database::conn()?; + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + let mut target = if u.id == id { - u + u.clone() } else { - if !u.has_permission(&conn, Permission::ChangeOthersPasswords)? { + if !u.has_permission(&tx, Permission::ChangeOthersPasswords)? { return Ok((StatusCode::FORBIDDEN, CANT_CHANGE_OTHERS_PASSW).into_response()); } - User::get_by_id(&conn, id)? + User::get_by_id(&tx, id)? }; - target.set_password(&conn, Some(&form.password))?; + + target.set_password(&tx, Some(&form.password))?; + LogEntry::new( + &tx, + u, + LogAction::ManuallyChangeUsersPassword { id: target.id }, + )?; + tx.commit().map_err(DatabaseError::from)?; + Ok(PASSW_CHANGED_SUCCESS.into_response()) } diff --git a/src/logs.rs b/src/logs.rs index 69f04cf..d15e66d 100644 --- a/src/logs.rs +++ b/src/logs.rs @@ -1,3 +1,5 @@ +use std::fmt::format; + use rusqlite::Connection; use serde::{Deserialize, Serialize}; use strum::IntoStaticStr; @@ -55,10 +57,53 @@ impl LogEntry { pub enum LogAction { Initialize, RegenInfradmin, - CreateUser { id: Uuid, handle: String }, - CreateTag { id: Uuid, name: String }, - CreatePerson { id: Uuid, pname: String }, - ChangeUserHandle { id: Uuid, old: String, new: String }, + 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 { @@ -67,7 +112,13 @@ impl LogAction { Self::CreateUser { id, .. } | Self::CreateTag { id, .. } | Self::CreatePerson { id, .. } - | Self::ChangeUserHandle { id, .. } => Some(*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 { @@ -77,15 +128,36 @@ impl LogAction { 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}") + } } } } diff --git a/src/web/pages/usersettings.rs b/src/web/pages/usersettings.rs index 37dd7e7..3eb96d4 100644 --- a/src/web/pages/usersettings.rs +++ b/src/web/pages/usersettings.rs @@ -96,6 +96,7 @@ pub async fn change_handle( new: u.handle.as_str().to_string(), }, )?; + tx.commit().map_err(DatabaseError::from)?; Ok(Redirect::to("/user-settings").into_response()) } @@ -110,7 +111,7 @@ pub async fn change_password( let mut u = User::authenticate(&headers)?.required()?; let mut conn = database::conn()?; let tx = conn.transaction().map_err(DatabaseError::from)?; - u.set_password(&tx, Some(&form.password))?; + tx.commit().map_err(DatabaseError::from)?; Ok(Redirect::to("/user-settings").into_response()) }