fixed backend errors, added delete and more configuration

This commit is contained in:
2026-03-25 13:20:04 +01:00
parent d47f30a53a
commit 3ec009c86d
7 changed files with 606 additions and 154 deletions

View File

@@ -2,7 +2,7 @@ use axum::{
extract::{DefaultBodyLimit, Multipart, Path, State},
http::{HeaderMap, StatusCode},
response::{IntoResponse, Json},
routing::{get, post},
routing::{get, post, delete},
Router,
};
use serde::{Deserialize, Serialize};
@@ -22,16 +22,22 @@ struct AppState {
#[derive(Serialize, Deserialize, Clone)]
struct SiteConfig {
title: String,
subtitle: String,
footer: String,
favicon: String,
theme: String,
custom_css: String,
}
impl Default for SiteConfig {
fn default() -> Self {
Self {
title: "Narlblog".to_string(),
subtitle: "A clean, modern blog".to_string(),
footer: "Built with Rust & Astro".to_string(),
favicon: "/favicon.svg".to_string(),
theme: "catppuccin-mocha".to_string(),
theme: "mocha".to_string(),
custom_css: "".to_string(),
}
}
}
@@ -63,6 +69,12 @@ struct UploadResponse {
url: String,
}
#[derive(Serialize)]
struct FileInfo {
name: String,
url: String,
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
@@ -92,10 +104,11 @@ async fn main() {
let app = Router::new()
.route("/api/config", get(get_config).post(update_config))
.route("/api/posts", get(list_posts).post(create_post))
.route("/api/posts/{slug}", get(get_post))
.route("/api/posts/{slug}", get(get_post).delete(delete_post))
.route("/api/uploads", get(list_uploads))
.route("/api/upload", post(upload_file))
.nest_service("/uploads", ServeDir::new(uploads_dir))
.layer(DefaultBodyLimit::max(10 * 1024 * 1024)) // 10MB limit
.layer(DefaultBodyLimit::max(50 * 1024 * 1024)) // 50MB limit
.layer(cors)
.with_state(state);
@@ -172,6 +185,62 @@ async fn create_post(
}))
}
async fn delete_post(
State(state): State<Arc<AppState>>,
headers: HeaderMap,
Path(slug): Path<String>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
check_auth(&headers, &state.admin_token)?;
let file_path = state.data_dir.join("posts").join(format!("{}.md", slug));
// Security check to prevent directory traversal
if file_path.parent() != Some(&state.data_dir.join("posts")) {
return Err((
StatusCode::BAD_REQUEST,
Json(ErrorResponse { error: "Invalid slug".to_string() }),
));
}
if file_path.exists() {
fs::remove_file(file_path).map_err(|_| {
(StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: "Delete error".to_string() }))
})?;
Ok(StatusCode::NO_CONTENT)
} else {
Err((
StatusCode::NOT_FOUND,
Json(ErrorResponse { error: "Post not found".to_string() }),
))
}
}
async fn list_uploads(
State(state): State<Arc<AppState>>,
headers: HeaderMap,
) -> Result<Json<Vec<FileInfo>>, (StatusCode, Json<ErrorResponse>)> {
check_auth(&headers, &state.admin_token)?;
let uploads_dir = state.data_dir.join("uploads");
let mut files = Vec::new();
if let Ok(entries) = fs::read_dir(uploads_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() {
if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
files.push(FileInfo {
name: name.to_string(),
url: format!("/uploads/{}", name),
});
}
}
}
}
Ok(Json(files))
}
async fn list_posts(State(state): State<Arc<AppState>>) -> impl IntoResponse {
let posts_dir = state.data_dir.join("posts");
let mut posts = Vec::new();
@@ -222,9 +291,7 @@ async fn upload_file(
) -> Result<Json<UploadResponse>, (StatusCode, Json<ErrorResponse>)> {
check_auth(&headers, &state.admin_token)?;
while let Some(field) = multipart.next_field().await.map_err(|_| {
(StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Multipart error".to_string() }))
})? {
while let Ok(Some(field)) = multipart.next_field().await {
if let Some(file_name) = field.file_name() {
let file_name = slug::slugify(file_name);
let uploads_dir = state.data_dir.join("uploads");
@@ -258,4 +325,4 @@ async fn upload_file(
StatusCode::BAD_REQUEST,
Json(ErrorResponse { error: "No file found".to_string() }),
))
}
}