From b4fd950b4c7c06a310793dd8699a87e757511bef Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sat, 1 Feb 2025 13:08:41 +0100 Subject: [PATCH 01/33] feat: track user usage in satellite --- src/libs/satellite/src/lib.rs | 1 + src/libs/satellite/src/memory.rs | 6 +++ src/libs/satellite/src/satellite.rs | 29 ++++++++--- src/libs/satellite/src/types.rs | 2 + src/libs/satellite/src/usage/impls.rs | 75 +++++++++++++++++++++++++++ src/libs/satellite/src/usage/mod.rs | 3 ++ src/libs/satellite/src/usage/store.rs | 57 ++++++++++++++++++++ src/libs/satellite/src/usage/types.rs | 31 +++++++++++ 8 files changed, 196 insertions(+), 8 deletions(-) create mode 100644 src/libs/satellite/src/usage/impls.rs create mode 100644 src/libs/satellite/src/usage/mod.rs create mode 100644 src/libs/satellite/src/usage/store.rs create mode 100644 src/libs/satellite/src/usage/types.rs diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index 2bbb85607..dc9f8913d 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -13,6 +13,7 @@ mod rules; mod satellite; mod storage; mod types; +mod usage; mod version; use crate::auth::types::config::AuthenticationConfig; diff --git a/src/libs/satellite/src/memory.rs b/src/libs/satellite/src/memory.rs index 78e1acb99..fb751d7e1 100644 --- a/src/libs/satellite/src/memory.rs +++ b/src/libs/satellite/src/memory.rs @@ -9,6 +9,7 @@ const UPGRADES: MemoryId = MemoryId::new(0); const DB: MemoryId = MemoryId::new(1); const ASSETS: MemoryId = MemoryId::new(2); const CONTENT_CHUNKS: MemoryId = MemoryId::new(3); +const USER_USAGE: MemoryId = MemoryId::new(4); thread_local! { pub static STATE: RefCell = RefCell::default(); @@ -33,10 +34,15 @@ fn get_memory_content_chunks() -> Memory { MEMORY_MANAGER.with(|m| m.borrow().get(CONTENT_CHUNKS)) } +fn get_memory_user_usage() -> Memory { + MEMORY_MANAGER.with(|m| m.borrow().get(USER_USAGE)) +} + pub fn init_stable_state() -> StableState { StableState { db: StableBTreeMap::init(get_memory_db()), assets: StableBTreeMap::init(get_memory_assets()), content_chunks: StableBTreeMap::init(get_memory_content_chunks()), + user_usage: StableBTreeMap::init(get_memory_user_usage()), } } diff --git a/src/libs/satellite/src/satellite.rs b/src/libs/satellite/src/satellite.rs index cc362caa7..799b3b1dc 100644 --- a/src/libs/satellite/src/satellite.rs +++ b/src/libs/satellite/src/satellite.rs @@ -38,6 +38,7 @@ use crate::storage::store::{ use crate::storage::strategy_impls::StorageState; use crate::types::interface::{Config, RulesType}; use crate::types::state::{HeapState, RuntimeState, State}; +use crate::usage::store::{decrease_user_usage, decrease_user_usage_by, increase_user_usage}; use ciborium::{from_reader, into_writer}; use ic_cdk::api::call::{arg_data, ArgDecoderConfig}; use ic_cdk::api::{caller, trap}; @@ -120,10 +121,12 @@ pub fn post_upgrade() { pub fn set_doc(collection: CollectionKey, key: Key, doc: SetDoc) -> Doc { let caller = caller(); - let result = set_doc_store(caller, collection, key, doc); + let result = set_doc_store(caller, collection.clone(), key, doc); match result { Ok(doc) => { + increase_user_usage(&caller, &collection); + invoke_on_set_doc(&caller, &doc); doc.data.after @@ -146,7 +149,10 @@ pub fn get_doc(collection: CollectionKey, key: Key) -> Option { pub fn del_doc(collection: CollectionKey, key: Key, doc: DelDoc) { let caller = caller(); - let deleted_doc = delete_doc_store(caller, collection, key, doc).unwrap_or_else(|e| trap(&e)); + let deleted_doc = + delete_doc_store(caller, collection.clone(), key, doc).unwrap_or_else(|e| trap(&e)); + + decrease_user_usage(&caller, &collection); invoke_on_delete_doc(&caller, &deleted_doc); } @@ -189,8 +195,10 @@ pub fn set_many_docs(docs: Vec<(CollectionKey, Key, SetDoc)>) -> Vec<(Key, Doc)> let mut results: Vec<(Key, Doc)> = Vec::new(); for (collection, key, doc) in docs { - let result = - set_doc_store(caller, collection, key.clone(), doc).unwrap_or_else(|e| trap(&e)); + let result = set_doc_store(caller, collection.clone(), key.clone(), doc) + .unwrap_or_else(|e| trap(&e)); + + increase_user_usage(&caller, &collection); results.push((result.key.clone(), result.data.after.clone())); @@ -208,8 +216,11 @@ pub fn del_many_docs(docs: Vec<(CollectionKey, Key, DelDoc)>) { let mut results: Vec>> = Vec::new(); for (collection, key, doc) in docs { - let deleted_doc = - delete_doc_store(caller, collection, key.clone(), doc).unwrap_or_else(|e| trap(&e)); + let deleted_doc = delete_doc_store(caller, collection.clone(), key.clone(), doc) + .unwrap_or_else(|e| trap(&e)); + + decrease_user_usage(&caller, &collection); + results.push(deleted_doc); } @@ -219,8 +230,10 @@ pub fn del_many_docs(docs: Vec<(CollectionKey, Key, DelDoc)>) { pub fn del_filtered_docs(collection: CollectionKey, filter: ListParams) { let caller = caller(); - let results = - delete_filtered_docs_store(caller, collection, &filter).unwrap_or_else(|e| trap(&e)); + let results = delete_filtered_docs_store(caller, collection.clone(), &filter) + .unwrap_or_else(|e| trap(&e)); + + decrease_user_usage_by(&caller, &collection, results.len() as u32); invoke_on_delete_filtered_docs(&caller, &results); } diff --git a/src/libs/satellite/src/types.rs b/src/libs/satellite/src/types.rs index ed50bf822..ae92184e1 100644 --- a/src/libs/satellite/src/types.rs +++ b/src/libs/satellite/src/types.rs @@ -3,6 +3,7 @@ pub mod state { use crate::db::types::state::{DbHeapState, DbRuntimeState, DbStable}; use crate::memory::init_stable_state; use crate::storage::types::state::{AssetsStable, ContentChunksStable}; + use crate::usage::types::state::UserUsageStable; use candid::CandidType; use junobuild_shared::types::state::Controllers; use junobuild_storage::types::state::StorageHeapState; @@ -27,6 +28,7 @@ pub mod state { pub db: DbStable, pub assets: AssetsStable, pub content_chunks: ContentChunksStable, + pub user_usage: UserUsageStable, } #[derive(Default, CandidType, Serialize, Deserialize, Clone)] diff --git a/src/libs/satellite/src/usage/impls.rs b/src/libs/satellite/src/usage/impls.rs new file mode 100644 index 000000000..6b108aa02 --- /dev/null +++ b/src/libs/satellite/src/usage/impls.rs @@ -0,0 +1,75 @@ +use crate::usage::types::interface::ModificationType; +use crate::usage::types::state::{UserUsage, UserUsageKey}; +use ic_cdk::api::time; +use ic_stable_structures::storable::Bound; +use ic_stable_structures::Storable; +use junobuild_shared::constants::INITIAL_VERSION; +use junobuild_shared::serializers::{deserialize_from_bytes, serialize_to_bytes}; +use junobuild_shared::types::state::{Timestamp, Version}; +use std::borrow::Cow; + +impl Storable for UserUsage { + fn to_bytes(&self) -> Cow<[u8]> { + serialize_to_bytes(self) + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + deserialize_from_bytes(bytes) + } + + const BOUND: Bound = Bound::Unbounded; +} + +impl Storable for UserUsageKey { + fn to_bytes(&self) -> Cow<[u8]> { + serialize_to_bytes(self) + } + + fn from_bytes(bytes: Cow<[u8]>) -> Self { + deserialize_from_bytes(bytes) + } + + const BOUND: Bound = Bound::Unbounded; +} + +impl UserUsage { + pub fn update( + current_user_usage: &Option, + modification: &ModificationType, + count: Option, + ) -> Self { + let now = time(); + let count = count.unwrap_or(1); + + // User usage for the collection + + let items_count: u32 = match current_user_usage { + None => 1, + Some(current_user_usage) => match modification { + ModificationType::Set => current_user_usage.items_count.saturating_add(count), + ModificationType::Delete => current_user_usage.items_count.saturating_sub(count), + }, + }; + + // Metadata for the UserUsage entity entry + + let created_at: Timestamp = match current_user_usage { + None => now, + Some(current_user_usage) => current_user_usage.created_at, + }; + + let version: Version = match current_user_usage { + None => INITIAL_VERSION, + Some(current_user_usage) => current_user_usage.version.unwrap_or_default() + 1, + }; + + let updated_at: Timestamp = now; + + UserUsage { + items_count, + created_at, + updated_at, + version: Some(version), + } + } +} diff --git a/src/libs/satellite/src/usage/mod.rs b/src/libs/satellite/src/usage/mod.rs new file mode 100644 index 000000000..207c1637a --- /dev/null +++ b/src/libs/satellite/src/usage/mod.rs @@ -0,0 +1,3 @@ +mod impls; +pub mod store; +pub mod types; diff --git a/src/libs/satellite/src/usage/store.rs b/src/libs/satellite/src/usage/store.rs new file mode 100644 index 000000000..be5baaeed --- /dev/null +++ b/src/libs/satellite/src/usage/store.rs @@ -0,0 +1,57 @@ +use crate::memory::STATE; +use crate::usage::types::interface::ModificationType; +use crate::usage::types::state::{UserUsage, UserUsageKey, UserUsageStable}; +use junobuild_collections::types::core::CollectionKey; +use junobuild_shared::types::state::UserId; + +pub fn increase_user_usage(user_id: &UserId, collection: &CollectionKey) { + update_user_usage(user_id, collection, &ModificationType::Set, None); +} + +pub fn decrease_user_usage(user_id: &UserId, collection: &CollectionKey) { + update_user_usage(user_id, collection, &ModificationType::Delete, None); +} + +pub fn decrease_user_usage_by(user_id: &UserId, collection: &CollectionKey, count: u32) { + update_user_usage(user_id, collection, &ModificationType::Delete, Some(count)); +} + +fn update_user_usage( + user_id: &UserId, + collection: &CollectionKey, + modification: &ModificationType, + count: Option, +) { + STATE.with(|state| { + update_user_usage_impl( + user_id, + collection, + modification, + count, + &mut state.borrow_mut().stable.user_usage, + ) + }) +} + +fn update_user_usage_impl( + user_id: &UserId, + collection: &CollectionKey, + modification: &ModificationType, + count: Option, + state: &mut UserUsageStable, +) { + let key = stable_user_usage_key(user_id, collection); + + let current_usage = state.get(&key); + + let update_usage = UserUsage::update(¤t_usage, modification, count); + + state.insert(key, update_usage); +} + +fn stable_user_usage_key(user_id: &UserId, collection: &CollectionKey) -> UserUsageKey { + UserUsageKey { + user_id: *user_id, + collection: collection.clone(), + } +} diff --git a/src/libs/satellite/src/usage/types.rs b/src/libs/satellite/src/usage/types.rs new file mode 100644 index 000000000..55c727adc --- /dev/null +++ b/src/libs/satellite/src/usage/types.rs @@ -0,0 +1,31 @@ +pub mod state { + use candid::{CandidType, Deserialize}; + use ic_stable_structures::StableBTreeMap; + use junobuild_collections::types::core::CollectionKey; + use junobuild_shared::types::memory::Memory; + use junobuild_shared::types::state::{Timestamp, UserId, Version}; + use serde::Serialize; + + pub type UserUsageStable = StableBTreeMap; + + #[derive(CandidType, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub struct UserUsageKey { + pub user_id: UserId, + pub collection: CollectionKey, + } + + #[derive(CandidType, Serialize, Deserialize, Clone)] + pub struct UserUsage { + pub items_count: u32, + pub created_at: Timestamp, + pub updated_at: Timestamp, + pub version: Option, + } +} + +pub mod interface { + pub enum ModificationType { + Set, + Delete, + } +} From 3ef141acff2e6f8c3bd2f99f6f2ef2d5af83674a Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sat, 1 Feb 2025 13:52:12 +0100 Subject: [PATCH 02/33] feat: get user usage --- src/libs/satellite/src/lib.rs | 14 +++++++++++++- src/libs/satellite/src/satellite.rs | 13 ++++++++++++- src/libs/satellite/src/usage/mod.rs | 1 + src/libs/satellite/src/usage/store.rs | 14 ++++++++++++++ src/libs/satellite/src/usage/user_usage.rs | 21 +++++++++++++++++++++ 5 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/libs/satellite/src/usage/user_usage.rs diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index ba58ce01a..aee5748d4 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -22,6 +22,7 @@ use crate::guards::{caller_is_admin_controller, caller_is_controller}; use crate::types::interface::{Config, RulesType}; use crate::version::SATELLITE_VERSION; use ic_cdk::api::trap; +use ic_cdk::caller; use ic_cdk_macros::{init, post_upgrade, pre_upgrade, query, update}; use junobuild_collections::types::core::CollectionKey; use junobuild_collections::types::interface::{DelRule, SetRule}; @@ -34,7 +35,7 @@ use junobuild_shared::types::interface::{ }; use junobuild_shared::types::list::ListParams; use junobuild_shared::types::list::ListResults; -use junobuild_shared::types::state::Controllers; +use junobuild_shared::types::state::{Controllers, UserId}; use junobuild_storage::http::types::{ HttpRequest, HttpResponse, StreamingCallbackHttpResponse, StreamingCallbackToken, }; @@ -72,6 +73,7 @@ pub use crate::types::hooks::{ OnDeleteFilteredAssetsContext, OnDeleteFilteredDocsContext, OnDeleteManyAssetsContext, OnDeleteManyDocsContext, OnSetDocContext, OnSetManyDocsContext, OnUploadAssetContext, }; +use crate::usage::types::state::UserUsage; // ============================================================================================ // END: Re-exported Types // ============================================================================================ @@ -394,6 +396,16 @@ pub fn get_many_assets( satellite::get_many_assets(assets) } +// --------------------------------------------------------- +// User usage +// --------------------------------------------------------- + +#[doc(hidden)] +#[query] +pub fn get_user_usage(collection: CollectionKey, user_id: Option) -> Option { + satellite::get_user_usage(&collection, &user_id) +} + // --------------------------------------------------------- // Mgmt // --------------------------------------------------------- diff --git a/src/libs/satellite/src/satellite.rs b/src/libs/satellite/src/satellite.rs index dfe191a1a..9bedb40cd 100644 --- a/src/libs/satellite/src/satellite.rs +++ b/src/libs/satellite/src/satellite.rs @@ -39,6 +39,8 @@ use crate::storage::strategy_impls::StorageState; use crate::types::interface::{Config, RulesType}; use crate::types::state::{HeapState, RuntimeState, State}; use crate::usage::store::{decrease_user_usage, decrease_user_usage_by, increase_user_usage}; +use crate::usage::types::state::UserUsage; +use crate::usage::user_usage::get_user_usage_by_id; use ciborium::{from_reader, into_writer}; use ic_cdk::api::call::{arg_data, ArgDecoderConfig}; use ic_cdk::api::{caller, trap}; @@ -55,7 +57,7 @@ use junobuild_shared::types::interface::{DeleteControllersArgs, SegmentArgs, Set use junobuild_shared::types::list::ListParams; use junobuild_shared::types::list::ListResults; use junobuild_shared::types::memory::Memory; -use junobuild_shared::types::state::{ControllerScope, Controllers}; +use junobuild_shared::types::state::{ControllerScope, Controllers, UserId}; use junobuild_shared::upgrade::{read_post_upgrade, write_pre_upgrade}; use junobuild_storage::http::types::{ HttpRequest, HttpResponse, StreamingCallbackHttpResponse, StreamingCallbackToken, @@ -541,3 +543,12 @@ pub fn get_many_assets( }) .collect() } + +// --------------------------------------------------------- +// User usage +// --------------------------------------------------------- + +pub fn get_user_usage(collection: &CollectionKey, user_id: &Option) -> Option { + let caller = caller(); + get_user_usage_by_id(collection, &user_id.unwrap_or(caller), caller) +} diff --git a/src/libs/satellite/src/usage/mod.rs b/src/libs/satellite/src/usage/mod.rs index 207c1637a..384e7316b 100644 --- a/src/libs/satellite/src/usage/mod.rs +++ b/src/libs/satellite/src/usage/mod.rs @@ -1,3 +1,4 @@ mod impls; pub mod store; pub mod types; +pub mod user_usage; diff --git a/src/libs/satellite/src/usage/store.rs b/src/libs/satellite/src/usage/store.rs index be5baaeed..310627401 100644 --- a/src/libs/satellite/src/usage/store.rs +++ b/src/libs/satellite/src/usage/store.rs @@ -16,6 +16,10 @@ pub fn decrease_user_usage_by(user_id: &UserId, collection: &CollectionKey, coun update_user_usage(user_id, collection, &ModificationType::Delete, Some(count)); } +pub fn get_user_usage(user_id: &UserId, collection: &CollectionKey) -> Option { + STATE.with(|state| get_user_usage_impl(user_id, collection, &state.borrow().stable.user_usage)) +} + fn update_user_usage( user_id: &UserId, collection: &CollectionKey, @@ -33,6 +37,16 @@ fn update_user_usage( }) } +fn get_user_usage_impl( + user_id: &UserId, + collection: &CollectionKey, + state: &UserUsageStable, +) -> Option { + let key = stable_user_usage_key(user_id, collection); + + state.get(&key) +} + fn update_user_usage_impl( user_id: &UserId, collection: &CollectionKey, diff --git a/src/libs/satellite/src/usage/user_usage.rs b/src/libs/satellite/src/usage/user_usage.rs new file mode 100644 index 000000000..e20b37338 --- /dev/null +++ b/src/libs/satellite/src/usage/user_usage.rs @@ -0,0 +1,21 @@ +use crate::get_controllers; +use crate::usage::store::get_user_usage as get_user_usage_store; +use crate::usage::types::state::UserUsage; +use candid::Principal; +use junobuild_collections::types::core::CollectionKey; +use junobuild_shared::controllers::is_controller; +use junobuild_shared::types::state::{Controllers, UserId}; + +pub fn get_user_usage_by_id( + collection: &CollectionKey, + user_id: &UserId, + caller: Principal, +) -> Option { + let controllers: Controllers = get_controllers(); + + if assert_caller(caller, user_id) || is_controller(caller, &controllers) { + return get_user_usage_store(user_id, collection); + } + + None +} From a0ac2485edf0eb381c6fbb3a2b13c17a2472e1f2 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sat, 1 Feb 2025 14:33:54 +0100 Subject: [PATCH 03/33] chore: remove unused --- src/libs/satellite/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index aee5748d4..78ae017b6 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -22,7 +22,6 @@ use crate::guards::{caller_is_admin_controller, caller_is_controller}; use crate::types::interface::{Config, RulesType}; use crate::version::SATELLITE_VERSION; use ic_cdk::api::trap; -use ic_cdk::caller; use ic_cdk_macros::{init, post_upgrade, pre_upgrade, query, update}; use junobuild_collections::types::core::CollectionKey; use junobuild_collections::types::interface::{DelRule, SetRule}; From 09f4d3cf576012671ca251b0621fca8b35d5e97a Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sat, 1 Feb 2025 14:34:00 +0100 Subject: [PATCH 04/33] feat: use util --- src/libs/satellite/src/usage/user_usage.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libs/satellite/src/usage/user_usage.rs b/src/libs/satellite/src/usage/user_usage.rs index e20b37338..56c2827fa 100644 --- a/src/libs/satellite/src/usage/user_usage.rs +++ b/src/libs/satellite/src/usage/user_usage.rs @@ -5,6 +5,7 @@ use candid::Principal; use junobuild_collections::types::core::CollectionKey; use junobuild_shared::controllers::is_controller; use junobuild_shared::types::state::{Controllers, UserId}; +use junobuild_shared::utils::principal_not_anonymous_and_equal; pub fn get_user_usage_by_id( collection: &CollectionKey, @@ -13,7 +14,9 @@ pub fn get_user_usage_by_id( ) -> Option { let controllers: Controllers = get_controllers(); - if assert_caller(caller, user_id) || is_controller(caller, &controllers) { + if principal_not_anonymous_and_equal(user_id.clone(), caller) + || is_controller(caller, &controllers) + { return get_user_usage_store(user_id, collection); } From c60426a22423a729bbffe2bd59e57e9a2e787aca Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sat, 1 Feb 2025 14:35:16 +0100 Subject: [PATCH 05/33] refactor: move entry points --- src/libs/satellite/src/satellite.rs | 5 +++-- src/libs/satellite/src/usage/mod.rs | 2 +- src/libs/satellite/src/usage/store.rs | 14 +------------- src/libs/satellite/src/usage/user_usage.rs | 14 ++++++++++++++ 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/libs/satellite/src/satellite.rs b/src/libs/satellite/src/satellite.rs index 9bedb40cd..cdb4d5a07 100644 --- a/src/libs/satellite/src/satellite.rs +++ b/src/libs/satellite/src/satellite.rs @@ -38,9 +38,10 @@ use crate::storage::store::{ use crate::storage::strategy_impls::StorageState; use crate::types::interface::{Config, RulesType}; use crate::types::state::{HeapState, RuntimeState, State}; -use crate::usage::store::{decrease_user_usage, decrease_user_usage_by, increase_user_usage}; use crate::usage::types::state::UserUsage; -use crate::usage::user_usage::get_user_usage_by_id; +use crate::usage::user_usage::{ + decrease_user_usage, decrease_user_usage_by, get_user_usage_by_id, increase_user_usage, +}; use ciborium::{from_reader, into_writer}; use ic_cdk::api::call::{arg_data, ArgDecoderConfig}; use ic_cdk::api::{caller, trap}; diff --git a/src/libs/satellite/src/usage/mod.rs b/src/libs/satellite/src/usage/mod.rs index 384e7316b..08773a8d9 100644 --- a/src/libs/satellite/src/usage/mod.rs +++ b/src/libs/satellite/src/usage/mod.rs @@ -1,4 +1,4 @@ mod impls; -pub mod store; +mod store; pub mod types; pub mod user_usage; diff --git a/src/libs/satellite/src/usage/store.rs b/src/libs/satellite/src/usage/store.rs index 310627401..e8d0d22e3 100644 --- a/src/libs/satellite/src/usage/store.rs +++ b/src/libs/satellite/src/usage/store.rs @@ -4,23 +4,11 @@ use crate::usage::types::state::{UserUsage, UserUsageKey, UserUsageStable}; use junobuild_collections::types::core::CollectionKey; use junobuild_shared::types::state::UserId; -pub fn increase_user_usage(user_id: &UserId, collection: &CollectionKey) { - update_user_usage(user_id, collection, &ModificationType::Set, None); -} - -pub fn decrease_user_usage(user_id: &UserId, collection: &CollectionKey) { - update_user_usage(user_id, collection, &ModificationType::Delete, None); -} - -pub fn decrease_user_usage_by(user_id: &UserId, collection: &CollectionKey, count: u32) { - update_user_usage(user_id, collection, &ModificationType::Delete, Some(count)); -} - pub fn get_user_usage(user_id: &UserId, collection: &CollectionKey) -> Option { STATE.with(|state| get_user_usage_impl(user_id, collection, &state.borrow().stable.user_usage)) } -fn update_user_usage( +pub fn update_user_usage( user_id: &UserId, collection: &CollectionKey, modification: &ModificationType, diff --git a/src/libs/satellite/src/usage/user_usage.rs b/src/libs/satellite/src/usage/user_usage.rs index 56c2827fa..005b67116 100644 --- a/src/libs/satellite/src/usage/user_usage.rs +++ b/src/libs/satellite/src/usage/user_usage.rs @@ -1,5 +1,7 @@ use crate::get_controllers; +use crate::usage::store; use crate::usage::store::get_user_usage as get_user_usage_store; +use crate::usage::types::interface::ModificationType; use crate::usage::types::state::UserUsage; use candid::Principal; use junobuild_collections::types::core::CollectionKey; @@ -22,3 +24,15 @@ pub fn get_user_usage_by_id( None } + +pub fn increase_user_usage(user_id: &UserId, collection: &CollectionKey) { + store::update_user_usage(user_id, collection, &ModificationType::Set, None); +} + +pub fn decrease_user_usage(user_id: &UserId, collection: &CollectionKey) { + store::update_user_usage(user_id, collection, &ModificationType::Delete, None); +} + +pub fn decrease_user_usage_by(user_id: &UserId, collection: &CollectionKey, count: u32) { + store::update_user_usage(user_id, collection, &ModificationType::Delete, Some(count)); +} From ee26b3331e5809c131272bbe26e76a37a62055ad Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sat, 1 Feb 2025 14:36:02 +0100 Subject: [PATCH 06/33] feat: clippy --- src/libs/satellite/src/usage/user_usage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/satellite/src/usage/user_usage.rs b/src/libs/satellite/src/usage/user_usage.rs index 005b67116..79d780c56 100644 --- a/src/libs/satellite/src/usage/user_usage.rs +++ b/src/libs/satellite/src/usage/user_usage.rs @@ -16,7 +16,7 @@ pub fn get_user_usage_by_id( ) -> Option { let controllers: Controllers = get_controllers(); - if principal_not_anonymous_and_equal(user_id.clone(), caller) + if principal_not_anonymous_and_equal(*user_id, caller) || is_controller(caller, &controllers) { return get_user_usage_store(user_id, collection); From 17b7ce0e16eec1251e7eeb53e840344e43c74f75 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sat, 1 Feb 2025 16:16:10 +0100 Subject: [PATCH 07/33] feat: expose get usage --- src/libs/satellite/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index 78ae017b6..62dd6b20d 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -457,7 +457,7 @@ macro_rules! include_satellite { list_controllers, list_custom_domains, list_docs, list_rules, memory_size, post_upgrade, pre_upgrade, set_auth_config, set_controllers, set_custom_domain, set_db_config, set_doc, set_many_docs, set_rule, set_storage_config, - upload_asset_chunk, version, + upload_asset_chunk, get_user_usage, version, }; #[ic_cdk::query] From 9e4638a5f7829d48c32fab4d94a656d24ebbd249 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sat, 1 Feb 2025 16:17:31 +0100 Subject: [PATCH 08/33] chore: usage did --- src/declarations/satellite/satellite.did.d.ts | 7 +++++++ .../satellite/satellite.factory.certified.did.js | 7 +++++++ src/declarations/satellite/satellite.factory.did.js | 7 +++++++ src/declarations/satellite/satellite.factory.did.mjs | 7 +++++++ src/libs/satellite/satellite.did | 7 +++++++ src/satellite/satellite.did | 7 +++++++ 6 files changed, 42 insertions(+) diff --git a/src/declarations/satellite/satellite.did.d.ts b/src/declarations/satellite/satellite.did.d.ts index 3b3718a90..649454a7c 100644 --- a/src/declarations/satellite/satellite.did.d.ts +++ b/src/declarations/satellite/satellite.did.d.ts @@ -237,6 +237,12 @@ export interface UploadChunk { export interface UploadChunkResult { chunk_id: bigint; } +export interface UserUsage { + updated_at: bigint; + created_at: bigint; + version: [] | [bigint]; + items_count: number; +} export interface _SERVICE { build_version: ActorMethod<[], string>; commit_asset_upload: ActorMethod<[CommitBatch], undefined>; @@ -265,6 +271,7 @@ export interface _SERVICE { get_many_docs: ActorMethod<[Array<[string, string]>], Array<[string, [] | [Doc]]>>; get_rule: ActorMethod<[RulesType, string], [] | [Rule]>; get_storage_config: ActorMethod<[], StorageConfig>; + get_user_usage: ActorMethod<[string, [] | [Principal]], [] | [UserUsage]>; http_request: ActorMethod<[HttpRequest], HttpResponse>; http_request_streaming_callback: ActorMethod< [StreamingCallbackToken], diff --git a/src/declarations/satellite/satellite.factory.certified.did.js b/src/declarations/satellite/satellite.factory.certified.did.js index 719731f95..8102123be 100644 --- a/src/declarations/satellite/satellite.factory.certified.did.js +++ b/src/declarations/satellite/satellite.factory.certified.did.js @@ -145,6 +145,12 @@ export const idlFactory = ({ IDL }) => { rate_config: IDL.Opt(RateConfig), write: Permission }); + const UserUsage = IDL.Record({ + updated_at: IDL.Nat64, + created_at: IDL.Nat64, + version: IDL.Opt(IDL.Nat64), + items_count: IDL.Nat32 + }); const HttpRequest = IDL.Record({ url: IDL.Text, method: IDL.Text, @@ -277,6 +283,7 @@ export const idlFactory = ({ IDL }) => { ), get_rule: IDL.Func([RulesType, IDL.Text], [IDL.Opt(Rule)], []), get_storage_config: IDL.Func([], [StorageConfig], []), + get_user_usage: IDL.Func([IDL.Text, IDL.Opt(IDL.Principal)], [IDL.Opt(UserUsage)], []), http_request: IDL.Func([HttpRequest], [HttpResponse], []), http_request_streaming_callback: IDL.Func( [StreamingCallbackToken], diff --git a/src/declarations/satellite/satellite.factory.did.js b/src/declarations/satellite/satellite.factory.did.js index a6515126e..e6567a054 100644 --- a/src/declarations/satellite/satellite.factory.did.js +++ b/src/declarations/satellite/satellite.factory.did.js @@ -145,6 +145,12 @@ export const idlFactory = ({ IDL }) => { rate_config: IDL.Opt(RateConfig), write: Permission }); + const UserUsage = IDL.Record({ + updated_at: IDL.Nat64, + created_at: IDL.Nat64, + version: IDL.Opt(IDL.Nat64), + items_count: IDL.Nat32 + }); const HttpRequest = IDL.Record({ url: IDL.Text, method: IDL.Text, @@ -277,6 +283,7 @@ export const idlFactory = ({ IDL }) => { ), get_rule: IDL.Func([RulesType, IDL.Text], [IDL.Opt(Rule)], ['query']), get_storage_config: IDL.Func([], [StorageConfig], ['query']), + get_user_usage: IDL.Func([IDL.Text, IDL.Opt(IDL.Principal)], [IDL.Opt(UserUsage)], ['query']), http_request: IDL.Func([HttpRequest], [HttpResponse], ['query']), http_request_streaming_callback: IDL.Func( [StreamingCallbackToken], diff --git a/src/declarations/satellite/satellite.factory.did.mjs b/src/declarations/satellite/satellite.factory.did.mjs index a6515126e..e6567a054 100644 --- a/src/declarations/satellite/satellite.factory.did.mjs +++ b/src/declarations/satellite/satellite.factory.did.mjs @@ -145,6 +145,12 @@ export const idlFactory = ({ IDL }) => { rate_config: IDL.Opt(RateConfig), write: Permission }); + const UserUsage = IDL.Record({ + updated_at: IDL.Nat64, + created_at: IDL.Nat64, + version: IDL.Opt(IDL.Nat64), + items_count: IDL.Nat32 + }); const HttpRequest = IDL.Record({ url: IDL.Text, method: IDL.Text, @@ -277,6 +283,7 @@ export const idlFactory = ({ IDL }) => { ), get_rule: IDL.Func([RulesType, IDL.Text], [IDL.Opt(Rule)], ['query']), get_storage_config: IDL.Func([], [StorageConfig], ['query']), + get_user_usage: IDL.Func([IDL.Text, IDL.Opt(IDL.Principal)], [IDL.Opt(UserUsage)], ['query']), http_request: IDL.Func([HttpRequest], [HttpResponse], ['query']), http_request_streaming_callback: IDL.Func( [StreamingCallbackToken], diff --git a/src/libs/satellite/satellite.did b/src/libs/satellite/satellite.did index d3a958c2a..8fbafdf60 100644 --- a/src/libs/satellite/satellite.did +++ b/src/libs/satellite/satellite.did @@ -197,6 +197,12 @@ type UploadChunk = record { order_id : opt nat; }; type UploadChunkResult = record { chunk_id : nat }; +type UserUsage = record { + updated_at : nat64; + created_at : nat64; + version : opt nat64; + items_count : nat32; +}; service : () -> { commit_asset_upload : (CommitBatch) -> (); count_assets : (text, ListParams) -> (nat64) query; @@ -230,6 +236,7 @@ service : () -> { ) query; get_rule : (RulesType, text) -> (opt Rule) query; get_storage_config : () -> (StorageConfig) query; + get_user_usage : (text, opt principal) -> (opt UserUsage) query; http_request : (HttpRequest) -> (HttpResponse) query; http_request_streaming_callback : (StreamingCallbackToken) -> ( StreamingCallbackHttpResponse, diff --git a/src/satellite/satellite.did b/src/satellite/satellite.did index dc6285e8f..9c3ab009b 100644 --- a/src/satellite/satellite.did +++ b/src/satellite/satellite.did @@ -199,6 +199,12 @@ type UploadChunk = record { order_id : opt nat; }; type UploadChunkResult = record { chunk_id : nat }; +type UserUsage = record { + updated_at : nat64; + created_at : nat64; + version : opt nat64; + items_count : nat32; +}; service : () -> { commit_asset_upload : (CommitBatch) -> (); count_assets : (text, ListParams) -> (nat64) query; @@ -232,6 +238,7 @@ service : () -> { ) query; get_rule : (RulesType, text) -> (opt Rule) query; get_storage_config : () -> (StorageConfig) query; + get_user_usage : (text, opt principal) -> (opt UserUsage) query; http_request : (HttpRequest) -> (HttpResponse) query; http_request_streaming_callback : (StreamingCallbackToken) -> ( StreamingCallbackHttpResponse, From f48ba476e08cc2d1d27b5858a51c8a761ee35018 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sat, 1 Feb 2025 16:30:30 +0100 Subject: [PATCH 09/33] test: more stable memory is used --- src/tests/satellite.datastore.spec.ts | 2 +- src/tests/satellite.storage.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/satellite.datastore.spec.ts b/src/tests/satellite.datastore.spec.ts index 634771a81..65a9a2575 100644 --- a/src/tests/satellite.datastore.spec.ts +++ b/src/tests/satellite.datastore.spec.ts @@ -767,7 +767,7 @@ describe.each([{ memory: { Heap: null } }, { memory: { Stable: null } }])( }, { memory: { Stable: null }, - expectMemory: 25_231_360n + expectMemory: 33_619_968n } ])('With collection', ({ memory, expectMemory }) => { const errorMsg = `${'Heap' in memory ? 'Heap' : 'Stable'} memory usage exceeded: ${expectMemory} bytes used, 20000 bytes allowed.`; diff --git a/src/tests/satellite.storage.spec.ts b/src/tests/satellite.storage.spec.ts index 912a5ac56..ebdc49f79 100644 --- a/src/tests/satellite.storage.spec.ts +++ b/src/tests/satellite.storage.spec.ts @@ -1206,7 +1206,7 @@ describe('Satellite storage', () => { }, { memory: { Stable: null }, - expectMemory: 25_231_360n, + expectMemory: 33_619_968n, allowedMemory: maxStableMemorySize, preUploadCount: 0 } From 16ce44afcbd7c8f8021fe0638d393bfb0bc242fb Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sat, 1 Feb 2025 17:15:40 +0100 Subject: [PATCH 10/33] chore: fmt --- src/libs/satellite/src/lib.rs | 6 +++--- src/libs/satellite/src/usage/user_usage.rs | 4 +--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index 62dd6b20d..83df8b714 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -452,12 +452,12 @@ macro_rules! include_satellite { count_docs, del_asset, del_assets, del_controllers, del_custom_domain, del_doc, del_docs, del_filtered_assets, del_filtered_docs, del_many_assets, del_many_docs, del_rule, deposit_cycles, get_asset, get_auth_config, get_config, get_db_config, - get_doc, get_many_assets, get_many_docs, get_storage_config, http_request, - http_request_streaming_callback, init, init_asset_upload, list_assets, + get_doc, get_many_assets, get_many_docs, get_storage_config, get_user_usage, + http_request, http_request_streaming_callback, init, init_asset_upload, list_assets, list_controllers, list_custom_domains, list_docs, list_rules, memory_size, post_upgrade, pre_upgrade, set_auth_config, set_controllers, set_custom_domain, set_db_config, set_doc, set_many_docs, set_rule, set_storage_config, - upload_asset_chunk, get_user_usage, version, + upload_asset_chunk, version, }; #[ic_cdk::query] diff --git a/src/libs/satellite/src/usage/user_usage.rs b/src/libs/satellite/src/usage/user_usage.rs index 79d780c56..93e8c2fb8 100644 --- a/src/libs/satellite/src/usage/user_usage.rs +++ b/src/libs/satellite/src/usage/user_usage.rs @@ -16,9 +16,7 @@ pub fn get_user_usage_by_id( ) -> Option { let controllers: Controllers = get_controllers(); - if principal_not_anonymous_and_equal(*user_id, caller) - || is_controller(caller, &controllers) - { + if principal_not_anonymous_and_equal(*user_id, caller) || is_controller(caller, &controllers) { return get_user_usage_store(user_id, collection); } From 92040adb606ce0f953fcf19e6d27483ecf525066 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sat, 1 Feb 2025 17:21:30 +0100 Subject: [PATCH 11/33] test: assert doc usage --- src/tests/satellite.user-usage.spec.ts | 271 +++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 src/tests/satellite.user-usage.spec.ts diff --git a/src/tests/satellite.user-usage.spec.ts b/src/tests/satellite.user-usage.spec.ts new file mode 100644 index 000000000..8489fbe42 --- /dev/null +++ b/src/tests/satellite.user-usage.spec.ts @@ -0,0 +1,271 @@ +import type { + DelDoc, + ListParams, + _SERVICE as SatelliteActor, + SetDoc, + SetRule, + UserUsage +} from '$declarations/satellite/satellite.did'; +import { idlFactory as idlFactorSatellite } from '$declarations/satellite/satellite.factory.did'; +import { Ed25519KeyIdentity } from '@dfinity/identity'; +import type { Principal } from '@dfinity/principal'; +import { assertNonNullish, fromNullable, toNullable } from '@dfinity/utils'; +import { type Actor, PocketIc } from '@hadronous/pic'; +import { toArray } from '@junobuild/utils'; +import { nanoid } from 'nanoid'; +import { afterAll, beforeAll, expect, inject } from 'vitest'; +import { controllersInitArgs, SATELLITE_WASM_PATH } from './utils/setup-tests.utils'; + +describe('Satellite User Usage', () => { + let pic: PocketIc; + let actor: Actor; + + const controller = Ed25519KeyIdentity.generate(); + + const TEST_COLLECTION = 'test'; + + const setRule: SetRule = { + memory: toNullable({ Heap: null }), + max_size: toNullable(), + max_capacity: toNullable(), + read: { Managed: null }, + mutable_permissions: toNullable(), + write: { Managed: null }, + version: toNullable(), + rate_config: toNullable() + }; + + const NO_FILTER_PARAMS: ListParams = { + matcher: toNullable(), + order: toNullable(), + owner: toNullable(), + paginate: toNullable() + }; + + beforeAll(async () => { + pic = await PocketIc.create(inject('PIC_URL')); + + const { actor: c } = await pic.setupCanister({ + idlFactory: idlFactorSatellite, + wasm: SATELLITE_WASM_PATH, + arg: controllersInitArgs(controller), + sender: controller.getPrincipal() + }); + + actor = c; + + actor.setIdentity(controller); + }); + + afterAll(async () => { + await pic?.tearDown(); + }); + + describe('Datastore', async () => { + const data = await toArray({ + hello: 'World' + }); + + beforeAll(async () => { + const { set_rule } = actor; + await set_rule({ Db: null }, TEST_COLLECTION, setRule); + }); + + const createDoc = async (): Promise => { + const key = nanoid(); + + const { set_doc } = actor; + + await set_doc(TEST_COLLECTION, key, { + data, + description: toNullable(), + version: toNullable() + }); + + return key; + }; + + describe('User', () => { + const user = Ed25519KeyIdentity.generate(); + + beforeAll(() => { + actor.setIdentity(user); + }); + + const countSetDocs = 10; + + it('should get a usage count after set documents', async () => { + await Promise.all(Array.from({ length: countSetDocs }).map(createDoc)); + + const { get_user_usage } = actor; + + const usageResponse = await get_user_usage(TEST_COLLECTION, toNullable()); + + const usage = fromNullable(usageResponse); + + assertNonNullish(usage); + + expect(usage.items_count).toEqual(countSetDocs); + + expect(usage.updated_at).not.toBeUndefined(); + expect(usage.updated_at).toBeGreaterThan(0n); + expect(usage.created_at).not.toBeUndefined(); + expect(usage.created_at).toBeGreaterThan(0n); + expect(usage.updated_at).toBeGreaterThan(usage.created_at); + + expect(usage.version).toEqual(toNullable(BigInt(countSetDocs))); + }); + + const countSetManyDocs = 5; + + it('should get a usage count after set many documents', async () => { + const { set_many_docs } = actor; + + const docs: [string, string, SetDoc][] = Array.from({ length: countSetManyDocs }).map( + () => [ + TEST_COLLECTION, + nanoid(), + { + data, + description: toNullable(), + version: toNullable() + } + ] + ); + + await set_many_docs(docs); + + const { get_user_usage } = actor; + + const usageResponse = await get_user_usage(TEST_COLLECTION, toNullable()); + + const usage = fromNullable(usageResponse); + + assertNonNullish(usage); + + expect(usage.items_count).toEqual(countSetManyDocs + countSetDocs); + expect(usage.version).toEqual(toNullable(BigInt(countSetManyDocs + countSetDocs))); + }); + + const countDelDoc = 1; + + it('should get a usage count after delete document', async () => { + const { del_doc, list_docs } = actor; + + const { items } = await list_docs(TEST_COLLECTION, NO_FILTER_PARAMS); + + const doc = items[0][1]; + + await del_doc(TEST_COLLECTION, items[0][0], { + version: doc.version ?? [] + }); + + const { get_user_usage } = actor; + + const usageResponse = await get_user_usage(TEST_COLLECTION, toNullable()); + + const usage = fromNullable(usageResponse); + + assertNonNullish(usage); + + expect(usage.items_count).toEqual(countSetManyDocs + countSetDocs - countDelDoc); + expect(usage.version).toEqual( + toNullable(BigInt(countSetManyDocs + countSetDocs + countDelDoc)) + ); + }); + + const countDelManyDocs = 2; + + it('should get a usage count after delete many documents', async () => { + const { del_many_docs, list_docs } = actor; + + const { items } = await list_docs(TEST_COLLECTION, NO_FILTER_PARAMS); + + const docs: [string, string, DelDoc][] = [items[0], items[1]].map(([key, doc]) => [ + TEST_COLLECTION, + key, + { + version: doc.version ?? [] + } + ]); + + await del_many_docs(docs); + + const { get_user_usage } = actor; + + const usageResponse = await get_user_usage(TEST_COLLECTION, toNullable()); + + const usage = fromNullable(usageResponse); + + assertNonNullish(usage); + + expect(usage.items_count).toEqual( + countSetManyDocs + countSetDocs - countDelDoc - countDelManyDocs + ); + expect(usage.version).toEqual( + toNullable(BigInt(countSetManyDocs + countSetDocs + countDelDoc + countDelManyDocs)) + ); + }); + + it('should get a usage count after delete filtered docs', async () => { + const { del_filtered_docs } = actor; + + await del_filtered_docs(TEST_COLLECTION, NO_FILTER_PARAMS); + + const { get_user_usage } = actor; + + const usageResponse = await get_user_usage(TEST_COLLECTION, toNullable()); + + const usage = fromNullable(usageResponse); + + assertNonNullish(usage); + + expect(usage.items_count).toEqual(0); + expect(usage.version).toEqual( + toNullable(BigInt(countSetManyDocs + countSetDocs + countDelDoc + countDelManyDocs + 1)) + ); + }); + }); + + describe('Guards', () => { + const user1 = Ed25519KeyIdentity.generate(); + + const fetchUsage = async (userId?: Principal): Promise => { + const { get_user_usage } = actor; + + const usageResponse = await get_user_usage(TEST_COLLECTION, toNullable(userId)); + return fromNullable(usageResponse); + }; + + it('should not get usage of another user', async () => { + actor.setIdentity(user1); + + await createDoc(); + + const usage = await fetchUsage(); + + assertNonNullish(usage); + + expect(usage.items_count).toEqual(1); + + const user2 = Ed25519KeyIdentity.generate(); + + actor.setIdentity(user2); + + const usage2 = await fetchUsage(user1.getPrincipal()); + + expect(usage2).toBeUndefined(); + }); + + it('should get usage of user if controller', async () => { + actor.setIdentity(controller); + + const usage = await fetchUsage(user1.getPrincipal()); + + assertNonNullish(usage); + + expect(usage.items_count).toEqual(1); + }); + }); + }); +}); From eea66a16e4df99a6e3ab4c1af696ff92c3eea84f Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 07:13:42 +0100 Subject: [PATCH 12/33] feat: impl --- src/libs/satellite/src/usage/impls.rs | 12 +++++++++++- src/libs/satellite/src/usage/store.rs | 11 ++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/libs/satellite/src/usage/impls.rs b/src/libs/satellite/src/usage/impls.rs index 6b108aa02..eaa5e2281 100644 --- a/src/libs/satellite/src/usage/impls.rs +++ b/src/libs/satellite/src/usage/impls.rs @@ -5,8 +5,9 @@ use ic_stable_structures::storable::Bound; use ic_stable_structures::Storable; use junobuild_shared::constants::INITIAL_VERSION; use junobuild_shared::serializers::{deserialize_from_bytes, serialize_to_bytes}; -use junobuild_shared::types::state::{Timestamp, Version}; +use junobuild_shared::types::state::{Timestamp, UserId, Version}; use std::borrow::Cow; +use junobuild_collections::types::core::CollectionKey; impl Storable for UserUsage { fn to_bytes(&self) -> Cow<[u8]> { @@ -73,3 +74,12 @@ impl UserUsage { } } } + +impl UserUsageKey { + pub fn new(user_id: &UserId, collection: &CollectionKey) -> Self { + Self { + user_id: *user_id, + collection: collection.clone(), + } + } +} \ No newline at end of file diff --git a/src/libs/satellite/src/usage/store.rs b/src/libs/satellite/src/usage/store.rs index e8d0d22e3..90dd3baff 100644 --- a/src/libs/satellite/src/usage/store.rs +++ b/src/libs/satellite/src/usage/store.rs @@ -30,7 +30,7 @@ fn get_user_usage_impl( collection: &CollectionKey, state: &UserUsageStable, ) -> Option { - let key = stable_user_usage_key(user_id, collection); + let key = UserUsageKey::new(user_id, collection); state.get(&key) } @@ -42,7 +42,7 @@ fn update_user_usage_impl( count: Option, state: &mut UserUsageStable, ) { - let key = stable_user_usage_key(user_id, collection); + let key = UserUsageKey::new(user_id, collection); let current_usage = state.get(&key); @@ -50,10 +50,3 @@ fn update_user_usage_impl( state.insert(key, update_usage); } - -fn stable_user_usage_key(user_id: &UserId, collection: &CollectionKey) -> UserUsageKey { - UserUsageKey { - user_id: *user_id, - collection: collection.clone(), - } -} From 44fc9f70a9b06875ca18edaa8dfa3397b60bbdf8 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 08:19:43 +0100 Subject: [PATCH 13/33] feat: collection type in user usage key --- src/libs/satellite/src/lib.rs | 18 +++++--- src/libs/satellite/src/satellite.rs | 52 +++++++++++++--------- src/libs/satellite/src/types.rs | 4 +- src/libs/satellite/src/usage/impls.rs | 14 ++++-- src/libs/satellite/src/usage/store.rs | 32 +++++++++---- src/libs/satellite/src/usage/types.rs | 4 +- src/libs/satellite/src/usage/user_usage.rs | 52 ++++++++++++++++++---- 7 files changed, 125 insertions(+), 51 deletions(-) diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index 83df8b714..9a9f60102 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -19,7 +19,7 @@ mod version; use crate::auth::types::config::AuthenticationConfig; use crate::db::types::config::DbConfig; use crate::guards::{caller_is_admin_controller, caller_is_controller}; -use crate::types::interface::{Config, RulesType}; +use crate::types::interface::{CollectionType, Config}; use crate::version::SATELLITE_VERSION; use ic_cdk::api::trap; use ic_cdk_macros::{init, post_upgrade, pre_upgrade, query, update}; @@ -175,25 +175,25 @@ pub fn count_collection_docs(collection: CollectionKey) -> usize { #[doc(hidden)] #[query(guard = "caller_is_admin_controller")] -pub fn get_rule(rules_type: RulesType, collection: CollectionKey) -> Option { +pub fn get_rule(rules_type: CollectionType, collection: CollectionKey) -> Option { satellite::get_rule(&rules_type, &collection) } #[doc(hidden)] #[query(guard = "caller_is_admin_controller")] -pub fn list_rules(rules_type: RulesType) -> Vec<(CollectionKey, Rule)> { +pub fn list_rules(rules_type: CollectionType) -> Vec<(CollectionKey, Rule)> { satellite::list_rules(rules_type) } #[doc(hidden)] #[update(guard = "caller_is_admin_controller")] -pub fn set_rule(rules_type: RulesType, collection: CollectionKey, rule: SetRule) -> Rule { +pub fn set_rule(rules_type: CollectionType, collection: CollectionKey, rule: SetRule) -> Rule { satellite::set_rule(rules_type, collection, rule) } #[doc(hidden)] #[update(guard = "caller_is_admin_controller")] -pub fn del_rule(rules_type: RulesType, collection: CollectionKey, rule: DelRule) { +pub fn del_rule(rules_type: CollectionType, collection: CollectionKey, rule: DelRule) { satellite::del_rule(rules_type, collection, rule) } @@ -401,8 +401,12 @@ pub fn get_many_assets( #[doc(hidden)] #[query] -pub fn get_user_usage(collection: CollectionKey, user_id: Option) -> Option { - satellite::get_user_usage(&collection, &user_id) +pub fn get_user_usage( + collection_key: CollectionKey, + collection_type: CollectionType, + user_id: Option, +) -> Option { + satellite::get_user_usage(&collection_key, &collection_type, &user_id) } // --------------------------------------------------------- diff --git a/src/libs/satellite/src/satellite.rs b/src/libs/satellite/src/satellite.rs index cdb4d5a07..38df4ee1c 100644 --- a/src/libs/satellite/src/satellite.rs +++ b/src/libs/satellite/src/satellite.rs @@ -36,11 +36,12 @@ use crate::storage::store::{ set_domain_store, }; use crate::storage::strategy_impls::StorageState; -use crate::types::interface::{Config, RulesType}; +use crate::types::interface::{CollectionType, Config}; use crate::types::state::{HeapState, RuntimeState, State}; use crate::usage::types::state::UserUsage; use crate::usage::user_usage::{ - decrease_user_usage, decrease_user_usage_by, get_user_usage_by_id, increase_user_usage, + decrease_db_usage, decrease_db_usage_by, get_db_usage_by_id, get_storage_usage_by_id, + increase_db_usage, }; use ciborium::{from_reader, into_writer}; use ic_cdk::api::call::{arg_data, ArgDecoderConfig}; @@ -128,7 +129,7 @@ pub fn set_doc(collection: CollectionKey, key: Key, doc: SetDoc) -> Doc { match result { Ok(doc) => { - increase_user_usage(&caller, &collection); + increase_db_usage(&caller, &collection); invoke_on_set_doc(&caller, &doc); @@ -155,7 +156,7 @@ pub fn del_doc(collection: CollectionKey, key: Key, doc: DelDoc) { let deleted_doc = delete_doc_store(caller, collection.clone(), key, doc).unwrap_or_else(|e| trap(&e)); - decrease_user_usage(&caller, &collection); + decrease_db_usage(&caller, &collection); invoke_on_delete_doc(&caller, &deleted_doc); } @@ -201,7 +202,7 @@ pub fn set_many_docs(docs: Vec<(CollectionKey, Key, SetDoc)>) -> Vec<(Key, Doc)> let result = set_doc_store(caller, collection.clone(), key.clone(), doc) .unwrap_or_else(|e| trap(&e)); - increase_user_usage(&caller, &collection); + increase_db_usage(&caller, &collection); results.push((result.key.clone(), result.data.after.clone())); @@ -222,7 +223,7 @@ pub fn del_many_docs(docs: Vec<(CollectionKey, Key, DelDoc)>) { let deleted_doc = delete_doc_store(caller, collection.clone(), key.clone(), doc) .unwrap_or_else(|e| trap(&e)); - decrease_user_usage(&caller, &collection); + decrease_db_usage(&caller, &collection); results.push(deleted_doc); } @@ -236,7 +237,7 @@ pub fn del_filtered_docs(collection: CollectionKey, filter: ListParams) { let results = delete_filtered_docs_store(caller, collection.clone(), &filter) .unwrap_or_else(|e| trap(&e)); - decrease_user_usage_by(&caller, &collection, results.len() as u32); + decrease_db_usage_by(&caller, &collection, results.len() as u32); invoke_on_delete_filtered_docs(&caller, &results); } @@ -263,31 +264,31 @@ pub fn count_collection_docs(collection: CollectionKey) -> usize { // Rules // --------------------------------------------------------- -pub fn get_rule(rules_type: &RulesType, collection: &CollectionKey) -> Option { +pub fn get_rule(rules_type: &CollectionType, collection: &CollectionKey) -> Option { match rules_type { - RulesType::Db => get_rule_db(collection), - RulesType::Storage => get_rule_storage(collection), + CollectionType::Db => get_rule_db(collection), + CollectionType::Storage => get_rule_storage(collection), } } -pub fn list_rules(rules_type: RulesType) -> Vec<(CollectionKey, Rule)> { +pub fn list_rules(rules_type: CollectionType) -> Vec<(CollectionKey, Rule)> { match rules_type { - RulesType::Db => get_rules_db(), - RulesType::Storage => get_rules_storage(), + CollectionType::Db => get_rules_db(), + CollectionType::Storage => get_rules_storage(), } } -pub fn set_rule(rules_type: RulesType, collection: CollectionKey, rule: SetRule) -> Rule { +pub fn set_rule(rules_type: CollectionType, collection: CollectionKey, rule: SetRule) -> Rule { match rules_type { - RulesType::Db => set_rule_db(collection, rule).unwrap_or_else(|e| trap(&e)), - RulesType::Storage => set_rule_storage(collection, rule).unwrap_or_else(|e| trap(&e)), + CollectionType::Db => set_rule_db(collection, rule).unwrap_or_else(|e| trap(&e)), + CollectionType::Storage => set_rule_storage(collection, rule).unwrap_or_else(|e| trap(&e)), } } -pub fn del_rule(rules_type: RulesType, collection: CollectionKey, rule: DelRule) { +pub fn del_rule(rules_type: CollectionType, collection: CollectionKey, rule: DelRule) { match rules_type { - RulesType::Db => del_rule_db(collection, rule).unwrap_or_else(|e| trap(&e)), - RulesType::Storage => del_rule_storage(collection, rule).unwrap_or_else(|e| trap(&e)), + CollectionType::Db => del_rule_db(collection, rule).unwrap_or_else(|e| trap(&e)), + CollectionType::Storage => del_rule_storage(collection, rule).unwrap_or_else(|e| trap(&e)), } } @@ -549,7 +550,16 @@ pub fn get_many_assets( // User usage // --------------------------------------------------------- -pub fn get_user_usage(collection: &CollectionKey, user_id: &Option) -> Option { +pub fn get_user_usage( + collection: &CollectionKey, + collection_type: &CollectionType, + user_id: &Option, +) -> Option { let caller = caller(); - get_user_usage_by_id(collection, &user_id.unwrap_or(caller), caller) + let user_id_or_caller = user_id.unwrap_or(caller); + + match collection_type { + CollectionType::Db => get_db_usage_by_id(collection, &user_id_or_caller, caller), + CollectionType::Storage => get_storage_usage_by_id(collection, &user_id_or_caller, caller), + } } diff --git a/src/libs/satellite/src/types.rs b/src/libs/satellite/src/types.rs index ae92184e1..91d1860fd 100644 --- a/src/libs/satellite/src/types.rs +++ b/src/libs/satellite/src/types.rs @@ -53,8 +53,8 @@ pub mod interface { use junobuild_storage::types::config::StorageConfig; use serde::Deserialize; - #[derive(CandidType, Deserialize)] - pub enum RulesType { + #[derive(CandidType, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum CollectionType { Db, Storage, } diff --git a/src/libs/satellite/src/usage/impls.rs b/src/libs/satellite/src/usage/impls.rs index eaa5e2281..ce06b75fc 100644 --- a/src/libs/satellite/src/usage/impls.rs +++ b/src/libs/satellite/src/usage/impls.rs @@ -1,13 +1,14 @@ +use crate::types::interface::CollectionType; use crate::usage::types::interface::ModificationType; use crate::usage::types::state::{UserUsage, UserUsageKey}; use ic_cdk::api::time; use ic_stable_structures::storable::Bound; use ic_stable_structures::Storable; +use junobuild_collections::types::core::CollectionKey; use junobuild_shared::constants::INITIAL_VERSION; use junobuild_shared::serializers::{deserialize_from_bytes, serialize_to_bytes}; use junobuild_shared::types::state::{Timestamp, UserId, Version}; use std::borrow::Cow; -use junobuild_collections::types::core::CollectionKey; impl Storable for UserUsage { fn to_bytes(&self) -> Cow<[u8]> { @@ -76,10 +77,15 @@ impl UserUsage { } impl UserUsageKey { - pub fn new(user_id: &UserId, collection: &CollectionKey) -> Self { + pub fn new( + user_id: &UserId, + collection_key: &CollectionKey, + collection_type: &CollectionType, + ) -> Self { Self { user_id: *user_id, - collection: collection.clone(), + collection_key: collection_key.clone(), + collection_type: collection_type.clone(), } } -} \ No newline at end of file +} diff --git a/src/libs/satellite/src/usage/store.rs b/src/libs/satellite/src/usage/store.rs index 90dd3baff..6bbc69316 100644 --- a/src/libs/satellite/src/usage/store.rs +++ b/src/libs/satellite/src/usage/store.rs @@ -1,23 +1,37 @@ use crate::memory::STATE; +use crate::types::interface::CollectionType; use crate::usage::types::interface::ModificationType; use crate::usage::types::state::{UserUsage, UserUsageKey, UserUsageStable}; use junobuild_collections::types::core::CollectionKey; use junobuild_shared::types::state::UserId; -pub fn get_user_usage(user_id: &UserId, collection: &CollectionKey) -> Option { - STATE.with(|state| get_user_usage_impl(user_id, collection, &state.borrow().stable.user_usage)) +pub fn get_user_usage( + user_id: &UserId, + collection_key: &CollectionKey, + collection_type: &CollectionType, +) -> Option { + STATE.with(|state| { + get_user_usage_impl( + user_id, + collection_key, + collection_type, + &state.borrow().stable.user_usage, + ) + }) } pub fn update_user_usage( user_id: &UserId, - collection: &CollectionKey, + collection_key: &CollectionKey, + collection_type: &CollectionType, modification: &ModificationType, count: Option, ) { STATE.with(|state| { update_user_usage_impl( user_id, - collection, + collection_key, + collection_type, modification, count, &mut state.borrow_mut().stable.user_usage, @@ -27,22 +41,24 @@ pub fn update_user_usage( fn get_user_usage_impl( user_id: &UserId, - collection: &CollectionKey, + collection_key: &CollectionKey, + collection_type: &CollectionType, state: &UserUsageStable, ) -> Option { - let key = UserUsageKey::new(user_id, collection); + let key = UserUsageKey::new(user_id, collection_key, collection_type); state.get(&key) } fn update_user_usage_impl( user_id: &UserId, - collection: &CollectionKey, + collection_key: &CollectionKey, + collection_type: &CollectionType, modification: &ModificationType, count: Option, state: &mut UserUsageStable, ) { - let key = UserUsageKey::new(user_id, collection); + let key = UserUsageKey::new(user_id, collection_key, collection_type); let current_usage = state.get(&key); diff --git a/src/libs/satellite/src/usage/types.rs b/src/libs/satellite/src/usage/types.rs index 55c727adc..10764f6ca 100644 --- a/src/libs/satellite/src/usage/types.rs +++ b/src/libs/satellite/src/usage/types.rs @@ -1,4 +1,5 @@ pub mod state { + use crate::types::interface::CollectionType; use candid::{CandidType, Deserialize}; use ic_stable_structures::StableBTreeMap; use junobuild_collections::types::core::CollectionKey; @@ -11,7 +12,8 @@ pub mod state { #[derive(CandidType, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct UserUsageKey { pub user_id: UserId, - pub collection: CollectionKey, + pub collection_key: CollectionKey, + pub collection_type: CollectionType, } #[derive(CandidType, Serialize, Deserialize, Clone)] diff --git a/src/libs/satellite/src/usage/user_usage.rs b/src/libs/satellite/src/usage/user_usage.rs index 93e8c2fb8..7a810b539 100644 --- a/src/libs/satellite/src/usage/user_usage.rs +++ b/src/libs/satellite/src/usage/user_usage.rs @@ -1,4 +1,5 @@ use crate::get_controllers; +use crate::types::interface::CollectionType; use crate::usage::store; use crate::usage::store::get_user_usage as get_user_usage_store; use crate::usage::types::interface::ModificationType; @@ -9,28 +10,63 @@ use junobuild_shared::controllers::is_controller; use junobuild_shared::types::state::{Controllers, UserId}; use junobuild_shared::utils::principal_not_anonymous_and_equal; -pub fn get_user_usage_by_id( +pub fn get_db_usage_by_id( collection: &CollectionKey, user_id: &UserId, caller: Principal, +) -> Option { + get_user_usage_by_id(collection, &CollectionType::Db, user_id, caller) +} + +pub fn get_storage_usage_by_id( + collection: &CollectionKey, + user_id: &UserId, + caller: Principal, +) -> Option { + get_user_usage_by_id(collection, &CollectionType::Storage, user_id, caller) +} + +fn get_user_usage_by_id( + collection_key: &CollectionKey, + collection_type: &CollectionType, + user_id: &UserId, + caller: Principal, ) -> Option { let controllers: Controllers = get_controllers(); if principal_not_anonymous_and_equal(*user_id, caller) || is_controller(caller, &controllers) { - return get_user_usage_store(user_id, collection); + return get_user_usage_store(user_id, collection_key, collection_type); } None } -pub fn increase_user_usage(user_id: &UserId, collection: &CollectionKey) { - store::update_user_usage(user_id, collection, &ModificationType::Set, None); +pub fn increase_db_usage(user_id: &UserId, collection: &CollectionKey) { + store::update_user_usage( + user_id, + collection, + &CollectionType::Db, + &ModificationType::Set, + None, + ); } -pub fn decrease_user_usage(user_id: &UserId, collection: &CollectionKey) { - store::update_user_usage(user_id, collection, &ModificationType::Delete, None); +pub fn decrease_db_usage(user_id: &UserId, collection: &CollectionKey) { + store::update_user_usage( + user_id, + collection, + &CollectionType::Db, + &ModificationType::Delete, + None, + ); } -pub fn decrease_user_usage_by(user_id: &UserId, collection: &CollectionKey, count: u32) { - store::update_user_usage(user_id, collection, &ModificationType::Delete, Some(count)); +pub fn decrease_db_usage_by(user_id: &UserId, collection: &CollectionKey, count: u32) { + store::update_user_usage( + user_id, + collection, + &CollectionType::Db, + &ModificationType::Delete, + Some(count), + ); } From 8142dff38325707600db5efe6a98f17cb88e3d65 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 08:39:48 +0100 Subject: [PATCH 14/33] feat: move collectiontype to state --- src/libs/satellite/src/lib.rs | 5 +++-- src/libs/satellite/src/satellite.rs | 4 ++-- src/libs/satellite/src/types.rs | 12 ++++++------ src/libs/satellite/src/usage/impls.rs | 2 +- src/libs/satellite/src/usage/store.rs | 2 +- src/libs/satellite/src/usage/types.rs | 2 +- src/libs/satellite/src/usage/user_usage.rs | 2 +- 7 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index b46b705a3..2f830029e 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -19,7 +19,9 @@ mod version; use crate::auth::types::config::AuthenticationConfig; use crate::db::types::config::DbConfig; use crate::guards::{caller_is_admin_controller, caller_is_controller}; -use crate::types::interface::{CollectionType, Config}; +use crate::types::interface::Config; +use crate::types::state::CollectionType; +use crate::usage::types::state::UserUsage; use crate::version::SATELLITE_VERSION; use ic_cdk::api::trap; use ic_cdk_macros::{init, post_upgrade, pre_upgrade, query, update}; @@ -43,7 +45,6 @@ use junobuild_storage::types::interface::{ AssetNoContent, CommitBatch, InitAssetKey, InitUploadResult, UploadChunk, UploadChunkResult, }; use junobuild_storage::types::state::FullPath; -use crate::usage::types::state::UserUsage; // ============================================================================================ // START: Re-exported Types diff --git a/src/libs/satellite/src/satellite.rs b/src/libs/satellite/src/satellite.rs index 09293ae67..5b9c63401 100644 --- a/src/libs/satellite/src/satellite.rs +++ b/src/libs/satellite/src/satellite.rs @@ -36,8 +36,8 @@ use crate::storage::store::{ set_domain_store, }; use crate::storage::strategy_impls::StorageState; -use crate::types::interface::{CollectionType, Config}; -use crate::types::state::{HeapState, RuntimeState, State}; +use crate::types::interface::Config; +use crate::types::state::{CollectionType, HeapState, RuntimeState, State}; use crate::usage::types::state::UserUsage; use crate::usage::user_usage::{ decrease_db_usage, decrease_db_usage_by, get_db_usage_by_id, get_storage_usage_by_id, diff --git a/src/libs/satellite/src/types.rs b/src/libs/satellite/src/types.rs index 91d1860fd..e295a5981 100644 --- a/src/libs/satellite/src/types.rs +++ b/src/libs/satellite/src/types.rs @@ -44,6 +44,12 @@ pub mod state { pub rng: Option, // rng = Random Number Generator pub db: DbRuntimeState, } + + #[derive(CandidType, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + pub enum CollectionType { + Db, + Storage, + } } pub mod interface { @@ -53,12 +59,6 @@ pub mod interface { use junobuild_storage::types::config::StorageConfig; use serde::Deserialize; - #[derive(CandidType, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub enum CollectionType { - Db, - Storage, - } - #[derive(CandidType, Deserialize)] pub struct Config { pub storage: StorageConfig, diff --git a/src/libs/satellite/src/usage/impls.rs b/src/libs/satellite/src/usage/impls.rs index ce06b75fc..4fc1b1dcd 100644 --- a/src/libs/satellite/src/usage/impls.rs +++ b/src/libs/satellite/src/usage/impls.rs @@ -1,4 +1,4 @@ -use crate::types::interface::CollectionType; +use crate::types::state::CollectionType; use crate::usage::types::interface::ModificationType; use crate::usage::types::state::{UserUsage, UserUsageKey}; use ic_cdk::api::time; diff --git a/src/libs/satellite/src/usage/store.rs b/src/libs/satellite/src/usage/store.rs index 6bbc69316..c2f4d947c 100644 --- a/src/libs/satellite/src/usage/store.rs +++ b/src/libs/satellite/src/usage/store.rs @@ -1,5 +1,5 @@ use crate::memory::STATE; -use crate::types::interface::CollectionType; +use crate::types::state::CollectionType; use crate::usage::types::interface::ModificationType; use crate::usage::types::state::{UserUsage, UserUsageKey, UserUsageStable}; use junobuild_collections::types::core::CollectionKey; diff --git a/src/libs/satellite/src/usage/types.rs b/src/libs/satellite/src/usage/types.rs index 10764f6ca..7323d9215 100644 --- a/src/libs/satellite/src/usage/types.rs +++ b/src/libs/satellite/src/usage/types.rs @@ -1,5 +1,5 @@ pub mod state { - use crate::types::interface::CollectionType; + use crate::types::state::CollectionType; use candid::{CandidType, Deserialize}; use ic_stable_structures::StableBTreeMap; use junobuild_collections::types::core::CollectionKey; diff --git a/src/libs/satellite/src/usage/user_usage.rs b/src/libs/satellite/src/usage/user_usage.rs index 7a810b539..9b7e44231 100644 --- a/src/libs/satellite/src/usage/user_usage.rs +++ b/src/libs/satellite/src/usage/user_usage.rs @@ -1,5 +1,5 @@ use crate::get_controllers; -use crate::types::interface::CollectionType; +use crate::types::state::CollectionType; use crate::usage::store; use crate::usage::store::get_user_usage as get_user_usage_store; use crate::usage::types::interface::ModificationType; From 8d38a2e221d4237b30d6514784302efab897bf13 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 08:41:02 +0100 Subject: [PATCH 15/33] feat: did --- src/declarations/satellite/satellite.did.d.ts | 2 +- .../satellite/satellite.factory.certified.did.js | 6 +++++- src/declarations/satellite/satellite.factory.did.js | 6 +++++- src/declarations/satellite/satellite.factory.did.mjs | 6 +++++- src/libs/satellite/satellite.did | 4 +++- src/satellite/satellite.did | 4 +++- 6 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/declarations/satellite/satellite.did.d.ts b/src/declarations/satellite/satellite.did.d.ts index 21ea50c1c..09352ad6b 100644 --- a/src/declarations/satellite/satellite.did.d.ts +++ b/src/declarations/satellite/satellite.did.d.ts @@ -271,7 +271,7 @@ export interface _SERVICE { get_many_docs: ActorMethod<[Array<[string, string]>], Array<[string, [] | [Doc]]>>; get_rule: ActorMethod<[CollectionType, string], [] | [Rule]>; get_storage_config: ActorMethod<[], StorageConfig>; - get_user_usage: ActorMethod<[string, [] | [Principal]], [] | [UserUsage]>; + get_user_usage: ActorMethod<[string, CollectionType, [] | [Principal]], [] | [UserUsage]>; http_request: ActorMethod<[HttpRequest], HttpResponse>; http_request_streaming_callback: ActorMethod< [StreamingCallbackToken], diff --git a/src/declarations/satellite/satellite.factory.certified.did.js b/src/declarations/satellite/satellite.factory.certified.did.js index 5daf333ac..4dd86d3b0 100644 --- a/src/declarations/satellite/satellite.factory.certified.did.js +++ b/src/declarations/satellite/satellite.factory.certified.did.js @@ -283,7 +283,11 @@ export const idlFactory = ({ IDL }) => { ), get_rule: IDL.Func([CollectionType, IDL.Text], [IDL.Opt(Rule)], []), get_storage_config: IDL.Func([], [StorageConfig], []), - get_user_usage: IDL.Func([IDL.Text, IDL.Opt(IDL.Principal)], [IDL.Opt(UserUsage)], []), + get_user_usage: IDL.Func( + [IDL.Text, CollectionType, IDL.Opt(IDL.Principal)], + [IDL.Opt(UserUsage)], + [] + ), http_request: IDL.Func([HttpRequest], [HttpResponse], []), http_request_streaming_callback: IDL.Func( [StreamingCallbackToken], diff --git a/src/declarations/satellite/satellite.factory.did.js b/src/declarations/satellite/satellite.factory.did.js index e33c94bc6..9deae27ca 100644 --- a/src/declarations/satellite/satellite.factory.did.js +++ b/src/declarations/satellite/satellite.factory.did.js @@ -283,7 +283,11 @@ export const idlFactory = ({ IDL }) => { ), get_rule: IDL.Func([CollectionType, IDL.Text], [IDL.Opt(Rule)], ['query']), get_storage_config: IDL.Func([], [StorageConfig], ['query']), - get_user_usage: IDL.Func([IDL.Text, IDL.Opt(IDL.Principal)], [IDL.Opt(UserUsage)], ['query']), + get_user_usage: IDL.Func( + [IDL.Text, CollectionType, IDL.Opt(IDL.Principal)], + [IDL.Opt(UserUsage)], + ['query'] + ), http_request: IDL.Func([HttpRequest], [HttpResponse], ['query']), http_request_streaming_callback: IDL.Func( [StreamingCallbackToken], diff --git a/src/declarations/satellite/satellite.factory.did.mjs b/src/declarations/satellite/satellite.factory.did.mjs index e33c94bc6..9deae27ca 100644 --- a/src/declarations/satellite/satellite.factory.did.mjs +++ b/src/declarations/satellite/satellite.factory.did.mjs @@ -283,7 +283,11 @@ export const idlFactory = ({ IDL }) => { ), get_rule: IDL.Func([CollectionType, IDL.Text], [IDL.Opt(Rule)], ['query']), get_storage_config: IDL.Func([], [StorageConfig], ['query']), - get_user_usage: IDL.Func([IDL.Text, IDL.Opt(IDL.Principal)], [IDL.Opt(UserUsage)], ['query']), + get_user_usage: IDL.Func( + [IDL.Text, CollectionType, IDL.Opt(IDL.Principal)], + [IDL.Opt(UserUsage)], + ['query'] + ), http_request: IDL.Func([HttpRequest], [HttpResponse], ['query']), http_request_streaming_callback: IDL.Func( [StreamingCallbackToken], diff --git a/src/libs/satellite/satellite.did b/src/libs/satellite/satellite.did index 628342489..2c1c4bae0 100644 --- a/src/libs/satellite/satellite.did +++ b/src/libs/satellite/satellite.did @@ -236,7 +236,9 @@ service : () -> { ) query; get_rule : (CollectionType, text) -> (opt Rule) query; get_storage_config : () -> (StorageConfig) query; - get_user_usage : (text, opt principal) -> (opt UserUsage) query; + get_user_usage : (text, CollectionType, opt principal) -> ( + opt UserUsage, + ) query; http_request : (HttpRequest) -> (HttpResponse) query; http_request_streaming_callback : (StreamingCallbackToken) -> ( StreamingCallbackHttpResponse, diff --git a/src/satellite/satellite.did b/src/satellite/satellite.did index 0e955ecda..f6b146728 100644 --- a/src/satellite/satellite.did +++ b/src/satellite/satellite.did @@ -238,7 +238,9 @@ service : () -> { ) query; get_rule : (CollectionType, text) -> (opt Rule) query; get_storage_config : () -> (StorageConfig) query; - get_user_usage : (text, opt principal) -> (opt UserUsage) query; + get_user_usage : (text, CollectionType, opt principal) -> ( + opt UserUsage, + ) query; http_request : (HttpRequest) -> (HttpResponse) query; http_request_streaming_callback : (StreamingCallbackToken) -> ( StreamingCallbackHttpResponse, From a739522d40afc8143d92a909a99fec45f2595ec5 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 08:41:55 +0100 Subject: [PATCH 16/33] test: type --- src/tests/satellite.user-usage.spec.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/tests/satellite.user-usage.spec.ts b/src/tests/satellite.user-usage.spec.ts index 8489fbe42..b8fbbac39 100644 --- a/src/tests/satellite.user-usage.spec.ts +++ b/src/tests/satellite.user-usage.spec.ts @@ -66,6 +66,8 @@ describe('Satellite User Usage', () => { hello: 'World' }); + const COLLECTION_TYPE = { Db: null }; + beforeAll(async () => { const { set_rule } = actor; await set_rule({ Db: null }, TEST_COLLECTION, setRule); @@ -99,7 +101,7 @@ describe('Satellite User Usage', () => { const { get_user_usage } = actor; - const usageResponse = await get_user_usage(TEST_COLLECTION, toNullable()); + const usageResponse = await get_user_usage(TEST_COLLECTION, COLLECTION_TYPE, toNullable()); const usage = fromNullable(usageResponse); @@ -137,7 +139,7 @@ describe('Satellite User Usage', () => { const { get_user_usage } = actor; - const usageResponse = await get_user_usage(TEST_COLLECTION, toNullable()); + const usageResponse = await get_user_usage(TEST_COLLECTION, COLLECTION_TYPE, toNullable()); const usage = fromNullable(usageResponse); @@ -162,7 +164,7 @@ describe('Satellite User Usage', () => { const { get_user_usage } = actor; - const usageResponse = await get_user_usage(TEST_COLLECTION, toNullable()); + const usageResponse = await get_user_usage(TEST_COLLECTION, COLLECTION_TYPE, toNullable()); const usage = fromNullable(usageResponse); @@ -193,7 +195,7 @@ describe('Satellite User Usage', () => { const { get_user_usage } = actor; - const usageResponse = await get_user_usage(TEST_COLLECTION, toNullable()); + const usageResponse = await get_user_usage(TEST_COLLECTION, COLLECTION_TYPE, toNullable()); const usage = fromNullable(usageResponse); @@ -214,7 +216,7 @@ describe('Satellite User Usage', () => { const { get_user_usage } = actor; - const usageResponse = await get_user_usage(TEST_COLLECTION, toNullable()); + const usageResponse = await get_user_usage(TEST_COLLECTION, COLLECTION_TYPE, toNullable()); const usage = fromNullable(usageResponse); @@ -233,7 +235,11 @@ describe('Satellite User Usage', () => { const fetchUsage = async (userId?: Principal): Promise => { const { get_user_usage } = actor; - const usageResponse = await get_user_usage(TEST_COLLECTION, toNullable(userId)); + const usageResponse = await get_user_usage( + TEST_COLLECTION, + COLLECTION_TYPE, + toNullable(userId) + ); return fromNullable(usageResponse); }; From f954fb59c27ff452eba4238cadaf8fe95cddfe72 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 09:05:56 +0100 Subject: [PATCH 17/33] feat: storage usage --- src/libs/satellite/src/satellite.rs | 21 +++++++++++---- src/libs/satellite/src/usage/user_usage.rs | 30 ++++++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/libs/satellite/src/satellite.rs b/src/libs/satellite/src/satellite.rs index 5b9c63401..0a71c212f 100644 --- a/src/libs/satellite/src/satellite.rs +++ b/src/libs/satellite/src/satellite.rs @@ -40,8 +40,8 @@ use crate::types::interface::Config; use crate::types::state::{CollectionType, HeapState, RuntimeState, State}; use crate::usage::types::state::UserUsage; use crate::usage::user_usage::{ - decrease_db_usage, decrease_db_usage_by, get_db_usage_by_id, get_storage_usage_by_id, - increase_db_usage, + decrease_db_usage, decrease_db_usage_by, decrease_storage_usage, decrease_storage_usage_by, + get_db_usage_by_id, get_storage_usage_by_id, increase_db_usage, increase_storage_usage, }; use ciborium::{from_reader, into_writer}; use ic_cdk::api::call::{arg_data, ArgDecoderConfig}; @@ -446,6 +446,8 @@ pub fn commit_asset_upload(commit: CommitBatch) { let asset = commit_batch_store(caller, commit).unwrap_or_else(|e| trap(&e)); + increase_storage_usage(&caller, &asset.key.collection); + invoke_upload_asset(&caller, &asset); } @@ -477,7 +479,11 @@ pub fn del_asset(collection: CollectionKey, full_path: FullPath) { let result = delete_asset_store(caller, &collection, full_path); match result { - Ok(asset) => invoke_on_delete_asset(&caller, &asset), + Ok(asset) => { + decrease_storage_usage(&caller, &collection); + + invoke_on_delete_asset(&caller, &asset) + } Err(error) => trap(&["Asset cannot be deleted: ", &error].join("")), } } @@ -490,6 +496,9 @@ pub fn del_many_assets(assets: Vec<(CollectionKey, String)>) { for (collection, full_path) in assets { let deleted_asset = delete_asset_store(caller, &collection, full_path).unwrap_or_else(|e| trap(&e)); + + decrease_storage_usage(&caller, &collection); + results.push(deleted_asset); } @@ -499,8 +508,10 @@ pub fn del_many_assets(assets: Vec<(CollectionKey, String)>) { pub fn del_filtered_assets(collection: CollectionKey, filter: ListParams) { let caller = caller(); - let results = - delete_filtered_assets_store(caller, collection, &filter).unwrap_or_else(|e| trap(&e)); + let results = delete_filtered_assets_store(caller, collection.clone(), &filter) + .unwrap_or_else(|e| trap(&e)); + + decrease_storage_usage_by(&caller, &collection, results.len() as u32); invoke_on_delete_filtered_assets(&caller, &results); } diff --git a/src/libs/satellite/src/usage/user_usage.rs b/src/libs/satellite/src/usage/user_usage.rs index 9b7e44231..a10d3726f 100644 --- a/src/libs/satellite/src/usage/user_usage.rs +++ b/src/libs/satellite/src/usage/user_usage.rs @@ -70,3 +70,33 @@ pub fn decrease_db_usage_by(user_id: &UserId, collection: &CollectionKey, count: Some(count), ); } + +pub fn increase_storage_usage(user_id: &UserId, collection: &CollectionKey) { + store::update_user_usage( + user_id, + collection, + &CollectionType::Storage, + &ModificationType::Set, + None, + ); +} + +pub fn decrease_storage_usage(user_id: &UserId, collection: &CollectionKey) { + store::update_user_usage( + user_id, + collection, + &CollectionType::Storage, + &ModificationType::Delete, + None, + ); +} + +pub fn decrease_storage_usage_by(user_id: &UserId, collection: &CollectionKey, count: u32) { + store::update_user_usage( + user_id, + collection, + &CollectionType::Storage, + &ModificationType::Delete, + Some(count), + ); +} From 7af0bf8fe47e5f08e1af0b8043d000f1ac3cd2ef Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 09:12:13 +0100 Subject: [PATCH 18/33] test: wip --- src/tests/satellite.user-usage.spec.ts | 50 ++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/tests/satellite.user-usage.spec.ts b/src/tests/satellite.user-usage.spec.ts index b8fbbac39..29b80ec3b 100644 --- a/src/tests/satellite.user-usage.spec.ts +++ b/src/tests/satellite.user-usage.spec.ts @@ -274,4 +274,54 @@ describe('Satellite User Usage', () => { }); }); }); + + describe('Storage', async () => { + + const COLLECTION_TYPE = {Storage: null}; + + describe('User', () => { + const user = Ed25519KeyIdentity.generate(); + + beforeAll(() => { + actor.setIdentity(user); + }); + + const uploadAsset = async () => { + const { init_asset_upload, upload_asset_chunk } = actor; + + const batch = await init_asset_upload({ + collection: '#dapp', + description: toNullable(), + encoding_type: [], + full_path: '/hello.html', + name: 'hello.html', + token: toNullable() + }); + } + + const countSetAssets = 10; + + it('should get a usage count after update asset', async () => { + await Promise.all(Array.from({length: countSetDocs}).map(createDoc)); + + const {get_user_usage} = actor; + + const usageResponse = await get_user_usage(TEST_COLLECTION, COLLECTION_TYPE, toNullable()); + + const usage = fromNullable(usageResponse); + + assertNonNullish(usage); + + expect(usage.items_count).toEqual(countSetDocs); + + expect(usage.updated_at).not.toBeUndefined(); + expect(usage.updated_at).toBeGreaterThan(0n); + expect(usage.created_at).not.toBeUndefined(); + expect(usage.created_at).toBeGreaterThan(0n); + expect(usage.updated_at).toBeGreaterThan(usage.created_at); + + expect(usage.version).toEqual(toNullable(BigInt(countSetDocs))); + }); + }); + }); }); From 5863bfc8df369b3105744d53c58f00c581c090c2 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 09:50:45 +0100 Subject: [PATCH 19/33] test: wip --- src/tests/satellite.user-usage.spec.ts | 71 +++++++++++++++++++------- 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/src/tests/satellite.user-usage.spec.ts b/src/tests/satellite.user-usage.spec.ts index 29b80ec3b..b1705c354 100644 --- a/src/tests/satellite.user-usage.spec.ts +++ b/src/tests/satellite.user-usage.spec.ts @@ -13,7 +13,8 @@ import { assertNonNullish, fromNullable, toNullable } from '@dfinity/utils'; import { type Actor, PocketIc } from '@hadronous/pic'; import { toArray } from '@junobuild/utils'; import { nanoid } from 'nanoid'; -import { afterAll, beforeAll, expect, inject } from 'vitest'; +import {afterAll, beforeAll, describe, expect, inject} from 'vitest'; +import { uploadAsset } from './utils/satellite-storage-tests.utils'; import { controllersInitArgs, SATELLITE_WASM_PATH } from './utils/setup-tests.utils'; describe('Satellite User Usage', () => { @@ -61,6 +62,22 @@ describe('Satellite User Usage', () => { await pic?.tearDown(); }); + describe.each([ + { title: "Datastore", features: [] }, + { + title: "Storage", + features: [ + { + page_views: false, + performance_metrics: false, + track_events: false + } + ] + } + ])('%s', ({ title }) => { + + }); + describe('Datastore', async () => { const data = await toArray({ hello: 'World' @@ -70,7 +87,7 @@ describe('Satellite User Usage', () => { beforeAll(async () => { const { set_rule } = actor; - await set_rule({ Db: null }, TEST_COLLECTION, setRule); + await set_rule(COLLECTION_TYPE, TEST_COLLECTION, setRule); }); const createDoc = async (): Promise => { @@ -276,35 +293,53 @@ describe('Satellite User Usage', () => { }); describe('Storage', async () => { + const COLLECTION_TYPE = { Storage: null }; - const COLLECTION_TYPE = {Storage: null}; + beforeAll(async () => { + actor.setIdentity(controller); + + const { set_rule } = actor; + await set_rule(COLLECTION_TYPE, TEST_COLLECTION, setRule); + }); describe('User', () => { const user = Ed25519KeyIdentity.generate(); - beforeAll(() => { + beforeAll(async () => { actor.setIdentity(user); - }); - const uploadAsset = async () => { - const { init_asset_upload, upload_asset_chunk } = actor; + // We need a user entry to upload to the storage, there is a guard + const { set_doc } = actor; - const batch = await init_asset_upload({ - collection: '#dapp', + await set_doc('#user', user.getPrincipal().toText(), { + data: await toArray({ + provider: 'internet_identity' + }), description: toNullable(), - encoding_type: [], - full_path: '/hello.html', - name: 'hello.html', - token: toNullable() + version: toNullable() }); - } + }); + + const upload = async (index: number) => { + const name = `hello-${index}.html`; + const full_path = `/${TEST_COLLECTION}/${name}`; + + await uploadAsset({ + full_path, + name, + collection: TEST_COLLECTION, + actor + }); + }; const countSetAssets = 10; it('should get a usage count after update asset', async () => { - await Promise.all(Array.from({length: countSetDocs}).map(createDoc)); + await Promise.all( + Array.from({ length: countSetAssets }).map(async (_, i) => await upload(i)) + ); - const {get_user_usage} = actor; + const { get_user_usage } = actor; const usageResponse = await get_user_usage(TEST_COLLECTION, COLLECTION_TYPE, toNullable()); @@ -312,7 +347,7 @@ describe('Satellite User Usage', () => { assertNonNullish(usage); - expect(usage.items_count).toEqual(countSetDocs); + expect(usage.items_count).toEqual(countSetAssets); expect(usage.updated_at).not.toBeUndefined(); expect(usage.updated_at).toBeGreaterThan(0n); @@ -320,7 +355,7 @@ describe('Satellite User Usage', () => { expect(usage.created_at).toBeGreaterThan(0n); expect(usage.updated_at).toBeGreaterThan(usage.created_at); - expect(usage.version).toEqual(toNullable(BigInt(countSetDocs))); + expect(usage.version).toEqual(toNullable(BigInt(countSetAssets))); }); }); }); From 667296432859c4b33a3eadfeff2f19dcab4cf481 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 10:01:39 +0100 Subject: [PATCH 20/33] test: usage storage --- src/tests/satellite.user-usage.spec.ts | 197 +++++++++++++++++++------ 1 file changed, 154 insertions(+), 43 deletions(-) diff --git a/src/tests/satellite.user-usage.spec.ts b/src/tests/satellite.user-usage.spec.ts index b1705c354..c09daa941 100644 --- a/src/tests/satellite.user-usage.spec.ts +++ b/src/tests/satellite.user-usage.spec.ts @@ -13,7 +13,7 @@ import { assertNonNullish, fromNullable, toNullable } from '@dfinity/utils'; import { type Actor, PocketIc } from '@hadronous/pic'; import { toArray } from '@junobuild/utils'; import { nanoid } from 'nanoid'; -import {afterAll, beforeAll, describe, expect, inject} from 'vitest'; +import {inject} from 'vitest'; import { uploadAsset } from './utils/satellite-storage-tests.utils'; import { controllersInitArgs, SATELLITE_WASM_PATH } from './utils/setup-tests.utils'; @@ -62,22 +62,6 @@ describe('Satellite User Usage', () => { await pic?.tearDown(); }); - describe.each([ - { title: "Datastore", features: [] }, - { - title: "Storage", - features: [ - { - page_views: false, - performance_metrics: false, - track_events: false - } - ] - } - ])('%s', ({ title }) => { - - }); - describe('Datastore', async () => { const data = await toArray({ hello: 'World' @@ -302,41 +286,45 @@ describe('Satellite User Usage', () => { await set_rule(COLLECTION_TYPE, TEST_COLLECTION, setRule); }); + const upload = async (index: number) => { + const name = `hello-${index}.html`; + const full_path = `/${TEST_COLLECTION}/${name}`; + + await uploadAsset({ + full_path, + name, + collection: TEST_COLLECTION, + actor + }); + }; + + const createUser = async (user: Principal) => { + // We need a user entry to upload to the storage, there is a guard + const { set_doc } = actor; + + await set_doc('#user', user.toText(), { + data: await toArray({ + provider: 'internet_identity' + }), + description: toNullable(), + version: toNullable() + }); + } + describe('User', () => { const user = Ed25519KeyIdentity.generate(); beforeAll(async () => { actor.setIdentity(user); - // We need a user entry to upload to the storage, there is a guard - const { set_doc } = actor; - - await set_doc('#user', user.getPrincipal().toText(), { - data: await toArray({ - provider: 'internet_identity' - }), - description: toNullable(), - version: toNullable() - }); + await createUser(user.getPrincipal()); }); - const upload = async (index: number) => { - const name = `hello-${index}.html`; - const full_path = `/${TEST_COLLECTION}/${name}`; - - await uploadAsset({ - full_path, - name, - collection: TEST_COLLECTION, - actor - }); - }; - - const countSetAssets = 10; + const countUploadAssets = 10; it('should get a usage count after update asset', async () => { await Promise.all( - Array.from({ length: countSetAssets }).map(async (_, i) => await upload(i)) + Array.from({ length: countUploadAssets }).map(async (_, i) => await upload(i)) ); const { get_user_usage } = actor; @@ -347,7 +335,7 @@ describe('Satellite User Usage', () => { assertNonNullish(usage); - expect(usage.items_count).toEqual(countSetAssets); + expect(usage.items_count).toEqual(countUploadAssets); expect(usage.updated_at).not.toBeUndefined(); expect(usage.updated_at).toBeGreaterThan(0n); @@ -355,7 +343,130 @@ describe('Satellite User Usage', () => { expect(usage.created_at).toBeGreaterThan(0n); expect(usage.updated_at).toBeGreaterThan(usage.created_at); - expect(usage.version).toEqual(toNullable(BigInt(countSetAssets))); + expect(usage.version).toEqual(toNullable(BigInt(countUploadAssets))); + }); + + const countDelAsset = 1; + + it('should get a usage count after delete one asset', async () => { + const { del_asset, list_assets } = actor; + + const { items } = await list_assets(TEST_COLLECTION, NO_FILTER_PARAMS); + + const asset = items[0][1]; + + await del_asset(TEST_COLLECTION, asset.key.full_path); + + const { get_user_usage } = actor; + + const usageResponse = await get_user_usage(TEST_COLLECTION, COLLECTION_TYPE, toNullable()); + + const usage = fromNullable(usageResponse); + + assertNonNullish(usage); + + expect(usage.items_count).toEqual(countUploadAssets - countDelAsset); + expect(usage.version).toEqual( + toNullable(BigInt(countUploadAssets + countDelAsset)) + ); + }); + + const countDelManyAssets = 2; + + it('should get a usage count after delete many assets', async () => { + const { del_many_assets, list_assets } = actor; + + const { items } = await list_assets(TEST_COLLECTION, NO_FILTER_PARAMS); + + const assets: [string, string][] = [items[0], items[1]].map(([_, asset]) => [ + TEST_COLLECTION, + asset.key.full_path + ]); + + await del_many_assets(assets); + + const { get_user_usage } = actor; + + const usageResponse = await get_user_usage(TEST_COLLECTION, COLLECTION_TYPE, toNullable()); + + const usage = fromNullable(usageResponse); + + assertNonNullish(usage); + + expect(usage.items_count).toEqual( + countUploadAssets - countDelAsset - countDelManyAssets + ); + expect(usage.version).toEqual( + toNullable(BigInt(countUploadAssets + countDelAsset + countDelManyAssets)) + ); + }); + + it('should get a usage count after delete filtered assets', async () => { + const { del_filtered_assets } = actor; + + await del_filtered_assets(TEST_COLLECTION, NO_FILTER_PARAMS); + + const { get_user_usage } = actor; + + const usageResponse = await get_user_usage(TEST_COLLECTION, COLLECTION_TYPE, toNullable()); + + const usage = fromNullable(usageResponse); + + assertNonNullish(usage); + + expect(usage.items_count).toEqual(0); + expect(usage.version).toEqual( + toNullable(BigInt(countUploadAssets + countDelAsset + countDelManyAssets + 1)) + ); + }); + }); + + describe('Guards', () => { + const user1 = Ed25519KeyIdentity.generate(); + + beforeAll(async () => { + actor.setIdentity(user1); + await createUser(user1.getPrincipal()); + }) + + const fetchUsage = async (userId?: Principal): Promise => { + const { get_user_usage } = actor; + + const usageResponse = await get_user_usage( + TEST_COLLECTION, + COLLECTION_TYPE, + toNullable(userId) + ); + return fromNullable(usageResponse); + }; + + it('should not get usage of another user', async () => { + await upload(100); + + const usage = await fetchUsage(); + + assertNonNullish(usage); + + expect(usage.items_count).toEqual(1); + + const user2 = Ed25519KeyIdentity.generate(); + + actor.setIdentity(user2); + await createUser(user2.getPrincipal()); + + const usage2 = await fetchUsage(user1.getPrincipal()); + + expect(usage2).toBeUndefined(); + }); + + it('should get usage of user if controller', async () => { + actor.setIdentity(controller); + + const usage = await fetchUsage(user1.getPrincipal()); + + assertNonNullish(usage); + + expect(usage.items_count).toEqual(1); }); }); }); From 1a0a060d6f1b8b4e10329f91dd14abc3d53e7ccd Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 10:01:49 +0100 Subject: [PATCH 21/33] test: usage storage --- src/tests/satellite.user-usage.spec.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/tests/satellite.user-usage.spec.ts b/src/tests/satellite.user-usage.spec.ts index c09daa941..47f26188c 100644 --- a/src/tests/satellite.user-usage.spec.ts +++ b/src/tests/satellite.user-usage.spec.ts @@ -13,7 +13,7 @@ import { assertNonNullish, fromNullable, toNullable } from '@dfinity/utils'; import { type Actor, PocketIc } from '@hadronous/pic'; import { toArray } from '@junobuild/utils'; import { nanoid } from 'nanoid'; -import {inject} from 'vitest'; +import { inject } from 'vitest'; import { uploadAsset } from './utils/satellite-storage-tests.utils'; import { controllersInitArgs, SATELLITE_WASM_PATH } from './utils/setup-tests.utils'; @@ -309,7 +309,7 @@ describe('Satellite User Usage', () => { description: toNullable(), version: toNullable() }); - } + }; describe('User', () => { const user = Ed25519KeyIdentity.generate(); @@ -366,9 +366,7 @@ describe('Satellite User Usage', () => { assertNonNullish(usage); expect(usage.items_count).toEqual(countUploadAssets - countDelAsset); - expect(usage.version).toEqual( - toNullable(BigInt(countUploadAssets + countDelAsset)) - ); + expect(usage.version).toEqual(toNullable(BigInt(countUploadAssets + countDelAsset))); }); const countDelManyAssets = 2; @@ -393,9 +391,7 @@ describe('Satellite User Usage', () => { assertNonNullish(usage); - expect(usage.items_count).toEqual( - countUploadAssets - countDelAsset - countDelManyAssets - ); + expect(usage.items_count).toEqual(countUploadAssets - countDelAsset - countDelManyAssets); expect(usage.version).toEqual( toNullable(BigInt(countUploadAssets + countDelAsset + countDelManyAssets)) ); @@ -427,7 +423,7 @@ describe('Satellite User Usage', () => { beforeAll(async () => { actor.setIdentity(user1); await createUser(user1.getPrincipal()); - }) + }); const fetchUsage = async (userId?: Principal): Promise => { const { get_user_usage } = actor; From 2ecaae28da3a9c788ce8a6728d5a37e44e5b3c5d Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 12:50:19 +0100 Subject: [PATCH 22/33] feat: signature consistence --- src/libs/satellite/src/satellite.rs | 22 +++++++++++----------- src/libs/satellite/src/usage/user_usage.rs | 22 +++++++++++----------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/libs/satellite/src/satellite.rs b/src/libs/satellite/src/satellite.rs index 0a71c212f..ab3cfffcf 100644 --- a/src/libs/satellite/src/satellite.rs +++ b/src/libs/satellite/src/satellite.rs @@ -129,7 +129,7 @@ pub fn set_doc(collection: CollectionKey, key: Key, doc: SetDoc) -> Doc { match result { Ok(doc) => { - increase_db_usage(&caller, &collection); + increase_db_usage(&collection, &caller); invoke_on_set_doc(&caller, &doc); @@ -156,7 +156,7 @@ pub fn del_doc(collection: CollectionKey, key: Key, doc: DelDoc) { let deleted_doc = delete_doc_store(caller, collection.clone(), key, doc).unwrap_or_else(|e| trap(&e)); - decrease_db_usage(&caller, &collection); + decrease_db_usage(&collection, &caller); invoke_on_delete_doc(&caller, &deleted_doc); } @@ -202,7 +202,7 @@ pub fn set_many_docs(docs: Vec<(CollectionKey, Key, SetDoc)>) -> Vec<(Key, Doc)> let result = set_doc_store(caller, collection.clone(), key.clone(), doc) .unwrap_or_else(|e| trap(&e)); - increase_db_usage(&caller, &collection); + increase_db_usage(&collection, &caller); results.push((result.key.clone(), result.data.after.clone())); @@ -223,7 +223,7 @@ pub fn del_many_docs(docs: Vec<(CollectionKey, Key, DelDoc)>) { let deleted_doc = delete_doc_store(caller, collection.clone(), key.clone(), doc) .unwrap_or_else(|e| trap(&e)); - decrease_db_usage(&caller, &collection); + decrease_db_usage(&collection, &caller); results.push(deleted_doc); } @@ -237,7 +237,7 @@ pub fn del_filtered_docs(collection: CollectionKey, filter: ListParams) { let results = delete_filtered_docs_store(caller, collection.clone(), &filter) .unwrap_or_else(|e| trap(&e)); - decrease_db_usage_by(&caller, &collection, results.len() as u32); + decrease_db_usage_by(&collection, &caller, results.len() as u32); invoke_on_delete_filtered_docs(&caller, &results); } @@ -446,7 +446,7 @@ pub fn commit_asset_upload(commit: CommitBatch) { let asset = commit_batch_store(caller, commit).unwrap_or_else(|e| trap(&e)); - increase_storage_usage(&caller, &asset.key.collection); + increase_storage_usage(&asset.key.collection, &caller); invoke_upload_asset(&caller, &asset); } @@ -480,7 +480,7 @@ pub fn del_asset(collection: CollectionKey, full_path: FullPath) { match result { Ok(asset) => { - decrease_storage_usage(&caller, &collection); + decrease_storage_usage(&collection, &caller); invoke_on_delete_asset(&caller, &asset) } @@ -497,7 +497,7 @@ pub fn del_many_assets(assets: Vec<(CollectionKey, String)>) { let deleted_asset = delete_asset_store(caller, &collection, full_path).unwrap_or_else(|e| trap(&e)); - decrease_storage_usage(&caller, &collection); + decrease_storage_usage(&collection, &caller); results.push(deleted_asset); } @@ -511,7 +511,7 @@ pub fn del_filtered_assets(collection: CollectionKey, filter: ListParams) { let results = delete_filtered_assets_store(caller, collection.clone(), &filter) .unwrap_or_else(|e| trap(&e)); - decrease_storage_usage_by(&caller, &collection, results.len() as u32); + decrease_storage_usage_by(&collection, &caller, results.len() as u32); invoke_on_delete_filtered_assets(&caller, &results); } @@ -570,7 +570,7 @@ pub fn get_user_usage( let user_id_or_caller = user_id.unwrap_or(caller); match collection_type { - CollectionType::Db => get_db_usage_by_id(collection, &user_id_or_caller, caller), - CollectionType::Storage => get_storage_usage_by_id(collection, &user_id_or_caller, caller), + CollectionType::Db => get_db_usage_by_id(caller, collection, &user_id_or_caller), + CollectionType::Storage => get_storage_usage_by_id(caller, collection, &user_id_or_caller), } } diff --git a/src/libs/satellite/src/usage/user_usage.rs b/src/libs/satellite/src/usage/user_usage.rs index a10d3726f..ed2689cce 100644 --- a/src/libs/satellite/src/usage/user_usage.rs +++ b/src/libs/satellite/src/usage/user_usage.rs @@ -11,26 +11,26 @@ use junobuild_shared::types::state::{Controllers, UserId}; use junobuild_shared::utils::principal_not_anonymous_and_equal; pub fn get_db_usage_by_id( + caller: Principal, collection: &CollectionKey, user_id: &UserId, - caller: Principal, ) -> Option { - get_user_usage_by_id(collection, &CollectionType::Db, user_id, caller) + get_user_usage_by_id(caller, collection, &CollectionType::Db, user_id) } pub fn get_storage_usage_by_id( + caller: Principal, collection: &CollectionKey, user_id: &UserId, - caller: Principal, ) -> Option { - get_user_usage_by_id(collection, &CollectionType::Storage, user_id, caller) + get_user_usage_by_id(caller, collection, &CollectionType::Storage, user_id) } fn get_user_usage_by_id( + caller: Principal, collection_key: &CollectionKey, collection_type: &CollectionType, user_id: &UserId, - caller: Principal, ) -> Option { let controllers: Controllers = get_controllers(); @@ -41,7 +41,7 @@ fn get_user_usage_by_id( None } -pub fn increase_db_usage(user_id: &UserId, collection: &CollectionKey) { +pub fn increase_db_usage(collection: &CollectionKey, user_id: &UserId) { store::update_user_usage( user_id, collection, @@ -51,7 +51,7 @@ pub fn increase_db_usage(user_id: &UserId, collection: &CollectionKey) { ); } -pub fn decrease_db_usage(user_id: &UserId, collection: &CollectionKey) { +pub fn decrease_db_usage(collection: &CollectionKey, user_id: &UserId) { store::update_user_usage( user_id, collection, @@ -61,7 +61,7 @@ pub fn decrease_db_usage(user_id: &UserId, collection: &CollectionKey) { ); } -pub fn decrease_db_usage_by(user_id: &UserId, collection: &CollectionKey, count: u32) { +pub fn decrease_db_usage_by(collection: &CollectionKey, user_id: &UserId, count: u32) { store::update_user_usage( user_id, collection, @@ -71,7 +71,7 @@ pub fn decrease_db_usage_by(user_id: &UserId, collection: &CollectionKey, count: ); } -pub fn increase_storage_usage(user_id: &UserId, collection: &CollectionKey) { +pub fn increase_storage_usage(collection: &CollectionKey, user_id: &UserId) { store::update_user_usage( user_id, collection, @@ -81,7 +81,7 @@ pub fn increase_storage_usage(user_id: &UserId, collection: &CollectionKey) { ); } -pub fn decrease_storage_usage(user_id: &UserId, collection: &CollectionKey) { +pub fn decrease_storage_usage(collection: &CollectionKey, user_id: &UserId) { store::update_user_usage( user_id, collection, @@ -91,7 +91,7 @@ pub fn decrease_storage_usage(user_id: &UserId, collection: &CollectionKey) { ); } -pub fn decrease_storage_usage_by(user_id: &UserId, collection: &CollectionKey, count: u32) { +pub fn decrease_storage_usage_by(collection: &CollectionKey, user_id: &UserId, count: u32) { store::update_user_usage( user_id, collection, From 66079da40469a95e19742cbd51a9aa44414988ef Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 13:03:43 +0100 Subject: [PATCH 23/33] feat: no usage for #dapp and #log --- src/libs/collections/src/constants.rs | 4 ++ src/libs/satellite/src/usage/user_usage.rs | 50 ++++++++++++++++++---- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/libs/collections/src/constants.rs b/src/libs/collections/src/constants.rs index 39f51f332..848936f1f 100644 --- a/src/libs/collections/src/constants.rs +++ b/src/libs/collections/src/constants.rs @@ -35,6 +35,8 @@ pub const DEFAULT_DB_COLLECTIONS: [(&str, SetRule); 2] = [ (LOG_COLLECTION_KEY, DEFAULT_DB_LOG_RULE), ]; +pub const DB_COLLECTIONS_NO_USER_USAGE: [&str; 1] = [LOG_COLLECTION_KEY]; + pub const ASSET_COLLECTION_KEY: &str = "#dapp"; pub const DEFAULT_ASSETS_COLLECTIONS: [(&str, SetRule); 1] = [( @@ -50,3 +52,5 @@ pub const DEFAULT_ASSETS_COLLECTIONS: [(&str, SetRule); 1] = [( rate_config: None, }, )]; + +pub const ASSETS_COLLECTIONS_NO_USER_USAGE: [&str; 1] = [ASSET_COLLECTION_KEY]; diff --git a/src/libs/satellite/src/usage/user_usage.rs b/src/libs/satellite/src/usage/user_usage.rs index ed2689cce..c97660d27 100644 --- a/src/libs/satellite/src/usage/user_usage.rs +++ b/src/libs/satellite/src/usage/user_usage.rs @@ -1,10 +1,12 @@ use crate::get_controllers; use crate::types::state::CollectionType; -use crate::usage::store; -use crate::usage::store::get_user_usage as get_user_usage_store; +use crate::usage::store::{get_user_usage as get_user_usage_store, update_user_usage}; use crate::usage::types::interface::ModificationType; use crate::usage::types::state::UserUsage; use candid::Principal; +use junobuild_collections::constants::{ + ASSETS_COLLECTIONS_NO_USER_USAGE, DB_COLLECTIONS_NO_USER_USAGE, +}; use junobuild_collections::types::core::CollectionKey; use junobuild_shared::controllers::is_controller; use junobuild_shared::types::state::{Controllers, UserId}; @@ -42,7 +44,11 @@ fn get_user_usage_by_id( } pub fn increase_db_usage(collection: &CollectionKey, user_id: &UserId) { - store::update_user_usage( + if is_db_collection_no_usage(collection) { + return; + } + + update_user_usage( user_id, collection, &CollectionType::Db, @@ -52,7 +58,11 @@ pub fn increase_db_usage(collection: &CollectionKey, user_id: &UserId) { } pub fn decrease_db_usage(collection: &CollectionKey, user_id: &UserId) { - store::update_user_usage( + if is_db_collection_no_usage(collection) { + return; + } + + update_user_usage( user_id, collection, &CollectionType::Db, @@ -62,7 +72,11 @@ pub fn decrease_db_usage(collection: &CollectionKey, user_id: &UserId) { } pub fn decrease_db_usage_by(collection: &CollectionKey, user_id: &UserId, count: u32) { - store::update_user_usage( + if is_db_collection_no_usage(collection) { + return; + } + + update_user_usage( user_id, collection, &CollectionType::Db, @@ -72,7 +86,11 @@ pub fn decrease_db_usage_by(collection: &CollectionKey, user_id: &UserId, count: } pub fn increase_storage_usage(collection: &CollectionKey, user_id: &UserId) { - store::update_user_usage( + if is_storage_collection_no_usage(collection) { + return; + } + + update_user_usage( user_id, collection, &CollectionType::Storage, @@ -82,7 +100,11 @@ pub fn increase_storage_usage(collection: &CollectionKey, user_id: &UserId) { } pub fn decrease_storage_usage(collection: &CollectionKey, user_id: &UserId) { - store::update_user_usage( + if is_storage_collection_no_usage(collection) { + return; + } + + update_user_usage( user_id, collection, &CollectionType::Storage, @@ -92,7 +114,11 @@ pub fn decrease_storage_usage(collection: &CollectionKey, user_id: &UserId) { } pub fn decrease_storage_usage_by(collection: &CollectionKey, user_id: &UserId, count: u32) { - store::update_user_usage( + if is_storage_collection_no_usage(collection) { + return; + } + + update_user_usage( user_id, collection, &CollectionType::Storage, @@ -100,3 +126,11 @@ pub fn decrease_storage_usage_by(collection: &CollectionKey, user_id: &UserId, c Some(count), ); } + +fn is_db_collection_no_usage(collection: &CollectionKey) -> bool { + DB_COLLECTIONS_NO_USER_USAGE.contains(&collection.as_str()) +} + +fn is_storage_collection_no_usage(collection: &CollectionKey) -> bool { + ASSETS_COLLECTIONS_NO_USER_USAGE.contains(&collection.as_str()) +} From 5f52436c88959bda40de4d888b779932f9b9b16b Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 13:04:30 +0100 Subject: [PATCH 24/33] feat: consistency --- src/libs/satellite/src/usage/store.rs | 12 ++++++------ src/libs/satellite/src/usage/user_usage.rs | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/libs/satellite/src/usage/store.rs b/src/libs/satellite/src/usage/store.rs index c2f4d947c..c3de41c5e 100644 --- a/src/libs/satellite/src/usage/store.rs +++ b/src/libs/satellite/src/usage/store.rs @@ -6,32 +6,32 @@ use junobuild_collections::types::core::CollectionKey; use junobuild_shared::types::state::UserId; pub fn get_user_usage( - user_id: &UserId, collection_key: &CollectionKey, collection_type: &CollectionType, + user_id: &UserId, ) -> Option { STATE.with(|state| { get_user_usage_impl( - user_id, collection_key, collection_type, + user_id, &state.borrow().stable.user_usage, ) }) } pub fn update_user_usage( - user_id: &UserId, collection_key: &CollectionKey, collection_type: &CollectionType, + user_id: &UserId, modification: &ModificationType, count: Option, ) { STATE.with(|state| { update_user_usage_impl( - user_id, collection_key, collection_type, + user_id, modification, count, &mut state.borrow_mut().stable.user_usage, @@ -40,9 +40,9 @@ pub fn update_user_usage( } fn get_user_usage_impl( - user_id: &UserId, collection_key: &CollectionKey, collection_type: &CollectionType, + user_id: &UserId, state: &UserUsageStable, ) -> Option { let key = UserUsageKey::new(user_id, collection_key, collection_type); @@ -51,9 +51,9 @@ fn get_user_usage_impl( } fn update_user_usage_impl( - user_id: &UserId, collection_key: &CollectionKey, collection_type: &CollectionType, + user_id: &UserId, modification: &ModificationType, count: Option, state: &mut UserUsageStable, diff --git a/src/libs/satellite/src/usage/user_usage.rs b/src/libs/satellite/src/usage/user_usage.rs index c97660d27..fb537b42e 100644 --- a/src/libs/satellite/src/usage/user_usage.rs +++ b/src/libs/satellite/src/usage/user_usage.rs @@ -49,9 +49,9 @@ pub fn increase_db_usage(collection: &CollectionKey, user_id: &UserId) { } update_user_usage( - user_id, collection, &CollectionType::Db, + user_id, &ModificationType::Set, None, ); @@ -63,9 +63,9 @@ pub fn decrease_db_usage(collection: &CollectionKey, user_id: &UserId) { } update_user_usage( - user_id, collection, &CollectionType::Db, + user_id, &ModificationType::Delete, None, ); @@ -77,9 +77,9 @@ pub fn decrease_db_usage_by(collection: &CollectionKey, user_id: &UserId, count: } update_user_usage( - user_id, collection, &CollectionType::Db, + user_id, &ModificationType::Delete, Some(count), ); @@ -91,9 +91,9 @@ pub fn increase_storage_usage(collection: &CollectionKey, user_id: &UserId) { } update_user_usage( - user_id, collection, &CollectionType::Storage, + user_id, &ModificationType::Set, None, ); @@ -105,9 +105,9 @@ pub fn decrease_storage_usage(collection: &CollectionKey, user_id: &UserId) { } update_user_usage( - user_id, collection, &CollectionType::Storage, + user_id, &ModificationType::Delete, None, ); @@ -119,9 +119,9 @@ pub fn decrease_storage_usage_by(collection: &CollectionKey, user_id: &UserId, c } update_user_usage( - user_id, collection, &CollectionType::Storage, + user_id, &ModificationType::Delete, Some(count), ); From 8c4e429e2a5188e1ecea5121827593aced5e5ea2 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 13:04:53 +0100 Subject: [PATCH 25/33] chore: lint --- src/tests/satellite.user-usage.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/satellite.user-usage.spec.ts b/src/tests/satellite.user-usage.spec.ts index 47f26188c..1c261df66 100644 --- a/src/tests/satellite.user-usage.spec.ts +++ b/src/tests/satellite.user-usage.spec.ts @@ -276,7 +276,7 @@ describe('Satellite User Usage', () => { }); }); - describe('Storage', async () => { + describe('Storage', () => { const COLLECTION_TYPE = { Storage: null }; beforeAll(async () => { From 1a79f490d8ec56824a2892b0367d8a7206687aa7 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 13:07:55 +0100 Subject: [PATCH 26/33] fix: signature --- src/libs/satellite/src/usage/user_usage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/satellite/src/usage/user_usage.rs b/src/libs/satellite/src/usage/user_usage.rs index fb537b42e..def38ca75 100644 --- a/src/libs/satellite/src/usage/user_usage.rs +++ b/src/libs/satellite/src/usage/user_usage.rs @@ -37,7 +37,7 @@ fn get_user_usage_by_id( let controllers: Controllers = get_controllers(); if principal_not_anonymous_and_equal(*user_id, caller) || is_controller(caller, &controllers) { - return get_user_usage_store(user_id, collection_key, collection_type); + return get_user_usage_store(collection_key, collection_type, user_id); } None From 0cdc9a2361c266ce3e82f287526fb06bbb3b5430 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 13:12:24 +0100 Subject: [PATCH 27/33] test: no tracking on default collections --- src/tests/satellite.user-usage.spec.ts | 51 ++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/tests/satellite.user-usage.spec.ts b/src/tests/satellite.user-usage.spec.ts index 1c261df66..ce00ab040 100644 --- a/src/tests/satellite.user-usage.spec.ts +++ b/src/tests/satellite.user-usage.spec.ts @@ -241,6 +241,7 @@ describe('Satellite User Usage', () => { COLLECTION_TYPE, toNullable(userId) ); + return fromNullable(usageResponse); }; @@ -274,6 +275,30 @@ describe('Satellite User Usage', () => { expect(usage.items_count).toEqual(1); }); }); + + describe('No user usage', () => { + beforeAll(() => { + actor.setIdentity(controller); + }); + + it('should get no usage of collection is log', async () => { + const { set_doc, get_doc, get_user_usage } = actor; + + const key = nanoid(); + + await set_doc('#log', key, { + data, + description: toNullable(), + version: toNullable() + }); + + const doc = await get_doc('#log', key); + expect(fromNullable(doc)).not.toBeUndefined(); + + const usageResponse = await get_user_usage('#log', COLLECTION_TYPE, toNullable()); + expect(fromNullable(usageResponse)).toBeUndefined(); + }); + }); }); describe('Storage', () => { @@ -465,5 +490,31 @@ describe('Satellite User Usage', () => { expect(usage.items_count).toEqual(1); }); }); + + describe('No user usage', () => { + beforeAll(() => { + actor.setIdentity(controller); + }); + + it('should get no usage of collection is dapp', async () => { + const { get_asset, get_user_usage } = actor; + + const name = `index.html`; + const full_path = `/${name}`; + + await uploadAsset({ + full_path, + name, + collection: '#dapp', + actor + }); + + const asset = await get_asset('#dapp', full_path); + expect(fromNullable(asset)).not.toBeUndefined(); + + const usageResponse = await get_user_usage('#dapp', COLLECTION_TYPE, toNullable()); + expect(fromNullable(usageResponse)).toBeUndefined(); + }); + }); }); }); From b58293c07ce9c97742db0c29de3ba910b41f10a8 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 14:09:54 +0100 Subject: [PATCH 28/33] feat: set user usage --- src/libs/satellite/src/lib.rs | 12 +++++++ src/libs/satellite/src/satellite.rs | 18 ++++++++++ src/libs/satellite/src/usage/impls.rs | 13 ++++++-- src/libs/satellite/src/usage/store.rs | 37 ++++++++++++++++++++- src/libs/satellite/src/usage/types.rs | 28 ++++++++++++++++ src/libs/satellite/src/usage/user_usage.rs | 38 +++++++++++++++++++++- 6 files changed, 142 insertions(+), 4 deletions(-) diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index 2f830029e..9d6f47b84 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -45,6 +45,7 @@ use junobuild_storage::types::interface::{ AssetNoContent, CommitBatch, InitAssetKey, InitUploadResult, UploadChunk, UploadChunkResult, }; use junobuild_storage::types::state::FullPath; +use crate::usage::types::interface::SetUserUsage; // ============================================================================================ // START: Re-exported Types @@ -410,6 +411,17 @@ pub fn get_user_usage( satellite::get_user_usage(&collection_key, &collection_type, &user_id) } +#[doc(hidden)] +#[update(guard = "caller_is_controller")] +pub fn set_user_usage( + collection_key: CollectionKey, + collection_type: CollectionType, + user_id: UserId, + usage: SetUserUsage, +) -> UserUsage { + satellite::set_user_usage(&collection_key, &collection_type, &user_id, &usage) +} + // --------------------------------------------------------- // Mgmt // --------------------------------------------------------- diff --git a/src/libs/satellite/src/satellite.rs b/src/libs/satellite/src/satellite.rs index ab3cfffcf..611d65c15 100644 --- a/src/libs/satellite/src/satellite.rs +++ b/src/libs/satellite/src/satellite.rs @@ -38,10 +38,12 @@ use crate::storage::store::{ use crate::storage::strategy_impls::StorageState; use crate::types::interface::Config; use crate::types::state::{CollectionType, HeapState, RuntimeState, State}; +use crate::usage::types::interface::SetUserUsage; use crate::usage::types::state::UserUsage; use crate::usage::user_usage::{ decrease_db_usage, decrease_db_usage_by, decrease_storage_usage, decrease_storage_usage_by, get_db_usage_by_id, get_storage_usage_by_id, increase_db_usage, increase_storage_usage, + set_db_usage, set_storage_usage, }; use ciborium::{from_reader, into_writer}; use ic_cdk::api::call::{arg_data, ArgDecoderConfig}; @@ -574,3 +576,19 @@ pub fn get_user_usage( CollectionType::Storage => get_storage_usage_by_id(caller, collection, &user_id_or_caller), } } + +pub fn set_user_usage( + collection: &CollectionKey, + collection_type: &CollectionType, + user_id: &UserId, + usage: &SetUserUsage, +) -> UserUsage { + match collection_type { + CollectionType::Db => { + set_db_usage(collection, user_id, usage.items_count).unwrap_or_else(|e| trap(&e)) + } + CollectionType::Storage => { + set_storage_usage(collection, user_id, usage.items_count).unwrap_or_else(|e| trap(&e)) + } + } +} diff --git a/src/libs/satellite/src/usage/impls.rs b/src/libs/satellite/src/usage/impls.rs index 4fc1b1dcd..e98a2755f 100644 --- a/src/libs/satellite/src/usage/impls.rs +++ b/src/libs/satellite/src/usage/impls.rs @@ -35,12 +35,15 @@ impl Storable for UserUsageKey { } impl UserUsage { - pub fn update( + pub fn set(current_user_usage: &Option, count: u32) -> Self { + UserUsage::apply_update(current_user_usage, count) + } + + pub fn increase_or_decrease( current_user_usage: &Option, modification: &ModificationType, count: Option, ) -> Self { - let now = time(); let count = count.unwrap_or(1); // User usage for the collection @@ -53,6 +56,12 @@ impl UserUsage { }, }; + UserUsage::apply_update(current_user_usage, items_count) + } + + fn apply_update(current_user_usage: &Option, items_count: u32) -> Self { + let now = time(); + // Metadata for the UserUsage entity entry let created_at: Timestamp = match current_user_usage { diff --git a/src/libs/satellite/src/usage/store.rs b/src/libs/satellite/src/usage/store.rs index c3de41c5e..bed1d3b78 100644 --- a/src/libs/satellite/src/usage/store.rs +++ b/src/libs/satellite/src/usage/store.rs @@ -39,6 +39,23 @@ pub fn update_user_usage( }) } +pub fn set_user_usage( + collection_key: &CollectionKey, + collection_type: &CollectionType, + user_id: &UserId, + count: u32, +) -> UserUsage { + STATE.with(|state| { + set_user_usage_impl( + collection_key, + collection_type, + user_id, + count, + &mut state.borrow_mut().stable.user_usage, + ) + }) +} + fn get_user_usage_impl( collection_key: &CollectionKey, collection_type: &CollectionType, @@ -62,7 +79,25 @@ fn update_user_usage_impl( let current_usage = state.get(&key); - let update_usage = UserUsage::update(¤t_usage, modification, count); + let update_usage = UserUsage::increase_or_decrease(¤t_usage, modification, count); state.insert(key, update_usage); } + +fn set_user_usage_impl( + collection_key: &CollectionKey, + collection_type: &CollectionType, + user_id: &UserId, + count: u32, + state: &mut UserUsageStable, +) -> UserUsage { + let key = UserUsageKey::new(user_id, collection_key, collection_type); + + let current_usage = state.get(&key); + + let update_usage = UserUsage::set(¤t_usage, count); + + state.insert(key, update_usage.clone()); + + update_usage +} diff --git a/src/libs/satellite/src/usage/types.rs b/src/libs/satellite/src/usage/types.rs index 7323d9215..6044e9332 100644 --- a/src/libs/satellite/src/usage/types.rs +++ b/src/libs/satellite/src/usage/types.rs @@ -9,6 +9,12 @@ pub mod state { pub type UserUsageStable = StableBTreeMap; + /// A unique key for identifying user usage within a collection. + /// + /// It consists of: + /// - `user_id`: The unique identifier for the user which is matched to the caller. + /// - `collection_key`: The collection where usage is tracked. + /// - `collection_type`: The type of collection (`Db` for datastore, `Storage` for assets). #[derive(CandidType, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct UserUsageKey { pub user_id: UserId, @@ -16,6 +22,14 @@ pub mod state { pub collection_type: CollectionType, } + /// Tracks the usage (create, set and delete) of a user in a collection. + /// + /// + /// Fields: + /// - `items_count`: The total number of changes by the user. Is not necessary equals to the number of items due to the lack of migration and the fact that controllers can reset the value. + /// - `created_at`: The timestamp when this user usage entry was first recorded. + /// - `updated_at`: The timestamp of the last update to this user usage entry. + /// - `version`: An optional field representing the version of this usage entry. In the future we might implement checks to avoid overwrite but, this is not the case currently. #[derive(CandidType, Serialize, Deserialize, Clone)] pub struct UserUsage { pub items_count: u32, @@ -26,8 +40,22 @@ pub mod state { } pub mod interface { + use candid::{CandidType, Deserialize}; + use serde::Serialize; + pub enum ModificationType { Set, Delete, } + + /// Represents the parameters for setting or updating a user's usage entry for a controller. + /// + /// This is useful if one want to set a value after the upgrade, given the lack of migration, or if a controller ever wants to reset the value to allow a user who would hit the limit to continue submitted changes. + /// + /// It includes: + /// - `items_count`: The total number of changes the user has in a specific collection. + #[derive(Default, CandidType, Serialize, Deserialize, Clone)] + pub struct SetUserUsage { + pub items_count: u32, + } } diff --git a/src/libs/satellite/src/usage/user_usage.rs b/src/libs/satellite/src/usage/user_usage.rs index def38ca75..37c23bf15 100644 --- a/src/libs/satellite/src/usage/user_usage.rs +++ b/src/libs/satellite/src/usage/user_usage.rs @@ -1,6 +1,8 @@ use crate::get_controllers; use crate::types::state::CollectionType; -use crate::usage::store::{get_user_usage as get_user_usage_store, update_user_usage}; +use crate::usage::store::{ + get_user_usage as get_user_usage_store, set_user_usage, update_user_usage, +}; use crate::usage::types::interface::ModificationType; use crate::usage::types::state::UserUsage; use candid::Principal; @@ -57,6 +59,23 @@ pub fn increase_db_usage(collection: &CollectionKey, user_id: &UserId) { ); } +pub fn set_db_usage( + collection: &CollectionKey, + user_id: &UserId, + count: u32, +) -> Result { + if is_db_collection_no_usage(collection) { + return Err(format!( + "Datastore usage is not recorded for collection {}.", + collection + )); + } + + let usage = set_user_usage(collection, &CollectionType::Db, user_id, count); + + Ok(usage) +} + pub fn decrease_db_usage(collection: &CollectionKey, user_id: &UserId) { if is_db_collection_no_usage(collection) { return; @@ -99,6 +118,23 @@ pub fn increase_storage_usage(collection: &CollectionKey, user_id: &UserId) { ); } +pub fn set_storage_usage( + collection: &CollectionKey, + user_id: &UserId, + count: u32, +) -> Result { + if is_storage_collection_no_usage(collection) { + return Err(format!( + "Storage usage is not recorded for collection {}.", + collection + )); + } + + let usage = set_user_usage(collection, &CollectionType::Storage, user_id, count); + + Ok(usage) +} + pub fn decrease_storage_usage(collection: &CollectionKey, user_id: &UserId) { if is_storage_collection_no_usage(collection) { return; From e80491ca7a9b514ca0d7823047b5c6443a448a50 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 14:14:22 +0100 Subject: [PATCH 29/33] refactor: rename --- src/libs/satellite/src/usage/impls.rs | 2 +- src/libs/satellite/src/usage/store.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/satellite/src/usage/impls.rs b/src/libs/satellite/src/usage/impls.rs index e98a2755f..bee01a7db 100644 --- a/src/libs/satellite/src/usage/impls.rs +++ b/src/libs/satellite/src/usage/impls.rs @@ -86,7 +86,7 @@ impl UserUsage { } impl UserUsageKey { - pub fn new( + pub fn create( user_id: &UserId, collection_key: &CollectionKey, collection_type: &CollectionType, diff --git a/src/libs/satellite/src/usage/store.rs b/src/libs/satellite/src/usage/store.rs index bed1d3b78..22e387202 100644 --- a/src/libs/satellite/src/usage/store.rs +++ b/src/libs/satellite/src/usage/store.rs @@ -62,7 +62,7 @@ fn get_user_usage_impl( user_id: &UserId, state: &UserUsageStable, ) -> Option { - let key = UserUsageKey::new(user_id, collection_key, collection_type); + let key = UserUsageKey::create(user_id, collection_key, collection_type); state.get(&key) } @@ -75,7 +75,7 @@ fn update_user_usage_impl( count: Option, state: &mut UserUsageStable, ) { - let key = UserUsageKey::new(user_id, collection_key, collection_type); + let key = UserUsageKey::create(user_id, collection_key, collection_type); let current_usage = state.get(&key); @@ -91,7 +91,7 @@ fn set_user_usage_impl( count: u32, state: &mut UserUsageStable, ) -> UserUsage { - let key = UserUsageKey::new(user_id, collection_key, collection_type); + let key = UserUsageKey::create(user_id, collection_key, collection_type); let current_usage = state.get(&key); From 312962595d95bcd1d83acf418be5fdcfbab8a70c Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 14:26:22 +0100 Subject: [PATCH 30/33] feat: expose for serverless --- src/libs/satellite/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index 9d6f47b84..4681fe856 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -21,6 +21,7 @@ use crate::db::types::config::DbConfig; use crate::guards::{caller_is_admin_controller, caller_is_controller}; use crate::types::interface::Config; use crate::types::state::CollectionType; +use crate::usage::types::interface::SetUserUsage; use crate::usage::types::state::UserUsage; use crate::version::SATELLITE_VERSION; use ic_cdk::api::trap; @@ -45,7 +46,6 @@ use junobuild_storage::types::interface::{ AssetNoContent, CommitBatch, InitAssetKey, InitUploadResult, UploadChunk, UploadChunkResult, }; use junobuild_storage::types::state::FullPath; -use crate::usage::types::interface::SetUserUsage; // ============================================================================================ // START: Re-exported Types @@ -473,7 +473,7 @@ macro_rules! include_satellite { http_request, http_request_streaming_callback, init, init_asset_upload, list_assets, list_controllers, list_custom_domains, list_docs, list_rules, memory_size, post_upgrade, pre_upgrade, set_auth_config, set_controllers, set_custom_domain, - set_db_config, set_doc, set_many_docs, set_rule, set_storage_config, + set_db_config, set_doc, set_many_docs, set_rule, set_storage_config, set_user_usage, upload_asset_chunk, version, }; From f31da4d883fa3549f60c7617f1654848f6e78d8f Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 14:28:04 +0100 Subject: [PATCH 31/33] feat: did set user usage --- src/declarations/satellite/satellite.did.d.ts | 4 ++++ .../satellite/satellite.factory.certified.did.js | 6 ++++++ src/declarations/satellite/satellite.factory.did.js | 6 ++++++ src/declarations/satellite/satellite.factory.did.mjs | 6 ++++++ src/libs/satellite/satellite.did | 4 ++++ src/satellite/satellite.did | 4 ++++ 6 files changed, 30 insertions(+) diff --git a/src/declarations/satellite/satellite.did.d.ts b/src/declarations/satellite/satellite.did.d.ts index 09352ad6b..907019278 100644 --- a/src/declarations/satellite/satellite.did.d.ts +++ b/src/declarations/satellite/satellite.did.d.ts @@ -191,6 +191,9 @@ export interface SetRule { rate_config: [] | [RateConfig]; write: Permission; } +export interface SetUserUsage { + items_count: number; +} export interface StorageConfig { iframe: [] | [StorageConfigIFrame]; rewrites: Array<[string, string]>; @@ -292,6 +295,7 @@ export interface _SERVICE { set_many_docs: ActorMethod<[Array<[string, string, SetDoc]>], Array<[string, Doc]>>; set_rule: ActorMethod<[CollectionType, string, SetRule], Rule>; set_storage_config: ActorMethod<[StorageConfig], undefined>; + set_user_usage: ActorMethod<[string, CollectionType, Principal, SetUserUsage], UserUsage>; upload_asset_chunk: ActorMethod<[UploadChunk], UploadChunkResult>; version: ActorMethod<[], string>; } diff --git a/src/declarations/satellite/satellite.factory.certified.did.js b/src/declarations/satellite/satellite.factory.certified.did.js index 4dd86d3b0..6233cfabf 100644 --- a/src/declarations/satellite/satellite.factory.certified.did.js +++ b/src/declarations/satellite/satellite.factory.certified.did.js @@ -237,6 +237,7 @@ export const idlFactory = ({ IDL }) => { rate_config: IDL.Opt(RateConfig), write: Permission }); + const SetUserUsage = IDL.Record({ items_count: IDL.Nat32 }); const UploadChunk = IDL.Record({ content: IDL.Vec(IDL.Nat8), batch_id: IDL.Nat, @@ -317,6 +318,11 @@ export const idlFactory = ({ IDL }) => { ), set_rule: IDL.Func([CollectionType, IDL.Text, SetRule], [Rule], []), set_storage_config: IDL.Func([StorageConfig], [], []), + set_user_usage: IDL.Func( + [IDL.Text, CollectionType, IDL.Principal, SetUserUsage], + [UserUsage], + [] + ), upload_asset_chunk: IDL.Func([UploadChunk], [UploadChunkResult], []), version: IDL.Func([], [IDL.Text], []) }); diff --git a/src/declarations/satellite/satellite.factory.did.js b/src/declarations/satellite/satellite.factory.did.js index 9deae27ca..73de6d16f 100644 --- a/src/declarations/satellite/satellite.factory.did.js +++ b/src/declarations/satellite/satellite.factory.did.js @@ -237,6 +237,7 @@ export const idlFactory = ({ IDL }) => { rate_config: IDL.Opt(RateConfig), write: Permission }); + const SetUserUsage = IDL.Record({ items_count: IDL.Nat32 }); const UploadChunk = IDL.Record({ content: IDL.Vec(IDL.Nat8), batch_id: IDL.Nat, @@ -317,6 +318,11 @@ export const idlFactory = ({ IDL }) => { ), set_rule: IDL.Func([CollectionType, IDL.Text, SetRule], [Rule], []), set_storage_config: IDL.Func([StorageConfig], [], []), + set_user_usage: IDL.Func( + [IDL.Text, CollectionType, IDL.Principal, SetUserUsage], + [UserUsage], + [] + ), upload_asset_chunk: IDL.Func([UploadChunk], [UploadChunkResult], []), version: IDL.Func([], [IDL.Text], ['query']) }); diff --git a/src/declarations/satellite/satellite.factory.did.mjs b/src/declarations/satellite/satellite.factory.did.mjs index 9deae27ca..73de6d16f 100644 --- a/src/declarations/satellite/satellite.factory.did.mjs +++ b/src/declarations/satellite/satellite.factory.did.mjs @@ -237,6 +237,7 @@ export const idlFactory = ({ IDL }) => { rate_config: IDL.Opt(RateConfig), write: Permission }); + const SetUserUsage = IDL.Record({ items_count: IDL.Nat32 }); const UploadChunk = IDL.Record({ content: IDL.Vec(IDL.Nat8), batch_id: IDL.Nat, @@ -317,6 +318,11 @@ export const idlFactory = ({ IDL }) => { ), set_rule: IDL.Func([CollectionType, IDL.Text, SetRule], [Rule], []), set_storage_config: IDL.Func([StorageConfig], [], []), + set_user_usage: IDL.Func( + [IDL.Text, CollectionType, IDL.Principal, SetUserUsage], + [UserUsage], + [] + ), upload_asset_chunk: IDL.Func([UploadChunk], [UploadChunkResult], []), version: IDL.Func([], [IDL.Text], ['query']) }); diff --git a/src/libs/satellite/satellite.did b/src/libs/satellite/satellite.did index 2c1c4bae0..ebf0d5238 100644 --- a/src/libs/satellite/satellite.did +++ b/src/libs/satellite/satellite.did @@ -155,6 +155,7 @@ type SetRule = record { rate_config : opt RateConfig; write : Permission; }; +type SetUserUsage = record { items_count : nat32 }; type StorageConfig = record { iframe : opt StorageConfigIFrame; rewrites : vec record { text; text }; @@ -262,6 +263,9 @@ service : () -> { ); set_rule : (CollectionType, text, SetRule) -> (Rule); set_storage_config : (StorageConfig) -> (); + set_user_usage : (text, CollectionType, principal, SetUserUsage) -> ( + UserUsage, + ); upload_asset_chunk : (UploadChunk) -> (UploadChunkResult); version : () -> (text) query; } diff --git a/src/satellite/satellite.did b/src/satellite/satellite.did index f6b146728..d36243651 100644 --- a/src/satellite/satellite.did +++ b/src/satellite/satellite.did @@ -157,6 +157,7 @@ type SetRule = record { rate_config : opt RateConfig; write : Permission; }; +type SetUserUsage = record { items_count : nat32 }; type StorageConfig = record { iframe : opt StorageConfigIFrame; rewrites : vec record { text; text }; @@ -264,6 +265,9 @@ service : () -> { ); set_rule : (CollectionType, text, SetRule) -> (Rule); set_storage_config : (StorageConfig) -> (); + set_user_usage : (text, CollectionType, principal, SetUserUsage) -> ( + UserUsage, + ); upload_asset_chunk : (UploadChunk) -> (UploadChunkResult); version : () -> (text) query; } From 4ba4b4e0a695d742016277bd644efd0120616e40 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 14:38:52 +0100 Subject: [PATCH 32/33] feat: admin controller only --- src/libs/satellite/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index 4681fe856..3010af824 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -412,7 +412,7 @@ pub fn get_user_usage( } #[doc(hidden)] -#[update(guard = "caller_is_controller")] +#[update(guard = "caller_is_admin_controller")] pub fn set_user_usage( collection_key: CollectionKey, collection_type: CollectionType, From 79f7737e5c781517710cd2db584fe1d9f5ee5319 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 2 Feb 2025 14:38:59 +0100 Subject: [PATCH 33/33] test: set user usage --- src/tests/satellite.user-usage.spec.ts | 98 +++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 11 deletions(-) diff --git a/src/tests/satellite.user-usage.spec.ts b/src/tests/satellite.user-usage.spec.ts index ce00ab040..00e56a9d7 100644 --- a/src/tests/satellite.user-usage.spec.ts +++ b/src/tests/satellite.user-usage.spec.ts @@ -13,7 +13,8 @@ import { assertNonNullish, fromNullable, toNullable } from '@dfinity/utils'; import { type Actor, PocketIc } from '@hadronous/pic'; import { toArray } from '@junobuild/utils'; import { nanoid } from 'nanoid'; -import { inject } from 'vitest'; +import { beforeAll, describe, expect, inject } from 'vitest'; +import { SATELLITE_ADMIN_ERROR_MSG } from './constants/satellite-tests.constants'; import { uploadAsset } from './utils/satellite-storage-tests.utils'; import { controllersInitArgs, SATELLITE_WASM_PATH } from './utils/setup-tests.utils'; @@ -88,9 +89,10 @@ describe('Satellite User Usage', () => { return key; }; - describe('User', () => { - const user = Ed25519KeyIdentity.generate(); + const user = Ed25519KeyIdentity.generate(); + let countTotalTestVersion: number; + describe('User', () => { beforeAll(() => { actor.setIdentity(user); }); @@ -223,10 +225,11 @@ describe('Satellite User Usage', () => { assertNonNullish(usage); + countTotalTestVersion = + countSetManyDocs + countSetDocs + countDelDoc + countDelManyDocs + 1; + expect(usage.items_count).toEqual(0); - expect(usage.version).toEqual( - toNullable(BigInt(countSetManyDocs + countSetDocs + countDelDoc + countDelManyDocs + 1)) - ); + expect(usage.version).toEqual(toNullable(BigInt(countTotalTestVersion))); }); }); @@ -274,6 +277,18 @@ describe('Satellite User Usage', () => { expect(usage.items_count).toEqual(1); }); + + it('should throw errors on set usage', async () => { + actor.setIdentity(user1); + + const { set_user_usage } = actor; + + await expect( + set_user_usage(TEST_COLLECTION, COLLECTION_TYPE, user1.getPrincipal(), { + items_count: 345 + }) + ).rejects.toThrow(SATELLITE_ADMIN_ERROR_MSG); + }); }); describe('No user usage', () => { @@ -299,6 +314,30 @@ describe('Satellite User Usage', () => { expect(fromNullable(usageResponse)).toBeUndefined(); }); }); + + describe('Admin', () => { + beforeAll(() => { + actor.setIdentity(controller); + }); + + it('should set usage for user', async () => { + const { set_user_usage } = actor; + + const usage = await set_user_usage(TEST_COLLECTION, COLLECTION_TYPE, user.getPrincipal(), { + items_count: 345 + }); + + expect(usage.items_count).toEqual(345); + + expect(usage.updated_at).not.toBeUndefined(); + expect(usage.updated_at).toBeGreaterThan(0n); + expect(usage.created_at).not.toBeUndefined(); + expect(usage.created_at).toBeGreaterThan(0n); + expect(usage.updated_at).toBeGreaterThan(usage.created_at); + + expect(usage.version).toEqual(toNullable(BigInt(countTotalTestVersion + 1))); + }); + }); }); describe('Storage', () => { @@ -336,9 +375,10 @@ describe('Satellite User Usage', () => { }); }; - describe('User', () => { - const user = Ed25519KeyIdentity.generate(); + const user = Ed25519KeyIdentity.generate(); + let countTotalTestVersion: number; + describe('User', () => { beforeAll(async () => { actor.setIdentity(user); @@ -435,10 +475,10 @@ describe('Satellite User Usage', () => { assertNonNullish(usage); + countTotalTestVersion = countUploadAssets + countDelAsset + countDelManyAssets + 1; + expect(usage.items_count).toEqual(0); - expect(usage.version).toEqual( - toNullable(BigInt(countUploadAssets + countDelAsset + countDelManyAssets + 1)) - ); + expect(usage.version).toEqual(toNullable(BigInt(countTotalTestVersion))); }); }); @@ -489,6 +529,18 @@ describe('Satellite User Usage', () => { expect(usage.items_count).toEqual(1); }); + + it('should throw errors on set usage', async () => { + actor.setIdentity(user1); + + const { set_user_usage } = actor; + + await expect( + set_user_usage(TEST_COLLECTION, COLLECTION_TYPE, user1.getPrincipal(), { + items_count: 345 + }) + ).rejects.toThrow(SATELLITE_ADMIN_ERROR_MSG); + }); }); describe('No user usage', () => { @@ -516,5 +568,29 @@ describe('Satellite User Usage', () => { expect(fromNullable(usageResponse)).toBeUndefined(); }); }); + + describe('Admin', () => { + beforeAll(() => { + actor.setIdentity(controller); + }); + + it('should set usage for user', async () => { + const { set_user_usage } = actor; + + const usage = await set_user_usage(TEST_COLLECTION, COLLECTION_TYPE, user.getPrincipal(), { + items_count: 456 + }); + + expect(usage.items_count).toEqual(456); + + expect(usage.updated_at).not.toBeUndefined(); + expect(usage.updated_at).toBeGreaterThan(0n); + expect(usage.created_at).not.toBeUndefined(); + expect(usage.created_at).toBeGreaterThan(0n); + expect(usage.updated_at).toBeGreaterThan(usage.created_at); + + expect(usage.version).toEqual(toNullable(BigInt(countTotalTestVersion + 1))); + }); + }); }); });