use anyhow::{anyhow, Context, Result};
use serde_json::{json, Value};
use std::collections::HashMap;
use trusty_common::memory_core::room_identity::{
parse_room_preserving_case, room_type_tag, DEFAULT_WING_ID,
};
use trusty_common::memory_core::store::rooms::{
create_room, list_room_summaries, rename_room, resolve_room_selector, RoomSummary,
};
use uuid::Uuid;
use super::helpers::{open_palace_handle, resolve_palace};
use crate::AppState;
fn check_wing(args: &Value, tool: &str) -> Result<()> {
let Some(wing) = args
.get("wing")
.and_then(|v| v.as_str())
.map(str::trim)
.filter(|s| !s.is_empty())
else {
return Ok(());
};
if Uuid::parse_str(wing) == Ok(DEFAULT_WING_ID) {
return Ok(());
}
Err(anyhow!(
"{tool}: wings are not implemented yet (ADR-0027 T9); omit `wing`, \
or pass the default wing id {DEFAULT_WING_ID}"
))
}
fn room_json(room: &RoomSummary, drawer_count: usize) -> Value {
json!({
"room_id": room.id.to_string(),
"label": room.label,
"room_type": room_type_tag(&room.room_type),
"wing_id": room.wing_id.to_string(),
"drawer_count": drawer_count,
"created_at": chrono::DateTime::from_timestamp_millis(room.created_at_ms)
.map(|t| t.to_rfc3339()),
"resolved": room.resolved,
"description": room.description,
})
}
pub(crate) async fn handle_room_list(state: &AppState, args: Value) -> Result<Value> {
check_wing(&args, "room_list")?;
let palace = resolve_palace(state, &args, "room_list")?;
let handle = open_palace_handle(state, &palace)?;
let counts: HashMap<Uuid, usize> =
handle
.drawers
.read()
.iter()
.fold(HashMap::new(), |mut acc, d| {
*acc.entry(d.room_id).or_insert(0) += 1;
acc
});
let store = handle.kg.store();
let rooms = tokio::task::spawn_blocking(move || list_room_summaries(&store))
.await
.context("join room_list")?
.context("list rooms")?;
let payload: Vec<Value> = rooms
.iter()
.map(|r| room_json(r, counts.get(&r.id).copied().unwrap_or(0)))
.collect();
Ok(json!({ "palace": palace, "rooms": payload }))
}
pub(crate) async fn handle_room_create(state: &AppState, args: Value) -> Result<Value> {
check_wing(&args, "room_create")?;
let palace = resolve_palace(state, &args, "room_create")?;
let label = args
.get("label")
.and_then(|v| v.as_str())
.map(str::trim)
.filter(|s| !s.is_empty())
.ok_or_else(|| anyhow!("room_create: missing 'label'"))?
.to_string();
let description = args
.get("description")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let handle = open_palace_handle(state, &palace)?;
let store = handle.kg.store();
let room = parse_room_preserving_case(&label);
let (summary, created) =
tokio::task::spawn_blocking(move || create_room(&store, &room, description))
.await
.context("join room_create")?
.context("create room")?;
Ok(json!({
"palace": palace,
"room_id": summary.id.to_string(),
"label": summary.label,
"created": created,
}))
}
pub(crate) async fn handle_room_rename(state: &AppState, args: Value) -> Result<Value> {
let palace = resolve_palace(state, &args, "room_rename")?;
let selector = args
.get("room")
.and_then(|v| v.as_str())
.map(str::trim)
.filter(|s| !s.is_empty())
.ok_or_else(|| anyhow!("room_rename: missing 'room' (a room_id or a label)"))?
.to_string();
let new_label = args
.get("new_label")
.and_then(|v| v.as_str())
.map(str::trim)
.filter(|s| !s.is_empty())
.ok_or_else(|| anyhow!("room_rename: missing 'new_label'"))?
.to_string();
let handle = open_palace_handle(state, &palace)?;
let store = handle.kg.store();
let summary = tokio::task::spawn_blocking(move || {
let id = resolve_room_selector(&store, &selector)?;
rename_room(&store, id, &new_label)
})
.await
.context("join room_rename")?
.context("rename room")?;
Ok(json!({
"palace": palace,
"room_id": summary.id.to_string(),
"label": summary.label,
}))
}