You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
yuurei/src/post.rs

155 lines
4.1 KiB

use super::*;
use cryptohelpers::sha256::Sha256Hash;
use hard_format::formats::{
PEMFormattedString,
PEMFormattedStr,
MaxLenString,
self,
};
use tripcode::Tripcode;
id_type!(PostID; "A unique post ID");
/// String type that limits its bytes to the ID string max limit.
pub type IDMaxString = MaxLenString<{defaults::POST_ID_MAX_LEN}>;
/// The timestamp type used in posts
pub type PostTimestamp = chrono::DateTime<defaults::Timezone>;
/// A size limited PEM formatting specifier
type PostBodyFormat = formats::BothFormat<formats::MaxLenFormat<{defaults::POST_BODY_MAX_SIZE}>, formats::PEMFormat>;
/// A size limited PEM string
pub type PostBodyString = hard_format::FormattedString<PostBodyFormat>;
/// A size limited PEM string
pub type PostBodyStr = hard_format::FormattedStr<PostBodyFormat>;
/// Identifiers for a post
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Ident
{
name: Option<IDMaxString>,
tripcode: Option<Tripcode>,
email: Option<IDMaxString>,
}
/// A single completed post
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Post
{
/// Unique ID for each post
id: PostID,
/// Identifiers for this post
ident: Ident,
/// The client-side encrypted body string
body: PostBodyString,
/// Signature of the body (optional).
signature: Option<PEMFormattedString>,
/// Hash of the body
hash: Sha256Hash,
/// When the post was created
created: PostTimestamp,
/// When the post was last edited.
///
/// # Notes
/// Each new edit is pushed to the end of the vec, creation does not count as an edit.
edited: Vec<PostTimestamp>,
/// Optional dynamic expiry duration.
expires_in: Option<tokio::time::Duration>,
}
impl Post
{
/// Time since this post was created as a Chrono `Duration`.
pub fn time_since_creation(&self) -> chrono::Duration
{
defaults::Timezone::now() - self.created
}
/// Has this post expired?
///
/// Expired posts should be removed
pub fn expired(&self) -> bool
{
if let Ok(dur) = &self.time_since_creation().to_std()
{
dur >= self.expires_in.as_ref().unwrap_or(&defaults::POST_EXPIRE)
} else {
// Conversion failed. Expire the post
true
}
}
/// The user-set name for this post if there is one.
#[inline] pub fn own_name(&self) -> Option<&str>
{
self.ident.name.as_ref().map(|x| x.as_str())
}
/// The name for this post.
///
/// If no name is set, returns the default anon name.
pub fn name(&self) -> &str
{
self.own_name().unwrap_or(defaults::ANON_NAME)
}
/// The email set for this post, if there is one.
pub fn email(&self) -> Option<&str>
{
self.ident.email.as_ref().map(|x| x.as_str())
}
/// Get the tripcode of this post, if there is one.
pub fn tripcode(&self) -> Option<&Tripcode>
{
self.ident.tripcode.as_ref()
}
/// The body of this post
pub fn body(&self) -> &PostBodyStr
{
self.body.as_ref()
}
/// The PEM formatted signature of this post, if there is one.
pub fn signature(&self) -> Option<&PEMFormattedStr>
{
self.signature.as_ref().map(|x| x.as_ref())
}
}
#[cfg(test)]
mod tests
{
#[test]
fn post_serialise()
{
use std::convert::TryInto;
let post = super::Post {
id: super::PostID::id_new(),
ident: super::Ident {
name: Some("Some name".to_owned().try_into().unwrap()),
email: None,
tripcode: Some(super::Tripcode::generate("uhh hello").unwrap()),
},
body: unsafe { super::PostBodyStr::new_unchecked("test").to_owned() }, //temporary
signature: None,
hash: Default::default(),
created: crate::defaults::Timezone::now(),
edited: Default::default(),
expires_in: None,
};
let post_json = serde_json::to_vec(&post).expect("Serialise");
println!("Post json: {}", std::str::from_utf8(&post_json[..]).unwrap());
let post2: super::Post = serde_json::from_slice(&post_json[..]).expect("Deserialise");
assert_eq!(post, post2);
println!("Post was: {:?}", post);
}
}