summaryrefslogtreecommitdiffstats
path: root/src/types/payload.rs
blob: 47f28a617039cc918fa98ce2e37565cea298409d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
use std::collections::BTreeMap;

use anyhow::Error;

use crate::types::util::IPFSHash;
use crate::types::util::IPNSHash;
use crate::types::util::MimeType;
use crate::types::util::Timestamp;
use crate::model::Model;


/// The Payload type represents the Payload of a Content object
///
/// The Payload type contains several variants, as an update (new block) may be either just a
/// metadata update (like a merge) or something more meaningful.
///
/// For example, the payload might be a `Payload::Post`, Alice has posted new kitten pictures!
/// Or a `Payload::ProfileUpdate` which contains information about Alice herself
///
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "payload_type")]
pub enum Payload {

    /// A variant that represents no payload
    ///
    /// This normally happens when merging two chains.
    None,

    /// A post
    ///
    /// A post can be anything which contains data. The data itself is put behind an IPFS hash, the
    /// Payload::Post only contains meta-information like payload size, content format (MIME) and
    /// optionally a list of IPFS hashes which are related to the post itself.
    Post {
        /// Format of the actual content
        #[serde(rename = "content-format")]
        content_format: MimeType,

        /// IPFS hash pointing to the actual content
        #[serde(rename = "content")]
        content: IPFSHash,

        /// If this post is a reply to another post, this field can be used to point to the
        /// replied-to post.
        #[serde(rename = "reply-to")]
        #[serde(default)]
        reply_to: Option<IPFSHash>,

        //
        //
        // Metadata for the post which should be visible to others
        //
        //

        /// A flag whether comments to this post will be propagated
        ///
        /// From a technical POV, comments can be done anyways, but the client which published the
        /// comment has to "accept" comments (via `PostComments`) to make them visible to others.
        /// This flag indicates whether the client will do that (either manually or automatically,
        /// which is not indicated here).
        ///
        /// # Available values
        ///
        /// A value of `Some(false)` means that comments will not be propagated, so others will not
        /// see them. A UI may not show "add comment"-buttons if this is set to `Some(false)`.
        ///
        /// A value of `Some(true)` means that comments will eventually be propagated. This means
        /// that the author might accept them by hand or tell their client to automatically accept
        /// all comments. This distinction is client-side implementation specific.
        ///
        /// A value of `None` indicates no setting here, which means that the client might or might
        /// not propagate any comments.
        #[serde(rename = "comments-will-be-propagated")]
        #[serde(default)]
        comments_will_be_propagated: Option<bool>,

        /// A value which describes until what date/time comments will be propagated
        ///
        /// This is a hint for other users whether comments will be propagated or not.
        /// A UI might not show a "Reply" button after that date.
        #[serde(rename = "comments-propagated-until")]
        #[serde(default)]
        comments_propagated_until: Option<Timestamp>,
    },

    /// Comments for a post
    ///
    /// Propagating answers to a post must be done by the author of the post itself.
    /// This variant is for publishing a message "These are the comments on my post <hash>".
    ///
    /// Always all comments should be published, not just the new ones!
    AttachedPostComments {
        /// The Hash of the Block for the Post which the comments are for
        #[serde(rename = "comments-for")]
        comments_for: IPFSHash,

        /// Hashes of direct answers to the post pointed to by "comments_for"
        /// This list always represents a full list of all answers. As comments are added, old
        /// versions of this object should be ignored by clients if newer variants for the same `comments_for`-object are published.
        #[serde(rename = "refs")]
        #[serde(default)]
        refs: Vec<IPFSHash>,
    },

    /// A variant describing a profile
    ///
    /// A profile contains the whole set of data which is considered "current" for the
    /// profile. Older versions of this shall then be ignored by clients.
    Profile {

        /// The self-assigned names of a user.
        #[serde(rename = "names")]
        names: Vec<String>,

        /// An optional user profile picture
        ///
        /// The picture itself is located behind a IPFS hash. If the hash does not resolve to a
        /// picture, clients should ignore it.
        ///
        /// A profile may only contain _one_ profile picture in the current version of the
        /// protocol.
        #[serde(rename = "picture")]
        #[serde(default)]
        picture: Option<IPFSHash>,

        /// A "more" field where arbitrary data can be stored. Like "Biography", "Hobbies",
        /// "Political opinion" or even pictures, ...
        ///
        /// The stored data can be of any type.
        #[serde(rename = "user-defined")]
        #[serde(default)]
        more: BTreeMap<String, Userdata>,
    },
}

impl Payload {
    /// Helper function
    pub fn is_none(&self) -> bool {
        match self {
            &Payload::None => true,
            _ => false,
        }
    }

    /// Helper function
    pub fn is_post(&self) -> bool {
        match self {
            &Payload::Post {..} => true,
            _ => false,
        }
    }

    /// Helper function
    pub fn is_attached_post_comments(&self) -> bool {
        match self {
            &Payload::AttachedPostComments {..} => true,
            _ => false,
        }
    }

    /// Helper function
    pub fn is_profile(&self) -> bool {
        match self {
            &Payload::Profile {..} => true,
            _ => false,
        }
    }

    pub async fn load(self, r: &Model) -> Result<LoadedPayload, Error> {
        match self {
            Payload::None => Ok(LoadedPayload::None),
            Payload::Post {
                content_format,
                content,
                reply_to,
                comments_will_be_propagated,
                comments_propagated_until
            } => {
                Ok({
                    LoadedPayload::Post {
                        content_format,
                        content: r.get_raw_bytes(content).await?,
                        reply_to,
                        comments_will_be_propagated,
                        comments_propagated_until
                    }
                })
            },

            Payload::AttachedPostComments { comments_for, refs } => {
                Ok({
                    LoadedPayload::AttachedPostComments {
                        comments_for,
                        refs
                    }
                })
            },

            Payload::Profile { names, picture, more } => {
                let picture = if let Some(p) = picture {
                    Some(r.get_raw_bytes(p).await?)
                } else {
                    None
                };

                Ok({
                    LoadedPayload::Profile {
                        names,
                        picture,
                        more
                    }
                })
            }
        }
    }
}

#[derive(Serialize, Deserialize, Debug)]
pub struct Userdata {
    #[serde(rename = "mimetype")]
    mimetype: MimeType,

    #[serde(rename = "data")]
    data: IPFSHash,
}

#[derive(Debug)]
pub enum LoadedPayload {
    None,

    Post {
        content_format: MimeType,
        content: Vec<u8>,
        reply_to: Option<IPFSHash>,
        comments_will_be_propagated: Option<bool>,
        comments_propagated_until: Option<Timestamp>,
    },

    AttachedPostComments {
        comments_for: IPFSHash,
        refs: Vec<IPFSHash>,
    },

    Profile {
        names: Vec<String>,
        picture: Option<Vec<u8>>,
        more: BTreeMap<String, Userdata>,
    },
}

impl LoadedPayload {
    pub fn is_none(&self) -> bool {
        match self {
            &LoadedPayload::None => true,
            _ => false,
        }
    }

    pub fn is_post(&self) -> bool {
        match self {
            &LoadedPayload::Post { .. }=> true,
            _ => false,
        }
    }

    pub fn is_attached_post_comments(&self) -> bool {
        match self {
            &LoadedPayload::AttachedPostComments { .. } => true,
            _ => false,
        }
    }

    pub fn is_profile(&self) -> bool {
        match self {
            &LoadedPayload::Profile { .. } => true,
            _ => false,
        }
    }
}