//! Repository layer for database operations //! //! This module provides the repository pattern for all database entities in Attune. //! Repositories abstract database operations and provide a clean interface for CRUD //! operations and queries. //! //! # Architecture //! //! - Each entity has its own repository module (e.g., `pack`, `action`, `trigger`) //! - Repositories use SQLx for database operations //! - Transaction support is provided through SQLx's transaction types //! - All operations return `Result` for consistent error handling //! //! # Example //! //! ```rust,no_run //! use attune_common::repositories::{PackRepository, FindByRef}; //! use attune_common::db::Database; //! //! async fn example(db: &Database) -> attune_common::Result<()> { //! if let Some(pack) = PackRepository::find_by_ref(db.pool(), "core").await? { //! println!("Found pack: {}", pack.label); //! } //! Ok(()) //! } //! ``` use sqlx::{Executor, Postgres, Transaction}; pub mod action; pub mod analytics; pub mod artifact; pub mod entity_history; pub mod event; pub mod execution; pub mod execution_admission; pub mod identity; pub mod inquiry; pub mod key; pub mod notification; pub mod pack; pub mod pack_test; pub mod queue_stats; pub mod rule; pub mod runtime; pub mod runtime_version; pub mod trigger; pub mod workflow; // Re-export repository types pub use action::{ActionRepository, PolicyRepository}; pub use analytics::AnalyticsRepository; pub use artifact::{ArtifactRepository, ArtifactVersionRepository}; pub use entity_history::EntityHistoryRepository; pub use event::{EnforcementRepository, EventRepository}; pub use execution::ExecutionRepository; pub use execution_admission::ExecutionAdmissionRepository; pub use identity::{IdentityRepository, PermissionAssignmentRepository, PermissionSetRepository}; pub use inquiry::InquiryRepository; pub use key::KeyRepository; pub use notification::NotificationRepository; pub use pack::PackRepository; pub use pack_test::PackTestRepository; pub use queue_stats::QueueStatsRepository; pub use rule::RuleRepository; pub use runtime::{RuntimeRepository, WorkerRepository}; pub use runtime_version::RuntimeVersionRepository; pub use trigger::{SensorRepository, TriggerRepository}; pub use workflow::{WorkflowDefinitionRepository, WorkflowExecutionRepository}; /// Explicit patch operation for update inputs where callers must distinguish /// between "leave unchanged", "set value", and "clear to NULL". #[derive(Debug, Clone, PartialEq)] pub enum Patch { Set(T), Clear, } /// Type alias for database connection/transaction pub type DbConnection<'c> = &'c mut Transaction<'c, Postgres>; /// Base repository trait providing common functionality /// /// This trait is not meant to be used directly, but serves as a foundation /// for specific repository implementations. pub trait Repository { /// The entity type this repository manages type Entity; /// Get the name of the table for this repository fn table_name() -> &'static str; } /// Trait for repositories that support finding by ID #[async_trait::async_trait] pub trait FindById: Repository { /// Find an entity by its ID /// /// # Arguments /// /// * `executor` - Database executor (pool or transaction) /// * `id` - The ID to search for /// /// # Returns /// /// * `Ok(Some(entity))` if found /// * `Ok(None)` if not found /// * `Err(error)` on database error async fn find_by_id<'e, E>(executor: E, id: i64) -> crate::Result> where E: Executor<'e, Database = Postgres> + 'e; /// Get an entity by its ID, returning an error if not found /// /// # Arguments /// /// * `executor` - Database executor (pool or transaction) /// * `id` - The ID to search for /// /// # Returns /// /// * `Ok(entity)` if found /// * `Err(NotFound)` if not found /// * `Err(error)` on database error async fn get_by_id<'e, E>(executor: E, id: i64) -> crate::Result where E: Executor<'e, Database = Postgres> + 'e, { Self::find_by_id(executor, id) .await? .ok_or_else(|| crate::Error::not_found(Self::table_name(), "id", id.to_string())) } } /// Trait for repositories that support finding by reference #[async_trait::async_trait] pub trait FindByRef: Repository { /// Find an entity by its reference string /// /// # Arguments /// /// * `executor` - Database executor (pool or transaction) /// * `ref_str` - The reference string to search for /// /// # Returns /// /// * `Ok(Some(entity))` if found /// * `Ok(None)` if not found /// * `Err(error)` on database error async fn find_by_ref<'e, E>(executor: E, ref_str: &str) -> crate::Result> where E: Executor<'e, Database = Postgres> + 'e; /// Get an entity by its reference, returning an error if not found /// /// # Arguments /// /// * `executor` - Database executor (pool or transaction) /// * `ref_str` - The reference string to search for /// /// # Returns /// /// * `Ok(entity)` if found /// * `Err(NotFound)` if not found /// * `Err(error)` on database error async fn get_by_ref<'e, E>(executor: E, ref_str: &str) -> crate::Result where E: Executor<'e, Database = Postgres> + 'e, { Self::find_by_ref(executor, ref_str) .await? .ok_or_else(|| crate::Error::not_found(Self::table_name(), "ref", ref_str)) } } /// Trait for repositories that support listing all entities #[async_trait::async_trait] pub trait List: Repository { /// List all entities /// /// # Arguments /// /// * `executor` - Database executor (pool or transaction) /// /// # Returns /// /// * `Ok(Vec)` - List of all entities /// * `Err(error)` on database error async fn list<'e, E>(executor: E) -> crate::Result> where E: Executor<'e, Database = Postgres> + 'e; } /// Trait for repositories that support creating entities #[async_trait::async_trait] pub trait Create: Repository { /// Input type for creating a new entity type CreateInput; /// Create a new entity /// /// # Arguments /// /// * `executor` - Database executor (pool or transaction) /// * `input` - The data for creating the entity /// /// # Returns /// /// * `Ok(entity)` - The created entity /// * `Err(error)` on database error or validation failure async fn create<'e, E>(executor: E, input: Self::CreateInput) -> crate::Result where E: Executor<'e, Database = Postgres> + 'e; } /// Trait for repositories that support updating entities #[async_trait::async_trait] pub trait Update: Repository { /// Input type for updating an entity type UpdateInput; /// Update an existing entity by ID /// /// # Arguments /// /// * `executor` - Database executor (pool or transaction) /// * `id` - The ID of the entity to update /// * `input` - The data for updating the entity /// /// # Returns /// /// * `Ok(entity)` - The updated entity /// * `Err(NotFound)` if the entity doesn't exist /// * `Err(error)` on database error or validation failure async fn update<'e, E>( executor: E, id: i64, input: Self::UpdateInput, ) -> crate::Result where E: Executor<'e, Database = Postgres> + 'e; } /// Trait for repositories that support deleting entities #[async_trait::async_trait] pub trait Delete: Repository { /// Delete an entity by ID /// /// # Arguments /// /// * `executor` - Database executor (pool or transaction) /// * `id` - The ID of the entity to delete /// /// # Returns /// /// * `Ok(true)` if the entity was deleted /// * `Ok(false)` if the entity didn't exist /// * `Err(error)` on database error async fn delete<'e, E>(executor: E, id: i64) -> crate::Result where E: Executor<'e, Database = Postgres> + 'e; } /// Helper struct for pagination parameters #[derive(Debug, Clone, Copy)] pub struct Pagination { /// Page number (0-based) pub page: i64, /// Number of items per page pub per_page: i64, } impl Pagination { /// Create a new Pagination instance pub fn new(page: i64, per_page: i64) -> Self { Self { page, per_page } } /// Calculate the OFFSET for SQL queries pub fn offset(&self) -> i64 { self.page * self.per_page } /// Get the LIMIT for SQL queries pub fn limit(&self) -> i64 { self.per_page } } impl Default for Pagination { fn default() -> Self { Self { page: 0, per_page: 50, } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_pagination() { let p = Pagination::new(0, 10); assert_eq!(p.offset(), 0); assert_eq!(p.limit(), 10); let p = Pagination::new(2, 10); assert_eq!(p.offset(), 20); assert_eq!(p.limit(), 10); } #[test] fn test_pagination_default() { let p = Pagination::default(); assert_eq!(p.page, 0); assert_eq!(p.per_page, 50); } }