use crate::access_keys::store::get_access_keys;
use crate::assets::storage::assert::{
assert_create_batch, assert_delete_asset, assert_get_asset, assert_list_assets,
assert_set_config, assert_write_asset,
};
use crate::assets::storage::state::{
count_assets_stable, delete_asset as delete_state_asset, delete_domain as delete_state_domain,
get_asset as get_state_asset, get_assets_stable, get_config as get_state_config, get_config,
get_content_chunks as get_state_content_chunks, get_domain as get_state_domain,
get_domains as get_state_domains, get_public_asset as get_state_public_asset,
get_rule as get_state_rule, insert_asset, insert_config as insert_state_config,
insert_domain as insert_state_domain,
};
use crate::assets::storage::strategy_impls::{StorageAssertions, StorageState, StorageUpload};
use crate::auth::store::get_config as get_auth_config;
use crate::certification::strategy_impls::StorageCertificate;
use crate::memory::state::STATE;
use crate::types::store::{AssertContext, StoreContext};
use candid::Principal;
use junobuild_collections::msg::msg_storage_collection_not_empty;
use junobuild_collections::types::core::CollectionKey;
use junobuild_collections::types::rules::{Memory, Rule};
use junobuild_shared::data::list::list_values;
use junobuild_shared::types::core::{Blob, DomainName};
use junobuild_shared::types::domain::CustomDomains;
use junobuild_shared::types::list::{ListParams, ListResults};
use junobuild_shared::types::state::AccessKeys;
use junobuild_storage::constants::{ROOT_404_HTML, ROOT_INDEX_HTML};
use junobuild_storage::errors::JUNO_STORAGE_ERROR_ASSET_NOT_FOUND;
use junobuild_storage::heap_utils::{
collect_assets_heap, collect_delete_assets_heap, count_assets_heap,
};
use junobuild_storage::runtime::{
delete_certified_asset as delete_runtime_certified_asset,
update_certified_asset as update_runtime_certified_asset,
};
use junobuild_storage::store::{commit_batch as commit_batch_storage, create_batch, create_chunk};
use junobuild_storage::strategies::StorageAssertionsStrategy;
use junobuild_storage::types::config::StorageConfig;
use junobuild_storage::types::interface::{
AssetNoContent, CommitBatch, InitAssetKey, SetStorageConfig, UploadChunk,
};
use junobuild_storage::types::runtime_state::{BatchId, ChunkId};
use junobuild_storage::types::state::{AssetAccessToken, FullPath};
use junobuild_storage::types::store::{Asset, AssetEncoding};
use junobuild_storage::utils::{
filter_collection_values, filter_values, get_token_protected_asset, map_asset_no_content,
should_include_asset_for_deletion,
};
use junobuild_storage::well_known::update::update_custom_domains_asset;
use junobuild_storage::well_known::utils::build_custom_domain;
pub fn get_content_chunks_store(
encoding: &AssetEncoding,
chunk_index: usize,
memory: &Memory,
) -> Option<Blob> {
get_state_content_chunks(encoding, chunk_index, memory)
}
pub fn delete_asset_store(
caller: Principal,
collection: &CollectionKey,
full_path: FullPath,
) -> Result<Option<Asset>, String> {
let controllers: AccessKeys = get_access_keys();
let config = get_config_store();
let context = StoreContext {
caller,
controllers: &controllers,
collection,
};
secure_delete_asset_impl(&context, full_path, &config)
}
pub fn delete_assets_store(collection: &CollectionKey) -> Result<(), String> {
let rule = get_state_rule(collection)?;
let full_paths = match rule.mem() {
Memory::Heap => STATE.with(|state| {
let state_ref = state.borrow();
collect_delete_assets_heap(collection, &state_ref.heap.storage.assets)
}),
Memory::Stable => STATE.with(|state| {
let stable = get_assets_stable(collection, &state.borrow().stable.assets);
stable
.iter()
.filter(|(_, asset)| {
should_include_asset_for_deletion(collection, &asset.key.full_path)
})
.map(|(_, asset)| asset.key.full_path.clone())
.collect()
}),
};
delete_assets_impl(&full_paths, collection, &rule)
}
pub fn list_assets_store(
caller: Principal,
collection: &CollectionKey,
filters: &ListParams,
) -> Result<ListResults<AssetNoContent>, String> {
let controllers: AccessKeys = get_access_keys();
let context = StoreContext {
caller,
controllers: &controllers,
collection,
};
secure_list_assets_impl(&context, filters)
}
pub fn count_assets_store(
caller: Principal,
collection: &CollectionKey,
filters: &ListParams,
) -> Result<usize, String> {
let results = list_assets_store(caller, collection, filters)?;
Ok(results.items_length)
}
pub fn get_asset_store(
caller: Principal,
collection: &CollectionKey,
full_path: FullPath,
) -> Result<Option<Asset>, String> {
let controllers: AccessKeys = get_access_keys();
let context = StoreContext {
caller,
controllers: &controllers,
collection,
};
secure_get_asset_impl(&context, full_path)
}
fn secure_get_asset_impl(
context: &StoreContext,
full_path: FullPath,
) -> Result<Option<Asset>, String> {
let rule = get_state_rule(context.collection)?;
let auth_config = get_auth_config();
let assert_context = AssertContext {
rule: &rule,
auth_config: &auth_config,
};
get_asset_impl(context, &assert_context, full_path)
}
fn get_asset_impl(
context: &StoreContext,
assert_context: &AssertContext,
full_path: FullPath,
) -> Result<Option<Asset>, String> {
let asset = get_state_asset(context.collection, &full_path, assert_context.rule);
match asset {
None => Ok(None),
Some(asset) => {
if assert_get_asset(context, assert_context, &asset).is_err() {
return Ok(None);
}
Ok(Some(asset))
}
}
}
pub fn get_public_asset_store(
full_path: FullPath,
token: AssetAccessToken,
) -> Option<(Asset, Memory)> {
let (asset, memory) = get_state_public_asset(&full_path);
match asset {
None => None,
Some(asset) => match &asset.key.token {
None => Some((asset.clone(), memory)),
Some(asset_token) => {
let protected_asset = get_token_protected_asset(&asset, asset_token, token);
protected_asset.map(|protected_asset| (protected_asset, memory))
}
},
}
}
pub fn assert_assets_collection_empty_store(collection: &CollectionKey) -> Result<(), String> {
let rule = get_state_rule(collection)?;
match rule.mem() {
Memory::Heap => STATE.with(|state| {
let state_ref = state.borrow();
let assets = collect_assets_heap(collection, &state_ref.heap.storage.assets);
assert_assets_collection_empty_impl(&assets, collection)
}),
Memory::Stable => STATE.with(|state| {
let stable = get_assets_stable(collection, &state.borrow().stable.assets);
let assets: Vec<(&FullPath, &Asset)> = stable
.iter()
.map(|(_, asset)| (&asset.key.full_path, asset))
.collect();
assert_assets_collection_empty_impl(&assets, collection)
}),
}
}
fn assert_assets_collection_empty_impl(
assets: &[(&FullPath, &Asset)],
collection: &CollectionKey,
) -> Result<(), String> {
let values = filter_collection_values(collection.clone(), assets);
if !values.is_empty() {
return Err(msg_storage_collection_not_empty(collection));
}
Ok(())
}
fn secure_list_assets_impl(
context: &StoreContext,
filters: &ListParams,
) -> Result<ListResults<AssetNoContent>, String> {
let rule = get_state_rule(context.collection)?;
let auth_config = get_auth_config();
let assert_context = AssertContext {
rule: &rule,
auth_config: &auth_config,
};
assert_list_assets(context, &assert_context)?;
match rule.mem() {
Memory::Heap => STATE.with(|state| {
let state_ref = state.borrow();
let assets = collect_assets_heap(context.collection, &state_ref.heap.storage.assets);
list_assets_impl(&assets, context, &rule, filters, &StorageAssertions)
}),
Memory::Stable => STATE.with(|state| {
let stable = get_assets_stable(context.collection, &state.borrow().stable.assets);
let assets: Vec<(&FullPath, &Asset)> = stable
.iter()
.map(|(_, asset)| (&asset.key.full_path, asset))
.collect();
list_assets_impl(&assets, context, &rule, filters, &StorageAssertions)
}),
}
}
fn list_assets_impl(
assets: &[(&FullPath, &Asset)],
&StoreContext {
caller,
controllers,
collection,
}: &StoreContext,
rule: &Rule,
filters: &ListParams,
assertions: &impl StorageAssertionsStrategy,
) -> Result<ListResults<AssetNoContent>, String> {
let matches = filter_values(
caller,
controllers,
&rule.read,
collection.clone(),
filters,
assets,
assertions,
)?;
let values = list_values(&matches, filters);
let result = ListResults::<AssetNoContent> {
items: values
.items
.into_iter()
.map(|(_, asset)| map_asset_no_content(&asset))
.collect(),
items_length: values.items_length,
items_page: values.items_page,
matches_length: values.matches_length,
matches_pages: values.matches_pages,
};
Ok(result)
}
fn secure_delete_asset_impl(
context: &StoreContext,
full_path: FullPath,
config: &StorageConfig,
) -> Result<Option<Asset>, String> {
let rule = get_state_rule(context.collection)?;
let auth_config = get_auth_config();
let assert_context = AssertContext {
rule: &rule,
auth_config: &auth_config,
};
delete_asset_impl(context, &assert_context, full_path, config)
}
fn delete_asset_impl(
context: &StoreContext,
assert_context: &AssertContext,
full_path: FullPath,
config: &StorageConfig,
) -> Result<Option<Asset>, String> {
let asset = get_state_asset(context.collection, &full_path, assert_context.rule);
match asset {
None => Err(JUNO_STORAGE_ERROR_ASSET_NOT_FOUND.to_string()),
Some(asset) => {
assert_write_asset(context, assert_context, &asset)?;
assert_delete_asset(context, &asset)?;
let certificate = &StorageCertificate;
let deleted = delete_state_asset(context.collection, &full_path, assert_context.rule);
delete_runtime_certified_asset(&asset, certificate);
if *full_path == *ROOT_404_HTML {
if let Some(index_asset) = get_state_asset(
context.collection,
&ROOT_INDEX_HTML.to_string(),
assert_context.rule,
) {
update_runtime_certified_asset(&index_asset, config, certificate);
}
}
Ok(deleted)
}
}
}
fn delete_assets_impl(
full_paths: &Vec<FullPath>,
collection: &CollectionKey,
rule: &Rule,
) -> Result<(), String> {
for full_path in full_paths {
let deleted_asset = delete_state_asset(collection, full_path, rule);
match deleted_asset {
None => {}
Some(deleted_asset) => {
delete_runtime_certified_asset(&deleted_asset, &StorageCertificate);
}
}
}
Ok(())
}
pub fn count_collection_assets_store(collection: &CollectionKey) -> Result<usize, String> {
let rule = get_state_rule(collection)?;
match rule.mem() {
Memory::Heap => STATE.with(|state| {
let state_ref = state.borrow();
let length = count_assets_heap(collection, &state_ref.heap.storage.assets);
Ok(length)
}),
Memory::Stable => STATE.with(|state| {
let length = count_assets_stable(collection, &state.borrow().stable.assets);
Ok(length)
}),
}
}
pub fn delete_filtered_assets_store(
caller: Principal,
collection: CollectionKey,
filters: &ListParams,
) -> Result<Vec<Option<Asset>>, String> {
let controllers: AccessKeys = get_access_keys();
let context = StoreContext {
caller,
controllers: &controllers,
collection: &collection,
};
let assets = secure_list_assets_impl(&context, filters)?;
delete_filtered_assets_store_impl(&context, &assets)
}
fn delete_filtered_assets_store_impl(
context: &StoreContext,
assets: &ListResults<AssetNoContent>,
) -> Result<Vec<Option<Asset>>, String> {
let rule = get_state_rule(context.collection)?;
let auth_config = get_auth_config();
let assert_context = AssertContext {
rule: &rule,
auth_config: &auth_config,
};
let config = get_config_store();
let mut results: Vec<Option<Asset>> = Vec::new();
for (_, asset) in &assets.items {
let deleted_asset = delete_asset_impl(
context,
&assert_context,
asset.key.full_path.clone(),
&config,
)?;
results.push(deleted_asset);
}
Ok(results)
}
pub fn set_asset_token_store(
caller: Principal,
collection: &CollectionKey,
full_path: &FullPath,
token: &AssetAccessToken,
) -> Result<(), String> {
let controllers: AccessKeys = get_access_keys();
let config = get_config_store();
let context = StoreContext {
caller,
controllers: &controllers,
collection,
};
secure_set_asset_token_impl(&context, full_path, token, &config)
}
fn secure_set_asset_token_impl(
context: &StoreContext,
full_path: &FullPath,
token: &AssetAccessToken,
config: &StorageConfig,
) -> Result<(), String> {
let rule = get_state_rule(context.collection)?;
let auth_config = get_auth_config();
let assert_context = AssertContext {
rule: &rule,
auth_config: &auth_config,
};
set_asset_token_impl(context, &assert_context, full_path, token, config)
}
fn set_asset_token_impl(
context: &StoreContext,
assert_context: &AssertContext,
full_path: &FullPath,
token: &AssetAccessToken,
config: &StorageConfig,
) -> Result<(), String> {
let asset = get_state_asset(context.collection, full_path, assert_context.rule)
.ok_or(JUNO_STORAGE_ERROR_ASSET_NOT_FOUND.to_string())?;
assert_write_asset(context, assert_context, &asset)?;
let updated_asset = Asset::update_token(&asset, token);
insert_asset(
context.collection,
full_path,
&updated_asset,
assert_context.rule,
);
if asset.key.token.is_some() ^ token.is_some() {
update_runtime_certified_asset(&updated_asset, config, &StorageCertificate);
}
Ok(())
}
pub fn create_batch_store(caller: Principal, init: InitAssetKey) -> Result<BatchId, String> {
let controllers: AccessKeys = get_access_keys();
let config = get_config();
secure_create_batch_impl(caller, &controllers, &config, init)
}
pub fn create_chunk_store(caller: Principal, chunk: UploadChunk) -> Result<ChunkId, String> {
let config = get_config();
create_chunk(caller, &config, chunk)
}
pub fn commit_batch_store(caller: Principal, commit_batch: CommitBatch) -> Result<Asset, String> {
let controllers: AccessKeys = get_access_keys();
let config = get_config();
let asset = commit_batch_storage(
caller,
&controllers,
&config,
commit_batch,
&StorageAssertions,
&StorageState,
&StorageUpload,
)?;
let config = get_config();
update_runtime_certified_asset(&asset, &config, &StorageCertificate);
Ok(asset)
}
fn secure_create_batch_impl(
caller: Principal,
controllers: &AccessKeys,
config: &StorageConfig,
init: InitAssetKey,
) -> Result<BatchId, String> {
let rule = get_state_rule(&init.collection)?;
let auth_config = get_auth_config();
let assert_context = AssertContext {
rule: &rule,
auth_config: &auth_config,
};
assert_create_batch(caller, controllers, &assert_context)?;
create_batch(
caller,
controllers,
config,
init,
None,
&StorageAssertions,
&StorageState,
)
}
pub fn set_config_store(proposed_config: &SetStorageConfig) -> Result<StorageConfig, String> {
let current_config = get_config();
assert_set_config(proposed_config, ¤t_config)?;
let config = StorageConfig::prepare(¤t_config, proposed_config);
insert_state_config(&config);
Ok(config)
}
pub fn get_config_store() -> StorageConfig {
get_state_config()
}
pub fn set_domain_store(domain_name: &DomainName, bn_id: &Option<String>) -> Result<(), String> {
set_domain_impl(domain_name, bn_id)
}
pub fn delete_domain_store(domain_name: &DomainName) -> Result<(), String> {
delete_domain_impl(domain_name)
}
pub fn get_custom_domains_store() -> CustomDomains {
get_state_domains()
}
fn delete_domain_impl(domain_name: &DomainName) -> Result<(), String> {
delete_state_domain(domain_name);
update_custom_domains_asset(&StorageState, &StorageCertificate)
}
fn set_domain_impl(domain_name: &DomainName, bn_id: &Option<String>) -> Result<(), String> {
set_state_domain_impl(domain_name, bn_id);
update_custom_domains_asset(&StorageState, &StorageCertificate)
}
fn set_state_domain_impl(domain_name: &DomainName, bn_id: &Option<String>) {
let domain = get_state_domain(domain_name);
let custom_domain = build_custom_domain(domain, bn_id);
insert_state_domain(domain_name, &custom_domain);
}