junobuild_storage/
impls.rs1use 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 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}