diff --git a/Cargo.lock b/Cargo.lock index 25c2f64..6c5a134 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3630,6 +3630,7 @@ dependencies = [ "tauri-plugin-single-instance", "tauri-plugin-window-state", "tokio", + "tracing-subscriber", ] [[package]] @@ -3674,6 +3675,7 @@ dependencies = [ "tokio", "tower", "tower-http", + "tracing-subscriber", "utoipa", "utoipa-scalar", "utoipa-swagger-ui", @@ -6038,6 +6040,7 @@ dependencies = [ "pin-project-lite", "tower-layer", "tower-service", + "tracing", ] [[package]] diff --git a/app/src-tauri/Cargo.toml b/app/src-tauri/Cargo.toml index 5d1884b..67401e4 100644 --- a/app/src-tauri/Cargo.toml +++ b/app/src-tauri/Cargo.toml @@ -29,6 +29,7 @@ tauri-plugin-shell = "2.0.0-beta.7" tauri-plugin-single-instance = "2.0.0-beta.9" tauri-plugin-window-state = "2.0.0-beta" tokio = { version = "1.38.0", features = ["full"] } +tracing-subscriber = "0.3.18" [features] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! diff --git a/app/src-tauri/src/main.rs b/app/src-tauri/src/main.rs index 322f0b5..595c477 100644 --- a/app/src-tauri/src/main.rs +++ b/app/src-tauri/src/main.rs @@ -18,6 +18,7 @@ enum Command { #[tokio::main] async fn main() { + tracing_subscriber::fmt::init(); let opt = Opt::parse(); match opt.command { diff --git a/app/src/components/nodes/JournalPage.tsx b/app/src/components/nodes/JournalPage.tsx index 8e2ace0..20ae931 100644 --- a/app/src/components/nodes/JournalPage.tsx +++ b/app/src/components/nodes/JournalPage.tsx @@ -9,6 +9,7 @@ import { parse as parseDate, format as formatDate } from "date-fns"; import { useDebounce } from "use-debounce"; const JOURNAL_PAGE_CONTENT_FIELD_NAME = "panorama/journal/page/content"; +const JOURNAL_PAGE_TITLE_FIELD_NAME = "panorama/journal/page/title"; export interface JournalPageProps { id: string; @@ -33,44 +34,41 @@ export default function JournalPage({ id, data }: JournalPageProps) { const previous = usePrevious(valueToSave); const changed = valueToSave !== previous; const [mode, setMode] = useState("preview"); - const [title, setTitle] = useState(() => data.title); + const [title, setTitle] = useState( + () => data?.fields?.[JOURNAL_PAGE_TITLE_FIELD_NAME], + ); const [isEditingTitle, setIsEditingTitle] = useState(false); + const saveData = useCallback(async () => { + const extra_data = { + [JOURNAL_PAGE_TITLE_FIELD_NAME]: title, + [JOURNAL_PAGE_CONTENT_FIELD_NAME]: valueToSave, + }; + console.log("extra Data", extra_data); + const resp = await fetch(`http://localhost:5195/node/${id}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ extra_data }), + }); + const data = await resp.text(); + console.log("result", data); + }, [title, valueToSave, id]); + useEffect(() => { if (changed) { (async () => { - console.log("Saving..."); - const resp = await fetch(`http://localhost:5195/node/${id}`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - extra_data: { - "panorama/journal/page/content": valueToSave, - }, - }), - }); - const data = await resp.text(); - console.log("result", data); - + await saveData(); queryClient.invalidateQueries({ queryKey: ["fetchNode", id] }); })(); } - }, [id, changed, valueToSave, queryClient]); + }, [changed, queryClient, saveData]); const saveChangedTitle = useCallback(() => { (async () => { - const resp = await fetch(`http://localhost:5195/node/${id}`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ title: title }), - }); + await saveData(); setIsEditingTitle(false); })(); - }, [title, id]); + }, [saveData]); return ( <> diff --git a/crates/panorama-core/Cargo.toml b/crates/panorama-core/Cargo.toml index 44ca1eb..7ea96f1 100644 --- a/crates/panorama-core/Cargo.toml +++ b/crates/panorama-core/Cargo.toml @@ -10,7 +10,7 @@ chrono = { version = "0.4.38", features = ["serde"] } cozo = { version = "0.7.6", features = ["storage-rocksdb"] } futures = "0.3.30" itertools = "0.13.0" -miette = "5.5.0" +miette = { version = "5.5.0", features = ["fancy", "backtrace"] } serde = { version = "1.0.203", features = ["derive"] } serde_json = "1.0.117" sugars = "3.0.1" diff --git a/crates/panorama-core/src/migrations.rs b/crates/panorama-core/src/migrations.rs index 1566769..0ddf4a4 100644 --- a/crates/panorama-core/src/migrations.rs +++ b/crates/panorama-core/src/migrations.rs @@ -107,7 +107,6 @@ fn migration_01(db: &DbInstance) -> Result<()> { id: String => type: String, - title: String? default null, created_at: Float default now(), updated_at: Float default now(), extra_data: Json default {}, @@ -131,6 +130,8 @@ fn migration_01(db: &DbInstance) -> Result<()> { } { ?[key, relation, field_name, type, is_fts_enabled] <- [ + ['panorama/journal/page/day', 'journal_day', 'day', 'string', false], + ['panorama/journal/page/title', 'journal', 'title', 'string', true], ['panorama/journal/page/content', 'journal', 'content', 'string', true], ['panorama/mail/config/imap_hostname', 'mail_config', 'imap_hostname', 'string', false], ['panorama/mail/config/imap_port', 'mail_config', 'imap_port', 'int', false], @@ -138,13 +139,13 @@ fn migration_01(db: &DbInstance) -> Result<()> { ['panorama/mail/config/imap_password', 'mail_config', 'imap_password', 'string', false], ['panorama/mail/message/body', 'message', 'body', 'string', true], ['panorama/mail/message/subject', 'message', 'subject', 'string', true], - ['panorama/mail/message/message_id', 'message', 'message_id', 'string', true], + ['panorama/mail/message/message_id', 'message', 'message_id', 'string', false], ] :put fqkey_to_dbkey { key, relation, field_name, type, is_fts_enabled } } # Create journal type - { :create journal { node_id: String => content: String } } + { :create journal { node_id: String => title: String default '', content: String } } { :create journal_day { day: String => node_id: String } } # Mail diff --git a/crates/panorama-core/src/state/journal.rs b/crates/panorama-core/src/state/journal.rs index 61cf6bb..4bc4660 100644 --- a/crates/panorama-core/src/state/journal.rs +++ b/crates/panorama-core/src/state/journal.rs @@ -7,13 +7,15 @@ use uuid::Uuid; use crate::{AppState, NodeId}; +use super::node::CreateOrUpdate; + impl AppState { pub async fn get_todays_journal_id(&self) -> Result { let today = todays_date(); let result = self.db.run_script( " - ?[node_id] := *journal_day[day, node_id], day = $day + ?[node_id] := *journal_day{day, node_id}, day = $day ", btmap! { "day".to_owned() => today.clone().into(), @@ -24,33 +26,46 @@ impl AppState { // TODO: Do this check on the server side if result.rows.len() == 0 { // Insert a new one - let uuid = Uuid::now_v7(); - let node_id = uuid.to_string(); + // let uuid = Uuid::now_v7(); + // let node_id = uuid.to_string(); - self.db.run_script( - " - { - ?[id, title, type] <- [[$node_id, $title, 'panorama/journal/page']] - :put node { id, title, type } - } - { - ?[node_id, content] <- [[$node_id, '']] - :put journal { node_id => content } - } - { - ?[day, node_id] <- [[$day, $node_id]] - :put journal_day { day => node_id } - } - ", - btmap! { - "node_id".to_owned() => node_id.clone().into(), - "day".to_owned() => today.clone().into(), - "title".to_owned() => today.clone().into(), - }, - ScriptMutability::Mutable, - )?; + let node_info = self + .create_or_update_node( + CreateOrUpdate::Create { + r#type: "panorama/journal/page".to_owned(), + }, + Some(btmap! { + "panorama/journal/page/day".to_owned() => today.clone().into(), + "panorama/journal/page/content".to_owned() => "".to_owned().into(), + "panorama/journal/page/title".to_owned() => today.clone().into(), + }), + ) + .await?; - return Ok(NodeId(uuid)); + // self.db.run_script( + // " + // { + // ?[id, type] <- [[$node_id, 'panorama/journal/page']] + // :put node { id, type } + // } + // { + // ?[node_id, title, content] <- [[$node_id, $title, '']] + // :put journal { node_id => title, content } + // } + // { + // ?[day, node_id] <- [[$day, $node_id]] + // :put journal_day { day => node_id } + // } + // ", + // btmap! { + // "node_id".to_owned() => node_id.clone().into(), + // "day".to_owned() => today.clone().into(), + // "title".to_owned() => today.clone().into(), + // }, + // ScriptMutability::Mutable, + // )?; + + return Ok(node_info.node_id); } let node_id = result.rows[0][0].get_str().unwrap(); diff --git a/crates/panorama-core/src/state/node.rs b/crates/panorama-core/src/state/node.rs index 5067a75..230a130 100644 --- a/crates/panorama-core/src/state/node.rs +++ b/crates/panorama-core/src/state/node.rs @@ -12,7 +12,7 @@ use tantivy::{ collector::TopDocs, query::QueryParser, schema::{OwnedValue, Value as _}, - Document, TantivyDocument, + Document, TantivyDocument, Term, }; use uuid::Uuid; @@ -30,6 +30,7 @@ pub struct NodeInfo { pub fields: Option>, } +#[derive(Debug)] pub struct FieldInfo { pub relation_name: String, pub relation_field: String, @@ -157,6 +158,7 @@ impl AppState { } } +#[derive(Debug)] pub enum CreateOrUpdate { Create { r#type: String }, Update { node_id: NodeId }, @@ -175,10 +177,17 @@ impl AppState { }; let node_id = node_id.to_string(); + let action = match opts { + CreateOrUpdate::Create { .. } => "put", + CreateOrUpdate::Update { .. } => "update", + }; + + println!("Request: {opts:?} {extra_data:?}"); + let tx = self.db.multi_transaction(true); let (created_at, updated_at) = match opts { - CreateOrUpdate::Create { r#type } => { + CreateOrUpdate::Create { ref r#type } => { let node_result = tx.run_script( " ?[id, type] <- [[$node_id, $type]] @@ -187,16 +196,15 @@ impl AppState { ", btmap! { "node_id".to_owned() => DataValue::from(node_id.clone()), - "type".to_owned() => DataValue::from(r#type), + "type".to_owned() => DataValue::from(r#type.to_owned()), }, )?; - println!("ROWS(1): {:?}", node_result); let created_at = DateTime::from_timestamp_millis( - (node_result.rows[0][4].get_float().unwrap() * 1000.0) as i64, + (node_result.rows[0][3].get_float().unwrap() * 1000.0) as i64, ) .unwrap(); let updated_at = DateTime::from_timestamp_millis( - (node_result.rows[0][5].get_float().unwrap() * 1000.0) as i64, + (node_result.rows[0][4].get_float().unwrap() * 1000.0) as i64, ) .unwrap(); (created_at, updated_at) @@ -211,7 +219,6 @@ impl AppState { "node_id".to_owned() => DataValue::from(node_id.clone()), }, )?; - println!("ROWS(2): {:?}", node_result); let created_at = DateTime::from_timestamp_millis( (node_result.rows[0][2].get_float().unwrap() * 1000.0) as i64, ) @@ -230,6 +237,7 @@ impl AppState { .get_by_left("node_id") .unwrap() .clone(); + if !extra_data.is_empty() { let keys = extra_data.keys().map(|s| s.to_owned()).collect::>(); let field_mapping = @@ -279,6 +287,11 @@ impl AppState { let mut writer = self.tantivy_index.writer(15_000_000).into_diagnostic()?; + + let delete_term = + Term::from_field_text(node_id_field.clone(), &node_id); + writer.delete_term(delete_term); + writer.add_document(doc).into_diagnostic()?; writer.commit().into_diagnostic()?; drop(writer); @@ -286,27 +299,27 @@ impl AppState { let keys = fields_mapping.keys().collect::>(); let keys_joined = keys.iter().join(", "); - let query = format!( - " + if !keys.is_empty() { + let query = format!( + " ?[ node_id, {keys_joined} ] <- [$input_data] - :put {relation} {{ node_id, {keys_joined} }} + :{action} {relation} {{ node_id, {keys_joined} }} " - ); + ); - let mut params = vec![]; - params.push(DataValue::from(node_id.clone())); - for key in keys { - params.push(fields_mapping[key].clone()); + let mut params = vec![]; + params.push(DataValue::from(node_id.clone())); + for key in keys { + params.push(fields_mapping[key].clone()); + } + + let result = tx.run_script( + &query, + btmap! { + "input_data".to_owned() => DataValue::List(params), + }, + ); } - - println!("Query: {:?} \n {:?}", query, params); - - let result = tx.run_script( - &query, - btmap! { - "input_data".to_owned() => DataValue::List(params), - }, - )?; } let input = DataValue::List( @@ -320,11 +333,12 @@ impl AppState { }) .collect_vec(), ); + tx.run_script( " - ?[key, id] <- $input_data - :put node_has_key { key, id } - ", + ?[key, id] <- $input_data + :put node_has_key { key, id } + ", btmap! { "input_data".to_owned() => input }, diff --git a/crates/panorama-daemon/Cargo.toml b/crates/panorama-daemon/Cargo.toml index 5b2e9c6..50f8390 100644 --- a/crates/panorama-daemon/Cargo.toml +++ b/crates/panorama-daemon/Cargo.toml @@ -14,7 +14,7 @@ csv = "1.3.0" dirs = "5.0.1" futures = "0.3.30" itertools = "0.13.0" -miette = "5.5.0" +miette = { version = "5.5.0", features = ["fancy", "backtrace"] } panorama-core = { path = "../panorama-core" } serde = { version = "1.0.202", features = ["derive"] } serde_json = "1.0.117" @@ -22,7 +22,8 @@ sugars = "3.0.1" tantivy = { version = "0.22.0", features = ["zstd"] } tokio = { version = "1.37.0", features = ["full"] } tower = "0.4.13" -tower-http = { version = "0.5.2", features = ["cors"] } +tower-http = { version = "0.5.2", features = ["cors", "trace"] } +tracing-subscriber = "0.3.18" uuid = { version = "1.8.0", features = ["v7"] } [dependencies.utoipa] diff --git a/crates/panorama-daemon/src/lib.rs b/crates/panorama-daemon/src/lib.rs index 0e067ba..3ceaa86 100644 --- a/crates/panorama-daemon/src/lib.rs +++ b/crates/panorama-daemon/src/lib.rs @@ -20,7 +20,10 @@ use miette::{IntoDiagnostic, Result}; use panorama_core::AppState; use tokio::net::TcpListener; use tower::ServiceBuilder; -use tower_http::cors::{self, CorsLayer}; +use tower_http::{ + cors::{self, CorsLayer}, + trace::TraceLayer, +}; use utoipa::OpenApi; use utoipa_scalar::{Scalar, Servable}; @@ -47,11 +50,13 @@ pub async fn run() -> Result<()> { let state = AppState::new(&panorama_dir).await?; - let cors = CorsLayer::new() + let cors_layer = CorsLayer::new() .allow_methods([Method::GET, Method::POST, Method::PUT]) .allow_headers(cors::Any) .allow_origin(cors::Any); + let trace_layer = TraceLayer::new_for_http(); + // build our application with a single route let app = Router::new() .merge(Scalar::with_url("/api/docs", ApiDoc::openapi())) @@ -61,7 +66,8 @@ pub async fn run() -> Result<()> { .nest("/journal", journal::router().with_state(state.clone())) .route("/mail/config", get(get_mail_config)) .route("/mail", get(get_mail)) - .layer(ServiceBuilder::new().layer(cors)) + .layer(ServiceBuilder::new().layer(cors_layer)) + .layer(ServiceBuilder::new().layer(trace_layer)) .with_state(state.clone()); let listener = TcpListener::bind("0.0.0.0:5195").await.into_diagnostic()?; diff --git a/crates/panorama-daemon/src/main.rs b/crates/panorama-daemon/src/main.rs index f2aadcf..8d99e44 100644 --- a/crates/panorama-daemon/src/main.rs +++ b/crates/panorama-daemon/src/main.rs @@ -2,6 +2,7 @@ use miette::Result; #[tokio::main] async fn main() -> Result<()> { + tracing_subscriber::fmt::init(); panorama_daemon::run().await?; Ok(()) } diff --git a/crates/panorama-daemon/src/node.rs b/crates/panorama-daemon/src/node.rs index 0347649..c688033 100644 --- a/crates/panorama-daemon/src/node.rs +++ b/crates/panorama-daemon/src/node.rs @@ -80,7 +80,6 @@ pub async fn get_node( #[derive(Deserialize, Debug)] pub struct UpdateData { - title: Option, extra_data: Option, }