Files
nixpanel-licensing/src/routes/admin.rs
T

139 lines
4.3 KiB
Rust

use axum::{extract::State, http::StatusCode, Json};
use serde::Deserialize;
use crate::{
error::AppResult,
middleware::auth::{AuthAdmin, AuthSuperAdmin},
services::admin::{create_admin, login_admin},
state::AppState,
};
#[derive(Deserialize)]
pub struct LoginRequest {
pub username: String,
pub password: String,
}
#[derive(Deserialize)]
pub struct CreateAdminRequest {
pub username: String,
pub password: String,
pub email: String,
pub role: String,
}
// POST /api/v1/admin/login
pub async fn login(
State(state): State<AppState>,
Json(body): Json<LoginRequest>,
) -> AppResult<Json<serde_json::Value>> {
let token = login_admin(&state.pool, &state.config, &body.username, &body.password).await?;
Ok(Json(serde_json::json!({ "token": token })))
}
// POST /api/v1/admin/users (superadmin only)
pub async fn create_user(
State(state): State<AppState>,
_auth: AuthSuperAdmin,
Json(body): Json<CreateAdminRequest>,
) -> AppResult<(StatusCode, Json<serde_json::Value>)> {
let admin = create_admin(&state.pool, &body.username, &body.password, &body.email, &body.role).await?;
Ok((StatusCode::CREATED, Json(serde_json::json!({
"id": admin.id, "username": admin.username, "role": admin.role
}))))
}
// GET /api/v1/admin/stats
pub async fn stats(
State(state): State<AppState>,
_auth: AuthAdmin,
) -> AppResult<Json<serde_json::Value>> {
let license_counts = sqlx::query!(
"SELECT status, COUNT(*) as count FROM licenses GROUP BY status"
)
.fetch_all(&state.pool)
.await?;
let plan_counts = sqlx::query!(
r#"SELECT p.name, COUNT(l.id) as count
FROM plans p LEFT JOIN licenses l ON p.id = l.plan_id
GROUP BY p.id, p.name"#
)
.fetch_all(&state.pool)
.await?;
let recent_validations = sqlx::query!(
r#"SELECT result, COUNT(*) as count
FROM validation_log
WHERE checked_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
GROUP BY result"#
)
.fetch_all(&state.pool)
.await?;
Ok(Json(serde_json::json!({
"licenses": license_counts.iter().map(|r| serde_json::json!({
"status": r.status, "count": r.count
})).collect::<Vec<_>>(),
"plans": plan_counts.iter().map(|r| serde_json::json!({
"name": r.name, "count": r.count
})).collect::<Vec<_>>(),
"recent_validations": recent_validations.iter().map(|r| serde_json::json!({
"result": r.result, "count": r.count
})).collect::<Vec<_>>(),
})))
}
// GET /api/v1/admin/plans
pub async fn plans(
State(state): State<AppState>,
_auth: AuthAdmin,
) -> AppResult<Json<serde_json::Value>> {
let plans = sqlx::query!("SELECT * FROM plans ORDER BY price_monthly ASC")
.fetch_all(&state.pool)
.await?;
let result: Vec<serde_json::Value> = plans.into_iter().map(|p| serde_json::json!({
"id": p.id, "name": p.name,
"max_domains": p.max_domains, "max_accounts": p.max_accounts,
"max_emails": p.max_emails, "max_databases": p.max_databases,
"max_disk_gb": p.max_disk_gb, "price_monthly": p.price_monthly,
"is_active": p.is_active,
})).collect();
Ok(Json(serde_json::json!(result)))
}
// GET /api/v1/admin/ip/:ip
pub async fn lookup_ip(
State(state): State<AppState>,
_auth: AuthAdmin,
axum::extract::Path(ip): axum::extract::Path<String>,
) -> AppResult<Json<serde_json::Value>> {
use crate::services::license::get_license_by_ip;
let license = get_license_by_ip(&state.pool, &ip)
.await?
.ok_or(crate::error::AppError::NotFound)?;
Ok(Json(serde_json::to_value(&license).unwrap()))
}
// GET /api/v1/admin/ip-conflicts
pub async fn ip_conflicts(
State(state): State<AppState>,
_auth: AuthAdmin,
) -> AppResult<Json<serde_json::Value>> {
let conflicts = sqlx::query!(
r#"SELECT server_ip, COUNT(*) as license_count, GROUP_CONCAT(id) as license_ids
FROM licenses WHERE server_ip IS NOT NULL AND status = 'active'
GROUP BY server_ip HAVING COUNT(*) > 1"#
)
.fetch_all(&state.pool)
.await?;
let result: Vec<serde_json::Value> = conflicts.into_iter().map(|r| serde_json::json!({
"server_ip": r.server_ip, "license_count": r.license_count, "license_ids": r.license_ids,
})).collect();
Ok(Json(serde_json::json!(result)))
}