journal
This commit is contained in:
parent
9ea6ee5248
commit
7fc3917134
8 changed files with 111 additions and 194 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
target
|
target
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
/export
|
|
@ -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};
|
use crate::{AppState, NodeId};
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
pub fn get_todays_journal_id(&self) -> Result<NodeId> {
|
pub async fn get_todays_journal_id(&self) -> Result<NodeId> {
|
||||||
todo!()
|
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()
|
||||||
|
}
|
||||||
|
|
|
@ -182,15 +182,15 @@ impl AppState {
|
||||||
);
|
);
|
||||||
|
|
||||||
self.db.run_script(
|
self.db.run_script(
|
||||||
"
|
"
|
||||||
?[node_id, account_node_id, mailbox_node_id, subject, headers, body, internal_date] <- $input_data
|
?[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 }
|
:put message { node_id, account_node_id, mailbox_node_id, subject, headers, body, internal_date }
|
||||||
",
|
",
|
||||||
btmap! {
|
btmap! {
|
||||||
"input_data".to_owned() => input_data,
|
"input_data".to_owned() => input_data,
|
||||||
},
|
},
|
||||||
ScriptMutability::Mutable,
|
ScriptMutability::Mutable,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
session.logout().await.into_diagnostic()?;
|
session.logout().await.into_diagnostic()?;
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ use std::{collections::HashMap, fs, path::Path};
|
||||||
|
|
||||||
use cozo::DbInstance;
|
use cozo::DbInstance;
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use node::FieldMapping;
|
|
||||||
use tantivy::{
|
use tantivy::{
|
||||||
directory::MmapDirectory,
|
directory::MmapDirectory,
|
||||||
schema::{Field, Schema, STORED, STRING, TEXT},
|
schema::{Field, Schema, STORED, STRING, TEXT},
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, HashMap},
|
collections::{BTreeMap, HashMap},
|
||||||
fmt::write,
|
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use cozo::{DataValue, MultiTransaction, NamedRows, ScriptMutability};
|
use cozo::{DataValue, MultiTransaction, NamedRows};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{bail, IntoDiagnostic, Result};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tantivy::{
|
use tantivy::{
|
||||||
collector::TopDocs,
|
collector::TopDocs,
|
||||||
doc,
|
|
||||||
query::QueryParser,
|
query::QueryParser,
|
||||||
schema::{OwnedValue, Value as _},
|
schema::{OwnedValue, Value as _},
|
||||||
Document, TantivyDocument,
|
Document, TantivyDocument,
|
||||||
|
@ -122,6 +120,10 @@ impl AppState {
|
||||||
btmap! { "node_id".to_owned() => node_id.to_string().into(), },
|
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(
|
let created_at = DateTime::from_timestamp_millis(
|
||||||
(result.rows[0][2].get_float().unwrap() * 1000.0) as i64,
|
(result.rows[0][2].get_float().unwrap() * 1000.0) as i64,
|
||||||
)
|
)
|
||||||
|
@ -248,6 +250,8 @@ impl AppState {
|
||||||
params.push(fields_mapping[key].clone());
|
params.push(fields_mapping[key].clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("Query: {:?} \n {:?}", query, params);
|
||||||
|
|
||||||
let result = tx.run_script(
|
let result = tx.run_script(
|
||||||
&query,
|
&query,
|
||||||
btmap! {
|
btmap! {
|
||||||
|
@ -334,11 +338,22 @@ impl AppState {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_str()
|
.as_str()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let all_fields = retrieved_doc.get_sorted_field_values();
|
||||||
let node_id = NodeId(Uuid::from_str(node_id).unwrap());
|
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::<HashMap<_, _>>();
|
||||||
(
|
(
|
||||||
node_id,
|
node_id,
|
||||||
json!({
|
json!({
|
||||||
"score": score,
|
"score": score,
|
||||||
|
"fields": fields,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -27,61 +27,9 @@ pub(super) fn router() -> Router<AppState> {
|
||||||
pub async fn get_todays_journal_id(
|
pub async fn get_todays_journal_id(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
) -> AppResult<Json<Value>> {
|
) -> AppResult<Json<Value>> {
|
||||||
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!({
|
Ok(Json(json!({
|
||||||
"node_id": node_id,
|
"node_id": node_id.to_string(),
|
||||||
"day": today,
|
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn todays_date() -> String {
|
|
||||||
let now = Local::now();
|
|
||||||
let date = now.date_naive();
|
|
||||||
date.format("%Y-%m-%d").to_string()
|
|
||||||
}
|
|
||||||
|
|
|
@ -15,11 +15,7 @@ mod node;
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use axum::{
|
use axum::{http::Method, routing::get, Router};
|
||||||
http::Method,
|
|
||||||
routing::{get, put},
|
|
||||||
Router,
|
|
||||||
};
|
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use panorama_core::AppState;
|
use panorama_core::AppState;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
@ -62,7 +58,6 @@ async fn main() -> Result<()> {
|
||||||
.merge(Scalar::with_url("/api/docs", ApiDoc::openapi()))
|
.merge(Scalar::with_url("/api/docs", ApiDoc::openapi()))
|
||||||
.route("/", get(|| async { "Hello, World!" }))
|
.route("/", get(|| async { "Hello, World!" }))
|
||||||
.route("/export", get(export))
|
.route("/export", get(export))
|
||||||
.route("/node/search", get(search_nodes))
|
|
||||||
.nest("/node", node::router().with_state(state.clone()))
|
.nest("/node", node::router().with_state(state.clone()))
|
||||||
.nest("/journal", journal::router().with_state(state.clone()))
|
.nest("/journal", journal::router().with_state(state.clone()))
|
||||||
.route("/mail/config", get(get_mail_config))
|
.route("/mail/config", get(get_mail_config))
|
||||||
|
|
|
@ -7,12 +7,10 @@ use axum::{
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use cozo::{DataValue, MultiTransaction, ScriptMutability};
|
use cozo::{DataValue, MultiTransaction};
|
||||||
use itertools::Itertools;
|
|
||||||
use panorama_core::state::node::ExtraData;
|
use panorama_core::state::node::ExtraData;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use utoipa::{OpenApi, ToSchema};
|
use utoipa::{OpenApi, ToSchema};
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::{error::AppResult, AppState};
|
use crate::{error::AppResult, AppState};
|
||||||
|
|
||||||
|
@ -26,9 +24,10 @@ pub(super) struct NodeApi;
|
||||||
|
|
||||||
pub(super) fn router() -> Router<AppState> {
|
pub(super) fn router() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
|
.route("/", put(create_node))
|
||||||
.route("/:id", get(get_node))
|
.route("/:id", get(get_node))
|
||||||
.route("/:id", post(update_node))
|
.route("/:id", post(update_node))
|
||||||
.route("/", put(create_node))
|
.route("/search", get(search_nodes))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, ToSchema, Clone)]
|
#[derive(Serialize, Deserialize, ToSchema, Clone)]
|
||||||
|
@ -103,10 +102,10 @@ pub async fn update_node(
|
||||||
|
|
||||||
tx.run_script(
|
tx.run_script(
|
||||||
"
|
"
|
||||||
# Always update the time
|
# Always update the time
|
||||||
?[ id, title ] <- [[ $node_id, $title ]]
|
?[ id, title ] <- [[ $node_id, $title ]]
|
||||||
:update node { id, title }
|
:update node { id, title }
|
||||||
",
|
",
|
||||||
btmap! {
|
btmap! {
|
||||||
"node_id".to_owned() => node_id_data.clone(),
|
"node_id".to_owned() => node_id_data.clone(),
|
||||||
"title".to_owned() => title,
|
"title".to_owned() => title,
|
||||||
|
@ -168,83 +167,18 @@ pub struct CreateNodeOpts {
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
put,
|
put,
|
||||||
path = "/",
|
path = "/",
|
||||||
responses(
|
responses((status = 200)),
|
||||||
(status = 200)
|
|
||||||
),
|
|
||||||
params(
|
|
||||||
("id" = String, Path, description = "Node ID"),
|
|
||||||
)
|
|
||||||
)]
|
)]
|
||||||
pub async fn create_node(
|
pub async fn create_node(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(opts): Json<CreateNodeOpts>,
|
Json(opts): Json<CreateNodeOpts>,
|
||||||
) -> AppResult<Json<Value>> {
|
) -> AppResult<Json<Value>> {
|
||||||
let node_id = Uuid::now_v7();
|
let node_info = state
|
||||||
let node_id = node_id.to_string();
|
.create_or_update_node(opts.ty, opts.extra_data)
|
||||||
|
.await?;
|
||||||
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::<BTreeMap<_, _>>();
|
|
||||||
|
|
||||||
let keys = fields_mapping.keys().collect::<Vec<_>>();
|
|
||||||
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()?;
|
|
||||||
|
|
||||||
Ok(Json(json!({
|
Ok(Json(json!({
|
||||||
"node_id": node_id,
|
"node_id": node_info.node_id.to_string(),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,50 +187,19 @@ pub struct SearchQuery {
|
||||||
query: String,
|
query: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/search",
|
||||||
|
responses((status = 200)),
|
||||||
|
)]
|
||||||
pub async fn search_nodes(
|
pub async fn search_nodes(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Query(query): Query<SearchQuery>,
|
Query(query): Query<SearchQuery>,
|
||||||
) -> AppResult<Json<Value>> {
|
) -> AppResult<Json<Value>> {
|
||||||
// TODO: This is temporary, there may be more ways to search so tacking on *
|
let search_result = state.search_nodes(query.query).await?;
|
||||||
// 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::<Vec<_>>();
|
|
||||||
|
|
||||||
Ok(Json(json!({
|
Ok(Json(json!({
|
||||||
"results": results
|
"results": search_result
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue