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 account;
|
||||||
pub mod downstream_api_key;
|
pub mod downstream_api_key;
|
||||||
|
pub mod event;
|
||||||
pub mod route_channel;
|
pub mod route_channel;
|
||||||
pub mod setting;
|
pub mod setting;
|
||||||
pub mod site;
|
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 admin;
|
||||||
mod auth;
|
mod auth;
|
||||||
mod comfyui;
|
mod comfyui;
|
||||||
|
mod events;
|
||||||
mod health;
|
mod health;
|
||||||
mod proxy;
|
mod proxy;
|
||||||
|
|
||||||
@@ -14,5 +15,6 @@ pub fn build_router(state: AppState) -> Router {
|
|||||||
.merge(proxy::routes())
|
.merge(proxy::routes())
|
||||||
.merge(comfyui::routes())
|
.merge(comfyui::routes())
|
||||||
.merge(auth::routes())
|
.merge(auth::routes())
|
||||||
|
.merge(events::routes())
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user