Skip to main content

backbone_bucket/
lib.rs

1//! Bucket 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// The module's extension (hand-written; ADR-0031).
29#[path = "lib.ext.rs"]
30mod lib_ext;
31pub use lib_ext::*;
32/// This crate's module and builder under fixed names, for the extension's `impl` blocks.
33#[allow(dead_code)]
34pub(crate) type ThisModule = BucketModule;
35#[allow(dead_code)]
36pub(crate) type ThisModuleBuilder = BucketModuleBuilder;
37
38// Re-exports for convenience - Domain entities
39pub use domain::entity::*;
40
41// Re-exports - State Machine
42pub use domain::state_machine::*;
43
44// Re-exports - Computed Fields
45pub use domain::computed::{HasComputedFields, ComputedFieldValues};
46
47// Re-exports - Permissions
48pub use domain::permission::{Action, FieldRestriction, PermissionChecker, PermissionError, PermissionResult};
49
50// Re-exports - Infrastructure
51pub use infrastructure::persistence::*;
52
53// Re-exports - Application services
54pub use application::service::AccessLogService;
55pub use application::service::BucketService;
56pub use application::service::ContentHashService;
57pub use application::service::ConversionJobService;
58pub use application::service::FileCommentService;
59pub use application::service::FileLockService;
60pub use application::service::FileShareService;
61pub use application::service::FileVersionService;
62pub use application::service::ProcessingJobService;
63pub use application::service::StoredFileService;
64pub use application::service::ThumbnailService;
65pub use application::service::UploadSessionService;
66pub use application::service::UserQuotaService;
67
68// Re-exports - Validation
69pub use application::validator::{ValidationError, ValidationResult};
70
71// Re-exports - Triggers
72pub use application::triggers::*;
73
74// Re-exports - Workflows
75pub use application::workflows::*;
76
77use std::sync::Arc;
78use axum::Router;
79use sqlx::PgPool;
80
81/// Bucket module configuration
82///
83/// Use the builder pattern to configure and register this module:
84///
85/// ```text
86/// let bucket = BucketModule::builder()
87///     .with_database(pool.clone())
88///     .build()?;
89///
90/// // Unguarded full CRUD (trusted/admin); compose a guarded router for production.
91/// let router = bucket.all_crud_routes();
92/// ```
93pub struct BucketModule {
94    #[allow(dead_code)]
95    pub(crate) access_log_service: Arc<AccessLogService>,
96    pub(crate) bucket_service: Arc<BucketService>,
97    pub(crate) content_hash_service: Arc<ContentHashService>,
98    pub(crate) conversion_job_service: Arc<ConversionJobService>,
99    pub(crate) file_comment_service: Arc<FileCommentService>,
100    pub(crate) file_lock_service: Arc<FileLockService>,
101    pub(crate) file_share_service: Arc<FileShareService>,
102    #[allow(dead_code)]
103    pub(crate) file_version_service: Arc<FileVersionService>,
104    pub(crate) processing_job_service: Arc<ProcessingJobService>,
105    pub(crate) stored_file_service: Arc<StoredFileService>,
106    #[allow(dead_code)]
107    pub(crate) thumbnail_service: Arc<ThumbnailService>,
108    pub(crate) upload_session_service: Arc<UploadSessionService>,
109    pub(crate) user_quota_service: Arc<UserQuotaService>,
110    /// The module's extension state (`lib.ext.rs`); its fields read through `Deref`.
111    pub(crate) ext: ModuleExt,
112}
113
114impl std::ops::Deref for BucketModule {
115    type Target = ModuleExt;
116    fn deref(&self) -> &Self::Target {
117        &self.ext
118    }
119}
120
121impl std::ops::DerefMut for BucketModule {
122    fn deref_mut(&mut self) -> &mut Self::Target {
123        &mut self.ext
124    }
125}
126
127/// What the extension's `build` hook receives from the generated build (ADR-0031).
128#[allow(dead_code)]
129pub(crate) struct ModuleParts<'a> {
130    pub(crate) db_pool: &'a PgPool,
131    pub(crate) access_log_service: &'a Arc<AccessLogService>,
132    pub(crate) access_log_repository: &'a Arc<AccessLogRepository>,
133    pub(crate) bucket_service: &'a Arc<BucketService>,
134    pub(crate) bucket_repository: &'a Arc<BucketRepository>,
135    pub(crate) content_hash_service: &'a Arc<ContentHashService>,
136    pub(crate) content_hash_repository: &'a Arc<ContentHashRepository>,
137    pub(crate) conversion_job_service: &'a Arc<ConversionJobService>,
138    pub(crate) conversion_job_repository: &'a Arc<ConversionJobRepository>,
139    pub(crate) file_comment_service: &'a Arc<FileCommentService>,
140    pub(crate) file_comment_repository: &'a Arc<FileCommentRepository>,
141    pub(crate) file_lock_service: &'a Arc<FileLockService>,
142    pub(crate) file_lock_repository: &'a Arc<FileLockRepository>,
143    pub(crate) file_share_service: &'a Arc<FileShareService>,
144    pub(crate) file_share_repository: &'a Arc<FileShareRepository>,
145    pub(crate) file_version_service: &'a Arc<FileVersionService>,
146    pub(crate) file_version_repository: &'a Arc<FileVersionRepository>,
147    pub(crate) processing_job_service: &'a Arc<ProcessingJobService>,
148    pub(crate) processing_job_repository: &'a Arc<ProcessingJobRepository>,
149    pub(crate) stored_file_service: &'a Arc<StoredFileService>,
150    pub(crate) stored_file_repository: &'a Arc<StoredFileRepository>,
151    pub(crate) thumbnail_service: &'a Arc<ThumbnailService>,
152    pub(crate) thumbnail_repository: &'a Arc<ThumbnailRepository>,
153    pub(crate) upload_session_service: &'a Arc<UploadSessionService>,
154    pub(crate) upload_session_repository: &'a Arc<UploadSessionRepository>,
155    pub(crate) user_quota_service: &'a Arc<UserQuotaService>,
156    pub(crate) user_quota_repository: &'a Arc<UserQuotaRepository>,
157}
158
159impl BucketModule {
160    /// Create a new module builder
161    pub fn builder() -> BucketModuleBuilder {
162        BucketModuleBuilder::new()
163    }
164
165    /// Mount ALL generated CRUD endpoints (12 per entity) with NO domain
166    /// validation — the fully **unguarded** surface. A well-formed request can
167    /// create invalid rows or soft-delete a referenced master out from under its
168    /// dependents. Prefer a guarded composition (read + validated writes) for any
169    /// real deployment; use this only in trusted/admin/seeding contexts.
170    pub fn all_crud_routes(&self) -> Router {
171        use presentation::http::{
172            create_bucket_routes,
173            create_content_hash_routes,
174            create_conversion_job_routes,
175            create_file_comment_routes,
176            create_file_lock_routes,
177            create_file_share_routes,
178            create_processing_job_routes,
179            create_stored_file_routes,
180            create_upload_session_routes,
181            create_user_quota_routes,
182        };
183
184        Router::new()
185            .merge(create_bucket_routes(self.bucket_service.clone()))
186            .merge(create_content_hash_routes(self.content_hash_service.clone()))
187            .merge(create_conversion_job_routes(self.conversion_job_service.clone()))
188            .merge(create_file_comment_routes(self.file_comment_service.clone()))
189            .merge(create_file_lock_routes(self.file_lock_service.clone()))
190            .merge(create_file_share_routes(self.file_share_service.clone()))
191            .merge(create_processing_job_routes(self.processing_job_service.clone()))
192            .merge(create_stored_file_routes(self.stored_file_service.clone()))
193            .merge(create_upload_session_routes(self.upload_session_service.clone()))
194            .merge(create_user_quota_routes(self.user_quota_service.clone()))
195    }
196
197    /// Deprecated alias for [`Self::all_crud_routes`]. `routes()` reads like
198    /// "the routes" but mounts UNVALIDATED generic CRUD on every entity — a naive
199    /// mount exposes unguarded writes. Compose a guarded router (read + validated
200    /// writes) for production, or call `all_crud_routes()` to opt into the full
201    /// unguarded surface explicitly.
202    #[deprecated(note = "mounts unvalidated generic CRUD; prefer readonly_routes() + validated writes, or all_crud_routes() for the full/unguarded surface")]
203    pub fn routes(&self) -> Router {
204        self.all_crud_routes()
205    }
206
207    /// Read-only routes for every entity (GET endpoints only) — the safe base.
208    ///
209    /// Generic mutation can't reach here, so this surface cannot bypass a
210    /// validated write service's invariants. Use this as the production base and
211    /// merge validated write routes (or a write service's HTTP layer) onto it.
212    pub fn readonly_routes(&self) -> Router {
213        use presentation::http::{
214            create_bucket_read_routes,
215            create_content_hash_read_routes,
216            create_conversion_job_read_routes,
217            create_file_comment_read_routes,
218            create_file_lock_read_routes,
219            create_file_share_read_routes,
220            create_processing_job_read_routes,
221            create_stored_file_read_routes,
222            create_upload_session_read_routes,
223            create_user_quota_read_routes,
224        };
225
226        Router::new()
227            .merge(create_bucket_read_routes(self.bucket_service.clone()))
228            .merge(create_content_hash_read_routes(self.content_hash_service.clone()))
229            .merge(create_conversion_job_read_routes(self.conversion_job_service.clone()))
230            .merge(create_file_comment_read_routes(self.file_comment_service.clone()))
231            .merge(create_file_lock_read_routes(self.file_lock_service.clone()))
232            .merge(create_file_share_read_routes(self.file_share_service.clone()))
233            .merge(create_processing_job_read_routes(self.processing_job_service.clone()))
234            .merge(create_stored_file_read_routes(self.stored_file_service.clone()))
235            .merge(create_upload_session_read_routes(self.upload_session_service.clone()))
236            .merge(create_user_quota_read_routes(self.user_quota_service.clone()))
237    }
238
239}
240
241/// Builder for BucketModule
242pub struct BucketModuleBuilder {
243    db_pool: Option<PgPool>,
244    /// The builder's extension state (`lib.ext.rs`); its fields read through `Deref`.
245    ext: ModuleBuilderExt,
246}
247
248impl std::ops::Deref for BucketModuleBuilder {
249    type Target = ModuleBuilderExt;
250    fn deref(&self) -> &Self::Target {
251        &self.ext
252    }
253}
254
255impl std::ops::DerefMut for BucketModuleBuilder {
256    fn deref_mut(&mut self) -> &mut Self::Target {
257        &mut self.ext
258    }
259}
260
261impl BucketModuleBuilder {
262    /// Create a new builder
263    pub fn new() -> Self {
264        Self {
265            db_pool: None,
266            ext: Default::default(),
267        }
268    }
269
270    /// Set the database connection pool
271    pub fn with_database(mut self, pool: PgPool) -> Self {
272        self.db_pool = Some(pool);
273        self
274    }
275
276
277    /// Build the module with configured dependencies
278    pub fn build(self) -> anyhow::Result<BucketModule> {
279        let db_pool = self.db_pool
280            .ok_or_else(|| anyhow::anyhow!("Database pool not configured"))?;
281
282        // AccessLog service
283        let access_log_repository = Arc::new(AccessLogRepository::new(db_pool.clone()));
284        let access_log_service = Arc::new(AccessLogService::with_repository(access_log_repository.clone()));
285
286        // Bucket service
287        let bucket_repository = Arc::new(BucketRepository::new(db_pool.clone()));
288        let bucket_service = Arc::new(BucketService::with_repository(bucket_repository.clone()).with_guard(Arc::new(crate::application::service::BucketRowRules)));
289
290        // ContentHash service
291        let content_hash_repository = Arc::new(ContentHashRepository::new(db_pool.clone()));
292        let content_hash_service = Arc::new(ContentHashService::with_repository(content_hash_repository.clone()));
293
294        // ConversionJob service
295        let conversion_job_repository = Arc::new(ConversionJobRepository::new(db_pool.clone()));
296        let conversion_job_service = Arc::new(ConversionJobService::with_repository(conversion_job_repository.clone()).with_guard(Arc::new(crate::application::service::ConversionJobRowRules)));
297
298        // FileComment service
299        let file_comment_repository = Arc::new(FileCommentRepository::new(db_pool.clone()));
300        let file_comment_service = Arc::new(FileCommentService::with_repository(file_comment_repository.clone()).with_guard(Arc::new(crate::application::service::FileCommentRowRules)));
301
302        // FileLock service
303        let file_lock_repository = Arc::new(FileLockRepository::new(db_pool.clone()));
304        let file_lock_service = Arc::new(FileLockService::with_repository(file_lock_repository.clone()).with_guard(Arc::new(crate::application::service::FileLockRowRules)));
305
306        // FileShare service
307        let file_share_repository = Arc::new(FileShareRepository::new(db_pool.clone()));
308        let file_share_service = Arc::new(FileShareService::with_repository(file_share_repository.clone()).with_guard(Arc::new(crate::application::service::FileShareRowRules)));
309
310        // FileVersion service
311        let file_version_repository = Arc::new(FileVersionRepository::new(db_pool.clone()));
312        let file_version_service = Arc::new(FileVersionService::with_repository(file_version_repository.clone()));
313
314        // ProcessingJob service
315        let processing_job_repository = Arc::new(ProcessingJobRepository::new(db_pool.clone()));
316        let processing_job_service = Arc::new(ProcessingJobService::with_repository(processing_job_repository.clone()).with_guard(Arc::new(crate::application::service::ProcessingJobRowRules)));
317
318        // StoredFile service
319        let stored_file_repository = Arc::new(StoredFileRepository::new(db_pool.clone()));
320        let stored_file_service = Arc::new(StoredFileService::with_repository(stored_file_repository.clone()).with_guard(Arc::new(crate::application::service::StoredFileRowRules)));
321
322        // Thumbnail service
323        let thumbnail_repository = Arc::new(ThumbnailRepository::new(db_pool.clone()));
324        let thumbnail_service = Arc::new(ThumbnailService::with_repository(thumbnail_repository.clone()));
325
326        // UploadSession service
327        let upload_session_repository = Arc::new(UploadSessionRepository::new(db_pool.clone()));
328        let upload_session_service = Arc::new(UploadSessionService::with_repository(upload_session_repository.clone()).with_guard(Arc::new(crate::application::service::UploadSessionRowRules)));
329
330        // UserQuota service
331        let user_quota_repository = Arc::new(UserQuotaRepository::new(db_pool.clone()));
332        let user_quota_service = Arc::new(UserQuotaService::with_repository(user_quota_repository.clone()).with_guard(Arc::new(crate::application::service::UserQuotaRowRules)));
333
334
335        // The extension builds its own state from what the generated build made.
336        let ext = self.ext.build(&ModuleParts {
337            db_pool: &db_pool,
338            access_log_service: &access_log_service,
339            access_log_repository: &access_log_repository,
340            bucket_service: &bucket_service,
341            bucket_repository: &bucket_repository,
342            content_hash_service: &content_hash_service,
343            content_hash_repository: &content_hash_repository,
344            conversion_job_service: &conversion_job_service,
345            conversion_job_repository: &conversion_job_repository,
346            file_comment_service: &file_comment_service,
347            file_comment_repository: &file_comment_repository,
348            file_lock_service: &file_lock_service,
349            file_lock_repository: &file_lock_repository,
350            file_share_service: &file_share_service,
351            file_share_repository: &file_share_repository,
352            file_version_service: &file_version_service,
353            file_version_repository: &file_version_repository,
354            processing_job_service: &processing_job_service,
355            processing_job_repository: &processing_job_repository,
356            stored_file_service: &stored_file_service,
357            stored_file_repository: &stored_file_repository,
358            thumbnail_service: &thumbnail_service,
359            thumbnail_repository: &thumbnail_repository,
360            upload_session_service: &upload_session_service,
361            upload_session_repository: &upload_session_repository,
362            user_quota_service: &user_quota_service,
363            user_quota_repository: &user_quota_repository,
364        })?;
365
366        Ok(BucketModule {
367            access_log_service,
368            bucket_service,
369            content_hash_service,
370            conversion_job_service,
371            file_comment_service,
372            file_lock_service,
373            file_share_service,
374            file_version_service,
375            processing_job_service,
376            stored_file_service,
377            thumbnail_service,
378            upload_session_service,
379            user_quota_service,
380            ext,
381        })
382    }
383}
384
385impl Default for BucketModuleBuilder {
386    fn default() -> Self {
387        Self::new()
388    }
389}
390
391// This directory's extension (hand-written; ADR-0031).
392include!("mod.ext.rs");