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 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 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}