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