feat: Events API routes migrated to Rust
- GET /api/events — list with filters (type, read, pagination)
- GET /api/events/count — unread count
- POST /api/events/{id}/read — mark one as read
- POST /api/events/read-all — mark all as read
- DELETE /api/events — clear all events
- Event model with sqlx FromRow
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
|
||||
/// System event for admin notifications.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct Event {
|
||||
pub id: i64,
|
||||
#[sqlx(rename = "type")]
|
||||
pub event_type: String,
|
||||
pub title: String,
|
||||
pub message: Option<String>,
|
||||
pub level: Option<String>,
|
||||
#[sqlx(rename = "read")]
|
||||
pub is_read: Option<bool>,
|
||||
pub related_id: Option<i64>,
|
||||
pub related_type: Option<String>,
|
||||
pub created_at: Option<String>,
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
pub mod account;
|
||||
pub mod downstream_api_key;
|
||||
pub mod event;
|
||||
pub mod route_channel;
|
||||
pub mod setting;
|
||||
pub mod site;
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
// Events routes — system event notifications
|
||||
|
||||
use crate::{
|
||||
db::models::event::Event,
|
||||
error::{AppError, AppResult},
|
||||
AppState,
|
||||
};
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
routing::{delete, get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Value};
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ListEventsQuery {
|
||||
limit: Option<i64>,
|
||||
offset: Option<i64>,
|
||||
#[serde(rename = "type")]
|
||||
filter_type: Option<String>,
|
||||
read: Option<String>, // "true" | "false"
|
||||
}
|
||||
|
||||
pub fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/api/events", get(list_events).delete(clear_events))
|
||||
.route("/api/events/count", get(unread_count))
|
||||
.route("/api/events/{id}/read", post(mark_read))
|
||||
.route("/api/events/read-all", post(mark_all_read))
|
||||
}
|
||||
|
||||
/// GET /api/events
|
||||
async fn list_events(
|
||||
State(state): State<AppState>,
|
||||
Query(q): Query<ListEventsQuery>,
|
||||
) -> AppResult<Json<Value>> {
|
||||
info!("Listing events");
|
||||
let limit = q.limit.unwrap_or(30).clamp(1, 500);
|
||||
let offset = q.offset.unwrap_or(0).max(0);
|
||||
|
||||
let mut sql = "SELECT * FROM events WHERE 1=1".to_string();
|
||||
let mut bind_idx = 0;
|
||||
let mut params: Vec<String> = vec![];
|
||||
|
||||
if let Some(ref t) = q.filter_type {
|
||||
bind_idx += 1;
|
||||
sql.push_str(&format!(" AND type = ?{}", bind_idx));
|
||||
params.push(t.clone());
|
||||
}
|
||||
if let Some(ref r) = q.read {
|
||||
if r == "true" {
|
||||
sql.push_str(" AND read = 1");
|
||||
} else if r == "false" {
|
||||
sql.push_str(" AND read = 0");
|
||||
}
|
||||
}
|
||||
|
||||
sql.push_str(" ORDER BY created_at DESC");
|
||||
sql.push_str(&format!(" LIMIT {} OFFSET {}", limit, offset));
|
||||
|
||||
let mut query = sqlx::query_as::<_, Event>(&sql);
|
||||
for p in ¶ms {
|
||||
query = query.bind(p);
|
||||
}
|
||||
let events: Vec<Event> = query.fetch_all(&*state.db).await?;
|
||||
|
||||
Ok(Json(json!({ "data": events })))
|
||||
}
|
||||
|
||||
/// GET /api/events/count — unread count
|
||||
async fn unread_count(State(state): State<AppState>) -> AppResult<Json<Value>> {
|
||||
let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM events WHERE read = 0")
|
||||
.fetch_one(&*state.db)
|
||||
.await?;
|
||||
Ok(Json(json!({ "count": count.0 })))
|
||||
}
|
||||
|
||||
/// POST /api/events/{id}/read
|
||||
async fn mark_read(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<i64>,
|
||||
) -> AppResult<Json<Value>> {
|
||||
info!("Marking event {} as read", id);
|
||||
sqlx::query("UPDATE events SET read = 1 WHERE id = ?")
|
||||
.bind(id)
|
||||
.execute(&*state.db)
|
||||
.await?;
|
||||
Ok(Json(json!({ "success": true })))
|
||||
}
|
||||
|
||||
/// POST /api/events/read-all
|
||||
async fn mark_all_read(State(state): State<AppState>) -> AppResult<Json<Value>> {
|
||||
info!("Marking all events as read");
|
||||
sqlx::query("UPDATE events SET read = 1 WHERE read = 0")
|
||||
.execute(&*state.db)
|
||||
.await?;
|
||||
Ok(Json(json!({ "success": true })))
|
||||
}
|
||||
|
||||
/// DELETE /api/events
|
||||
async fn clear_events(State(state): State<AppState>) -> AppResult<Json<Value>> {
|
||||
info!("Clearing all events");
|
||||
sqlx::query("DELETE FROM events")
|
||||
.execute(&*state.db)
|
||||
.await?;
|
||||
Ok(Json(json!({ "success": true })))
|
||||
}
|
||||
@@ -4,6 +4,7 @@ use axum::{routing::get, Router};
|
||||
mod admin;
|
||||
mod auth;
|
||||
mod comfyui;
|
||||
mod events;
|
||||
mod health;
|
||||
mod proxy;
|
||||
|
||||
@@ -14,5 +15,6 @@ pub fn build_router(state: AppState) -> Router {
|
||||
.merge(proxy::routes())
|
||||
.merge(comfyui::routes())
|
||||
.merge(auth::routes())
|
||||
.merge(events::routes())
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user