use chrono::{DateTime, Utc}; use derive_builder::Builder; use friendly_id; use shrinkwraprs::Shrinkwrap; use sqlx::{self, FromRow}; // Kasten is German for "Box," and is used both because this is // supposed to be a Zettlekasten, and because "Box" is a heavily // reserved word in Rust. So, for that matter, are "crate" and // "cargo," "cell," and so forth. If I'd wanted to go the Full // Noguchi, I guess I could have used "envelope." #[derive(Shrinkwrap, Clone)] pub(crate) struct NoteId(pub String); #[derive(Shrinkwrap, Clone)] pub(crate) struct ParentId(pub String); /// The different kinds of objects we support. #[derive(Clone, Debug, PartialEq, Eq)] pub enum NoteKind { Kasten, Note, Resource, } impl From for NoteKind { fn from(kind: String) -> Self { match &kind[..] { "box" => NoteKind::Kasten, "note" => NoteKind::Note, "resource" => NoteKind::Resource, _ => panic!("Illegal value in database: {}", kind), } } } impl From for String { fn from(kind: NoteKind) -> Self { match kind { NoteKind::Kasten => "box", NoteKind::Note => "note", NoteKind::Resource => "resource", } .to_string() } } impl NoteKind { pub fn to_string(&self) -> String { String::from(self.clone()) } } /// The different kinds of relationships we support. I do not yet /// know how to ensure that there is a maximum of one (a -> /// b)::Direct, and that for any (a -> b) there is no (b <- a), that /// is, nor, for that matter, do I know how to prevent cycles. #[derive(Clone, Debug, PartialEq, Eq)] pub enum RelationshipKind { Direct, Reference, Embed, Kasten, Unacked, } impl From for RelationshipKind { fn from(kind: String) -> Self { match &kind[..] { "direct" => RelationshipKind::Direct, "reference" => RelationshipKind::Reference, "embed" => RelationshipKind::Embed, "kasten" => RelationshipKind::Kasten, "unacked" => RelationshipKind::Unacked, _ => panic!("Illegal value in database: {}", kind), } } } impl From for String { fn from(kind: RelationshipKind) -> Self { match kind { RelationshipKind::Direct => "direct", RelationshipKind::Reference => "reference", RelationshipKind::Embed => "embed", RelationshipKind::Kasten => "kasten", RelationshipKind::Unacked => "unacked", } .to_string() } } impl RelationshipKind { pub fn to_string(&self) -> String { String::from(self.clone()) } } // A Note is the base construct of our system. It represents a // single note and contains information about its parent and location. // This is the object *retrieved* from the database. #[derive(Clone, Debug, FromRow)] pub(crate) struct RowNote { pub id: String, pub parent_id: Option, pub content: String, pub kind: String, pub location: i64, pub creation_date: DateTime, pub updated_date: DateTime, pub lastview_date: DateTime, pub deleted_date: Option>, } /// A Note as it's returned from the private layer. This is /// provided to ensure that the NoteKind is an enum, and that we /// control the list of possible values stored in the database. #[derive(Clone, Debug)] pub struct Note { pub id: String, pub parent_id: Option, pub content: String, pub kind: NoteKind, pub location: i64, pub creation_date: DateTime, pub updated_date: DateTime, pub lastview_date: DateTime, pub deleted_date: Option>, } impl From for Note { fn from(note: RowNote) -> Self { Self { id: note.id, parent_id: note.parent_id, content: note.content, kind: NoteKind::from(note.kind), location: note.location, creation_date: note.creation_date, updated_date: note.updated_date, lastview_date: note.lastview_date, deleted_date: note.deleted_date, } } } /// A new Note object as it's inserted into the system. It has no /// parent or location information; those are data relative to the /// parent, and must be provided by the client. In the case of a /// Kasten, no location or parent is necessary. #[derive(Clone, Debug, Builder)] pub struct NewNote { #[builder(default = r#"friendly_id::create()"#)] pub id: String, pub content: String, #[builder(default = r#"NoteKind::Note"#)] pub kind: NoteKind, #[builder(default = r#"chrono::Utc::now()"#)] pub creation_date: DateTime, #[builder(default = r#"chrono::Utc::now()"#)] pub updated_date: DateTime, #[builder(default = r#"chrono::Utc::now()"#)] pub lastview_date: DateTime, #[builder(default = r#"None"#)] pub deleted_date: Option>, } impl From for Note { /// Only used for building new kastens, so the decision- making is /// limited to kasten-level things, like pointing to self and /// having a location of zero. fn from(note: NewNote) -> Self { Self { id: note.id, parent_id: None, content: note.content, kind: note.kind, location: 0, creation_date: note.creation_date, updated_date: note.updated_date, lastview_date: note.lastview_date, deleted_date: note.deleted_date, } } } #[derive(Clone, Debug, FromRow)] pub(crate) struct JustId { pub id: String, } #[derive(Clone, Debug, FromRow)] pub(crate) struct PageTitle { pub id: String, pub content: String, } #[derive(Clone, Debug, FromRow)] pub(crate) struct RowCount { pub count: i64, } #[derive(Clone, Debug, FromRow)] pub(crate) struct NoteRelationshipRow { pub parent_id: String, pub note_id: String, pub location: i64, pub kind: String, } #[derive(Clone, Debug)] pub(crate) struct NoteRelationship { pub parent_id: String, pub note_id: String, pub location: i64, pub kind: RelationshipKind, } impl From for NoteRelationship { fn from(rel: NoteRelationshipRow) -> Self { Self { parent_id: rel.parent_id, note_id: rel.note_id, location: rel.location, kind: RelationshipKind::from(rel.kind), } } } #[cfg(test)] mod tests { use super::*; #[test] fn can_build_new_note() { let now = chrono::Utc::now(); let newnote = NewNoteBuilder::default().content("bar".to_string()).build().unwrap(); assert!(newnote.id.len() > 4); assert!((newnote.creation_date - now).num_minutes() < 1); assert!((newnote.updated_date - now).num_minutes() < 1); assert!((newnote.lastview_date - now).num_minutes() < 1); assert!(newnote.deleted_date.is_none()); } }