diff --git a/src/api/auth.rs b/src/api/auth.rs index fed35b7..edd55f2 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -5,13 +5,16 @@ use axum::{ }; use serde::Deserialize; -use crate::users::{ - User, - auth::{ - AuthError, COOKIE_NAME, SessionAuthRequired, SessionAuthenticate, UserAuthRequired, - implementation::authenticate_via_credentials, +use crate::{ + database, + users::{ + User, + auth::{ + AuthError, COOKIE_NAME, SessionAuthRequired, SessionAuthenticate, UserAuthRequired, + implementation::authenticate_via_credentials, + }, + sessions::Session, }, - sessions::Session, }; #[derive(Deserialize)] @@ -22,7 +25,8 @@ pub struct LoginForm { fn login_common(creds: LoginForm) -> Result<(String, String), AuthError> { let u = authenticate_via_credentials(&creds.handle, &creds.password)?.required()?; - let (_, token) = Session::new_for_user(&u)?; + let conn = database::conn()?; + let (_, token) = Session::new_for_user(&conn, &u)?; let secure = match cfg!(debug_assertions) { false => "; Secure", true => "", @@ -49,13 +53,15 @@ pub async fn login_form(Form(creds): Form) -> Result Result { let mut s = Session::authenticate(&headers)?.required()?; - s.revoke(Some(&User::get_by_id(s.user_id)?))?; + let conn = database::conn()?; + s.revoke(&conn, Some(&User::get_by_id(&conn, s.user_id)?))?; let cookie = format!("{COOKIE_NAME}=revoking; Path=/; HttpOnly; Max-Age=0"); Ok(([(header::SET_COOKIE, cookie)], "Logged out!").into_response()) } pub async fn logout_form(headers: HeaderMap) -> Result { let mut s = Session::authenticate(&headers)?.required()?; - s.revoke(Some(&User::get_by_id(s.user_id)?))?; + let conn = database::conn()?; + s.revoke(&conn, Some(&User::get_by_id(&conn, s.user_id)?))?; let cookie = format!("{COOKIE_NAME}=revoking; Path=/; HttpOnly; Max-Age=0"); Ok(([(header::SET_COOKIE, cookie)], Redirect::to("/")).into_response()) } diff --git a/src/api/mod.rs b/src/api/mod.rs index 99af302..d92bc0c 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -85,5 +85,5 @@ composite_from!( PersonError, QuoteError, DatabaseError, - RedirectViaError + RedirectViaError, ); diff --git a/src/api/persons.rs b/src/api/persons.rs index 55894ca..78e8378 100644 --- a/src/api/persons.rs +++ b/src/api/persons.rs @@ -9,6 +9,7 @@ use uuid::Uuid; use crate::{ api::CompositeError, + database, persons::{Name, Person}, users::{ User, @@ -21,21 +22,24 @@ pub const CANT_SET_PRIMARYNAME: &str = "You don't have permission to swap primar pub async fn get_all(headers: HeaderMap) -> Result { User::authenticate(&headers)?.required()?; - Ok(Json(Person::get_all()?).into_response()) + let conn = database::conn()?; + Ok(Json(Person::get_all(&conn)?).into_response()) } pub async fn get_by_id( Path(id): Path, headers: HeaderMap, ) -> Result { User::authenticate(&headers)?.required()?; - Ok(Json(Person::get_by_id(id)?).into_response()) + let conn = database::conn()?; + Ok(Json(Person::get_by_id(&conn, id)?).into_response()) } pub async fn pid_names( Path(id): Path, headers: HeaderMap, ) -> Result { User::authenticate(&headers)?.required()?; - Ok(Json(Person::get_by_id(id)?.get_all_names()?).into_response()) + let conn = database::conn()?; + Ok(Json(Person::get_by_id(&conn, id)?.get_all_names(&conn)?).into_response()) } #[derive(Deserialize)] @@ -48,7 +52,8 @@ pub async fn create( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let p = Person::create(form.name, u.id)?; + let conn = database::conn()?; + let p = Person::create(&conn, form.name, u.id)?; Ok((StatusCode::CREATED, Json(p)).into_response()) } pub async fn add_name( @@ -57,27 +62,30 @@ pub async fn add_name( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let p = Person::get_by_id(id)?; - let n = p.add_name(form.name, u.id)?; + let conn = database::conn()?; + let p = Person::get_by_id(&conn, id)?; + let n = p.add_name(&conn, form.name, u.id)?; Ok((StatusCode::CREATED, Json(n)).into_response()) } pub async fn n_by_id(Path(id): Path, headers: HeaderMap) -> Result { User::authenticate(&headers)?.required()?; - Ok(Json(Name::get_by_id(id)?).into_response()) + let conn = database::conn()?; + Ok(Json(Name::get_by_id(&conn, id)?).into_response()) } pub async fn n_setprimary( Path(id): Path, headers: HeaderMap, ) -> Result { let u = User::authenticate(&headers)?.required()?; - if !u.has_permission(Permission::ChangePersonPrimaryName)? { + let conn = database::conn()?; + if !u.has_permission(&conn, Permission::ChangePersonPrimaryName)? { return Ok((StatusCode::FORBIDDEN, CANT_SET_PRIMARYNAME).into_response()); } - let mut n = Name::get_by_id(id)?; - n.set_primary()?; + let mut n = Name::get_by_id(&conn, id)?; + n.set_primary(&conn)?; n.is_primary = true; Ok(Json(n).into_response()) } diff --git a/src/api/quotes.rs b/src/api/quotes.rs index 7fc6c7c..3842484 100644 --- a/src/api/quotes.rs +++ b/src/api/quotes.rs @@ -10,6 +10,7 @@ use uuid::Uuid; use crate::{ api::CompositeError, + database, persons::Name, quotes::Quote, users::{ @@ -23,7 +24,8 @@ pub async fn get_by_id( headers: HeaderMap, ) -> Result { User::authenticate(&headers)?.required()?; - Ok(Json(Quote::get_by_id(id)?).into_response()) + let conn = database::conn()?; + Ok(Json(Quote::get_by_id(&conn, id)?).into_response()) } #[derive(Deserialize)] @@ -46,14 +48,16 @@ pub async fn create( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; + let conn = database::conn()?; let lines = form .lines .into_iter() - .map(|l| Ok((l.content, Name::get_by_id(l.name_id)?))) + .map(|l| Ok((l.content, Name::get_by_id(&conn, l.name_id)?))) .collect::, CompositeError>>()?; let q = Quote::create( + &conn, lines, form.timestamp, form.context, diff --git a/src/api/sessions.rs b/src/api/sessions.rs index b7bf472..7c983f0 100644 --- a/src/api/sessions.rs +++ b/src/api/sessions.rs @@ -8,6 +8,7 @@ use uuid::Uuid; use crate::{ api::CompositeError, + database, users::{ User, auth::{UserAuthRequired, UserAuthenticate}, @@ -23,10 +24,11 @@ pub async fn get_by_id( headers: HeaderMap, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let s = Session::get_by_id(id)?; + let conn = database::conn()?; + let s = Session::get_by_id(&conn, id)?; match s.user_id == u.id - || u.has_permission(Permission::ListOthersSessions) + || u.has_permission(&conn, Permission::ListOthersSessions) .is_ok_and(|v| v) { true => Ok(Json(s).into_response()), @@ -39,17 +41,18 @@ pub async fn revoke_by_id( headers: HeaderMap, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let mut s = Session::get_by_id(id)?; + let conn = database::conn()?; + let mut s = Session::get_by_id(&conn, id)?; match s.user_id == u.id - || u.has_permission(Permission::RevokeOthersSessions) + || u.has_permission(&conn, Permission::RevokeOthersSessions) .is_ok_and(|v| v) { true => { - s.revoke(Some(&u))?; + s.revoke(&conn, Some(&u))?; Ok(Json(s).into_response()) } - false => match u.has_permission(Permission::ListOthersSessions)? { + false => match u.has_permission(&conn, 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 6f72285..61345b5 100644 --- a/src/api/tags.rs +++ b/src/api/tags.rs @@ -9,6 +9,7 @@ use uuid::Uuid; use crate::{ api::CompositeError, + database, tags::{Tag, TagName}, users::{ User, @@ -24,7 +25,8 @@ const TAG_DELETED: &str = "Tag deleted successfully."; pub async fn get_all(headers: HeaderMap) -> Result { User::authenticate(&headers)?.required()?; - Ok(Json(Tag::get_all()?).into_response()) + let conn = database::conn()?; + Ok(Json(Tag::get_all(&conn)?).into_response()) } pub async fn get_by_id( @@ -32,7 +34,8 @@ pub async fn get_by_id( headers: HeaderMap, ) -> Result { User::authenticate(&headers)?.required()?; - Ok(Json(Tag::get_by_id(id)?).into_response()) + let conn = database::conn()?; + Ok(Json(Tag::get_by_id(&conn, id)?).into_response()) } pub async fn get_by_name( @@ -40,7 +43,8 @@ pub async fn get_by_name( headers: HeaderMap, ) -> Result { User::authenticate(&headers)?.required()?; - Ok(Json(Tag::get_by_name(name)?).into_response()) + let conn = database::conn()?; + Ok(Json(Tag::get_by_name(&conn, name)?).into_response()) } #[derive(Deserialize)] @@ -52,10 +56,11 @@ pub async fn create( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; - if !u.has_permission(Permission::CreateTags)? { + let conn = database::conn()?; + if !u.has_permission(&conn, Permission::CreateTags)? { return Ok((StatusCode::FORBIDDEN, CANT_MAKE_TAGS).into_response()); } - Ok(Json(Tag::create(form.name)?).into_response()) + Ok(Json(Tag::create(&conn, form.name)?).into_response()) } pub async fn rename( @@ -64,19 +69,21 @@ pub async fn rename( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; - if !u.has_permission(Permission::RenameTags)? { + let conn = database::conn()?; + if !u.has_permission(&conn, Permission::RenameTags)? { return Ok((StatusCode::FORBIDDEN, CANT_RENAME_TAGS).into_response()); } - let mut tag = Tag::get_by_id(id)?; - tag.rename(form.name)?; + let mut tag = Tag::get_by_id(&conn, id)?; + tag.rename(&conn, form.name)?; Ok(Json(tag).into_response()) } pub async fn delete(Path(id): Path, headers: HeaderMap) -> Result { let u = User::authenticate(&headers)?.required()?; - if !u.has_permission(Permission::DeleteTags)? { + let conn = database::conn()?; + if !u.has_permission(&conn, Permission::DeleteTags)? { return Ok((StatusCode::FORBIDDEN, CANT_DEL_TAGS).into_response()); } - Tag::get_by_id(id)?.delete()?; + Tag::get_by_id(&conn, id)?.delete(&conn)?; Ok((StatusCode::OK, TAG_DELETED).into_response()) } diff --git a/src/api/users.rs b/src/api/users.rs index 0ebddb7..eac1cd3 100644 --- a/src/api/users.rs +++ b/src/api/users.rs @@ -9,6 +9,7 @@ use uuid::Uuid; use crate::{ api::CompositeError, + database, users::{ User, auth::{UserAuthRequired, UserAuthenticate}, @@ -32,7 +33,8 @@ pub async fn get_by_id( headers: HeaderMap, ) -> Result { User::authenticate(&headers)?.required()?; - Ok(Json(User::get_by_id(id)?).into_response()) + let conn = database::conn()?; + Ok(Json(User::get_by_id(&conn, id)?).into_response()) } pub async fn get_by_handle( @@ -40,12 +42,14 @@ pub async fn get_by_handle( headers: HeaderMap, ) -> Result { User::authenticate(&headers)?.required()?; - Ok(Json(User::get_by_handle(handle)?).into_response()) + let conn = database::conn()?; + Ok(Json(User::get_by_handle(&conn, handle)?).into_response()) } pub async fn get_all(headers: HeaderMap) -> Result { User::authenticate(&headers)?.required()?; - Ok(Json(User::get_all()?).into_response()) + let conn = database::conn()?; + Ok(Json(User::get_all(&conn)?).into_response()) } #[derive(Deserialize)] @@ -57,10 +61,11 @@ pub async fn create( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; - if !u.has_permission(Permission::ManuallyCreateUsers)? { + let conn = database::conn()?; + if !u.has_permission(&conn, Permission::ManuallyCreateUsers)? { return Ok((StatusCode::FORBIDDEN, CANT_MANUALLY_MAKE_USERS).into_response()); } - Ok(Json(User::create(form.handle)?).into_response()) + Ok(Json(User::create(&conn, form.handle)?).into_response()) } pub async fn change_handle( Path(id): Path, @@ -68,15 +73,17 @@ pub async fn change_handle( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; + let conn = database::conn()?; + let mut target = if u.id == id { u } else { - if !u.has_permission(Permission::ChangeOthersHandles)? { + if !u.has_permission(&conn, Permission::ChangeOthersHandles)? { return Ok((StatusCode::FORBIDDEN, CANT_CHANGE_OTHERS_HANDLE).into_response()); } - User::get_by_id(id)? + User::get_by_id(&conn, id)? }; - target.set_handle(form.handle)?; + target.set_handle(&conn, form.handle)?; Ok(HANDLE_CHANGED_SUCCESS.into_response()) } @@ -90,14 +97,15 @@ pub async fn change_password( Json(form): Json, ) -> Result { let u = User::authenticate(&headers)?.required()?; + let conn = database::conn()?; let mut target = if u.id == id { u } else { - if !u.has_permission(Permission::ChangeOthersPasswords)? { + if !u.has_permission(&conn, Permission::ChangeOthersPasswords)? { return Ok((StatusCode::FORBIDDEN, CANT_CHANGE_OTHERS_PASSW).into_response()); } - User::get_by_id(id)? + User::get_by_id(&conn, id)? }; - target.set_password(Some(&form.password))?; + target.set_password(&conn, Some(&form.password))?; Ok(PASSW_CHANGED_SUCCESS.into_response()) } diff --git a/src/database/mod.rs b/src/database/mod.rs index 295541c..a09175e 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -32,7 +32,7 @@ impl IntoResponse for DatabaseError { } } -pub fn conn() -> Result { +pub fn conn() -> Result { let conn = Connection::open(&*DB_URL)?; for pragma in CONNECTION_PRAGMAS { conn.query_row(pragma, (), |_| Ok(())).optional()?; diff --git a/src/logs.rs b/src/logs.rs index 1546992..69f04cf 100644 --- a/src/logs.rs +++ b/src/logs.rs @@ -1,11 +1,9 @@ +use rusqlite::Connection; use serde::{Deserialize, Serialize}; use strum::IntoStaticStr; use uuid::Uuid; -use crate::{ - database::{self, DatabaseError}, - users::User, -}; +use crate::{database::DatabaseError, users::User}; #[derive(Debug)] pub struct LogEntry { @@ -15,13 +13,12 @@ pub struct LogEntry { } impl LogEntry { - pub fn new(actor: User, data: LogAction) -> Result { + pub fn new(conn: &Connection, actor: User, data: LogAction) -> Result { let log = LogEntry { id: Uuid::now_v7(), actor, data, }; - let conn = database::conn()?; let actiontype: &'static str = (&log.data).into(); let payload = serde_json::to_string(&log.data).unwrap(); conn.prepare( @@ -36,14 +33,14 @@ impl LogEntry { ))?; Ok(log) } - pub fn get_all() -> Result, DatabaseError> { - Ok(database::conn()? + pub fn get_all(conn: &Connection) -> Result, DatabaseError> { + Ok(conn .prepare("SELECT id, actor, target, actiontype, payload FROM logs ORDER BY id DESC")? .query_map((), |r| { let payload: String = r.get(4)?; Ok(LogEntry { id: r.get(0)?, - actor: User::get_by_id(r.get(1)?).unwrap(), + actor: User::get_by_id(conn, r.get(1)?).unwrap(), data: serde_json::from_str(&payload).unwrap(), }) })? diff --git a/src/persons/mod.rs b/src/persons/mod.rs index 4e7ed25..906172c 100644 --- a/src/persons/mod.rs +++ b/src/persons/mod.rs @@ -2,11 +2,11 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use rusqlite::OptionalExtension; +use rusqlite::{Connection, OptionalExtension}; use serde::Serialize; use uuid::Uuid; -use crate::database::{self, DatabaseError}; +use crate::database::DatabaseError; #[derive(Serialize)] pub struct Person { @@ -39,13 +39,11 @@ pub enum PersonError { } impl Person { - pub fn total_count() -> Result { - let conn = database::conn()?; - let count: i64 = conn.query_row("SELECT COUNT(*) FROM persons", (), |r| r.get(0))?; - Ok(count) + pub fn total_count(conn: &Connection) -> Result { + Ok(conn.query_row("SELECT COUNT(*) FROM persons", (), |r| r.get(0))?) } - pub fn get_all() -> Result, PersonError> { - Ok(database::conn()? + pub fn get_all(conn: &Connection) -> Result, PersonError> { + Ok(conn .prepare("SELECT p.id, p.created_by, n.name FROM persons p JOIN names n ON p.id = n.person_id AND n.is_primary = 1")? .query_map((), |r| { Ok(Person { @@ -57,8 +55,8 @@ impl Person { .collect::, _>>()?) } - pub fn get_by_id(id: Uuid) -> Result { - let res = database::conn()? + pub fn get_by_id(conn: &Connection, id: Uuid) -> Result { + let res = conn .prepare("SELECT p.created_by, n.name FROM persons p JOIN names n ON p.id = n.person_id AND n.is_primary = 1 WHERE p.id = ?1")? .query_one((&id,), |r| { Ok(Person { @@ -74,8 +72,8 @@ impl Person { } } - pub fn get_in_quote_count(&self) -> Result { - Ok(database::conn()? + pub fn get_in_quote_count(&self, conn: &Connection) -> Result { + Ok(conn .prepare( r#" SELECT COUNT(DISTINCT l.quote_id) AS quote_count @@ -86,8 +84,8 @@ impl Person { .query_one((self.id,), |r| Ok(r.get(0)?))?) } - pub fn get_all_names(&self) -> Result, PersonError> { - Ok(database::conn()? + pub fn get_all_names(&self, conn: &Connection) -> Result, PersonError> { + Ok(conn .prepare("SELECT id, is_primary, person_id, created_by, name FROM names WHERE person_id = ?1")? .query_map((&self.id,), |r| { Ok(Name { @@ -101,10 +99,14 @@ impl Person { .collect::, _>>()?) } - pub fn add_name(&self, name: String, created_by: Uuid) -> Result { + pub fn add_name( + &self, + conn: &Connection, + name: String, + created_by: Uuid, + ) -> Result { let id = Uuid::now_v7(); - database::conn()? - .prepare("INSERT INTO names VALUES (?1, ?2, ?3, ?4, ?5)")? + conn.prepare("INSERT INTO names VALUES (?1, ?2, ?3, ?4, ?5)")? .execute((id, 0, self.id, created_by, &name))?; Ok(Name { id, @@ -115,18 +117,18 @@ impl Person { }) } - pub fn create(primary_name: String, created_by: Uuid) -> Result { + pub fn create( + conn: &Connection, + primary_name: String, + created_by: Uuid, + ) -> Result { let person_id = Uuid::now_v7(); let name_id = Uuid::now_v7(); - let conn = database::conn()?; - conn.execute("BEGIN TRANSACTION", ())?; - conn.prepare("INSERT INTO persons(id, created_by) VALUES (?1, ?2)")? .execute((person_id, created_by))?; conn.prepare("INSERT INTO names VALUES (?1, ?2, ?3, ?4, ?5)")? .execute((name_id, 1, person_id, created_by, &primary_name))?; - conn.execute("COMMIT", ())?; Ok(Person { id: person_id, @@ -137,8 +139,8 @@ impl Person { } impl Name { - pub fn get_by_id(id: Uuid) -> Result { - let res = database::conn()? + pub fn get_by_id(conn: &Connection, id: Uuid) -> Result { + let res = conn .prepare("SELECT id, is_primary, person_id, created_by, name FROM names WHERE id = ?1")? .query_one((&id,), |r| { Ok(Name { @@ -155,20 +157,15 @@ impl Name { None => Err(PersonError::NoNameWithId(id)), } } - pub fn set_primary(&mut self) -> Result<(), PersonError> { + pub fn set_primary(&mut self, conn: &Connection) -> Result<(), PersonError> { if self.is_primary { return Err(PersonError::AlreadyPrimary); } - - let conn = database::conn()?; - conn.execute("BEGIN TRANSACTION", ())?; - conn.prepare("UPDATE names SET is_primary = 0 WHERE person_id = ?1 AND is_primary = 1")? .execute((&self.person_id,))?; conn.prepare("UPDATE names SET is_primary = 1 WHERE id = ?1")? .execute((&self.id,))?; - conn.execute("COMMIT", ())?; self.is_primary = true; Ok(()) } diff --git a/src/quotes/mod.rs b/src/quotes/mod.rs index f2f4660..604f60c 100644 --- a/src/quotes/mod.rs +++ b/src/quotes/mod.rs @@ -1,13 +1,10 @@ use axum::{http::StatusCode, response::IntoResponse}; use chrono::{DateTime, FixedOffset}; -use rusqlite::OptionalExtension; +use rusqlite::{Connection, OptionalExtension}; use serde::Serialize; use uuid::Uuid; -use crate::{ - database::{self, DatabaseError}, - persons::Name, -}; +use crate::{database::DatabaseError, persons::Name}; #[derive(Serialize)] pub struct Quote { @@ -38,14 +35,10 @@ pub enum QuoteError { } impl Quote { - pub fn total_count() -> Result { - let conn = database::conn()?; - let count: i64 = conn.query_row("SELECT COUNT(*) FROM quotes", (), |r| r.get(0))?; - Ok(count) + pub fn total_count(conn: &Connection) -> Result { + Ok(conn.query_row("SELECT COUNT(*) FROM quotes", (), |r| r.get(0))?) } - pub fn get_by_id(id: Uuid) -> Result { - let conn = database::conn()?; - + pub fn get_by_id(conn: &Connection, id: Uuid) -> Result { let quotemain = conn .prepare( "SELECT timestamp, location, context, created_by, public FROM quotes WHERE id = ?1", @@ -100,6 +93,7 @@ impl Quote { }) } pub fn create( + conn: &Connection, lines: Vec<(String, Name)>, timestamp: DateTime, context: Option, @@ -111,15 +105,12 @@ impl Quote { return Err(QuoteError::EmptyQuote); } - let conn = database::conn()?; let quote_id = Uuid::now_v7(); let lines: Vec<(Uuid, String, Name)> = lines .into_iter() .map(|(c, a)| (Uuid::now_v7(), c, a)) .collect(); - conn.execute("BEGIN TRANSACTION", ())?; - let mut quote_stmt = conn.prepare( r#" INSERT INTO quotes (id, timestamp, location, context, created_by, public) @@ -138,7 +129,6 @@ impl Quote { line_stmt.execute((id, quote_id, content, attr.id, ordering as i64))?; } - conn.execute("COMMIT", ())?; Ok(Quote { id: quote_id, lines: lines diff --git a/src/tags.rs b/src/tags.rs index 5a38376..9ed697f 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -5,14 +5,14 @@ use axum::{ response::{IntoResponse, Response}, }; use rusqlite::{ - OptionalExtension, Result as RusqliteResult, ToSql, + Connection, OptionalExtension, Result as RusqliteResult, ToSql, ffi::SQLITE_CONSTRAINT_UNIQUE, types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef}, }; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::database::{self, DatabaseError}; +use crate::database::DatabaseError; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Tag { @@ -21,13 +21,11 @@ pub struct Tag { } impl Tag { - pub fn total_count() -> Result { - let conn = database::conn()?; - let count: i64 = conn.query_row("SELECT COUNT(*) FROM tags", (), |r| r.get(0))?; - Ok(count) + pub fn total_count(conn: &Connection) -> Result { + Ok(conn.query_row("SELECT COUNT(*) FROM tags", (), |r| r.get(0))?) } - pub fn get_all() -> Result, TagError> { - Ok(database::conn()? + pub fn get_all(conn: &Connection) -> Result, TagError> { + Ok(conn .prepare("SELECT id, tagname FROM tags")? .query_map((), |r| { Ok(Tag { @@ -37,8 +35,8 @@ impl Tag { })? .collect::, _>>()?) } - pub fn get_by_id(id: Uuid) -> Result { - let res = database::conn()? + pub fn get_by_id(conn: &Connection, id: Uuid) -> Result { + let res = conn .prepare("SELECT tagname FROM tags WHERE id = ?1")? .query_one((&id,), |r| { Ok(Tag { @@ -52,13 +50,13 @@ impl Tag { None => Err(TagError::NoTagWithId(id)), } } - pub fn get_tagged_quotes_count(&self) -> Result { - Ok(database::conn()? + pub fn get_tagged_quotes_count(&self, conn: &Connection) -> Result { + Ok(conn .prepare("SELECT COUNT(*) FROM quote_tags WHERE tag_id = ?1")? .query_one((self.id,), |r| Ok(r.get(0)?))?) } - pub fn get_by_name(name: TagName) -> Result { - let res = database::conn()? + pub fn get_by_name(conn: &Connection, name: TagName) -> Result { + let res = conn .prepare("SELECT id, tagname FROM tags WHERE tagname = ?1")? .query_one((&name,), |r| { Ok(Tag { @@ -72,23 +70,20 @@ impl Tag { None => Err(TagError::NoTagWithName(name)), } } - pub fn create(name: TagName) -> Result { + pub fn create(conn: &Connection, name: TagName) -> Result { let id = Uuid::now_v7(); - database::conn()? - .prepare("INSERT INTO tags(id, tagname) VALUES (?1, ?2)")? + conn.prepare("INSERT INTO tags(id, tagname) VALUES (?1, ?2)")? .execute((id, &name))?; Ok(Tag { id, name }) } - pub fn rename(&mut self, name: TagName) -> Result<(), TagError> { - database::conn()? - .prepare("UPDATE tags SET tagname = ?1 WHERE id = ?2")? + pub fn rename(&mut self, conn: &Connection, name: TagName) -> Result<(), TagError> { + conn.prepare("UPDATE tags SET tagname = ?1 WHERE id = ?2")? .execute((&name, self.id))?; self.name = name; Ok(()) } - pub fn delete(self) -> Result<(), TagError> { - database::conn()? - .prepare("DELETE FROM tags WHERE id = ?1")? + pub fn delete(self, conn: &Connection) -> Result<(), TagError> { + conn.prepare("DELETE FROM tags WHERE id = ?1")? .execute((self.id,))?; Ok(()) } diff --git a/src/users/auth/implementation.rs b/src/users/auth/implementation.rs index eeb0dbf..5366ce7 100644 --- a/src/users/auth/implementation.rs +++ b/src/users/auth/implementation.rs @@ -202,7 +202,7 @@ pub fn authenticate_via_credentials( match user { Some((id, Some(passhash))) => match User::match_hash_password(password, &passhash)? { - true => Ok(Some(User::get_by_id(id)?)), + true => Ok(Some(User::get_by_id(&conn, id)?)), false => Err(AuthError::InvalidCredentials), }, _ => { @@ -213,18 +213,20 @@ pub fn authenticate_via_credentials( } fn authenticate_bearer(token: &str) -> Result, AuthError> { - let mut s = Session::get_by_token(token)?; + let conn = database::conn().map_err(|e| DatabaseError::from(e))?; + let mut s = Session::get_by_token(&conn, token)?; if s.is_expired_or_revoked() { return Err(AuthError::InvalidCredentials); } - s.prolong()?; - Ok(Some(User::get_by_id(s.user_id)?)) + s.prolong(&conn)?; + Ok(Some(User::get_by_id(&conn, s.user_id)?)) } fn authenticate_bearer_with_session(token: &str) -> Result, AuthError> { - let mut s = Session::get_by_token(token)?; + let conn = database::conn().map_err(|e| DatabaseError::from(e))?; + let mut s = Session::get_by_token(&conn, token)?; if s.is_expired_or_revoked() { return Err(AuthError::InvalidCredentials); } - s.prolong()?; + s.prolong(&conn)?; Ok(Some(s)) } diff --git a/src/users/mod.rs b/src/users/mod.rs index 0fabe98..633caec 100644 --- a/src/users/mod.rs +++ b/src/users/mod.rs @@ -3,13 +3,13 @@ use axum::{ response::{IntoResponse, Response}, }; use chrono::{DateTime, NaiveDate}; -use rusqlite::{OptionalExtension, ffi::SQLITE_CONSTRAINT_UNIQUE}; +use rusqlite::{Connection, OptionalExtension, ffi::SQLITE_CONSTRAINT_UNIQUE}; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{ ISE_MSG, - database::{self, DatabaseError}, + database::DatabaseError, users::{ auth::UserPasswordHashing, handle::{UserHandle, UserHandleError}, @@ -45,13 +45,11 @@ pub enum UserError { } impl User { - pub fn total_count() -> Result { - let conn = database::conn()?; - let count: i64 = conn.query_row("SELECT COUNT(*) FROM users", (), |r| r.get(0))?; - Ok(count) + pub fn total_count(conn: &Connection) -> Result { + Ok(conn.query_row("SELECT COUNT(*) FROM users", (), |r| r.get(0))?) } - pub fn get_by_id(id: Uuid) -> Result { - let res = database::conn()? + pub fn get_by_id(conn: &Connection, id: Uuid) -> Result { + let res = conn .prepare("SELECT handle FROM users WHERE id = ?1")? .query_one((&id,), |r| { Ok(User { @@ -65,8 +63,8 @@ impl User { None => Err(UserError::NoUserWithId(id)), } } - pub fn get_by_handle(handle: UserHandle) -> Result { - let res = database::conn()? + pub fn get_by_handle(conn: &Connection, handle: UserHandle) -> Result { + let res = conn .prepare("SELECT id, handle FROM users WHERE handle = ?1")? .query_one((&handle,), |r| { Ok(User { @@ -80,8 +78,8 @@ impl User { None => Err(UserError::NoUserWithHandle(handle)), } } - pub fn get_all() -> Result, UserError> { - Ok(database::conn()? + pub fn get_all(conn: &Connection) -> Result, UserError> { + Ok(conn .prepare("SELECT id, handle FROM users")? .query_map((), |r| { Ok(User { @@ -92,16 +90,18 @@ impl User { .collect::, _>>()?) } - pub fn create(handle: UserHandle) -> Result { - let conn = database::conn()?; + pub fn create(conn: &Connection, handle: UserHandle) -> Result { let id = Uuid::now_v7(); conn.prepare("INSERT INTO users(id, handle) VALUES (?1, ?2)")? .execute((&id, &handle))?; Ok(User { id, handle }) } - pub fn set_handle(&mut self, new_handle: UserHandle) -> Result<(), UserError> { - let conn = database::conn()?; + pub fn set_handle( + &mut self, + conn: &Connection, + new_handle: UserHandle, + ) -> Result<(), UserError> { conn.prepare("UPDATE users SET handle = ?1 WHERE id = ?2")? .execute((&new_handle, self.id))?; self.handle = new_handle; @@ -118,8 +118,11 @@ impl User { // DANGEROUS: AUTH impl User { - pub fn set_password(&mut self, passw: Option<&str>) -> Result<(), UserError> { - let conn = database::conn()?; + pub fn set_password( + &mut self, + conn: &Connection, + passw: Option<&str>, + ) -> Result<(), UserError> { match passw { None => { conn.prepare("UPDATE users SET password = NULL WHERE id = ?1")? @@ -145,15 +148,14 @@ impl User { /// to do everything and probably should not be used as a regular account /// due to the ramifications of compromise. But it could be used for that, /// and have its name changed. - pub fn create_infradmin() -> Result { + pub fn create_infradmin(conn: &Connection) -> Result { let mut u = User { id: Uuid::max(), handle: UserHandle::new("Infradmin")?, }; - database::conn()? - .prepare("INSERT INTO users(id, handle) VALUES (?1, ?2)")? + conn.prepare("INSERT INTO users(id, handle) VALUES (?1, ?2)")? .execute((&u.id, &u.handle))?; - u.regenerate_infradmin_password()?; + u.regenerate_infradmin_password(conn)?; Ok(u) } @@ -176,9 +178,9 @@ impl User { /// to do everything and probably should not be used as a regular account /// due to the ramifications of compromise. But it could be used for that, /// and have its name changed. - pub fn regenerate_infradmin_password(&mut self) -> Result<(), UserError> { + pub fn regenerate_infradmin_password(&mut self, conn: &Connection) -> Result<(), UserError> { let passw = auth::generate_token(auth::TokenSize::Char16); - self.set_password(Some(&passw))?; + self.set_password(conn, Some(&passw))?; log::info!("[USERS] The infradmin account password has been (re)generated."); log::info!("[USERS] Handle: {}", self.handle.as_str()); log::info!("[USERS] Password: {}", passw); @@ -192,13 +194,12 @@ impl User { /// for actions performed by Mnemosyne internally. /// It shall not be available for log-in. /// It should not have its name changed, and should be protected from that. - pub fn create_systemuser() -> Result { + pub fn create_systemuser(conn: &Connection) -> Result { let u = User { id: Uuid::nil(), handle: UserHandle::new("Mnemosyne")?, }; - database::conn()? - .prepare("INSERT INTO users(id, handle) VALUES (?1, ?2)")? + conn.prepare("INSERT INTO users(id, handle) VALUES (?1, ?2)")? .execute((&u.id, &u.handle))?; Ok(u) diff --git a/src/users/permissions.rs b/src/users/permissions.rs index ee68170..991121a 100644 --- a/src/users/permissions.rs +++ b/src/users/permissions.rs @@ -1,3 +1,5 @@ +use rusqlite::Connection; + use crate::{database::DatabaseError, users::User}; /// Infradmin and systemuser have all permissions. @@ -21,6 +23,7 @@ pub enum Permission { impl User { pub fn has_permission( &self, + #[allow(unused)] conn: &Connection, #[allow(unused)] permission: Permission, ) -> Result { // Infradmin and systemuser have all permissions diff --git a/src/users/sessions.rs b/src/users/sessions.rs index f1d9930..bf5b01a 100644 --- a/src/users/sessions.rs +++ b/src/users/sessions.rs @@ -3,13 +3,13 @@ use axum::{ response::{IntoResponse, Response}, }; use chrono::{DateTime, Duration, Utc}; -use rusqlite::OptionalExtension; +use rusqlite::{Connection, OptionalExtension}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use uuid::Uuid; use crate::{ - database::{self, DatabaseError}, + database::DatabaseError, users::{ User, auth::{self, COOKIE_NAME}, @@ -70,8 +70,8 @@ impl IntoResponse for SessionError { } impl Session { - pub fn get_by_id(id: Uuid) -> Result { - let res = database::conn()? + pub fn get_by_id(conn: &Connection, id: Uuid) -> Result { + let res = conn .prepare("SELECT user_id, expiry, revoked, revoked_at, revoked_by FROM sessions WHERE id = ?1")? .query_one((&id,), |r| Ok(Session { id, @@ -90,9 +90,9 @@ impl Session { None => Err(SessionError::NoSessionWithId(id)), } } - pub fn get_by_token(token: &str) -> Result { + pub fn get_by_token(conn: &Connection, token: &str) -> Result { let hashed = Sha256::digest(token.as_bytes()).to_vec(); - let res = database::conn()? + let res = conn .prepare("SELECT id, user_id, expiry, revoked, revoked_at, revoked_by FROM sessions WHERE token = ?1")? .query_one((hashed,), |r| Ok(Session { id: r.get(0)?, @@ -111,14 +111,13 @@ impl Session { None => Err(SessionError::NoSessionWithToken(token.to_string())), } } - pub fn new_for_user(user: &User) -> Result<(Session, String), SessionError> { + pub fn new_for_user(conn: &Connection, user: &User) -> Result<(Session, String), SessionError> { let id = Uuid::now_v7(); let token = auth::generate_token(auth::TokenSize::Char64); let hashed = Sha256::digest(token.as_bytes()).to_vec(); let expiry = Utc::now() + Session::DEFAULT_PROLONGATION; - database::conn()? - .prepare("INSERT INTO sessions(id, token, user_id, expiry) VALUES (?1, ?2, ?3, ?4)")? + conn.prepare("INSERT INTO sessions(id, token, user_id, expiry) VALUES (?1, ?2, ?3, ?4)")? .execute((&id, &hashed, user.id, expiry))?; let s = Session { id, @@ -131,7 +130,7 @@ impl Session { pub const DEFAULT_PROLONGATION: Duration = Duration::days(14); const PROLONGATION_THRESHOLD: Duration = Duration::hours(2); - pub fn prolong(&mut self) -> Result<(), SessionError> { + pub fn prolong(&mut self, conn: &Connection) -> Result<(), SessionError> { if self.expiry - Session::DEFAULT_PROLONGATION + Session::PROLONGATION_THRESHOLD > Utc::now() { @@ -139,21 +138,19 @@ impl Session { } let expiry = Utc::now() + Session::DEFAULT_PROLONGATION; - database::conn()? - .prepare("UPDATE sessions SET expiry = ?1 WHERE id = ?2")? + conn.prepare("UPDATE sessions SET expiry = ?1 WHERE id = ?2")? .execute((&expiry, &self.id))?; self.expiry = expiry; Ok(()) } - pub fn revoke(&mut self, actor: Option<&User>) -> Result<(), SessionError> { + pub fn revoke(&mut self, conn: &Connection, actor: Option<&User>) -> Result<(), SessionError> { let now = Utc::now(); let id = actor.map(|u| u.id).unwrap_or(Uuid::nil()); - database::conn()? - .prepare( - "UPDATE sessions SET revoked = ?1, revoked_at = ?2, revoked_by = ?3 WHERE id = ?4", - )? - .execute((&true, &now, &id, &self.id))?; + conn.prepare( + "UPDATE sessions SET revoked = ?1, revoked_at = ?2, revoked_by = ?3 WHERE id = ?4", + )? + .execute((&true, &now, &id, &self.id))?; self.status = SessionStatus::Revoked { revoked_at: now, revoked_by: id, diff --git a/src/users/setup.rs b/src/users/setup.rs index 0271246..a213d33 100644 --- a/src/users/setup.rs +++ b/src/users/setup.rs @@ -8,27 +8,33 @@ use crate::{ }; pub fn initialise_reserved_users_if_needed() -> Result<(), UserError> { - let conn = database::conn()?; + let mut conn = database::conn()?; + let tx = conn.transaction()?; - if conn + if tx .prepare("SELECT handle FROM users WHERE id = ?1")? .query_one((&Uuid::nil(),), |_| Ok(())) .optional()? .is_none() { - let u = User::create_systemuser()?; - LogEntry::new(u, LogAction::Initialize)?; + let u = User::create_systemuser(&tx)?; + LogEntry::new(&tx, u, LogAction::Initialize)?; } - if conn + if tx .prepare("SELECT handle FROM users WHERE id = ?1")? .query_one((&Uuid::max(),), |_| Ok(())) .optional()? .is_none() { - User::create_infradmin()?; - LogEntry::new(User::get_by_id(Uuid::nil())?, LogAction::RegenInfradmin)?; + User::create_infradmin(&tx)?; + LogEntry::new( + &tx, + User::get_by_id(&tx, Uuid::nil())?, + LogAction::RegenInfradmin, + )?; } + tx.commit()?; Ok(()) } diff --git a/src/web/pages/dashboard.rs b/src/web/pages/dashboard.rs index 82d9010..3727ba7 100644 --- a/src/web/pages/dashboard.rs +++ b/src/web/pages/dashboard.rs @@ -4,6 +4,8 @@ use maud::{Markup, PreEscaped, html}; use uuid::Uuid; use crate::{ + api::CompositeError, + database::{self, DatabaseError}, persons::{Name, Person}, quotes::{Quote, QuoteLine}, tags::Tag, @@ -20,9 +22,12 @@ const LINKS: &[(&str, &str, &str)] = &[ ("Add Person", "/persons/add", icons::CONTACT), ]; -pub async fn page(req: Request) -> Markup { +pub async fn page(req: Request) -> Result { let u = User::authenticate(req.headers()).ok().flatten(); - base( + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + + Ok(base( "Dashboard | Mnemosyne", html!( (nav(u.as_ref(), req.uri().path())) @@ -55,25 +60,25 @@ pub async fn page(req: Request) -> Markup { } div class="mx-auto max-w-4xl mt-4 flex flex-row gap-2" { (chip(html!({ - @match Quote::total_count() { + @match Quote::total_count(&tx) { Ok(count) => {(count) " QUOTES TOTAL"}, Err(_) => span class="text-red-400" {"QUOTE COUNT ERR"}, } }))) (chip(html!({ - @match Person::total_count() { + @match Person::total_count(&tx) { Ok(count) => {(count) " PERSONS TOTAL"}, Err(_) => span class="text-red-400" {"PERSON COUNT ERR"}, } }))) (chip(html!({ - @match Tag::total_count() { + @match Tag::total_count(&tx) { Ok(count) => {(count) " TAGS TOTAL"}, Err(_) => span class="text-red-400" {"TAG COUNT ERR"} } }))) (chip(html!({ - @match User::total_count() { + @match User::total_count(&tx) { Ok(count) => {(count) " USERS TOTAL"}, Err(_) => span class="text-red-400" {"USER COUNT ERR"} } @@ -82,7 +87,7 @@ pub async fn page(req: Request) -> Markup { div class="text-4xl xs:text-6xl sm:text-8xl text-neutral-800/25 mt-16 text-center font-semibold font-lora select-none" {"Mnemosyne"} ), - ) + )) } fn sample_quote_1() -> Quote { diff --git a/src/web/pages/logs.rs b/src/web/pages/logs.rs index fa3a5e3..e31eaa3 100644 --- a/src/web/pages/logs.rs +++ b/src/web/pages/logs.rs @@ -6,6 +6,7 @@ use maud::{PreEscaped, html}; use crate::{ api::CompositeError, + database::{self, DatabaseError}, logs::LogEntry, users::{User, auth::UserAuthenticate, permissions::Permission}, web::{RedirectViaError, components::nav::nav, icons, pages::base}, @@ -14,14 +15,16 @@ use crate::{ pub async fn page(req: Request) -> Result { let u = User::authenticate(req.headers())? .ok_or(RedirectViaError(Redirect::to("/login?re=/logs")))?; - let logs = LogEntry::get_all()?; + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + let logs = LogEntry::get_all(&tx)?; Ok(base( "Persons | Mnemosyne", html!( (nav(Some(&u), req.uri().path())) - @if let Ok(true) = u.has_permission(Permission::BrowseServerLogs) { + @if let Ok(true) = u.has_permission(&tx, Permission::BrowseServerLogs) { div class="max-w-4xl mx-auto px-2" { div class="my-4" { p class="flex items-center gap-2" { diff --git a/src/web/pages/persons.rs b/src/web/pages/persons.rs index 05a00af..12fa0b4 100644 --- a/src/web/pages/persons.rs +++ b/src/web/pages/persons.rs @@ -9,6 +9,7 @@ use serde::Deserialize; use crate::{ api::CompositeError, + database::{self, DatabaseError}, logs::{LogAction, LogEntry}, persons::Person, users::{ @@ -20,6 +21,8 @@ use crate::{ pub async fn page(req: Request) -> Result { let u = User::authenticate(req.headers())?; + let mut conn = database::conn()?; + let tx = conn.transaction()?; Ok(base( "Persons | Mnemosyne", @@ -33,14 +36,14 @@ pub async fn page(req: Request) -> Result { span class="text-2xl font-semibold font-lora" {"Persons"} } p class="text-neutral-500 text-sm font-light" { - @if let Ok(c) = Person::total_count() { + @if let Ok(c) = Person::total_count(&tx) { (c) " persons in total." } @else { "Could not get total person count." } } } - @if let Ok(persons) = Person::get_all() { + @if let Ok(persons) = Person::get_all(&tx) { div class="max-w-4xl mx-auto px-2 mt-4 flex flex-wrap gap-2" { @for person in &persons { div class="rounded px-4 py-2 bg-neutral-200/10 border border-neutral-200/15 flex items-center" { @@ -49,7 +52,7 @@ pub async fn page(req: Request) -> Result { div class="w-px h-2/3 my-auto mx-2 bg-neutral-200/15" {} div class="text-xs flex items-center" { ( - if let Ok(i) = person.get_in_quote_count() { + if let Ok(i) = person.get_in_quote_count(&tx) { i.to_string() } else { "?".to_string() @@ -96,13 +99,18 @@ pub async fn create( Form(form): Form, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let p = Person::create(form.primary_name, u.id)?; + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + + let p = Person::create(&tx, form.primary_name, u.id)?; LogEntry::new( + &tx, u, LogAction::CreatePerson { id: p.id, pname: p.primary_name, }, )?; + tx.commit().map_err(DatabaseError::from)?; Ok(Redirect::to("/persons").into_response()) } diff --git a/src/web/pages/tags.rs b/src/web/pages/tags.rs index 48dc966..8222f79 100644 --- a/src/web/pages/tags.rs +++ b/src/web/pages/tags.rs @@ -9,6 +9,7 @@ use serde::Deserialize; use crate::{ api::CompositeError, + database::{self, DatabaseError}, logs::{LogAction, LogEntry}, tags::{Tag, TagName}, users::{ @@ -20,6 +21,7 @@ use crate::{ pub async fn page(req: Request) -> Result { let u = User::authenticate(req.headers())?; + let conn = database::conn()?; Ok(base( "Tags | Mnemosyne", @@ -33,14 +35,14 @@ pub async fn page(req: Request) -> Result { span class="text-2xl font-semibold font-lora" {"Tags"} } p class="text-neutral-500 text-sm font-light" { - @if let Ok(c) = Tag::total_count() { + @if let Ok(c) = Tag::total_count(&conn) { (c) " tags in total." } @else { "Could not get total tag count." } } } - @if let Ok(tags) = Tag::get_all() { + @if let Ok(tags) = Tag::get_all(&conn) { div class="max-w-4xl mx-auto mt-4 flex flex-wrap gap-2" { @for tag in &tags { div class="rounded-full px-3 py-1 bg-neutral-200/10 border border-neutral-200/15 flex" { @@ -49,7 +51,7 @@ pub async fn page(req: Request) -> Result { div class="w-px h-2/3 my-auto mx-2 bg-neutral-200/15" {} div class="text-xs flex items-center" { ( - if let Ok(i) = tag.get_tagged_quotes_count() { + if let Ok(i) = tag.get_tagged_quotes_count(&conn) { i.to_string() } else { "?".to_string() @@ -96,13 +98,18 @@ pub async fn create( Form(form): Form, ) -> Result { let u = User::authenticate(&headers)?.required()?; - let t = Tag::create(form.tagname)?; + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + + let t = Tag::create(&tx, form.tagname)?; LogEntry::new( + &tx, u, LogAction::CreateTag { id: t.id, name: t.name.to_string(), }, )?; + tx.commit().map_err(DatabaseError::from)?; Ok(Redirect::to("/tags").into_response()) } diff --git a/src/web/pages/users.rs b/src/web/pages/users.rs index 1c8194f..3afa995 100644 --- a/src/web/pages/users.rs +++ b/src/web/pages/users.rs @@ -5,6 +5,7 @@ use axum::{ use maud::{PreEscaped, html}; use crate::{ + database, users::{ User, auth::{AuthError, UserAuthenticate}, @@ -22,8 +23,9 @@ pub mod profile; pub async fn page(req: Request) -> Result { let u = User::authenticate(req.headers())?; + let conn = database::conn()?; let us = match u.is_some() { - true => User::get_all(), + true => User::get_all(&conn), false => Ok(vec![]), }; @@ -44,7 +46,7 @@ pub async fn page(req: Request) -> Result { } @else { "Could not fetch user count." } - @if let Ok(true) = u.has_permission(Permission::ManuallyCreateUsers) { + @if let Ok(true) = u.has_permission(&conn, Permission::ManuallyCreateUsers) { " " a href="/users/create" class="text-blue-500 hover:text-blue-400 hover:underline" { "Create a new user" diff --git a/src/web/pages/users/create.rs b/src/web/pages/users/create.rs index 2494610..1d80301 100644 --- a/src/web/pages/users/create.rs +++ b/src/web/pages/users/create.rs @@ -9,6 +9,7 @@ use serde::Deserialize; use crate::{ api::CompositeError, + database::{self, DatabaseError}, logs::{LogAction, LogEntry}, users::{ User, @@ -21,6 +22,7 @@ use crate::{ pub async fn page(req: Request) -> Result { let u = User::authenticate(req.headers())?; + let conn = database::conn()?; Ok(base( "Users | Mnemosyne", @@ -34,7 +36,7 @@ pub async fn page(req: Request) -> Result { span class="text-2xl font-semibold font-lora" {"Create a new user"} } } - @if let Ok(true) = u.has_permission(Permission::ManuallyCreateUsers) { + @if let Ok(true) = u.has_permission(&conn, Permission::ManuallyCreateUsers) { div class="mx-auto max-w-4xl px-2 mt-4" { form action="/users/create-form" method="post" class="flex flex-col" { label for="handle" class="font-light text-neutral-500" {"Handle"} @@ -71,17 +73,22 @@ pub async fn create_user( Form(form): Form, ) -> Result { let u = User::authenticate(&headers)?.required()?; - if !u.has_permission(Permission::ManuallyCreateUsers)? { + let mut conn = database::conn().map_err(DatabaseError::from)?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + + if !u.has_permission(&tx, Permission::ManuallyCreateUsers)? { return Ok((StatusCode::FORBIDDEN).into_response()); } - let mut nu = User::create(form.handle)?; - nu.set_password(Some(&form.password))?; + let mut nu = User::create(&tx, form.handle)?; + nu.set_password(&tx, Some(&form.password))?; LogEntry::new( + &tx, u, LogAction::CreateUser { id: nu.id, handle: nu.handle.as_str().to_string(), }, )?; + tx.commit().map_err(DatabaseError::from)?; Ok(Redirect::to("/users").into_response()) } diff --git a/src/web/pages/users/profile.rs b/src/web/pages/users/profile.rs index c980d4b..33534cb 100644 --- a/src/web/pages/users/profile.rs +++ b/src/web/pages/users/profile.rs @@ -7,12 +7,11 @@ use maud::{PreEscaped, html}; use uuid::Uuid; use crate::{ + api::CompositeError, + database::{self, DatabaseError}, persons::Name, quotes::{Quote, QuoteLine}, - users::{ - User, UserError, - auth::{AuthError, UserAuthenticate}, - }, + users::{User, UserError, auth::UserAuthenticate}, web::{ components::{nav::nav, quote::quote}, icons, @@ -20,12 +19,15 @@ use crate::{ }, }; -pub async fn page(Path(id): Path, req: Request) -> Result { +pub async fn page(Path(id): Path, req: Request) -> Result { let u = match User::authenticate(req.headers())? { Some(u) => u, None => return Ok(Redirect::to("/users").into_response()), }; - let user = match User::get_by_id(id) { + let mut conn = database::conn().map_err(DatabaseError::from)?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + + let user = match User::get_by_id(&tx, id) { Ok(u) => u, Err(UserError::NoUserWithId(_)) => { return Ok(base( diff --git a/src/web/pages/usersettings.rs b/src/web/pages/usersettings.rs index 2315288..37dd7e7 100644 --- a/src/web/pages/usersettings.rs +++ b/src/web/pages/usersettings.rs @@ -9,6 +9,7 @@ use serde::Deserialize; use crate::{ api::CompositeError, + database::{self, DatabaseError}, logs::{LogAction, LogEntry}, users::{ User, @@ -81,9 +82,13 @@ pub async fn change_handle( Form(form): Form, ) -> Result { let mut u = User::authenticate(&headers)?.required()?; + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + let oldhandle = u.handle.as_str().to_string(); - u.set_handle(form.handle)?; + u.set_handle(&tx, form.handle)?; LogEntry::new( + &tx, u.clone(), LogAction::ChangeUserHandle { id: u.id, @@ -103,6 +108,9 @@ pub async fn change_password( Form(form): Form, ) -> Result { let mut u = User::authenticate(&headers)?.required()?; - u.set_password(Some(&form.password))?; + let mut conn = database::conn()?; + let tx = conn.transaction().map_err(DatabaseError::from)?; + + u.set_password(&tx, Some(&form.password))?; Ok(Redirect::to("/user-settings").into_response()) }