junobuild_storage/
impls.rs

1use crate::http::types::HeaderField;
2use crate::types::config::{
3    StorageConfig, StorageConfigHeaders, StorageConfigIFrame, StorageConfigRawAccess,
4    StorageConfigRedirects, StorageConfigRewrites,
5};
6use crate::types::interface::{AssetEncodingNoContent, AssetNoContent, SetStorageConfig};
7use crate::types::state::{AssetAccessToken, StorageHeapState};
8use crate::types::store::{Asset, AssetEncoding, AssetKey, Batch, BatchExpiry};
9use ic_cdk::api::time;
10use ic_stable_structures::storable::Bound;
11use ic_stable_structures::Storable;
12use junobuild_collections::constants::assets::DEFAULT_ASSETS_COLLECTIONS;
13use junobuild_collections::types::interface::SetRule;
14use junobuild_collections::types::rules::{Memory, Rule, Rules};
15use junobuild_shared::data::version::{next_version, next_version_from};
16use junobuild_shared::memory::serializers::{
17    deserialize_from_bytes, serialize_into_bytes, serialize_to_bytes,
18};
19use junobuild_shared::types::core::{Blob, Hash, Hashable};
20use junobuild_shared::types::state::Timestamped;
21use junobuild_shared::types::state::{Timestamp, Version, Versioned};
22use sha2::{Digest, Sha256};
23use std::borrow::Cow;
24use std::cmp::Ordering;
25use std::collections::HashMap;
26
27impl Default for StorageHeapState {
28    fn default() -> Self {
29        Self::new_with_storage_collections(Vec::from(DEFAULT_ASSETS_COLLECTIONS))
30    }
31}
32
33impl StorageHeapState {
34    pub fn new_with_storage_collections(storage_collections: Vec<(&str, SetRule)>) -> Self {
35        let now = time();
36
37        StorageHeapState {
38            assets: HashMap::new(),
39            rules: storage_collections
40                .into_iter()
41                .map(|(collection, rule)| {
42                    (
43                        collection.to_owned(),
44                        Rule {
45                            read: rule.read,
46                            write: rule.write,
47                            memory: Some(rule.memory.unwrap_or(Memory::Heap)),
48                            mutable_permissions: Some(rule.mutable_permissions.unwrap_or(false)),
49                            max_size: rule.max_size,
50                            max_capacity: rule.max_capacity,
51                            max_changes_per_user: rule.max_changes_per_user,
52                            created_at: now,
53                            updated_at: now,
54                            version: rule.version,
55                            rate_config: rule.rate_config,
56                        },
57                    )
58                })
59                .collect::<Rules>(),
60            config: StorageConfig {
61                headers: StorageConfigHeaders::default(),
62                rewrites: StorageConfigRewrites::default(),
63                redirects: Some(StorageConfigRedirects::default()),
64                iframe: None,
65                raw_access: None,
66                created_at: Some(now),
67                updated_at: Some(now),
68                // For backwards compatibility start with None
69                version: None,
70                max_memory_size: None,
71            },
72            custom_domains: HashMap::new(),
73        }
74    }
75}
76
77impl From<&Vec<Blob>> for AssetEncoding {
78    fn from(content_chunks: &Vec<Blob>) -> Self {
79        let mut total_length: u128 = 0;
80        let mut hasher = Sha256::new();
81
82        // Calculate sha256 and total length
83        for chunk in content_chunks.iter() {
84            total_length += u128::try_from(chunk.len()).unwrap();
85
86            hasher.update(chunk);
87        }
88
89        let sha256 = hasher.finalize().into();
90
91        AssetEncoding {
92            modified: time(),
93            content_chunks: content_chunks.clone(),
94            total_length,
95            sha256,
96        }
97    }
98}
99
100impl StorageConfig {
101    pub fn unwrap_redirects(&self) -> StorageConfigRedirects {
102        self.redirects.clone().unwrap_or_default()
103    }
104
105    pub fn unwrap_iframe(&self) -> StorageConfigIFrame {
106        self.iframe.clone().unwrap_or(StorageConfigIFrame::Deny)
107    }
108
109    pub fn unwrap_raw_access(&self) -> StorageConfigRawAccess {
110        self.raw_access
111            .clone()
112            .unwrap_or(StorageConfigRawAccess::Deny)
113    }
114}
115
116impl Timestamped for AssetNoContent {
117    fn created_at(&self) -> Timestamp {
118        self.created_at
119    }
120
121    fn updated_at(&self) -> Timestamp {
122        self.updated_at
123    }
124
125    fn cmp_updated_at(&self, other: &Self) -> Ordering {
126        self.updated_at.cmp(&other.updated_at)
127    }
128
129    fn cmp_created_at(&self, other: &Self) -> Ordering {
130        self.created_at.cmp(&other.created_at)
131    }
132}
133
134impl From<&Asset> for AssetNoContent {
135    fn from(asset: &Asset) -> Self {
136        AssetNoContent {
137            key: asset.key.clone(),
138            headers: asset.headers.clone(),
139            encodings: asset
140                .encodings
141                .clone()
142                .into_iter()
143                .map(|(key, encoding)| {
144                    (
145                        key,
146                        AssetEncodingNoContent {
147                            modified: encoding.modified,
148                            total_length: encoding.total_length,
149                            sha256: encoding.sha256,
150                        },
151                    )
152                })
153                .collect(),
154            created_at: asset.created_at,
155            updated_at: asset.updated_at,
156            version: asset.version,
157        }
158    }
159}
160
161impl Storable for Asset {
162    fn to_bytes(&self) -> Cow<'_, [u8]> {
163        serialize_to_bytes(self)
164    }
165
166    fn into_bytes(self) -> Vec<u8> {
167        serialize_into_bytes(&self)
168    }
169
170    fn from_bytes(bytes: Cow<[u8]>) -> Self {
171        deserialize_from_bytes(bytes)
172    }
173
174    const BOUND: Bound = Bound::Unbounded;
175}
176
177impl Timestamped for Asset {
178    fn created_at(&self) -> Timestamp {
179        self.created_at
180    }
181
182    fn updated_at(&self) -> Timestamp {
183        self.updated_at
184    }
185
186    fn cmp_updated_at(&self, other: &Self) -> Ordering {
187        self.updated_at.cmp(&other.updated_at)
188    }
189
190    fn cmp_created_at(&self, other: &Self) -> Ordering {
191        self.created_at.cmp(&other.created_at)
192    }
193}
194
195impl Asset {
196    pub fn prepare(
197        key: AssetKey,
198        headers: Vec<HeaderField>,
199        existing_asset: &Option<Asset>,
200    ) -> Self {
201        let now = time();
202
203        let created_at: Timestamp = match existing_asset {
204            None => now,
205            Some(current_doc) => current_doc.created_at,
206        };
207
208        let version = next_version(existing_asset);
209
210        let encodings = match existing_asset {
211            None => HashMap::new(),
212            Some(existing_asset) => existing_asset.encodings.clone(),
213        };
214
215        let updated_at: Timestamp = now;
216
217        Asset {
218            key,
219            headers,
220            encodings,
221            created_at,
222            updated_at,
223            version: Some(version),
224        }
225    }
226
227    pub fn update_token(current_asset: &Asset, token: &AssetAccessToken) -> Self {
228        let now = time();
229
230        let version = next_version(&Some(current_asset));
231
232        Self {
233            key: AssetKey {
234                token: token.clone(),
235                ..current_asset.key.clone()
236            },
237            updated_at: now,
238            version: Some(version),
239            ..current_asset.clone()
240        }
241    }
242}
243
244impl Versioned for Asset {
245    fn version(&self) -> Option<Version> {
246        self.version
247    }
248}
249
250impl Versioned for &Asset {
251    fn version(&self) -> Option<Version> {
252        self.version
253    }
254}
255
256impl BatchExpiry for Batch {
257    fn expires_at(&self) -> Timestamp {
258        self.expires_at
259    }
260}
261
262impl Hashable for AssetKey {
263    fn hash(&self) -> Hash {
264        let mut hasher = Sha256::new();
265        hasher.update(self.name.as_bytes());
266        hasher.update(self.full_path.as_bytes());
267        if let Some(token) = &self.token {
268            hasher.update(token.as_bytes());
269        }
270        hasher.update(self.collection.as_bytes());
271        hasher.update(serialize_to_bytes(&self.owner));
272        if let Some(description) = &self.description {
273            hasher.update(description.as_bytes());
274        }
275        hasher.finalize().into()
276    }
277}
278
279impl Hashable for Asset {
280    fn hash(&self) -> Hash {
281        let mut hasher = Sha256::new();
282        hasher.update(self.key.hash());
283        for HeaderField(ref key, ref value) in &self.headers {
284            hasher.update(key.as_bytes());
285            hasher.update(value.as_bytes());
286        }
287        hasher.update(self.created_at.to_le_bytes());
288        hasher.update(self.updated_at.to_le_bytes());
289        if let Some(version) = self.version {
290            hasher.update(version.to_le_bytes());
291        }
292        hasher.finalize().into()
293    }
294}
295
296impl Hashable for AssetEncoding {
297    fn hash(&self) -> Hash {
298        let mut hasher = Sha256::new();
299        hasher.update(self.modified.to_le_bytes());
300        hasher.update(self.total_length.to_le_bytes());
301        hasher.update(self.sha256);
302        hasher.finalize().into()
303    }
304}
305
306impl StorageConfig {
307    pub fn prepare(current_config: &StorageConfig, user_config: &SetStorageConfig) -> Self {
308        let now = time();
309
310        let created_at: Timestamp = current_config.created_at.unwrap_or(now);
311
312        let version = next_version_from(current_config);
313
314        let updated_at: Timestamp = now;
315
316        StorageConfig {
317            headers: user_config.headers.clone(),
318            rewrites: user_config.rewrites.clone(),
319            redirects: user_config.redirects.clone(),
320            iframe: user_config.iframe.clone(),
321            raw_access: user_config.raw_access.clone(),
322            max_memory_size: user_config.max_memory_size.clone(),
323            created_at: Some(created_at),
324            updated_at: Some(updated_at),
325            version: Some(version),
326        }
327    }
328}
329
330impl Versioned for StorageConfig {
331    fn version(&self) -> Option<Version> {
332        self.version
333    }
334}