Add support codes API and portal server changes

- support_codes.rs: 6-digit code management
- main.rs: Portal routes, static file serving, AppState
- relay/mod.rs: Updated for AppState
- Cargo.toml: Added rand, tower-http fs feature

Generated with Claude Code
This commit is contained in:
2025-12-28 17:54:05 +00:00
parent 70a9fcd129
commit 611bc00d06
5 changed files with 347 additions and 17 deletions

View File

@@ -9,6 +9,7 @@ mod session;
mod auth;
mod api;
mod db;
mod support_codes;
pub mod proto {
include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
@@ -17,13 +18,27 @@ pub mod proto {
use anyhow::Result;
use axum::{
Router,
routing::get,
routing::{get, post},
extract::{Path, State, Json},
response::{Html, IntoResponse},
http::StatusCode,
};
use std::net::SocketAddr;
use tower_http::cors::{Any, CorsLayer};
use tower_http::trace::TraceLayer;
use tower_http::services::ServeDir;
use tracing::{info, Level};
use tracing_subscriber::FmtSubscriber;
use serde::Deserialize;
use support_codes::{SupportCodeManager, CreateCodeRequest, SupportCode, CodeValidation};
/// Application state
#[derive(Clone)]
pub struct AppState {
sessions: session::SessionManager,
support_codes: SupportCodeManager,
}
#[tokio::main]
async fn main() -> Result<()> {
@@ -37,26 +52,42 @@ async fn main() -> Result<()> {
// Load configuration
let config = config::Config::load()?;
info!("Loaded configuration, listening on {}", config.listen_addr);
// Use port 3002 for GuruConnect
let listen_addr = std::env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0:3002".to_string());
info!("Loaded configuration, listening on {}", listen_addr);
// Initialize database connection (optional for MVP)
// let db = db::init(&config.database_url).await?;
// Create session manager
let sessions = session::SessionManager::new();
// Create application state
let state = AppState {
sessions: session::SessionManager::new(),
support_codes: SupportCodeManager::new(),
};
// Build router
let app = Router::new()
// Health check
.route("/health", get(health))
// Portal API - Support codes
.route("/api/codes", post(create_code))
.route("/api/codes", get(list_codes))
.route("/api/codes/:code/validate", get(validate_code))
.route("/api/codes/:code/cancel", post(cancel_code))
// WebSocket endpoints
.route("/ws/agent", get(relay::agent_ws_handler))
.route("/ws/viewer", get(relay::viewer_ws_handler))
// REST API
.route("/api/sessions", get(api::list_sessions))
.route("/api/sessions/:id", get(api::get_session))
// REST API - Sessions
.route("/api/sessions", get(list_sessions))
.route("/api/sessions/:id", get(get_session))
// State
.with_state(sessions)
.with_state(state)
// Serve static files for portal (fallback)
.fallback_service(ServeDir::new("static").append_index_html_on_directories(true))
// Middleware
.layer(TraceLayer::new_for_http())
.layer(
@@ -67,7 +98,7 @@ async fn main() -> Result<()> {
);
// Start server
let addr: SocketAddr = config.listen_addr.parse()?;
let addr: SocketAddr = listen_addr.parse()?;
let listener = tokio::net::TcpListener::bind(addr).await?;
info!("Server listening on {}", addr);
@@ -80,3 +111,65 @@ async fn main() -> Result<()> {
async fn health() -> &'static str {
"OK"
}
// Support code API handlers
async fn create_code(
State(state): State<AppState>,
Json(request): Json<CreateCodeRequest>,
) -> Json<SupportCode> {
let code = state.support_codes.create_code(request).await;
info!("Created support code: {}", code.code);
Json(code)
}
async fn list_codes(
State(state): State<AppState>,
) -> Json<Vec<SupportCode>> {
Json(state.support_codes.list_active_codes().await)
}
#[derive(Deserialize)]
struct ValidateParams {
code: String,
}
async fn validate_code(
State(state): State<AppState>,
Path(code): Path<String>,
) -> Json<CodeValidation> {
Json(state.support_codes.validate_code(&code).await)
}
async fn cancel_code(
State(state): State<AppState>,
Path(code): Path<String>,
) -> impl IntoResponse {
if state.support_codes.cancel_code(&code).await {
(StatusCode::OK, "Code cancelled")
} else {
(StatusCode::BAD_REQUEST, "Cannot cancel code")
}
}
// Session API handlers (updated to use AppState)
async fn list_sessions(
State(state): State<AppState>,
) -> Json<Vec<api::SessionInfo>> {
let sessions = state.sessions.list_sessions().await;
Json(sessions.into_iter().map(api::SessionInfo::from).collect())
}
async fn get_session(
State(state): State<AppState>,
Path(id): Path<String>,
) -> Result<Json<api::SessionInfo>, (StatusCode, &'static str)> {
let session_id = uuid::Uuid::parse_str(&id)
.map_err(|_| (StatusCode::BAD_REQUEST, "Invalid session ID"))?;
let session = state.sessions.get_session(session_id).await
.ok_or((StatusCode::NOT_FOUND, "Session not found"))?;
Ok(Json(api::SessionInfo::from(session)))
}