Skip to main content

backbone_maintenance/
lib.rs

1//! Maintenance Module
2//!
3//! Generated by metaphor-schema. Enhanced with runtime implementations.
4//!
5//! This module provides:
6//! - Domain entities and repositories
7//! - Application services
8//! - HTTP and gRPC handlers
9//! - Route configuration
10//! - State machine enforcement
11//! - Validation rules runtime
12//! - RBAC middleware
13//! - Trigger execution system
14//! - Computed fields
15//! - Workflow orchestrator
16
17#![recursion_limit = "1024"]
18#![allow(unused_imports)]
19
20// Generated modules
21pub mod domain;
22pub mod infrastructure;
23pub mod application;
24pub mod presentation;
25pub mod seeders;
26pub mod exports;
27
28// <<< CUSTOM
29// Hand-authored `impl MaintenanceModule` extension (the safe default route composer). Kept OUT of
30// lib.rs's generated impl region so `metaphor make` regen can't clobber it — see
31// maintenance_module_ext.rs.
32pub mod maintenance_module_ext;
33// END CUSTOM
34// Re-exports for convenience - Domain entities
35pub use domain::entity::*;
36
37// Re-exports - Infrastructure
38pub use infrastructure::persistence::*;
39
40// Re-exports - Application services
41pub use application::service::MaintenanceRequestService;
42pub use application::service::MaintenanceScheduleService;
43pub use application::service::MaintenanceStageService;
44pub use application::service::MaintenanceVisitService;
45pub use application::service::MaintenanceVisitPartService;
46
47// Re-exports - Validation
48pub use application::validator::{ValidationError, ValidationResult};
49
50// Re-exports - Workflows
51pub use application::workflows::*;
52
53// <<< CUSTOM
54// Committed write-side contract: the engine + its port traits, re-exported so a consuming service
55// reaches them as `maintenance::GlPostSink` etc. rather than deep module paths.
56pub use application::service::{
57    GlPostSink, InventoryPort, LoggingSink, MaintenanceEventSink, MaintenanceWriteService,
58    MaintenanceRequestWriteService,
59};
60// END CUSTOM
61use std::sync::Arc;
62use axum::Router;
63use sqlx::PgPool;
64
65/// Maintenance module configuration
66///
67/// Use the builder pattern to configure and register this module:
68///
69/// ```text
70/// let maintenance = MaintenanceModule::builder()
71///     .with_database(pool.clone())
72///     .build()?;
73///
74/// // Unguarded full CRUD (trusted/admin); compose a guarded router for production.
75/// let router = maintenance.all_crud_routes();
76/// ```
77pub struct MaintenanceModule {
78    pub(crate) maintenance_request_service: Arc<MaintenanceRequestService>,
79    pub(crate) maintenance_schedule_service: Arc<MaintenanceScheduleService>,
80    pub(crate) maintenance_stage_service: Arc<MaintenanceStageService>,
81    pub(crate) maintenance_visit_service: Arc<MaintenanceVisitService>,
82    pub(crate) maintenance_visit_part_service: Arc<MaintenanceVisitPartService>,
83    // <<< CUSTOM FIELDS
84    /// The realized published read contract for sibling modules.
85    pub(crate) query: Arc<dyn crate::exports::MaintenanceQueryService>,
86    /// The validated visit write engine + the ports its completion verb posts through. The ports are
87    /// `Option` because only `lifecycle_routes()` needs them — mounting it fails fast if unset.
88    pub(crate) write_svc: Arc<application::service::MaintenanceWriteService>,
89    /// The validated maintenance-request write engine (create / field update / stage transition +
90    /// clone-on-done successor spawn). No ports: the request family posts through the outbox.
91    pub(crate) request_write_svc: Arc<application::service::MaintenanceRequestWriteService>,
92    pub(crate) gl_sink: Option<Arc<dyn application::service::GlPostSink>>,
93    pub(crate) inventory_port: Option<Arc<dyn application::service::InventoryPort>>,
94    pub(crate) event_sink: Arc<dyn application::service::MaintenanceEventSink>,
95    // END CUSTOM
96}
97
98impl MaintenanceModule {
99    /// Create a new module builder
100    pub fn builder() -> MaintenanceModuleBuilder {
101        MaintenanceModuleBuilder::new()
102    }
103
104    /// Mount ALL generated CRUD endpoints (12 per entity) with NO domain
105    /// validation — the fully **unguarded** surface. A well-formed request can
106    /// create invalid rows or soft-delete a referenced master out from under its
107    /// dependents. Prefer a guarded composition (read + validated writes) for any
108    /// real deployment; use this only in trusted/admin/seeding contexts.
109    pub fn all_crud_routes(&self) -> Router {
110        use presentation::http::{
111            create_maintenance_request_routes,
112            create_maintenance_schedule_routes,
113            create_maintenance_stage_routes,
114            create_maintenance_visit_routes,
115            create_maintenance_visit_part_routes,
116        };
117
118        Router::new()
119            .merge(create_maintenance_request_routes(self.maintenance_request_service.clone()))
120            .merge(create_maintenance_schedule_routes(self.maintenance_schedule_service.clone()))
121            .merge(create_maintenance_stage_routes(self.maintenance_stage_service.clone()))
122            .merge(create_maintenance_visit_routes(self.maintenance_visit_service.clone()))
123            .merge(create_maintenance_visit_part_routes(self.maintenance_visit_part_service.clone()))
124    }
125
126    /// Deprecated alias for [`Self::all_crud_routes`]. `routes()` reads like
127    /// "the routes" but mounts UNVALIDATED generic CRUD on every entity — a naive
128    /// mount exposes unguarded writes. Compose a guarded router (read + validated
129    /// writes) for production, or call `all_crud_routes()` to opt into the full
130    /// unguarded surface explicitly.
131    #[deprecated(note = "mounts unvalidated generic CRUD; prefer readonly_routes() + validated writes, or all_crud_routes() for the full/unguarded surface")]
132    pub fn routes(&self) -> Router {
133        self.all_crud_routes()
134    }
135
136    /// Read-only routes for every entity (GET endpoints only) — the safe base.
137    ///
138    /// Generic mutation can't reach here, so this surface cannot bypass a
139    /// validated write service's invariants. Use this as the production base and
140    /// merge validated write routes (or a write service's HTTP layer) onto it.
141    pub fn readonly_routes(&self) -> Router {
142        use presentation::http::{
143            create_maintenance_request_read_routes,
144            create_maintenance_schedule_read_routes,
145            create_maintenance_stage_read_routes,
146            create_maintenance_visit_read_routes,
147            create_maintenance_visit_part_read_routes,
148        };
149
150        Router::new()
151            .merge(create_maintenance_request_read_routes(self.maintenance_request_service.clone()))
152            .merge(create_maintenance_schedule_read_routes(self.maintenance_schedule_service.clone()))
153            .merge(create_maintenance_stage_read_routes(self.maintenance_stage_service.clone()))
154            .merge(create_maintenance_visit_read_routes(self.maintenance_visit_service.clone()))
155            .merge(create_maintenance_visit_part_read_routes(self.maintenance_visit_part_service.clone()))
156    }
157
158    // <<< CUSTOM METHODS
159    // END CUSTOM
160}
161
162/// Builder for MaintenanceModule
163pub struct MaintenanceModuleBuilder {
164    db_pool: Option<PgPool>,
165    // <<< CUSTOM
166    gl_sink: Option<Arc<dyn application::service::GlPostSink>>,
167    inventory_port: Option<Arc<dyn application::service::InventoryPort>>,
168    event_sink: Option<Arc<dyn application::service::MaintenanceEventSink>>,
169    // END CUSTOM
170}
171
172impl MaintenanceModuleBuilder {
173    /// Create a new builder
174    pub fn new() -> Self {
175        Self {
176            db_pool: None,
177            gl_sink: None,
178            inventory_port: None,
179            event_sink: None,
180        }
181    }
182
183    /// Set the database connection pool
184    pub fn with_database(mut self, pool: PgPool) -> Self {
185        self.db_pool = Some(pool);
186        self
187    }
188
189    // <<< CUSTOM - custom builder methods
190    /// Supply the GL-posting sink the completion verb posts the cost journal through. Required to
191    /// mount [`crate::MaintenanceModule::lifecycle_routes`]; a composing service adapts its
192    /// accounting `PostingService` to [`crate::GlPostSink`].
193    pub fn with_gl_sink(mut self, sink: Arc<dyn application::service::GlPostSink>) -> Self {
194        self.gl_sink = Some(sink);
195        self
196    }
197
198    /// Supply the inventory port the completion verb issues parts through (valued at inventory's
199    /// moving-average). Required to mount [`crate::MaintenanceModule::lifecycle_routes`].
200    pub fn with_inventory_port(
201        mut self,
202        port: Arc<dyn application::service::InventoryPort>,
203    ) -> Self {
204        self.inventory_port = Some(port);
205        self
206    }
207
208    /// Supply the domain-event sink. Defaults to a logging sink if unset.
209    pub fn with_event_sink(
210        mut self,
211        sink: Arc<dyn application::service::MaintenanceEventSink>,
212    ) -> Self {
213        self.event_sink = Some(sink);
214        self
215    }
216    // END CUSTOM
217
218    /// Build the module with configured dependencies
219    pub fn build(self) -> anyhow::Result<MaintenanceModule> {
220        let db_pool = self.db_pool
221            .ok_or_else(|| anyhow::anyhow!("Database pool not configured"))?;
222
223        // MaintenanceRequest service
224        let maintenance_request_repository = Arc::new(MaintenanceRequestRepository::new(db_pool.clone()));
225        let maintenance_request_service = Arc::new(MaintenanceRequestService::with_repository(maintenance_request_repository.clone()));
226
227        // MaintenanceSchedule service
228        let maintenance_schedule_repository = Arc::new(MaintenanceScheduleRepository::new(db_pool.clone()));
229        let maintenance_schedule_service = Arc::new(MaintenanceScheduleService::with_repository(maintenance_schedule_repository.clone()));
230
231        // MaintenanceStage service
232        let maintenance_stage_repository = Arc::new(MaintenanceStageRepository::new(db_pool.clone()));
233        let maintenance_stage_service = Arc::new(MaintenanceStageService::with_repository(maintenance_stage_repository.clone()));
234
235        // MaintenanceVisit service
236        let maintenance_visit_repository = Arc::new(MaintenanceVisitRepository::new(db_pool.clone()));
237        let maintenance_visit_service = Arc::new(MaintenanceVisitService::with_repository(maintenance_visit_repository.clone()));
238
239        // MaintenanceVisitPart service
240        let maintenance_visit_part_repository = Arc::new(MaintenanceVisitPartRepository::new(db_pool.clone()));
241        let maintenance_visit_part_service = Arc::new(MaintenanceVisitPartService::with_repository(maintenance_visit_part_repository.clone()));
242
243        // <<< CUSTOM
244        let query: Arc<dyn crate::exports::MaintenanceQueryService> =
245            Arc::new(application::service::MaintenanceQueryServiceImpl::new(db_pool.clone()));
246        let write_svc =
247            Arc::new(application::service::MaintenanceWriteService::new(db_pool.clone()));
248        let request_write_svc = Arc::new(
249            application::service::MaintenanceRequestWriteService::new(db_pool.clone()),
250        );
251        let event_sink: Arc<dyn application::service::MaintenanceEventSink> = self
252            .event_sink
253            .unwrap_or_else(|| Arc::new(application::service::LoggingSink));
254        // END CUSTOM
255
256        Ok(MaintenanceModule {
257            maintenance_request_service,
258            maintenance_schedule_service,
259            maintenance_stage_service,
260            maintenance_visit_service,
261            maintenance_visit_part_service,
262            query,
263            write_svc,
264            request_write_svc,
265            gl_sink: self.gl_sink,
266            inventory_port: self.inventory_port,
267            event_sink,
268        })
269    }
270}
271
272impl Default for MaintenanceModuleBuilder {
273    fn default() -> Self {
274        Self::new()
275    }
276}