My personal-knowledge-system, with deeply integrated task tracking and long term goal planning capabilities.
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: zettel parsing

+188 -19
+1 -6
crates/dto/src/entity/group.rs
··· 2 2 3 3 use migration::prelude::Local; 4 4 use migration::types::*; 5 - use sea_orm::ActiveValue::Set; 6 5 use sea_orm::entity::prelude::*; 6 + use sea_orm::ActiveValue::Set; 7 7 use std::future::ready; 8 8 use std::pin::Pin; 9 9 ··· 56 56 'life0: 'async_trait, 57 57 { 58 58 let now = Local::now().naive_local(); 59 - 60 - // set modified at 61 59 self.modified_at = Set(now); 62 - 63 - // we set the timestamp to non-utc 64 60 if insert && self.created_at.is_not_set() { 65 61 self.created_at = Set(now); 66 62 } 67 - 68 63 if insert && self.nano_id.is_not_set() { 69 64 self.nano_id = Set(NanoId::default()); 70 65 }
+1 -6
crates/dto/src/entity/task.rs
··· 2 2 3 3 use migration::prelude::Local; 4 4 use migration::types::*; 5 - use sea_orm::ActiveValue::Set; 6 5 use sea_orm::entity::prelude::*; 6 + use sea_orm::ActiveValue::Set; 7 7 use std::future::ready; 8 8 use std::pin::Pin; 9 9 ··· 54 54 'life0: 'async_trait, 55 55 { 56 56 let now = Local::now().naive_local(); 57 - 58 - // set modified at 59 57 self.modified_at = Set(now); 60 - 61 - // we set the timestamp to non-utc 62 58 if insert && self.created_at.is_not_set() { 63 59 self.created_at = Set(now); 64 60 } 65 - 66 61 if insert && self.nano_id.is_not_set() { 67 62 self.nano_id = Set(NanoId::default()); 68 63 }
+1 -6
crates/dto/src/entity/zettel.rs
··· 1 1 //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 2 2 3 3 use migration::{prelude::Local, types::*}; 4 - use sea_orm::ActiveValue::Set; 5 4 use sea_orm::entity::prelude::*; 5 + use sea_orm::ActiveValue::Set; 6 6 use std::{future::ready, pin::Pin}; 7 7 8 8 #[sea_orm::model] ··· 38 38 'life0: 'async_trait, 39 39 { 40 40 let now = Local::now().naive_local(); 41 - 42 - // set modified at 43 41 self.modified_at = Set(now); 44 - 45 - // we set the timestamp to non-utc 46 42 if insert && self.created_at.is_not_set() { 47 43 self.created_at = Set(now); 48 44 } 49 - 50 45 if insert && self.nano_id.is_not_set() { 51 46 self.nano_id = Set(NanoId::default()); 52 47 }
+16
crates/dto/src/lib.rs
··· 8 8 /// exported traits for the database 9 9 pub use sea_orm::ActiveModelTrait; 10 10 pub use sea_orm::ActiveValue; 11 + pub use sea_orm::ColumnTrait; 12 + pub use sea_orm::EntityTrait; 13 + pub use sea_orm::IntoActiveModel; 14 + pub use sea_orm::QueryFilter; 11 15 12 16 /// Exporting this as a generic NanoId. 13 17 pub use migration::types::NanoId; ··· 27 31 /// Everything related to groups. 28 32 pub use entity::group::ActiveModel as GroupActiveModel; 29 33 pub use entity::group::ActiveModelEx as GroupActiveModelEx; 34 + pub use entity::group::Column as GroupColumns; 30 35 pub use entity::group::Entity as GroupEntity; 31 36 pub use entity::group::Model as GroupModel; 32 37 pub use entity::group::ModelEx as GroupModelEx; ··· 34 39 /// Everything related to tasks. 35 40 pub use entity::task::ActiveModel as TaskActiveModel; 36 41 pub use entity::task::ActiveModelEx as TaskActiveModelEx; 42 + pub use entity::task::Column as TaskColumns; 37 43 pub use entity::task::Entity as TaskEntity; 38 44 pub use entity::task::Model as TaskModel; 39 45 pub use entity::task::ModelEx as TaskModelEx; ··· 41 47 /// Everything related to zetetl's. 42 48 pub use entity::zettel::ActiveModel as ZettelActiveModel; 43 49 pub use entity::zettel::ActiveModelEx as ZettelActiveModelEx; 50 + pub use entity::zettel::Column as ZettelColumns; 44 51 pub use entity::zettel::Entity as ZettelEntity; 45 52 pub use entity::zettel::Model as ZettelModel; 46 53 pub use entity::zettel::ModelEx as ZettelModelEx; ··· 48 55 /// Everything related to tag's. 49 56 pub use entity::tag::ActiveModel as TagActiveModel; 50 57 pub use entity::tag::ActiveModelEx as TagActiveModelEx; 58 + pub use entity::tag::Column as TagColumns; 51 59 pub use entity::tag::Entity as TagEntity; 52 60 pub use entity::tag::Model as TagModel; 53 61 pub use entity::tag::ModelEx as TagModelEx; 62 + 63 + /// Everything related to the zettel_tag entries. 64 + pub use entity::zettel_tag::ActiveModel as ZettelTagActiveModel; 65 + pub use entity::zettel_tag::ActiveModelEx as ZettelTagActiveModelEx; 66 + pub use entity::zettel_tag::Column as ZettelTagColumns; 67 + pub use entity::zettel_tag::Entity as ZettelTagEntity; 68 + pub use entity::zettel_tag::Model as ZettelTagModel; 69 + pub use entity::zettel_tag::ModelEx as ZettelTagModelEx;
+1
src/types/kasten.rs
··· 6 6 7 7 /// The `Kasten` that stores the `Link`s between `Zettel`s 8 8 #[derive(Debug, Clone)] 9 + #[expect(dead_code)] 9 10 pub struct Kasten { 10 11 /// Private field so it can only be instantiated from a `Path` 11 12 _private: (),
+168 -1
src/types/zettel.rs
··· 1 - use dto::{DateTime, TagEntity, ZettelActiveModel, ZettelEntity, ZettelModelEx}; 1 + use dto::{ 2 + ActiveModelTrait, ActiveValue, ColumnTrait, DateTime, EntityTrait as _, IntoActiveModel, 3 + QueryFilter, TagActiveModel, TagEntity, ZettelActiveModel, ZettelEntity, ZettelModelEx, 4 + ZettelTagActiveModel, ZettelTagColumns, ZettelTagEntity, 5 + }; 2 6 use serde::{Deserialize, Serialize}; 3 7 use std::{ 4 8 fmt::Display, 5 9 path::{Path, PathBuf}, 6 10 }; 11 + use tracing::info; 7 12 8 13 use color_eyre::eyre::{Error, Result, eyre}; 9 14 use dto::NanoId; ··· 100 105 fn absolute_path(&self, ws: &Workspace) -> PathBuf { 101 106 ws.root.clone().join(&self.file_path) 102 107 } 108 + 109 + /// uses the id and root to parse out of the root directory 110 + pub async fn from_id(id: &ZettelId, ws: &Workspace) -> Result<Self> { 111 + let mut path = ws.root.clone(); 112 + path.push(id.0.to_string()); 113 + Self::from_path(path, ws).await 114 + } 115 + 116 + pub async fn from_path(path: impl Into<PathBuf>, ws: &Workspace) -> Result<Self> { 117 + let path: PathBuf = path.into(); 118 + 119 + let id = ZettelId::try_from(path.as_path())?; 120 + 121 + let (front_matter, _) = FrontMatter::extract_from_file(&ws.root.clone().join(path)).await?; 122 + 123 + let mut zettel_tag_strings = front_matter.tag_strings.clone(); 124 + 125 + zettel_tag_strings.sort(); 126 + 127 + // get the zettel from the db 128 + let db_zettel: ZettelModelEx = if let Some(z) = ZettelEntity::load() 129 + .with(TagEntity) 130 + .filter_by_nano_id(id.clone()) 131 + .one(&ws.db) 132 + .await? 133 + { 134 + z 135 + } else { 136 + // if zettel is missing from db, we just add it here 137 + info!("adding zettel to db"); 138 + let am = ZettelActiveModel { 139 + nano_id: ActiveValue::Set(id.clone().into()), 140 + title: ActiveValue::Set(front_matter.title.clone()), 141 + ..Default::default() 142 + }; 143 + 144 + am.insert(&ws.db).await?; 145 + 146 + ZettelEntity::load() 147 + .with(TagEntity) 148 + .filter_by_nano_id(id.clone()) 149 + .one(&ws.db) 150 + .await? 151 + .expect("we just inserted the zettel") 152 + }; 153 + 154 + // get the tags for it 155 + for db_tag in db_zettel.tags { 156 + if let Ok(idx) = zettel_tag_strings.binary_search(&db_tag.name) { 157 + // we remove tags we have already processed 158 + zettel_tag_strings.remove(idx); 159 + } else { 160 + // the db says the file has tag `x`, but that tag is missing from the 161 + // front matter, we can assume its gone, lets delete that link 162 + let x = ZettelTagEntity::find() 163 + .filter(ZettelTagColumns::ZettelNanoId.eq(id.0.clone())) 164 + .filter(ZettelTagColumns::TagNanoId.eq(db_tag.nano_id)) 165 + .one(&ws.db) 166 + .await? 167 + .expect("this link must exist"); 168 + 169 + x.into_active_model().delete(&ws.db).await?; 170 + } 171 + } 172 + 173 + // now any tags that are left inside zettel_tag_strings, 174 + // we have to put them inside the db 175 + for new_tag in zettel_tag_strings { 176 + // create a new tag 177 + let tag = TagActiveModel { 178 + name: ActiveValue::Set(new_tag), 179 + ..Default::default() 180 + } 181 + .insert(&ws.db) 182 + .await?; 183 + 184 + // this zettel has this tag now 185 + let _ = ZettelTagActiveModel { 186 + zettel_nano_id: ActiveValue::Set(id.to_string()), 187 + tag_nano_id: ActiveValue::Set(tag.nano_id.to_string()), 188 + } 189 + .insert(&ws.db) 190 + .await?; 191 + } 192 + 193 + if front_matter.title != db_zettel.title { 194 + let am = ZettelActiveModel { 195 + id: ActiveValue::Unchanged(db_zettel.id), 196 + title: ActiveValue::Set(front_matter.title.clone()), 197 + ..Default::default() 198 + }; 199 + 200 + am.update(&ws.db).await?; 201 + } 202 + 203 + Ok(ZettelEntity::load() 204 + .with(TagEntity) 205 + .filter_by_nano_id(id.clone()) 206 + .one(&ws.db) 207 + .await? 208 + .expect("We just inserted it right above") 209 + .into()) 210 + } 211 + 212 + // pub fn apply_node_transform(&self, node: &mut Node<Zettel, Link>) { 213 + // node.set_label(self.front_matter.title.to_owned()); 214 + // let disp = node.display_mut(); 215 + // disp.radius = 100.0; 216 + // } 217 + 218 + // fn links_from_content(src_id: &ZettelId, content: &str, ws: &Workspace) -> ZkResult<Vec<Link>> { 219 + // let parsed = Parser::new(content); 220 + 221 + // let mut links = vec![]; 222 + 223 + // for event in parsed { 224 + // if let Event::Start(MkTag::Link { dest_url, .. }) = event { 225 + // info!("Found dest_url: {dest_url:#?}"); 226 + 227 + // let dest_path = { 228 + // // remove leading "./" 229 + // let without_prefix = dest_url.strip_prefix("./").unwrap_or(&dest_url); 230 + 231 + // // remove "#" and everything after it 232 + // let without_anchor = without_prefix.split('#').next().unwrap(); 233 + 234 + // // add .md if not present 235 + // let normalized = if without_anchor.ends_with(".md") { 236 + // without_anchor.to_string() 237 + // } else { 238 + // format!("{}.md", without_anchor) 239 + // }; 240 + 241 + // let mut tmp_root = ws.root.clone(); 242 + // tmp_root.push(normalized); 243 + // tmp_root 244 + // }; 245 + // // simplest way to validate that the path exists 246 + // let canon_url = match dest_path.canonicalize() { 247 + // Ok(canon_url) => canon_url, 248 + // Err(_) => { 249 + // error!("Link not found!: {dest_path:?}"); 250 + // continue; 251 + // } 252 + // }; 253 + 254 + // let dst_id = ZettelId::try_from(canon_url)?; 255 + 256 + // let link = Link::new(src_id, dst_id); 257 + 258 + // links.push(link) 259 + // } 260 + // } 261 + 262 + // Ok(links) 263 + // } 103 264 } 104 265 105 266 impl From<ZettelModelEx> for Zettel { ··· 179 340 f.write_str(&self.0.to_string()) 180 341 } 181 342 } 343 + 344 + impl From<ZettelId> for NanoId { 345 + fn from(value: ZettelId) -> Self { 346 + value.0 347 + } 348 + }