journal
This commit is contained in:
parent
9ea6ee5248
commit
7fc3917134
8 changed files with 111 additions and 194 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@ node_modules
|
|||
dist
|
||||
target
|
||||
.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};
|
||||
|
||||
impl AppState {
|
||||
pub fn get_todays_journal_id(&self) -> Result<NodeId> {
|
||||
todo!()
|
||||
pub async fn get_todays_journal_id(&self) -> Result<NodeId> {
|
||||
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(
|
||||
"
|
||||
?[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()?;
|
||||
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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::<HashMap<_, _>>();
|
||||
(
|
||||
node_id,
|
||||
json!({
|
||||
"score": score,
|
||||
"fields": fields,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
|
@ -27,61 +27,9 @@ pub(super) fn router() -> Router<AppState> {
|
|||
pub async fn get_todays_journal_id(
|
||||
State(state): State<AppState>,
|
||||
) -> 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!({
|
||||
"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()
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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<AppState> {
|
||||
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<AppState>,
|
||||
Json(opts): Json<CreateNodeOpts>,
|
||||
) -> AppResult<Json<Value>> {
|
||||
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::<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()?;
|
||||
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<AppState>,
|
||||
Query(query): Query<SearchQuery>,
|
||||
) -> AppResult<Json<Value>> {
|
||||
// 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::<Vec<_>>();
|
||||
let search_result = state.search_nodes(query.query).await?;
|
||||
|
||||
Ok(Json(json!({
|
||||
"results": results
|
||||
"results": search_result
|
||||
})))
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue