mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-10-26 02:09:07 +00:00
Letsencrypt works
This commit is contained in:
@@ -4,6 +4,7 @@ use axum::{
|
||||
response::Json,
|
||||
Json as JsonExtractor,
|
||||
};
|
||||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
use crate::{
|
||||
database::{
|
||||
@@ -64,7 +65,7 @@ pub async fn get_certificate_details(
|
||||
pub async fn create_certificate(
|
||||
State(app_state): State<AppState>,
|
||||
JsonExtractor(cert_data): JsonExtractor<certificate::CreateCertificateDto>,
|
||||
) -> Result<Json<certificate::CertificateResponse>, StatusCode> {
|
||||
) -> Result<Json<certificate::CertificateResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
tracing::info!("Creating certificate: {:?}", cert_data);
|
||||
let repo = CertificateRepository::new(app_state.db.connection().clone());
|
||||
let cert_service = CertificateService::new();
|
||||
@@ -73,9 +74,54 @@ pub async fn create_certificate(
|
||||
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)?
|
||||
.map_err(|e| {
|
||||
tracing::error!("Failed to generate self-signed certificate: {:?}", e);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(json!({
|
||||
"error": "Failed to generate self-signed certificate",
|
||||
"details": format!("{:?}", e)
|
||||
})))
|
||||
})?
|
||||
}
|
||||
_ => return Err(StatusCode::BAD_REQUEST),
|
||||
"letsencrypt" => {
|
||||
// Validate required fields for Let's Encrypt
|
||||
let dns_provider_id = cert_data.dns_provider_id
|
||||
.ok_or((StatusCode::BAD_REQUEST, Json(json!({
|
||||
"error": "DNS provider ID is required for Let's Encrypt certificates"
|
||||
}))))?;
|
||||
let acme_email = cert_data.acme_email
|
||||
.as_ref()
|
||||
.ok_or((StatusCode::BAD_REQUEST, Json(json!({
|
||||
"error": "ACME email is required for Let's Encrypt certificates"
|
||||
}))))?;
|
||||
|
||||
let cert_service = CertificateService::with_db(app_state.db.connection().clone());
|
||||
cert_service.generate_letsencrypt_certificate(
|
||||
&cert_data.domain,
|
||||
dns_provider_id,
|
||||
acme_email,
|
||||
false // production by default
|
||||
).await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Failed to generate Let's Encrypt certificate: {:?}", e);
|
||||
// Return a more detailed error response
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(json!({
|
||||
"error": "Failed to generate Let's Encrypt certificate",
|
||||
"details": format!("{:?}", e)
|
||||
})))
|
||||
})?
|
||||
}
|
||||
"imported" => {
|
||||
// For imported certificates, use provided PEM data
|
||||
if cert_data.certificate_pem.is_empty() || cert_data.private_key.is_empty() {
|
||||
return Err((StatusCode::BAD_REQUEST, Json(json!({
|
||||
"error": "Certificate PEM and private key are required for imported certificates"
|
||||
}))));
|
||||
}
|
||||
(cert_data.certificate_pem.clone(), cert_data.private_key.clone())
|
||||
}
|
||||
_ => return Err((StatusCode::BAD_REQUEST, Json(json!({
|
||||
"error": "Invalid certificate type. Supported types: self_signed, letsencrypt, imported"
|
||||
})))),
|
||||
};
|
||||
|
||||
// Create certificate with generated data
|
||||
@@ -85,7 +131,13 @@ pub async fn create_certificate(
|
||||
|
||||
match repo.create(create_dto).await {
|
||||
Ok(certificate) => Ok(Json(certificate.into())),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to save certificate to database: {:?}", e);
|
||||
Err((StatusCode::INTERNAL_SERVER_ERROR, Json(json!({
|
||||
"error": "Failed to save certificate to database",
|
||||
"details": format!("{:?}", e)
|
||||
}))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
102
src/web/handlers/dns_providers.rs
Normal file
102
src/web/handlers/dns_providers.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::Json,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
database::{
|
||||
entities::dns_provider::{
|
||||
CreateDnsProviderDto, UpdateDnsProviderDto, DnsProviderResponseDto,
|
||||
},
|
||||
repository::DnsProviderRepository,
|
||||
},
|
||||
web::AppState,
|
||||
};
|
||||
|
||||
pub async fn create_dns_provider(
|
||||
State(state): State<AppState>,
|
||||
Json(dto): Json<CreateDnsProviderDto>,
|
||||
) -> Result<Json<DnsProviderResponseDto>, StatusCode> {
|
||||
let repo = DnsProviderRepository::new(state.db.connection().clone());
|
||||
|
||||
match repo.create(dto).await {
|
||||
Ok(provider) => Ok(Json(provider.to_response_dto())),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_dns_providers(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<Vec<DnsProviderResponseDto>>, StatusCode> {
|
||||
let repo = DnsProviderRepository::new(state.db.connection().clone());
|
||||
|
||||
match repo.find_all().await {
|
||||
Ok(providers) => {
|
||||
let responses: Vec<DnsProviderResponseDto> = providers
|
||||
.into_iter()
|
||||
.map(|p| p.to_response_dto())
|
||||
.collect();
|
||||
Ok(Json(responses))
|
||||
}
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_dns_provider(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<Json<DnsProviderResponseDto>, StatusCode> {
|
||||
let repo = DnsProviderRepository::new(state.db.connection().clone());
|
||||
|
||||
match repo.find_by_id(id).await {
|
||||
Ok(Some(provider)) => Ok(Json(provider.to_response_dto())),
|
||||
Ok(None) => Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_dns_provider(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
Json(dto): Json<UpdateDnsProviderDto>,
|
||||
) -> Result<Json<DnsProviderResponseDto>, StatusCode> {
|
||||
let repo = DnsProviderRepository::new(state.db.connection().clone());
|
||||
|
||||
match repo.update(id, dto).await {
|
||||
Ok(Some(updated_provider)) => Ok(Json(updated_provider.to_response_dto())),
|
||||
Ok(None) => Err(StatusCode::NOT_FOUND),
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_dns_provider(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<StatusCode, StatusCode> {
|
||||
let repo = DnsProviderRepository::new(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),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_active_cloudflare_providers(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<Vec<DnsProviderResponseDto>>, StatusCode> {
|
||||
let repo = DnsProviderRepository::new(state.db.connection().clone());
|
||||
|
||||
match repo.find_active_by_type("cloudflare").await {
|
||||
Ok(providers) => {
|
||||
let responses: Vec<DnsProviderResponseDto> = providers
|
||||
.into_iter()
|
||||
.map(|p| p.to_response_dto())
|
||||
.collect();
|
||||
Ok(Json(responses))
|
||||
}
|
||||
Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,13 @@ pub mod servers;
|
||||
pub mod certificates;
|
||||
pub mod templates;
|
||||
pub mod client_configs;
|
||||
pub mod dns_providers;
|
||||
pub mod tasks;
|
||||
|
||||
pub use users::*;
|
||||
pub use servers::*;
|
||||
pub use certificates::*;
|
||||
pub use templates::*;
|
||||
pub use client_configs::*;
|
||||
pub use client_configs::*;
|
||||
pub use dns_providers::*;
|
||||
pub use tasks::*;
|
||||
135
src/web/handlers/tasks.rs
Normal file
135
src/web/handlers/tasks.rs
Normal file
@@ -0,0 +1,135 @@
|
||||
use axum::{
|
||||
extract::State,
|
||||
http::StatusCode,
|
||||
response::Json,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::web::AppState;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TaskStatusResponse {
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub schedule: String,
|
||||
pub status: String,
|
||||
pub last_run: Option<String>,
|
||||
pub next_run: Option<String>,
|
||||
pub total_runs: u64,
|
||||
pub success_count: u64,
|
||||
pub error_count: u64,
|
||||
pub last_error: Option<String>,
|
||||
pub last_duration_ms: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct TasksStatusResponse {
|
||||
pub tasks: HashMap<String, TaskStatusResponse>,
|
||||
pub summary: TasksSummary,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct TasksSummary {
|
||||
pub total_tasks: usize,
|
||||
pub running_tasks: usize,
|
||||
pub successful_tasks: usize,
|
||||
pub failed_tasks: usize,
|
||||
pub idle_tasks: usize,
|
||||
}
|
||||
|
||||
/// Get status of all scheduled tasks
|
||||
pub async fn get_tasks_status(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<TasksStatusResponse>, StatusCode> {
|
||||
// Get task status from the scheduler
|
||||
// For now, we'll return a mock response since we need to expose the scheduler
|
||||
// In a real implementation, you'd store a reference to the TaskScheduler in AppState
|
||||
|
||||
let mut tasks = HashMap::new();
|
||||
let mut running_count = 0;
|
||||
let mut success_count = 0;
|
||||
let mut error_count = 0;
|
||||
let mut idle_count = 0;
|
||||
|
||||
// Mock data for demonstration - in real implementation, get from TaskScheduler
|
||||
let xray_sync_task = TaskStatusResponse {
|
||||
name: "Xray Synchronization".to_string(),
|
||||
description: "Synchronizes database state with xray servers".to_string(),
|
||||
schedule: "0 */5 * * * * (every 5 minutes)".to_string(),
|
||||
status: "Success".to_string(),
|
||||
last_run: Some(chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string()),
|
||||
next_run: Some((chrono::Utc::now() + chrono::Duration::minutes(5)).format("%Y-%m-%d %H:%M:%S UTC").to_string()),
|
||||
total_runs: 120,
|
||||
success_count: 118,
|
||||
error_count: 2,
|
||||
last_error: None,
|
||||
last_duration_ms: Some(1234),
|
||||
};
|
||||
|
||||
let cert_renewal_task = TaskStatusResponse {
|
||||
name: "Certificate Renewal".to_string(),
|
||||
description: "Renews Let's Encrypt certificates that expire within 15 days".to_string(),
|
||||
schedule: "0 0 2 * * * (daily at 2 AM)".to_string(),
|
||||
status: "Idle".to_string(),
|
||||
last_run: Some((chrono::Utc::now() - chrono::Duration::hours(8)).format("%Y-%m-%d %H:%M:%S UTC").to_string()),
|
||||
next_run: Some((chrono::Utc::now() + chrono::Duration::hours(16)).format("%Y-%m-%d %H:%M:%S UTC").to_string()),
|
||||
total_runs: 5,
|
||||
success_count: 5,
|
||||
error_count: 0,
|
||||
last_error: None,
|
||||
last_duration_ms: Some(567),
|
||||
};
|
||||
|
||||
// Count task statuses
|
||||
match xray_sync_task.status.as_str() {
|
||||
"Running" => running_count += 1,
|
||||
"Success" => success_count += 1,
|
||||
"Error" => error_count += 1,
|
||||
"Idle" => idle_count += 1,
|
||||
_ => idle_count += 1,
|
||||
}
|
||||
|
||||
match cert_renewal_task.status.as_str() {
|
||||
"Running" => running_count += 1,
|
||||
"Success" => success_count += 1,
|
||||
"Error" => error_count += 1,
|
||||
"Idle" => idle_count += 1,
|
||||
_ => idle_count += 1,
|
||||
}
|
||||
|
||||
tasks.insert("xray_sync".to_string(), xray_sync_task);
|
||||
tasks.insert("cert_renewal".to_string(), cert_renewal_task);
|
||||
|
||||
let summary = TasksSummary {
|
||||
total_tasks: tasks.len(),
|
||||
running_tasks: running_count,
|
||||
successful_tasks: success_count,
|
||||
failed_tasks: error_count,
|
||||
idle_tasks: idle_count,
|
||||
};
|
||||
|
||||
let response = TasksStatusResponse { tasks, summary };
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
/// Trigger manual execution of a specific task
|
||||
pub async fn trigger_task(
|
||||
State(_state): State<AppState>,
|
||||
axum::extract::Path(task_id): axum::extract::Path<String>,
|
||||
) -> Result<Json<serde_json::Value>, StatusCode> {
|
||||
// In a real implementation, you'd trigger the actual task
|
||||
// For now, return a success response
|
||||
match task_id.as_str() {
|
||||
"xray_sync" | "cert_renewal" => {
|
||||
Ok(Json(serde_json::json!({
|
||||
"success": true,
|
||||
"message": format!("Task '{}' has been triggered", task_id)
|
||||
})))
|
||||
}
|
||||
_ => {
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user