139 lines
4.3 KiB
Rust
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)))
|
|
}
|