use std::collections::HashMap; use axum::{ extract::{Path, State}, http::StatusCode, Json, }; use cozo::{DataValue, ScriptMutability, Vector}; use serde_json::Value; use crate::{error::AppResult, AppState}; pub async fn get_node( State(state): State, Path(node_id): Path, ) -> AppResult<(StatusCode, Json)> { let result = state.db.run_script( " j[content] := *journal{ node_id, content }, node_id = $node_id j[content] := not *journal{ node_id }, node_id = $node_id, content = null jd[day] := *journal_day{ node_id, day }, node_id = $node_id jd[day] := not *journal_day{ node_id }, node_id = $node_id, day = null ?[ extra_data, content, day, created_at, updated_at, type, title ] := *node{ id, type, title, created_at, updated_at, extra_data }, j[content], jd[day], id = $node_id :limit 1 ", btmap! {"node_id".to_owned() => node_id.clone().into()}, ScriptMutability::Immutable, )?; if result.rows.len() == 0 { return Ok((StatusCode::NOT_FOUND, Json(json!(null)))); } let row = &result.rows[0]; let extra_data = row[0].get_str(); let day = row[2].get_str(); Ok(( StatusCode::OK, Json(json!({ "node": node_id, "extra_data": extra_data, "content": row[1].get_str(), "day": day, "created_at": row[3].get_float(), "updated_at": row[4].get_float(), "type": row[5].get_str(), "title": row[6].get_str(), })), )) } #[derive(Deserialize, Debug)] pub struct UpdateData { title: Option, extra_data: Option>, } pub async fn update_node( State(state): State, Path(node_id): Path, Json(update_data): Json, ) -> AppResult> { println!("Update data: {:?}", update_data); let node_id_data = DataValue::from(node_id.clone()); // TODO: Combine these into the same script let tx = state.db.multi_transaction(true); if let Some(title) = update_data.title { let title = DataValue::from(title); tx.run_script( " # 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, }, )?; } if let Some(extra_data) = update_data.extra_data { let result = tx.run_script( " ?[key, relation, field_name, type] := *fqkey_to_dbkey{key, relation, field_name, type}, is_in(key, $keys) ", btmap! { "keys".to_owned() => DataValue::List( extra_data .keys() .map(|s| DataValue::from(s.as_str())) .collect::>() ), }, )?; let s = |s: &DataValue| s.get_str().unwrap().to_owned(); let result = result .rows .into_iter() .map(|row| (s(&row[0]), (s(&row[1]), s(&row[2]), s(&row[3])))) .collect::>(); for (key, (relation, field_name, ty)) in result.iter() { let new_value = extra_data.get(key).unwrap(); // TODO: Make this more generic let new_value = DataValue::from(new_value.as_str().unwrap()); let query = format!( " ?[ node_id, {field_name} ] <- [[$node_id, $input_data]] :update {relation} {{ node_id, {field_name} }} " ); println!("QUERY: {query:?}"); let result = tx.run_script( &query, btmap! { "node_id".to_owned() => node_id_data.clone(), "input_data".to_owned() => new_value, }, )?; println!("RESULT: {result:?}"); } } tx.run_script( " # Always update the time ?[ id, updated_at ] <- [[ $node_id, now() ]] :update node { id, updated_at } ", btmap! { "node_id".to_owned() => node_id_data, }, )?; tx.commit()?; Ok(Json(json!({}))) } pub async fn node_types() -> AppResult> { Ok(Json(json!({ "types": [ { "id": "panorama/journal/page", "display": "Journal Entry" }, ] }))) }