···3131 .not_null()
3232 .default(Priority::default().to_string()),
3333 )
3434- .col(timestamp(Group::CreatedAt).default(Expr::current_timestamp()))
3535- .col(timestamp(Group::ModifiedAt).default(Expr::current_timestamp()))
3434+ .col(date_time(Group::CreatedAt).default(Expr::current_timestamp()))
3535+ .col(date_time(Group::ModifiedAt).default(Expr::current_timestamp()))
3636 .col(string(Group::ZettelId).not_null().unique_key())
3737 // foreign key for the zettel related to this group
3838 .foreign_key(
···11//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0
22-#![expect(unused_imports)]
3233+#![expect(unused_imports)]
44pub use super::group::Entity as Group;
55pub use super::tag::Entity as Tag;
66pub use super::task::Entity as Task;
···11//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0
2233-use migration::types::*;
44-use sea_orm::entity::prelude::*;
33+use migration::{prelude::Local, types::*};
54use sea_orm::ActiveValue::Set;
55+use sea_orm::entity::prelude::*;
66use std::{future::ready, pin::Pin};
7788#[sea_orm::model]
···1616 pub nano_id: NanoId,
1717 pub title: String,
1818 pub file_path: String,
1919+ pub created_at: DateTime,
2020+ pub modified_at: DateTime,
1921 #[sea_orm(has_one)]
2022 pub group: HasOne<super::group::Entity>,
2123 #[sea_orm(has_one)]
···3537 Self: Send + 'async_trait,
3638 'life0: 'async_trait,
3739 {
4040+ let now = Local::now().naive_local();
4141+4242+ // set modified at
4343+ self.modified_at = Set(now);
4444+4545+ // we set the timestamp to non-utc
4646+ if insert && self.created_at.is_not_set() {
4747+ self.created_at = Set(now);
4848+ }
4949+3850 if insert && self.nano_id.is_not_set() {
3951 self.nano_id = Set(NanoId::default());
4052 }
+1-1
crates/dto/src/lib.rs
···1515/// and add additional functionality to it.
1616pub use migration::types::Priority as PriorityDTO;
17171818-pub use sea_orm::entity::prelude::DateTimeUtc;
1818+pub use sea_orm::entity::prelude::DateTime;
19192020/// Color type, exporting as DTO because I might
2121/// want to newtype wrap this, might not have to, depending
+7-1
crates/dto/tests/common/mod.rs
···33 path::PathBuf,
44};
5566+use dto::{Migrator, MigratorTrait};
67use rand::RngExt;
78use sea_orm::{Database, DatabaseConnection};
89···2627 path.clone().canonicalize().unwrap().to_string_lossy()
2728 );
28292929- Database::connect(db_conn_string).await.unwrap()
3030+ let conn = Database::connect(db_conn_string).await.unwrap();
3131+3232+ // run da migrations every time we connect, just in case
3333+ Migrator::up(&conn, None).await.unwrap();
3434+3535+ conn
3036}
+22
src/cli/mod.rs
···25252626#[derive(Subcommand, Debug)]
2727pub enum Commands {
2828+ /// Manage `Zettel`s
2929+ #[command(subcommand)]
3030+ Zettel(ZettelSubcommand),
3131+2832 // / Manage TARS groups.
2933 // #[command(subcommand)]
3034 // Group(GroupSubcommand),
···5054 // Import(ImportArgs),
5155}
52565757+#[derive(Subcommand, Debug)]
5858+/// Subcommand to manage tars groups.
5959+pub enum ZettelSubcommand {
6060+ /// Add a group.
6161+ New {
6262+ #[arg(short, long)]
6363+ /// The file-path for data to pe put into.
6464+ title: String,
6565+ },
6666+ /// List groups.
6767+ List {
6868+ /// Filter by tag
6969+ #[arg(short = 't', long)]
7070+ by_tag: String,
7171+ },
7272+}
7373+5374// #[derive(Subcommand, Debug)]
5475// /// Subcommand to manage tars groups.
5576// pub enum GroupSubcommand {
···6586// /// The file-path for data to pe put into.
6687// pub out_file: PathBuf,
6788// }
8989+//
68906991const VERSION_MESSAGE: &str = concat!(
7092 env!("CARGO_PKG_VERSION"),
···11+use async_trait::async_trait;
12use crossterm::event::{KeyEvent, MouseEvent};
23use ratatui::{
34 Frame,
···1011 tui::{Event, Signal},
1112};
12131414+/// The zk component
1515+mod zk;
1616+1717+pub use zk::*;
1818+1319/// `Component` is a trait that represents a visual and interactive element of the user interface.
1420///
1521/// Implementers of this trait can be registered with the main application loop and will be able to
1622/// receive events, update state, and be rendered on the screen.
1717-pub trait Component: Send {
2323+///
2424+#[async_trait]
2525+pub trait Component: Send + Sync {
1826 /// Register a signal handler that can send signals for processing if necessary.
1927 ///
2028 /// # Arguments
···106114 /// # Returns
107115 ///
108116 /// * [`color_eyre::Result<Option<signal>>`] - A signal to be processed or none.
109109- fn update(&mut self, signal: Signal) -> color_eyre::Result<Option<Signal>>;
117117+ async fn update(&mut self, signal: Signal) -> color_eyre::Result<Option<Signal>>;
110118111119 /// Render the component on the screen. (REQUIRED)
112120 ///
···11use std::path::PathBuf;
2233use color_eyre::eyre::{Context, Result};
44-use dto::{Database, DatabaseConnection};
44+use dto::{Database, DatabaseConnection, Migrator, MigratorTrait};
55use tokio::fs::{File, create_dir_all};
66use tracing::debug;
7788/// The `Workspace` in which the filaments exist.
99-#[expect(dead_code)]
109#[derive(Debug, Clone)]
1110pub struct Workspace {
1211 /// Private field so it can only be instantiated from a `Path`
···3938 let conn = Database::connect(db_conn_string)
4039 .await
4140 .context("Failed to connect to the database in the filaments workspace!")?;
4141+4242+ // run da migrations every time we connect, just in case
4343+ Migrator::up(&conn, None).await?;
42444345 Ok(Self {
4446 _private: (),
+76-3
src/types/zettel.rs
···11+use dto::{DateTime, TagEntity, ZettelActiveModel, ZettelEntity, ZettelModelEx};
12use std::path::PathBuf;
2333-use dto::{NanoId, ZettelModelEx};
44+use color_eyre::eyre::Result;
55+use dto::NanoId;
66+use tokio::{fs::File, io::AsyncWriteExt};
4755-use crate::types::Tag;
88+use crate::types::{FrontMatter, Tag, Workspace};
69710/// A `Zettel` is a note about a single idea.
811/// It can have many `Tag`s, just meaning it can fall under many
···1215pub struct Zettel {
1316 /// Should only be constructed from models.
1417 _private: (),
1515-1618 pub id: NanoId,
1719 pub title: String,
1820 /// a workspace-local file path, needs to be canonicalized before usage
1921 pub file_path: PathBuf,
2222+ pub created_at: DateTime,
2023 pub tags: Vec<Tag>,
2124}
22252626+impl Zettel {
2727+ pub async fn new(title: impl Into<String>, ws: &Workspace) -> Result<Self> {
2828+ // fn new(title: impl Into<String>) -> Result<Self> {
2929+ let title = title.into();
3030+3131+ // make a file that has a random identifier, and then
3232+ // also has the name "title"
3333+ let nano_id = NanoId::default();
3434+3535+ let local_file_path = format!("{nano_id}.md");
3636+3737+ // now we have to create the file
3838+ let mut file = File::create_new(ws.root.clone().join(&local_file_path)).await?;
3939+4040+ let inserted = ZettelActiveModel::builder()
4141+ .set_title(title.clone())
4242+ .set_file_path(local_file_path)
4343+ .set_nano_id(nano_id)
4444+ .insert(&ws.db)
4545+ .await?;
4646+4747+ // need to load tags...
4848+ let zettel = ZettelEntity::load()
4949+ .filter_by_nano_id(inserted.nano_id)
5050+ .with(TagEntity)
5151+ .one(&ws.db)
5252+ .await?
5353+ .expect("This must exist since we just inserted it");
5454+5555+ let front_matter = FrontMatter::new(
5656+ title,
5757+ zettel.created_at,
5858+ zettel.tags.iter().map(|t| t.name.clone()).collect(),
5959+ );
6060+6161+ file.write_all(front_matter.to_string().as_bytes()).await?;
6262+6363+ Ok(zettel.into())
6464+ }
6565+6666+ /// Returns the most up-to-date `FrontMatter` for this
6767+ /// `Zettel`
6868+ #[expect(dead_code)]
6969+ pub async fn front_matter(&self, ws: &Workspace) -> Result<FrontMatter> {
7070+ let path = self.absolute_path(ws);
7171+ let (fm, _) = FrontMatter::extract_from_file(path).await?;
7272+ Ok(fm)
7373+ }
7474+7575+ /// Returns the content of this `Zettel`, which is everything
7676+ /// but the `FrontMatter`
7777+ #[expect(dead_code)]
7878+ pub async fn content(&self, ws: &Workspace) -> Result<String> {
7979+ let path = self.absolute_path(ws);
8080+ let (_, content) = FrontMatter::extract_from_file(path).await?;
8181+ Ok(content)
8282+ }
8383+8484+ #[expect(dead_code)]
8585+ async fn open_file(&self, ws: &Workspace) -> Result<File> {
8686+ let path = self.absolute_path(ws);
8787+ Ok(File::open(path).await?)
8888+ }
8989+9090+ fn absolute_path(&self, ws: &Workspace) -> PathBuf {
9191+ ws.root.clone().join(&self.file_path)
9292+ }
9393+}
9494+2395impl From<ZettelModelEx> for Zettel {
2496 fn from(value: ZettelModelEx) -> Self {
2597 assert!(
···33105 id: value.nano_id,
34106 title: value.title,
35107 file_path: value.file_path.into(),
108108+ created_at: value.created_at,
36109 tags: value.tags.into_iter().map(Into::into).collect(),
37110 }
38111 }