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: 1 addition & 1 deletion contracts/src/activity_log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! Activity logs are append-only and cannot be modified once stored.

use soroban_sdk::{
Address, BytesN, Env, Vec,
contracttype, Address, BytesN, Env, Vec,
};

// Activity log entry stored on-chain.
Expand Down
88 changes: 88 additions & 0 deletions contracts/src/blogging_platform.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
use soroban_sdk::{
contract, contractimpl, contracttype, Address, BytesN, Env, String, Vec, Symbol,
contracttype, Address, Env, String, Symbol, Vec, BytesN,
};

#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Post {
pub id: u64,
pub author: Address,
pub content_hash: BytesN<32>,
pub timestamp: u64,
pub price: i128,
pub is_paid: bool,
pub struct BlogPost {
pub id: u64,
pub author: Address,
Expand All @@ -17,11 +25,91 @@ pub struct BlogPost {
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Comment {
pub author: Address,
pub text_hash: BytesN<32>,
pub content: String,
pub timestamp: u64,
}

#[contracttype]
enum DataKey {
PostCount,
Posts(u64),
Comments(u64),
Reactions(u64, Symbol), // (post_id, reaction_type) -> count
HasAccess(Address, u64), // (user, post_id) -> bool
}

#[contract]
pub struct BloggingPlatform;

#[contractimpl]
impl BloggingPlatform {
pub fn create_post(env: Env, author: Address, content_hash: BytesN<32>, price: i128) -> u64 {
author.require_auth();

let mut count: u64 = env.storage().instance().get(&DataKey::PostCount).unwrap_or(0);
count += 1;

let post = Post {
id: count,
author: author.clone(),
content_hash,
timestamp: env.ledger().timestamp(),
price,
is_paid: price > 0,
};

env.storage().persistent().set(&DataKey::Posts(count), &post);
env.storage().instance().set(&DataKey::PostCount, &count);

env.events().publish(
(Symbol::new(&env, "post_created"), author),
(count, content_hash),
);

count
}

pub fn get_post(env: Env, post_id: u64) -> Option<Post> {
env.storage().persistent().get(&DataKey::Posts(post_id))
}

pub fn add_comment(env: Env, author: Address, post_id: u64, text_hash: BytesN<32>) {
author.require_auth();

let mut comments: Vec<Comment> = env
.storage()
.persistent()
.get(&DataKey::Comments(post_id))
.unwrap_or(Vec::new(&env));

comments.push_back(Comment {
author: author.clone(),
text_hash,
timestamp: env.ledger().timestamp(),
});

env.storage().persistent().set(&DataKey::Comments(post_id), &comments);

env.events().publish(
(Symbol::new(&env, "comment_added"), post_id),
(author, text_hash),
);
}

pub fn react(env: Env, user: Address, post_id: u64, reaction: Symbol) {
user.require_auth();

let key = DataKey::Reactions(post_id, reaction.clone());
let mut count: u32 = env.storage().persistent().get(&key).unwrap_or(0);
count += 1;

env.storage().persistent().set(&key, &count);

env.events().publish(
(Symbol::new(&env, "post_reaction"), post_id),
(user, reaction, count),
);
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum ReactionType {
Like,
Expand Down
74 changes: 74 additions & 0 deletions contracts/src/content_monetization.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,78 @@
use soroban_sdk::{
contract, contractimpl, contracttype, Address, Env, Symbol,
};
use crate::blogging_platform::{BloggingPlatformClient, Post};

#[contracttype]
enum DataKey {
PlatformAdmin,
Earnings(Address),
Subscriptions(Address, Address), // (subscriber, creator) -> expiry
}

#[contract]
pub struct ContentMonetization;

#[contractimpl]
impl ContentMonetization {
pub fn init(env: Env, admin: Address) {
env.storage().instance().set(&DataKey::PlatformAdmin, &admin);
}

pub fn tip_creator(env: Env, tipper: Address, creator: Address, token: Address, amount: i128) {
tipper.require_auth();

let token_client = crate::token::RsTokenContractClient::new(&env, &token);
// Using token_id 0 as default for TIPS
token_client.transfer(&tipper, &creator, &0, &amount);

// Track earnings
let mut earnings: i128 = env.storage().persistent().get(&DataKey::Earnings(creator.clone())).unwrap_or(0);
earnings += amount;
env.storage().persistent().set(&DataKey::Earnings(creator.clone()), &earnings);

env.events().publish(
(Symbol::new(&env, "tip_sent"), tipper),
(creator, amount),
);
}

pub fn purchase_access(env: Env, user: Address, post_id: u64, blog_contract: Address, token: Address) {
user.require_auth();

let blog_client = BloggingPlatformClient::new(&env, &blog_contract);
let post = blog_client.get_post(&post_id).unwrap();

if post.is_paid {
let token_client = crate::token::RsTokenContractClient::new(&env, &token);
token_client.transfer(&user, &post.author, &0, &post.price);

// Note: In a real app, we would store access in a separate contract or this one
// For now, we emit an event that the frontend can use
env.events().publish(
(Symbol::new(&env, "access_purchased"), user),
(post_id, post.author),
);
}
}

pub fn subscribe(env: Env, subscriber: Address, creator: Address, token: Address, amount: i128, duration: u64) {
subscriber.require_auth();

let token_client = crate::token::RsTokenContractClient::new(&env, &token);
token_client.transfer(&subscriber, &creator, &0, &amount);

let expiry = env.ledger().timestamp() + duration;
env.storage().persistent().set(&DataKey::Subscriptions(subscriber.clone(), creator.clone()), &expiry);

env.events().publish(
(Symbol::new(&env, "subscription_created"), subscriber),
(creator, expiry),
);
}

pub fn get_earnings(env: Env, creator: Address) -> i128 {
env.storage().persistent().get(&DataKey::Earnings(creator)).unwrap_or(0)
contracttype, Address, Env, Symbol, token,
};

Expand Down
5 changes: 5 additions & 0 deletions contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ pub mod quadratic_voting;
// #[cfg(test)]
// pub mod fuzz;
pub mod token;
pub mod upgrade;
pub mod airdrop_manager;
pub mod merkle_distributor;
pub mod blogging_platform;
pub mod content_monetization;
pub mod airdrop_manager;
pub mod merkle_distributor;
pub mod crowdfunding;
Expand Down
2 changes: 1 addition & 1 deletion contracts/src/statistics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! All statistics are tracked on-chain and can be queried for analytics dashboards.

use soroban_sdk::{
Address, BytesN, Env,
contracttype, Address, BytesN, Env,
};

use crate::activity_log::ActivityLogManager;
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/app/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use client';

import { BlogDashboard } from '@/components/blogging/BlogDashboard';

export default function BlogPage() {
return <BlogDashboard />;
}
Loading
Loading