use anyhow::{Context as _, anyhow}; use sqlx::{Acquire as _, SqliteConnection}; use uuid::Uuid; use crate::domain::warren::{ models::{ file::AbsoluteFilePath, share::{ CreateShareError, CreateShareRequest, CreateShareResponse, DeleteShareError, DeleteShareRequest, DeleteShareResponse, GetShareError, GetShareRequest, ListSharesError, ListSharesRequest, ListSharesResponse, Share, VerifySharePasswordError, VerifySharePasswordRequest, VerifySharePasswordResponse, }, warren::{ CreateWarrenError, CreateWarrenRequest, DeleteWarrenError, DeleteWarrenRequest, EditWarrenError, EditWarrenRequest, FetchWarrenError, FetchWarrenRequest, FetchWarrensError, FetchWarrensRequest, ListWarrensError, ListWarrensRequest, Warren, WarrenName, }, }, ports::WarrenRepository, }; use super::{Sqlite, is_not_found_error}; impl WarrenRepository for Sqlite { async fn create_warren( &self, request: CreateWarrenRequest, ) -> Result { let mut connection = self .pool .acquire() .await .context("Failed to get a Sqlite connection")?; let warren = self .create_warren(&mut connection, request.name(), request.path()) .await .context("Failed to create new warren")?; Ok(warren) } async fn edit_warren(&self, request: EditWarrenRequest) -> Result { let mut connection = self .pool .acquire() .await .context("Failed to get a Sqlite connection")?; let warren = self .edit_warren( &mut connection, request.id(), request.name(), request.path(), ) .await .context("Failed to edit existing warren")?; Ok(warren) } async fn delete_warren( &self, request: DeleteWarrenRequest, ) -> Result { let mut connection = self .pool .acquire() .await .context("Failed to get a Sqlite connection")?; let warren = self .delete_warren(&mut connection, request.id()) .await .context("Failed to delete existing warren")?; Ok(warren) } async fn fetch_warrens( &self, request: FetchWarrensRequest, ) -> Result, FetchWarrensError> { let mut connection = self .pool .acquire() .await .context("Failed to get a Sqlite connection")?; let warrens = self .fetch_warrens(&mut connection, request.ids()) .await .map_err(|err| anyhow!(err).context("Failed to fetch warrens"))?; Ok(warrens) } async fn list_warrens( &self, _request: ListWarrensRequest, ) -> Result, ListWarrensError> { let mut connection = self .pool .acquire() .await .context("Failed to get a Sqlite connection")?; let warrens = self .fetch_all_warrens(&mut connection) .await .map_err(|err| anyhow!(err).context("Failed to list all warrens"))?; Ok(warrens) } async fn fetch_warren(&self, request: FetchWarrenRequest) -> Result { let mut connection = self .pool .acquire() .await .context("Failed to get a Sqlite connection")?; let warren = self .get_warren(&mut connection, request.id()) .await .map_err(|err| { if is_not_found_error(&err) { return FetchWarrenError::NotFound(request.id().clone()); } anyhow!(err) .context(format!("Failed to fetch warren with id {:?}", request.id())) .into() })?; Ok(warren) } async fn get_warren_share(&self, request: GetShareRequest) -> Result { let mut connection = self .pool .acquire() .await .context("Failed to get a Sqlite connection")?; super::share::get_share(&mut connection, request) .await .map_err(Into::into) } async fn create_warren_share( &self, request: CreateShareRequest, ) -> Result { let mut connection = self .pool .acquire() .await .context("Failed to get a Sqlite connection")?; super::share::create_share(&mut connection, request) .await .map(CreateShareResponse::new) .map_err(Into::into) } async fn list_warren_shares( &self, request: ListSharesRequest, ) -> Result { let warren = self.fetch_warren((&request).into()).await?; let mut connection = self .pool .acquire() .await .context("Failed to get a Sqlite connection")?; let path = request.path().clone(); super::share::list_shares(&mut connection, request) .await .map(|shares| ListSharesResponse::new(warren, path, shares)) .map_err(Into::into) } async fn delete_warren_share( &self, request: DeleteShareRequest, ) -> Result { let mut connection = self .pool .acquire() .await .context("Failed to get a Sqlite connection")?; super::share::delete_share(&mut connection, request) .await .map(DeleteShareResponse::new) .map_err(Into::into) } async fn verify_warren_share_password( &self, request: VerifySharePasswordRequest, ) -> Result { let mut connection = self .pool .acquire() .await .context("Failed to get a Sqlite connection")?; super::share::verify_password(&mut connection, request) .await .map(VerifySharePasswordResponse::new) .map_err(Into::into) } } impl Sqlite { async fn create_warren( &self, connection: &mut SqliteConnection, name: &WarrenName, path: &AbsoluteFilePath, ) -> Result { let mut tx = connection.begin().await?; let warren: Warren = sqlx::query_as( " INSERT INTO warrens ( id, name, path ) VALUES ( $1, $2, $3 ) RETURNING * ", ) .bind(Uuid::new_v4()) .bind(name) .bind(path) .fetch_one(&mut *tx) .await?; tx.commit().await?; Ok(warren) } async fn edit_warren( &self, connection: &mut SqliteConnection, id: &Uuid, name: &WarrenName, path: &AbsoluteFilePath, ) -> Result { let mut tx = connection.begin().await?; let warren: Warren = sqlx::query_as( " UPDATE warrens SET name = $2, path = $3 WHERE id = $1 RETURNING * ", ) .bind(id) .bind(name) .bind(path) .fetch_one(&mut *tx) .await?; tx.commit().await?; Ok(warren) } async fn delete_warren( &self, connection: &mut SqliteConnection, id: &Uuid, ) -> Result { let mut tx = connection.begin().await?; let warren: Warren = sqlx::query_as( " DELETE FROM warrens WHERE id = $1 RETURNING * ", ) .bind(id) .fetch_one(&mut *tx) .await?; tx.commit().await?; Ok(warren) } async fn get_warren( &self, connection: &mut SqliteConnection, id: &Uuid, ) -> Result { let warren: Warren = sqlx::query_as( " SELECT * FROM warrens WHERE id = $1 ", ) .bind(id) .fetch_one(connection) .await?; Ok(warren) } async fn fetch_warrens( &self, connection: &mut SqliteConnection, ids: &[Uuid], ) -> Result, sqlx::Error> { let mut ids_as_string = ids.into_iter().fold(String::new(), |mut acc, id| { let encoded = hex::encode(id.as_bytes()); acc.push_str("x'"); acc.push_str(encoded.as_str()); acc.push_str("',"); acc }); ids_as_string.pop(); let warrens: Vec = sqlx::query_as::(&format!( " SELECT * FROM warrens WHERE id IN ({ids_as_string}) ", )) .fetch_all(&mut *connection) .await?; Ok(warrens) } async fn fetch_all_warrens( &self, connection: &mut SqliteConnection, ) -> Result, sqlx::Error> { let warrens: Vec = sqlx::query_as::( " SELECT * FROM warrens ", ) .fetch_all(&mut *connection) .await?; Ok(warrens) } }