use crate::access_keys::store::get_access_keys;
use crate::auth::store::get_config as get_auth_config;
use crate::db::assert::{
assert_delete_doc, assert_get_doc, assert_get_docs, assert_set_config, assert_set_doc,
};
use crate::db::state::{
count_docs_heap, count_docs_stable, delete_collection as delete_state_collection,
delete_doc as delete_state_doc, get_config, get_doc as get_state_doc, get_docs_heap,
get_docs_stable, get_rule as get_state_rule, init_collection as init_state_collection,
insert_config, insert_doc as insert_state_doc,
is_collection_empty as is_state_collection_empty,
};
use crate::db::types::config::DbConfig;
use crate::db::types::interface::{DelDoc, SetDbConfig, SetDoc};
use crate::db::types::state::{Doc, DocContext, DocUpsert};
use crate::db::types::store::AssertSetDocOptions;
use crate::db::utils::filter_values;
use crate::memory::state::STATE;
use crate::types::store::{AssertContext, StoreContext};
use candid::Principal;
use junobuild_collections::msg::msg_db_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::Key;
use junobuild_shared::types::list::{ListParams, ListResults};
use junobuild_shared::types::state::{AccessKeys, UserId};
pub fn init_collection_store(collection: &CollectionKey, memory: &Memory) {
init_state_collection(collection, memory);
}
pub fn delete_collection_store(collection: &CollectionKey) -> Result<(), String> {
secure_delete_collection(collection)
}
fn secure_delete_collection(collection: &CollectionKey) -> Result<(), String> {
let rule = get_state_rule(collection)?;
delete_collection_impl(collection, &rule.memory)
}
fn delete_collection_impl(
collection: &CollectionKey,
memory: &Option<Memory>,
) -> Result<(), String> {
let empty = is_state_collection_empty(collection, memory)?;
if !empty {
return Err(msg_db_collection_not_empty(collection));
}
delete_state_collection(collection, memory)?;
Ok(())
}
pub fn get_doc_store(
caller: UserId,
collection: CollectionKey,
key: Key,
) -> Result<Option<Doc>, String> {
let controllers: AccessKeys = get_access_keys();
let context = StoreContext {
caller,
controllers: &controllers,
collection: &collection,
};
secure_get_doc(&context, key)
}
fn secure_get_doc(context: &StoreContext, key: Key) -> Result<Option<Doc>, 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_doc_impl(context, &assert_context, key)
}
fn get_doc_impl(
context: &StoreContext,
assert_context: &AssertContext,
key: Key,
) -> Result<Option<Doc>, String> {
let value = get_state_doc(context.collection, &key, assert_context.rule)?;
match value {
None => Ok(None),
Some(value) => {
if assert_get_doc(context, assert_context, &value).is_err() {
return Ok(None);
}
Ok(Some(value))
}
}
}
pub fn set_doc_store(
caller: UserId,
collection: CollectionKey,
key: Key,
value: SetDoc,
) -> Result<DocContext<DocUpsert>, String> {
let controllers: AccessKeys = get_access_keys();
let config = get_config();
let context = StoreContext {
caller,
controllers: &controllers,
collection: &collection,
};
let assert_options = AssertSetDocOptions {
with_assert_rate: true,
};
let data = secure_set_doc(&context, &config, &assert_options, key.clone(), value)?;
Ok(DocContext {
key,
collection,
data,
})
}
pub fn internal_set_doc_store(
caller: UserId,
collection: CollectionKey,
key: Key,
value: SetDoc,
assert_options: &AssertSetDocOptions,
) -> Result<DocContext<DocUpsert>, String> {
let controllers: AccessKeys = get_access_keys();
let config = get_config();
let context = StoreContext {
caller,
controllers: &controllers,
collection: &collection,
};
let data = secure_set_doc(&context, &config, assert_options, key.clone(), value)?;
Ok(DocContext {
key,
collection,
data,
})
}
fn secure_set_doc(
context: &StoreContext,
config: &Option<DbConfig>,
assert_options: &AssertSetDocOptions,
key: Key,
value: SetDoc,
) -> Result<DocUpsert, 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_doc_impl(context, &assert_context, config, assert_options, key, value)
}
fn set_doc_impl(
context: &StoreContext,
assert_context: &AssertContext,
config: &Option<DbConfig>,
assert_options: &AssertSetDocOptions,
key: Key,
value: SetDoc,
) -> Result<DocUpsert, String> {
let current_doc = get_state_doc(context.collection, &key, assert_context.rule)?;
assert_set_doc(
context,
assert_context,
config,
assert_options,
&key,
&value,
¤t_doc,
)?;
let doc: Doc = Doc::prepare(context.caller, ¤t_doc, value);
let (_evicted_doc, after) =
insert_state_doc(context.collection, &key, &doc, assert_context.rule)?;
Ok(DocUpsert {
before: current_doc,
after,
})
}
pub fn list_docs_store(
caller: Principal,
collection: CollectionKey,
filter: &ListParams,
) -> Result<ListResults<Doc>, String> {
let controllers: AccessKeys = get_access_keys();
secure_get_docs(caller, &controllers, collection, filter)
}
pub fn count_docs_store(
caller: Principal,
collection: CollectionKey,
filter: &ListParams,
) -> Result<usize, String> {
let results = list_docs_store(caller, collection, filter)?;
Ok(results.items_length)
}
fn secure_get_docs(
caller: Principal,
controllers: &AccessKeys,
collection: CollectionKey,
filter: &ListParams,
) -> Result<ListResults<Doc>, String> {
let context: StoreContext = StoreContext {
caller,
collection: &collection,
controllers,
};
let rule = get_state_rule(&collection)?;
let auth_config = get_auth_config();
let assert_context = AssertContext {
rule: &rule,
auth_config: &auth_config,
};
assert_get_docs(&context, &assert_context)?;
match rule.mem() {
Memory::Heap => STATE.with(|state| {
let state_ref = state.borrow();
let docs = get_docs_heap(&collection, &state_ref.heap.db.db)?;
get_docs_impl(&docs, caller, controllers, filter, &rule)
}),
Memory::Stable => STATE.with(|state| {
let stable = get_docs_stable(&collection, &state.borrow().stable.db)?;
let docs: Vec<(&Key, &Doc)> = stable.iter().map(|(key, doc)| (&key.key, doc)).collect();
get_docs_impl(&docs, caller, controllers, filter, &rule)
}),
}
}
fn get_docs_impl<'a>(
docs: &[(&'a Key, &'a Doc)],
caller: Principal,
controllers: &AccessKeys,
filters: &ListParams,
rule: &Rule,
) -> Result<ListResults<Doc>, String> {
let matches = filter_values(caller, controllers, &rule.read, docs, filters)?;
let results = list_values(&matches, filters);
Ok(results)
}
pub fn delete_doc_store(
caller: UserId,
collection: CollectionKey,
key: Key,
value: DelDoc,
) -> Result<DocContext<Option<Doc>>, String> {
let controllers: AccessKeys = get_access_keys();
let context = StoreContext {
caller,
controllers: &controllers,
collection: &collection,
};
let doc = secure_delete_doc(&context, key.clone(), value)?;
Ok(DocContext {
key,
collection,
data: doc,
})
}
fn secure_delete_doc(
context: &StoreContext,
key: Key,
value: DelDoc,
) -> Result<Option<Doc>, 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_doc_impl(context, &assert_context, key, value)
}
fn delete_doc_impl(
context: &StoreContext,
assert_context: &AssertContext,
key: Key,
value: DelDoc,
) -> Result<Option<Doc>, String> {
let current_doc = get_state_doc(context.collection, &key, assert_context.rule)?;
assert_delete_doc(context, assert_context, &key, &value, ¤t_doc)?;
delete_state_doc(context.collection, &key, assert_context.rule)
}
pub fn delete_docs_store(collection: &CollectionKey) -> Result<(), String> {
let rule = get_state_rule(collection)?;
let keys = match rule.mem() {
Memory::Heap => STATE.with(|state| {
get_docs_heap(collection, &state.borrow().heap.db.db)
.map(|docs| docs.into_iter().map(|(key, _)| key.clone()).collect())
}),
Memory::Stable => STATE.with(|state| {
get_docs_stable(collection, &state.borrow().stable.db)
.map(|docs| docs.iter().map(|(key, _)| key.key.clone()).collect())
}),
}?;
delete_docs_impl(&keys, collection, &rule)
}
fn delete_docs_impl(
keys: &Vec<Key>,
collection: &CollectionKey,
rule: &Rule,
) -> Result<(), String> {
for key in keys {
delete_state_doc(collection, key, rule)?;
}
Ok(())
}
pub fn count_collection_docs_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_docs_heap(collection, &state_ref.heap.db.db)?;
Ok(length)
}),
Memory::Stable => STATE.with(|state| {
let length = count_docs_stable(collection, &state.borrow().stable.db)?;
Ok(length)
}),
}
}
pub fn delete_filtered_docs_store(
caller: Principal,
collection: CollectionKey,
filter: &ListParams,
) -> Result<Vec<DocContext<Option<Doc>>>, String> {
let controllers: AccessKeys = get_access_keys();
let docs = secure_get_docs(caller, &controllers, collection.clone(), filter)?;
let context = StoreContext {
caller,
controllers: &controllers,
collection: &collection,
};
delete_filtered_docs_store_impl(&context, &docs)
}
fn delete_filtered_docs_store_impl(
context: &StoreContext,
docs: &ListResults<Doc>,
) -> Result<Vec<DocContext<Option<Doc>>>, 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 mut results: Vec<DocContext<Option<Doc>>> = Vec::new();
for (key, doc) in &docs.items {
let value = DelDoc {
version: doc.version,
};
let deleted_doc = delete_doc_impl(context, &assert_context, key.clone(), value)?;
let doc_context = DocContext {
key: key.clone(),
collection: context.collection.clone(),
data: deleted_doc,
};
results.push(doc_context);
}
Ok(results)
}
pub fn set_config_store(proposed_config: &SetDbConfig) -> Result<DbConfig, String> {
let current_config = get_config();
assert_set_config(proposed_config, ¤t_config)?;
let config = DbConfig::prepare(¤t_config, proposed_config);
insert_config(&config);
Ok(config)
}
pub fn get_config_store() -> Option<DbConfig> {
get_config()
}