Skip to main content

backbone_bucket/
lib.ext.rs

1//! The module's extension (hand-written; ADR-0031): what the module adds beside its
2//! generated services. The generated module and builder carry `ModuleExt` and
3//! `ModuleBuilderExt` and dereference to them, so their fields read as `module.field`.
4
5#[allow(unused_imports)]
6use super::*;
7
8/// State the module adds; read through the generated module's `Deref`.
9pub struct ModuleExt {
10    // Custom business logic services
11    pub locking_service: Arc<LockingService>,
12    pub multipart_upload_service: Arc<MultipartUploadService>,
13    pub conversion_service: Arc<ConversionService>,
14    #[allow(deprecated)]
15    pub cdn_service: Arc<CdnService>,
16    pub video_thumbnail_service: Arc<VideoThumbnailService>,
17    pub document_preview_service: Arc<DocumentPreviewService>,
18    /// Storage backend (mode-B serving + uploads). None until `.with_storage()` is called.
19    pub storage: Option<Arc<dyn ObjectStorage>>,
20    /// Bucket-module config (storage + serving). None until `.with_config()` is called.
21    pub bucket_config: Option<Arc<BucketConfig>>,
22    /// High-level file operations service, wired when both `storage` and `bucket_config` are set.
23    pub file_service: Option<Arc<FileService>>,
24    /// Direct handle to the StoredFile repository — used by the mode-B
25    /// serving handler to look up files by `storage_key`.
26    pub stored_file_repository: Arc<infrastructure::persistence::StoredFileRepository>,
27}
28
29/// State the builder adds.
30pub struct ModuleBuilderExt {
31    // bucket-serving fields (see docs/serving.md)
32    storage: Option<Arc<dyn ObjectStorage>>,
33    bucket_config: Option<BucketConfig>,
34}
35
36impl Default for ModuleBuilderExt {
37    fn default() -> Self {
38        Self {
39            storage: None,
40            bucket_config: None,
41        }
42    }
43}
44
45impl ModuleBuilderExt {
46    /// Build the extension's state from what the generated build made.
47    #[allow(unused_variables, clippy::redundant_clone)]
48    pub(crate) fn build(self, parts: &ModuleParts<'_>) -> anyhow::Result<ModuleExt> {
49        let db_pool = parts.db_pool.clone();
50        let stored_file_repository = parts.stored_file_repository.clone();
51        let bucket_repository = parts.bucket_repository.clone();
52        let conversion_job_repository = parts.conversion_job_repository.clone();
53        let file_lock_repository = parts.file_lock_repository.clone();
54        let processing_job_repository = parts.processing_job_repository.clone();
55        let thumbnail_repository = parts.thumbnail_repository.clone();
56        let upload_session_repository = parts.upload_session_repository.clone();
57        let user_quota_repository = parts.user_quota_repository.clone();
58        // Custom business logic services
59        let locking_service = Arc::new(LockingService::new(
60            file_lock_repository, stored_file_repository.clone(),
61        ));
62        let multipart_upload_service = Arc::new(MultipartUploadService::new(
63            upload_session_repository, bucket_repository.clone(), user_quota_repository,
64        ));
65        let conversion_service = Arc::new(ConversionService::new(
66            conversion_job_repository, stored_file_repository.clone(),
67        ));
68        #[allow(deprecated)]
69        let cdn_service = Arc::new(CdnService::new(
70            stored_file_repository.clone(), bucket_repository,
71        ));
72        let video_thumbnail_service = Arc::new(VideoThumbnailService::new(
73            processing_job_repository.clone(), thumbnail_repository.clone(), stored_file_repository.clone(),
74        ));
75        let document_preview_service = Arc::new(DocumentPreviewService::new(
76            processing_job_repository, thumbnail_repository, stored_file_repository.clone(),
77        ));
78        // Bucket-serving wiring (see docs/serving.md)
79        let bucket_config = self.bucket_config.map(Arc::new);
80        let storage = self.storage;
81        let file_service = match (&storage, &bucket_config) {
82            (Some(s), Some(c)) => Some(Arc::new(FileService::new(
83                s.clone(),
84                stored_file_repository.clone(),
85                c.clone(),
86            ))),
87            _ => None,
88        };
89        Ok(ModuleExt {
90            locking_service,
91            multipart_upload_service,
92            conversion_service,
93            cdn_service,
94            video_thumbnail_service,
95            document_preview_service,
96            storage,
97            bucket_config,
98            file_service,
99            stored_file_repository,
100        })
101    }
102}
103
104impl crate::ThisModule {
105    /// What [`upload_router`] needs, built from this module's own services.
106    ///
107    /// `None` until the module has both a storage backend and a config (the
108    /// file service is wired only then). A host mounts uploads with
109    /// `upload_router::<I>(bucket.upload_context()?, config)` and never holds
110    /// the generic bucket service itself.
111    pub fn upload_context(&self) -> Option<UploadContext> {
112        Some(UploadContext {
113            file_service: self.file_service.clone()?,
114            multipart_service: self.multipart_upload_service.clone(),
115            bucket_service: self.bucket_service.clone(),
116            storage: self.storage.clone()?,
117        })
118    }
119}
120
121impl crate::ThisModuleBuilder {
122    // Bucket-serving builder methods (see docs/serving.md)
123    /// Attach the bucket-module runtime config (storage + serving).
124    pub fn with_config(mut self, config: BucketConfig) -> Self {
125        self.bucket_config = Some(config);
126        self
127    }
128    /// Attach the object-storage backend (`LocalStorage`, `S3Storage`, …).
129    pub fn with_storage(mut self, storage: Arc<dyn ObjectStorage>) -> Self {
130        self.storage = Some(storage);
131        self
132    }
133}