Skip to content
Merged
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
2 changes: 0 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ members = [
"src/logs",
"src/xrc",
"src/user",
"src/dialectica",
"src/alex_wallet",
"src/emporium",
"src/logs",
"src/asset_manager",
"src/feed",
"src/alex_revshare",
"src/kairos",
]
resolver = "2"
11 changes: 1 addition & 10 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: all clean fresh ii xrc icrc7 icrc7-scion nft-manager alex-backend perpetua feed icp-swap tokenomics user system-api alex-wallet vetkd emporium logs asset-manager alex-revshare dialectica kairos ensure-identities clean-identities test icp-ledger lbry alex tokens frontend help
.PHONY: all clean fresh ii xrc icrc7 icrc7-scion nft-manager alex-backend perpetua feed icp-swap tokenomics user system-api alex-wallet vetkd emporium logs asset-manager alex-revshare kairos ensure-identities clean-identities test icp-ledger lbry alex tokens frontend help

# Start dfx and basic setup
clean:
Expand Down Expand Up @@ -163,14 +163,6 @@ alex-revshare:
candid-extractor target/wasm32-unknown-unknown/release/alex_revshare.wasm > src/alex_revshare/alex_revshare.did
dfx deploy alex_revshare --specified-id e454q-riaaa-aaaap-qqcyq-cai

# Deploy Dialectica
dialectica:
@echo "Deploying Dialectica..."
cargo build --release --target wasm32-unknown-unknown --package dialectica
candid-extractor target/wasm32-unknown-unknown/release/dialectica.wasm > src/dialectica/dialectica.did
dfx deploy dialectica
dfx generate dialectica

# Deploy Kairos
kairos:
@echo "Deploying Kairos..."
Expand Down Expand Up @@ -293,7 +285,6 @@ help:
@echo " logs - Deploy Logs"
@echo " asset-manager - Deploy Asset Manager"
@echo " alex-revshare - Deploy Alex Revshare"
@echo " dialectica - Deploy Dialectica canister"
@echo " kairos - Deploy Kairos canister"
@echo " ensure-identities - Ensure all required identities, Create if don't exist"
@echo " clean-identities - Remove all project identities"
Expand Down
10 changes: 0 additions & 10 deletions dfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@
"package": "user",
"candid": "src/user/user.did"
},
"dialectica": {
"type": "rust",
"package": "dialectica",
"candid": "src/dialectica/dialectica.did"
},
"alex_backend": {
"candid": "src/alex_backend/alex_backend.did",
"package": "alex_backend",
Expand Down Expand Up @@ -145,11 +140,6 @@
"package": "alex_revshare",
"type": "rust",
"specified_id": "e454q-riaaa-aaaap-qqcyq-cai"
},
"kairos": {
"type": "rust",
"package": "kairos",
"candid": "src/kairos/kairos.did"
}
},
"defaults": {
Expand Down
81 changes: 78 additions & 3 deletions src/alex_backend/alex_backend.did
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
type Activity = record {
id : nat64;
activity_type : ActivityType;
updated_at : nat64;
user : principal;
created_at : nat64;
arweave_id : text;
};
type ActivityError = variant {
AnonymousNotAllowed;
NotFound : nat64;
Unauthorized;
AlreadyExists;
InvalidArweaveId;
InternalError : text;
InvalidComment : text;
};
type ActivityType = variant { Comment : text; Reaction : ReactionType };
type CommentInfo = record {
id : nat64;
user : principal;
created_at : nat64;
comment : text;
};
type HttpRequest = record {
url : text;
method : text;
Expand All @@ -9,7 +33,20 @@ type HttpResponse = record {
headers : vec record { text; text };
status_code : nat16;
};
type Result = variant { Ok : text; Err : text };
type ReactionCounts = record {
total_comments : nat64;
likes : nat64;
dislikes : nat64;
};
type ReactionType = variant { Like; Dislike };
type Result = variant { Ok : Activity; Err : ActivityError };
type Result_1 = variant { Ok : vec Activity; Err : ActivityError };
type Result_2 = variant { Ok : vec CommentInfo; Err : ActivityError };
type Result_3 = variant { Ok : nat64; Err : ActivityError };
type Result_4 = variant { Ok : ReactionCounts; Err : ActivityError };
type Result_5 = variant { Ok : opt ReactionType; Err : ActivityError };
type Result_6 = variant { Ok; Err : ActivityError };
type Result_7 = variant { Ok : text; Err : text };
type UserNFTInfo = record {
"principal" : principal;
username : text;
Expand All @@ -18,8 +55,46 @@ type UserNFTInfo = record {
has_nfts : bool;
};
service : () -> {
// Add a comment to an NFT
add_comment : (text, text) -> (Result);
// Add or update a reaction to an NFT
add_reaction : (text, ReactionType) -> (Result);
// Get all activities for a specific NFT
get_activities : (text) -> (Result_1) query;
// Get a specific activity by ID
get_activity : (nat64) -> (Result) query;
// Get all comments for a specific NFT
get_comments : (text) -> (Result_2) query;
// Get the impression count for an article
get_impressions : (text) -> (Result_3) query;
// Get aggregated reaction counts for a specific NFT
get_reaction_counts : (text) -> (Result_4) query;
get_stored_nft_users : () -> (vec UserNFTInfo) query;
// Get all activities by a specific user
get_user_activities : (principal) -> (Result_1) query;
// Get the current user's reaction for a specific NFT
get_user_reaction : (text) -> (Result_5) query;
// Get a specific user's reaction for a specific NFT
get_user_reaction_for_principal : (text, principal) -> (Result_5) query;
// Get the view count for an article
get_view_count : (text) -> (Result_3) query;
http_request : (HttpRequest) -> (HttpResponse) query;
start_alex_supply_timer : () -> (Result);
update_alex_supply : () -> (Result);
// Record an impression for an article (article appeared in feed)
// Anyone can call, always increments counter
record_impression : (text) -> (Result_3);
// Record a view for an article (user opened full article)
// Anyone can call
// - Authenticated users: deduplicated (only counted once per user)
// - Anonymous users: always added
record_view : (text) -> (Result_3);
// Remove a comment (only by the comment author)
remove_comment : (nat64) -> (Result_6);
// Remove a user's reaction from an NFT
remove_reaction : (text) -> (Result_6);
start_alex_supply_timer : () -> (Result_7);
update_alex_supply : () -> (Result_7);
// Update a comment (only by the comment author)
update_comment : (nat64, text) -> (Result);
// Get the caller's principal
whoami : () -> (principal) query;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ use candid::Principal;
use ic_cdk::api::caller;
use ic_cdk_macros::query;

use crate::errors::activity::{ActivityError, ActivityResult};
use crate::models::activity::{Activity, ActivityType, CommentInfo, ReactionCounts, ReactionType};
use crate::store::{
ACTIVITIES, ARWEAVE_ACTIVITIES, USER_ACTIVITIES, USER_REACTIONS,
use crate::dialectica::errors::activity::{ActivityError, ActivityResult};
use crate::dialectica::models::activity::{Activity, ActivityType, CommentInfo, ReactionCounts, ReactionType};
use crate::dialectica::store::{
ACTIVITIES, ARWEAVE_ACTIVITIES, IMPRESSIONS, USER_ACTIVITIES, USER_REACTIONS, VIEWS,
StorableString, StorablePrincipal, StorableUserReactionKey, UserReactionKey
};

Expand Down Expand Up @@ -200,4 +200,33 @@ pub fn get_activity(activity_id: u64) -> ActivityResult<Activity> {
None => Err(ActivityError::NotFound(activity_id)),
}
})
}

/// Get the impression count for an article
#[query]
pub fn get_impressions(arweave_id: String) -> ActivityResult<u64> {
if arweave_id.trim().is_empty() || arweave_id.len() != 43 {
return Err(ActivityError::InvalidArweaveId);
}

IMPRESSIONS.with(|impressions| {
let impressions = impressions.borrow();
Ok(impressions.get(&StorableString(arweave_id)).unwrap_or(0))
})
}

/// Get the view count for an article
#[query]
pub fn get_view_count(arweave_id: String) -> ActivityResult<u64> {
if arweave_id.trim().is_empty() || arweave_id.len() != 43 {
return Err(ActivityError::InvalidArweaveId);
}

VIEWS.with(|views| {
let views = views.borrow();
match views.get(&StorableString(arweave_id)) {
Some(viewers) => Ok(viewers.0.0.len() as u64),
None => Ok(0),
}
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ use candid::Principal;
use ic_cdk::api::caller;
use ic_cdk_macros::update;

use crate::errors::activity::{ActivityError, ActivityResult};
use crate::models::activity::{Activity, ActivityType, ReactionType};
use crate::store::{
use crate::dialectica::errors::activity::{ActivityError, ActivityResult};
use crate::dialectica::models::activity::{Activity, ActivityType, ReactionType};
use crate::dialectica::store::{
get_next_activity_id, ActivityIdList, StorableActivity, StorableActivityIdList,
StorablePrincipal, StorableString, StorableUserReactionKey, UserReactionKey,
ACTIVITIES, ARWEAVE_ACTIVITIES, USER_ACTIVITIES, USER_REACTIONS,
StorablePrincipal, StorableString, StorableUserReactionKey, StorableViewersList,
UserReactionKey, ViewersList,
ACTIVITIES, ARWEAVE_ACTIVITIES, IMPRESSIONS, USER_ACTIVITIES, USER_REACTIONS, VIEWS,
};

/// Add or update a reaction to an NFT
Expand Down Expand Up @@ -330,4 +331,64 @@ pub fn update_comment(activity_id: u64, new_comment: String) -> ActivityResult<A
None => Err(ActivityError::NotFound(activity_id)),
}
})
}

/// Record an impression for an article (article appeared in feed)
/// Anyone can call, always increments counter
#[update]
pub fn record_impression(arweave_id: String) -> ActivityResult<u64> {
if arweave_id.trim().is_empty() || arweave_id.len() != 43 {
return Err(ActivityError::InvalidArweaveId);
}

IMPRESSIONS.with(|impressions| {
let mut impressions = impressions.borrow_mut();
let current = impressions.get(&StorableString(arweave_id.clone())).unwrap_or(0);
let new_count = current + 1;
impressions.insert(StorableString(arweave_id), new_count);
Ok(new_count)
})
}

/// Record a view for an article (user opened full article)
/// Anyone can call
/// - Authenticated users: deduplicated (only counted once per user)
/// - Anonymous users: always added
#[update]
pub fn record_view(arweave_id: String) -> ActivityResult<u64> {
let caller = caller();

if arweave_id.trim().is_empty() || arweave_id.len() != 43 {
return Err(ActivityError::InvalidArweaveId);
}

VIEWS.with(|views| {
let mut views = views.borrow_mut();
let mut viewers = match views.get(&StorableString(arweave_id.clone())) {
Some(list) => list.0.0,
None => Vec::new(),
};

if caller == Principal::anonymous() {
// Anonymous: always add None
viewers.push(None);
} else {
// Authenticated: check if already viewed
let already_viewed = viewers.iter().any(|v| match v {
Some(p) => *p == caller,
None => false,
});

if !already_viewed {
viewers.push(Some(caller));
}
}

let count = viewers.len() as u64;
views.insert(
StorableString(arweave_id),
StorableViewersList(ViewersList(viewers)),
);
Ok(count)
})
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
use candid::Principal;
use ic_cdk_macros::init;
use crate::store::init_counters;

pub mod api;
pub mod errors;
pub mod models;
pub mod store;

// Re-export main types for use in export_candid!()
// Re-export main types
pub use api::queries::*;
pub use api::updates::*;
pub use errors::activity::{ActivityError, ActivityResult};
Expand All @@ -18,10 +14,7 @@ pub use models::types::{
AddCommentRequest, AddReactionRequest, ActivityResponse, UpdateCommentRequest
};

ic_cdk::export_candid!();

#[init]
fn init() {
ic_cdk::setup();
init_counters();
}
// Initialize dialectica counters - call this from main init
pub fn init() {
store::init_counters();
}
Loading