Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: max items per user #1166

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/console/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub const DEFAULT_RELEASES_COLLECTIONS: [(&str, SetRule); 1] = [(
mutable_permissions: Some(false),
max_size: None,
max_capacity: None,
max_items_per_user: None,
version: None,
rate_config: None,
},
Expand Down
3 changes: 3 additions & 0 deletions src/libs/collections/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub const DEFAULT_DB_LOG_RULE: SetRule = SetRule {
mutable_permissions: Some(false),
max_size: None,
max_capacity: Some(100),
max_items_per_user: None,
version: None,
rate_config: None,
};
Expand All @@ -28,6 +29,7 @@ pub const DEFAULT_DB_COLLECTIONS: [(&str, SetRule); 2] = [
mutable_permissions: Some(false),
max_size: None,
max_capacity: None,
max_items_per_user: None,
version: None,
rate_config: Some(DEFAULT_RATE_CONFIG),
},
Expand All @@ -46,6 +48,7 @@ pub const DEFAULT_ASSETS_COLLECTIONS: [(&str, SetRule); 1] = [(
mutable_permissions: Some(false),
max_size: None,
max_capacity: None,
max_items_per_user: None,
version: None,
rate_config: None,
},
Expand Down
2 changes: 2 additions & 0 deletions src/libs/collections/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ impl Rule {
mutable_permissions: Some(user_rule.mutable_permissions.unwrap_or(true)),
max_size: user_rule.max_size,
max_capacity: user_rule.max_capacity,
max_items_per_user: user_rule.max_items_per_user,
created_at,
updated_at,
version: Some(version),
Expand All @@ -76,6 +77,7 @@ impl Rule {
mutable_permissions: current_rule.mutable_permissions,
max_size: current_rule.max_size,
max_capacity: current_rule.max_capacity,
max_items_per_user: current_rule.max_items_per_user,
created_at,
updated_at,
version: Some(version),
Expand Down
2 changes: 2 additions & 0 deletions src/libs/collections/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub mod rules {
pub memory: Option<Memory>,
pub max_size: Option<u128>,
pub max_capacity: Option<u32>,
pub max_items_per_user: Option<u32>,
pub created_at: Timestamp,
pub updated_at: Timestamp,
pub version: Option<Version>,
Expand Down Expand Up @@ -73,6 +74,7 @@ pub mod interface {
pub memory: Option<Memory>,
pub max_size: Option<u128>,
pub max_capacity: Option<u32>,
pub max_items_per_user: Option<u32>,
pub version: Option<Version>,
pub rate_config: Option<RateConfig>,
}
Expand Down
1 change: 1 addition & 0 deletions src/libs/satellite/src/db/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ impl Default for DbHeapState {
mutable_permissions: Some(rule.mutable_permissions.unwrap_or(false)),
max_size: rule.max_size,
max_capacity: rule.max_capacity,
max_items_per_user: rule.max_items_per_user,
created_at: now,
updated_at: now,
version: rule.version,
Expand Down
1 change: 1 addition & 0 deletions src/libs/satellite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod rules;
mod satellite;
mod storage;
mod types;
mod usage;
mod version;

use crate::auth::types::config::AuthenticationConfig;
Expand Down
6 changes: 6 additions & 0 deletions src/libs/satellite/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<State> = RefCell::default();
Expand All @@ -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()),
}
}
29 changes: 21 additions & 8 deletions src/libs/satellite/src/satellite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand All @@ -146,7 +149,10 @@ pub fn get_doc(collection: CollectionKey, key: Key) -> Option<Doc> {
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);
}
Expand Down Expand Up @@ -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()));

Expand All @@ -208,8 +216,11 @@ pub fn del_many_docs(docs: Vec<(CollectionKey, Key, DelDoc)>) {
let mut results: Vec<DocContext<Option<Doc>>> = 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);
}

Expand All @@ -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);
}
Expand Down
2 changes: 2 additions & 0 deletions src/libs/satellite/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)]
Expand Down
75 changes: 75 additions & 0 deletions src/libs/satellite/src/usage/impls.rs
Original file line number Diff line number Diff line change
@@ -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<UserUsage>,
modification: &ModificationType,
count: Option<u32>,
) -> 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),
}
}
}
3 changes: 3 additions & 0 deletions src/libs/satellite/src/usage/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod impls;
pub mod store;
pub mod types;
57 changes: 57 additions & 0 deletions src/libs/satellite/src/usage/store.rs
Original file line number Diff line number Diff line change
@@ -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<u32>,
) {
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<u32>,
state: &mut UserUsageStable,
) {
let key = stable_user_usage_key(user_id, collection);

let current_usage = state.get(&key);

let update_usage = UserUsage::update(&current_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(),
}
}
31 changes: 31 additions & 0 deletions src/libs/satellite/src/usage/types.rs
Original file line number Diff line number Diff line change
@@ -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<UserUsageKey, UserUsage, Memory>;

#[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<Version>,
}
}

pub mod interface {
pub enum ModificationType {
Set,
Delete,
}
}
1 change: 1 addition & 0 deletions src/libs/storage/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ impl StorageHeapState {
mutable_permissions: Some(rule.mutable_permissions.unwrap_or(false)),
max_size: rule.max_size,
max_capacity: rule.max_capacity,
max_items_per_user: rule.max_items_per_user,
created_at: now,
updated_at: now,
version: rule.version,
Expand Down
Loading