mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-10-25 17:59:08 +00:00
init rust. WIP: tls for inbounds
This commit is contained in:
137
src/web/handlers/certificates.rs
Normal file
137
src/web/handlers/certificates.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::Json,
|
||||
Json as JsonExtractor,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
use crate::{
|
||||
database::{
|
||||
entities::certificate,
|
||||
repository::CertificateRepository,
|
||||
},
|
||||
services::certificates::CertificateService,
|
||||
web::AppState,
|
||||
};
|
||||
|
||||
/// List all certificates
|
||||
pub async fn list_certificates(
|
||||
State(app_state): State<AppState>,
|
||||
) -> Result<Json<Vec<certificate::CertificateResponse>>, StatusCode> {
|
||||
let repo = CertificateRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.find_all().await {
|
||||
Ok(certificates) => {
|
||||
let responses: Vec<certificate::CertificateResponse> = certificates
|
||||
.into_iter()
|
||||
.map(|c| c.into())
|
||||
.collect();
|
||||
Ok(Json(responses))
|
||||
}
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get certificate by ID
|
||||
pub async fn get_certificate(
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Json<certificate::CertificateResponse>, StatusCode> {
|
||||
let repo = CertificateRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.find_by_id(id).await {
|
||||
Ok(Some(certificate)) => Ok(Json(certificate.into())),
|
||||
Ok(None) => Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get certificate details with PEM data by ID
|
||||
pub async fn get_certificate_details(
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Json<certificate::CertificateDetailsResponse>, StatusCode> {
|
||||
let repo = CertificateRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.find_by_id(id).await {
|
||||
Ok(Some(certificate)) => Ok(Json(certificate.into())),
|
||||
Ok(None) => Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new certificate
|
||||
pub async fn create_certificate(
|
||||
State(app_state): State<AppState>,
|
||||
JsonExtractor(cert_data): JsonExtractor<certificate::CreateCertificateDto>,
|
||||
) -> Result<Json<certificate::CertificateResponse>, StatusCode> {
|
||||
tracing::info!("Creating certificate: {:?}", cert_data);
|
||||
let repo = CertificateRepository::new(app_state.db.connection().clone());
|
||||
let cert_service = CertificateService::new();
|
||||
|
||||
// Generate certificate based on type
|
||||
let (cert_pem, private_key) = match cert_data.cert_type.as_str() {
|
||||
"self_signed" => {
|
||||
cert_service.generate_self_signed(&cert_data.domain).await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
}
|
||||
_ => return Err(StatusCode::BAD_REQUEST),
|
||||
};
|
||||
|
||||
// Create certificate with generated data
|
||||
let mut create_dto = cert_data;
|
||||
create_dto.certificate_pem = cert_pem;
|
||||
create_dto.private_key = private_key;
|
||||
|
||||
match repo.create(create_dto).await {
|
||||
Ok(certificate) => Ok(Json(certificate.into())),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update certificate
|
||||
pub async fn update_certificate(
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
JsonExtractor(cert_data): JsonExtractor<certificate::UpdateCertificateDto>,
|
||||
) -> Result<Json<certificate::CertificateResponse>, StatusCode> {
|
||||
let repo = CertificateRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.update(id, cert_data).await {
|
||||
Ok(certificate) => Ok(Json(certificate.into())),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete certificate
|
||||
pub async fn delete_certificate(
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<StatusCode, StatusCode> {
|
||||
let repo = CertificateRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.delete(id).await {
|
||||
Ok(true) => Ok(StatusCode::NO_CONTENT),
|
||||
Ok(false) => Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get certificates expiring soon
|
||||
pub async fn get_expiring_certificates(
|
||||
State(app_state): State<AppState>,
|
||||
) -> Result<Json<Vec<certificate::CertificateResponse>>, StatusCode> {
|
||||
let repo = CertificateRepository::new(app_state.db.connection().clone());
|
||||
|
||||
// Get certificates expiring in next 30 days
|
||||
match repo.find_expiring_soon(30).await {
|
||||
Ok(certificates) => {
|
||||
let responses: Vec<certificate::CertificateResponse> = certificates
|
||||
.into_iter()
|
||||
.map(|c| c.into())
|
||||
.collect();
|
||||
Ok(Json(responses))
|
||||
}
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
9
src/web/handlers/mod.rs
Normal file
9
src/web/handlers/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
pub mod users;
|
||||
pub mod servers;
|
||||
pub mod certificates;
|
||||
pub mod templates;
|
||||
|
||||
pub use users::*;
|
||||
pub use servers::*;
|
||||
pub use certificates::*;
|
||||
pub use templates::*;
|
||||
575
src/web/handlers/servers.rs
Normal file
575
src/web/handlers/servers.rs
Normal file
@@ -0,0 +1,575 @@
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::Json,
|
||||
Json as JsonExtractor,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
use crate::{
|
||||
database::{
|
||||
entities::{server, server_inbound},
|
||||
repository::{ServerRepository, ServerInboundRepository, InboundTemplateRepository, CertificateRepository, InboundUsersRepository},
|
||||
},
|
||||
web::AppState,
|
||||
};
|
||||
|
||||
/// List all servers
|
||||
pub async fn list_servers(
|
||||
State(app_state): State<AppState>,
|
||||
) -> Result<Json<Vec<server::ServerResponse>>, StatusCode> {
|
||||
let repo = ServerRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.find_all().await {
|
||||
Ok(servers) => {
|
||||
let responses: Vec<server::ServerResponse> = servers
|
||||
.into_iter()
|
||||
.map(|s| s.into())
|
||||
.collect();
|
||||
Ok(Json(responses))
|
||||
}
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get server by ID
|
||||
pub async fn get_server(
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Json<server::ServerResponse>, StatusCode> {
|
||||
let repo = ServerRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.find_by_id(id).await {
|
||||
Ok(Some(server)) => Ok(Json(server.into())),
|
||||
Ok(None) => Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new server
|
||||
pub async fn create_server(
|
||||
State(app_state): State<AppState>,
|
||||
Json(server_data): Json<server::CreateServerDto>,
|
||||
) -> Result<Json<server::ServerResponse>, StatusCode> {
|
||||
let repo = ServerRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.create(server_data).await {
|
||||
Ok(server) => Ok(Json(server.into())),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update server
|
||||
pub async fn update_server(
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
Json(server_data): Json<server::UpdateServerDto>,
|
||||
) -> Result<Json<server::ServerResponse>, StatusCode> {
|
||||
let repo = ServerRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.update(id, server_data).await {
|
||||
Ok(server) => Ok(Json(server.into())),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete server
|
||||
pub async fn delete_server(
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<StatusCode, StatusCode> {
|
||||
let repo = ServerRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.delete(id).await {
|
||||
Ok(true) => Ok(StatusCode::NO_CONTENT),
|
||||
Ok(false) => Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Test server connection
|
||||
pub async fn test_server_connection(
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
let repo = ServerRepository::new(app_state.db.connection().clone());
|
||||
|
||||
let server = match repo.find_by_id(id).await {
|
||||
Ok(Some(server)) => server,
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
let endpoint = server.get_grpc_endpoint();
|
||||
|
||||
match app_state.xray_service.test_connection(id, &endpoint).await {
|
||||
Ok(connected) => {
|
||||
// Update server status based on connection test
|
||||
let new_status = if connected { "online" } else { "offline" };
|
||||
let update_dto = server::UpdateServerDto {
|
||||
name: None,
|
||||
hostname: None,
|
||||
grpc_port: None,
|
||||
api_credentials: None,
|
||||
default_certificate_id: None,
|
||||
status: Some(new_status.to_string()),
|
||||
};
|
||||
|
||||
let _ = repo.update(id, update_dto).await; // Ignore update errors for now
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"connected": connected,
|
||||
"endpoint": endpoint
|
||||
})))
|
||||
},
|
||||
Err(e) => {
|
||||
// Update status to error
|
||||
let update_dto = server::UpdateServerDto {
|
||||
name: None,
|
||||
hostname: None,
|
||||
grpc_port: None,
|
||||
api_credentials: None,
|
||||
default_certificate_id: None,
|
||||
status: Some("error".to_string()),
|
||||
};
|
||||
|
||||
let _ = repo.update(id, update_dto).await; // Ignore update errors for now
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"connected": false,
|
||||
"endpoint": endpoint,
|
||||
"error": e.to_string()
|
||||
})))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get server statistics
|
||||
pub async fn get_server_stats(
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
let repo = ServerRepository::new(app_state.db.connection().clone());
|
||||
|
||||
let server = match repo.find_by_id(id).await {
|
||||
Ok(Some(server)) => server,
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
let endpoint = server.get_grpc_endpoint();
|
||||
|
||||
match app_state.xray_service.get_stats(id, &endpoint).await {
|
||||
Ok(stats) => Ok(Json(stats)),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// List server inbounds
|
||||
pub async fn list_server_inbounds(
|
||||
State(app_state): State<AppState>,
|
||||
Path(server_id): Path<Uuid>,
|
||||
) -> Result<Json<Vec<server_inbound::ServerInboundResponse>>, StatusCode> {
|
||||
let repo = ServerInboundRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.find_by_server_id_with_template(server_id).await {
|
||||
Ok(responses) => Ok(Json(responses)),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create server inbound
|
||||
pub async fn create_server_inbound(
|
||||
State(app_state): State<AppState>,
|
||||
Path(server_id): Path<Uuid>,
|
||||
JsonExtractor(inbound_data): JsonExtractor<server_inbound::CreateServerInboundDto>,
|
||||
) -> Result<Json<server_inbound::ServerInboundResponse>, StatusCode> {
|
||||
tracing::info!("Creating server inbound for server {}: {:?}", server_id, inbound_data);
|
||||
|
||||
let server_repo = ServerRepository::new(app_state.db.connection().clone());
|
||||
let inbound_repo = ServerInboundRepository::new(app_state.db.connection().clone());
|
||||
let template_repo = InboundTemplateRepository::new(app_state.db.connection().clone());
|
||||
let cert_repo = CertificateRepository::new(app_state.db.connection().clone());
|
||||
|
||||
// Get server info
|
||||
let server = match server_repo.find_by_id(server_id).await {
|
||||
Ok(Some(server)) => server,
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
// Get template info
|
||||
let template = match template_repo.find_by_id(inbound_data.template_id).await {
|
||||
Ok(Some(template)) => template,
|
||||
Ok(None) => return Err(StatusCode::BAD_REQUEST),
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
// Create inbound in database first with protocol-aware tag
|
||||
let inbound = match inbound_repo.create_with_protocol(server_id, inbound_data, &template.protocol).await {
|
||||
Ok(inbound) => {
|
||||
// Send sync event for immediate synchronization
|
||||
crate::services::events::send_sync_event(
|
||||
crate::services::events::SyncEvent::InboundChanged(server_id)
|
||||
);
|
||||
inbound
|
||||
},
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
// Try to create inbound on xray server only if it's active
|
||||
let endpoint = server.get_grpc_endpoint();
|
||||
if inbound.is_active {
|
||||
// Get certificate data if certificate is specified
|
||||
let (cert_pem, key_pem) = if let Some(cert_id) = inbound.certificate_id {
|
||||
match cert_repo.find_by_id(cert_id).await {
|
||||
Ok(Some(cert)) => {
|
||||
tracing::info!("Using certificate {} for inbound {}", cert.domain, inbound.tag);
|
||||
(Some(cert.certificate_pem()), Some(cert.private_key_pem()))
|
||||
},
|
||||
Ok(None) => {
|
||||
tracing::warn!("Certificate {} not found, creating inbound without TLS", cert_id);
|
||||
(None, None)
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Error fetching certificate {}: {}", cert_id, e);
|
||||
(None, None)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::info!("No certificate specified for inbound {}, creating without TLS", inbound.tag);
|
||||
(None, None)
|
||||
};
|
||||
|
||||
match app_state.xray_service.create_inbound_with_certificate(
|
||||
server_id,
|
||||
&endpoint,
|
||||
&inbound.tag,
|
||||
inbound.port_override.unwrap_or(template.default_port),
|
||||
&template.protocol,
|
||||
template.base_settings.clone(),
|
||||
template.stream_settings.clone(),
|
||||
cert_pem.as_deref(),
|
||||
key_pem.as_deref(),
|
||||
).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("Successfully created inbound {} on xray server {}", inbound.tag, endpoint);
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to create inbound on xray server {}: {}", endpoint, e);
|
||||
// Note: We don't fail the request since the inbound is already in DB
|
||||
// The user can manually sync or retry later
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::info!("Inbound {} created as inactive, skipping xray server creation", inbound.tag);
|
||||
}
|
||||
|
||||
Ok(Json(inbound.into()))
|
||||
}
|
||||
|
||||
/// Update server inbound
|
||||
pub async fn update_server_inbound(
|
||||
State(app_state): State<AppState>,
|
||||
Path((server_id, inbound_id)): Path<(Uuid, Uuid)>,
|
||||
JsonExtractor(inbound_data): JsonExtractor<server_inbound::UpdateServerInboundDto>,
|
||||
) -> Result<Json<server_inbound::ServerInboundResponse>, StatusCode> {
|
||||
tracing::info!("Updating server inbound {} for server {}: {:?}", inbound_id, server_id, inbound_data);
|
||||
|
||||
let server_repo = ServerRepository::new(app_state.db.connection().clone());
|
||||
let inbound_repo = ServerInboundRepository::new(app_state.db.connection().clone());
|
||||
let template_repo = InboundTemplateRepository::new(app_state.db.connection().clone());
|
||||
let cert_repo = CertificateRepository::new(app_state.db.connection().clone());
|
||||
|
||||
// Get server info
|
||||
let server = match server_repo.find_by_id(server_id).await {
|
||||
Ok(Some(server)) => server,
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
// Get current inbound state
|
||||
let current_inbound = match inbound_repo.find_by_id(inbound_id).await {
|
||||
Ok(Some(inbound)) if inbound.server_id == server_id => inbound,
|
||||
Ok(Some(_)) => return Err(StatusCode::BAD_REQUEST),
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
// Check if is_active status is changing
|
||||
let old_is_active = current_inbound.is_active;
|
||||
let new_is_active = inbound_data.is_active.unwrap_or(old_is_active);
|
||||
let endpoint = server.get_grpc_endpoint();
|
||||
|
||||
// Handle xray server changes based on active status change
|
||||
if old_is_active && !new_is_active {
|
||||
// Becoming inactive - remove from xray server
|
||||
tracing::info!("Inbound {} becoming inactive, removing from xray server {}", current_inbound.tag, endpoint);
|
||||
match app_state.xray_service.remove_inbound(server_id, &endpoint, ¤t_inbound.tag).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("Successfully removed inbound {} from xray server", current_inbound.tag);
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to remove inbound {} from xray server: {}", current_inbound.tag, e);
|
||||
// Continue with database update even if xray removal fails
|
||||
}
|
||||
}
|
||||
} else if !old_is_active && new_is_active {
|
||||
// Becoming active - add to xray server
|
||||
tracing::info!("Inbound {} becoming active, adding to xray server {}", current_inbound.tag, endpoint);
|
||||
|
||||
// Get template info for recreation
|
||||
let template = match template_repo.find_by_id(current_inbound.template_id).await {
|
||||
Ok(Some(template)) => template,
|
||||
Ok(None) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
// Use updated port if provided, otherwise keep current
|
||||
let port = inbound_data.port_override.unwrap_or(current_inbound.port_override.unwrap_or(template.default_port));
|
||||
|
||||
// Get certificate data if certificate is specified (could be updated)
|
||||
let certificate_id = inbound_data.certificate_id.or(current_inbound.certificate_id);
|
||||
let (cert_pem, key_pem) = if let Some(cert_id) = certificate_id {
|
||||
match cert_repo.find_by_id(cert_id).await {
|
||||
Ok(Some(cert)) => {
|
||||
tracing::info!("Using certificate {} for inbound {}", cert.domain, current_inbound.tag);
|
||||
(Some(cert.certificate_pem()), Some(cert.private_key_pem()))
|
||||
},
|
||||
Ok(None) => {
|
||||
tracing::warn!("Certificate {} not found, creating inbound without TLS", cert_id);
|
||||
(None, None)
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Error fetching certificate {}: {}", cert_id, e);
|
||||
(None, None)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tracing::info!("No certificate specified for inbound {}, creating without TLS", current_inbound.tag);
|
||||
(None, None)
|
||||
};
|
||||
|
||||
match app_state.xray_service.create_inbound_with_certificate(
|
||||
server_id,
|
||||
&endpoint,
|
||||
¤t_inbound.tag,
|
||||
port,
|
||||
&template.protocol,
|
||||
template.base_settings.clone(),
|
||||
template.stream_settings.clone(),
|
||||
cert_pem.as_deref(),
|
||||
key_pem.as_deref(),
|
||||
).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("Successfully added inbound {} to xray server", current_inbound.tag);
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to add inbound {} to xray server: {}", current_inbound.tag, e);
|
||||
// Continue with database update even if xray creation fails
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update database
|
||||
match inbound_repo.update(inbound_id, inbound_data).await {
|
||||
Ok(updated_inbound) => {
|
||||
// Send sync event for immediate synchronization
|
||||
crate::services::events::send_sync_event(
|
||||
crate::services::events::SyncEvent::InboundChanged(server_id)
|
||||
);
|
||||
Ok(Json(updated_inbound.into()))
|
||||
},
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get server inbound by ID
|
||||
pub async fn get_server_inbound(
|
||||
State(app_state): State<AppState>,
|
||||
Path((server_id, inbound_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<Json<server_inbound::ServerInboundResponse>, StatusCode> {
|
||||
let repo = ServerInboundRepository::new(app_state.db.connection().clone());
|
||||
|
||||
// Verify the inbound belongs to the server
|
||||
match repo.find_by_id(inbound_id).await {
|
||||
Ok(Some(inbound)) if inbound.server_id == server_id => {
|
||||
Ok(Json(inbound.into()))
|
||||
}
|
||||
Ok(Some(_)) => Err(StatusCode::BAD_REQUEST),
|
||||
Ok(None) => Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete server inbound
|
||||
pub async fn delete_server_inbound(
|
||||
State(app_state): State<AppState>,
|
||||
Path((server_id, inbound_id)): Path<(Uuid, Uuid)>,
|
||||
) -> Result<StatusCode, StatusCode> {
|
||||
let server_repo = ServerRepository::new(app_state.db.connection().clone());
|
||||
let inbound_repo = ServerInboundRepository::new(app_state.db.connection().clone());
|
||||
|
||||
// Get server and inbound info
|
||||
let server = match server_repo.find_by_id(server_id).await {
|
||||
Ok(Some(server)) => server,
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
// Verify the inbound belongs to the server
|
||||
let inbound = match inbound_repo.find_by_id(inbound_id).await {
|
||||
Ok(Some(inbound)) if inbound.server_id == server_id => inbound,
|
||||
Ok(Some(_)) => return Err(StatusCode::BAD_REQUEST),
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
// Try to remove inbound from xray server first
|
||||
let endpoint = server.get_grpc_endpoint();
|
||||
match app_state.xray_service.remove_inbound(server_id, &endpoint, &inbound.tag).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("Successfully removed inbound {} from xray server {}", inbound.tag, endpoint);
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to remove inbound from xray server {}: {}", endpoint, e);
|
||||
// Continue with database deletion even if xray removal fails
|
||||
}
|
||||
}
|
||||
|
||||
// Delete from database
|
||||
match inbound_repo.delete(inbound_id).await {
|
||||
Ok(true) => {
|
||||
// Send sync event for immediate synchronization
|
||||
crate::services::events::send_sync_event(
|
||||
crate::services::events::SyncEvent::InboundChanged(server_id)
|
||||
);
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
},
|
||||
Ok(false) => Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add user to server inbound (database only - sync will apply changes)
|
||||
pub async fn add_user_to_inbound(
|
||||
State(app_state): State<AppState>,
|
||||
Path((server_id, inbound_id)): Path<(Uuid, Uuid)>,
|
||||
JsonExtractor(user_data): JsonExtractor<serde_json::Value>,
|
||||
) -> Result<StatusCode, StatusCode> {
|
||||
use crate::database::entities::inbound_users::CreateInboundUserDto;
|
||||
|
||||
let server_repo = ServerRepository::new(app_state.db.connection().clone());
|
||||
let inbound_repo = ServerInboundRepository::new(app_state.db.connection().clone());
|
||||
|
||||
// Get server and inbound to validate they exist
|
||||
let _server = match server_repo.find_by_id(server_id).await {
|
||||
Ok(Some(server)) => server,
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
let inbound = match inbound_repo.find_by_id(inbound_id).await {
|
||||
Ok(Some(inbound)) => inbound,
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
// Verify inbound belongs to server
|
||||
if inbound.server_id != server_id {
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
// Extract user data
|
||||
let username = user_data["username"].as_str().unwrap_or("").to_string();
|
||||
let level = user_data["level"].as_u64().unwrap_or(0) as i32;
|
||||
|
||||
if username.is_empty() {
|
||||
tracing::error!("Missing required user data: username");
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
// Create inbound user repository
|
||||
let inbound_users_repo = InboundUsersRepository::new(app_state.db.connection().clone());
|
||||
|
||||
// Check if username already exists on this inbound
|
||||
if inbound_users_repo.username_exists_on_inbound(&username, inbound_id).await.unwrap_or(false) {
|
||||
tracing::error!("Username '{}' already exists on inbound {}", username, inbound_id);
|
||||
return Err(StatusCode::CONFLICT);
|
||||
}
|
||||
|
||||
// Create inbound user DTO
|
||||
let inbound_user_dto = CreateInboundUserDto {
|
||||
server_inbound_id: inbound_id,
|
||||
username: username.clone(),
|
||||
level: Some(level),
|
||||
};
|
||||
|
||||
// Create user in database
|
||||
match inbound_users_repo.create(inbound_user_dto).await {
|
||||
Ok(created_user) => {
|
||||
tracing::info!("Inbound user created: username={} email={} server={} inbound={}",
|
||||
username, created_user.email, server_id, inbound_id);
|
||||
|
||||
// Send sync event for immediate synchronization
|
||||
crate::services::events::send_sync_event(
|
||||
crate::services::events::SyncEvent::UserAccessChanged(server_id)
|
||||
);
|
||||
|
||||
tracing::info!("User will be synced to xray server immediately via event");
|
||||
Ok(StatusCode::CREATED)
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to create inbound user: {}", e);
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove user from server inbound
|
||||
pub async fn remove_user_from_inbound(
|
||||
State(app_state): State<AppState>,
|
||||
Path((server_id, inbound_id, email)): Path<(Uuid, Uuid, String)>,
|
||||
) -> Result<StatusCode, StatusCode> {
|
||||
let server_repo = ServerRepository::new(app_state.db.connection().clone());
|
||||
let inbound_repo = ServerInboundRepository::new(app_state.db.connection().clone());
|
||||
|
||||
// Get server and inbound
|
||||
let server = match server_repo.find_by_id(server_id).await {
|
||||
Ok(Some(server)) => server,
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
let inbound = match inbound_repo.find_by_id(inbound_id).await {
|
||||
Ok(Some(inbound)) => inbound,
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
// Verify inbound belongs to server
|
||||
if inbound.server_id != server_id {
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
// Get inbound tag
|
||||
let template_repo = InboundTemplateRepository::new(app_state.db.connection().clone());
|
||||
let template = match template_repo.find_by_id(inbound.template_id).await {
|
||||
Ok(Some(template)) => template,
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
let inbound_tag = &inbound.tag;
|
||||
|
||||
// Remove user from xray server
|
||||
match app_state.xray_service.remove_user(server_id, &format!("{}:{}", server.hostname, server.grpc_port), &inbound_tag, &email).await {
|
||||
Ok(_) => {
|
||||
tracing::info!("Successfully removed user {} from server {} inbound {}", email, server_id, inbound_id);
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to remove user {} from server {} inbound {}: {}", email, server_id, inbound_id, e);
|
||||
Err(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
88
src/web/handlers/templates.rs
Normal file
88
src/web/handlers/templates.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::Json,
|
||||
Json as JsonExtractor,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
use crate::{
|
||||
database::{
|
||||
entities::inbound_template,
|
||||
repository::InboundTemplateRepository,
|
||||
},
|
||||
web::AppState,
|
||||
};
|
||||
|
||||
/// List all inbound templates
|
||||
pub async fn list_templates(
|
||||
State(app_state): State<AppState>,
|
||||
) -> Result<Json<Vec<inbound_template::InboundTemplateResponse>>, StatusCode> {
|
||||
let repo = InboundTemplateRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.find_all().await {
|
||||
Ok(templates) => {
|
||||
let responses: Vec<inbound_template::InboundTemplateResponse> = templates
|
||||
.into_iter()
|
||||
.map(|t| t.into())
|
||||
.collect();
|
||||
Ok(Json(responses))
|
||||
}
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get template by ID
|
||||
pub async fn get_template(
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Json<inbound_template::InboundTemplateResponse>, StatusCode> {
|
||||
let repo = InboundTemplateRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.find_by_id(id).await {
|
||||
Ok(Some(template)) => Ok(Json(template.into())),
|
||||
Ok(None) => Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new template
|
||||
pub async fn create_template(
|
||||
State(app_state): State<AppState>,
|
||||
JsonExtractor(template_data): JsonExtractor<inbound_template::CreateInboundTemplateDto>,
|
||||
) -> Result<Json<inbound_template::InboundTemplateResponse>, StatusCode> {
|
||||
tracing::info!("Creating template: {:?}", template_data);
|
||||
let repo = InboundTemplateRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.create(template_data).await {
|
||||
Ok(template) => Ok(Json(template.into())),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update template
|
||||
pub async fn update_template(
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
JsonExtractor(template_data): JsonExtractor<inbound_template::UpdateInboundTemplateDto>,
|
||||
) -> Result<Json<inbound_template::InboundTemplateResponse>, StatusCode> {
|
||||
let repo = InboundTemplateRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.update(id, template_data).await {
|
||||
Ok(template) => Ok(Json(template.into())),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete template
|
||||
pub async fn delete_template(
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<StatusCode, StatusCode> {
|
||||
let repo = InboundTemplateRepository::new(app_state.db.connection().clone());
|
||||
|
||||
match repo.delete(id).await {
|
||||
Ok(true) => Ok(StatusCode::NO_CONTENT),
|
||||
Ok(false) => Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
206
src/web/handlers/users.rs
Normal file
206
src/web/handlers/users.rs
Normal file
@@ -0,0 +1,206 @@
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
response::Json,
|
||||
Json as JsonExtractor,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::database::entities::user::{CreateUserDto, UpdateUserDto, Model as UserModel};
|
||||
use crate::database::repository::UserRepository;
|
||||
use crate::web::AppState;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct PaginationQuery {
|
||||
#[serde(default = "default_page")]
|
||||
pub page: u64,
|
||||
#[serde(default = "default_per_page")]
|
||||
pub per_page: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SearchQuery {
|
||||
pub q: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub pagination: PaginationQuery,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct UsersResponse {
|
||||
pub users: Vec<UserResponse>,
|
||||
pub total: u64,
|
||||
pub page: u64,
|
||||
pub per_page: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct UserResponse {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub comment: Option<String>,
|
||||
pub telegram_id: Option<i64>,
|
||||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
fn default_page() -> u64 { 1 }
|
||||
fn default_per_page() -> u64 { 20 }
|
||||
|
||||
impl From<UserModel> for UserResponse {
|
||||
fn from(user: UserModel) -> Self {
|
||||
Self {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
comment: user.comment,
|
||||
telegram_id: user.telegram_id,
|
||||
created_at: user.created_at,
|
||||
updated_at: user.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all users with pagination
|
||||
pub async fn get_users(
|
||||
State(app_state): State<AppState>,
|
||||
Query(query): Query<PaginationQuery>,
|
||||
) -> Result<Json<UsersResponse>, StatusCode> {
|
||||
let repo = UserRepository::new(app_state.db.connection().clone());
|
||||
|
||||
let users = repo.get_all(query.page, query.per_page)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let total = repo.count()
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let response = UsersResponse {
|
||||
users: users.into_iter().map(UserResponse::from).collect(),
|
||||
total,
|
||||
page: query.page,
|
||||
per_page: query.per_page,
|
||||
};
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
/// Search users by name
|
||||
pub async fn search_users(
|
||||
State(app_state): State<AppState>,
|
||||
Query(query): Query<SearchQuery>,
|
||||
) -> Result<Json<UsersResponse>, StatusCode> {
|
||||
let repo = UserRepository::new(app_state.db.connection().clone());
|
||||
|
||||
let users = if let Some(search_query) = query.q {
|
||||
repo.search_by_name(&search_query, query.pagination.page, query.pagination.per_page)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
} else {
|
||||
repo.get_all(query.pagination.page, query.pagination.per_page)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
};
|
||||
|
||||
let total = repo.count()
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let response = UsersResponse {
|
||||
users: users.into_iter().map(UserResponse::from).collect(),
|
||||
total,
|
||||
page: query.pagination.page,
|
||||
per_page: query.pagination.per_page,
|
||||
};
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
/// Get user by ID
|
||||
pub async fn get_user(
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Json<UserResponse>, StatusCode> {
|
||||
let repo = UserRepository::new(app_state.db.connection().clone());
|
||||
|
||||
let user = repo.get_by_id(id)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
match user {
|
||||
Some(user) => Ok(Json(UserResponse::from(user))),
|
||||
None => Err(StatusCode::NOT_FOUND),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new user
|
||||
pub async fn create_user(
|
||||
State(app_state): State<AppState>,
|
||||
JsonExtractor(dto): JsonExtractor<CreateUserDto>,
|
||||
) -> Result<Json<UserResponse>, StatusCode> {
|
||||
let repo = UserRepository::new(app_state.db.connection().clone());
|
||||
|
||||
// Check if telegram ID is already in use
|
||||
if let Some(telegram_id) = dto.telegram_id {
|
||||
let exists = repo.telegram_id_exists(telegram_id)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
if exists {
|
||||
return Err(StatusCode::CONFLICT);
|
||||
}
|
||||
}
|
||||
|
||||
let user = repo.create(dto)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
Ok(Json(UserResponse::from(user)))
|
||||
}
|
||||
|
||||
/// Update user by ID
|
||||
pub async fn update_user(
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
JsonExtractor(dto): JsonExtractor<UpdateUserDto>,
|
||||
) -> Result<Json<UserResponse>, StatusCode> {
|
||||
let repo = UserRepository::new(app_state.db.connection().clone());
|
||||
|
||||
// Check if telegram ID is already in use by another user
|
||||
if let Some(telegram_id) = dto.telegram_id {
|
||||
if let Some(existing_user) = repo.get_by_telegram_id(telegram_id).await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? {
|
||||
if existing_user.id != id {
|
||||
return Err(StatusCode::CONFLICT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let user = repo.update(id, dto)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
match user {
|
||||
Some(user) => Ok(Json(UserResponse::from(user))),
|
||||
None => Err(StatusCode::NOT_FOUND),
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete user by ID
|
||||
pub async fn delete_user(
|
||||
State(app_state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Json<Value>, StatusCode> {
|
||||
let repo = UserRepository::new(app_state.db.connection().clone());
|
||||
|
||||
let deleted = repo.delete(id)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
if deleted {
|
||||
Ok(Json(json!({ "message": "User deleted successfully" })))
|
||||
} else {
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
}
|
||||
}
|
||||
70
src/web/mod.rs
Normal file
70
src/web/mod.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use anyhow::Result;
|
||||
use axum::{
|
||||
Router,
|
||||
routing::get,
|
||||
http::StatusCode,
|
||||
response::Json,
|
||||
serve,
|
||||
};
|
||||
use serde_json::{json, Value};
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::TcpListener;
|
||||
use tower_http::cors::CorsLayer;
|
||||
use tower_http::services::ServeDir;
|
||||
use tracing::info;
|
||||
|
||||
use crate::config::WebConfig;
|
||||
use crate::database::DatabaseManager;
|
||||
use crate::services::XrayService;
|
||||
|
||||
pub mod handlers;
|
||||
pub mod routes;
|
||||
|
||||
use routes::api_routes;
|
||||
|
||||
/// Application state shared across handlers
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub db: DatabaseManager,
|
||||
#[allow(dead_code)]
|
||||
pub config: WebConfig,
|
||||
pub xray_service: XrayService,
|
||||
}
|
||||
|
||||
/// Start the web server
|
||||
pub async fn start_server(db: DatabaseManager, config: WebConfig) -> Result<()> {
|
||||
let xray_service = XrayService::new();
|
||||
|
||||
let app_state = AppState {
|
||||
db,
|
||||
config: config.clone(),
|
||||
xray_service,
|
||||
};
|
||||
|
||||
// Serve static files
|
||||
let serve_dir = ServeDir::new("static");
|
||||
|
||||
let app = Router::new()
|
||||
.route("/health", get(health_check))
|
||||
.nest("/api", api_routes())
|
||||
.nest_service("/", serve_dir)
|
||||
.layer(CorsLayer::permissive())
|
||||
.with_state(app_state);
|
||||
|
||||
let addr: SocketAddr = format!("{}:{}", config.host, config.port).parse()?;
|
||||
info!("Starting web server on {}", addr);
|
||||
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
serve(listener, app).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Health check endpoint
|
||||
async fn health_check() -> Result<Json<Value>, StatusCode> {
|
||||
Ok(Json(json!({
|
||||
"status": "ok",
|
||||
"service": "xray-admin",
|
||||
"version": env!("CARGO_PKG_VERSION")
|
||||
})))
|
||||
}
|
||||
27
src/web/routes/mod.rs
Normal file
27
src/web/routes/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use axum::{
|
||||
Router,
|
||||
routing::get,
|
||||
};
|
||||
|
||||
use crate::web::{AppState, handlers};
|
||||
|
||||
pub mod servers;
|
||||
|
||||
/// Create API routes
|
||||
pub fn api_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.nest("/users", user_routes())
|
||||
.nest("/servers", servers::server_routes())
|
||||
.nest("/certificates", servers::certificate_routes())
|
||||
.nest("/templates", servers::template_routes())
|
||||
}
|
||||
|
||||
/// User management routes
|
||||
fn user_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(handlers::get_users).post(handlers::create_user))
|
||||
.route("/search", get(handlers::search_users))
|
||||
.route("/:id", get(handlers::get_user)
|
||||
.put(handlers::update_user)
|
||||
.delete(handlers::delete_user))
|
||||
}
|
||||
38
src/web/routes/servers.rs
Normal file
38
src/web/routes/servers.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use axum::{
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
use crate::{
|
||||
web::{AppState, handlers},
|
||||
};
|
||||
|
||||
pub fn server_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
// Server management
|
||||
.route("/", get(handlers::list_servers).post(handlers::create_server))
|
||||
.route("/:id", get(handlers::get_server).put(handlers::update_server).delete(handlers::delete_server))
|
||||
.route("/:id/test", post(handlers::test_server_connection))
|
||||
.route("/:id/stats", get(handlers::get_server_stats))
|
||||
|
||||
// Server inbounds
|
||||
.route("/:server_id/inbounds", get(handlers::list_server_inbounds).post(handlers::create_server_inbound))
|
||||
.route("/:server_id/inbounds/:inbound_id", get(handlers::get_server_inbound).put(handlers::update_server_inbound).delete(handlers::delete_server_inbound))
|
||||
|
||||
// User management for inbounds
|
||||
.route("/:server_id/inbounds/:inbound_id/users", post(handlers::add_user_to_inbound))
|
||||
.route("/:server_id/inbounds/:inbound_id/users/:email", axum::routing::delete(handlers::remove_user_from_inbound))
|
||||
}
|
||||
|
||||
pub fn certificate_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(handlers::list_certificates).post(handlers::create_certificate))
|
||||
.route("/:id", get(handlers::get_certificate).put(handlers::update_certificate).delete(handlers::delete_certificate))
|
||||
.route("/:id/details", get(handlers::get_certificate_details))
|
||||
.route("/expiring", get(handlers::get_expiring_certificates))
|
||||
}
|
||||
|
||||
pub fn template_routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(handlers::list_templates).post(handlers::create_template))
|
||||
.route("/:id", get(handlers::get_template).put(handlers::update_template).delete(handlers::delete_template))
|
||||
}
|
||||
Reference in New Issue
Block a user