remove old frontend

This commit is contained in:
2026-07-14 21:36:55 -07:00
parent bd79966218
commit c9d497153f
37 changed files with 8 additions and 4748 deletions
+3 -6
View File
@@ -10,7 +10,6 @@ use serde_json::{json, Value};
use sha2::Sha256;
use tokio::{sync::{broadcast, Mutex}, task};
use tokio_postgres::{Client, NoTls};
use tower_http::services::{ServeDir, ServeFile};
use tracing::{error, info, warn};
use uuid::Uuid;
@@ -77,19 +76,18 @@ async fn main() {
let state = AppState { reply_enabled: Arc::new(AtomicBool::new(config.reply_enabled)), source: Arc::new(Mutex::new(SourceStatus { detail: "Starting blivedm_rs listener".into(), ..Default::default() })), config, events };
migrate(&state.config.database_url).await.expect("database migration failed");
spawn_live_listener(state.clone());
let static_files = ServeDir::new("/app/web").not_found_service(ServeFile::new("/app/web/index.html"));
let app = Router::new()
.route("/health", get(health)).route("/ws", get(ws))
.route("/api/auth/login", post(login)).route("/api/auth/logout", post(logout))
.route("/api/admin/status", get(status)).route("/api/admin/viewers", get(viewers)).route("/api/admin/ledger", get(ledger))
.route("/api/admin/reconnect", post(reconnect)).route("/api/admin/reply", post(toggle_reply)).route("/api/admin/obs-url", get(obs_url))
.route("/api/test/event", post(test_event)).fallback_service(static_files).with_state(state.clone());
.route("/api/admin/reconnect", post(reconnect)).route("/api/admin/reply", post(toggle_reply))
.route("/api/test/event", post(test_event)).with_state(state.clone());
let addr = format!("0.0.0.0:{}", state.config.port);
let listener = tokio::net::TcpListener::bind(&addr).await.expect("bind failed");
info!(%addr, "Rust backend listening"); axum::serve(listener, app).await.expect("server failed");
}
async fn migrate(url: &str) -> Result<(), String> { let client = connect(url).await?; client.batch_execute(include_str!("../../server/migrations/001_initial.sql")).await.map_err(|e| e.to_string()) }
async fn migrate(url: &str) -> Result<(), String> { let client = connect(url).await?; client.batch_execute(include_str!("../migrations/001_initial.sql")).await.map_err(|e| e.to_string()) }
async fn connect(url: &str) -> Result<Client, String> { let (client, connection) = tokio_postgres::connect(url, NoTls).await.map_err(|e| e.to_string())?; tokio::spawn(async move { if let Err(e) = connection.await { warn!(error = %e, "postgres connection ended"); } }); Ok(client) }
fn session(config: &Config) -> String { let mut mac = HmacSha256::new_from_slice(config.session_secret.as_bytes()).expect("hmac key"); mac.update(b"admin"); URL_SAFE_NO_PAD.encode(mac.finalize().into_bytes()) }
fn cookie(headers: &HeaderMap, name: &str) -> Option<String> { headers.get("cookie")?.to_str().ok()?.split(';').find_map(|part| part.trim().split_once('=').filter(|(k, _)| *k == name).map(|(_, v)| v.to_owned())) }
@@ -106,7 +104,6 @@ async fn viewers(State(state): State<AppState>, headers: HeaderMap, Query(query)
async fn ledger(State(state): State<AppState>, headers: HeaderMap) -> Response { if !admin(&state, &headers) { return unauthorized(); } let db = match connect(&state.config.database_url).await { Ok(db) => db, Err(e) => return error_response(e) }; match db.query("SELECT l.id::text,l.uid,a.display_name,l.delta,l.reason,l.created_at::text FROM point_ledger l JOIN viewer_accounts a ON(a.scope=l.scope AND a.room_id=l.room_id AND a.uid=l.uid) WHERE l.scope='live' AND l.room_id=$1 ORDER BY l.created_at DESC LIMIT 100", &[&state.config.room_id]).await { Ok(rows) => Json(rows.into_iter().map(|r| json!({"id":r.get::<_, String>(0),"uid":r.get::<_, String>(1),"displayName":r.get::<_, String>(2),"delta":r.get::<_, i32>(3),"reason":r.get::<_, String>(4),"createdAt":r.get::<_, String>(5)})).collect::<Vec<_>>()).into_response(), Err(e) => error_response(e.to_string()) } }
async fn toggle_reply(State(state): State<AppState>, headers: HeaderMap, Json(body): Json<ToggleReply>) -> Response { if !admin(&state, &headers) { return unauthorized(); } state.reply_enabled.store(body.enabled, Ordering::Relaxed); Json(json!({"enabled":body.enabled,"available":true,"detail":"CookieCloud-backed reply ready"})).into_response() }
async fn reconnect(State(state): State<AppState>, headers: HeaderMap) -> Response { if !admin(&state, &headers) { return unauthorized(); } spawn_live_listener(state.clone()); Json(json!({"ok":true})).into_response() }
async fn obs_url(State(state): State<AppState>, headers: HeaderMap) -> Response { if !admin(&state, &headers) { return unauthorized(); } Json(format!("/obs?token={}", state.config.obs_access_token)).into_response() }
async fn test_event(State(state): State<AppState>, headers: HeaderMap, Json(body): Json<TestEvent>) -> Response { if !admin(&state, &headers) { return unauthorized(); } let message = match body { TestEvent::Enter{uid,name} => Incoming::Enter{viewer:Viewer{uid,name}}, TestEvent::Danmaku{uid,name,text} => Incoming::Danmaku{viewer:Viewer{uid,name},text}, TestEvent::Gift{uid,name,gift_name,battery,quantity} => Incoming::Gift{viewer:Viewer{uid,name},name:gift_name,battery,quantity,event_id:format!("test-{}",Uuid::new_v4())} }; if let Err(e) = process(&state, message, "test").await { return error_response(e); } Json(json!({"ok":true})).into_response() }
async fn ws(State(state): State<AppState>, headers: HeaderMap, Query(query): Query<WsQuery>, upgrade: WebSocketUpgrade) -> Response { let allowed = admin(&state, &headers) || query.token.as_deref() == Some(&state.config.obs_access_token); if !allowed { return (StatusCode::UNAUTHORIZED, "Unauthorized").into_response(); } upgrade.on_upgrade(move |socket| ws_loop(socket, state.events.subscribe())) }
async fn ws_loop(mut socket: WebSocket, mut rx: broadcast::Receiver<String>) { while let Ok(text) = rx.recv().await { if socket.send(Message::Text(text.into())).await.is_err() { break; } } }