re-uploading work
This commit is contained in:
306
crates/common/src/repositories/mod.rs
Normal file
306
crates/common/src/repositories/mod.rs
Normal file
@@ -0,0 +1,306 @@
|
||||
//! 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<T, Error>` 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 artifact;
|
||||
pub mod event;
|
||||
pub mod execution;
|
||||
pub mod identity;
|
||||
pub mod inquiry;
|
||||
pub mod key;
|
||||
pub mod notification;
|
||||
pub mod pack;
|
||||
pub mod pack_installation;
|
||||
pub mod pack_test;
|
||||
pub mod queue_stats;
|
||||
pub mod rule;
|
||||
pub mod runtime;
|
||||
pub mod trigger;
|
||||
pub mod workflow;
|
||||
|
||||
// Re-export repository types
|
||||
pub use action::{ActionRepository, PolicyRepository};
|
||||
pub use artifact::ArtifactRepository;
|
||||
pub use event::{EnforcementRepository, EventRepository};
|
||||
pub use execution::ExecutionRepository;
|
||||
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_installation::PackInstallationRepository;
|
||||
pub use pack_test::PackTestRepository;
|
||||
pub use queue_stats::QueueStatsRepository;
|
||||
pub use rule::RuleRepository;
|
||||
pub use runtime::{RuntimeRepository, WorkerRepository};
|
||||
pub use trigger::{SensorRepository, TriggerRepository};
|
||||
pub use workflow::{WorkflowDefinitionRepository, WorkflowExecutionRepository};
|
||||
|
||||
/// 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<Option<Self::Entity>>
|
||||
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<Self::Entity>
|
||||
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<Option<Self::Entity>>
|
||||
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<Self::Entity>
|
||||
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<entity>)` - List of all entities
|
||||
/// * `Err(error)` on database error
|
||||
async fn list<'e, E>(executor: E) -> crate::Result<Vec<Self::Entity>>
|
||||
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<Self::Entity>
|
||||
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<Self::Entity>
|
||||
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<bool>
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user