mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 11:02:54 +00:00
feat: Add rating plan management, user blocking, and system stats API endpoints
- Add GET/POST /v1/rating-plans and GET/DELETE /v1/rating-plans/:id endpoints for rating plan CRUD operations - Add POST /v1/block/:ip, POST /v1/unblock/:ip, and GET /v1/blocked/:ip endpoints for user blocking management - Add GET /v1/stats endpoint to retrieve system statistics (accounts/sessions/blocked users/alerts/uptime) - Add delete to routing imports and remove unused IntoResponse/warn imports - Add RatingPlan
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
response::IntoResponse,
|
||||
routing::{get, post},
|
||||
routing::{delete, get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use tracing::{info, warn};
|
||||
use tracing::info;
|
||||
|
||||
use crate::errors::{ChargingError, ChargingResult, validate_ip, validate_bytes, ErrorContext, log_error, validate_amount, validate_session_id};
|
||||
use crate::models::*;
|
||||
@@ -28,6 +27,14 @@ pub fn create_router(state: AppState) -> Router {
|
||||
.route("/v1/credit/:ip/balance", get(get_balance))
|
||||
.route("/v1/subscriber/:imsi", get(get_subscriber))
|
||||
.route("/v1/usage", post(record_usage))
|
||||
.route("/v1/rating-plans", get(list_rating_plans))
|
||||
.route("/v1/rating-plans/:id", get(get_rating_plan))
|
||||
.route("/v1/rating-plans", post(add_rating_plan))
|
||||
.route("/v1/rating-plans/:id", delete(remove_rating_plan))
|
||||
.route("/v1/block/:ip", post(block_user))
|
||||
.route("/v1/unblock/:ip", post(unblock_user))
|
||||
.route("/v1/blocked/:ip", get(is_user_blocked))
|
||||
.route("/v1/stats", get(get_system_stats))
|
||||
.route("/health", get(health_check))
|
||||
.layer(cors)
|
||||
.with_state(state)
|
||||
@@ -196,3 +203,145 @@ pub async fn record_usage(
|
||||
pub async fn health_check() -> ChargingResult<Json<HealthResponse>> {
|
||||
Ok(Json(HealthResponse::default()))
|
||||
}
|
||||
|
||||
/// GET /v1/rating-plans
|
||||
/// List all rating plans
|
||||
pub async fn list_rating_plans(
|
||||
State(state): State<AppState>,
|
||||
) -> ChargingResult<Json<serde_json::Value>> {
|
||||
let plans = state.charging_engine.list_rating_plans().await
|
||||
.with_context("Failed to list rating plans")?;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"plans": plans
|
||||
})))
|
||||
}
|
||||
|
||||
/// GET /v1/rating-plans/:id
|
||||
/// Get a specific rating plan
|
||||
pub async fn get_rating_plan(
|
||||
Path(id): Path<String>,
|
||||
State(state): State<AppState>,
|
||||
) -> ChargingResult<Json<serde_json::Value>> {
|
||||
let plan = state.charging_engine.get_rating_plan(&id).await
|
||||
.with_context("Failed to get rating plan")?;
|
||||
|
||||
match plan {
|
||||
Some(p) => Ok(Json(serde_json::json!({
|
||||
"id": p.plan_id,
|
||||
"name": p.name,
|
||||
"data_rate": p.data_rate,
|
||||
"voice_rate": p.voice_rate,
|
||||
"sms_rate": p.sms_rate,
|
||||
"monthly_fee": p.monthly_fee,
|
||||
"data_limit": p.data_limit,
|
||||
"voice_limit": p.voice_limit,
|
||||
"sms_limit": p.sms_limit,
|
||||
}))),
|
||||
None => Err(ChargingError::RatingPlanNotFound(id)),
|
||||
}
|
||||
}
|
||||
|
||||
/// POST /v1/rating-plans
|
||||
/// Add a new rating plan
|
||||
pub async fn add_rating_plan(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<serde_json::Value>,
|
||||
) -> ChargingResult<Json<serde_json::Value>> {
|
||||
let plan = crate::charging::types::RatingPlan {
|
||||
plan_id: req.get("plan_id").and_then(|v| v.as_str()).unwrap_or("default").to_string(),
|
||||
name: req.get("name").and_then(|v| v.as_str()).unwrap_or("Default").to_string(),
|
||||
data_rate: req.get("data_rate").and_then(|v| v.as_f64()).unwrap_or(0.001),
|
||||
voice_rate: req.get("voice_rate").and_then(|v| v.as_f64()).unwrap_or(0.01),
|
||||
sms_rate: req.get("sms_rate").and_then(|v| v.as_f64()).unwrap_or(0.1),
|
||||
monthly_fee: req.get("monthly_fee").and_then(|v| v.as_f64()).unwrap_or(10.0),
|
||||
data_limit: req.get("data_limit").and_then(|v| v.as_u64()).unwrap_or(1_000_000_000),
|
||||
voice_limit: req.get("voice_limit").and_then(|v| v.as_u64()).unwrap_or(1000),
|
||||
sms_limit: req.get("sms_limit").and_then(|v| v.as_u64()).unwrap_or(100),
|
||||
};
|
||||
|
||||
state.charging_engine.add_rating_plan(plan.clone()).await
|
||||
.with_context("Failed to add rating plan")?;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"status": "added",
|
||||
"plan_id": plan.plan_id,
|
||||
})))
|
||||
}
|
||||
|
||||
/// DELETE /v1/rating-plans/:id
|
||||
/// Remove a rating plan
|
||||
pub async fn remove_rating_plan(
|
||||
Path(id): Path<String>,
|
||||
State(state): State<AppState>,
|
||||
) -> ChargingResult<Json<serde_json::Value>> {
|
||||
let removed = state.charging_engine.remove_rating_plan(&id).await
|
||||
.with_context("Failed to remove rating plan")?;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"status": if removed { "removed" } else { "not_found" },
|
||||
"plan_id": id,
|
||||
})))
|
||||
}
|
||||
|
||||
/// POST /v1/block/:ip
|
||||
/// Block a user
|
||||
pub async fn block_user(
|
||||
Path(ip): Path<String>,
|
||||
State(state): State<AppState>,
|
||||
) -> ChargingResult<Json<serde_json::Value>> {
|
||||
state.charging_engine.block_user(&ip).await
|
||||
.with_context("Failed to block user")?;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"status": "blocked",
|
||||
"ip": ip,
|
||||
})))
|
||||
}
|
||||
|
||||
/// POST /v1/unblock/:ip
|
||||
/// Unblock a user
|
||||
pub async fn unblock_user(
|
||||
Path(ip): Path<String>,
|
||||
State(state): State<AppState>,
|
||||
) -> ChargingResult<Json<serde_json::Value>> {
|
||||
state.charging_engine.unblock_user(&ip).await
|
||||
.with_context("Failed to unblock user")?;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"status": "unblocked",
|
||||
"ip": ip,
|
||||
})))
|
||||
}
|
||||
|
||||
/// GET /v1/blocked/:ip
|
||||
/// Check if user is blocked
|
||||
pub async fn is_user_blocked(
|
||||
Path(ip): Path<String>,
|
||||
State(state): State<AppState>,
|
||||
) -> ChargingResult<Json<serde_json::Value>> {
|
||||
let blocked = state.charging_engine.is_user_blocked(&ip).await
|
||||
.with_context("Failed to check blocked status")?;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"ip": ip,
|
||||
"blocked": blocked,
|
||||
})))
|
||||
}
|
||||
|
||||
/// GET /v1/stats
|
||||
/// Get system statistics
|
||||
pub async fn get_system_stats(
|
||||
State(state): State<AppState>,
|
||||
) -> ChargingResult<Json<serde_json::Value>> {
|
||||
let stats = state.charging_engine.get_system_statistics().await
|
||||
.with_context("Failed to get system statistics")?;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"total_accounts": stats.total_accounts,
|
||||
"active_sessions": stats.active_sessions,
|
||||
"blocked_users": stats.blocked_users,
|
||||
"low_balance_alerts": stats.low_balance_alerts,
|
||||
"uptime": stats.uptime,
|
||||
})))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user