//! Structure for updating posts in-place use super::*; use std::{ fmt, hash::{ Hash,Hasher, }, }; use chrono::prelude::*; /// Represents all valid timestamps for a post. /// /// Usually in UTC, pls keep it in Utc... #[derive(Debug, Clone, PartialEq, Eq)] #[derive(Serialize, Deserialize)] pub struct PostTimestamp { pub opened: DateTime, pub closed: Option>, pub last_edit: DateTime, } impl PostTimestamp { /// Create a new timestamp for a new post pub fn new() -> Self { let now = Utc::now(); Self { opened: now.clone(), closed: None, last_edit: now, } } } impl Hash for PostTimestamp { fn hash(&self, state: &mut H) { self.opened.hash(state); self.closed.hash(state); self.last_edit.hash(state); } } impl fmt::Display for PostTimestamp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, " Opened - {}", self.opened)?; writeln!(f, " Edited - {}", self.last_edit)?; write!(f, " Closed - ")?; if let Some(closed) = &self.closed { writeln!(f, "{}", closed)?; } else { writeln!(f, "Stil open")?; } Ok(()) } } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] /// A closed and finished post. The inverse of `Imouto`. pub struct Static { id: identity::PostID, user: identity::User, title: Option, karada: String, timestamp: PostTimestamp, /// Hash of the rest of the post data. . . . hash: crypto::sha256::Sha256Hash, } impl Static { /// Compute the hash for this instance pub fn into_hashed(mut self) -> Self { self.hash = Default::default(); let bytes = serde_cbor::to_vec(&self).expect("Failed to serialise static post to CBOR"); Self { hash: crypto::sha256::compute_slice(&bytes), ..self } } /// Validate the internal hash of this instance pub fn validate_hash(&self) -> bool { self.clone().into_hashed().hash == self.hash } } use suspend::{ Suspendable, SuspendStream, }; #[async_trait] impl Suspendable for Static { async fn suspend(self, into: &mut S) -> Result<(), suspend::Error> { let mut output = suspend::Object::new(); output.insert("post-static", self); into.set_object(output).await } async fn load(from: &mut S) -> Result { let mut input = from.get_object().await?.ok_or(suspend::Error::BadObject)?; input.try_get("post-static").ok_or(suspend::Error::BadObject) } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn static_post_ser() { let mut output = suspend::MemorySuspendStream::new(); let post1 = Static { id: identity::PostID::new(), user: Default::default(), title: None, karada: "hello world".to_owned(), timestamp: PostTimestamp::new(), hash: Default::default(), }.into_hashed(); let post1c = post1.clone(); assert!(post1.validate_hash()); post1.suspend(&mut output).await.expect("Suspend failed"); let buffer = output.buffer().clone(); let post2 = Static::load(&mut output).await.expect("Suspend load failed"); assert_eq!(post1c, post2); assert!(post1c.validate_hash()); assert!(post2.validate_hash()); let buffer2 = suspend::oneshot(post2.clone()).await.expect("Oneshot failed"); assert_eq!(&buffer2[..], &buffer[..]); let post3: Static = suspend::single(buffer2).await.expect("Single failed"); assert!(post3.validate_hash()); assert_eq!(post3, post2); } }