use std::sync::Arc;
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::IntoResponse;
use serde::{Deserialize, Serialize};
use serde_json::json;
use tuitbot_core::auth::passphrase;
use tuitbot_core::config::Config;
use tuitbot_core::net::local_ip;
use crate::state::AppState;
#[derive(Serialize)]
struct LanStatus {
bind_host: String,
bind_port: u16,
lan_enabled: bool,
local_ip: Option<String>,
passphrase_configured: bool,
}
pub async fn get_status(State(state): State<Arc<AppState>>) -> impl IntoResponse {
let hash = state.passphrase_hash.read().await;
let status = LanStatus {
bind_host: state.bind_host.clone(),
bind_port: state.bind_port,
lan_enabled: state.bind_host == "0.0.0.0",
local_ip: local_ip(),
passphrase_configured: hash.is_some(),
};
axum::Json(serde_json::to_value(status).unwrap())
}
pub async fn reset_passphrase(State(state): State<Arc<AppState>>) -> impl IntoResponse {
match passphrase::reset_passphrase(&state.data_dir) {
Ok(new_passphrase) => {
match passphrase::load_passphrase_hash(&state.data_dir) {
Ok(new_hash) => {
let mut hash = state.passphrase_hash.write().await;
*hash = new_hash;
drop(hash);
let mut mtime = state.passphrase_hash_mtime.write().await;
*mtime = passphrase::passphrase_hash_mtime(&state.data_dir);
}
Err(e) => {
tracing::error!(error = %e, "Failed to reload passphrase hash");
}
}
(
StatusCode::OK,
axum::Json(json!({"passphrase": new_passphrase})),
)
.into_response()
}
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(json!({"error": format!("failed to reset passphrase: {e}")})),
)
.into_response(),
}
}
#[derive(Deserialize)]
pub struct ToggleLanRequest {
host: String,
}
pub async fn toggle_lan(
State(state): State<Arc<AppState>>,
axum::Json(body): axum::Json<ToggleLanRequest>,
) -> impl IntoResponse {
if body.host != "0.0.0.0" && body.host != "127.0.0.1" {
return (
StatusCode::BAD_REQUEST,
axum::Json(json!({"error": "host must be \"0.0.0.0\" or \"127.0.0.1\""})),
)
.into_response();
}
let config_path_str = state.config_path.to_string_lossy().to_string();
let mut config = match Config::load(Some(&config_path_str)) {
Ok(c) => c,
Err(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(json!({"error": format!("failed to load config: {e}")})),
)
.into_response();
}
};
config.server.host = body.host;
let toml_str = match toml::to_string_pretty(&config) {
Ok(s) => s,
Err(e) => {
return (
StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(json!({"error": format!("failed to serialize config: {e}")})),
)
.into_response();
}
};
if let Err(e) = std::fs::write(&state.config_path, toml_str) {
return (
StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(json!({"error": format!("failed to write config: {e}")})),
)
.into_response();
}
(
StatusCode::OK,
axum::Json(json!({"restart_required": true})),
)
.into_response()
}