diff --git a/Cargo.lock b/Cargo.lock index cfa1b56..cd05612 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,31 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a" +[[package]] +name = "async-channel" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca33f4bc4ed1babef42cad36cc1f51fa88be00420404e5b1e80ab1b18f7678c" +dependencies = [ + "concurrent-queue", + "event-listener 4.0.0", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-fs" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "blocking", + "futures-lite 1.13.0", +] + [[package]] name = "async-graphql" version = "6.0.11" @@ -192,6 +217,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + [[package]] name = "async-lock" version = "3.2.0" @@ -236,6 +270,12 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "async-task" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1" + [[package]] name = "async-trait" version = "0.1.73" @@ -247,12 +287,28 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "async-walkdir" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826d88d73e87e7504b635b6e427561faa6a65f4a2f59e75efcbfa51a0876bb90" +dependencies = [ + "async-fs", + "futures-lite 1.13.0", +] + [[package]] name = "atomic" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" @@ -319,9 +375,11 @@ dependencies = [ "anyhow", "async-graphql", "async-graphql-axum", + "async-walkdir", "axum", "bincode", "figment", + "futures", "heck", "hirola", "kasuku-database", @@ -332,7 +390,6 @@ dependencies = [ "tokio", "tower-http", "types", - "walkdir", "xtra", ] @@ -444,6 +501,22 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +dependencies = [ + "async-channel", + "async-lock 3.2.0", + "async-task", + "fastrand 2.0.0", + "futures-io", + "futures-lite 2.1.0", + "piper", + "tracing", +] + [[package]] name = "borsh" version = "1.2.1" @@ -977,6 +1050,15 @@ dependencies = [ "ascii_utils", ] +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.0.0" @@ -1134,6 +1216,31 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeee267a1883f7ebef3700f262d2d54de95dfaf38189015a74fdc4e0c7ad8143" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.28" @@ -1692,6 +1799,15 @@ dependencies = [ "libc", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -2313,6 +2429,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand 2.0.0", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.27" @@ -2361,7 +2488,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4277e630563df7dd8c99cca81c361d38c1b2d81c6ffd3086a4714aae81df7968" dependencies = [ "anyhow", - "async-lock", + "async-lock 3.2.0", "bincode", "dashmap", "plugy-core", @@ -3269,7 +3396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" dependencies = [ "cfg-if", - "fastrand", + "fastrand 2.0.0", "redox_syscall 0.3.5", "rustix 0.38.26", "windows-sys 0.48.0", @@ -3754,6 +3881,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" + [[package]] name = "walkdir" version = "2.4.0" diff --git a/Kasuku.toml b/Kasuku.toml index f482f2b..e1bb496 100644 --- a/Kasuku.toml +++ b/Kasuku.toml @@ -1,8 +1,10 @@ [internals] cache-path = "./cache" -[apps] -planning = { plugins = ["tasks"], mount = "./Tasks" } +[vaults.planning] +plugins = ["tasks"] +mount = "/home/geoff/Documents/kasuku" +file_types = ["md"] # budget = { plugins = ["tasks", "finance", "schedule"], mount = "./Bills" } # entertainment = { plugins = ["files", "spotify", "audible"], mount = "./Media" } diff --git a/README.md b/README.md index f51e91a..66cfa28 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Kasuku is a all-in-one personal management system using wasm ## Todo Features -- [**Tasks:**](/plugins/tasks/) Stay on top of your to-do list and tasks. Mark tasks as complete, set priorities, and organize your day. +- [**📝Tasks:**](/plugins/tasks/) Stay on top of your to-do list and tasks. Mark tasks as complete, set priorities, and organize your day. - **Notes:** Capture your thoughts, ideas, and inspirations. Organize notes into categories and easily search for them. diff --git a/crates/backend/Cargo.toml b/crates/backend/Cargo.toml index 543d5ba..9e0fcbc 100644 --- a/crates/backend/Cargo.toml +++ b/crates/backend/Cargo.toml @@ -24,6 +24,10 @@ heck = "0.4" hirola = { version = "0.4.0-beta.0", default-features = false } serde_json = "1" figment = { version = "0.10", features = ["toml", "env"] } -walkdir = "2.4.0" +async-walkdir = "0.2.0" notify = "6.1.1" -xtra = { git = "https://github.com/Restioson/xtra", features = ["tokio", "macros"] } \ No newline at end of file +xtra = { git = "https://github.com/Restioson/xtra", features = [ + "tokio", + "macros", +] } +futures = "0.3" diff --git a/crates/backend/src/lib.rs b/crates/backend/src/lib.rs index d0e5091..a9e94ae 100644 --- a/crates/backend/src/lib.rs +++ b/crates/backend/src/lib.rs @@ -3,6 +3,7 @@ pub mod query; use async_graphql::{http::GraphiQLSource, EmptySubscription, Schema}; use async_graphql_axum::GraphQL; +use async_walkdir::{Filtering, WalkDir}; use axum::{ http::Method, response::{Html, IntoResponse}, @@ -18,7 +19,9 @@ use std::{ sync::{Arc, Mutex}, }; use tower_http::cors::{Any, CorsLayer}; -use types::{config::Config, BackendPlugin, Emitter, Fetcher, GlobalContext, Plugin, UserInfo, Database}; +use types::{ + config::Config, BackendPlugin, Database, Emitter, Fetcher, GlobalContext, Plugin, UserInfo, +}; use xtra::Mailbox; use crate::{mutation::MutationRoot, query::QueryRoot}; @@ -32,6 +35,7 @@ pub struct KasukuContext; pub struct KasukuRuntime { inner: Arc>>, config: Config, + database: Arc>>, } impl Deref for KasukuRuntime { @@ -43,7 +47,21 @@ impl Deref for KasukuRuntime { impl KasukuRuntime { pub async fn new(config: Config) -> Self { - let db = Glue::new(KasukuDatabase::new("/tmp/kasuku-6").await.unwrap()); + use futures::FutureExt; + let ks = KasukuDatabase::open(&config.internals.database_path) + .then(|res| async { + if res.is_err() { + let new_db = KasukuDatabase::new(&config.internals.database_path) + .await + .unwrap(); + new_db.save().await.unwrap(); + return new_db; + } + res.unwrap() + }) + .await; + let db = Glue::new(ks); + let db = Arc::new(Mutex::new(db)); let runtime = Runtime::new().unwrap(); let runtime = runtime.context(Fetcher).context(Emitter).context(Database); @@ -58,9 +76,80 @@ impl KasukuRuntime { .unwrap(); plugin.on_load(::types::Context::acquire()).await.unwrap(); } + // db.lock().unwrap().storage.save().await.unwrap(); + db.lock() + .as_mut() + .unwrap() + .execute( + "CREATE TABLE IF NOT EXISTS vaults ( + name TEXT NOT NULL PRIMARY KEY, + mount TEXT NOT NULL, + ); + CREATE TABLE IF NOT EXISTS entries ( + path TEXT NOT NULL PRIMARY KEY, + vault TEXT NOT NULL, + last_modified INTEGER, + meta TEXT + )", + ) + .unwrap(); + + for (vault, vault_config) in config.vaults.clone() { + let mount = vault_config.mount.clone(); + // let db_clone = Arc::clone(&db); + let _res = db + .lock() + .as_mut() + .unwrap() + .execute(format!( + "INSERT INTO vaults(name, mount) VALUES ('{vault}', '{}') ON CONFLICT(name) DO + UPDATE SET mount = EXCLUDED.mount", + mount.to_str().unwrap() + )) + .unwrap_or(vec![]); + + // tokio::spawn(async move { + use futures::stream::StreamExt; + let mut entries = WalkDir::new(mount).filter(|entry| async move { + if let Some(true) = entry + .path() + .file_name() + .map(|f| f.to_string_lossy().starts_with('.')) + { + return Filtering::IgnoreDir; + } + if let Some(true) = entry + .path() + .file_name() + .map(|f| f.to_string_lossy().ends_with(".md")) + { + return Filtering::Continue; + } + Filtering::Ignore + }); + loop { + match entries.next().await { + Some(Ok(entry)) => { + println!("file: {}", entry.path().display()); + let _res = db.lock().unwrap().execute(format!( + "INSERT INTO entries(path, vault) VALUES('{}', '{}')", + entry.path().display(), + vault + )); + } + Some(Err(e)) => { + eprintln!("error: {}", e); + break; + } + None => break, + } + } + // }); + } KasukuRuntime { inner: Arc::new(runtime), config, + database: db, } } } diff --git a/crates/backend/src/query.rs b/crates/backend/src/query.rs index d191f5b..47b171f 100644 --- a/crates/backend/src/query.rs +++ b/crates/backend/src/query.rs @@ -1,50 +1,26 @@ use ::types::{Emit, Event}; use async_graphql::*; +use kasuku_database::prelude::Payload; +use serde::Deserialize; +use serde::Serialize; use crate::{BackendPlugin, KasukuRuntime}; pub struct QueryRoot; -#[derive(Debug, serde::Serialize, serde::Deserialize, InputObject)] -pub struct Render { - file: String, - renderer: Option, -} - #[derive(Debug, serde::Serialize, serde::Deserialize, SimpleObject)] +#[graphql(complex)] pub struct File { - name: String, path: String, - size: usize, - mime_type: String, - last_modified: String, - meta: Option, -} - -#[derive(Debug, serde::Serialize, serde::Deserialize, Enum, Clone, Copy, PartialEq, Eq)] -pub enum DomNode { - Element, - Comment, - Text, - Fragment, // Element { - // name: String, - // attributes: HashMap, - // children: Vec, - // }, - // Comment(Comment), - // Text(Text), - // Fragment(Vec), -} - -#[derive(Debug, serde::Serialize, serde::Deserialize, SimpleObject)] -pub struct RenderResult { - main: DomNode, - toolbar: Vec, + // size: usize, + // mime_type: String, + // last_modified: String, + // meta: Option, } -#[Object] -impl QueryRoot { - async fn render(&self, ctx: &Context<'_>, _req: Render) -> serde_json::Value { +#[ComplexObject] +impl File { + async fn render(&self, ctx: &Context<'_>, renderer: Option) -> serde_json::Value { let runtime: &KasukuRuntime = ctx.data().unwrap(); let plugin: ::types::PluginWrapper = runtime.get_plugin_by_name("tasks").unwrap(); @@ -63,11 +39,84 @@ impl QueryRoot { .unwrap(); serde_json::from_slice(&res.0).unwrap() } - async fn list_files(&self) -> Vec { - vec![] +} + +#[Object] +impl QueryRoot { + async fn render_file( + &self, + ctx: &Context<'_>, + path: String, + renderer: Option, + ) -> serde_json::Value { + File { path } + .render(ctx, renderer) + .await + .unwrap() + } + async fn vaults(&self, ctx: &Context<'_>) -> Vec { + let runtime: &KasukuRuntime = ctx.data().unwrap(); + let mut lock = runtime.database.lock(); + let db = lock.as_mut(); + let database = db.unwrap(); + let res = database + .execute(format!("Select name, mount from vaults")) + .unwrap(); + let payload = res.get(0).unwrap(); + match payload { + Payload::Select { rows, .. } => rows + .into_iter() + .map(|row| Vault { + name: row.get(0).unwrap().into(), + mount: row.get(1).unwrap().into(), + }) + .collect(), + _ => unreachable!(), + } } async fn config(&self) -> u8 { 28 } } + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, async_graphql::SimpleObject)] +#[graphql(complex)] +pub struct Vault { + pub name: String, + pub mount: String, + // pub plugins: Vec, +} + +#[ComplexObject] +impl Vault { + async fn entries( + &self, + ctx: &Context<'_>, + offset: Option, + limit: Option, + ) -> Vec { + let runtime: &KasukuRuntime = ctx.data().unwrap(); + let mut lock = runtime.database.lock(); + let db = lock.as_mut(); + let database = db.unwrap(); + let res = database + .execute(format!( + "Select path from entries WHERE vault = '{}' OFFSET {} LIMIT {};", + self.name, + offset.unwrap_or(0), + limit.unwrap_or(0) + )) + .unwrap(); + let payload = res.get(0).unwrap(); + match payload { + Payload::Select { rows, .. } => rows + .into_iter() + .map(|row| File { + path: row.get(0).unwrap().into(), + }) + .collect(), + _ => unreachable!(), + } + } +} diff --git a/crates/database/src/lib.rs b/crates/database/src/lib.rs index 4a2e1fe..24bdb7e 100644 --- a/crates/database/src/lib.rs +++ b/crates/database/src/lib.rs @@ -1,12 +1,12 @@ use std::collections::HashMap; use std::io::ErrorKind; use std::ops::ControlFlow; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::Arc; use futures::StreamExt; -use gluesql_core::data::{Schema, Key}; +use gluesql_core::data::{Key, Schema}; use gluesql_core::error::Error; use gluesql_core::store::{ AlterTable, CustomFunction, CustomFunctionMut, DataRow, Index, IndexMut, Metadata, RowIter, @@ -24,11 +24,10 @@ use baildon::btree::Direction; type Result = std::result::Result; pub mod prelude { - pub use gluesql_core::prelude::*; pub use super::KasukuDatabase as Database; + pub use gluesql_core::prelude::*; } - pub struct KasukuDatabase { pub schemas: Baildon, config: BaildonConfig, @@ -43,7 +42,8 @@ pub(crate) struct BaildonConfig { } impl KasukuDatabase { - pub async fn new(path: &str) -> Result { + pub async fn new>(path: P) -> Result { + let path = path.as_ref(); // Create our path tokio::fs::create_dir_all(path) .await @@ -69,7 +69,7 @@ impl KasukuDatabase { .map_err(|e| Error::StorageMsg(e.to_string()))? { return Err(Error::StorageMsg(format!( - "database '{path}' already exists" + "database '{path:?}' already exists" ))); } let schemas: Baildon = Baildon::try_new(&canonical_path, 13) @@ -88,13 +88,13 @@ impl KasukuDatabase { }) } - pub async fn open(path: &str) -> Result { - let mut db_file = PathBuf::from(path); + pub async fn open>(path: P) -> Result { + let mut db_file = PathBuf::from(path.as_ref()); db_file.push("schema"); db_file.set_extension("db"); let schemas: Baildon = Baildon::try_open(&db_file) .await - .map_err(|e| Error::StorageMsg(e.to_string()))?; + .map_err(|e| Error::StorageMsg(format!("Failed to read schemas: {e}")))?; // let mut f_path = PathBuf::from(db_file); db_file.set_extension("cfg"); @@ -104,14 +104,14 @@ impl KasukuDatabase { .create(false) .open(&db_file) .await - .map_err(|e| Error::StorageMsg(e.to_string()))?; + .map_err(|e| Error::StorageMsg(format!("Failed to read cfg file: {e}")))?; let mut s_cfg = String::new(); let _ = file .read_to_string(&mut s_cfg) .await .map_err(|e| Error::StorageMsg(e.to_string()))?; - let config: BaildonConfig = - serde_json::from_str(&s_cfg).map_err(|e| Error::StorageMsg(e.to_string()))?; + let config: BaildonConfig = serde_json::from_str(&s_cfg) + .map_err(|e| Error::StorageMsg(format!("Failed to read config json: {e}")))?; Ok(KasukuDatabase { schemas, config, diff --git a/crates/types/src/config.rs b/crates/types/src/config.rs index 0c92431..98697e9 100644 --- a/crates/types/src/config.rs +++ b/crates/types/src/config.rs @@ -7,9 +7,8 @@ use std::fmt; use std::path::PathBuf; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] pub struct Config { - pub apps: BTreeMap, + pub vaults: BTreeMap, pub events: BTreeMap>, pub internals: Internals, #[serde(deserialize_with = "deserialize_plugins")] @@ -23,7 +22,6 @@ where struct PluginVisitor; #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] - #[serde(rename_all = "camelCase")] struct InnerPlugin { #[serde(skip_serializing_if = "Option::is_none")] pub headless: Option, @@ -61,21 +59,19 @@ where } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AppConfig { - pub mount: String, +pub struct VaultConfig { + pub mount: PathBuf, pub plugins: Vec, + pub file_types: Vec } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] pub struct Internals { #[serde(rename = "cache-path")] - pub cache: PathBuf, + pub database_path: PathBuf, } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] pub struct PluginConfig { pub name: String, pub headless: bool, @@ -84,7 +80,6 @@ pub struct PluginConfig { } #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] pub struct RemoteConfig { pub friendly_name: String, pub description: String, @@ -101,7 +96,7 @@ pub enum EventSource { impl Config { pub fn validate(&self) -> Result<(), String> { let mut dep_graph: DepGraph<&str> = DepGraph::new(); - for (title, app) in &self.apps { + for (title, app) in &self.vaults { dep_graph.register_dependencies(title, app.plugins.iter().map(|s| &**s).collect()) } // TODO: check validity of dependencies diff --git a/crates/types/src/database.rs b/crates/types/src/database.rs deleted file mode 100644 index 3e5bca7..0000000 --- a/crates/types/src/database.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::PluginData; - -pub struct Database; - -#[plugy::macros::context(data = PluginData)] -impl Database { - pub async fn query( - caller: &mut plugy::runtime::Caller<'_, plugy::runtime::Plugin>, - query: String, - ) -> Vec { - bincode::serialize("test").unwrap() - // let mut storage = caller.data(); - // match storage { - // Some(s) => { - // let storage = &s.plugin.data.storage; - // let fut = send_query(&storage, query).await; - // // let res = fut.await.unwrap(); - // vec![] - // } - // None => todo!(), - // } - } -} diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index 9ae3f4b..0500191 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -3,7 +3,6 @@ pub mod node; #[cfg(not(target_arch = "wasm32"))] use async_trait::async_trait; use hirola::prelude::EventListener; -use kasuku_database::prelude::parse; #[cfg(not(target_arch = "wasm32"))] use kasuku_database::{prelude::Glue, KasukuDatabase}; use node::Node; @@ -11,6 +10,7 @@ use node::Node; use oci_distribution::Client; pub use pulldown_cmark::{Alignment, Event as PulldownEvent, HeadingLevel, LinkType}; use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; +use sqlparser::{ast::Statement, dialect::PostgreSqlDialect, parser::Parser}; use std::{ collections::HashMap, fmt::{self}, @@ -155,6 +155,8 @@ pub enum Error { Serde(#[from] serde_error::Error), #[error("a render was requested but cannot be completed")] InvalidRender, + #[error("parse sql error")] + SqlParser, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -531,3 +533,9 @@ async fn pull_wasm(client: &mut Client, reference: &Reference) -> Vec { println!("Annotations: {:?}", image_content.annotations); image_content.data } + +const DIALECT: PostgreSqlDialect = PostgreSqlDialect {}; + +pub fn parse>(sql: Sql) -> Result, Error> { + Parser::parse_sql(&DIALECT, sql.as_ref()).map_err(|_e| Error::SqlParser) +} diff --git a/plugins/tasks/src/lib.rs b/plugins/tasks/src/lib.rs index 633c087..7930695 100644 --- a/plugins/tasks/src/lib.rs +++ b/plugins/tasks/src/lib.rs @@ -40,14 +40,13 @@ impl Plugin for Tasks { CodeBlockKind::Fenced("rust".to_owned()), ))); ctx.execute( - "CREATE TABLE Tasks ( + "CREATE TABLE IF NOT EXISTS tasks ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, completed INTEGER NOT NULL DEFAULT 0, due INTEGER, meta TEXT - ); - INSERT INTO Tasks(id, name) VALUES(1, 'Test Task');", + );", ); Ok(()) }