backbone-maintenance 0.7.21

Minimal Backbone Framework module skeleton
Documentation
//! Maintenance Module
//!
//! Generated by metaphor-schema. Enhanced with runtime implementations.
//!
//! This module provides:
//! - Domain entities and repositories
//! - Application services
//! - HTTP and gRPC handlers
//! - Route configuration
//! - State machine enforcement
//! - Validation rules runtime
//! - RBAC middleware
//! - Trigger execution system
//! - Computed fields
//! - Workflow orchestrator

#![recursion_limit = "1024"]
#![allow(unused_imports)]

// Generated modules
pub mod domain;
pub mod infrastructure;
pub mod application;
pub mod presentation;
pub mod seeders;
pub mod exports;

// <<< CUSTOM
// Hand-authored `impl MaintenanceModule` extension (the safe default route composer). Kept OUT of
// lib.rs's generated impl region so `metaphor make` regen can't clobber it — see
// maintenance_module_ext.rs.
pub mod maintenance_module_ext;
// END CUSTOM
// Re-exports for convenience - Domain entities
pub use domain::entity::*;

// Re-exports - Infrastructure
pub use infrastructure::persistence::*;

// Re-exports - Application services
pub use application::service::MaintenanceRequestService;
pub use application::service::MaintenanceScheduleService;
pub use application::service::MaintenanceStageService;
pub use application::service::MaintenanceVisitService;
pub use application::service::MaintenanceVisitPartService;

// Re-exports - Validation
pub use application::validator::{ValidationError, ValidationResult};

// Re-exports - Workflows
pub use application::workflows::*;

// <<< CUSTOM
// Committed write-side contract: the engine + its port traits, re-exported so a consuming service
// reaches them as `maintenance::GlPostSink` etc. rather than deep module paths.
pub use application::service::{
    GlPostSink, InventoryPort, LoggingSink, MaintenanceEventSink, MaintenanceWriteService,
    MaintenanceRequestWriteService,
};
// END CUSTOM
use std::sync::Arc;
use axum::Router;
use sqlx::PgPool;

/// Maintenance module configuration
///
/// Use the builder pattern to configure and register this module:
///
/// ```text
/// let maintenance = MaintenanceModule::builder()
///     .with_database(pool.clone())
///     .build()?;
///
/// // Unguarded full CRUD (trusted/admin); compose a guarded router for production.
/// let router = maintenance.all_crud_routes();
/// ```
pub struct MaintenanceModule {
    pub(crate) maintenance_request_service: Arc<MaintenanceRequestService>,
    pub(crate) maintenance_schedule_service: Arc<MaintenanceScheduleService>,
    pub(crate) maintenance_stage_service: Arc<MaintenanceStageService>,
    pub(crate) maintenance_visit_service: Arc<MaintenanceVisitService>,
    pub(crate) maintenance_visit_part_service: Arc<MaintenanceVisitPartService>,
    // <<< CUSTOM FIELDS
    /// The realized published read contract for sibling modules.
    pub(crate) query: Arc<dyn crate::exports::MaintenanceQueryService>,
    /// The validated visit write engine + the ports its completion verb posts through. The ports are
    /// `Option` because only `lifecycle_routes()` needs them — mounting it fails fast if unset.
    pub(crate) write_svc: Arc<application::service::MaintenanceWriteService>,
    /// The validated maintenance-request write engine (create / field update / stage transition +
    /// clone-on-done successor spawn). No ports: the request family posts through the outbox.
    pub(crate) request_write_svc: Arc<application::service::MaintenanceRequestWriteService>,
    pub(crate) gl_sink: Option<Arc<dyn application::service::GlPostSink>>,
    pub(crate) inventory_port: Option<Arc<dyn application::service::InventoryPort>>,
    pub(crate) event_sink: Arc<dyn application::service::MaintenanceEventSink>,
    // END CUSTOM
}

impl MaintenanceModule {
    /// Create a new module builder
    pub fn builder() -> MaintenanceModuleBuilder {
        MaintenanceModuleBuilder::new()
    }

    /// Mount ALL generated CRUD endpoints (12 per entity) with NO domain
    /// validation — the fully **unguarded** surface. A well-formed request can
    /// create invalid rows or soft-delete a referenced master out from under its
    /// dependents. Prefer a guarded composition (read + validated writes) for any
    /// real deployment; use this only in trusted/admin/seeding contexts.
    pub fn all_crud_routes(&self) -> Router {
        use presentation::http::{
            create_maintenance_request_routes,
            create_maintenance_schedule_routes,
            create_maintenance_stage_routes,
            create_maintenance_visit_routes,
            create_maintenance_visit_part_routes,
        };

        Router::new()
            .merge(create_maintenance_request_routes(self.maintenance_request_service.clone()))
            .merge(create_maintenance_schedule_routes(self.maintenance_schedule_service.clone()))
            .merge(create_maintenance_stage_routes(self.maintenance_stage_service.clone()))
            .merge(create_maintenance_visit_routes(self.maintenance_visit_service.clone()))
            .merge(create_maintenance_visit_part_routes(self.maintenance_visit_part_service.clone()))
    }

    /// Deprecated alias for [`Self::all_crud_routes`]. `routes()` reads like
    /// "the routes" but mounts UNVALIDATED generic CRUD on every entity — a naive
    /// mount exposes unguarded writes. Compose a guarded router (read + validated
    /// writes) for production, or call `all_crud_routes()` to opt into the full
    /// unguarded surface explicitly.
    #[deprecated(note = "mounts unvalidated generic CRUD; prefer readonly_routes() + validated writes, or all_crud_routes() for the full/unguarded surface")]
    pub fn routes(&self) -> Router {
        self.all_crud_routes()
    }

    /// Read-only routes for every entity (GET endpoints only) — the safe base.
    ///
    /// Generic mutation can't reach here, so this surface cannot bypass a
    /// validated write service's invariants. Use this as the production base and
    /// merge validated write routes (or a write service's HTTP layer) onto it.
    pub fn readonly_routes(&self) -> Router {
        use presentation::http::{
            create_maintenance_request_read_routes,
            create_maintenance_schedule_read_routes,
            create_maintenance_stage_read_routes,
            create_maintenance_visit_read_routes,
            create_maintenance_visit_part_read_routes,
        };

        Router::new()
            .merge(create_maintenance_request_read_routes(self.maintenance_request_service.clone()))
            .merge(create_maintenance_schedule_read_routes(self.maintenance_schedule_service.clone()))
            .merge(create_maintenance_stage_read_routes(self.maintenance_stage_service.clone()))
            .merge(create_maintenance_visit_read_routes(self.maintenance_visit_service.clone()))
            .merge(create_maintenance_visit_part_read_routes(self.maintenance_visit_part_service.clone()))
    }

    // <<< CUSTOM METHODS
    // END CUSTOM
}

/// Builder for MaintenanceModule
pub struct MaintenanceModuleBuilder {
    db_pool: Option<PgPool>,
    // <<< CUSTOM
    gl_sink: Option<Arc<dyn application::service::GlPostSink>>,
    inventory_port: Option<Arc<dyn application::service::InventoryPort>>,
    event_sink: Option<Arc<dyn application::service::MaintenanceEventSink>>,
    // END CUSTOM
}

impl MaintenanceModuleBuilder {
    /// Create a new builder
    pub fn new() -> Self {
        Self {
            db_pool: None,
            gl_sink: None,
            inventory_port: None,
            event_sink: None,
        }
    }

    /// Set the database connection pool
    pub fn with_database(mut self, pool: PgPool) -> Self {
        self.db_pool = Some(pool);
        self
    }

    // <<< CUSTOM - custom builder methods
    /// Supply the GL-posting sink the completion verb posts the cost journal through. Required to
    /// mount [`crate::MaintenanceModule::lifecycle_routes`]; a composing service adapts its
    /// accounting `PostingService` to [`crate::GlPostSink`].
    pub fn with_gl_sink(mut self, sink: Arc<dyn application::service::GlPostSink>) -> Self {
        self.gl_sink = Some(sink);
        self
    }

    /// Supply the inventory port the completion verb issues parts through (valued at inventory's
    /// moving-average). Required to mount [`crate::MaintenanceModule::lifecycle_routes`].
    pub fn with_inventory_port(
        mut self,
        port: Arc<dyn application::service::InventoryPort>,
    ) -> Self {
        self.inventory_port = Some(port);
        self
    }

    /// Supply the domain-event sink. Defaults to a logging sink if unset.
    pub fn with_event_sink(
        mut self,
        sink: Arc<dyn application::service::MaintenanceEventSink>,
    ) -> Self {
        self.event_sink = Some(sink);
        self
    }
    // END CUSTOM

    /// Build the module with configured dependencies
    pub fn build(self) -> anyhow::Result<MaintenanceModule> {
        let db_pool = self.db_pool
            .ok_or_else(|| anyhow::anyhow!("Database pool not configured"))?;

        // MaintenanceRequest service
        let maintenance_request_repository = Arc::new(MaintenanceRequestRepository::new(db_pool.clone()));
        let maintenance_request_service = Arc::new(MaintenanceRequestService::with_repository(maintenance_request_repository.clone()));

        // MaintenanceSchedule service
        let maintenance_schedule_repository = Arc::new(MaintenanceScheduleRepository::new(db_pool.clone()));
        let maintenance_schedule_service = Arc::new(MaintenanceScheduleService::with_repository(maintenance_schedule_repository.clone()));

        // MaintenanceStage service
        let maintenance_stage_repository = Arc::new(MaintenanceStageRepository::new(db_pool.clone()));
        let maintenance_stage_service = Arc::new(MaintenanceStageService::with_repository(maintenance_stage_repository.clone()));

        // MaintenanceVisit service
        let maintenance_visit_repository = Arc::new(MaintenanceVisitRepository::new(db_pool.clone()));
        let maintenance_visit_service = Arc::new(MaintenanceVisitService::with_repository(maintenance_visit_repository.clone()));

        // MaintenanceVisitPart service
        let maintenance_visit_part_repository = Arc::new(MaintenanceVisitPartRepository::new(db_pool.clone()));
        let maintenance_visit_part_service = Arc::new(MaintenanceVisitPartService::with_repository(maintenance_visit_part_repository.clone()));

        // <<< CUSTOM
        let query: Arc<dyn crate::exports::MaintenanceQueryService> =
            Arc::new(application::service::MaintenanceQueryServiceImpl::new(db_pool.clone()));
        let write_svc =
            Arc::new(application::service::MaintenanceWriteService::new(db_pool.clone()));
        let request_write_svc = Arc::new(
            application::service::MaintenanceRequestWriteService::new(db_pool.clone()),
        );
        let event_sink: Arc<dyn application::service::MaintenanceEventSink> = self
            .event_sink
            .unwrap_or_else(|| Arc::new(application::service::LoggingSink));
        // END CUSTOM

        Ok(MaintenanceModule {
            maintenance_request_service,
            maintenance_schedule_service,
            maintenance_stage_service,
            maintenance_visit_service,
            maintenance_visit_part_service,
            query,
            write_svc,
            request_write_svc,
            gl_sink: self.gl_sink,
            inventory_port: self.inventory_port,
            event_sink,
        })
    }
}

impl Default for MaintenanceModuleBuilder {
    fn default() -> Self {
        Self::new()
    }
}