openws-server 0.1.5

WebSocket server with room-based messaging, private messages, HTTP broadcast, and configurable limits
use axum::http::HeaderMap;
use axum::extract::State;
use axum::http::StatusCode;
use axum::Json;
use serde::Deserialize;
use tracing::warn;

use crate::message::ServerMessage;
use crate::state::AppState;

#[derive(Debug, Deserialize)]
pub struct BroadcastRequest {
    pub room: String,
    pub text: String,
    #[serde(default)]
    pub from: String,
}

pub async fn broadcast_handler(
    State(state): State<AppState>,
    headers: HeaderMap,
    Json(payload): Json<BroadcastRequest>,
) -> (StatusCode, Json<serde_json::Value>) {
    if !state.config.broadcast.api_key.is_empty() {
        let provided_key = headers
            .get("X-Api-Key")
            .and_then(|v| v.to_str().ok())
            .unwrap_or("");
        if provided_key != state.config.broadcast.api_key {
            warn!("broadcast API: invalid API key");
            return (
                StatusCode::UNAUTHORIZED,
                Json(serde_json::json!({ "error": "unauthorized" })),
            );
        }
    } else {
        warn!("broadcast API: no API key configured, endpoint disabled");
        return (
            StatusCode::FORBIDDEN,
            Json(serde_json::json!({ "error": "broadcast endpoint disabled" })),
        );
    }

    {
        let rooms = state.rooms.read().await;
        if !rooms.contains_key(&payload.room) {
            return (
                StatusCode::NOT_FOUND,
                Json(serde_json::json!({ "error": "room not found" })),
            );
        }
    }

    let from = if payload.from.is_empty() {
        "system".into()
    } else {
        payload.from
    };

    let at = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_secs();

    let msg = ServerMessage::Message {
        room: payload.room.clone(),
        from,
        text: payload.text,
        at,
    };

    state.broadcast(&payload.room, &msg, None).await;

    (
        StatusCode::OK,
        Json(serde_json::json!({ "status": "ok" })),
    )
}