//! Postgres-Implementierung des `AttachmentRepository`-Ports. use async_trait::async_trait; use sqlx::PgPool; use uuid::Uuid; use holzleitner_application::error::ApplicationError; use holzleitner_application::ports::{ AttachmentLocalRef, AttachmentRef, AttachmentRepository, NewAttachment, }; pub struct PgAttachmentRepository { pool: PgPool, } impl PgAttachmentRepository { pub fn new(pool: PgPool) -> Self { Self { pool } } } fn db(e: E) -> ApplicationError { ApplicationError::Repository(e.to_string()) } #[async_trait] impl AttachmentRepository for PgAttachmentRepository { async fn create(&self, attachment: NewAttachment) -> Result { let id: Uuid = sqlx::query_scalar( r#" INSERT INTO attachments ( docuframe_object_id, mime_type, size_bytes, filename, checksum_sha256, width, height, uploaded_by, delivery_id ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id "#, ) .bind(attachment.docuframe_object_id) .bind(attachment.mime_type) .bind(attachment.size_bytes) .bind(attachment.filename) .bind(attachment.checksum_sha256) .bind(attachment.width) .bind(attachment.height) .bind(attachment.uploaded_by) .bind(attachment.delivery_id) .fetch_one(&self.pool) .await .map_err(db)?; Ok(id) } async fn get(&self, id: Uuid) -> Result, ApplicationError> { let row: Option<(String, String)> = sqlx::query_as( "SELECT docuframe_object_id, mime_type FROM attachments WHERE id = $1", ) .bind(id) .fetch_optional(&self.pool) .await .map_err(db)?; Ok(row.map(|(docuframe_object_id, mime_type)| AttachmentRef { docuframe_object_id, mime_type, })) } async fn delivery_belegnummer( &self, delivery_id: Uuid, ) -> Result, ApplicationError> { let belegnummer: Option = sqlx::query_scalar( "SELECT erp_belegnummer FROM deliveries WHERE id = $1", ) .bind(delivery_id) .fetch_optional(&self.pool) .await .map_err(db)?; Ok(belegnummer) } async fn delivery_customer_number( &self, delivery_id: Uuid, ) -> Result, ApplicationError> { let customer_number: Option = sqlx::query_scalar( "SELECT c.erp_customer_id \ FROM deliveries d \ JOIN customers c ON c.id = d.customer_id \ WHERE d.id = $1", ) .bind(delivery_id) .fetch_optional(&self.pool) .await .map_err(db)?; Ok(customer_number) } async fn list_active_for_delivery( &self, delivery_id: Uuid, ) -> Result, ApplicationError> { let rows: Vec<(Uuid, String)> = sqlx::query_as( "SELECT id, docuframe_object_id FROM attachments \ WHERE delivery_id = $1 AND deleted_at IS NULL", ) .bind(delivery_id) .fetch_all(&self.pool) .await .map_err(db)?; Ok(rows .into_iter() .map(|(id, reference)| AttachmentLocalRef { id, reference }) .collect()) } async fn mark_deleted(&self, id: Uuid) -> Result<(), ApplicationError> { sqlx::query("UPDATE attachments SET deleted_at = now() WHERE id = $1") .bind(id) .execute(&self.pool) .await .map_err(db)?; Ok(()) } }