2024-06-13 18:42:45 +00:00
|
|
|
use std::{
|
|
|
|
collections::{BTreeMap, HashMap},
|
|
|
|
str::FromStr,
|
|
|
|
};
|
2024-05-25 15:11:34 +00:00
|
|
|
|
2024-05-25 10:04:05 +00:00
|
|
|
use axum::{
|
2024-05-27 05:43:09 +00:00
|
|
|
extract::{Path, Query, State},
|
2024-05-25 11:45:23 +00:00
|
|
|
http::StatusCode,
|
2024-06-03 05:02:45 +00:00
|
|
|
routing::{get, post, put},
|
2024-06-02 23:12:03 +00:00
|
|
|
Json, Router,
|
2024-05-25 10:04:05 +00:00
|
|
|
};
|
2024-06-10 19:03:40 +00:00
|
|
|
use chrono::{DateTime, Utc};
|
2024-06-10 19:52:13 +00:00
|
|
|
use cozo::{DataValue, MultiTransaction};
|
2024-06-13 18:42:45 +00:00
|
|
|
use itertools::Itertools;
|
|
|
|
use miette::IntoDiagnostic;
|
|
|
|
use panorama_core::{
|
|
|
|
state::node::{CreateOrUpdate, ExtraData},
|
|
|
|
NodeId,
|
|
|
|
};
|
2024-05-25 10:04:05 +00:00
|
|
|
use serde_json::Value;
|
2024-06-02 21:37:36 +00:00
|
|
|
use utoipa::{OpenApi, ToSchema};
|
2024-06-13 18:42:45 +00:00
|
|
|
use uuid::Uuid;
|
2024-05-25 10:04:05 +00:00
|
|
|
|
|
|
|
use crate::{error::AppResult, AppState};
|
|
|
|
|
2024-06-02 23:12:03 +00:00
|
|
|
/// Node API
|
2024-06-02 21:37:36 +00:00
|
|
|
#[derive(OpenApi)]
|
2024-06-03 05:02:45 +00:00
|
|
|
#[openapi(
|
|
|
|
paths(get_node, update_node, create_node),
|
|
|
|
components(schemas(GetNodeResult))
|
|
|
|
)]
|
2024-06-02 21:37:36 +00:00
|
|
|
pub(super) struct NodeApi;
|
|
|
|
|
2024-06-02 23:12:03 +00:00
|
|
|
pub(super) fn router() -> Router<AppState> {
|
2024-06-03 05:02:45 +00:00
|
|
|
Router::new()
|
2024-06-10 19:52:13 +00:00
|
|
|
.route("/", put(create_node))
|
2024-06-03 05:02:45 +00:00
|
|
|
.route("/:id", get(get_node))
|
|
|
|
.route("/:id", post(update_node))
|
2024-06-10 19:52:13 +00:00
|
|
|
.route("/search", get(search_nodes))
|
2024-06-02 23:12:03 +00:00
|
|
|
}
|
|
|
|
|
2024-06-02 21:37:36 +00:00
|
|
|
#[derive(Serialize, Deserialize, ToSchema, Clone)]
|
|
|
|
struct GetNodeResult {
|
2024-06-10 19:03:40 +00:00
|
|
|
node_id: String,
|
|
|
|
fields: HashMap<String, Value>,
|
|
|
|
created_at: DateTime<Utc>,
|
|
|
|
updated_at: DateTime<Utc>,
|
2024-06-02 21:37:36 +00:00
|
|
|
}
|
|
|
|
|
2024-06-03 05:02:45 +00:00
|
|
|
/// Get node info
|
|
|
|
///
|
|
|
|
/// This endpoint retrieves all the fields for a particular node
|
2024-06-02 21:37:36 +00:00
|
|
|
#[utoipa::path(
|
|
|
|
get,
|
|
|
|
path = "/{id}",
|
|
|
|
responses(
|
2024-06-03 05:02:45 +00:00
|
|
|
(status = 200, body = [GetNodeResult]),
|
|
|
|
(status = 404, description = "the node ID provided was not found")
|
2024-06-02 23:12:03 +00:00
|
|
|
),
|
|
|
|
params(
|
|
|
|
("id" = String, Path, description = "Node ID"),
|
|
|
|
),
|
2024-06-02 21:37:36 +00:00
|
|
|
)]
|
2024-05-25 10:04:05 +00:00
|
|
|
pub async fn get_node(
|
|
|
|
State(state): State<AppState>,
|
|
|
|
Path(node_id): Path<String>,
|
2024-05-25 11:45:23 +00:00
|
|
|
) -> AppResult<(StatusCode, Json<Value>)> {
|
2024-06-10 19:03:40 +00:00
|
|
|
let node_info = state.get_node(&node_id).await?;
|
2024-05-25 10:04:05 +00:00
|
|
|
|
2024-05-25 11:45:23 +00:00
|
|
|
Ok((
|
|
|
|
StatusCode::OK,
|
|
|
|
Json(json!({
|
2024-06-10 19:03:40 +00:00
|
|
|
"node_id": node_id,
|
|
|
|
"fields": node_info.fields,
|
|
|
|
"created_at": node_info.created_at,
|
|
|
|
"updated_at": node_info.updated_at,
|
2024-05-25 11:45:23 +00:00
|
|
|
})),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2024-05-25 15:11:34 +00:00
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
pub struct UpdateData {
|
2024-05-27 06:27:17 +00:00
|
|
|
extra_data: Option<ExtraData>,
|
2024-05-25 15:11:34 +00:00
|
|
|
}
|
2024-05-25 11:45:23 +00:00
|
|
|
|
2024-06-03 05:02:45 +00:00
|
|
|
/// Update node info
|
2024-06-02 23:12:03 +00:00
|
|
|
#[utoipa::path(
|
|
|
|
post,
|
|
|
|
path = "/{id}",
|
|
|
|
responses(
|
|
|
|
(status = 200)
|
|
|
|
),
|
|
|
|
params(
|
|
|
|
("id" = String, Path, description = "Node ID"),
|
|
|
|
)
|
|
|
|
)]
|
2024-05-25 11:45:23 +00:00
|
|
|
pub async fn update_node(
|
|
|
|
State(state): State<AppState>,
|
|
|
|
Path(node_id): Path<String>,
|
2024-06-13 18:42:45 +00:00
|
|
|
Json(opts): Json<UpdateData>,
|
2024-05-25 11:45:23 +00:00
|
|
|
) -> AppResult<Json<Value>> {
|
2024-06-13 18:42:45 +00:00
|
|
|
let node_id = NodeId(Uuid::from_str(&node_id).into_diagnostic()?);
|
|
|
|
let node_info = state
|
|
|
|
.create_or_update_node(CreateOrUpdate::Update { node_id }, opts.extra_data)
|
|
|
|
.await?;
|
2024-05-27 02:56:12 +00:00
|
|
|
|
2024-06-13 18:42:45 +00:00
|
|
|
Ok(Json(json!({
|
|
|
|
"node_id": node_info.node_id.to_string(),
|
|
|
|
})))
|
2024-05-25 10:04:05 +00:00
|
|
|
}
|
2024-05-25 15:11:34 +00:00
|
|
|
|
2024-05-27 06:27:17 +00:00
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
pub struct CreateNodeOpts {
|
|
|
|
// TODO: Allow submitting a string
|
|
|
|
// id: Option<String>,
|
|
|
|
#[serde(rename = "type")]
|
|
|
|
ty: String,
|
|
|
|
extra_data: Option<ExtraData>,
|
|
|
|
}
|
|
|
|
|
2024-06-03 05:02:45 +00:00
|
|
|
#[utoipa::path(
|
|
|
|
put,
|
|
|
|
path = "/",
|
2024-06-10 19:52:13 +00:00
|
|
|
responses((status = 200)),
|
2024-06-03 05:02:45 +00:00
|
|
|
)]
|
2024-05-27 06:27:17 +00:00
|
|
|
pub async fn create_node(
|
|
|
|
State(state): State<AppState>,
|
|
|
|
Json(opts): Json<CreateNodeOpts>,
|
|
|
|
) -> AppResult<Json<Value>> {
|
2024-06-10 19:52:13 +00:00
|
|
|
let node_info = state
|
2024-06-13 18:42:45 +00:00
|
|
|
.create_or_update_node(
|
|
|
|
CreateOrUpdate::Create { r#type: opts.ty },
|
|
|
|
opts.extra_data,
|
|
|
|
)
|
2024-06-10 19:52:13 +00:00
|
|
|
.await?;
|
2024-05-27 06:27:17 +00:00
|
|
|
|
|
|
|
Ok(Json(json!({
|
2024-06-10 19:52:13 +00:00
|
|
|
"node_id": node_info.node_id.to_string(),
|
2024-05-27 06:27:17 +00:00
|
|
|
})))
|
2024-05-27 05:43:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
pub struct SearchQuery {
|
|
|
|
query: String,
|
|
|
|
}
|
|
|
|
|
2024-06-10 19:52:13 +00:00
|
|
|
#[utoipa::path(
|
|
|
|
get,
|
|
|
|
path = "/search",
|
|
|
|
responses((status = 200)),
|
|
|
|
)]
|
2024-05-27 05:43:09 +00:00
|
|
|
pub async fn search_nodes(
|
|
|
|
State(state): State<AppState>,
|
|
|
|
Query(query): Query<SearchQuery>,
|
|
|
|
) -> AppResult<Json<Value>> {
|
2024-06-10 19:52:13 +00:00
|
|
|
let search_result = state.search_nodes(query.query).await?;
|
2024-06-13 18:42:45 +00:00
|
|
|
let search_result = search_result
|
|
|
|
.into_iter()
|
|
|
|
.map(|(id, value)| value["fields"].clone())
|
|
|
|
.collect_vec();
|
2024-05-27 05:43:09 +00:00
|
|
|
|
|
|
|
Ok(Json(json!({
|
2024-06-13 18:42:45 +00:00
|
|
|
"results": search_result,
|
2024-05-27 05:43:09 +00:00
|
|
|
})))
|
|
|
|
}
|
2024-05-27 06:27:17 +00:00
|
|
|
|
|
|
|
fn get_rows_for_extra_keys(
|
|
|
|
tx: &MultiTransaction,
|
|
|
|
extra_data: &ExtraData,
|
|
|
|
) -> AppResult<HashMap<String, (String, String, String)>> {
|
|
|
|
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::<Vec<_>>()
|
|
|
|
),
|
|
|
|
},
|
|
|
|
)?;
|
|
|
|
|
|
|
|
let s = |s: &DataValue| s.get_str().unwrap().to_owned();
|
|
|
|
|
|
|
|
Ok(
|
|
|
|
result
|
|
|
|
.rows
|
|
|
|
.into_iter()
|
|
|
|
.map(|row| (s(&row[0]), (s(&row[1]), s(&row[2]), s(&row[3]))))
|
|
|
|
.collect::<HashMap<_, _>>(),
|
|
|
|
)
|
|
|
|
}
|