notesmachine/server/nm-store/src/structs.rs

247 lines
6.9 KiB
Rust

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<String> 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<NoteKind> 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<String> 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<RelationshipKind> 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<String>,
pub content: String,
pub kind: String,
pub location: i64,
pub creation_date: DateTime<Utc>,
pub updated_date: DateTime<Utc>,
pub lastview_date: DateTime<Utc>,
pub deleted_date: Option<DateTime<Utc>>,
}
/// 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<String>,
pub content: String,
pub kind: NoteKind,
pub location: i64,
pub creation_date: DateTime<Utc>,
pub updated_date: DateTime<Utc>,
pub lastview_date: DateTime<Utc>,
pub deleted_date: Option<DateTime<Utc>>,
}
impl From<RowNote> 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<Utc>,
#[builder(default = r#"chrono::Utc::now()"#)]
pub updated_date: DateTime<Utc>,
#[builder(default = r#"chrono::Utc::now()"#)]
pub lastview_date: DateTime<Utc>,
#[builder(default = r#"None"#)]
pub deleted_date: Option<DateTime<Utc>>,
}
impl From<NewNote> 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<NoteRelationshipRow> 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());
}
}