make transactions higher level (pass them everywhere)

This commit is contained in:
2026-04-03 19:05:29 +02:00
parent e4cccde466
commit 449136ce37
25 changed files with 283 additions and 217 deletions

View File

@@ -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<Markup, CompositeError> {
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 {

View File

@@ -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<Response, CompositeError> {
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" {

View File

@@ -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<Response, AuthError> {
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<Response, AuthError> {
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<Response, AuthError> {
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<PersonNameForm>,
) -> Result<Response, CompositeError> {
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())
}

View File

@@ -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<Response, AuthError> {
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<Response, AuthError> {
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<Response, AuthError> {
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<TagForm>,
) -> Result<Response, CompositeError> {
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())
}

View File

@@ -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<Response, AuthError> {
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<Response, AuthError> {
} @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"

View File

@@ -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<Response, AuthError> {
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<Response, AuthError> {
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<CreateUserWithPasswordForm>,
) -> Result<Response, CompositeError> {
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())
}

View File

@@ -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<Uuid>, req: Request) -> Result<Response, AuthError> {
pub async fn page(Path(id): Path<Uuid>, req: Request) -> Result<Response, CompositeError> {
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(

View File

@@ -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<HandleForm>,
) -> Result<Response, CompositeError> {
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<PasswordForm>,
) -> Result<Response, CompositeError> {
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())
}