Skip to content

Instantly share code, notes, and snippets.

@siman
Created March 24, 2019 08:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save siman/c63a285efa43ab7b825760b5d24144b8 to your computer and use it in GitHub Desktop.
Save siman/c63a285efa43ab7b825760b5d24144b8 to your computer and use it in GitHub Desktop.
Separate fields (name, title, body, tabs)
#![cfg_attr(not(feature = "std"), no_std)]
use rstd::prelude::*;
use parity_codec::Codec;
use parity_codec_derive::{Encode, Decode};
use srml_support::{StorageMap, StorageValue, dispatch, decl_module, decl_storage, decl_event, ensure, Parameter};
use srml_support::traits::{Currency};
use runtime_primitives::traits::{Zero, SimpleArithmetic, As, Member, MaybeSerializeDebug};
use system::{self, ensure_signed};
use crate::governance::{GovernanceCurrency, BalanceOf };
use runtime_io::print;
use {timestamp};
const DEFAULT_URL_MAX_LEN: u32 = 2_000;
const DEFAULT_TAG_MAX_LEN: u32 = 50;
const DEFAULT_SLUG_MIN_LEN: u32 = 5;
const DEFAULT_SLUG_MAX_LEN: u32 = 50;
const DEFAULT_BLOG_NAME_MIN_LEN: u32 = 3;
const DEFAULT_BLOG_NAME_MAX_LEN: u32 = 50;
const DEFAULT_BLOG_DESC_MIN_LEN: u32 = 0;
const DEFAULT_BLOG_DESC_MAX_LEN: u32 = 1_000;
const DEFAULT_POST_TITLE_MIN_LEN: u32 = 3;
const DEFAULT_POST_TITLE_MAX_LEN: u32 = 200;
const DEFAULT_POST_BODY_MIN_LEN: u32 = 0;
const DEFAULT_POST_BODY_MAX_LEN: u32 = 10_000;
const DEFAULT_COMMENT_MIN_LEN: u32 = 3;
const DEFAULT_COMMENT_MAX_LEN: u32 = 1_000;
pub trait Trait: system::Trait + GovernanceCurrency + timestamp::Trait {
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
type BlogId: Parameter + Member + SimpleArithmetic + Codec + Default + Copy
+ As<usize> + As<u64> + MaybeSerializeDebug + PartialEq;
type PostId: Parameter + Member + SimpleArithmetic + Codec + Default + Copy
+ As<usize> + As<u64> + MaybeSerializeDebug + PartialEq;
type CommentId: Parameter + Member + SimpleArithmetic + Codec + Default + Copy
+ As<usize> + As<u64> + MaybeSerializeDebug + PartialEq;
}
#[derive(Clone, Debug, Encode, Decode, PartialEq)]
pub struct Blog<T: Trait> {
id: T::BlogId,
owner_id: T::AccountId,
created_at_block: T::BlockNumber,
created_at_time: T::Moment,
// TODO updated_at_X : Option<X>
slug: Vec<u8>,
name: Vec<u8>,
desc: Vec<u8>,
tags: Vec<Vec<u8>>,
image_url: Option<Vec<u8>>,
writers: Vec<T::AccountId>
}
#[derive(Clone, Debug, Encode, Decode, PartialEq)]
pub struct BlogUpdate {
slug: Option<Vec<u8>>,
name: Option<Vec<u8>>,
desc: Option<Vec<u8>>,
tags: Option<Vec<Vec<u8>>>,
image_url: Option<Option<Vec<u8>>>
}
#[derive(Clone, Debug, Encode, Decode, PartialEq)]
pub struct Post<T: Trait> {
id: T::PostId,
blog_id: T::BlogId,
owner_id: T::AccountId,
created_at_block: T::BlockNumber,
created_at_time: T::Moment,
// TODO updated_at_*
slug: Vec<u8>,
title: Vec<u8>,
body: Vec<u8>,
tags: Vec<Vec<u8>>,
image_url: Option<Vec<u8>>
}
#[derive(Clone, Debug, Encode, Decode, PartialEq)]
pub struct PostUpdate {
slug: Option<Vec<u8>>,
title: Option<Vec<u8>>,
body: Option<Vec<u8>>,
tags: Option<Vec<Vec<u8>>>,
image_url: Option<Option<Vec<u8>>>
}
#[derive(Clone, Debug, Encode, Decode, PartialEq)]
pub struct Comment<T: Trait> {
id: T::CommentId,
parent_id: Option<T::CommentId>,
post_id: T::PostId,
blog_id: T::BlogId,
owner_id: T::AccountId,
created_at_block: T::BlockNumber,
created_at_time: T::Moment,
body: Vec<u8>
}
#[derive(Clone, Debug, Encode, Decode, PartialEq)]
pub struct CommentUpdate {
body: Option<Vec<u8>>
}
decl_storage! {
trait Store for Module<T: Trait> as Blogs {
BlogById get(blog_by_id): map T::BlogId => Option<Blog<T>>;
PostById get(post_by_id): map T::PostId => Option<Post<T>>;
CommentById get(comment_by_id): map T::CommentId => Option<Comment<T>>;
BlogIdsByOwner get(blog_ids_by_owner): map T::AccountId => Vec<T::BlogId>;
PostIdsByBlogId get(post_ids_by_blog_id): map T::BlogId => Vec<T::PostId>;
CommentIdsByPostId get(comment_ids_by_post_id): map T::PostId => Vec<T::CommentId>;
BlogIdBySlug get(blog_id_by_slug): map Vec<u8> => Option<T::BlogId>;
PostIdBySlug get(post_id_by_slug): map Vec<u8> => Option<T::PostId>;
NextBlogId get(next_blog_id): T::BlogId = T::BlogId::sa(1);
NextPostId get(next_post_id): T::PostId = T::PostId::sa(1);
NextCommentId get(next_comment_id): T::CommentId = T::CommentId::sa(1);
UrlMaxLen get(url_max_len): u32 = DEFAULT_URL_MAX_LEN;
TagMaxLen get(tag_max_len): u32 = DEFAULT_TAG_MAX_LEN;
SlugMinLen get(slug_min_len): u32 = DEFAULT_SLUG_MIN_LEN;
SlugMaxLen get(slug_max_len): u32 = DEFAULT_SLUG_MAX_LEN;
BlogNameMinLen get(blog_name_min_len): u32 = DEFAULT_BLOG_NAME_MIN_LEN;
BlogNameMaxLen get(blog_name_max_len): u32 = DEFAULT_BLOG_NAME_MAX_LEN;
BlogDescMinLen get(blog_desc_min_len): u32 = DEFAULT_BLOG_DESC_MIN_LEN;
BlogDescMaxLen get(blog_desc_max_len): u32 = DEFAULT_BLOG_DESC_MAX_LEN;
PostTitleMinLen get(post_title_min_len): u32 = DEFAULT_POST_TITLE_MIN_LEN;
PostTitleMaxLen get(post_title_max_len): u32 = DEFAULT_POST_TITLE_MAX_LEN;
PostBodyMinLen get(post_body_min_len): u32 = DEFAULT_POST_BODY_MIN_LEN;
PostBodyMaxLen get(post_body_max_len): u32 = DEFAULT_POST_BODY_MAX_LEN;
CommentMinLen get(comment_min_len): u32 = DEFAULT_COMMENT_MIN_LEN;
CommentMaxLen get(comment_max_len): u32 = DEFAULT_COMMENT_MAX_LEN;
}
}
decl_event! {
pub enum Event<T> where
<T as system::Trait>::AccountId,
<T as Trait>::BlogId,
<T as Trait>::PostId,
<T as Trait>::CommentId
{
BlogAdded(AccountId, BlogId),
BlogUpdated(AccountId, BlogId),
BlogDeleted(AccountId, BlogId),
PostAdded(AccountId, BlogId, PostId),
PostUpdated(AccountId, BlogId, PostId),
PostDeleted(AccountId, BlogId, PostId),
CommentAdded(AccountId, BlogId, PostId, CommentId),
CommentUpdated(AccountId, BlogId, PostId, CommentId),
CommentDeleted(AccountId, BlogId, PostId, CommentId),
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
fn deposit_event<T>() = default;
fn on_initialise(_now: T::BlockNumber) {
// Stub
}
fn on_finalise(_now: T::BlockNumber) {
// Stub
}
fn add_blog(origin, blog: BlogUpdate) {
let owner = ensure_signed(origin)?;
let slug = blog.slug.unwrap_or_default();
ensure!(slug.len() >= Self::slug_min_len() as usize, "Blog slug is too short");
ensure!(slug.len() <= Self::slug_max_len() as usize, "Blog slug is too long");
ensure!(!<BlogIdBySlug<T>>::exists(slug.clone()), "Blog slug is not unique");
let name = blog.name.unwrap_or_default();
ensure!(name.len() >= Self::blog_name_min_len() as usize, "Blog name is too short");
ensure!(name.len() <= Self::blog_name_max_len() as usize, "Blog name is too long");
let desc = blog.desc.unwrap_or_default();
ensure!(desc.len() >= Self::blog_desc_min_len() as usize, "Blog description is too short");
ensure!(desc.len() <= Self::blog_desc_max_len() as usize, "Blog description is too long");
let tags = blog.tags.unwrap_or_default();
// TODO validate tags in a loop
let image_url = blog.image_url.unwrap_or_default();
// image_url.map(|url| )
// ensure!(url.len() <= Self::image_url_max_len() as usize, "Blog image URL is too long");
let blog_id = Self::next_blog_id();
let new_blog: Blog<T> = Blog {
id: blog_id,
owner_id: owner.clone(),
created_at_block: <system::Module<T>>::block_number(),
created_at_time: <timestamp::Module<T>>::now(),
slug: slug.clone(),
name,
desc,
tags,
image_url,
writers: vec![]
};
<BlogById<T>>::insert(blog_id, new_blog);
<BlogIdsByOwner<T>>::mutate(owner.clone(), |ids| ids.push(blog_id));
<BlogIdBySlug<T>>::insert(slug, blog_id);
<NextBlogId<T>>::mutate(|n| { *n += T::BlogId::sa(1); });
Self::deposit_event(RawEvent::BlogAdded(owner.clone(), blog_id));
}
fn add_post(origin, blog_id: T::BlogId, post: PostUpdate) {
let owner = ensure_signed(origin)?;
ensure!(!<BlogById<T>>::exists(blog_id), "Unknown blog id");
let slug = post.slug.unwrap_or_default();
ensure!(slug.len() >= Self::slug_min_len() as usize, "Post slug is too short");
ensure!(slug.len() <= Self::slug_max_len() as usize, "Post slug is too long");
ensure!(!<PostIdBySlug<T>>::exists(slug.clone()), "Post slug is not unique");
let title = post.title.unwrap_or_default();
ensure!(title.len() >= Self::post_title_min_len() as usize, "Post title is too short");
ensure!(title.len() <= Self::post_title_max_len() as usize, "Post title is too long");
let body = post.body.unwrap_or_default();
ensure!(body.len() >= Self::post_body_min_len() as usize, "Post body is too short");
ensure!(body.len() <= Self::post_body_max_len() as usize, "Post body is too long");
let tags = post.tags.unwrap_or_default();
// TODO validate tags in a loop
let image_url = post.image_url.unwrap_or_default();
// image_url.map(|url| )
// ensure!(url.len() <= Self::image_url_max_len() as usize, "Post image URL is too long");
let post_id = Self::next_post_id();
let new_post: Post<T> = Post {
id: post_id,
blog_id,
owner_id: owner.clone(),
created_at_block: <system::Module<T>>::block_number(),
created_at_time: <timestamp::Module<T>>::now(),
slug: slug.clone(),
title,
body,
tags,
image_url
};
<PostById<T>>::insert(post_id, new_post);
<PostIdsByBlogId<T>>::mutate(blog_id, |ids| ids.push(post_id));
<PostIdBySlug<T>>::insert(slug, post_id);
<NextPostId<T>>::mutate(|n| { *n += T::PostId::sa(1); });
Self::deposit_event(RawEvent::PostAdded(owner.clone(), blog_id, post_id));
}
fn add_comment(origin, post_id: T::PostId, parent_id: Option<T::CommentId>, comment: CommentUpdate) {
let owner = ensure_signed(origin)?;
ensure!(!<PostById<T>>::exists(post_id), "Unknown post id");
let post = Self::post_by_id(&post_id).unwrap();
let blog_id = post.blog_id;
if let Some(id) = parent_id {
ensure!(!<CommentById<T>>::exists(id), "Unknown parent comment id");
}
let body = comment.body.unwrap_or_default();
ensure!(body.len() >= Self::comment_min_len() as usize, "Comment is too short");
ensure!(body.len() <= Self::comment_max_len() as usize, "Comment is too long");
let comment_id = Self::next_comment_id();
let new_comment: Comment<T> = Comment {
id: comment_id,
parent_id,
post_id,
blog_id,
owner_id: owner.clone(),
created_at_block: <system::Module<T>>::block_number(),
created_at_time: <timestamp::Module<T>>::now(),
body
};
<CommentById<T>>::insert(comment_id, new_comment);
<CommentIdsByPostId<T>>::mutate(post_id, |ids| ids.push(comment_id));
<NextCommentId<T>>::mutate(|n| { *n += T::CommentId::sa(1); });
Self::deposit_event(RawEvent::CommentAdded(owner.clone(), blog_id, post_id, comment_id));
}
// TODO fn update_blog(origin, blog_id: T::BlogId, blog: BlogUpdate) {}
// TODO fn update_post(origin, post_id: T::PostId, post: PostUpdate) {}
// TODO fn update_comment(origin, comment_id: T::CommentId, comment: CommentUpdate) {}
// TODO fn delete_blog(origin, blog_id: T::BlogId) {}
// TODO fn delete_post(origin, post_id: T::PostId) {}
// TODO fn delete_comment(origin, comment_id: T::CommentId) {}
// TODO spend some tokens on: create/update a blog/post/comment.
}
}
impl<T: Trait> Module<T> {
// Stub
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment