use axum::{ extract::{ ws::{Message, WebSocket}, Path, State, WebSocketUpgrade, }, http::StatusCode, response::Response, }; use askama::Template; use askama_web::WebTemplate; use futures::{SinkExt, StreamExt}; use crate::auth_guard::AuthUser; use crate::db; use crate::state::AppState; fn format_time(t: time::OffsetDateTime) -> String { format!( "{:04}-{:02}-{:02} {:02}:{:02}:{:02} UTC", t.year(), t.month() as u8, t.day(), t.hour(), t.minute(), t.second(), ) } pub struct LogRow { pub level: String, pub message: String, pub created_at: String, } #[derive(Template, WebTemplate)] #[template(path = "logs.html")] pub struct LogsTemplate { pub bot_id: String, pub bot_name: String, pub logs: Vec, } pub async fn history( State(state): State, AuthUser(user): AuthUser, Path(bot_id): Path, ) -> Result { let bot = db::get_bot_owned(&state.db, &bot_id, &user.sub) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? .ok_or(StatusCode::NOT_FOUND)?; let rows = db::list_logs(&state.db, &bot_id, 200) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let logs = rows .into_iter() .map(|l| LogRow { level: l.level, message: l.message, created_at: format_time(l.created_at), }) .collect(); Ok(LogsTemplate { bot_id, bot_name: bot.name, logs, }) } pub async fn ws_upgrade( State(state): State, AuthUser(user): AuthUser, Path(bot_id): Path, ws: WebSocketUpgrade, ) -> Result { let _bot = db::get_bot_owned(&state.db, &bot_id, &user.sub) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? .ok_or(StatusCode::NOT_FOUND)?; let rx = state.bot_manager.subscribe_logs(&bot_id).await; Ok(ws.on_upgrade(move |socket| handle_ws(socket, rx))) } async fn handle_ws( socket: WebSocket, mut rx: tokio::sync::broadcast::Receiver, ) { let (mut sender, mut _receiver) = socket.split(); while let Ok(entry) = rx.recv().await { let json = match serde_json::to_string(&entry) { Ok(j) => j, Err(_) => continue, }; if sender.send(Message::Text(json.into())).await.is_err() { break; } } }