Files
nx9-url-shortener/src/web/pages.rs
T

123 lines
4.0 KiB
Rust

use axum::{
extract::{ConnectInfo, Path, State},
http::{HeaderMap, StatusCode},
response::{Html, IntoResponse, Response},
};
use chrono::Utc;
use std::net::SocketAddr;
use uuid::Uuid;
use crate::analytics::get_client_country;
use crate::models::VisitRecord;
use crate::services::landing_pages::get_landing_page_by_code;
use crate::state::AppState;
use crate::utils::get_client_ip;
// GET /p/:code and GET /p/:code/*slug
// Resolve and render landing page
pub async fn resolve_page(
State(state): State<AppState>,
Path(code): Path<String>,
headers: HeaderMap,
connect_info: Option<ConnectInfo<SocketAddr>>,
) -> Response {
if !crate::utils::validation::validate_page_code(&code) {
return (StatusCode::NOT_FOUND, "Not Found").into_response();
}
let page_opt = match get_landing_page_by_code(&state.db, &code) {
Ok(page) => page,
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Database error").into_response(),
};
match page_opt {
Some(page) => {
// Check state
if page.state == "archived" {
return (StatusCode::GONE, "This landing page has been archived").into_response();
}
// Record view analytics
let ip = get_client_ip(&headers, connect_info);
let country = get_client_country(&headers);
let user_agent = headers
.get("user-agent")
.and_then(|h| h.to_str().ok())
.unwrap_or("Unknown")
.to_string();
let referer = headers
.get("referer")
.and_then(|h| h.to_str().ok())
.unwrap_or("Direct")
.to_string();
let accept_language = headers
.get("accept-language")
.and_then(|h| h.to_str().ok())
.unwrap_or("Unknown")
.to_string();
let record = VisitRecord {
id: Uuid::new_v4().to_string(),
target_type: "page".to_string(),
target_id: page.id.clone(),
timestamp: Utc::now().to_rfc3339(),
ip_address: ip,
user_agent,
referer,
accept_language,
country,
status_code: 200,
};
state.analytics_queue.push(record);
// Render raw HTML
Html(page.html_content).into_response()
}
None => (StatusCode::NOT_FOUND, "Landing page not found").into_response(),
}
}
// GET /
// Serve static root landing page from www/index.html
pub async fn root_landing() -> Response {
let mut target_path = std::path::PathBuf::from("www/index.html");
if !target_path.exists() {
// Search relative to executable
if let Ok(exe_path) = std::env::current_exe() {
if let Some(exe_dir) = exe_path.parent() {
let path1 = exe_dir.join("www/index.html");
if path1.exists() {
target_path = path1;
} else if let Some(parent1) = exe_dir.parent() {
let path2 = parent1.join("www/index.html");
if path2.exists() {
target_path = path2;
} else if let Some(parent2) = parent1.parent() {
let path3 = parent2.join("www/index.html");
if path3.exists() {
target_path = path3;
}
}
}
}
}
}
if !target_path.exists() {
// Search in CARGO_MANIFEST_DIR
if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
let path = std::path::PathBuf::from(manifest_dir).join("www/index.html");
if path.exists() {
target_path = path;
}
}
}
match std::fs::read_to_string(&target_path) {
Ok(content) => Html(content).into_response(),
Err(_) => (StatusCode::NOT_FOUND, "Not Found").into_response(),
}
}