Created
March 24, 2019 08:25
-
-
Save siman/c63a285efa43ab7b825760b5d24144b8 to your computer and use it in GitHub Desktop.
Separate fields (name, title, body, tabs)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![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