Files
guru-connect/server/src/api/mod.rs
Mike Swanson ccc6ba9c02
All checks were successful
Build and Test / Build Agent (Windows) (push) Successful in 12m18s
Build and Test / Build Server (Linux) (push) Successful in 14m11s
Build and Test / Security Audit (push) Successful in 5m32s
Build and Test / Build Summary (push) Successful in 9s
ci: enforce clippy -D warnings and cargo audit as hard gates
Flip both CI gates from informational to hard-fail (SPEC-001 quality gates):
- clippy: `-- -D warnings` on the server crate. Cleared the debt via clippy --fix
  (unused imports/style), targeted #[allow(dead_code)] on native-remote-control
  future API, and #[allow(clippy::too_many_arguments)] on 3 protocol-mirroring fns.
- cargo audit: hard-fail with documented per-ID --ignore flags (rsa RUSTSEC-2023-0071
  unfixable/unreachable in active tree; gtk-rs + glib Linux-only tray backend not
  compiled into the Windows agent; proc-macro-error build-time). New advisories fail.
- Move [profile.release] to the workspace root (it was silently ignored in the server
  member), activating lto/codegen-units/strip.

No behavioral changes. Reviewed and gates verified passing on the build host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 00:18:50 +00:00

220 lines
6.2 KiB
Rust

//! REST API endpoints
pub mod auth;
pub mod auth_logout;
pub mod changelog;
pub mod downloads;
pub mod releases;
pub mod users;
use axum::{
extract::{Path, State},
Json,
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::db;
use crate::session::SessionManager;
/// Viewer info returned by API
#[derive(Debug, Serialize)]
pub struct ViewerInfoApi {
pub id: String,
pub name: String,
pub connected_at: String,
}
impl From<crate::session::ViewerInfo> for ViewerInfoApi {
fn from(v: crate::session::ViewerInfo) -> Self {
Self {
id: v.id,
name: v.name,
connected_at: v.connected_at.to_rfc3339(),
}
}
}
/// Session info returned by API
#[derive(Debug, Serialize)]
pub struct SessionInfo {
pub id: String,
pub agent_id: String,
pub agent_name: String,
pub started_at: String,
pub viewer_count: usize,
pub viewers: Vec<ViewerInfoApi>,
pub is_streaming: bool,
pub is_online: bool,
pub is_persistent: bool,
pub last_heartbeat: String,
pub os_version: Option<String>,
pub is_elevated: bool,
pub uptime_secs: i64,
pub display_count: i32,
pub agent_version: Option<String>,
}
impl From<crate::session::Session> for SessionInfo {
fn from(s: crate::session::Session) -> Self {
Self {
id: s.id.to_string(),
agent_id: s.agent_id,
agent_name: s.agent_name,
started_at: s.started_at.to_rfc3339(),
viewer_count: s.viewer_count,
viewers: s.viewers.into_iter().map(ViewerInfoApi::from).collect(),
is_streaming: s.is_streaming,
is_online: s.is_online,
is_persistent: s.is_persistent,
last_heartbeat: s.last_heartbeat.to_rfc3339(),
os_version: s.os_version,
is_elevated: s.is_elevated,
uptime_secs: s.uptime_secs,
display_count: s.display_count,
agent_version: s.agent_version,
}
}
}
/// List all active sessions
#[allow(dead_code)] // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
pub async fn list_sessions(State(sessions): State<SessionManager>) -> Json<Vec<SessionInfo>> {
let sessions = sessions.list_sessions().await;
Json(sessions.into_iter().map(SessionInfo::from).collect())
}
/// Get a specific session by ID
#[allow(dead_code)] // TODO(native-remote-control): consumed by the integration API; see docs/specs/native-remote-control/
pub async fn get_session(
State(sessions): State<SessionManager>,
Path(id): Path<String>,
) -> Result<Json<SessionInfo>, (axum::http::StatusCode, &'static str)> {
let session_id = Uuid::parse_str(&id)
.map_err(|_| (axum::http::StatusCode::BAD_REQUEST, "Invalid session ID"))?;
let session = sessions
.get_session(session_id)
.await
.ok_or((axum::http::StatusCode::NOT_FOUND, "Session not found"))?;
Ok(Json(SessionInfo::from(session)))
}
// ============================================================================
// Machine API Types
// ============================================================================
/// Machine info returned by API
#[derive(Debug, Serialize)]
pub struct MachineInfo {
pub id: String,
pub agent_id: String,
pub hostname: String,
pub os_version: Option<String>,
pub is_elevated: bool,
pub is_persistent: bool,
pub first_seen: String,
pub last_seen: String,
pub status: String,
}
impl From<db::machines::Machine> for MachineInfo {
fn from(m: db::machines::Machine) -> Self {
Self {
id: m.id.to_string(),
agent_id: m.agent_id,
hostname: m.hostname,
os_version: m.os_version,
is_elevated: m.is_elevated,
is_persistent: m.is_persistent,
first_seen: m.first_seen.to_rfc3339(),
last_seen: m.last_seen.to_rfc3339(),
status: m.status,
}
}
}
/// Session record for history
#[derive(Debug, Serialize)]
pub struct SessionRecord {
pub id: String,
pub started_at: String,
pub ended_at: Option<String>,
pub duration_secs: Option<i32>,
pub is_support_session: bool,
pub support_code: Option<String>,
pub status: String,
}
impl From<db::sessions::DbSession> for SessionRecord {
fn from(s: db::sessions::DbSession) -> Self {
Self {
id: s.id.to_string(),
started_at: s.started_at.to_rfc3339(),
ended_at: s.ended_at.map(|t| t.to_rfc3339()),
duration_secs: s.duration_secs,
is_support_session: s.is_support_session,
support_code: s.support_code,
status: s.status,
}
}
}
/// Event record for history
#[derive(Debug, Serialize)]
pub struct EventRecord {
pub id: i64,
pub session_id: String,
pub event_type: String,
pub timestamp: String,
pub viewer_id: Option<String>,
pub viewer_name: Option<String>,
pub details: Option<serde_json::Value>,
pub ip_address: Option<String>,
}
impl From<db::events::SessionEvent> for EventRecord {
fn from(e: db::events::SessionEvent) -> Self {
Self {
id: e.id,
session_id: e.session_id.to_string(),
event_type: e.event_type,
timestamp: e.timestamp.to_rfc3339(),
viewer_id: e.viewer_id,
viewer_name: e.viewer_name,
details: e.details,
ip_address: e.ip_address,
}
}
}
/// Full machine history (for export)
#[derive(Debug, Serialize)]
pub struct MachineHistory {
pub machine: MachineInfo,
pub sessions: Vec<SessionRecord>,
pub events: Vec<EventRecord>,
pub exported_at: String,
}
/// Query parameters for machine deletion
#[derive(Debug, Deserialize)]
pub struct DeleteMachineParams {
/// If true, send uninstall command to agent (if online)
#[serde(default)]
pub uninstall: bool,
/// If true, include history in response before deletion
#[serde(default)]
pub export: bool,
}
/// Response for machine deletion
#[derive(Debug, Serialize)]
pub struct DeleteMachineResponse {
pub success: bool,
pub message: String,
pub uninstall_sent: bool,
pub history: Option<MachineHistory>,
}