From 7fc3917134145dfc5ac3a5de210b30ad962573cf Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Mon, 10 Jun 2024 15:52:13 -0400 Subject: [PATCH] journal --- .gitignore | 3 +- crates/panorama-core/src/state/journal.rs | 62 +++++++++- crates/panorama-core/src/state/mail.rs | 18 +-- crates/panorama-core/src/state/mod.rs | 1 - crates/panorama-core/src/state/node.rs | 23 +++- crates/panorama-daemon/src/journal.rs | 56 +-------- crates/panorama-daemon/src/main.rs | 7 +- crates/panorama-daemon/src/node.rs | 135 +++------------------- 8 files changed, 111 insertions(+), 194 deletions(-) diff --git a/.gitignore b/.gitignore index 85ea769..a19807c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules dist target -.DS_Store \ No newline at end of file +.DS_Store +/export \ No newline at end of file diff --git a/crates/panorama-core/src/state/journal.rs b/crates/panorama-core/src/state/journal.rs index 520545a..5008084 100644 --- a/crates/panorama-core/src/state/journal.rs +++ b/crates/panorama-core/src/state/journal.rs @@ -1,9 +1,65 @@ -use miette::Result; +use std::str::FromStr; + +use chrono::Local; +use cozo::ScriptMutability; +use miette::{IntoDiagnostic, Result}; +use uuid::Uuid; use crate::{AppState, NodeId}; impl AppState { - pub fn get_todays_journal_id(&self) -> Result { - todo!() + 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 + ", + btmap! { + "day".to_owned() => today.clone().into(), + }, + ScriptMutability::Immutable, + )?; + + // 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(); + + 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, + )?; + + return Ok(NodeId(uuid)); + } + + let node_id = result.rows[0][0].get_str().unwrap(); + Ok(NodeId(Uuid::from_str(node_id).into_diagnostic()?)) } } + +fn todays_date() -> String { + let now = Local::now(); + let date = now.date_naive(); + date.format("%Y-%m-%d").to_string() +} diff --git a/crates/panorama-core/src/state/mail.rs b/crates/panorama-core/src/state/mail.rs index f7dbc2f..6a27ab8 100644 --- a/crates/panorama-core/src/state/mail.rs +++ b/crates/panorama-core/src/state/mail.rs @@ -182,15 +182,15 @@ impl AppState { ); self.db.run_script( - " - ?[node_id, account_node_id, mailbox_node_id, subject, headers, body, internal_date] <- $input_data - :put message { node_id, account_node_id, mailbox_node_id, subject, headers, body, internal_date } - ", - btmap! { - "input_data".to_owned() => input_data, - }, - ScriptMutability::Mutable, - )?; + " + ?[node_id, account_node_id, mailbox_node_id, subject, headers, body, internal_date] <- $input_data + :put message { node_id, account_node_id, mailbox_node_id, subject, headers, body, internal_date } + ", + btmap! { + "input_data".to_owned() => input_data, + }, + ScriptMutability::Mutable, + )?; session.logout().await.into_diagnostic()?; diff --git a/crates/panorama-core/src/state/mod.rs b/crates/panorama-core/src/state/mod.rs index bd604a1..6fd763e 100644 --- a/crates/panorama-core/src/state/mod.rs +++ b/crates/panorama-core/src/state/mod.rs @@ -7,7 +7,6 @@ use std::{collections::HashMap, fs, path::Path}; use cozo::DbInstance; use miette::{IntoDiagnostic, Result}; -use node::FieldMapping; use tantivy::{ directory::MmapDirectory, schema::{Field, Schema, STORED, STRING, TEXT}, diff --git a/crates/panorama-core/src/state/node.rs b/crates/panorama-core/src/state/node.rs index 39a4671..d931775 100644 --- a/crates/panorama-core/src/state/node.rs +++ b/crates/panorama-core/src/state/node.rs @@ -1,17 +1,15 @@ use std::{ collections::{BTreeMap, HashMap}, - fmt::write, str::FromStr, }; use chrono::{DateTime, Utc}; -use cozo::{DataValue, MultiTransaction, NamedRows, ScriptMutability}; +use cozo::{DataValue, MultiTransaction, NamedRows}; use itertools::Itertools; -use miette::{IntoDiagnostic, Result}; +use miette::{bail, IntoDiagnostic, Result}; use serde_json::Value; use tantivy::{ collector::TopDocs, - doc, query::QueryParser, schema::{OwnedValue, Value as _}, Document, TantivyDocument, @@ -122,6 +120,10 @@ impl AppState { btmap! { "node_id".to_owned() => node_id.to_string().into(), }, )?; + if result.rows.is_empty() { + bail!("Not found") + } + let created_at = DateTime::from_timestamp_millis( (result.rows[0][2].get_float().unwrap() * 1000.0) as i64, ) @@ -248,6 +250,8 @@ impl AppState { params.push(fields_mapping[key].clone()); } + println!("Query: {:?} \n {:?}", query, params); + let result = tx.run_script( &query, btmap! { @@ -334,11 +338,22 @@ impl AppState { .unwrap() .as_str() .unwrap(); + let all_fields = retrieved_doc.get_sorted_field_values(); let node_id = NodeId(Uuid::from_str(node_id).unwrap()); + let fields = all_fields + .into_iter() + .map(|(field, value)| { + ( + serde_json::to_string(&field).unwrap(), + serde_json::to_string(&value).unwrap(), + ) + }) + .collect::>(); ( node_id, json!({ "score": score, + "fields": fields, }), ) }) diff --git a/crates/panorama-daemon/src/journal.rs b/crates/panorama-daemon/src/journal.rs index 7bccbf4..8dd5ede 100644 --- a/crates/panorama-daemon/src/journal.rs +++ b/crates/panorama-daemon/src/journal.rs @@ -27,61 +27,9 @@ pub(super) fn router() -> Router { pub async fn get_todays_journal_id( State(state): State, ) -> AppResult> { - let today = todays_date(); + let node_id = state.get_todays_journal_id().await?; - let result = state.db.run_script( - " - ?[node_id] := *journal_day[day, node_id], day = $day - ", - btmap! { - "day".to_owned() => today.clone().into(), - }, - ScriptMutability::Immutable, - )?; - - // 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(); - - state.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, - )?; - - return Ok(Json(json!({ - "node_id": node_id - }))); - } - - let node_id = result.rows[0][0].get_str().unwrap(); Ok(Json(json!({ - "node_id": node_id, - "day": today, + "node_id": node_id.to_string(), }))) } - -fn todays_date() -> String { - let now = Local::now(); - let date = now.date_naive(); - date.format("%Y-%m-%d").to_string() -} diff --git a/crates/panorama-daemon/src/main.rs b/crates/panorama-daemon/src/main.rs index 4fb69cf..3bbdb41 100644 --- a/crates/panorama-daemon/src/main.rs +++ b/crates/panorama-daemon/src/main.rs @@ -15,11 +15,7 @@ mod node; use std::fs; -use axum::{ - http::Method, - routing::{get, put}, - Router, -}; +use axum::{http::Method, routing::get, Router}; use miette::{IntoDiagnostic, Result}; use panorama_core::AppState; use tokio::net::TcpListener; @@ -62,7 +58,6 @@ async fn main() -> Result<()> { .merge(Scalar::with_url("/api/docs", ApiDoc::openapi())) .route("/", get(|| async { "Hello, World!" })) .route("/export", get(export)) - .route("/node/search", get(search_nodes)) .nest("/node", node::router().with_state(state.clone())) .nest("/journal", journal::router().with_state(state.clone())) .route("/mail/config", get(get_mail_config)) diff --git a/crates/panorama-daemon/src/node.rs b/crates/panorama-daemon/src/node.rs index ed032c6..d85b8ae 100644 --- a/crates/panorama-daemon/src/node.rs +++ b/crates/panorama-daemon/src/node.rs @@ -7,12 +7,10 @@ use axum::{ Json, Router, }; use chrono::{DateTime, Utc}; -use cozo::{DataValue, MultiTransaction, ScriptMutability}; -use itertools::Itertools; +use cozo::{DataValue, MultiTransaction}; use panorama_core::state::node::ExtraData; use serde_json::Value; use utoipa::{OpenApi, ToSchema}; -use uuid::Uuid; use crate::{error::AppResult, AppState}; @@ -26,9 +24,10 @@ pub(super) struct NodeApi; pub(super) fn router() -> Router { Router::new() + .route("/", put(create_node)) .route("/:id", get(get_node)) .route("/:id", post(update_node)) - .route("/", put(create_node)) + .route("/search", get(search_nodes)) } #[derive(Serialize, Deserialize, ToSchema, Clone)] @@ -103,10 +102,10 @@ pub async fn update_node( tx.run_script( " - # Always update the time - ?[ id, title ] <- [[ $node_id, $title ]] - :update node { id, title } - ", + # Always update the time + ?[ id, title ] <- [[ $node_id, $title ]] + :update node { id, title } + ", btmap! { "node_id".to_owned() => node_id_data.clone(), "title".to_owned() => title, @@ -168,83 +167,18 @@ pub struct CreateNodeOpts { #[utoipa::path( put, path = "/", - responses( - (status = 200) - ), - params( - ("id" = String, Path, description = "Node ID"), - ) + responses((status = 200)), )] pub async fn create_node( State(state): State, Json(opts): Json, ) -> AppResult> { - let node_id = Uuid::now_v7(); - let node_id = node_id.to_string(); - - let tx = state.db.multi_transaction(true); - - let result = tx.run_script( - " - ?[id, type] <- [[$node_id, $type]] - :put node { id, type } - ", - btmap! { - "node_id".to_owned() => DataValue::from(node_id.clone()), - "type".to_owned() => DataValue::from(opts.ty), - }, - ); - - if let Some(extra_data) = opts.extra_data { - let result = get_rows_for_extra_keys(&tx, &extra_data)?; - - let result_by_relation = result - .iter() - .into_group_map_by(|(key, (relation, field_name, ty))| relation); - - for (relation, fields) in result_by_relation.iter() { - let fields_mapping = fields - .into_iter() - .map(|(key, (_, field_name, ty))| { - let new_value = extra_data.get(*key).unwrap(); - // TODO: Make this more generic - let new_value = match ty.as_str() { - "int" => DataValue::from(new_value.as_i64().unwrap()), - _ => DataValue::from(new_value.as_str().unwrap()), - }; - (field_name.to_owned(), new_value) - }) - .collect::>(); - - let keys = fields_mapping.keys().collect::>(); - let keys_joined = keys.iter().join(", "); - - let query = format!( - " - ?[ node_id, {keys_joined} ] <- [$input_data] - :insert {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 result = tx.run_script( - &query, - btmap! { - "input_data".to_owned() => DataValue::List(params), - }, - )?; - } - } - - tx.commit()?; + let node_info = state + .create_or_update_node(opts.ty, opts.extra_data) + .await?; Ok(Json(json!({ - "node_id": node_id, + "node_id": node_info.node_id.to_string(), }))) } @@ -253,50 +187,19 @@ pub struct SearchQuery { query: String, } +#[utoipa::path( + get, + path = "/search", + responses((status = 200)), +)] pub async fn search_nodes( State(state): State, Query(query): Query, ) -> AppResult> { - // TODO: This is temporary, there may be more ways to search so tacking on * - // at the end may destroy some queries - let query = format!("{}*", query.query); - - let results = state.db.run_script( - " - results[node_id, content, score] := ~journal:text_index {node_id, content, | - query: $q, - k: 10, - score_kind: 'tf_idf', - bind_score: score - } - - ?[node_id, content, title, score] := - results[node_id, content, score], - *node{ id: node_id, title } - - :order -score - ", - btmap! { - "q".to_owned() => DataValue::from(query), - }, - ScriptMutability::Immutable, - )?; - - let results = results - .rows - .into_iter() - .map(|row| { - json!({ - "node_id": row[0].get_str().unwrap(), - "content": row[1].get_str().unwrap(), - "title": row[2].get_str().unwrap(), - "score": row[3].get_float().unwrap(), - }) - }) - .collect::>(); + let search_result = state.search_nodes(query.query).await?; Ok(Json(json!({ - "results": results + "results": search_result }))) }