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