content_directory.rs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. #![cfg_attr(not(feature = "std"), no_std)]
  2. use crate::storage::data_object_type_registry::Trait as DOTRTrait;
  3. use crate::traits::Members;
  4. use parity_codec::Codec;
  5. use parity_codec_derive::{Decode, Encode};
  6. use rstd::prelude::*;
  7. use runtime_primitives::traits::{As, MaybeDebug, MaybeSerializeDebug, Member, SimpleArithmetic};
  8. use srml_support::{
  9. decl_event, decl_module, decl_storage, ensure, Parameter, StorageMap, StorageValue,
  10. };
  11. use system::{self, ensure_signed};
  12. pub trait Trait: timestamp::Trait + system::Trait + DOTRTrait + MaybeDebug {
  13. type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
  14. type MetadataId: Parameter
  15. + Member
  16. + SimpleArithmetic
  17. + Codec
  18. + Default
  19. + Copy
  20. + As<usize>
  21. + As<u64>
  22. + MaybeSerializeDebug
  23. + PartialEq;
  24. // Schema ID should be defined in a different Trait in future
  25. type SchemaId: Parameter
  26. + Member
  27. + SimpleArithmetic
  28. + Codec
  29. + Default
  30. + Copy
  31. + As<usize>
  32. + As<u64>
  33. + MaybeSerializeDebug
  34. + PartialEq;
  35. type Members: Members<Self>;
  36. }
  37. static MSG_CREATOR_MUST_BE_MEMBER: &str = "Only active members may create content!";
  38. // TODO for future: static MSG_INVALID_SCHEMA_ID: &str = "The metadata schema is not known or invalid!";
  39. static MSG_METADATA_NOT_FOUND: &str = "Metadata with the given ID cannot be found!";
  40. static MSG_ONLY_OWNER_MAY_PUBLISH: &str = "Only metadata owners may publish their metadata!";
  41. const DEFAULT_FIRST_METADATA_ID: u64 = 1;
  42. #[derive(Clone, Encode, Decode, PartialEq)]
  43. #[cfg_attr(feature = "std", derive(Debug))]
  44. pub enum MetadataState {
  45. Draft,
  46. Published,
  47. }
  48. impl Default for MetadataState {
  49. fn default() -> Self {
  50. MetadataState::Draft
  51. }
  52. }
  53. // Content metadata contains two fields: one is the numeric ID of the metadata
  54. // scheme to use, which is currently related to off-chain definitions ONLY.
  55. // The other is the serialized metadata.
  56. #[derive(Clone, Encode, Decode, PartialEq)]
  57. #[cfg_attr(feature = "std", derive(Debug))]
  58. pub struct ContentMetadata<T: Trait> {
  59. pub schema: T::SchemaId,
  60. pub metadata: Vec<u8>,
  61. pub origin: T::AccountId,
  62. pub state: MetadataState,
  63. }
  64. decl_event! {
  65. pub enum Event<T> where
  66. <T as Trait>::MetadataId
  67. {
  68. MetadataDraftCreated(MetadataId),
  69. MetadataPublished(MetadataId),
  70. }
  71. }
  72. decl_storage! {
  73. trait Store for Module<T: Trait> as ContentDirectory {
  74. // Start at this value
  75. pub FirstMetadataId get(first_metadata_id) config(first_metadata_id): T::MetadataId = T::MetadataId::sa(DEFAULT_FIRST_METADATA_ID);
  76. // Increment
  77. pub NextMetadataId get(next_metadata_id) build(|config: &GenesisConfig<T>| config.first_metadata_id): T::MetadataId = T::MetadataId::sa(DEFAULT_FIRST_METADATA_ID);
  78. // Mapping of Data object types
  79. pub MetadataMap get(metadata): map T::MetadataId => Option<ContentMetadata<T>>;
  80. }
  81. }
  82. decl_module! {
  83. pub struct Module<T: Trait> for enum Call where origin: T::Origin {
  84. fn deposit_event<T>() = default;
  85. pub fn add_metadata(origin, schema: T::SchemaId, metadata: Vec<u8>)
  86. {
  87. // Origin has to be a member
  88. let who = ensure_signed(origin)?;
  89. ensure!(T::Members::is_active_member(&who), MSG_CREATOR_MUST_BE_MEMBER);
  90. // TODO in future, we want the schema IDs to correspond to a schema
  91. // registry, and validate the data. For now, we just allow the
  92. // following schema IDs:
  93. // 1 - Video (schema TBD, i.e. handled in UI only)
  94. // 2 - Podcast (schema TBD, i.e. handled in UI only)
  95. // Pseudocode
  96. // if schema not in (1, 2) {
  97. // return Err(MSG_INVALID_SCHEMA_ID);
  98. // }
  99. //
  100. // See https://github.com/Joystream/substrate-runtime-joystream/issues/20
  101. // New and data
  102. let new_id = Self::next_metadata_id();
  103. let data: ContentMetadata<T> = ContentMetadata {
  104. origin: who.clone(),
  105. schema: schema,
  106. metadata: metadata.clone(),
  107. state: MetadataState::Draft,
  108. };
  109. // Store
  110. <MetadataMap<T>>::insert(new_id, data);
  111. <NextMetadataId<T>>::mutate(|n| { *n += T::MetadataId::sa(1); });
  112. // Publish event
  113. Self::deposit_event(RawEvent::MetadataDraftCreated(new_id));
  114. }
  115. pub fn publish_metadata(origin, id: T::MetadataId)
  116. {
  117. // Ensure signed account
  118. let who = ensure_signed(origin)?;
  119. // Try t find metadata
  120. let mut data = Self::metadata(id).ok_or(MSG_METADATA_NOT_FOUND)?;
  121. // Ensure it's the metadata creator who publishes
  122. ensure!(data.origin == who, MSG_ONLY_OWNER_MAY_PUBLISH);
  123. // Modify
  124. data.state = MetadataState::Published;
  125. // Store
  126. <MetadataMap<T>>::insert(id, data);
  127. // Publish event
  128. Self::deposit_event(RawEvent::MetadataPublished(id));
  129. }
  130. }
  131. }
  132. #[cfg(test)]
  133. mod tests {
  134. use crate::storage::mock::*;
  135. #[test]
  136. fn add_metadata() {
  137. with_default_mock_builder(|| {
  138. let res =
  139. TestContentDirectory::add_metadata(Origin::signed(1), 1, "foo".as_bytes().to_vec());
  140. assert!(res.is_ok());
  141. });
  142. }
  143. #[test]
  144. fn publish_metadata() {
  145. with_default_mock_builder(|| {
  146. let res =
  147. TestContentDirectory::add_metadata(Origin::signed(1), 1, "foo".as_bytes().to_vec());
  148. assert!(res.is_ok());
  149. // Grab ID from event
  150. let metadata_id = match System::events().last().unwrap().event {
  151. MetaEvent::content_directory(
  152. content_directory::RawEvent::MetadataDraftCreated(metadata_id),
  153. ) => metadata_id,
  154. _ => 0xdeadbeefu64, // invalid value, unlikely to match
  155. };
  156. assert_ne!(metadata_id, 0xdeadbeefu64);
  157. // Publishing a bad ID should fail
  158. let res = TestContentDirectory::publish_metadata(Origin::signed(1), metadata_id + 1);
  159. assert!(res.is_err());
  160. // Publishing should not work for non-owners
  161. let res = TestContentDirectory::publish_metadata(Origin::signed(2), metadata_id);
  162. assert!(res.is_err());
  163. // For the owner, it should work however
  164. let res = TestContentDirectory::publish_metadata(Origin::signed(1), metadata_id);
  165. assert!(res.is_ok());
  166. });
  167. }
  168. }