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: add zettel table

+257 -44
+2
crates/db/migration/src/lib.rs
··· 4 4 5 5 mod m20260318_233726_group_table; 6 6 mod m20260319_002245_task_table; 7 + mod m20260323_002518_zettel_table; 7 8 8 9 pub struct Migrator; 9 10 ··· 13 14 vec![ 14 15 Box::new(m20260318_233726_group_table::Migration), 15 16 Box::new(m20260319_002245_task_table::Migration), 17 + Box::new(m20260323_002518_zettel_table::Migration), 16 18 ] 17 19 } 18 20 }
+18 -7
crates/db/migration/src/m20260318_233726_group_table.rs
··· 1 1 use sea_orm_migration::{prelude::*, schema::*}; 2 2 3 - use crate::types::{NANO_ID_LEN, NanoId, Priority}; 3 + use crate::{ 4 + m20260323_002518_zettel_table::Zettel, 5 + types::{NANO_ID_LEN, Priority}, 6 + }; 4 7 5 8 #[derive(DeriveMigrationName)] 6 9 pub struct Migration; ··· 18 21 string(Group::NanoId) 19 22 .string_len(NANO_ID_LEN as u32) 20 23 .unique_key() 21 - .not_null() 22 - .default(NanoId::default().0), 24 + .not_null(), 23 25 ) 24 26 .col(string(Group::Name).not_null()) 25 27 //Note: Color is a hex color with the leading # 26 28 .col(string(Group::Color).not_null()) 27 - .col(string(Group::DescriptionPath).not_null()) 28 29 .col( 29 30 string(Group::Priority) 30 31 .not_null() ··· 32 33 ) 33 34 .col(timestamp(Group::CreatedAt).default(Expr::current_timestamp())) 34 35 .col(timestamp(Group::ModifiedAt).default(Expr::current_timestamp())) 36 + .col(string(Group::ZettelId).not_null().unique_key()) 37 + // foreign key for the zettel related to this group 38 + .foreign_key( 39 + ForeignKey::create() 40 + .name("fk_task_zettel_id") 41 + .from(Group::Table, Group::ZettelId) 42 + .to(Zettel::Table, Zettel::NanoId) 43 + .on_update(ForeignKeyAction::Cascade) 44 + .on_delete(ForeignKeyAction::Cascade), 45 + ) 35 46 .col(string_null(Group::ParentGroupId)) 47 + // foreign key for the parent group related to this group 36 48 .foreign_key( 37 49 ForeignKey::create() 38 50 .name("fk_group_parent_id") // unique constraint name ··· 90 102 /// Priority level of the group 91 103 Priority, 92 104 93 - /// The relative file path to the location of 94 - /// the description note for this task 95 - DescriptionPath, 105 + /// The Id of the Zettel created for this Group 106 + ZettelId, 96 107 97 108 /// Creation time 98 109 CreatedAt,
+18 -9
crates/db/migration/src/m20260319_002245_task_table.rs
··· 2 2 3 3 use crate::{ 4 4 m20260318_233726_group_table::Group, 5 - types::{NANO_ID_LEN, NanoId, Priority}, 5 + m20260323_002518_zettel_table::Zettel, 6 + types::{NANO_ID_LEN, Priority}, 6 7 }; 7 8 8 9 #[derive(DeriveMigrationName)] ··· 21 22 string(Task::NanoId) 22 23 .string_len(NANO_ID_LEN as u32) 23 24 .unique_key() 24 - .not_null() 25 - .default(NanoId::default().0), 25 + .not_null(), 26 26 ) 27 27 .col(string(Task::Name).not_null()) 28 - .col(string(Task::DescriptionPath).not_null()) 29 28 .col( 30 29 string(Task::Priority) 31 30 .not_null() ··· 34 33 .col(timestamp(Task::Due).null()) 35 34 .col(timestamp(Task::CreatedAt).default(Expr::current_timestamp())) 36 35 .col(timestamp(Task::ModifiedAt).default(Expr::current_timestamp())) 36 + .col(string(Task::ZettelId).not_null().unique_key()) 37 + // foreign key for the zettel related to this task 38 + .foreign_key( 39 + ForeignKey::create() 40 + .name("fk_task_zettel_id") 41 + .from(Task::Table, Task::ZettelId) 42 + .to(Zettel::Table, Zettel::NanoId) 43 + .on_update(ForeignKeyAction::Cascade) 44 + .on_delete(ForeignKeyAction::Cascade), 45 + ) 37 46 .col(string(Task::GroupId).not_null()) 47 + // foreign key for the group related to this task 38 48 .foreign_key( 39 49 ForeignKey::create() 40 - .name("fk_task_group_id") // unique constraint name 50 + .name("fk_task_group_id") 41 51 .from(Task::Table, Task::GroupId) 42 - .to(Group::Table, Group::NanoId) // self-referential to the nano-id 52 + .to(Group::Table, Group::NanoId) 43 53 .on_update(ForeignKeyAction::Cascade) 44 54 .on_delete(ForeignKeyAction::Cascade), 45 55 ) ··· 116 126 /// Priority level of the group 117 127 Priority, 118 128 119 - /// The relative file path to the location of 120 - /// the description note for this task 121 - DescriptionPath, 129 + /// The Id of the Zettel created for thi Task 130 + ZettelId, 122 131 123 132 /// The duedate for this task 124 133 Due,
+66
crates/db/migration/src/m20260323_002518_zettel_table.rs
··· 1 + use sea_orm_migration::{prelude::*, schema::*}; 2 + 3 + use crate::types::NANO_ID_LEN; 4 + 5 + #[derive(DeriveMigrationName)] 6 + pub struct Migration; 7 + 8 + #[async_trait::async_trait] 9 + impl MigrationTrait for Migration { 10 + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { 11 + manager 12 + .create_table( 13 + Table::create() 14 + .table(Zettel::Table) 15 + .if_not_exists() 16 + .col(pk_auto(Zettel::Id)) 17 + .col( 18 + string(Zettel::NanoId) 19 + .string_len(NANO_ID_LEN as u32) 20 + .unique_key() 21 + .not_null(), 22 + ) 23 + .col(string(Zettel::Title).not_null()) 24 + .col(string(Zettel::FilePath).not_null()) 25 + .to_owned(), 26 + ) 27 + .await?; 28 + 29 + manager 30 + .create_index( 31 + Index::create() 32 + .name("idx_zettel_pub_id") 33 + .table(Zettel::Table) 34 + .col(Zettel::NanoId) 35 + .to_owned(), 36 + ) 37 + .await 38 + } 39 + 40 + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { 41 + manager 42 + .drop_index(Index::drop().name("idx_zettel_pub_id").to_owned()) 43 + .await?; 44 + 45 + manager 46 + .drop_table(Table::drop().table("post").to_owned()) 47 + .await 48 + } 49 + } 50 + 51 + #[derive(DeriveIden)] 52 + pub enum Zettel { 53 + Table, 54 + 55 + /// Unique integer id 56 + Id, 57 + 58 + /// Unique nano-id that is user-facing 59 + NanoId, 60 + 61 + /// The title of this zettel 62 + Title, 63 + 64 + /// The relative file-path to this `Zettel` 65 + FilePath, 66 + }
+30 -2
crates/db/src/entity/group.rs
··· 2 2 3 3 use migration::types::*; 4 4 use sea_orm::entity::prelude::*; 5 + use sea_orm::ActiveValue::Set; 6 + use std::future::ready; 7 + use std::pin::Pin; 5 8 6 9 #[sea_orm::model] 7 10 #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] ··· 13 16 pub nano_id: NanoId, 14 17 pub name: String, 15 18 pub color: String, 16 - pub description_path: String, 17 19 pub priority: Priority, 18 20 pub created_at: DateTimeUtc, 19 21 pub modified_at: DateTimeUtc, 22 + #[sea_orm(unique)] 23 + pub zettel_id: NanoId, 20 24 pub parent_group_id: Option<NanoId>, 21 25 #[sea_orm( 22 26 self_ref, ··· 29 33 pub group: HasOne<Entity>, 30 34 #[sea_orm(has_many)] 31 35 pub tasks: HasMany<super::task::Entity>, 36 + #[sea_orm( 37 + belongs_to, 38 + from = "zettel_id", 39 + to = "nano_id", 40 + on_update = "Cascade", 41 + on_delete = "Cascade" 42 + )] 43 + pub zettel: HasOne<super::zettel::Entity>, 32 44 } 33 45 34 - impl ActiveModelBehavior for ActiveModel {} 46 + impl ActiveModelBehavior for ActiveModel { 47 + fn before_save<'life0, 'async_trait, C>( 48 + mut self, 49 + _db: &'life0 C, 50 + insert: bool, 51 + ) -> Pin<Box<dyn Future<Output = Result<Self, DbErr>> + Send + 'async_trait>> 52 + where 53 + C: ConnectionTrait + 'async_trait, 54 + Self: Send + 'async_trait, 55 + 'life0: 'async_trait, 56 + { 57 + if insert && self.nano_id.is_not_set() { 58 + self.nano_id = Set(NanoId::default()); 59 + } 60 + Box::pin(ready(Ok(self))) 61 + } 62 + }
+1
crates/db/src/entity/mod.rs
··· 4 4 5 5 pub mod group; 6 6 pub mod task; 7 + pub mod zettel;
+1
crates/db/src/entity/prelude.rs
··· 2 2 3 3 pub use super::group::Entity as Group; 4 4 pub use super::task::Entity as Task; 5 + pub use super::zettel::Entity as Zettel;
+30 -2
crates/db/src/entity/task.rs
··· 2 2 3 3 use migration::types::*; 4 4 use sea_orm::entity::prelude::*; 5 + use sea_orm::ActiveValue::Set; 6 + use std::future::ready; 7 + use std::pin::Pin; 5 8 6 9 #[sea_orm::model] 7 10 #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] ··· 12 15 #[sea_orm(unique)] 13 16 pub nano_id: NanoId, 14 17 pub name: String, 15 - pub description_path: String, 16 18 pub priority: Priority, 17 19 pub due: Option<DateTimeUtc>, 18 20 pub created_at: DateTimeUtc, 19 21 pub modified_at: DateTimeUtc, 22 + #[sea_orm(unique)] 23 + pub zettel_id: NanoId, 20 24 pub group_id: NanoId, 21 25 #[sea_orm( 22 26 belongs_to, ··· 26 30 on_delete = "Cascade" 27 31 )] 28 32 pub group: HasOne<super::group::Entity>, 33 + #[sea_orm( 34 + belongs_to, 35 + from = "zettel_id", 36 + to = "nano_id", 37 + on_update = "Cascade", 38 + on_delete = "Cascade" 39 + )] 40 + pub zettel: HasOne<super::zettel::Entity>, 29 41 } 30 42 31 - impl ActiveModelBehavior for ActiveModel {} 43 + impl ActiveModelBehavior for ActiveModel { 44 + fn before_save<'life0, 'async_trait, C>( 45 + mut self, 46 + _db: &'life0 C, 47 + insert: bool, 48 + ) -> Pin<Box<dyn Future<Output = Result<Self, DbErr>> + Send + 'async_trait>> 49 + where 50 + C: ConnectionTrait + 'async_trait, 51 + Self: Send + 'async_trait, 52 + 'life0: 'async_trait, 53 + { 54 + if insert && self.nano_id.is_not_set() { 55 + self.nano_id = Set(NanoId::default()); 56 + } 57 + Box::pin(ready(Ok(self))) 58 + } 59 + }
+42
crates/db/src/entity/zettel.rs
··· 1 + //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 2 + 3 + use migration::types::*; 4 + use sea_orm::entity::prelude::*; 5 + use sea_orm::ActiveValue::Set; 6 + use std::future::{ready, Future}; 7 + use std::pin::Pin; 8 + 9 + #[sea_orm::model] 10 + #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] 11 + #[sea_orm(table_name = "zettel")] 12 + /// What is up!!! 13 + pub struct Model { 14 + #[sea_orm(primary_key)] 15 + pub id: i64, 16 + #[sea_orm(unique)] 17 + pub nano_id: NanoId, 18 + pub title: String, 19 + pub file_path: String, 20 + #[sea_orm(has_one)] 21 + pub group: HasOne<super::group::Entity>, 22 + #[sea_orm(has_one)] 23 + pub task: HasOne<super::task::Entity>, 24 + } 25 + 26 + impl ActiveModelBehavior for ActiveModel { 27 + fn before_save<'life0, 'async_trait, C>( 28 + mut self, 29 + _db: &'life0 C, 30 + insert: bool, 31 + ) -> Pin<Box<dyn Future<Output = Result<Self, DbErr>> + Send + 'async_trait>> 32 + where 33 + C: ConnectionTrait + 'async_trait, 34 + Self: Send + 'async_trait, 35 + 'life0: 'async_trait, 36 + { 37 + if insert && self.nano_id.is_not_set() { 38 + self.nano_id = Set(NanoId::default()); 39 + } 40 + Box::pin(ready(Ok(self))) 41 + } 42 + }
+45 -20
crates/db/tests/task.rs
··· 1 1 //! Testing task functionality with the database abstraction. 2 2 3 - use db::entity::{group, prelude::*}; 3 + use db::entity::{group, prelude::*, zettel}; 4 4 use db::{ActiveValue::Set, entity::task}; 5 5 use sea_orm::ActiveModelTrait; 6 6 mod common; ··· 9 9 async fn test_group_task_insert() { 10 10 let db = common::fresh_test_db().await; 11 11 12 - let group = group::ActiveModel { 12 + let group_zettel: zettel::Model = zettel::ActiveModel { 13 + title: Set("Something".to_owned()), 14 + file_path: Set("/voo/doo".to_owned()), 15 + ..Default::default() 16 + } 17 + .insert(db.as_ref()) 18 + .await 19 + .unwrap(); 20 + 21 + let group: group::Model = group::ActiveModel { 13 22 name: Set("something".to_owned()), 14 23 color: Set("color".to_owned()), 15 - description_path: Set("something".to_owned()), 24 + zettel_id: Set(group_zettel.nano_id.clone()), 16 25 ..Default::default() 17 - }; 26 + } 27 + .insert(db.as_ref()) 28 + .await 29 + .unwrap(); 18 30 19 - let group: group::Model = group.insert(db.as_ref()).await.unwrap(); 31 + let task_zettel: zettel::Model = zettel::ActiveModel { 32 + // nano_id: Set(NanoId::default()), 33 + title: Set("nomething".to_owned()), 34 + file_path: Set("/voo/doo".to_owned()), 35 + ..Default::default() 36 + } 37 + .insert(db.as_ref()) 38 + .await 39 + .unwrap(); 20 40 21 - let task = task::ActiveModel { 41 + let task: task::Model = task::ActiveModel { 22 42 name: Set("something".to_owned()), 23 - description_path: Set("something".to_owned()), 24 43 group_id: Set(group.nano_id.to_owned()), 44 + zettel_id: Set(task_zettel.nano_id.clone()), 25 45 ..Default::default() 26 - }; 27 - 28 - let task: task::Model = task.insert(db.as_ref()).await.unwrap(); 46 + } 47 + .insert(db.as_ref()) 48 + .await 49 + .unwrap(); 29 50 30 - let task = Task::find_by_nano_id(task.nano_id) 31 - .inner_join(Group) 32 - // .reverse_join(Group) 33 - // .find_with_related(Group) 34 - .all(db.as_ref()) 51 + let task = Task::load() 52 + .filter_by_nano_id(task.nano_id.clone()) 53 + .with(Group) 54 + .with(Zettel) 55 + .one(db.as_ref()) 35 56 .await 57 + .unwrap() 36 58 .unwrap(); 37 59 38 - let task = Task::load() 39 - .filter_by_nano_id(task.first().unwrap().nano_id.clone()) 40 - .with(Group) 60 + let group = Group::load() 61 + .filter_by_nano_id(group.nano_id.clone()) 62 + .with(Task) 63 + .with(Zettel) 41 64 .one(db.as_ref()) 42 65 .await 43 66 .unwrap() 44 67 .unwrap(); 45 68 46 - println!("{group:#?}"); 47 - println!("{task:#?}"); 69 + println!("group: {group:#?}"); 70 + println!("task: {task:#?}"); 71 + 72 + panic!() 48 73 }
+4 -4
justfile
··· 39 39 sea-orm-cli generate entity \ 40 40 --database-url {{dev-db-url}} \ 41 41 --output-dir ./src/entity \ 42 - --entity-format=dense # add flag if expanded format is needed for debugging 42 + --experimental-preserve-user-modifications \ 43 + --entity-format=dense # add flag if expanded format is needed for debugging 43 44 44 - # add migraton::types to every file in entity 45 - sed -i '4i use migration::types::*;' ./src/entity/*.rs 46 45 47 46 # replace elementary types with specific ones 48 47 sed -i 's/pub nano_id: String/pub nano_id: NanoId/g' ./src/entity/*.rs ··· 51 50 # replace parent_group_id with proper nano_id 52 51 sed -i 's/pub parent_group_id: Option<String>/pub parent_group_id: Option<NanoId>/g' ./src/entity/*.rs 53 52 54 - # replace group_id with nano_id 53 + # replace foregin key id's with nano_id 55 54 sed -i 's/pub group_id: String/pub group_id: NanoId/g' ./src/entity/*.rs 55 + sed -i 's/pub zettel_id: String/pub zettel_id: NanoId/g' ./src/entity/*.rs 56 56 57 57 58 58