1use crate::handle::{DbHandle, DbPool};
2use crate::DbError;
3use sea_orm::TransactionTrait;
4use sova_core::extend::named;
5use sova_core::{Next, Request, Response};
6use std::sync::Arc;
7
8pub fn transaction() -> impl sova_core::extend::IntoMwEntry {
10 named("db_tx", |mut req: Request, next: Next| async move {
11 let Some(DbHandle::Conn(conn)) = req.get::<DbHandle>().cloned() else {
12 return Response::text("database connection missing for transaction").status(500);
13 };
14 let tx = match conn.begin().await {
15 Ok(tx) => tx,
16 Err(err) => return DbError(err).into_response_via_error(),
17 };
18 let arc = Arc::new(tx);
19 req.set(DbHandle::Tx(Arc::clone(&arc)));
20 let res = next(req).await;
21 match Arc::try_unwrap(arc) {
22 Ok(tx) => {
23 if res.status_code().is_success() {
24 if let Err(err) = tx.commit().await {
25 tracing::error!(error = %err, "db commit failed");
26 return Response::text("Internal Server Error").status(500);
27 }
28 } else if let Err(err) = tx.rollback().await {
29 tracing::error!(error = %err, "db rollback failed");
30 }
31 }
32 Err(_) => tracing::error!("db transaction still held after request"),
33 }
34 res
35 })
36}
37
38trait IntoResponseViaError {
39 fn into_response_via_error(self) -> Response;
40}
41
42impl IntoResponseViaError for DbError {
43 fn into_response_via_error(self) -> Response {
44 sova_core::IntoResponse::into_response(self)
45 }
46}
47
48pub(crate) fn inject_conn(pool: DbPool) -> impl sova_core::extend::IntoMwEntry {
50 named(
51 "db",
52 sova_core::with_state(pool, |pool, mut req, next| async move {
53 match pool.get() {
54 Ok(conn) => {
55 req.set(DbHandle::Conn(conn));
56 next(req).await
57 }
58 Err(err) => sova_core::IntoResponse::into_response(err),
59 }
60 }),
61 )
62}