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: unique tag names + parsing

+26 -63
+1 -1
crates/dto/migration/src/m20260327_175853_tag_table.rs
··· 20 20 .unique_key() 21 21 .not_null(), 22 22 ) 23 - .col(string(Tag::Name).not_null()) 23 + .col(string(Tag::Name).unique_key().not_null()) 24 24 .col(integer(Tag::Color).not_null()) 25 25 .to_owned(), 26 26 )
+1 -1
crates/dto/src/entity/prelude.rs
··· 1 1 //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 2 - 3 2 #![expect(unused_imports)] 3 + 4 4 pub use super::group::Entity as Group; 5 5 pub use super::tag::Entity as Tag; 6 6 pub use super::task::Entity as Task;
+3 -4
crates/dto/src/entity/tag.rs
··· 1 1 //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 2 2 3 3 use migration::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] ··· 13 13 pub id: i64, 14 14 #[sea_orm(unique)] 15 15 pub nano_id: NanoId, 16 + #[sea_orm(unique)] 16 17 pub name: String, 17 18 pub color: Color, 18 19 #[sea_orm(has_many, via = "zettel_tag")] ··· 33 34 if insert && self.nano_id.is_not_set() { 34 35 self.nano_id = Set(NanoId::default()); 35 36 } 36 - 37 37 if insert && self.color.is_not_set() { 38 - self.color = Set(Color::default()) 38 + self.color = Set(Color::default()); 39 39 } 40 - 41 40 Box::pin(ready(Ok(self))) 42 41 } 43 42 }
+21 -57
src/types/zettel.rs
··· 138 138 } 139 139 140 140 // now any tags that are left inside zettel_tag_strings, 141 - // we have to put them inside the db 142 - for new_tag in tag_strings { 143 - // create a new tag 144 - let tag = TagActiveModel { 145 - name: ActiveValue::Set(new_tag), 146 - ..Default::default() 147 - } 148 - .insert(&ws.db) 149 - .await?; 141 + // we have to look up the tags in the db and then reset them? 142 + for tag_str in tag_strings { 143 + // this is the tag that either already exists with this name, or we just created this new one 144 + let tag = if let Some(existing) = TagEntity::load() 145 + .filter_by_name(&tag_str) 146 + .one(&ws.db) 147 + .await? 148 + { 149 + existing 150 + } else { 151 + let am = TagActiveModel { 152 + name: ActiveValue::Set(tag_str), 153 + ..Default::default() 154 + }; 155 + 156 + am.insert(&ws.db).await?.into() 157 + }; 150 158 151 159 // this zettel has this tag now 152 160 let _ = ZettelTagActiveModel { ··· 223 231 224 232 let (front_matter, _) = FrontMatter::extract_from_file(&ws.root.clone().join(path)).await?; 225 233 226 - let mut zettel_tag_strings = front_matter.tag_strings.clone(); 227 - 228 - zettel_tag_strings.sort(); 229 - 230 234 // get the zettel from the db 231 235 let db_zettel: ZettelModelEx = if let Some(existing_zettel) = ZettelEntity::load() 232 236 .with(TagEntity) ··· 254 258 .expect("we just inserted the zettel") 255 259 }; 256 260 257 - // get the tags for it 258 - for db_tag in db_zettel.tags { 259 - if let Ok(idx) = zettel_tag_strings.binary_search(&db_tag.name) { 260 - // we remove tags we have already processed 261 - zettel_tag_strings.remove(idx); 262 - } else { 263 - // the db says the file has tag `x`, but that tag is missing from the 264 - // front matter, we can assume its gone, lets delete that link 265 - let to_remove = ZettelTagEntity::find() 266 - .filter(ZettelTagColumns::ZettelNanoId.eq(id.0.clone())) 267 - .filter(ZettelTagColumns::TagNanoId.eq(db_tag.nano_id)) 268 - .one(&ws.db) 269 - .await? 270 - .expect("this link must exist"); 271 - 272 - to_remove.into_active_model().delete(&ws.db).await?; 273 - } 274 - } 275 - 276 - // now any tags that are left inside zettel_tag_strings, 277 - // we have to put them inside the db 278 - for new_tag in zettel_tag_strings { 279 - // create a new tag 280 - let tag = TagActiveModel { 281 - name: ActiveValue::Set(new_tag), 282 - ..Default::default() 283 - } 284 - .insert(&ws.db) 285 - .await?; 286 - 287 - // this zettel has this tag now 288 - let _ = ZettelTagActiveModel { 289 - zettel_nano_id: ActiveValue::Set(id.to_string()), 290 - tag_nano_id: ActiveValue::Set(tag.nano_id.to_string()), 291 - } 292 - .insert(&ws.db) 293 - .await?; 294 - } 261 + let mut temp_zettel: Self = db_zettel.clone().into(); 262 + temp_zettel.sync_tags(ws).await?; 295 263 296 264 if front_matter.title != db_zettel.title { 297 - let am = ZettelActiveModel { 298 - id: ActiveValue::Unchanged(db_zettel.id), 299 - title: ActiveValue::Set(front_matter.title.clone()), 300 - ..Default::default() 301 - }; 302 - 265 + let mut am = db_zettel.into_active_model(); 266 + am.title = ActiveValue::Set(front_matter.title.clone()); 303 267 am.update(&ws.db).await?; 304 268 } 305 269