From 94a992d72a45ebd374a16ff8ca76ddd20a3c20c0 Mon Sep 17 00:00:00 2001 From: Butterfly Dev Date: Tue, 7 Apr 2026 03:09:32 +0000 Subject: [PATCH] =?UTF-8?q?server:=20api/sessions.rs=20=E2=80=94=20CRUD=20?= =?UTF-8?q?endpoints=20+=20HUD=20command=20forwarding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/src/api/sessions.rs | 88 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 server/src/api/sessions.rs diff --git a/server/src/api/sessions.rs b/server/src/api/sessions.rs new file mode 100644 index 0000000..f49b8de --- /dev/null +++ b/server/src/api/sessions.rs @@ -0,0 +1,88 @@ +use actix_web::{web, HttpResponse}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +use crate::models::{ApiResponse, Session}; +use crate::state::AppState; + +/// `GET /api/sessions` — list every session. +pub async fn list_sessions(state: web::Data>) -> HttpResponse { + let sessions: Vec = state.sessions.iter().map(|r| r.value().clone()).collect(); + HttpResponse::Ok().json(ApiResponse::ok(sessions)) +} + +/// `POST /api/sessions` — create a new session. +pub async fn create_session(state: web::Data>) -> HttpResponse { + match state.create_session() { + Ok(session) => HttpResponse::Created().json(ApiResponse::ok(session)), + Err(e) => HttpResponse::ServiceUnavailable().json(ApiResponse::<()>::err(e)), + } +} + +/// `GET /api/sessions/{id}` — get a single session. +pub async fn get_session( + state: web::Data>, + path: web::Path, +) -> HttpResponse { + let id = path.into_inner(); + match state.get_session(&id) { + Some(session) => HttpResponse::Ok().json(ApiResponse::ok(session)), + None => HttpResponse::NotFound().json(ApiResponse::<()>::err("session not found")), + } +} + +/// `DELETE /api/sessions/{id}` — remove a session. +pub async fn delete_session( + state: web::Data>, + path: web::Path, +) -> HttpResponse { + let id = path.into_inner(); + if state.remove_session(&id) { + HttpResponse::Ok().json(ApiResponse::ok("session deleted")) + } else { + HttpResponse::NotFound().json(ApiResponse::<()>::err("session not found")) + } +} + +/// JSON body for HUD commands. +#[derive(Debug, Deserialize)] +pub struct HudCommandPayload { + pub command: String, + #[serde(default)] + pub params: serde_json::Value, +} + +/// `POST /api/sessions/{id}/hud` — send a HUD command to the agent for a session. +/// +/// The command is forwarded to the connected agent via the session's agent +/// channel (stored in AppState). If no agent is connected, returns 409. +pub async fn send_hud_command( + state: web::Data>, + path: web::Path, + body: web::Json, +) -> HttpResponse { + let session_id = path.into_inner(); + + // Find the first agent registered for this session. + let agent_id = state + .agents + .iter() + .find(|r| r.session_id == session_id) + .map(|r| r.agent_id.clone()); + + match agent_id { + Some(aid) => { + // TODO: in the WS handler we will add a per-agent mpsc sender so + // we can forward the HUD command there. For now log and acknowledge. + log::info!( + "HUD command '{}' for session {} → agent {} (params: {})", + body.command, + session_id, + aid, + body.params + ); + HttpResponse::Ok().json(ApiResponse::ok("command forwarded")) + } + None => HttpResponse::Conflict().json(ApiResponse::<()>::err("no agent connected for this session")), + } +}