update
This commit is contained in:
parent
791349a2c4
commit
e0b7ebedac
12 changed files with 128 additions and 88 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -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]]
|
||||
|
|
|
@ -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!!
|
||||
|
|
|
@ -18,6 +18,7 @@ enum Command {
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
let opt = Opt::parse();
|
||||
|
||||
match opt.command {
|
||||
|
|
|
@ -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<PreviewType>("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 (
|
||||
<>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<NodeId> {
|
||||
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();
|
||||
|
|
|
@ -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<HashMap<String, Value>>,
|
||||
}
|
||||
|
||||
#[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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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
|
||||
},
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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()?;
|
||||
|
|
|
@ -2,6 +2,7 @@ use miette::Result;
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
panorama_daemon::run().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -80,7 +80,6 @@ pub async fn get_node(
|
|||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct UpdateData {
|
||||
title: Option<String>,
|
||||
extra_data: Option<ExtraData>,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue