ui redesign, markdown fix + metadata and auth header
This commit is contained in:
+45
-9
@@ -6,19 +6,22 @@ pub mod models;
|
||||
use axum::{
|
||||
Router,
|
||||
extract::DefaultBodyLimit,
|
||||
http::{HeaderValue, header},
|
||||
routing::{delete, get, post},
|
||||
};
|
||||
use std::{env, fs, path::PathBuf, sync::Arc};
|
||||
use tokio::sync::Mutex;
|
||||
use tower_http::{
|
||||
cors::{Any, CorsLayer},
|
||||
cors::{AllowOrigin, CorsLayer},
|
||||
services::ServeDir,
|
||||
};
|
||||
use tracing::{error, info};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub admin_token: String,
|
||||
pub data_dir: PathBuf,
|
||||
pub cookie_secure: bool,
|
||||
pub post_lock: Mutex<()>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@@ -27,13 +30,23 @@ async fn main() {
|
||||
dotenvy::dotenv().ok();
|
||||
|
||||
let port = env::var("PORT").unwrap_or_else(|_| "3000".to_string());
|
||||
let admin_token = env::var("ADMIN_TOKEN").unwrap_or_else(|_| "secret".to_string());
|
||||
let admin_token = env::var("ADMIN_TOKEN")
|
||||
.ok()
|
||||
.filter(|t| !t.trim().is_empty())
|
||||
.expect("ADMIN_TOKEN must be set to a non-empty value");
|
||||
if admin_token.len() < 16 {
|
||||
warn!(
|
||||
"ADMIN_TOKEN is shorter than 16 characters. Use a long random string in production."
|
||||
);
|
||||
}
|
||||
let data_dir_str = env::var("DATA_DIR").unwrap_or_else(|_| "../data".to_string());
|
||||
let data_dir = PathBuf::from(data_dir_str);
|
||||
let cookie_secure = env::var("COOKIE_SECURE")
|
||||
.map(|v| v != "false" && v != "0")
|
||||
.unwrap_or(true);
|
||||
|
||||
info!("Initializing backend with data dir: {:?}", data_dir);
|
||||
|
||||
// Ensure directories exist
|
||||
let posts_dir = data_dir.join("posts");
|
||||
let uploads_dir = data_dir.join("uploads");
|
||||
if let Err(e) = fs::create_dir_all(&posts_dir) {
|
||||
@@ -46,14 +59,36 @@ async fn main() {
|
||||
let state = Arc::new(AppState {
|
||||
admin_token,
|
||||
data_dir,
|
||||
cookie_secure,
|
||||
post_lock: Mutex::new(()),
|
||||
});
|
||||
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin(Any)
|
||||
.allow_methods(Any)
|
||||
.allow_headers(Any);
|
||||
// CORS — locked down by default. Set FRONTEND_ORIGIN to the public URL of
|
||||
// the frontend if you ever expose the backend directly to browsers.
|
||||
// Normal deployments hit the backend through the Astro proxy, which is
|
||||
// server-to-server and not subject to CORS.
|
||||
let cors = match env::var("FRONTEND_ORIGIN").ok().filter(|s| !s.is_empty()) {
|
||||
Some(origin) => {
|
||||
let value = HeaderValue::from_str(&origin)
|
||||
.expect("FRONTEND_ORIGIN must be a valid origin URL");
|
||||
CorsLayer::new()
|
||||
.allow_origin(AllowOrigin::exact(value))
|
||||
.allow_methods([
|
||||
axum::http::Method::GET,
|
||||
axum::http::Method::POST,
|
||||
axum::http::Method::DELETE,
|
||||
axum::http::Method::OPTIONS,
|
||||
])
|
||||
.allow_headers([header::AUTHORIZATION, header::CONTENT_TYPE])
|
||||
.allow_credentials(true)
|
||||
}
|
||||
None => CorsLayer::new(),
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
.route("/api/auth/login", post(handlers::auth::login))
|
||||
.route("/api/auth/logout", post(handlers::auth::logout))
|
||||
.route("/api/auth/me", get(handlers::auth::me))
|
||||
.route(
|
||||
"/api/config",
|
||||
get(handlers::config::get_config).post(handlers::config::update_config),
|
||||
@@ -72,6 +107,7 @@ async fn main() {
|
||||
delete(handlers::upload::delete_upload),
|
||||
)
|
||||
.route("/api/upload", post(handlers::upload::upload_file))
|
||||
.route("/healthz", get(|| async { "ok" }))
|
||||
.nest_service("/uploads", ServeDir::new(uploads_dir))
|
||||
.layer(DefaultBodyLimit::max(50 * 1024 * 1024))
|
||||
.layer(cors)
|
||||
|
||||
Reference in New Issue
Block a user