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.

Merge pull request #2 from suri-codes/db

DTO

authored by

Surendra Jammishetti and committed by
GitHub
590026b9 75f2a8ad

+974 -352
+2
.config/config.kdl
··· 1 + 2 + filaments_dir "{INSERT_HERE}" 1 3 2 4 keymap { 3 5
+1
.gitignore
··· 16 16 # Added by cargo 17 17 18 18 /target 19 + /ZettelKasten
+23 -23
Cargo.lock
··· 1200 1200 ] 1201 1201 1202 1202 [[package]] 1203 - name = "db" 1204 - version = "0.1.0" 1205 - dependencies = [ 1206 - "anyhow", 1207 - "migration", 1208 - "rand 0.10.0", 1209 - "sea-orm", 1210 - "thiserror 2.0.18", 1211 - "tokio", 1212 - "tracing", 1213 - ] 1214 - 1215 - [[package]] 1216 1203 name = "deltae" 1217 1204 version = "0.3.2" 1218 1205 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1364 1351 checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" 1365 1352 1366 1353 [[package]] 1354 + name = "dto" 1355 + version = "0.1.0" 1356 + dependencies = [ 1357 + "anyhow", 1358 + "migration", 1359 + "rand 0.10.0", 1360 + "sea-orm", 1361 + "thiserror 2.0.18", 1362 + "tokio", 1363 + "tracing", 1364 + ] 1365 + 1366 + [[package]] 1367 1367 name = "dunce" 1368 1368 version = "1.0.5" 1369 1369 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1501 1501 "clap", 1502 1502 "color-eyre", 1503 1503 "crossterm", 1504 - "db", 1505 1504 "directories", 1505 + "dto", 1506 1506 "futures", 1507 1507 "human-panic", 1508 1508 "kdl", ··· 4276 4276 checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" 4277 4277 4278 4278 [[package]] 4279 - name = "sakura" 4280 - version = "0.1.0" 4281 - dependencies = [ 4282 - "automerge", 4283 - "autosurgeon", 4284 - "serde", 4285 - ] 4286 - 4287 - [[package]] 4288 4279 name = "same-file" 4289 4280 version = "1.0.6" 4290 4281 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5465 5456 "tracing", 5466 5457 "tracing-core", 5467 5458 "tracing-log", 5459 + ] 5460 + 5461 + [[package]] 5462 + name = "tree" 5463 + version = "0.1.0" 5464 + dependencies = [ 5465 + "automerge", 5466 + "autosurgeon", 5467 + "serde", 5468 5468 ] 5469 5469 5470 5470 [[package]]
+8 -4
Cargo.toml
··· 1 1 [workspace] 2 2 members = [ 3 - "crates/db", 4 - "crates/db/migration", 5 - "crates/sakura" 3 + "crates/dto", 4 + "crates/dto/migration", 5 + "crates/tree" 6 6 ] 7 7 8 8 ··· 47 47 readme.workspace = true 48 48 keywords.workspace = true 49 49 50 + [[bin]] 51 + name = "fil" 52 + path = "./src/main.rs" 53 + 50 54 [lints] 51 55 workspace = true 52 56 ··· 68 72 tracing-error = "0.2.1" 69 73 clap = { version = "4.5.60", features = ["derive", "cargo", "wrap_help", "unicode", "string", "unstable-styles"] } 70 74 kdl = "6.5.0" 71 - db = {path="./crates/db"} 75 + dto = {path="./crates/dto"} 72 76 73 77 [build-dependencies] 74 78 anyhow = "1.0.102"
+1 -1
crates/db/Cargo.toml crates/dto/Cargo.toml
··· 1 1 [package] 2 - name = "db" 2 + name = "dto" 3 3 version.workspace = true 4 4 edition.workspace = true 5 5 authors.workspace = true
crates/db/migration/Cargo.toml crates/dto/migration/Cargo.toml
crates/db/migration/README.md crates/dto/migration/README.md
-18
crates/db/migration/src/lib.rs
··· 1 - pub use sea_orm_migration::prelude::*; 2 - 3 - pub mod types; 4 - 5 - mod m20260318_233726_group_table; 6 - mod m20260319_002245_task_table; 7 - 8 - pub struct Migrator; 9 - 10 - #[async_trait::async_trait] 11 - impl MigratorTrait for Migrator { 12 - fn migrations() -> Vec<Box<dyn MigrationTrait>> { 13 - vec![ 14 - Box::new(m20260318_233726_group_table::Migration), 15 - Box::new(m20260319_002245_task_table::Migration), 16 - ] 17 - } 18 - }
+18 -7
crates/db/migration/src/m20260318_233726_group_table.rs crates/dto/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 crates/dto/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,
crates/db/migration/src/main.rs crates/dto/migration/src/main.rs
crates/db/migration/src/types/mod.rs crates/dto/migration/src/types/mod.rs
crates/db/migration/src/types/nano_id.rs crates/dto/migration/src/types/nano_id.rs
crates/db/migration/src/types/priority.rs crates/dto/migration/src/types/priority.rs
-34
crates/db/src/entity/group.rs
··· 1 - //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 2 - 3 - use sea_orm::entity::prelude::*; 4 - use migration::types::*; 5 - 6 - #[sea_orm::model] 7 - #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] 8 - #[sea_orm(table_name = "group")] 9 - pub struct Model { 10 - #[sea_orm(primary_key)] 11 - pub id: i64, 12 - #[sea_orm(unique)] 13 - pub nano_id: NanoId, 14 - pub name: String, 15 - pub color: String, 16 - pub description_path: String, 17 - pub priority: Priority, 18 - pub created_at: DateTimeUtc, 19 - pub modified_at: DateTimeUtc, 20 - pub parent_group_id: Option<NanoId>, 21 - #[sea_orm( 22 - self_ref, 23 - relation_enum = "SelfRef", 24 - from = "parent_group_id", 25 - to = "nano_id", 26 - on_update = "Cascade", 27 - on_delete = "Cascade" 28 - )] 29 - pub group: HasOne<Entity>, 30 - #[sea_orm(has_many)] 31 - pub tasks: HasMany<super::task::Entity>, 32 - } 33 - 34 - impl ActiveModelBehavior for ActiveModel {}
+3
crates/db/src/entity/mod.rs crates/dto/src/entity/mod.rs
··· 3 3 pub mod prelude; 4 4 5 5 pub mod group; 6 + pub mod tag; 6 7 pub mod task; 8 + pub mod zettel; 9 + pub mod zettel_tag;
-4
crates/db/src/entity/prelude.rs
··· 1 - //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 2 - 3 - pub use super::group::Entity as Group; 4 - pub use super::task::Entity as Task;
-31
crates/db/src/entity/task.rs
··· 1 - //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 2 - 3 - use migration::types::*; 4 - use sea_orm::entity::prelude::*; 5 - 6 - #[sea_orm::model] 7 - #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] 8 - #[sea_orm(table_name = "task")] 9 - pub struct Model { 10 - #[sea_orm(primary_key)] 11 - pub id: i64, 12 - #[sea_orm(unique)] 13 - pub nano_id: NanoId, 14 - pub name: String, 15 - pub description_path: String, 16 - pub priority: Priority, 17 - pub due: Option<DateTimeUtc>, 18 - pub created_at: DateTimeUtc, 19 - pub modified_at: DateTimeUtc, 20 - pub group_id: NanoId, 21 - #[sea_orm( 22 - belongs_to, 23 - from = "group_id", 24 - to = "nano_id", 25 - on_update = "Cascade", 26 - on_delete = "Cascade" 27 - )] 28 - pub group: HasOne<super::group::Entity>, 29 - } 30 - 31 - impl ActiveModelBehavior for ActiveModel {}
-11
crates/db/src/errors.rs
··· 1 - use thiserror::Error; 2 - 3 - pub type DbResult<T> = Result<T, crate::errors::DbError>; 4 - 5 - #[derive(Debug, Error)] 6 - pub enum DbError { 7 - #[error("database file not found, tried looking at {not_found_at}")] 8 - NotFound { not_found_at: String }, 9 - #[error("Seaorm Error")] 10 - SeaOrm(#[from] sea_orm::error::DbErr), 11 - }
+20 -20
crates/db/src/lib.rs crates/dto/src/db.rs
··· 1 - //! The database abstraction for the different actions `Filaments` requires 2 - //! from a database service. 3 - 4 - use std::path::PathBuf; 1 + use std::{ops::Deref, path::PathBuf}; 5 2 6 - use migration::{Migrator, MigratorTrait}; 3 + use migration::{Migrator, MigratorTrait as _}; 7 4 use sea_orm::{Database, DatabaseConnection}; 8 5 use tracing::debug; 9 6 10 - use crate::errors::{DbError, DbResult}; 11 - 12 - /// Database Errors 13 - mod errors; 14 - 15 - /// Database entities 16 - pub mod entity; 17 - 18 - /// Types defined in migration 19 - pub use migration::types::*; 20 - 21 - pub use sea_orm::ActiveValue; 22 - 23 - #[expect(unused_imports)] 24 - pub use errors::*; 7 + use thiserror::Error; 25 8 26 9 /// Database struct 27 10 #[derive(Debug)] ··· 29 12 conn: DatabaseConnection, 30 13 } 31 14 15 + pub type DbResult<T> = Result<T, DbError>; 16 + 17 + #[derive(Debug, Error)] 18 + pub enum DbError { 19 + #[error("database file not found, tried looking at {not_found_at}")] 20 + NotFound { not_found_at: String }, 21 + #[error("Seaorm Error")] 22 + SeaOrm(#[from] sea_orm::error::DbErr), 23 + } 24 + 32 25 impl AsRef<DatabaseConnection> for Db { 33 26 fn as_ref(&self) -> &DatabaseConnection { 27 + &self.conn 28 + } 29 + } 30 + 31 + impl Deref for Db { 32 + type Target = DatabaseConnection; 33 + fn deref(&self) -> &Self::Target { 34 34 &self.conn 35 35 } 36 36 }
+1 -1
crates/db/tests/common/mod.rs crates/dto/tests/common/mod.rs
··· 3 3 path::PathBuf, 4 4 }; 5 5 6 - use db::Db; 6 + use dto::Db; 7 7 use rand::RngExt; 8 8 9 9 pub async fn fresh_test_db() -> Db {
-48
crates/db/tests/task.rs
··· 1 - //! Testing task functionality with the database abstraction. 2 - 3 - use db::entity::{group, prelude::*}; 4 - use db::{ActiveValue::Set, entity::task}; 5 - use sea_orm::ActiveModelTrait; 6 - mod common; 7 - 8 - #[tokio::test] 9 - async fn test_group_task_insert() { 10 - let db = common::fresh_test_db().await; 11 - 12 - let group = group::ActiveModel { 13 - name: Set("something".to_owned()), 14 - color: Set("color".to_owned()), 15 - description_path: Set("something".to_owned()), 16 - ..Default::default() 17 - }; 18 - 19 - let group: group::Model = group.insert(db.as_ref()).await.unwrap(); 20 - 21 - let task = task::ActiveModel { 22 - name: Set("something".to_owned()), 23 - description_path: Set("something".to_owned()), 24 - group_id: Set(group.nano_id.to_owned()), 25 - ..Default::default() 26 - }; 27 - 28 - let task: task::Model = task.insert(db.as_ref()).await.unwrap(); 29 - 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()) 35 - .await 36 - .unwrap(); 37 - 38 - let task = Task::load() 39 - .filter_by_nano_id(task.first().unwrap().nano_id.clone()) 40 - .with(Group) 41 - .one(db.as_ref()) 42 - .await 43 - .unwrap() 44 - .unwrap(); 45 - 46 - println!("{group:#?}"); 47 - println!("{task:#?}"); 48 - }
+24
crates/dto/migration/src/lib.rs
··· 1 + pub use sea_orm_migration::prelude::*; 2 + 3 + pub mod types; 4 + 5 + mod m20260318_233726_group_table; 6 + mod m20260319_002245_task_table; 7 + mod m20260323_002518_zettel_table; 8 + mod m20260327_175853_tag_table; 9 + mod m20260327_180618_zettel_tag_table; 10 + 11 + pub struct Migrator; 12 + 13 + #[async_trait::async_trait] 14 + impl MigratorTrait for Migrator { 15 + fn migrations() -> Vec<Box<dyn MigrationTrait>> { 16 + vec![ 17 + Box::new(m20260318_233726_group_table::Migration), 18 + Box::new(m20260319_002245_task_table::Migration), 19 + Box::new(m20260323_002518_zettel_table::Migration), 20 + Box::new(m20260327_175853_tag_table::Migration), 21 + Box::new(m20260327_180618_zettel_tag_table::Migration), 22 + ] 23 + } 24 + }
+66
crates/dto/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(Zettel::Table).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 userfacing 59 + NanoId, 60 + 61 + /// Title of this zettel 62 + Title, 63 + 64 + /// local file path to this `Zettel` 65 + FilePath, 66 + }
+52
crates/dto/migration/src/m20260327_175853_tag_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(Tag::Table) 15 + .if_not_exists() 16 + .col(pk_auto(Tag::Id)) 17 + .col( 18 + string(Tag::NanoId) 19 + .string_len(NANO_ID_LEN as u32) 20 + .unique_key() 21 + .not_null(), 22 + ) 23 + .col(string(Tag::Name).not_null()) 24 + .col(string(Tag::Color).not_null()) 25 + .to_owned(), 26 + ) 27 + .await 28 + } 29 + 30 + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { 31 + manager 32 + .drop_table(Table::drop().table(Tag::Table).to_owned()) 33 + .await 34 + } 35 + } 36 + 37 + #[derive(DeriveIden)] 38 + pub enum Tag { 39 + Table, 40 + 41 + /// Unique integer id 42 + Id, 43 + 44 + /// Unique userfacing nano-id 45 + NanoId, 46 + 47 + /// Name of the tag (case sensitive) 48 + Name, 49 + 50 + /// Color of this tag 51 + Color, 52 + }
+56
crates/dto/migration/src/m20260327_180618_zettel_tag_table.rs
··· 1 + use sea_orm_migration::{prelude::*, schema::*}; 2 + 3 + use crate::{m20260323_002518_zettel_table::Zettel, m20260327_175853_tag_table::Tag}; 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(ZettelTag::Table) 15 + .if_not_exists() 16 + .col(string(ZettelTag::ZettelNanoId).not_null()) 17 + .col(string(ZettelTag::TagNanoId).not_null()) 18 + .primary_key( 19 + Index::create() 20 + .col(ZettelTag::ZettelNanoId) 21 + .col(ZettelTag::TagNanoId), 22 + ) 23 + .foreign_key( 24 + ForeignKey::create() 25 + .name("fk-zettel_tag-zettel_nano_id") 26 + .from(ZettelTag::Table, ZettelTag::ZettelNanoId) 27 + .to(Zettel::Table, Zettel::NanoId) 28 + .on_delete(ForeignKeyAction::Cascade) 29 + .on_update(ForeignKeyAction::Cascade), 30 + ) 31 + .foreign_key( 32 + ForeignKey::create() 33 + .name("fk-zettel_tag-tag_nano_id") 34 + .from(ZettelTag::Table, ZettelTag::TagNanoId) 35 + .to(Tag::Table, Tag::NanoId) 36 + .on_delete(ForeignKeyAction::Cascade) 37 + .on_update(ForeignKeyAction::Cascade), 38 + ) 39 + .to_owned(), 40 + ) 41 + .await 42 + } 43 + 44 + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { 45 + manager 46 + .drop_table(Table::drop().table(ZettelTag::Table).to_owned()) 47 + .await 48 + } 49 + } 50 + 51 + #[derive(DeriveIden)] 52 + enum ZettelTag { 53 + Table, 54 + ZettelNanoId, 55 + TagNanoId, 56 + }
+62
crates/dto/src/entity/group.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; 7 + use std::pin::Pin; 8 + 9 + #[sea_orm::model] 10 + #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] 11 + #[sea_orm(table_name = "group")] 12 + pub struct Model { 13 + #[sea_orm(primary_key)] 14 + pub id: i64, 15 + #[sea_orm(unique)] 16 + pub nano_id: NanoId, 17 + pub name: String, 18 + pub color: String, 19 + pub priority: Priority, 20 + pub created_at: DateTimeUtc, 21 + pub modified_at: DateTimeUtc, 22 + #[sea_orm(unique)] 23 + pub zettel_id: NanoId, 24 + pub parent_group_id: Option<NanoId>, 25 + #[sea_orm( 26 + self_ref, 27 + relation_enum = "SelfRef", 28 + from = "parent_group_id", 29 + to = "nano_id", 30 + on_update = "Cascade", 31 + on_delete = "Cascade" 32 + )] 33 + pub group: HasOne<Entity>, 34 + #[sea_orm(has_many)] 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>, 44 + } 45 + 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 + }
+8
crates/dto/src/entity/prelude.rs
··· 1 + //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 2 + #![expect(unused_imports)] 3 + 4 + pub use super::group::Entity as Group; 5 + pub use super::tag::Entity as Tag; 6 + pub use super::task::Entity as Task; 7 + pub use super::zettel::Entity as Zettel; 8 + pub use super::zettel_tag::Entity as ZettelTag;
+37
crates/dto/src/entity/tag.rs
··· 1 + //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 2 + 3 + use migration::types::*; 4 + use sea_orm::{ActiveValue::Set, entity::prelude::*}; 5 + use std::{future::ready, pin::Pin}; 6 + 7 + #[sea_orm::model] 8 + #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] 9 + #[sea_orm(table_name = "tag")] 10 + pub struct Model { 11 + #[sea_orm(primary_key)] 12 + pub id: i64, 13 + #[sea_orm(unique)] 14 + pub nano_id: NanoId, 15 + pub name: String, 16 + pub color: String, 17 + #[sea_orm(has_many, via = "zettel_tag")] 18 + pub zettels: HasMany<super::zettel::Entity>, 19 + } 20 + 21 + impl ActiveModelBehavior for ActiveModel { 22 + fn before_save<'life0, 'async_trait, C>( 23 + mut self, 24 + _db: &'life0 C, 25 + insert: bool, 26 + ) -> Pin<Box<dyn Future<Output = Result<Self, DbErr>> + Send + 'async_trait>> 27 + where 28 + C: ConnectionTrait + 'async_trait, 29 + Self: Send + 'async_trait, 30 + 'life0: 'async_trait, 31 + { 32 + if insert && self.nano_id.is_not_set() { 33 + self.nano_id = Set(NanoId::default()); 34 + } 35 + Box::pin(ready(Ok(self))) 36 + } 37 + }
+59
crates/dto/src/entity/task.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; 7 + use std::pin::Pin; 8 + 9 + #[sea_orm::model] 10 + #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] 11 + #[sea_orm(table_name = "task")] 12 + pub struct Model { 13 + #[sea_orm(primary_key)] 14 + pub id: i64, 15 + #[sea_orm(unique)] 16 + pub nano_id: NanoId, 17 + pub name: String, 18 + pub priority: Priority, 19 + pub due: Option<DateTimeUtc>, 20 + pub created_at: DateTimeUtc, 21 + pub modified_at: DateTimeUtc, 22 + #[sea_orm(unique)] 23 + pub zettel_id: NanoId, 24 + pub group_id: NanoId, 25 + #[sea_orm( 26 + belongs_to, 27 + from = "group_id", 28 + to = "nano_id", 29 + on_update = "Cascade", 30 + on_delete = "Cascade" 31 + )] 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>, 41 + } 42 + 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/dto/src/entity/zettel.rs
··· 1 + //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 2 + 3 + use migration::types::*; 4 + use sea_orm::{ActiveValue::Set, entity::prelude::*}; 5 + use std::{future::ready, pin::Pin}; 6 + 7 + #[sea_orm::model] 8 + #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] 9 + #[sea_orm(table_name = "zettel")] 10 + /// What is up!!! 11 + pub struct Model { 12 + #[sea_orm(primary_key)] 13 + pub id: i64, 14 + #[sea_orm(unique)] 15 + pub nano_id: NanoId, 16 + pub title: String, 17 + pub file_path: String, 18 + #[sea_orm(has_one)] 19 + pub group: HasOne<super::group::Entity>, 20 + #[sea_orm(has_one)] 21 + pub task: HasOne<super::task::Entity>, 22 + #[sea_orm(has_many, via = "zettel_tag")] 23 + pub tags: HasMany<super::tag::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 + }
+31
crates/dto/src/entity/zettel_tag.rs
··· 1 + //! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 2 + 3 + use sea_orm::entity::prelude::*; 4 + 5 + #[sea_orm::model] 6 + #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] 7 + #[sea_orm(table_name = "zettel_tag")] 8 + pub struct Model { 9 + #[sea_orm(primary_key, auto_increment = false)] 10 + pub zettel_nano_id: String, 11 + #[sea_orm(primary_key, auto_increment = false)] 12 + pub tag_nano_id: String, 13 + #[sea_orm( 14 + belongs_to, 15 + from = "tag_nano_id", 16 + to = "nano_id", 17 + on_update = "Cascade", 18 + on_delete = "Cascade" 19 + )] 20 + pub tag: HasOne<super::tag::Entity>, 21 + #[sea_orm( 22 + belongs_to, 23 + from = "zettel_nano_id", 24 + to = "nano_id", 25 + on_update = "Cascade", 26 + on_delete = "Cascade" 27 + )] 28 + pub zettel: HasOne<super::zettel::Entity>, 29 + } 30 + 31 + impl ActiveModelBehavior for ActiveModel {}
+46
crates/dto/src/lib.rs
··· 1 + //! The DTO's (Data Transfer Objects) used to interact with 2 + //! the Database. There is also a simple database struct in here. 3 + 4 + /// Database and its Errors 5 + mod db; 6 + pub use db::*; 7 + 8 + /// exported traits for the database 9 + pub use sea_orm::ActiveModelTrait; 10 + pub use sea_orm::ActiveValue; 11 + 12 + /// Exporting this as a generic NanoId. 13 + pub use migration::types::NanoId; 14 + /// Exporting this as DTO so we can newtype this in a later crate 15 + /// and add additional functionality to it. 16 + pub use migration::types::Priority as PriorityDTO; 17 + 18 + mod entity; 19 + 20 + /// Everything related to groups. 21 + pub use entity::group::ActiveModel as GroupActiveModel; 22 + pub use entity::group::ActiveModelEx as GroupActiveModelEx; 23 + pub use entity::group::Entity as GroupEntity; 24 + pub use entity::group::Model as GroupModel; 25 + pub use entity::group::ModelEx as GroupModelEx; 26 + 27 + /// Everything related to tasks. 28 + pub use entity::task::ActiveModel as TaskActiveModel; 29 + pub use entity::task::ActiveModelEx as TaskActiveModelEx; 30 + pub use entity::task::Entity as TaskEntity; 31 + pub use entity::task::Model as TaskModel; 32 + pub use entity::task::ModelEx as TaskModelEx; 33 + 34 + /// Everything related to zetetl's. 35 + pub use entity::zettel::ActiveModel as ZettelActiveModel; 36 + pub use entity::zettel::ActiveModelEx as ZettelActiveModelEx; 37 + pub use entity::zettel::Entity as ZettelEntity; 38 + pub use entity::zettel::Model as ZettelModel; 39 + pub use entity::zettel::ModelEx as ZettelModelEx; 40 + 41 + /// Everything related to tag's. 42 + pub use entity::tag::ActiveModel as TagActiveModel; 43 + pub use entity::tag::ActiveModelEx as TagActiveModelEx; 44 + pub use entity::tag::Entity as TagEntity; 45 + pub use entity::tag::Model as TagModel; 46 + pub use entity::tag::ModelEx as TagModelEx;
+72
crates/dto/tests/task.rs
··· 1 + //! Testing task functionality with the database abstraction. 2 + 3 + use dto::{ 4 + ActiveModelTrait as _, ActiveValue::Set, GroupActiveModel, GroupEntity, GroupModel, 5 + TaskActiveModel, TaskEntity, TaskModel, ZettelActiveModel, ZettelEntity, ZettelModel, 6 + }; 7 + mod common; 8 + 9 + #[tokio::test] 10 + async fn test_group_task_insert() { 11 + let db = common::fresh_test_db().await; 12 + 13 + let group_zettel: ZettelModel = ZettelActiveModel { 14 + title: Set("Something".to_owned()), 15 + file_path: Set("/voo/doo".to_owned()), 16 + ..Default::default() 17 + } 18 + .insert(db.as_ref()) 19 + .await 20 + .unwrap(); 21 + 22 + let group: GroupModel = GroupActiveModel { 23 + name: Set("something".to_owned()), 24 + color: Set("color".to_owned()), 25 + zettel_id: Set(group_zettel.nano_id.clone()), 26 + ..Default::default() 27 + } 28 + .insert(db.as_ref()) 29 + .await 30 + .unwrap(); 31 + 32 + let task_zettel: ZettelModel = ZettelActiveModel { 33 + // nano_id: Set(NanoId::default()), 34 + title: Set("nomething".to_owned()), 35 + file_path: Set("/voo/doo".to_owned()), 36 + ..Default::default() 37 + } 38 + .insert(db.as_ref()) 39 + .await 40 + .unwrap(); 41 + 42 + let task: TaskModel = TaskActiveModel { 43 + name: Set("something".to_owned()), 44 + group_id: Set(group.nano_id.to_owned()), 45 + zettel_id: Set(task_zettel.nano_id.clone()), 46 + ..Default::default() 47 + } 48 + .insert(db.as_ref()) 49 + .await 50 + .unwrap(); 51 + 52 + let task = TaskEntity::load() 53 + .filter_by_nano_id(task.nano_id.clone()) 54 + .with(GroupEntity) 55 + .with(ZettelEntity) 56 + .one(db.as_ref()) 57 + .await 58 + .unwrap() 59 + .unwrap(); 60 + 61 + let group = GroupEntity::load() 62 + .filter_by_nano_id(group.nano_id.clone()) 63 + .with(TaskEntity) 64 + .with(ZettelEntity) 65 + .one(db.as_ref()) 66 + .await 67 + .unwrap() 68 + .unwrap(); 69 + 70 + println!("group: {group:#?}"); 71 + println!("task: {task:#?}"); 72 + }
+64
crates/dto/tests/zettel.rs
··· 1 + use dto::{ 2 + ActiveModelTrait, ActiveValue::Set, TagActiveModel, TagEntity, TagModel, ZettelActiveModel, 3 + ZettelEntity, ZettelModel, 4 + }; 5 + use sea_orm::IntoActiveModel; 6 + 7 + mod common; 8 + 9 + #[tokio::test] 10 + async fn test_zettel_tag_insert() { 11 + let db = common::fresh_test_db().await; 12 + 13 + let tag: TagModel = TagActiveModel { 14 + name: Set("Penis".to_owned()), 15 + color: Set("Some Color".to_owned()), 16 + ..Default::default() 17 + } 18 + .insert(&*db) 19 + .await 20 + .unwrap(); 21 + 22 + let _: ZettelModel = ZettelActiveModel { 23 + // nano_id: Set(NanoId::default()), 24 + title: Set("something1".to_owned()), 25 + file_path: Set("/voo/doo".to_owned()), 26 + ..Default::default() 27 + } 28 + .insert(db.as_ref()) 29 + .await 30 + .unwrap(); 31 + 32 + let x = ZettelActiveModel::builder() 33 + .set_title("Hello") 34 + .set_file_path("/voo/doo") 35 + // .add_tag( 36 + // TagActiveModel::builder() 37 + // .set_name("Hi") 38 + // .set_color("some color"), 39 + // ) 40 + .add_tag(tag.clone().into_active_model()) 41 + .insert(&*db) 42 + .await 43 + .unwrap(); 44 + 45 + dbg!(x); 46 + let _: ZettelModel = ZettelActiveModel { 47 + // nano_id: Set(NanoId::default()), 48 + title: Set("nomething2".to_owned()), 49 + file_path: Set("/voo/doo".to_owned()), 50 + ..Default::default() 51 + } 52 + .insert(db.as_ref()) 53 + .await 54 + .unwrap(); 55 + 56 + let zettels_for_tag = TagEntity::load() 57 + .filter_by_nano_id(tag.nano_id.clone()) 58 + .with(ZettelEntity) 59 + .all(db.as_ref()) 60 + .await 61 + .unwrap(); 62 + 63 + dbg!(zettels_for_tag); 64 + }
+1 -1
crates/sakura/Cargo.toml crates/tree/Cargo.toml
··· 1 1 [package] 2 - name = "sakura" 2 + name = "tree" 3 3 version = "0.1.0" 4 4 authors.workspace = true 5 5 edition.workspace = true
+19 -19
crates/sakura/src/behaviors.rs crates/tree/src/behaviors.rs
··· 8 8 /// be set as the first child as the new root `Node`. 9 9 /// 10 10 /// ``` 11 - /// use sakura::*; 12 - /// use sakura::InsertBehavior::*; 11 + /// use tree::*; 12 + /// use tree::InsertBehavior::*; 13 13 /// 14 14 /// let mut tree: Tree<i32> = Tree::new(); 15 15 /// let root_node = Node::new(1); ··· 27 27 /// `Result` containing the `NodeId` of the child that was added or a `NodeIdError` 28 28 /// 29 29 /// ``` 30 - /// use sakura::*; 31 - /// use sakura::InsertBehavior::*; 30 + /// use tree::*; 31 + /// use tree::InsertBehavior::*; 32 32 /// 33 33 /// let root_node = Node::new(1); 34 34 /// let child_node = Node::new(2); ··· 52 52 /// 53 53 /// 54 54 /// ``` 55 - /// use sakura::*; 56 - /// use sakura::InsertBehavior::*; 57 - /// use sakura::RemoveBehavior::*; 55 + /// use tree::*; 56 + /// use tree::InsertBehavior::*; 57 + /// use tree::RemoveBehavior::*; 58 58 /// 59 59 /// let mut tree: Tree<i32> = Tree::new(); 60 60 /// ··· 80 80 /// If `A` doesn't have a parent, then this behaves exactly like 81 81 /// `RemoveBehavior::OrphanChildren`. 82 82 /// ``` 83 - /// use sakura::*; 84 - /// use sakura::InsertBehavior::*; 85 - /// use sakura::RemoveBehavior::*; 83 + /// use tree::*; 84 + /// use tree::InsertBehavior::*; 85 + /// use tree::RemoveBehavior::*; 86 86 /// 87 87 /// let mut tree: Tree<i32> = Tree::new(); 88 88 /// ··· 106 106 /// `NodeId`'s. 107 107 /// 108 108 /// ``` 109 - /// use sakura::*; 110 - /// use sakura::InsertBehavior::*; 111 - /// use sakura::RemoveBehavior::*; 109 + /// use tree::*; 110 + /// use tree::InsertBehavior::*; 111 + /// use tree::RemoveBehavior::*; 112 112 /// 113 113 /// let mut tree: Tree<i32> = Tree::new(); 114 114 /// ··· 135 135 /// last child of the new root `Node`. 136 136 /// 137 137 /// ``` 138 - /// use sakura::*; 139 - /// use sakura::InsertBehavior::*; 140 - /// use sakura::MoveBehavior::*; 138 + /// use tree::*; 139 + /// use tree::InsertBehavior::*; 140 + /// use tree::MoveBehavior::*; 141 141 /// 142 142 /// let mut tree: Tree<i32> = Tree::new(); 143 143 /// ··· 167 167 /// NOTE: During the shift-up part of the above scenario, the `Node` being 168 168 /// shifted up will always be added as the last child of its new parent. 169 169 /// ``` 170 - /// use sakura::*; 171 - /// use sakura::InsertBehavior::*; 172 - /// use sakura::MoveBehavior::*; 170 + /// use tree::*; 171 + /// use tree::InsertBehavior::*; 172 + /// use tree::MoveBehavior::*; 173 173 /// 174 174 /// let mut tree: Tree<i32> = Tree::new(); 175 175 ///
crates/sakura/src/error.rs crates/tree/src/error.rs
crates/sakura/src/iterators.rs crates/tree/src/iterators.rs
crates/sakura/src/lib.rs crates/tree/src/lib.rs
+6 -6
crates/sakura/src/node.rs crates/tree/src/node.rs
··· 24 24 /// Creates a new `Node` with the provided data 25 25 /// 26 26 /// ``` 27 - /// use sakura::Node; 27 + /// use tree::Node; 28 28 /// 29 29 /// let _one: Node<i32> = Node::new(1); 30 30 /// ``` ··· 41 41 /// Returns a reference to the data inside the `Node` 42 42 /// 43 43 /// ``` 44 - /// use sakura::Node; 44 + /// use tree::Node; 45 45 /// 46 46 /// let x = 10; 47 47 /// let node: Node<i32> = Node::new(x); ··· 54 54 /// Returns a mutable reference to the data inside the `Node` 55 55 /// 56 56 /// ``` 57 - /// use sakura::Node; 57 + /// use tree::Node; 58 58 /// 59 59 /// let x = 10; 60 60 /// let mut node: Node<i32> = Node::new(x); ··· 69 69 /// Returns the data previously in the node 70 70 /// 71 71 /// ``` 72 - /// use sakura::Node; 72 + /// use tree::Node; 73 73 /// 74 74 /// let x = 10; 75 75 /// let mut y = 15; ··· 89 89 /// Returns the parent of this `Node`, if it has one. 90 90 /// 91 91 /// ``` 92 - /// use sakura::Node; 92 + /// use tree::Node; 93 93 /// 94 94 /// let node: Node<i32> = Node::new(1); 95 95 /// # assert_eq!(node.parent(), None); ··· 101 101 /// Returns the children of this `Node` 102 102 /// 103 103 /// ``` 104 - /// use sakura::Node; 104 + /// use tree::Node; 105 105 /// 106 106 /// let node: Node<i32> = Node::new(0); 107 107 /// # assert_eq!(node.children().len(), 0);
+57 -57
crates/sakura/src/tree.rs crates/tree/src/tree.rs
··· 26 26 /// Creates a new `TreeBuilder` with default settings. 27 27 /// 28 28 /// ``` 29 - /// use sakura::TreeBuilder; 29 + /// use tree::TreeBuilder; 30 30 /// 31 31 /// let _tree_builder: TreeBuilder<i32> = TreeBuilder::new(); 32 32 /// ··· 44 44 /// Sets the root `Node` for the resulting `Tree` from this `TreeBuilder`. 45 45 /// 46 46 /// ``` 47 - /// use sakura::TreeBuilder; 48 - /// use sakura::Node; 47 + /// use tree::TreeBuilder; 48 + /// use tree::Node; 49 49 /// 50 50 /// let _tree_builder: TreeBuilder<i32> = TreeBuilder::new().with_root(Node::new(1)); 51 51 /// ``` ··· 66 66 /// that your `Tree` will **contain** at **any given time**._ 67 67 /// 68 68 /// ``` 69 - /// use sakura::TreeBuilder; 69 + /// use tree::TreeBuilder; 70 70 /// 71 71 /// let _tree_builder: TreeBuilder<i32> = TreeBuilder::new().with_node_capacity(1); 72 72 /// ··· 105 105 /// The maximum amount of nodes that have been removed at any given time is **3**. 106 106 /// 107 107 /// ``` 108 - /// use sakura::TreeBuilder; 108 + /// use tree::TreeBuilder; 109 109 /// 110 110 /// let _tree_builder: TreeBuilder<i32> = TreeBuilder::new().with_node_capacity(1); 111 111 /// ··· 121 121 /// Build a `Tree` based upon the current settings in the `TreeBuilder`. 122 122 /// 123 123 /// ``` 124 - /// use sakura::TreeBuilder; 125 - /// use sakura::Tree; 126 - /// use sakura::Node; 124 + /// use tree::TreeBuilder; 125 + /// use tree::Tree; 126 + /// use tree::Node; 127 127 /// 128 128 /// let _tree: Tree<i32> = TreeBuilder::new() 129 129 /// .with_root(Node::new(5)) ··· 154 154 /// 155 155 /// # Panics 156 156 /// Any function that takes a `NodeId` can `panic`, but this should 157 - /// only happen with improper `NodeId` management within `Sakura`, and 157 + /// only happen with improper `NodeId` management within `tree`, and 158 158 /// should have nothing to do with library user's code. 159 159 #[derive(Debug, Serialize, Deserialize, Reconcile, Hydrate)] 160 160 pub struct Tree<T> { ··· 209 209 /// Creates a new `Tree` with default settings (no root `Node` and no space pre-allocation) 210 210 /// 211 211 /// ``` 212 - /// use sakura::Tree; 212 + /// use tree::Tree; 213 213 /// 214 214 /// let _tree: Tree<i32> = Tree::new(); 215 215 /// ``` ··· 231 231 /// it exists. Otherwise, a `None` is returned. 232 232 /// 233 233 /// ``` 234 - /// use sakura::*; 235 - /// use sakura::InsertBehavior::*; 234 + /// use tree::*; 235 + /// use tree::InsertBehavior::*; 236 236 /// 237 237 /// let mut tree: Tree<i32> = Tree::new(); 238 238 /// let root_id = tree.insert(Node::new(5), AsRoot).unwrap(); ··· 248 248 /// Returns the maximum height of the `Tree`. 249 249 /// 250 250 /// ``` 251 - /// use sakura::*; 252 - /// use sakura::InsertBehavior::*; 251 + /// use tree::*; 252 + /// use tree::InsertBehavior::*; 253 253 /// 254 254 /// let mut tree: Tree<i32> = Tree::new(); 255 255 /// # assert_eq!(0, tree.height()); ··· 285 285 /// # Panics 286 286 /// 287 287 /// Can panic if the `NodeId` does not exist in the `Tree`, but this would 288 - /// be a bug in `Sakura` 288 + /// be a bug in `tree` 289 289 /// 290 290 /// ``` 291 - /// use sakura::*; 292 - /// use sakura::InsertBehavior::*; 291 + /// use tree::*; 292 + /// use tree::InsertBehavior::*; 293 293 /// 294 294 /// let mut tree: Tree<i32> = Tree::new(); 295 295 /// let root_id = tree.insert(Node::new(5), AsRoot).unwrap(); ··· 324 324 /// # Panics 325 325 /// 326 326 /// Can panic if the `NodeId` does not exist in the `Tree`, but this would 327 - /// be a bug in `Sakura` 327 + /// be a bug in `tree` 328 328 /// 329 329 /// ``` 330 - /// use sakura::*; 331 - /// use sakura::InsertBehavior::*; 330 + /// use tree::*; 331 + /// use tree::InsertBehavior::*; 332 332 /// 333 333 /// let mut tree: Tree<i32> = Tree::new(); 334 334 /// let root_id = tree.insert(Node::new(5), AsRoot).unwrap(); ··· 363 363 /// # Panics 364 364 /// 365 365 /// Can panic if the `NodeId` does not exist in the `Tree`, but this would 366 - /// be a bug in `Sakura` 366 + /// be a bug in `tree` 367 367 /// 368 368 /// ``` 369 - /// use sakura::*; 370 - /// use sakura::InsertBehavior::*; 369 + /// use tree::*; 370 + /// use tree::InsertBehavior::*; 371 371 /// 372 372 /// let root_node = Node::new(1); 373 373 /// let child_node = Node::new(2); ··· 401 401 /// # Panics 402 402 /// 403 403 /// Can panic if the `NodeId` does not exist in the `Tree`, but this would 404 - /// be a bug in `Sakura` 404 + /// be a bug in `tree` 405 405 /// 406 406 /// 407 407 /// ``` 408 - /// use sakura::*; 409 - /// use sakura::InsertBehavior::*; 410 - /// use sakura::RemoveBehavior::*; 408 + /// use tree::*; 409 + /// use tree::InsertBehavior::*; 410 + /// use tree::RemoveBehavior::*; 411 411 /// 412 412 /// let mut tree: Tree<i32> = Tree::new(); 413 413 /// let root_id = tree.insert(Node::new(0), AsRoot).unwrap(); ··· 489 489 /// # Panics 490 490 /// 491 491 /// Can panic if the `NodeId` does not exist in the `Tree`, but this would 492 - /// be a bug in `Sakura` 492 + /// be a bug in `tree` 493 493 /// 494 494 #[allow(clippy::needless_pass_by_value)] 495 495 pub fn move_node( ··· 582 582 /// # Panics 583 583 /// 584 584 /// Can panic if the `NodeId` does not exist in the `Tree`, but this would 585 - /// be a bug in `Sakura` 585 + /// be a bug in `tree` 586 586 /// 587 587 /// ``` 588 - /// use sakura::*; 589 - /// use sakura::InsertBehavior::*; 588 + /// use tree::*; 589 + /// use tree::InsertBehavior::*; 590 590 /// 591 591 /// let mut tree: Tree<i32> = Tree::new(); 592 592 /// ··· 644 644 /// # Panics 645 645 /// 646 646 /// Can panic if the `NodeId` does not exist in the `Tree`, but this would 647 - /// be a bug in `Sakura` 647 + /// be a bug in `tree` 648 648 /// 649 649 /// ``` 650 - /// use sakura::*; 651 - /// use sakura::InsertBehavior::*; 650 + /// use tree::*; 651 + /// use tree::InsertBehavior::*; 652 652 /// 653 653 /// let mut tree: Tree<i32> = Tree::new(); 654 654 /// ··· 696 696 /// # Panics 697 697 /// 698 698 /// Can panic if the `NodeId` does not exist in the `Tree`, but this would 699 - /// be a bug in `Sakura` 699 + /// be a bug in `tree` 700 700 /// 701 701 /// ``` 702 - /// use sakura::*; 703 - /// use sakura::InsertBehavior::*; 702 + /// use tree::*; 703 + /// use tree::InsertBehavior::*; 704 704 /// 705 705 /// let mut tree: Tree<i32> = Tree::new(); 706 706 /// let root_id = tree.insert(Node::new(0), AsRoot).unwrap(); ··· 726 726 /// # Panics 727 727 /// 728 728 /// Can panic if the `NodeId` does not exist in the `Tree`, but this would 729 - /// be a bug in `Sakura` 729 + /// be a bug in `tree` 730 730 /// 731 731 /// 732 732 /// ``` 733 - /// use sakura::*; 734 - /// use sakura::InsertBehavior::*; 733 + /// use tree::*; 734 + /// use tree::InsertBehavior::*; 735 735 /// 736 736 /// let mut tree: Tree<i32> = Tree::new(); 737 737 /// let root_id = tree.insert(Node::new(0), AsRoot).unwrap(); ··· 758 758 /// # Panics 759 759 /// 760 760 /// Can panic if the `NodeId` does not exist in the `Tree`, but this would 761 - /// be a bug in `Sakura` 761 + /// be a bug in `tree` 762 762 /// 763 763 /// ``` 764 - /// use sakura::*; 765 - /// use sakura::InsertBehavior::*; 764 + /// use tree::*; 765 + /// use tree::InsertBehavior::*; 766 766 /// 767 767 /// let mut tree: Tree<i32> = Tree::new(); 768 768 /// let root_id = tree.insert(Node::new(0), AsRoot).unwrap(); ··· 787 787 /// # Panics 788 788 /// 789 789 /// Can panic if the `NodeId` does not exist in the `Tree`, but this would 790 - /// be a bug in `Sakura` 790 + /// be a bug in `tree` 791 791 /// 792 792 /// ``` 793 - /// use sakura::*; 794 - /// use sakura::InsertBehavior::*; 793 + /// use tree::*; 794 + /// use tree::InsertBehavior::*; 795 795 /// 796 796 /// let mut tree: Tree<i32> = Tree::new(); 797 797 /// let root_id = tree.insert(Node::new(0), AsRoot).unwrap(); ··· 816 816 /// # Panics 817 817 /// 818 818 /// Can panic if the `NodeId` does not exist in the `Tree`, but this would 819 - /// be a bug in `Sakura` 819 + /// be a bug in `tree` 820 820 /// 821 821 /// ``` 822 - /// use sakura::*; 823 - /// use sakura::InsertBehavior::*; 822 + /// use tree::*; 823 + /// use tree::InsertBehavior::*; 824 824 /// 825 825 /// let mut tree: Tree<i32> = Tree::new(); 826 826 /// let root_id = tree.insert(Node::new(0), AsRoot).unwrap(); ··· 851 851 /// # Panics 852 852 /// 853 853 /// Can panic if the `NodeId` does not exist in the `Tree`, but this would 854 - /// be a bug in `Sakura` 854 + /// be a bug in `tree` 855 855 /// 856 856 /// ``` 857 - /// use sakura::*; 858 - /// use sakura::InsertBehavior::*; 857 + /// use tree::*; 858 + /// use tree::InsertBehavior::*; 859 859 /// 860 860 /// let mut tree: Tree<i32> = Tree::new(); 861 861 /// let root_id = tree.insert(Node::new(0), AsRoot).unwrap(); ··· 971 971 assert!( 972 972 idx <= self.nodes.len(), 973 973 "NodeId: {node_id:?} is out of bounds. This is a bug inside 974 - Sakura.", 974 + tree.", 975 975 ); 976 976 977 977 if self.nodes.get(idx).is_none() { ··· 1063 1063 /// Function can error if something goes wrong during debug! 1064 1064 /// 1065 1065 /// ``` 1066 - /// use sakura::Tree; 1067 - /// use sakura::Node; 1068 - /// use sakura::InsertBehavior::*; 1066 + /// use tree::Tree; 1067 + /// use tree::Node; 1068 + /// use tree::InsertBehavior::*; 1069 1069 /// 1070 1070 /// let mut tree = Tree::<i32>::new(); 1071 1071 /// let root_id = tree.insert(Node::new(0), AsRoot).unwrap(); ··· 1085 1085 /// Writes nothing if the tree is empty. 1086 1086 /// 1087 1087 /// ``` 1088 - /// use sakura::Tree; 1088 + /// use tree::Tree; 1089 1089 /// 1090 1090 /// let tree = Tree::<i32>::new(); 1091 1091 /// let mut s = String::new();
+11 -11
flake.lock
··· 8 8 "rust-analyzer-src": "rust-analyzer-src" 9 9 }, 10 10 "locked": { 11 - "lastModified": 1774163246, 12 - "narHash": "sha256-gzlqyLjP44LWraUd3Zn4xrQKOtK+zcBJ77pnsSUsxcM=", 11 + "lastModified": 1774555695, 12 + "narHash": "sha256-JpTx7Rn8sILuXAH9a0K0UCZST5KY9ZTMzrZ61KcsNno=", 13 13 "owner": "nix-community", 14 14 "repo": "fenix", 15 - "rev": "4cd28929c68cae521589bc21958d3793904ed1e2", 15 + "rev": "d76ca95395662ed18b02b894e487eb41fd0e7d99", 16 16 "type": "github" 17 17 }, 18 18 "original": { ··· 23 23 }, 24 24 "nixpkgs": { 25 25 "locked": { 26 - "lastModified": 1773821835, 27 - "narHash": "sha256-TJ3lSQtW0E2JrznGVm8hOQGVpXjJyXY2guAxku2O9A4=", 28 - "rev": "b40629efe5d6ec48dd1efba650c797ddbd39ace0", 29 - "revCount": 964859, 26 + "lastModified": 1774386573, 27 + "narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=", 28 + "rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9", 29 + "revCount": 969196, 30 30 "type": "tarball", 31 - "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.964859%2Brev-b40629efe5d6ec48dd1efba650c797ddbd39ace0/019d03e9-2959-7276-adaa-d074e96422de/source.tar.gz" 31 + "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.969196%2Brev-46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9/019d279e-af65-79ce-92be-5dee7b1e36d4/source.tar.gz" 32 32 }, 33 33 "original": { 34 34 "type": "tarball", ··· 44 44 "rust-analyzer-src": { 45 45 "flake": false, 46 46 "locked": { 47 - "lastModified": 1774097238, 48 - "narHash": "sha256-hcujm/qEX4RUybdBCrQKdQNqTRYDItmnbjJRP5ky5vc=", 47 + "lastModified": 1774454876, 48 + "narHash": "sha256-bwkM8HseUs/22x+hy6FWvJMP6q/2CKBrm4sYxz9rMY8=", 49 49 "owner": "rust-lang", 50 50 "repo": "rust-analyzer", 51 - "rev": "76de1de27c0ca1329bc41324edab22c82d69e779", 51 + "rev": "9253d39eab8b9c9da3c1412fc94764e01d55a02b", 52 52 "type": "github" 53 53 }, 54 54 "original": {
+5 -5
justfile
··· 26 26 dev-db-url := "sqlite:///" + dev-db 27 27 28 28 # build entities from migrations 29 - [working-directory:"crates/db"] 29 + [working-directory:"crates/dto"] 30 30 entity: 31 31 # create the dev db 32 32 rm -f {{dev-db}} ··· 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
+4 -4
src/app.rs
··· 40 40 41 41 impl App { 42 42 /// Construct a new `App` instance. 43 - pub fn new(tick_rate: f64, frame_rate: f64) -> Self { 43 + pub fn new(tick_rate: f64, frame_rate: f64) -> Result<Self> { 44 44 let (signal_tx, signal_rx) = mpsc::unbounded_channel(); 45 45 46 - Self { 46 + Ok(Self { 47 47 tick_rate, 48 48 frame_rate, 49 49 components: vec![], 50 50 should_quit: false, 51 51 should_suspend: false, 52 - config: Config::new(), 52 + config: Config::parse()?, 53 53 region: Region::default(), 54 54 last_tick_key_events: Vec::new(), 55 55 signal_tx, 56 56 signal_rx, 57 - } 57 + }) 58 58 } 59 59 60 60 pub async fn run(&mut self) -> Result<()> {
+20 -10
src/cli.rs src/cli/mod.rs
··· 2 2 3 3 use crate::config::{get_config_dir, get_data_dir}; 4 4 5 + mod process; 6 + 5 7 #[derive(Parser, Debug)] 6 8 #[command(author, version = version(), about)] 7 9 pub struct Cli { ··· 19 21 20 22 #[derive(Subcommand, Debug)] 21 23 pub enum Commands { 22 - /// Manage TARS groups. 23 - // #[command(subcommand)] 24 - // Group(GroupSubcommand), 24 + // / Manage TARS groups. 25 + // #[command(subcommand)] 26 + // Group(GroupSubcommand), 25 27 26 - /// Manage TARS tasks. 27 - // #[command(subcommand)] 28 - // Task(TaskSubcommand), 29 - 30 - /// simple testing stuff 31 - Test, 32 - // Imports bulk data into TARS 28 + // / Manage TARS tasks. 29 + // #[command(subcommand)] 30 + // Task(TaskSubcommand), 31 + // 32 + // 33 + /// Initalize Filaments. 34 + /// 35 + /// This will write a default config to ~/.config/filaments, 36 + /// as well as creating a new "notebook" in the current 37 + /// directory with the specified name. Note that we currently 38 + /// only support one notebook. 39 + Init { 40 + #[arg(default_value = "ZettelKasten")] 41 + name: String, 42 + }, 33 43 // NOTE: By default the importer will fill in fields with 34 44 // default values if they arent present / aren't able to be 35 45 // parsed properly
+54
src/cli/process.rs
··· 1 + use std::{ 2 + env::current_dir, 3 + fs::{File, create_dir_all}, 4 + io::Write, 5 + }; 6 + 7 + use color_eyre::eyre::{Context, Result}; 8 + 9 + use crate::{ 10 + cli::Commands, 11 + config::{Config, get_config_dir}, 12 + }; 13 + 14 + impl Commands { 15 + pub fn process(self) -> Result<()> { 16 + match self { 17 + Self::Init { name } => { 18 + // create the directory 19 + let dir = current_dir() 20 + .context("Failed to get current directory")? 21 + .join(&name); 22 + 23 + // create the .filaments folder 24 + let filaments_dir = dir.join(".filaments"); 25 + 26 + create_dir_all(&filaments_dir) 27 + .context("Failed to create the filaments directory!")?; 28 + 29 + // create the database inside there 30 + File::create(filaments_dir.join("filaments.db"))?; 31 + 32 + // write config that sets the filaments directory to current dir! 33 + let config_kdl = dbg! {Config::generate(&dir)}; 34 + 35 + // create the config dir 36 + let config_dir = get_config_dir(); 37 + 38 + create_dir_all(config_dir).expect("creating the config dir should not error"); 39 + 40 + let mut config_file = File::create(get_config_dir().join("config.kdl")) 41 + .context("Failed to create config file")?; 42 + 43 + write!(config_file, "{config_kdl}")?; 44 + 45 + println!("wrote config to {config_file:#?}"); 46 + 47 + // report status! 48 + println!("Initialized successfully!"); 49 + } 50 + } 51 + 52 + Ok(()) 53 + } 54 + }
+76 -17
src/config.rs
··· 1 + use color_eyre::eyre::Context; 1 2 use directories::ProjectDirs; 2 3 use kdl::KdlDocument; 3 4 use serde::Deserialize; 4 - use std::{env, path::PathBuf, sync::LazyLock}; 5 + use std::{ 6 + env::{self, home_dir}, 7 + fmt::Debug, 8 + fs::File, 9 + io::Read, 10 + path::{Path, PathBuf}, 11 + sync::LazyLock, 12 + }; 5 13 6 14 use crate::keymap::KeyMap; 7 15 ··· 29 37 #[derive(Clone, Debug, Deserialize, Default)] 30 38 #[expect(dead_code)] 31 39 pub struct AppConfig { 40 + /// The directory where the single instance of the filaments exists. 41 + pub filaments: PathBuf, 32 42 #[serde(default)] 33 - pub data_dir: PathBuf, 43 + pub data: PathBuf, 34 44 #[serde(default)] 35 - pub config_dir: PathBuf, 45 + pub config: PathBuf, 36 46 } 37 47 38 48 /// Configuration for the App ··· 45 55 } 46 56 47 57 impl Config { 48 - pub fn new() -> Self { 49 - let default_config: KdlDocument = DEFAULT_CONFIG 58 + /// generates a new config with the provided `filaments_dir` 59 + pub fn generate(filaments_dir: &Path) -> KdlDocument { 60 + let mut default_config: KdlDocument = DEFAULT_CONFIG 50 61 .parse() 51 62 .expect("Default config should always be a valid KDL document."); 52 63 53 - let keymap_node = default_config 54 - .get("keymap") 55 - .expect("Config::new Keymap must exist in default config."); 64 + if let Some(node) = default_config 65 + .nodes_mut() 66 + .iter_mut() 67 + .find(|n| n.name().value() == "filaments_dir") 68 + && let Some(entry) = node.entries_mut().get_mut(0) 69 + { 70 + *entry.value_mut() = kdl::KdlValue::String(filaments_dir.to_string_lossy().to_string()); 71 + entry.clear_format(); 72 + } 73 + 74 + default_config 75 + } 76 + 77 + /// Parse the config from `~/.config/filametns` 78 + /// 79 + /// # Errors 80 + /// 81 + /// Will error if the config doesn't exist or if there 82 + /// is a problem parsing it. 83 + pub fn parse() -> color_eyre::Result<Self> { 84 + let config: KdlDocument = { 85 + let file_path = get_config_dir().join("config.kdl"); 86 + 87 + let mut file = File::open(file_path).context("Failed to find file!")?; 88 + 89 + let mut str = String::new(); 90 + 91 + file.read_to_string(&mut str) 92 + .context("Failed to read file!")?; 56 93 57 - let keymap = 58 - KeyMap::try_from(keymap_node).expect("default config should always be a valid keymap"); 94 + str.parse().context("Expected to be valid kdl")? 95 + }; 96 + 97 + let keymap = KeyMap::try_from( 98 + config 99 + .get("keymap") 100 + .expect("Keymap must exist in the config"), 101 + ) 102 + .context("Keymap is not valid!")?; 59 103 60 - Self { 104 + let filaments_dir_str = config 105 + .get("filaments_dir") 106 + .expect("config should always have this specified") 107 + .get(0) 108 + .and_then(|arg| arg.as_string()) 109 + .expect("filaments_dir must be a string"); 110 + 111 + let filaments_dir = PathBuf::from(filaments_dir_str) 112 + .canonicalize() 113 + .context("Filaments directory does not exist!")?; 114 + 115 + Ok(Self { 61 116 app_config: AppConfig { 62 - data_dir: get_data_dir(), 63 - config_dir: get_config_dir(), 117 + filaments: filaments_dir, 118 + data: get_data_dir(), 119 + config: get_config_dir(), 64 120 }, 65 121 keymap, 66 - } 122 + }) 67 123 } 68 124 } 69 125 ··· 80 136 /// Returns the path to the OS-agnostic config directory. 81 137 pub fn get_config_dir() -> PathBuf { 82 138 CONFIG_DIRECTORY.clone().unwrap_or_else(|| { 83 - project_directory().map_or_else( 139 + home_dir().map_or_else( 84 140 || PathBuf::from(".").join(".config"), 85 - |proj_dirs| proj_dirs.config_local_dir().to_path_buf(), 141 + |mut path| { 142 + path.push(".config"); 143 + path.push("filaments"); 144 + path 145 + }, 86 146 ) 87 147 }) 88 148 } 89 - 90 149 fn project_directory() -> Option<ProjectDirs> { 91 150 ProjectDirs::from("com", "suri-codes", env!("CARGO_PKG_NAME")) 92 151 }
+7 -11
src/main.rs
··· 4 4 5 5 use crate::{app::App, cli::Cli}; 6 6 use clap::Parser; 7 - use db::Db; 8 7 9 8 mod app; 10 9 mod cli; ··· 23 22 24 23 let args = Cli::parse(); 25 24 26 - let _db = Db::connect("/tmp/filaments/test_db.sqlite").await?; 27 - 28 25 // if there is any subcommand, we want to execute that, otherwise we 29 26 // just run the app 27 + if let Some(command) = args.command { 28 + return command.process(); 29 + } 30 30 31 - if let Some(command) = args.command { 32 - match command { 33 - cli::Commands::Test => {} 34 - } 35 - } else { 36 - let mut app = App::new(args.tick_rate, args.frame_rate); 31 + // if no command we run the app 32 + 33 + let mut app = App::new(args.tick_rate, args.frame_rate)?; 34 + app.run().await?; 37 35 38 - app.run().await?; 39 - } 40 36 Ok(()) 41 37 }