sfx 0.1.3

SFX is a streamlined, full-stack Rust framework for building small web services with integrated authentication, localization, and config-driven UI components
Documentation
use hotaru::http::*;
use hotaru::prelude::*;

use crate::APP;
use crate::admin::check_is_admin;
use crate::local_auth::LOCAL_AUTH;
use crate::op;

fn normalize_admin_entry(raw: &str) -> Option<String> {
    let trimmed = raw.trim();
    if trimmed.is_empty() {
        return None;
    }

    let entry = if trimmed.contains('@') {
        trimmed.to_string()
    } else {
        format!("{}@local", trimmed)
    };

    let mut parts = entry.splitn(2, '@');
    let uid = parts.next()?;
    let server = parts.next()?;
    if uid.parse::<u32>().is_err() || server.is_empty() {
        return None;
    }

    Some(entry)
}

async fn validate_admin_entry(entry: &str) -> Result<(), String> {
    let mut parts = entry.splitn(2, '@');
    let uid = parts
        .next()
        .and_then(|uid| uid.parse::<u32>().ok())
        .ok_or_else(|| "Invalid admin uid".to_string())?;
    let server = parts.next().unwrap_or_default();

    if server == "local" && LOCAL_AUTH.admin_get_user(uid).await.is_none() {
        return Err("Local user not found".to_string());
    }

    Ok(())
}

endpoint! {
    APP.url("/admin/admins/json"),

    pub admin_entries_json <HTTP> {
        if !check_is_admin(req).await {
            return json_response(object!({ success: false, message: "Unauthorized" }))
                .status(StatusCode::UNAUTHORIZED);
        }
        json_response(object!({
            success: true,
            admins: op::read_admin_entries(),
        }))
    }
}

endpoint! {
    APP.url("/admin/admins"),

    pub admin_entries <HTTP> {
        if !check_is_admin(req).await {
            return json_response(object!({ success: false, message: "Unauthorized" }))
                .status(StatusCode::UNAUTHORIZED);
        }
        if req.method() != POST {
            return json_response(object!({ success: false, message: "Method not allowed" }))
                .status(StatusCode::METHOD_NOT_ALLOWED);
        }

        let form = req.form_or_default().await.clone();
        let raw = form.get_or_default("uid");
        let entry = match normalize_admin_entry(&raw) {
            Some(entry) => entry,
            None => {
                return json_response(object!({ success: false, message: "Invalid admin entry" }))
                    .status(StatusCode::BAD_REQUEST);
            }
        };

        if let Err(message) = validate_admin_entry(&entry).await {
            return json_response(object!({ success: false, message: message }))
                .status(StatusCode::BAD_REQUEST);
        }

        match op::add_admin_entry(&entry) {
            Ok(()) => json_response(object!({ success: true, entry: entry })).status(StatusCode::OK),
            Err(err) => json_response(object!({ success: false, message: err.to_string() }))
                .status(StatusCode::INTERNAL_SERVER_ERROR),
        }
    }
}

endpoint! {
    APP.url("/admin/admins/<entry>/delete"),

    pub admin_entry_delete <HTTP> {
        if !check_is_admin(req).await {
            return json_response(object!({ success: false, message: "Unauthorized" }))
                .status(StatusCode::UNAUTHORIZED);
        }
        if req.method() != POST {
            return json_response(object!({ success: false, message: "Method not allowed" }))
                .status(StatusCode::METHOD_NOT_ALLOWED);
        }

        let entry = req
            .param("entry")
            .map(|entry| hotaru_lib::url_encoding::decode_url_owned(&entry))
            .and_then(|entry| normalize_admin_entry(&entry));

        let entry = match entry {
            Some(entry) => entry,
            None => {
                return json_response(object!({ success: false, message: "Invalid admin entry" }))
                    .status(StatusCode::BAD_REQUEST);
            }
        };

        match op::remove_admin_entry(&entry) {
            Ok(()) => json_response(object!({ success: true })).status(StatusCode::OK),
            Err(err) => json_response(object!({ success: false, message: err.to_string() }))
                .status(StatusCode::INTERNAL_SERVER_ERROR),
        }
    }
}