feat: Add public status page with 10-second polling
Implement status.corrosionmgmt.com public status page showcasing all Corrosion servers that opt-in. Drives platform visibility and attracts new customers. Backend: - Migration 007: status_page_description TEXT column - models/public.rs: PublicServerStatus, PlatformHealth, StatusPageResponse - db/public.rs: get_public_servers() with uptime calculations (24h/7d/30d) - api/public.rs: GET /api/public/status (no auth) - api/settings.rs: public site config endpoints (auth required) Frontend: - StatusPageView.vue: Server grid with live stats, uptime badges, wipe schedules - Platform health header: total servers, online count, total players - Auto-refresh every 10 seconds via polling - Mobile-responsive design - SettingsView.vue: Public Status tab with opt-in toggle Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
85
backend/src/api/settings.rs
Normal file
85
backend/src/api/settings.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
extract::State,
|
||||
routing::{get, put},
|
||||
Json, Router,
|
||||
};
|
||||
|
||||
use crate::db;
|
||||
use crate::middleware::auth::AuthUser;
|
||||
use crate::models::error::{ApiError, ApiResult};
|
||||
use crate::models::public::{PublicSiteConfig, UpdatePublicSiteRequest};
|
||||
use crate::AppState;
|
||||
|
||||
pub fn router() -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
.route("/public-site", get(get_public_site_settings))
|
||||
.route("/public-site", put(update_public_site_settings))
|
||||
}
|
||||
|
||||
/// GET /api/settings/public-site — Get public site configuration
|
||||
async fn get_public_site_settings(
|
||||
auth: AuthUser,
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> ApiResult<Json<PublicSiteConfig>> {
|
||||
let license_id = auth.license_id.ok_or(ApiError::LicenseInvalid)?;
|
||||
|
||||
let config = db::public::get_public_site_config(&state.db, license_id)
|
||||
.await
|
||||
.map_err(|e| ApiError::Internal(format!("Failed to fetch config: {}", e)))?;
|
||||
|
||||
match config {
|
||||
Some(cfg) => Ok(Json(cfg)),
|
||||
None => {
|
||||
// Create default config if none exists
|
||||
let config_id = db::public::create_public_site_config(&state.db, license_id)
|
||||
.await
|
||||
.map_err(|e| ApiError::Internal(format!("Failed to create config: {}", e)))?;
|
||||
|
||||
// Fetch the newly created config
|
||||
let new_config = db::public::get_public_site_config(&state.db, license_id)
|
||||
.await
|
||||
.map_err(|e| ApiError::Internal(format!("Failed to fetch new config: {}", e)))?
|
||||
.ok_or_else(|| ApiError::Internal("Config creation failed".to_string()))?;
|
||||
|
||||
Ok(Json(new_config))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// PUT /api/settings/public-site — Update public site configuration
|
||||
async fn update_public_site_settings(
|
||||
auth: AuthUser,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<UpdatePublicSiteRequest>,
|
||||
) -> ApiResult<Json<serde_json::Value>> {
|
||||
let license_id = auth.license_id.ok_or(ApiError::LicenseInvalid)?;
|
||||
|
||||
// Ensure config exists
|
||||
let config_exists = db::public::get_public_site_config(&state.db, license_id)
|
||||
.await
|
||||
.map_err(|e| ApiError::Internal(format!("Failed to check config: {}", e)))?;
|
||||
|
||||
if config_exists.is_none() {
|
||||
db::public::create_public_site_config(&state.db, license_id)
|
||||
.await
|
||||
.map_err(|e| ApiError::Internal(format!("Failed to create config: {}", e)))?;
|
||||
}
|
||||
|
||||
// Update fields
|
||||
db::public::update_public_site_config(
|
||||
&state.db,
|
||||
license_id,
|
||||
req.show_on_status_page,
|
||||
req.status_page_description,
|
||||
req.site_enabled,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| ApiError::Internal(format!("Failed to update config: {}", e)))?;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"success": true,
|
||||
"message": "Public site settings updated"
|
||||
})))
|
||||
}
|
||||
Reference in New Issue
Block a user