junobuild_storage/
utils.rs

1use crate::constants::{
2    ASSET_ENCODING_NO_COMPRESSION, WELL_KNOWN_CUSTOM_DOMAINS, WELL_KNOWN_II_ALTERNATIVE_ORIGINS,
3};
4use crate::http::types::HeaderField;
5use crate::types::interface::AssetNoContent;
6use crate::types::state::FullPath;
7use crate::types::store::{Asset, AssetEncoding, AssetKey};
8use candid::Principal;
9use junobuild_collections::assert::stores::assert_permission;
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    rule: &'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) -> Vec<(&'a FullPath, &'a Asset)> {
36    let (regex_key, regex_description) = matcher_regex(matcher);
37
38    assets
39        .iter()
40        .filter_map(|(key, asset)| {
41            if filter_collection(collection.clone(), asset)
42                && filter_full_path(&regex_key, asset)
43                && filter_description(&regex_description, asset)
44                && filter_owner(*owner, asset)
45                && filter_timestamps(matcher, *asset)
46                && assert_permission(rule, asset.key.owner, caller, controllers)
47            {
48                Some((*key, *asset))
49            } else {
50                None
51            }
52        })
53        .collect()
54}
55
56fn filter_full_path(regex: &Option<Regex>, asset: &Asset) -> bool {
57    match regex {
58        None => true,
59        Some(re) => re.is_match(&asset.key.full_path),
60    }
61}
62
63fn filter_description(regex: &Option<Regex>, asset: &Asset) -> bool {
64    match regex {
65        None => true,
66        Some(re) => match &asset.key.description {
67            None => false,
68            Some(description) => re.is_match(description),
69        },
70    }
71}
72
73fn filter_collection(collection: CollectionKey, asset: &Asset) -> bool {
74    asset.key.collection == collection
75}
76
77fn filter_owner(filter_owner: Option<UserId>, asset: &Asset) -> bool {
78    match filter_owner {
79        None => true,
80        Some(filter_owner) => filter_owner == asset.key.owner,
81    }
82}
83
84pub fn filter_collection_values<'a>(
85    collection: CollectionKey,
86    assets: &'a [(&'a FullPath, &'a Asset)],
87) -> Vec<(&'a FullPath, &'a Asset)> {
88    assets
89        .iter()
90        .filter_map(|(key, asset)| {
91            if filter_collection(collection.clone(), asset) {
92                Some((*key, *asset))
93            } else {
94                None
95            }
96        })
97        .collect()
98}
99
100pub fn get_token_protected_asset(
101    asset: &Asset,
102    asset_token: &String,
103    token: Option<String>,
104) -> Option<Asset> {
105    match token {
106        None => None,
107        Some(token) => {
108            if &token == asset_token {
109                return Some(asset.clone());
110            }
111
112            None
113        }
114    }
115}
116
117pub fn should_include_asset_for_deletion(collection: &CollectionKey, asset_path: &String) -> bool {
118    let excluded_paths = [
119        WELL_KNOWN_CUSTOM_DOMAINS.to_string(),
120        WELL_KNOWN_II_ALTERNATIVE_ORIGINS.to_string(),
121    ];
122
123    collection != COLLECTION_ASSET_KEY || !excluded_paths.contains(asset_path)
124}
125
126pub fn map_content_type_headers(content_type: &str) -> Vec<HeaderField> {
127    vec![HeaderField(
128        "content-type".to_string(),
129        content_type.to_string(),
130    )]
131}
132
133pub fn map_content_encoding(content: &Blob) -> AssetEncoding {
134    let max_chunk_size = 1_900_000; // Max 1.9 MB per chunk
135    let chunks = content
136        .chunks(max_chunk_size)
137        .map(|chunk| chunk.to_vec())
138        .collect();
139
140    AssetEncoding::from(&chunks)
141}
142
143pub fn create_asset_with_content(
144    content: &str,
145    headers: &[HeaderField],
146    existing_asset: Option<Asset>,
147    key: AssetKey,
148) -> Asset {
149    let mut asset: Asset = Asset::prepare(key, headers.to_vec(), &existing_asset);
150
151    let encoding = map_content_encoding(&content.as_bytes().to_vec());
152
153    asset
154        .encodings
155        .insert(ASSET_ENCODING_NO_COMPRESSION.to_string(), encoding);
156
157    asset
158}