diff options
| author | ops <ops@localhost> | 2026-10-18 13:00:05 +0000 |
|---|---|---|
| committer | ops <ops@localhost> | 2026-10-18 13:00:05 +0000 |
| commit | e373f154dbeacc5a0e83b4ec73c3aa316239c029 (patch) | |
| tree | d4d5fb2acf3ae1508aab8bb56a63f750f5ae2824 /contact-service/src/main.rs | |
| parent | 902cfd395fb03d6147813560ac5fcd7146702a1d (diff) | |
Remove "Message Me" functionality
Remove the custom message me form to reduce maintenance burden
May potentially add it again (improved).
Diffstat (limited to 'contact-service/src/main.rs')
| -rw-r--r-- | contact-service/src/main.rs | 220 |
1 files changed, 0 insertions, 220 deletions
diff --git a/contact-service/src/main.rs b/contact-service/src/main.rs deleted file mode 100644 index 1abfe04..0000000 --- a/contact-service/src/main.rs +++ /dev/null @@ -1,220 +0,0 @@ -//! Website Contact Form - -// Copyright (C) 2026 Doan Luu Duc Tai <doan@luuduct.ai> -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see <https://www.gnu.org/licenses/>. - -use axum::{ - extract::{Form, State}, - response::Html, - routing::post, - Router, -}; -use chrono::Utc; -use serde::Deserialize; -use std::io::Write; -use std::path::PathBuf; -use std::process::{Command, Stdio}; -use std::sync::Arc; - -#[derive(Clone)] -struct AppConfig { - bind_addr: String, - cert_path: PathBuf, - inbox_dir: PathBuf, - sq_bin: PathBuf, -} - -impl AppConfig { - fn from_env() -> Self { - let home = std::env::var("HOME").unwrap_or_else(|_| "/home/ops".into()); - - Self { - bind_addr: std::env::var("BIND_ADDR").unwrap_or_else(|_| "0.0.0.0:8085".into()), - cert_path: PathBuf::from( - std::env::var("CERT_PATH") - .unwrap_or_else(|_| format!("{}/stack/contact-cert.pgp", home)), - ), - inbox_dir: PathBuf::from( - std::env::var("INBOX_DIR") - .unwrap_or_else(|_| format!("{}/stack/inbox", home)), - ), - sq_bin: PathBuf::from( - std::env::var("SQ_BIN") - .unwrap_or_else(|_| format!("{}/.cargo/bin/sq", home)), - ), - } - } -} - -#[derive(Deserialize)] -struct ContactForm { - sender: Option<String>, - message: String, -} - -#[tokio::main] -async fn main() { - let config = Arc::new(AppConfig::from_env()); - - let app = Router::new() - .route("/send-message", post(handle_message)) - .with_state(config.clone()); - - let listener = tokio::net::TcpListener::bind(&config.bind_addr) - .await - .unwrap_or_else(|_| panic!("Failed to bind to {}", config.bind_addr)); - - println!("Contact backend listening on http://{}", config.bind_addr); - println!("Using PQC Cert: {:?}", config.cert_path); - println!("Storing inbox in: {:?}", config.inbox_dir); - - axum::serve(listener, app).await.unwrap(); -} - -async fn handle_message( - State(config): State<Arc<AppConfig>>, - Form(form): Form<ContactForm>, -) -> Html<&'static str> { - let message = form.message.trim(); - if message.is_empty() { - return Html(ERROR_PAGE); - } - - let raw_sender = form.sender.as_deref().unwrap_or("").trim(); - let sender_display = if raw_sender.is_empty() { - "Anonymous" - } else { - raw_sender - }; - - let now = Utc::now(); - let timestamp_str = now.format("%Y-%m-%d_%H-%M-%S").to_string(); - - let sanitized_sender: String = raw_sender - .chars() - .filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '_') - .take(30) - .collect(); - - let filename = if sanitized_sender.is_empty() { - format!("{}.pgp", timestamp_str) - } else { - format!("{}_{}.pgp", timestamp_str, sanitized_sender) - }; - - let output_path = config.inbox_dir.join(filename); - - let payload = format!( - "=== BEGIN MESSAGE ===\nSender: {}\nTimestamp: {}\n============================\n\n{}", - sender_display, - now.to_rfc3339(), - message - ); - - let mut child = match Command::new(&config.sq_bin) - .args([ - "encrypt", - "--for-file", - config.cert_path.to_str().unwrap_or_default(), - "--without-signature", - ]) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - { - Ok(proc) => proc, - Err(e) => { - eprintln!("Failed to spawn sq binary ({:?}): {}", config.sq_bin, e); - return Html(ERROR_PAGE); - } - }; - - if let Some(mut stdin) = child.stdin.take() { - if stdin.write_all(payload.as_bytes()).is_err() { - return Html(ERROR_PAGE); - } - } - - let output = match child.wait_with_output() { - Ok(out) => out, - Err(_) => return Html(ERROR_PAGE), - }; - - if !output.status.success() { - eprintln!( - "sq encryption failed: {}", - String::from_utf8_lossy(&output.stderr) - ); - return Html(ERROR_PAGE); - } - - if std::fs::write(&output_path, &output.stdout).is_err() { - eprintln!("Failed to write to file {:?}", output_path); - return Html(ERROR_PAGE); - } - - Html(SUCCESS_PAGE) -} - -const SUCCESS_PAGE: &str = r#"<!doctype html> -<html lang="en"> -<head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width,initial-scale=1"> - <title>Message Sent</title> - <link rel="stylesheet" href="/styles.css"> -</head> -<body> - <div id="page"> - <div class="badge">[ JS-free ]</div> - <div id="content"> - <pre> -<a href="/" class="logo"> - ____ ___ _ _ _ -| _ \ / _ \ / \ | \ | | -| | | | | | |/ _ \ | \| | -| |_| | |_| / ___ \| |\ | -|____/ \___/_/ \_\_| \_| LUU DUC TAI -</a> - -+------------------------------------------------------------------------------+ -| [+] Success: Message encrypted and delivered to inbox. | -+------------------------------------------------------------------------------+ - -<a href="/"><-- Return to Home Page</a> - </pre> - <div class="source-footer"> - <a href="https://git.doan.luuduct.ai/stack/tree/contact-service/src/main.rs">[ Source Code ]</a> - </div> - </div> - </div> -</body> -</html>"#; - -const ERROR_PAGE: &str = r#"<!doctype html> -<html lang="en"> -<head> - <meta charset="utf-8"> - <title>Error</title> - <link rel="stylesheet" href="/styles.css"> -</head> -<body> - <pre> -[!] Error: Unable to process or encrypt message. - -<a href="/message.html"><-- Try Again</a> - </pre> -</body> -</html>"#; |
