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