mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-11-03 10:22:18 +00:00
feat: Add subscriber account and usage recording API endpoints with validation
- Add GET /v1/subscriber/:imsi endpoint to retrieve subscriber account details - Add POST /v1/usage endpoint to record usage events with session validation - Remove unused validate_imsi import and dummy validation calls from check_credit - Add validate_amount call for bytes_used in deduct_credit - Add balance retrieval logging and fix IP clone in get_balance response - Add IMSI/session_id validation and error handling for missing fields in record_usage - Return subscriber
This commit is contained in:
@@ -7,7 +7,7 @@ use axum::{
|
|||||||
use tower_http::cors::{Any, CorsLayer};
|
use tower_http::cors::{Any, CorsLayer};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use crate::errors::{ChargingError, ChargingResult, validate_ip, validate_bytes, ErrorContext, log_error, validate_amount, validate_imsi, validate_session_id};
|
use crate::errors::{ChargingError, ChargingResult, validate_ip, validate_bytes, ErrorContext, log_error, validate_amount, validate_session_id};
|
||||||
use crate::models::*;
|
use crate::models::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -26,6 +26,8 @@ pub fn create_router(state: AppState) -> Router {
|
|||||||
.route("/v1/credit/:ip/deduct", post(deduct_credit))
|
.route("/v1/credit/:ip/deduct", post(deduct_credit))
|
||||||
.route("/v1/credit/:ip/add", post(add_credit))
|
.route("/v1/credit/:ip/add", post(add_credit))
|
||||||
.route("/v1/credit/:ip/balance", get(get_balance))
|
.route("/v1/credit/:ip/balance", get(get_balance))
|
||||||
|
.route("/v1/subscriber/:imsi", get(get_subscriber))
|
||||||
|
.route("/v1/usage", post(record_usage))
|
||||||
.route("/health", get(health_check))
|
.route("/health", get(health_check))
|
||||||
.layer(cors)
|
.layer(cors)
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
@@ -41,9 +43,6 @@ pub async fn check_credit(
|
|||||||
// Validate input
|
// Validate input
|
||||||
validate_ip(&ip)?;
|
validate_ip(&ip)?;
|
||||||
validate_bytes(req.bytes_requested)?;
|
validate_bytes(req.bytes_requested)?;
|
||||||
// Use validation functions to ensure they're not removed
|
|
||||||
let _ = validate_imsi(&ip);
|
|
||||||
let _ = validate_session_id(&ip);
|
|
||||||
|
|
||||||
let allowed = state.charging_engine.check_credit(&ip, req.bytes_requested).await
|
let allowed = state.charging_engine.check_credit(&ip, req.bytes_requested).await
|
||||||
.with_context("Failed to check credit")
|
.with_context("Failed to check credit")
|
||||||
@@ -76,6 +75,7 @@ pub async fn deduct_credit(
|
|||||||
// Validate input
|
// Validate input
|
||||||
validate_ip(&ip)?;
|
validate_ip(&ip)?;
|
||||||
validate_bytes(req.bytes_used)?;
|
validate_bytes(req.bytes_used)?;
|
||||||
|
validate_amount(req.bytes_used as f64)?;
|
||||||
|
|
||||||
let new_balance = state.charging_engine.deduct_credit(&ip, req.bytes_used).await
|
let new_balance = state.charging_engine.deduct_credit(&ip, req.bytes_used).await
|
||||||
.with_context("Failed to deduct credit")?;
|
.with_context("Failed to deduct credit")?;
|
||||||
@@ -122,13 +122,75 @@ pub async fn get_balance(
|
|||||||
|
|
||||||
let balance = state.charging_engine.get_balance(&ip).await
|
let balance = state.charging_engine.get_balance(&ip).await
|
||||||
.with_context("Failed to get balance")?;
|
.with_context("Failed to get balance")?;
|
||||||
|
|
||||||
|
info!("Retrieved balance for IP: {}", ip);
|
||||||
|
|
||||||
Ok(Json(BalanceResponse {
|
Ok(Json(BalanceResponse {
|
||||||
ip,
|
ip: ip.clone(),
|
||||||
balance_bytes: balance as i64,
|
balance_bytes: balance as i64,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// GET /v1/subscriber/:imsi
|
||||||
|
/// Get subscriber account by IMSI
|
||||||
|
pub async fn get_subscriber(
|
||||||
|
Path(imsi): Path<String>,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
) -> ChargingResult<Json<serde_json::Value>> {
|
||||||
|
let account = state.charging_engine.get_subscriber_account(&imsi).await
|
||||||
|
.with_context("Failed to get subscriber account")?;
|
||||||
|
|
||||||
|
match account {
|
||||||
|
Some(acc) => Ok(Json(serde_json::json!({
|
||||||
|
"imsi": acc.imsi,
|
||||||
|
"balance": acc.balance,
|
||||||
|
"data_used": acc.data_used,
|
||||||
|
"data_limit": acc.data_limit,
|
||||||
|
"voice_used": acc.voice_used,
|
||||||
|
"voice_limit": acc.voice_limit,
|
||||||
|
"sms_used": acc.sms_used,
|
||||||
|
"sms_limit": acc.sms_limit,
|
||||||
|
}))),
|
||||||
|
None => Err(ChargingError::SubscriberNotFound(imsi)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST /v1/usage
|
||||||
|
/// Record usage event
|
||||||
|
pub async fn record_usage(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Json(req): Json<serde_json::Value>,
|
||||||
|
) -> ChargingResult<Json<serde_json::Value>> {
|
||||||
|
let imsi = req.get("imsi").and_then(|v| v.as_str()).ok_or_else(|| {
|
||||||
|
ChargingError::InvalidInput("Missing IMSI".to_string())
|
||||||
|
})?;
|
||||||
|
let session_id = req.get("session_id").and_then(|v| v.as_str()).ok_or_else(|| {
|
||||||
|
ChargingError::InvalidInput("Missing session_id".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
validate_session_id(session_id)?;
|
||||||
|
|
||||||
|
// Create and record usage event
|
||||||
|
let event = crate::charging::types::UsageEvent {
|
||||||
|
imsi: imsi.to_string(),
|
||||||
|
session_id: session_id.to_string(),
|
||||||
|
volume: req.get("volume").and_then(|v| v.as_u64()).unwrap_or(0),
|
||||||
|
cost: req.get("cost").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
rate: req.get("rate").and_then(|v| v.as_f64()).unwrap_or(0.0),
|
||||||
|
usage_type: crate::charging::types::UsageType::Data,
|
||||||
|
timestamp: chrono::Utc::now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
state.charging_engine.record_usage_event(&event).await
|
||||||
|
.with_context("Failed to record usage event")?;
|
||||||
|
|
||||||
|
Ok(Json(serde_json::json!({
|
||||||
|
"status": "recorded",
|
||||||
|
"imsi": imsi,
|
||||||
|
"session_id": session_id,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
/// GET /health
|
/// GET /health
|
||||||
/// Health check endpoint
|
/// Health check endpoint
|
||||||
pub async fn health_check() -> ChargingResult<Json<HealthResponse>> {
|
pub async fn health_check() -> ChargingResult<Json<HealthResponse>> {
|
||||||
|
|||||||
Reference in New Issue
Block a user