junobuild_storage/
utils.rs1use crate::constants::{
2 ASSET_ENCODING_NO_COMPRESSION, WELL_KNOWN_CUSTOM_DOMAINS, WELL_KNOWN_II_ALTERNATIVE_ORIGINS,
3};
4use crate::http::types::HeaderField;
5use crate::strategies::StorageAssertionsStrategy;
6use crate::types::interface::AssetNoContent;
7use crate::types::state::FullPath;
8use crate::types::store::{Asset, AssetEncoding, AssetKey};
9use candid::Principal;
10use junobuild_collections::constants::assets::COLLECTION_ASSET_KEY;
11use junobuild_collections::types::core::CollectionKey;
12use junobuild_collections::types::rules::Permission;
13use junobuild_shared::list::{filter_timestamps, matcher_regex};
14use junobuild_shared::types::core::Blob;
15use junobuild_shared::types::list::ListParams;
16use junobuild_shared::types::state::{Controllers, UserId};
17use regex::Regex;
18
19pub fn map_asset_no_content(asset: &Asset) -> (FullPath, AssetNoContent) {
20 (asset.key.full_path.clone(), AssetNoContent::from(asset))
21}
22
23pub fn filter_values<'a>(
24 caller: Principal,
25 controllers: &'a Controllers,
26 permission: &'a Permission,
27 collection: CollectionKey,
28 ListParams {
29 matcher,
30 order: _,
31 paginate: _,
32 owner,
33 }: &'a ListParams,
34 assets: &'a [(&'a FullPath, &'a Asset)],
35 assertions: &impl StorageAssertionsStrategy,
36) -> Result<Vec<(&'a FullPath, &'a Asset)>, String> {
37 let (regex_key, regex_description) = matcher_regex(matcher)?;
38
39 let result = assets
40 .iter()
41 .filter_map(|(key, asset)| {
42 if filter_collection(collection.clone(), asset)
43 && filter_full_path(®ex_key, asset)
44 && filter_description(®ex_description, asset)
45 && filter_owner(*owner, asset)
46 && filter_timestamps(matcher, *asset)
47 && assertions.assert_list_permission(
48 permission,
49 asset.key.owner,
50 caller,
51 &collection,
52 controllers,
53 )
54 {
55 Some((*key, *asset))
56 } else {
57 None
58 }
59 })
60 .collect();
61
62 Ok(result)
63}
64
65fn filter_full_path(regex: &Option<Regex>, asset: &Asset) -> bool {
66 match regex {
67 None => true,
68 Some(re) => re.is_match(&asset.key.full_path),
69 }
70}
71
72fn filter_description(regex: &Option<Regex>, asset: &Asset) -> bool {
73 match regex {
74 None => true,
75 Some(re) => match &asset.key.description {
76 None => false,
77 Some(description) => re.is_match(description),
78 },
79 }
80}
81
82fn filter_collection(collection: CollectionKey, asset: &Asset) -> bool {
83 asset.key.collection == collection
84}
85
86fn filter_owner(filter_owner: Option<UserId>, asset: &Asset) -> bool {
87 match filter_owner {
88 None => true,
89 Some(filter_owner) => filter_owner == asset.key.owner,
90 }
91}
92
93pub fn filter_collection_values<'a>(
94 collection: CollectionKey,
95 assets: &'a [(&'a FullPath, &'a Asset)],
96) -> Vec<(&'a FullPath, &'a Asset)> {
97 assets
98 .iter()
99 .filter_map(|(key, asset)| {
100 if filter_collection(collection.clone(), asset) {
101 Some((*key, *asset))
102 } else {
103 None
104 }
105 })
106 .collect()
107}
108
109pub fn get_token_protected_asset(
110 asset: &Asset,
111 asset_token: &String,
112 token: Option<String>,
113) -> Option<Asset> {
114 match token {
115 None => None,
116 Some(token) => {
117 if &token == asset_token {
118 return Some(asset.clone());
119 }
120
121 None
122 }
123 }
124}
125
126pub fn should_include_asset_for_deletion(collection: &CollectionKey, asset_path: &String) -> bool {
127 let excluded_paths = [
128 WELL_KNOWN_CUSTOM_DOMAINS.to_string(),
129 WELL_KNOWN_II_ALTERNATIVE_ORIGINS.to_string(),
130 ];
131
132 collection != COLLECTION_ASSET_KEY || !excluded_paths.contains(asset_path)
133}
134
135pub fn map_content_type_headers(content_type: &str) -> Vec<HeaderField> {
136 vec![HeaderField(
137 "content-type".to_string(),
138 content_type.to_string(),
139 )]
140}
141
142pub fn map_content_encoding(content: &Blob) -> AssetEncoding {
143 let max_chunk_size = 1_900_000; let chunks = content
145 .chunks(max_chunk_size)
146 .map(|chunk| chunk.to_vec())
147 .collect();
148
149 AssetEncoding::from(&chunks)
150}
151
152pub fn create_asset_with_content(
153 content: &str,
154 headers: &[HeaderField],
155 existing_asset: Option<Asset>,
156 key: AssetKey,
157) -> Asset {
158 let mut asset: Asset = Asset::prepare(key, headers.to_vec(), &existing_asset);
159
160 let encoding = map_content_encoding(&content.as_bytes().to_vec());
161
162 asset
163 .encodings
164 .insert(ASSET_ENCODING_NO_COMPRESSION.to_string(), encoding);
165
166 asset
167}
168
169pub fn clone_asset_encoding_content_chunks(encoding: &AssetEncoding, chunk_index: usize) -> Blob {
170 encoding.content_chunks[chunk_index].clone()
171}