lib.rs 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576
  1. // Ensure we're `no_std` when compiling for Wasm.
  2. #![cfg_attr(not(feature = "std"), no_std)]
  3. #![allow(clippy::type_complexity)]
  4. #[cfg(feature = "std")]
  5. pub use serde::{Deserialize, Serialize};
  6. use codec::{Codec, Decode, Encode};
  7. pub use frame_support::dispatch::DispatchResult;
  8. use frame_support::{
  9. decl_error, decl_event, decl_module, decl_storage, ensure, traits::Get, Parameter,
  10. };
  11. use frame_system::ensure_signed;
  12. use sp_arithmetic::traits::{BaseArithmetic, One};
  13. pub use sp_io::storage::clear_prefix;
  14. use sp_runtime::traits::{MaybeSerialize, Member};
  15. use sp_std::prelude::*;
  16. mod mock;
  17. mod tests;
  18. pub trait Trait: frame_system::Trait + pallet_timestamp::Trait + Sized {
  19. type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
  20. type ForumUserId: Parameter
  21. + Member
  22. + BaseArithmetic
  23. + Codec
  24. + Default
  25. + Copy
  26. + MaybeSerialize
  27. + PartialEq;
  28. type ModeratorId: Parameter
  29. + Member
  30. + BaseArithmetic
  31. + Codec
  32. + Default
  33. + Copy
  34. + MaybeSerialize
  35. + PartialEq;
  36. type CategoryId: Parameter
  37. + Member
  38. + BaseArithmetic
  39. + Codec
  40. + Default
  41. + Copy
  42. + MaybeSerialize
  43. + PartialEq
  44. + From<u64>
  45. + Into<u64>;
  46. type ThreadId: Parameter
  47. + Member
  48. + BaseArithmetic
  49. + Codec
  50. + Default
  51. + Copy
  52. + MaybeSerialize
  53. + PartialEq
  54. + From<u64>
  55. + Into<u64>;
  56. type PostId: Parameter
  57. + Member
  58. + BaseArithmetic
  59. + Codec
  60. + Default
  61. + Copy
  62. + MaybeSerialize
  63. + PartialEq
  64. + From<u64>
  65. + Into<u64>;
  66. type PostReactionId: Parameter
  67. + Member
  68. + BaseArithmetic
  69. + Codec
  70. + Default
  71. + Copy
  72. + MaybeSerialize
  73. + PartialEq
  74. + From<u64>
  75. + Into<u64>;
  76. type MaxCategoryDepth: Get<u64>;
  77. type MapLimits: StorageLimits;
  78. fn is_lead(account_id: &<Self as frame_system::Trait>::AccountId) -> bool;
  79. fn is_forum_member(
  80. account_id: &<Self as frame_system::Trait>::AccountId,
  81. forum_user_id: &Self::ForumUserId,
  82. ) -> bool;
  83. fn is_moderator(account_id: &Self::AccountId, moderator_id: &Self::ModeratorId) -> bool;
  84. fn calculate_hash(text: &[u8]) -> Self::Hash;
  85. }
  86. /// Upper bounds for storage maps and double maps. Needed to prevent potential block exhaustion during deletion, etc.
  87. /// MaxSubcategories, MaxThreadsInCategory, and MaxPostsInThread should be reasonably small because when the category is deleted
  88. /// all of it's subcategories with their threads and posts will be iterated over and deleted.
  89. pub trait StorageLimits {
  90. /// Maximum direct subcategories in a category
  91. type MaxSubcategories: Get<u64>;
  92. /// Maximum direct threads in a category
  93. type MaxThreadsInCategory: Get<u64>;
  94. /// Maximum posts in a thread
  95. type MaxPostsInThread: Get<u64>;
  96. /// Maximum moderator count for a single category
  97. type MaxModeratorsForCategory: Get<u64>;
  98. /// Maximum total of all existing categories
  99. type MaxCategories: Get<u64>;
  100. /// Maximum number of poll alternatives
  101. type MaxPollAlternativesNumber: Get<u64>;
  102. }
  103. /// Represents all poll alternatives and vote count for each one
  104. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  105. #[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
  106. pub struct PollAlternative<Hash> {
  107. /// hash of alternative description
  108. pub alternative_text_hash: Hash,
  109. /// Vote count for the alternative
  110. pub vote_count: u32,
  111. }
  112. /// Represents a poll
  113. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
  114. #[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)]
  115. pub struct Poll<Timestamp, Hash> {
  116. /// hash of description
  117. pub description_hash: Hash,
  118. /// pallet_timestamp of poll end
  119. pub end_time: Timestamp,
  120. /// Alternative description and count
  121. pub poll_alternatives: Vec<PollAlternative<Hash>>,
  122. }
  123. /// Represents a thread post
  124. #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
  125. #[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
  126. pub struct Post<ForumUserId, ThreadId, Hash> {
  127. /// Id of thread to which this post corresponds.
  128. pub thread_id: ThreadId,
  129. /// Hash of current text
  130. pub text_hash: Hash,
  131. /// Author of post.
  132. pub author_id: ForumUserId,
  133. }
  134. /// Represents a thread
  135. #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
  136. #[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
  137. pub struct Thread<ForumUserId, CategoryId, Moment, Hash> {
  138. /// Title hash
  139. pub title_hash: Hash,
  140. /// Category in which this thread lives
  141. pub category_id: CategoryId,
  142. /// Author of post.
  143. pub author_id: ForumUserId,
  144. /// Whether thread is archived.
  145. pub archived: bool,
  146. /// poll description.
  147. pub poll: Option<Poll<Moment, Hash>>,
  148. // Number of posts in thread, needed for map limit checks
  149. pub num_direct_posts: u32,
  150. }
  151. /// Represents a category
  152. #[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
  153. #[derive(Encode, Decode, Default, Clone, PartialEq, Eq)]
  154. pub struct Category<CategoryId, ThreadId, Hash> {
  155. /// Title
  156. pub title_hash: Hash,
  157. /// Description
  158. pub description_hash: Hash,
  159. /// Whether category is archived.
  160. pub archived: bool,
  161. /// Number of subcategories, needed for emptiness checks when trying to delete category
  162. pub num_direct_subcategories: u32,
  163. // Number of threads in category, needed for emptiness checks when trying to delete category
  164. pub num_direct_threads: u32,
  165. pub num_direct_moderators: u32,
  166. /// Parent category, if child of another category, otherwise this category is a root category
  167. pub parent_category_id: Option<CategoryId>,
  168. /// Sticky threads list
  169. pub sticky_thread_ids: Vec<ThreadId>,
  170. }
  171. #[derive(Encode, Decode, Clone, PartialEq, Eq)]
  172. pub enum PrivilegedActor<T: Trait> {
  173. Lead,
  174. Moderator(T::ModeratorId),
  175. }
  176. impl<T: Trait> core::fmt::Debug for PrivilegedActor<T> {
  177. fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
  178. match self {
  179. PrivilegedActor::Lead => write!(formatter, "PrivilegedActor {{ Lead }}"),
  180. PrivilegedActor::Moderator(moderator_id) => {
  181. write!(formatter, "PrivilegedActor {{ {:?} }}", moderator_id)
  182. }
  183. }
  184. }
  185. }
  186. /// Represents a sequence of categories which have child-parent relatioonship
  187. /// where last element is final ancestor, or root, in the context of the category tree.
  188. type CategoryTreePath<CategoryId, ThreadId, Hash> =
  189. Vec<(CategoryId, Category<CategoryId, ThreadId, Hash>)>;
  190. // TODO: remove when this issue is solved https://github.com/rust-lang/rust-clippy/issues/3381
  191. // temporary type for functions argument
  192. type CategoryTreePathArg<CategoryId, ThreadId, Hash> =
  193. [(CategoryId, Category<CategoryId, ThreadId, Hash>)];
  194. decl_error! {
  195. /// Forum predefined errors
  196. pub enum Error for Module<T: Trait> {
  197. /// Origin doesn't correspond to any lead account
  198. OriginNotForumLead,
  199. /// Forum user id not match its account.
  200. ForumUserIdNotMatchAccount,
  201. /// Moderator id not match its account.
  202. ModeratorIdNotMatchAccount,
  203. // Errors about thread.
  204. /// Thread not authored by the given user.
  205. AccountDoesNotMatchThreadAuthor,
  206. /// Thread does not exist
  207. ThreadDoesNotExist,
  208. /// Moderator can't moderate category containing thread.
  209. ModeratorModerateOriginCategory,
  210. /// Moderator can't moderate destination category.
  211. ModeratorModerateDestinationCategory,
  212. /// Origin is the same as the destination.
  213. ThreadMoveInvalid,
  214. /// Thread not being updated.
  215. ThreadNotBeingUpdated,
  216. /// Thread is immutable, i.e. archived.
  217. ThreadImmutable,
  218. // Errors about post.
  219. /// Post does not exist.
  220. PostDoesNotExist,
  221. /// Account does not match post author.
  222. AccountDoesNotMatchPostAuthor,
  223. // Errors about category.
  224. /// Category not being updated.
  225. CategoryNotBeingUpdated,
  226. /// Ancestor category immutable, i.e. deleted or archived
  227. AncestorCategoryImmutable,
  228. /// Maximum valid category depth exceeded.
  229. MaxValidCategoryDepthExceeded,
  230. /// Category does not exist.
  231. CategoryDoesNotExist,
  232. /// Provided moderator is not given category moderator
  233. CategoryModeratorDoesNotExist,
  234. /// Category still contains some threads.
  235. CategoryNotEmptyThreads,
  236. /// Category still contains some subcategories.
  237. CategoryNotEmptyCategories,
  238. /// No permissions to delete category.
  239. ModeratorCantDeleteCategory,
  240. /// No permissions to update category.
  241. ModeratorCantUpdateCategory,
  242. // Errors about poll.
  243. /// Poll items number too short.
  244. PollAlternativesTooShort,
  245. /// Poll not exist.
  246. PollNotExist,
  247. /// Poll date setting is wrong.
  248. PollTimeSetting,
  249. /// Poll data committed is wrong.
  250. PollData,
  251. /// Poll data committed after poll expired.
  252. PollCommitExpired,
  253. // Error data migration
  254. /// data migration not done yet.
  255. DataMigrationNotDone,
  256. // Error for limited size
  257. /// Maximum size of storage map exceeded
  258. MapSizeLimit,
  259. }
  260. }
  261. decl_storage! {
  262. trait Store for Module<T: Trait> as Forum_1_1 {
  263. /// Map category identifier to corresponding category.
  264. pub CategoryById get(fn category_by_id) config(): map hasher(blake2_128_concat) T::CategoryId => Category<T::CategoryId, T::ThreadId, T::Hash>;
  265. /// Category identifier value to be used for the next Category created.
  266. pub NextCategoryId get(fn next_category_id) config(): T::CategoryId;
  267. /// Counter for all existing categories.
  268. pub CategoryCounter get(fn category_counter) config(): T::CategoryId;
  269. /// Map thread identifier to corresponding thread.
  270. pub ThreadById get(fn thread_by_id) config(): double_map hasher(blake2_128_concat) T::CategoryId, hasher(blake2_128_concat) T::ThreadId => Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>;
  271. /// Thread identifier value to be used for next Thread in threadById.
  272. pub NextThreadId get(fn next_thread_id) config(): T::ThreadId;
  273. /// Map post identifier to corresponding post.
  274. pub PostById get(fn post_by_id) config(): double_map hasher(blake2_128_concat) T::ThreadId, hasher(blake2_128_concat) T::PostId => Post<T::ForumUserId, T::ThreadId, T::Hash>;
  275. /// Post identifier value to be used for for next post created.
  276. pub NextPostId get(fn next_post_id) config(): T::PostId;
  277. /// Moderator set for each Category
  278. pub CategoryByModerator get(fn category_by_moderator) config(): double_map hasher(blake2_128_concat) T::CategoryId, hasher(blake2_128_concat) T::ModeratorId => ();
  279. /// If data migration is done, set as configible for unit test purpose
  280. pub DataMigrationDone get(fn data_migration_done) config(): bool;
  281. }
  282. }
  283. decl_event!(
  284. pub enum Event<T>
  285. where
  286. <T as Trait>::CategoryId,
  287. <T as Trait>::ModeratorId,
  288. <T as Trait>::ThreadId,
  289. <T as Trait>::PostId,
  290. <T as Trait>::ForumUserId,
  291. <T as Trait>::PostReactionId,
  292. {
  293. /// A category was introduced
  294. CategoryCreated(CategoryId),
  295. /// A category with given id was updated.
  296. /// The second argument reflects the new archival status of the category.
  297. CategoryUpdated(CategoryId, bool),
  298. // A category was deleted
  299. CategoryDeleted(CategoryId),
  300. /// A thread with given id was created.
  301. ThreadCreated(ThreadId),
  302. /// A thread with given id was moderated.
  303. ThreadModerated(ThreadId, Vec<u8>),
  304. /// A thread with given id was updated.
  305. /// The second argument reflects the new archival status of the thread.
  306. ThreadUpdated(ThreadId, bool),
  307. /// A thread with given id was moderated.
  308. ThreadTitleUpdated(ThreadId),
  309. /// A thread was deleted.
  310. ThreadDeleted(ThreadId),
  311. /// A thread was moved to new category
  312. ThreadMoved(ThreadId, CategoryId),
  313. /// Post with given id was created.
  314. PostAdded(PostId),
  315. /// Post with givne id was moderated.
  316. PostModerated(PostId, Vec<u8>),
  317. /// Post with given id had its text updated.
  318. /// The second argument reflects the number of total edits when the text update occurs.
  319. PostTextUpdated(PostId),
  320. /// Thumb up post
  321. PostReacted(ForumUserId, PostId, PostReactionId),
  322. /// Vote on poll
  323. VoteOnPoll(ThreadId, u32),
  324. /// Sticky thread updated for category
  325. CategoryStickyThreadUpdate(CategoryId, Vec<ThreadId>),
  326. /// An moderator ability to moderate a category and its subcategories updated
  327. CategoryMembershipOfModeratorUpdated(ModeratorId, CategoryId, bool),
  328. }
  329. );
  330. decl_module! {
  331. pub struct Module<T: Trait> for enum Call where origin: T::Origin {
  332. /// Predefined errors
  333. type Error = Error<T>;
  334. fn deposit_event() = default;
  335. /// Enable a moderator can moderate a category and its sub categories.
  336. #[weight = 10_000_000] // TODO: adjust weight
  337. fn update_category_membership_of_moderator(origin, moderator_id: T::ModeratorId, category_id: T::CategoryId, new_value: bool) -> DispatchResult {
  338. // Ensure data migration is done
  339. Self::ensure_data_migration_done()?;
  340. clear_prefix(b"Forum ForumUserById");
  341. let account_id = ensure_signed(origin)?;
  342. Self::ensure_can_update_category_membership_of_moderator(account_id, &category_id, &moderator_id, new_value)?;
  343. //
  344. // == MUTATION SAFE ==
  345. //
  346. if new_value {
  347. <CategoryByModerator<T>>::insert(category_id, moderator_id, ());
  348. <CategoryById<T>>::mutate(category_id, |category| category.num_direct_moderators += 1);
  349. } else {
  350. <CategoryByModerator<T>>::remove(category_id, moderator_id);
  351. <CategoryById<T>>::mutate(category_id, |category| category.num_direct_moderators -= 1);
  352. }
  353. // Generate event
  354. Self::deposit_event(RawEvent::CategoryMembershipOfModeratorUpdated(moderator_id, category_id, new_value));
  355. Ok(())
  356. }
  357. /// Add a new category.
  358. #[weight = 10_000_000] // TODO: adjust weight
  359. fn create_category(origin, parent_category_id: Option<T::CategoryId>, title: Vec<u8>, description: Vec<u8>) -> DispatchResult {
  360. // Ensure data migration is done
  361. Self::ensure_data_migration_done()?;
  362. let account_id = ensure_signed(origin)?;
  363. Self::ensure_can_create_category(account_id, &parent_category_id)?;
  364. //
  365. // == MUTATION SAFE ==
  366. //
  367. // Get next category id
  368. let next_category_id = <NextCategoryId<T>>::get();
  369. // Create new category
  370. let new_category = Category {
  371. title_hash: T::calculate_hash(title.as_slice()),
  372. description_hash: T::calculate_hash(description.as_slice()),
  373. archived: false,
  374. num_direct_subcategories: 0,
  375. num_direct_threads: 0,
  376. num_direct_moderators: 0,
  377. parent_category_id,
  378. sticky_thread_ids: vec![],
  379. };
  380. // Insert category in map
  381. <CategoryById<T>>::mutate(next_category_id, |value| *value = new_category);
  382. // Update other next category id
  383. <NextCategoryId<T>>::mutate(|value| *value += One::one());
  384. // Update total category count
  385. <CategoryCounter<T>>::mutate(|value| *value += One::one());
  386. // If not root, increase parent's subcategories counter
  387. if let Some(tmp_parent_category_id) = parent_category_id {
  388. <CategoryById<T>>::mutate(tmp_parent_category_id, |c| {
  389. c.num_direct_subcategories += 1;
  390. });
  391. }
  392. // Generate event
  393. Self::deposit_event(RawEvent::CategoryCreated(next_category_id));
  394. Ok(())
  395. }
  396. /// Update category
  397. #[weight = 10_000_000] // TODO: adjust weight
  398. fn update_category_archival_status(origin, actor: PrivilegedActor<T>, category_id: T::CategoryId, new_archival_status: bool) -> DispatchResult {
  399. // Ensure data migration is done
  400. Self::ensure_data_migration_done()?;
  401. let account_id = ensure_signed(origin)?;
  402. // Ensure actor can update category
  403. let category = Self::ensure_can_moderate_category(account_id, &actor, &category_id)?;
  404. // No change, invalid transaction
  405. if new_archival_status == category.archived {
  406. return Err(Error::<T>::CategoryNotBeingUpdated.into())
  407. }
  408. //
  409. // == MUTATION SAFE ==
  410. //
  411. // Mutate category, and set possible new change parameters
  412. <CategoryById<T>>::mutate(category_id, |c| c.archived = new_archival_status);
  413. // Generate event
  414. Self::deposit_event(RawEvent::CategoryUpdated(category_id, new_archival_status));
  415. Ok(())
  416. }
  417. #[weight = 10_000_000] // TODO: adjust weight
  418. fn delete_category(origin, actor: PrivilegedActor<T>, category_id: T::CategoryId) -> DispatchResult {
  419. // Ensure data migration is done
  420. Self::ensure_data_migration_done()?;
  421. let account_id = ensure_signed(origin)?;
  422. let category = Self::ensure_can_delete_category(account_id, &actor, &category_id)?;
  423. //
  424. // == MUTATION SAFE ==
  425. //
  426. // Delete thread
  427. <CategoryById<T>>::remove(category_id);
  428. if let Some(parent_category_id) = category.parent_category_id {
  429. <CategoryById<T>>::mutate(parent_category_id, |tmp_category| tmp_category.num_direct_subcategories -= 1);
  430. }
  431. // Update total category count
  432. <CategoryCounter<T>>::mutate(|value| *value -= One::one());
  433. // Store the event
  434. Self::deposit_event(RawEvent::CategoryDeleted(category_id));
  435. Ok(())
  436. }
  437. /// Create new thread in category with poll
  438. #[weight = 10_000_000] // TODO: adjust weight
  439. fn create_thread(
  440. origin,
  441. forum_user_id: T::ForumUserId,
  442. category_id: T::CategoryId,
  443. title: Vec<u8>,
  444. text: Vec<u8>,
  445. poll: Option<Poll<T::Moment, T::Hash>>,
  446. ) -> DispatchResult {
  447. // Ensure data migration is done
  448. Self::ensure_data_migration_done()?;
  449. let account_id = ensure_signed(origin)?;
  450. Self::ensure_can_create_thread(account_id, &forum_user_id, &category_id)?;
  451. // Ensure poll is valid
  452. if let Some(ref data) = poll {
  453. // Check all poll alternatives
  454. Self::ensure_poll_alternatives_length_is_valid(&data.poll_alternatives)?;
  455. // Check poll self information
  456. Self::ensure_poll_is_valid(data)?;
  457. }
  458. //
  459. // == MUTATION SAFE ==
  460. //
  461. // Create and add new thread
  462. let new_thread_id = <NextThreadId<T>>::get();
  463. // Add inital post to thread
  464. let _ = Self::add_new_post(new_thread_id, &text, forum_user_id);
  465. // Build a new thread
  466. let new_thread = Thread {
  467. category_id,
  468. title_hash: T::calculate_hash(&title),
  469. author_id: forum_user_id,
  470. archived: false,
  471. poll,
  472. num_direct_posts: 1,
  473. };
  474. // Store thread
  475. <ThreadById<T>>::mutate(category_id, new_thread_id, |value| {
  476. *value = new_thread.clone()
  477. });
  478. // Update next thread id
  479. <NextThreadId<T>>::mutate(|n| *n += One::one());
  480. // Update category's thread counter
  481. <CategoryById<T>>::mutate(category_id, |c| c.num_direct_threads += 1);
  482. // Generate event
  483. Self::deposit_event(RawEvent::ThreadCreated(new_thread_id));
  484. Ok(())
  485. }
  486. #[weight = 10_000_000] // TODO: adjust weight
  487. fn edit_thread_title(origin, forum_user_id: T::ForumUserId, category_id: T::CategoryId, thread_id: T::ThreadId, new_title: Vec<u8>) -> DispatchResult {
  488. // Ensure data migration is done
  489. Self::ensure_data_migration_done()?;
  490. let account_id = ensure_signed(origin)?;
  491. let thread = Self::ensure_can_edit_thread_title(account_id, &category_id, &thread_id, &forum_user_id)?;
  492. //
  493. // == MUTATION SAFE ==
  494. //
  495. // Update thread title
  496. let title_hash = T::calculate_hash(&new_title);
  497. <ThreadById<T>>::mutate(thread.category_id, thread_id, |thread| thread.title_hash = title_hash);
  498. // Store the event
  499. Self::deposit_event(RawEvent::ThreadTitleUpdated(thread_id));
  500. Ok(())
  501. }
  502. /// Update category
  503. #[weight = 10_000_000] // TODO: adjust weight
  504. fn update_thread_archival_status(origin, actor: PrivilegedActor<T>, category_id: T::CategoryId, thread_id: T::ThreadId, new_archival_status: bool) -> DispatchResult {
  505. // Ensure data migration is done
  506. Self::ensure_data_migration_done()?;
  507. let account_id = ensure_signed(origin)?;
  508. // Ensure actor can update category
  509. let (_, thread) = Self::ensure_can_update_thread_archival_status(account_id, &actor, &category_id, &thread_id)?;
  510. // No change, invalid transaction
  511. if new_archival_status == thread.archived {
  512. return Err(Error::<T>::ThreadNotBeingUpdated.into());
  513. }
  514. //
  515. // == MUTATION SAFE ==
  516. //
  517. // Mutate thread, and set possible new change parameters
  518. <ThreadById<T>>::mutate(thread.category_id, thread_id, |c| c.archived = new_archival_status);
  519. // Generate event
  520. Self::deposit_event(RawEvent::ThreadUpdated(thread_id, new_archival_status));
  521. Ok(())
  522. }
  523. #[weight = 10_000_000] // TODO: adjust weight
  524. fn delete_thread(origin, actor: PrivilegedActor<T>, category_id: T::CategoryId, thread_id: T::ThreadId) -> DispatchResult {
  525. // Ensure data migration is done
  526. Self::ensure_data_migration_done()?;
  527. let account_id = ensure_signed(origin)?;
  528. let thread = Self::ensure_can_moderate_thread(account_id, &actor, &category_id, &thread_id)?;
  529. //
  530. // == MUTATION SAFE ==
  531. //
  532. // Delete thread
  533. Self::delete_thread_inner(thread.category_id, thread_id);
  534. // Store the event
  535. Self::deposit_event(RawEvent::ThreadDeleted(thread_id));
  536. Ok(())
  537. }
  538. #[weight = 10_000_000] // TODO: adjust weight
  539. fn move_thread_to_category(origin, actor: PrivilegedActor<T>, category_id: T::CategoryId, thread_id: T::ThreadId, new_category_id: T::CategoryId) -> DispatchResult {
  540. // Ensure data migration is done
  541. Self::ensure_data_migration_done()?;
  542. let account_id = ensure_signed(origin)?;
  543. // Make sure moderator move between selected categories
  544. let thread = Self::ensure_can_move_thread(account_id, &actor, &category_id, &thread_id, &new_category_id)?;
  545. //
  546. // == MUTATION SAFE ==
  547. //
  548. <ThreadById<T>>::remove(thread.category_id, thread_id);
  549. <ThreadById<T>>::insert(new_category_id, thread_id, thread.clone());
  550. <CategoryById<T>>::mutate(thread.category_id, |category| category.num_direct_threads -= 1);
  551. <CategoryById<T>>::mutate(new_category_id, |category| category.num_direct_threads += 1);
  552. // Store the event
  553. Self::deposit_event(RawEvent::ThreadMoved(thread_id, new_category_id));
  554. Ok(())
  555. }
  556. /// submit a poll
  557. #[weight = 10_000_000] // TODO: adjust weight
  558. fn vote_on_poll(origin, forum_user_id: T::ForumUserId, category_id: T::CategoryId, thread_id: T::ThreadId, index: u32) -> DispatchResult {
  559. // Ensure data migration is done
  560. Self::ensure_data_migration_done()?;
  561. let account_id = ensure_signed(origin)?;
  562. // get forum user id.
  563. Self::ensure_is_forum_user(account_id, &forum_user_id)?;
  564. // Get thread
  565. let (_, thread) = Self::ensure_thread_is_mutable(&category_id, &thread_id)?;
  566. let category_id = thread.category_id;
  567. // Make sure poll exist
  568. let poll = Self::ensure_vote_is_valid(thread, index)?;
  569. //
  570. // == MUTATION SAFE ==
  571. //
  572. // Store new poll alternative statistics
  573. let new_poll_alternatives: Vec<PollAlternative<T::Hash>> = poll.poll_alternatives
  574. .iter()
  575. .enumerate()
  576. .map(|(old_index, old_value)| if index as usize == old_index
  577. { PollAlternative {
  578. alternative_text_hash: old_value.alternative_text_hash,
  579. vote_count: old_value.vote_count + 1,
  580. }
  581. } else {
  582. old_value.clone()
  583. })
  584. .collect();
  585. Self::ensure_poll_alternatives_length_is_valid(&new_poll_alternatives)?;
  586. // Update thread with one object
  587. <ThreadById<T>>::mutate(category_id, thread_id, |value| {
  588. *value = Thread {
  589. poll: Some( Poll {
  590. poll_alternatives: new_poll_alternatives,
  591. ..poll
  592. }),
  593. ..(value.clone())
  594. }
  595. });
  596. // Store the event
  597. Self::deposit_event(RawEvent::VoteOnPoll(thread_id, index));
  598. Ok(())
  599. }
  600. #[weight = 10_000_000] // TODO: adjust weight
  601. fn moderate_thread(origin, actor: PrivilegedActor<T>, category_id: T::CategoryId, thread_id: T::ThreadId, rationale: Vec<u8>) -> DispatchResult {
  602. // Ensure data migration is done
  603. Self::ensure_data_migration_done()?;
  604. let account_id = ensure_signed(origin)?;
  605. // Ensure actor is allowed to moderate post
  606. let thread = Self::ensure_can_moderate_thread(account_id, &actor, &category_id, &thread_id)?;
  607. //
  608. // == MUTATION SAFE ==
  609. //
  610. // Delete thread
  611. Self::delete_thread_inner(thread.category_id, thread_id);
  612. // Generate event
  613. Self::deposit_event(RawEvent::ThreadModerated(thread_id, rationale));
  614. Ok(())
  615. }
  616. /// Add post
  617. #[weight = 10_000_000] // TODO: adjust weight
  618. fn add_post(origin, forum_user_id: T::ForumUserId, category_id: T::CategoryId, thread_id: T::ThreadId, text: Vec<u8>) -> DispatchResult {
  619. // Ensure data migration is done
  620. Self::ensure_data_migration_done()?;
  621. let account_id = ensure_signed(origin)?;
  622. // Make sure thread exists and is mutable
  623. let (_, thread) = Self::ensure_can_add_post(account_id, &forum_user_id, &category_id, &thread_id)?;
  624. // Ensure map limits are not reached
  625. Self::ensure_map_limits::<<<T>::MapLimits as StorageLimits>::MaxPostsInThread>(
  626. thread.num_direct_posts as u64,
  627. )?;
  628. //
  629. // == MUTATION SAFE ==
  630. //
  631. // Add new post
  632. let (post_id, _) = Self::add_new_post(thread_id, text.as_slice(), forum_user_id);
  633. // Update thread's post counter
  634. <ThreadById<T>>::mutate(thread.category_id, thread_id, |c| c.num_direct_posts += 1);
  635. // Generate event
  636. Self::deposit_event(RawEvent::PostAdded(post_id));
  637. Ok(())
  638. }
  639. /// like or unlike a post.
  640. #[weight = 10_000_000] // TODO: adjust weight
  641. fn react_post(origin, forum_user_id: T::ForumUserId, category_id: T::CategoryId, thread_id: T::ThreadId, post_id: T::PostId, react: T::PostReactionId) -> DispatchResult {
  642. // Ensure data migration is done
  643. Self::ensure_data_migration_done()?;
  644. let account_id = ensure_signed(origin)?;
  645. // Check that account is forum member
  646. Self::ensure_is_forum_user(account_id, &forum_user_id)?;
  647. // Make sure there exists a mutable post with post id `post_id`
  648. Self::ensure_post_is_mutable(&category_id, &thread_id, &post_id)?;
  649. //
  650. // == MUTATION SAFE ==
  651. //
  652. Self::deposit_event(RawEvent::PostReacted(forum_user_id, post_id, react));
  653. Ok(())
  654. }
  655. /// Edit post text
  656. #[weight = 10_000_000] // TODO: adjust weight
  657. fn edit_post_text(origin, forum_user_id: T::ForumUserId, category_id: T::CategoryId, thread_id: T::ThreadId, post_id: T::PostId, new_text: Vec<u8>) -> DispatchResult {
  658. // Ensure data migration is done
  659. Self::ensure_data_migration_done()?;
  660. let account_id = ensure_signed(origin)?;
  661. // Check that account is forum member
  662. Self::ensure_is_forum_user(account_id, &forum_user_id)?;
  663. // Make sure there exists a mutable post with post id `post_id`
  664. let post = Self::ensure_post_is_mutable(&category_id, &thread_id, &post_id)?;
  665. // Signer does not match creator of post with identifier postId
  666. ensure!(post.author_id == forum_user_id, Error::<T>::AccountDoesNotMatchPostAuthor);
  667. //
  668. // == MUTATION SAFE ==
  669. //
  670. // Update post text
  671. let text_hash = T::calculate_hash(&new_text);
  672. <PostById<T>>::mutate(post.thread_id, post_id, |p| p.text_hash = text_hash);
  673. // Generate event
  674. Self::deposit_event(RawEvent::PostTextUpdated(post_id));
  675. Ok(())
  676. }
  677. /// Moderate post
  678. #[weight = 10_000_000] // TODO: adjust weight
  679. fn moderate_post(origin, actor: PrivilegedActor<T>, category_id: T::CategoryId, thread_id: T::ThreadId, post_id: T::PostId, rationale: Vec<u8>) -> DispatchResult {
  680. // Ensure data migration is done
  681. Self::ensure_data_migration_done()?;
  682. let account_id = ensure_signed(origin)?;
  683. // Ensure actor is allowed to moderate post
  684. Self::ensure_can_moderate_post(account_id, &actor, &category_id, &thread_id, &post_id)?;
  685. //
  686. // == MUTATION SAFE ==
  687. //
  688. Self::delete_post_inner(category_id, thread_id, post_id);
  689. // Generate event
  690. Self::deposit_event(RawEvent::PostModerated(post_id, rationale));
  691. Ok(())
  692. }
  693. /// Set stickied threads for category
  694. #[weight = 10_000_000] // TODO: adjust weight
  695. fn set_stickied_threads(origin, actor: PrivilegedActor<T>, category_id: T::CategoryId, stickied_ids: Vec<T::ThreadId>) -> DispatchResult {
  696. // Ensure data migration is done
  697. Self::ensure_data_migration_done()?;
  698. let account_id = ensure_signed(origin)?;
  699. Self::ensure_can_set_stickied_threads(account_id, &actor, &category_id, &stickied_ids)?;
  700. //
  701. // == MUTATION SAFE ==
  702. //
  703. // Update category
  704. <CategoryById<T>>::mutate(category_id, |category| category.sticky_thread_ids = stickied_ids.clone());
  705. // Generate event
  706. Self::deposit_event(RawEvent::CategoryStickyThreadUpdate(category_id, stickied_ids));
  707. Ok(())
  708. }
  709. }
  710. }
  711. impl<T: Trait> Module<T> {
  712. pub fn add_new_post(
  713. thread_id: T::ThreadId,
  714. text: &[u8],
  715. author_id: T::ForumUserId,
  716. ) -> (T::PostId, Post<T::ForumUserId, T::ThreadId, T::Hash>) {
  717. // Make and add initial post
  718. let new_post_id = <NextPostId<T>>::get();
  719. // Build a post
  720. let new_post = Post {
  721. thread_id,
  722. text_hash: T::calculate_hash(text),
  723. author_id,
  724. };
  725. // Store post
  726. <PostById<T>>::mutate(thread_id, new_post_id, |value| *value = new_post.clone());
  727. // Update next post id
  728. <NextPostId<T>>::mutate(|n| *n += One::one());
  729. (new_post_id, new_post)
  730. }
  731. fn delete_thread_inner(category_id: T::CategoryId, thread_id: T::ThreadId) {
  732. // Delete thread
  733. <ThreadById<T>>::remove(category_id, thread_id);
  734. // Delete all thread's posts
  735. <PostById<T>>::remove_prefix(thread_id);
  736. // decrease category's thread counter
  737. <CategoryById<T>>::mutate(category_id, |category| category.num_direct_threads -= 1);
  738. }
  739. fn delete_post_inner(category_id: T::CategoryId, thread_id: T::ThreadId, post_id: T::PostId) {
  740. // Delete post
  741. <PostById<T>>::remove(thread_id, post_id);
  742. // Decrease thread's post counter
  743. <ThreadById<T>>::mutate(category_id, thread_id, |thread| {
  744. thread.num_direct_posts -= 1
  745. });
  746. }
  747. // Ensure poll is valid
  748. fn ensure_poll_is_valid(poll: &Poll<T::Moment, T::Hash>) -> Result<(), Error<T>> {
  749. // Poll end time must larger than now
  750. if poll.end_time < <pallet_timestamp::Module<T>>::now() {
  751. return Err(Error::<T>::PollTimeSetting);
  752. }
  753. Ok(())
  754. }
  755. // Ensure poll alternative size is valid
  756. fn ensure_poll_alternatives_length_is_valid(
  757. alternatives: &[PollAlternative<T::Hash>],
  758. ) -> Result<(), Error<T>> {
  759. Self::ensure_map_limits::<<<T>::MapLimits as StorageLimits>::MaxPollAlternativesNumber>(
  760. alternatives.len() as u64,
  761. )?;
  762. ensure!(
  763. alternatives.len() as u64 >= 2,
  764. Error::<T>::PollAlternativesTooShort
  765. );
  766. Ok(())
  767. }
  768. fn ensure_post_is_mutable(
  769. category_id: &T::CategoryId,
  770. thread_id: &T::ThreadId,
  771. post_id: &T::PostId,
  772. ) -> Result<Post<T::ForumUserId, T::ThreadId, T::Hash>, Error<T>> {
  773. // Make sure post exists
  774. let post = Self::ensure_post_exists(thread_id, post_id)?;
  775. // and make sure thread is mutable
  776. Self::ensure_thread_is_mutable(category_id, thread_id)?;
  777. Ok(post)
  778. }
  779. fn ensure_post_exists(
  780. thread_id: &T::ThreadId,
  781. post_id: &T::PostId,
  782. ) -> Result<Post<T::ForumUserId, T::ThreadId, T::Hash>, Error<T>> {
  783. if !<PostById<T>>::contains_key(thread_id, post_id) {
  784. return Err(Error::<T>::PostDoesNotExist);
  785. }
  786. Ok(<PostById<T>>::get(thread_id, post_id))
  787. }
  788. fn ensure_can_moderate_post(
  789. account_id: T::AccountId,
  790. actor: &PrivilegedActor<T>,
  791. category_id: &T::CategoryId,
  792. thread_id: &T::ThreadId,
  793. post_id: &T::PostId,
  794. ) -> Result<Post<T::ForumUserId, T::ThreadId, T::Hash>, Error<T>> {
  795. // Ensure the moderator can moderate the category
  796. Self::ensure_can_moderate_category(account_id, &actor, &category_id)?;
  797. // Make sure post exists and is mutable
  798. let post = Self::ensure_post_is_mutable(&category_id, &thread_id, &post_id)?;
  799. Ok(post)
  800. }
  801. fn ensure_thread_is_mutable(
  802. category_id: &T::CategoryId,
  803. thread_id: &T::ThreadId,
  804. ) -> Result<
  805. (
  806. Category<T::CategoryId, T::ThreadId, T::Hash>,
  807. Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>,
  808. ),
  809. Error<T>,
  810. > {
  811. // Make sure thread exists
  812. let thread = Self::ensure_thread_exists(category_id, thread_id)?;
  813. if thread.archived {
  814. return Err(Error::<T>::ThreadImmutable);
  815. }
  816. // and corresponding category is mutable
  817. let category = Self::ensure_category_is_mutable(category_id)?;
  818. Ok((category, thread))
  819. }
  820. fn ensure_can_update_thread_archival_status(
  821. account_id: T::AccountId,
  822. actor: &PrivilegedActor<T>,
  823. category_id: &T::CategoryId,
  824. thread_id: &T::ThreadId,
  825. ) -> Result<
  826. (
  827. Category<T::CategoryId, T::ThreadId, T::Hash>,
  828. Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>,
  829. ),
  830. Error<T>,
  831. > {
  832. // Check actor's role
  833. Self::ensure_actor_role(account_id, actor)?;
  834. let (category, thread) = Self::ensure_thread_is_mutable(category_id, thread_id)?;
  835. // Ensure actor can delete category
  836. Self::ensure_can_moderate_category_path(actor, category_id)?;
  837. Ok((category, thread))
  838. }
  839. fn ensure_thread_exists(
  840. category_id: &T::CategoryId,
  841. thread_id: &T::ThreadId,
  842. ) -> Result<Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>, Error<T>> {
  843. if !<ThreadById<T>>::contains_key(category_id, thread_id) {
  844. return Err(Error::<T>::ThreadDoesNotExist);
  845. }
  846. Ok(<ThreadById<T>>::get(category_id, thread_id))
  847. }
  848. fn ensure_can_edit_thread_title(
  849. account_id: T::AccountId,
  850. category_id: &T::CategoryId,
  851. thread_id: &T::ThreadId,
  852. forum_user_id: &T::ForumUserId,
  853. ) -> Result<Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>, Error<T>> {
  854. // Check that account is forum member
  855. Self::ensure_is_forum_user(account_id, &forum_user_id)?;
  856. // Ensure thread is mutable
  857. let (_, thread) = Self::ensure_thread_is_mutable(category_id, thread_id)?;
  858. // Ensure forum user is author of the thread
  859. Self::ensure_is_thread_author(&thread, forum_user_id)?;
  860. Ok(thread)
  861. }
  862. fn ensure_is_thread_author(
  863. thread: &Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>,
  864. forum_user_id: &T::ForumUserId,
  865. ) -> Result<(), Error<T>> {
  866. ensure!(
  867. thread.author_id == *forum_user_id,
  868. Error::<T>::AccountDoesNotMatchThreadAuthor
  869. );
  870. Ok(())
  871. }
  872. fn ensure_actor_role(
  873. account_id: T::AccountId,
  874. actor: &PrivilegedActor<T>,
  875. ) -> Result<(), Error<T>> {
  876. match actor {
  877. PrivilegedActor::Lead => {
  878. Self::ensure_is_forum_lead_account(&account_id)?;
  879. }
  880. PrivilegedActor::Moderator(moderator_id) => {
  881. Self::ensure_is_moderator_account(&account_id, &moderator_id)?;
  882. }
  883. };
  884. Ok(())
  885. }
  886. // Ensure forum user is lead - check via account
  887. fn ensure_is_forum_lead_account(account_id: &T::AccountId) -> Result<(), Error<T>> {
  888. let is_lead = T::is_lead(account_id);
  889. ensure!(is_lead, Error::<T>::OriginNotForumLead);
  890. Ok(())
  891. }
  892. /// Ensure forum user id registered and its account id matched
  893. fn ensure_is_forum_user(
  894. account_id: T::AccountId,
  895. forum_user_id: &T::ForumUserId,
  896. ) -> Result<(), Error<T>> {
  897. let is_member = T::is_forum_member(&account_id, forum_user_id);
  898. ensure!(is_member, Error::<T>::ForumUserIdNotMatchAccount);
  899. Ok(())
  900. }
  901. /// Ensure moderator id registered and its accound id matched - check via account
  902. fn ensure_is_moderator_account(
  903. account_id: &T::AccountId,
  904. moderator_id: &T::ModeratorId,
  905. ) -> Result<(), Error<T>> {
  906. let is_moderator = T::is_moderator(&account_id, moderator_id);
  907. ensure!(is_moderator, Error::<T>::ModeratorIdNotMatchAccount);
  908. Ok(())
  909. }
  910. // Ensure actor can manipulate thread.
  911. fn ensure_can_moderate_thread(
  912. account_id: T::AccountId,
  913. actor: &PrivilegedActor<T>,
  914. category_id: &T::CategoryId,
  915. thread_id: &T::ThreadId,
  916. ) -> Result<Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>, Error<T>> {
  917. // Check that account is forum member
  918. Self::ensure_can_moderate_category(account_id, actor, category_id)?;
  919. let thread = Self::ensure_thread_exists(category_id, thread_id)?;
  920. Ok(thread)
  921. }
  922. fn ensure_can_move_thread(
  923. account_id: T::AccountId,
  924. actor: &PrivilegedActor<T>,
  925. category_id: &T::CategoryId,
  926. thread_id: &T::ThreadId,
  927. new_category_id: &T::CategoryId,
  928. ) -> Result<Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>, Error<T>> {
  929. ensure!(
  930. category_id != new_category_id,
  931. Error::<T>::ThreadMoveInvalid,
  932. );
  933. let thread = Self::ensure_can_moderate_thread(account_id, actor, category_id, thread_id)
  934. .map_err(|_| Error::<T>::ModeratorModerateOriginCategory)?;
  935. Self::ensure_can_moderate_category_path(actor, new_category_id)
  936. .map_err(|_| Error::<T>::ModeratorModerateDestinationCategory)?;
  937. Ok(thread)
  938. }
  939. fn ensure_category_is_mutable(
  940. category_id: &T::CategoryId,
  941. ) -> Result<Category<T::CategoryId, T::ThreadId, T::Hash>, Error<T>> {
  942. let category_tree_path = Self::build_category_tree_path(&category_id);
  943. Self::ensure_can_mutate_in_path_leaf(&category_tree_path)?;
  944. Ok(category_tree_path[0].1.clone())
  945. }
  946. fn ensure_can_mutate_in_path_leaf(
  947. category_tree_path: &CategoryTreePathArg<T::CategoryId, T::ThreadId, T::Hash>,
  948. ) -> Result<(), Error<T>> {
  949. // Is parent category directly or indirectly deleted or archived category
  950. ensure!(
  951. !category_tree_path
  952. .iter()
  953. .any(|(_, c): &(_, Category<T::CategoryId, T::ThreadId, T::Hash>)| c.archived),
  954. Error::<T>::AncestorCategoryImmutable
  955. );
  956. Ok(())
  957. }
  958. fn ensure_can_add_subcategory_path_leaf(
  959. parent_category_id: &T::CategoryId,
  960. ) -> Result<(), Error<T>> {
  961. // Get the path from parent category to root
  962. let category_tree_path =
  963. Self::ensure_valid_category_and_build_category_tree_path(parent_category_id)?;
  964. let max_category_depth: u64 = T::MaxCategoryDepth::get();
  965. // Check if max depth reached
  966. if category_tree_path.len() as u64 >= max_category_depth {
  967. return Err(Error::<T>::MaxValidCategoryDepthExceeded);
  968. }
  969. Self::ensure_can_mutate_in_path_leaf(&category_tree_path)?;
  970. Ok(())
  971. }
  972. /// Build category tree path and validate them
  973. fn ensure_valid_category_and_build_category_tree_path(
  974. category_id: &T::CategoryId,
  975. ) -> Result<CategoryTreePath<T::CategoryId, T::ThreadId, T::Hash>, Error<T>> {
  976. ensure!(
  977. <CategoryById<T>>::contains_key(category_id),
  978. Error::<T>::CategoryDoesNotExist
  979. );
  980. // Get path from parent to root of category tree.
  981. let category_tree_path = Self::build_category_tree_path(&category_id);
  982. assert!(!category_tree_path.len() > 0);
  983. Ok(category_tree_path)
  984. }
  985. /// Builds path and populates in `path`.
  986. /// Requires that `category_id` is valid
  987. fn build_category_tree_path(
  988. category_id: &T::CategoryId,
  989. ) -> CategoryTreePath<T::CategoryId, T::ThreadId, T::Hash> {
  990. // Get path from parent to root of category tree.
  991. let mut category_tree_path = vec![];
  992. Self::_build_category_tree_path(category_id, &mut category_tree_path);
  993. category_tree_path
  994. }
  995. /// Builds path and populates in `path`.
  996. /// Requires that `category_id` is valid
  997. fn _build_category_tree_path(
  998. category_id: &T::CategoryId,
  999. path: &mut CategoryTreePath<T::CategoryId, T::ThreadId, T::Hash>,
  1000. ) {
  1001. // Grab category
  1002. let category = <CategoryById<T>>::get(*category_id);
  1003. // Add category to path container
  1004. path.push((*category_id, category.clone()));
  1005. // Make recursive call on parent if we are not at root
  1006. if let Some(parent_category_id) = category.parent_category_id {
  1007. assert!(<CategoryById<T>>::contains_key(parent_category_id));
  1008. Self::_build_category_tree_path(&parent_category_id, path);
  1009. }
  1010. }
  1011. fn ensure_can_delete_category(
  1012. account_id: T::AccountId,
  1013. actor: &PrivilegedActor<T>,
  1014. category_id: &T::CategoryId,
  1015. ) -> Result<Category<T::CategoryId, T::ThreadId, T::Hash>, Error<T>> {
  1016. // Check actor's role
  1017. Self::ensure_actor_role(account_id, actor)?;
  1018. // Ensure category exists
  1019. let category = Self::ensure_category_exists(category_id)?;
  1020. // Ensure category is empty
  1021. ensure!(
  1022. category.num_direct_threads == 0,
  1023. Error::<T>::CategoryNotEmptyThreads,
  1024. );
  1025. ensure!(
  1026. category.num_direct_subcategories == 0,
  1027. Error::<T>::CategoryNotEmptyCategories,
  1028. );
  1029. // check moderator's privilege
  1030. if let Some(parent_category_id) = category.parent_category_id {
  1031. Self::ensure_can_moderate_category_path(actor, &parent_category_id)
  1032. .map_err(|_| Error::<T>::ModeratorCantDeleteCategory)?;
  1033. return Ok(category);
  1034. }
  1035. // category is root - only lead can delete it
  1036. match actor {
  1037. PrivilegedActor::Lead => Ok(category),
  1038. PrivilegedActor::Moderator(_) => Err(Error::<T>::ModeratorCantDeleteCategory),
  1039. }
  1040. }
  1041. /// check if an account can moderate a category.
  1042. fn ensure_can_moderate_category(
  1043. account_id: T::AccountId,
  1044. actor: &PrivilegedActor<T>,
  1045. category_id: &T::CategoryId,
  1046. ) -> Result<Category<T::CategoryId, T::ThreadId, T::Hash>, Error<T>> {
  1047. // Ensure actor's role
  1048. Self::ensure_actor_role(account_id, actor)?;
  1049. Self::ensure_can_moderate_category_path(actor, category_id)
  1050. }
  1051. // check that moderator is allowed to manipulate category in hierarchy
  1052. fn ensure_can_moderate_category_path(
  1053. actor: &PrivilegedActor<T>,
  1054. category_id: &T::CategoryId,
  1055. ) -> Result<Category<T::CategoryId, T::ThreadId, T::Hash>, Error<T>> {
  1056. fn check_moderator<T: Trait>(
  1057. category_tree_path: &CategoryTreePathArg<T::CategoryId, T::ThreadId, T::Hash>,
  1058. moderator_id: &T::ModeratorId,
  1059. ) -> Result<(), Error<T>> {
  1060. for item in category_tree_path {
  1061. if <CategoryByModerator<T>>::contains_key(item.0, moderator_id) {
  1062. return Ok(());
  1063. }
  1064. }
  1065. Err(Error::<T>::ModeratorCantUpdateCategory)
  1066. }
  1067. // TODO: test if this line can possibly create panic! It calls assert internaly
  1068. // Get path from category to root + ensure category exists
  1069. let category_tree_path =
  1070. Self::ensure_valid_category_and_build_category_tree_path(category_id)?;
  1071. match actor {
  1072. PrivilegedActor::Lead => (),
  1073. PrivilegedActor::Moderator(moderator_id) => {
  1074. check_moderator::<T>(&category_tree_path, moderator_id)?
  1075. }
  1076. };
  1077. let category = category_tree_path[0].1.clone();
  1078. Ok(category)
  1079. }
  1080. fn ensure_can_update_category_membership_of_moderator(
  1081. account_id: T::AccountId,
  1082. category_id: &T::CategoryId,
  1083. moderator_id: &T::ModeratorId,
  1084. new_value: bool,
  1085. ) -> Result<(), Error<T>> {
  1086. // Not signed by forum LEAD
  1087. Self::ensure_is_forum_lead_account(&account_id)?;
  1088. // ensure category exists.
  1089. let category = Self::ensure_category_exists(category_id)?;
  1090. if new_value {
  1091. Self::ensure_map_limits::<<<T>::MapLimits as StorageLimits>::MaxModeratorsForCategory>(
  1092. category.num_direct_moderators as u64,
  1093. )?;
  1094. } else {
  1095. ensure!(
  1096. <CategoryByModerator<T>>::contains_key(category_id, moderator_id),
  1097. Error::<T>::CategoryModeratorDoesNotExist
  1098. );
  1099. }
  1100. Ok(())
  1101. }
  1102. fn ensure_category_exists(
  1103. category_id: &T::CategoryId,
  1104. ) -> Result<Category<T::CategoryId, T::ThreadId, T::Hash>, Error<T>> {
  1105. ensure!(
  1106. <CategoryById<T>>::contains_key(&category_id),
  1107. Error::<T>::CategoryDoesNotExist
  1108. );
  1109. Ok(<CategoryById<T>>::get(category_id))
  1110. }
  1111. fn ensure_can_create_category(
  1112. account_id: T::AccountId,
  1113. parent_category_id: &Option<T::CategoryId>,
  1114. ) -> Result<Option<Category<T::CategoryId, T::ThreadId, T::Hash>>, Error<T>> {
  1115. // Not signed by forum LEAD
  1116. Self::ensure_is_forum_lead_account(&account_id)?;
  1117. Self::ensure_map_limits::<<<T>::MapLimits as StorageLimits>::MaxCategories>(
  1118. <CategoryCounter<T>>::get().into() as u64,
  1119. )?;
  1120. // If not root, then check that we can create in parent category
  1121. if let Some(tmp_parent_category_id) = parent_category_id {
  1122. // Can we mutate in this category?
  1123. Self::ensure_can_add_subcategory_path_leaf(&tmp_parent_category_id)?;
  1124. let parent_category = <CategoryById<T>>::get(tmp_parent_category_id);
  1125. Self::ensure_map_limits::<<<T>::MapLimits as StorageLimits>::MaxSubcategories>(
  1126. parent_category.num_direct_subcategories as u64,
  1127. )?;
  1128. return Ok(Some(parent_category));
  1129. }
  1130. Ok(None)
  1131. }
  1132. fn ensure_can_create_thread(
  1133. account_id: T::AccountId,
  1134. forum_user_id: &T::ForumUserId,
  1135. category_id: &T::CategoryId,
  1136. ) -> Result<Category<T::CategoryId, T::ThreadId, T::Hash>, Error<T>> {
  1137. // Check that account is forum member
  1138. Self::ensure_is_forum_user(account_id, &forum_user_id)?;
  1139. let category = Self::ensure_category_is_mutable(category_id)?;
  1140. Self::ensure_map_limits::<<<T>::MapLimits as StorageLimits>::MaxThreadsInCategory>(
  1141. category.num_direct_threads as u64,
  1142. )?;
  1143. Ok(category)
  1144. }
  1145. fn ensure_can_add_post(
  1146. account_id: T::AccountId,
  1147. forum_user_id: &T::ForumUserId,
  1148. category_id: &T::CategoryId,
  1149. thread_id: &T::ThreadId,
  1150. ) -> Result<
  1151. (
  1152. Category<T::CategoryId, T::ThreadId, T::Hash>,
  1153. Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>,
  1154. ),
  1155. Error<T>,
  1156. > {
  1157. // Check that account is forum member
  1158. Self::ensure_is_forum_user(account_id, &forum_user_id)?;
  1159. let (category, thread) = Self::ensure_thread_is_mutable(category_id, thread_id)?;
  1160. Ok((category, thread))
  1161. }
  1162. fn ensure_can_set_stickied_threads(
  1163. account_id: T::AccountId,
  1164. actor: &PrivilegedActor<T>,
  1165. category_id: &T::CategoryId,
  1166. stickied_ids: &[T::ThreadId],
  1167. ) -> Result<Category<T::CategoryId, T::ThreadId, T::Hash>, Error<T>> {
  1168. // Ensure actor can moderate the category
  1169. let category = Self::ensure_can_moderate_category(account_id, &actor, &category_id)?;
  1170. // Ensure all thread id valid and is under the category
  1171. for item in stickied_ids {
  1172. Self::ensure_thread_exists(&category_id, item)?;
  1173. }
  1174. Ok(category)
  1175. }
  1176. /// Check the vote is valid
  1177. fn ensure_vote_is_valid(
  1178. thread: Thread<T::ForumUserId, T::CategoryId, T::Moment, T::Hash>,
  1179. index: u32,
  1180. ) -> Result<Poll<T::Moment, T::Hash>, Error<T>> {
  1181. // Ensure poll exists
  1182. let poll = thread.poll.ok_or(Error::<T>::PollNotExist)?;
  1183. // Poll not expired
  1184. if poll.end_time < <pallet_timestamp::Module<T>>::now() {
  1185. Err(Error::<T>::PollCommitExpired)
  1186. } else {
  1187. let alternative_length = poll.poll_alternatives.len();
  1188. // The selected alternative index is valid
  1189. if index as usize >= alternative_length {
  1190. Err(Error::<T>::PollData)
  1191. } else {
  1192. Ok(poll)
  1193. }
  1194. }
  1195. }
  1196. // supposed to be called before mutations - checks if next entity can be added
  1197. fn ensure_map_limits<U: Get<u64>>(current_amount: u64) -> Result<(), Error<T>> {
  1198. fn check_limit<T: Trait>(amount: u64, limit: u64) -> Result<(), Error<T>> {
  1199. if amount >= limit {
  1200. return Err(Error::<T>::MapSizeLimit);
  1201. }
  1202. Ok(())
  1203. }
  1204. check_limit(current_amount, U::get())
  1205. }
  1206. /// Ensure data migration is done
  1207. fn ensure_data_migration_done() -> Result<(), Error<T>> {
  1208. if DataMigrationDone::get() {
  1209. Ok(())
  1210. } else {
  1211. Err(Error::<T>::DataMigrationNotDone)
  1212. }
  1213. }
  1214. }