use crate::web::{creator_info_from_http, open_handle, ApiError};
use crate::{ActivitySource, AppState, DaemonEvent};
use axum::{
extract::{Query, State},
http::HeaderMap,
Json,
};
use serde::Deserialize;
use serde_json::{json, Value};
use uuid::Uuid;
#[derive(Deserialize)]
pub(crate) struct ListMessagesQuery {
palace: String,
#[serde(default)]
unread_only: Option<bool>,
}
pub(crate) async fn list_messages_handler(
State(state): State<AppState>,
Query(q): Query<ListMessagesQuery>,
) -> Result<Json<Value>, ApiError> {
let handle = open_handle(&state, &q.palace)?;
let unread_only = q.unread_only.unwrap_or(false);
let messages = crate::messaging::list_messages(&handle, unread_only);
let payload: Vec<Value> = messages
.into_iter()
.map(|m| {
let formatted = m.to_injection_block();
json!({
"id": m.id.to_string(),
"from_palace": m.from_palace,
"to_palace": m.to_palace,
"purpose": m.purpose,
"sent_at": m.sent_at.to_rfc3339(),
"read": m.read,
"content": m.content,
"formatted": formatted,
})
})
.collect();
Ok(Json(json!(payload)))
}
#[derive(Deserialize)]
pub(crate) struct SendMessageBody {
to_palace: String,
purpose: String,
content: String,
#[serde(default)]
from_palace: Option<String>,
}
pub(crate) async fn send_message_handler(
State(state): State<AppState>,
headers: HeaderMap,
Json(body): Json<SendMessageBody>,
) -> Result<Json<Value>, ApiError> {
let from_palace = body
.from_palace
.or_else(|| state.default_palace.clone())
.unwrap_or_else(|| "<unknown>".to_string());
let drawer_id = crate::messaging::send_message_to_palace(
&state.registry,
&state.data_root,
&from_palace,
&body.to_palace,
&body.purpose,
body.content,
creator_info_from_http(&headers),
)
.await
.map_err(|e| ApiError::internal(format!("send_message: {e:#}")))?;
let drawer_count = open_handle(&state, &body.to_palace)
.map(|h| h.drawers.read().len())
.unwrap_or(0);
state.emit(DaemonEvent::DrawerAdded {
palace_id: body.to_palace.clone(),
palace_name: body.to_palace.clone(),
drawer_count,
timestamp: chrono::Utc::now(),
content_preview: format!("[msg from {from_palace}] {}", body.purpose),
source: ActivitySource::Http,
});
Ok(Json(json!({
"drawer_id": drawer_id.to_string(),
"from_palace": from_palace,
"to_palace": body.to_palace,
"purpose": body.purpose,
"status": "sent",
})))
}
#[derive(Deserialize)]
pub(crate) struct MarkReadBody {
palace: String,
drawer_id: String,
}
pub(crate) async fn mark_message_read_handler(
State(state): State<AppState>,
Json(body): Json<MarkReadBody>,
) -> Result<Json<Value>, ApiError> {
let uuid = Uuid::parse_str(&body.drawer_id)
.map_err(|_| ApiError::bad_request("drawer_id must be a UUID"))?;
let handle = open_handle(&state, &body.palace)?;
let flipped = crate::messaging::mark_message_read(&handle, uuid)
.await
.map_err(|e| ApiError::internal(format!("mark_read: {e:#}")))?;
Ok(Json(json!({"flipped": flipped})))
}