use std::sync::Arc; use async_trait::async_trait; use libertas_core::{ config::AppConfig, error::{CoreError, CoreResult}, models::Media, repositories::MediaRepository, schema::ListMediaOptions, }; use sqlx::PgPool; use uuid::Uuid; use crate::{ db_models::PostgresMedia, query_builder::{MediaQueryBuilder, QueryBuilder}, }; #[derive(Clone)] pub struct PostgresMediaRepository { pool: PgPool, query_builder: Arc, } impl PostgresMediaRepository { pub fn new(pool: PgPool, config: &AppConfig) -> Self { let allowed_columns = config .allowed_sort_columns .clone() .unwrap_or_else(|| vec!["created_at".to_string(), "original_filename".to_string()]); Self { pool, query_builder: Arc::new(MediaQueryBuilder::new(allowed_columns)), } } pub(crate) async fn create_internal<'a>( exec: impl sqlx::Executor<'a, Database = sqlx::Postgres>, media: &Media, ) -> CoreResult<()> { sqlx::query!( r#" INSERT INTO media (id, owner_id, storage_path, original_filename, mime_type, hash, created_at, thumbnail_path) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) "#, media.id, media.owner_id, media.storage_path, media.original_filename, media.mime_type, media.hash, media.created_at, media.thumbnail_path ) .execute(exec) .await .map_err(|e| CoreError::Database(e.to_string()))?; Ok(()) } } #[async_trait] impl MediaRepository for PostgresMediaRepository { async fn create(&self, media: &Media) -> CoreResult<()> { Self::create_internal(&self.pool, media).await } async fn find_by_hash(&self, hash: &str) -> CoreResult> { let pg_media = sqlx::query_as!( PostgresMedia, r#" SELECT id, owner_id, storage_path, original_filename, mime_type, hash, created_at, thumbnail_path FROM media WHERE hash = $1 "#, hash ) .fetch_optional(&self.pool) .await .map_err(|e| CoreError::Database(e.to_string()))?; Ok(pg_media.map(|m| m.into())) } async fn find_by_id(&self, id: Uuid) -> CoreResult> { let pg_media = sqlx::query_as!( PostgresMedia, r#" SELECT id, owner_id, storage_path, original_filename, mime_type, hash, created_at, thumbnail_path FROM media WHERE id = $1 "#, id ) .fetch_optional(&self.pool) .await .map_err(|e| CoreError::Database(e.to_string()))?; Ok(pg_media.map(|m| m.into())) } async fn list_by_user( &self, user_id: Uuid, options: &ListMediaOptions, ) -> CoreResult> { let mut query = sqlx::QueryBuilder::new( r#" SELECT media.id, media.owner_id, media.storage_path, media.original_filename, media.mime_type, media.hash, media.created_at, media.thumbnail_path FROM media WHERE media.owner_id = "#, ); query.push_bind(user_id); query = self.query_builder.apply_options_to_query(query, options)?; let pg_media = query .build_query_as::() .fetch_all(&self.pool) .await .map_err(|e| CoreError::Database(e.to_string()))?; let media_list = pg_media.into_iter().map(|m| m.into()).collect(); Ok(media_list) } async fn update_thumbnail_path(&self, id: Uuid, thumbnail_path: String) -> CoreResult<()> { sqlx::query!( r#" UPDATE media SET thumbnail_path = $2 WHERE id = $1 "#, id, thumbnail_path ) .execute(&self.pool) .await .map_err(|e| CoreError::Database(e.to_string()))?; Ok(()) } async fn delete(&self, id: Uuid) -> CoreResult<()> { sqlx::query!( r#" DELETE FROM media WHERE id = $1 "#, id ) .execute(&self.pool) .await .map_err(|e| CoreError::Database(e.to_string()))?; Ok(()) } }