/* * Copyright (C) 2026 Fluxer Contributors * * This file is part of Fluxer. * * Fluxer is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Fluxer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Fluxer. If not, see . */ use std::sync::Arc; use axum::{ Json, extract::State, http::StatusCode, response::{IntoResponse, Response}, }; use tracing::{error, info, warn}; use crate::alerts::send_discord_crash_alert; use crate::config::Config; use crate::db::{CounterRequest, CrashRequest, GaugeRequest, HistogramRequest, Storage}; pub struct AppState { pub storage: Box, pub config: Config, } pub async fn ingest_counter( State(state): State>, Json(req): Json, ) -> StatusCode { match state.storage.insert_counter(req).await { Ok(()) => StatusCode::ACCEPTED, Err(e) => { error!("Failed to insert counter: {}", e); StatusCode::INTERNAL_SERVER_ERROR } } } pub async fn ingest_gauge( State(state): State>, Json(req): Json, ) -> StatusCode { match state.storage.insert_gauge(req).await { Ok(()) => StatusCode::ACCEPTED, Err(e) => { error!("Failed to insert gauge: {}", e); StatusCode::INTERNAL_SERVER_ERROR } } } pub async fn ingest_histogram( State(state): State>, Json(req): Json, ) -> StatusCode { match state.storage.insert_histogram(req).await { Ok(()) => StatusCode::ACCEPTED, Err(e) => { error!("Failed to insert histogram: {}", e); StatusCode::INTERNAL_SERVER_ERROR } } } #[allow(clippy::cognitive_complexity)] pub async fn ingest_crash( State(state): State>, Json(req): Json, ) -> Response { let guild_id = req.guild_id.clone(); match state.storage.insert_crash(req).await { Ok(event) => { info!("Recorded crash for guild {}", guild_id); if let Some(webhook_url) = &state.config.alert_webhook_url { let admin_endpoint = state.config.admin_endpoint.as_deref(); match send_discord_crash_alert(webhook_url, &event, admin_endpoint).await { Ok(()) => { if let Err(e) = state.storage.mark_crash_notified(&event.id).await { warn!("Failed to mark crash as notified: {}", e); } } Err(e) => { error!("Failed to send Discord alert: {}", e); } } } StatusCode::ACCEPTED.into_response() } Err(e) => { error!("Failed to insert crash: {}", e); StatusCode::INTERNAL_SERVER_ERROR.into_response() } } } #[derive(serde::Deserialize)] pub struct BatchRequest { #[serde(default)] pub counters: Vec, #[serde(default)] pub gauges: Vec, #[serde(default)] pub histograms: Vec, } #[allow(clippy::cognitive_complexity)] pub async fn ingest_batch( State(state): State>, Json(req): Json, ) -> StatusCode { let mut had_error = false; for counter in req.counters { if let Err(e) = state.storage.insert_counter(counter).await { error!("Failed to insert counter in batch: {}", e); had_error = true; } } for gauge in req.gauges { if let Err(e) = state.storage.insert_gauge(gauge).await { error!("Failed to insert gauge in batch: {}", e); had_error = true; } } for histogram in req.histograms { if let Err(e) = state.storage.insert_histogram(histogram).await { error!("Failed to insert histogram in batch: {}", e); had_error = true; } } if had_error { StatusCode::PARTIAL_CONTENT } else { StatusCode::ACCEPTED } }