refactor
This commit is contained in:
105
backend/src/handlers/posts.rs
Normal file
105
backend/src/handlers/posts.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::{HeaderMap, StatusCode},
|
||||
response::IntoResponse,
|
||||
Json,
|
||||
};
|
||||
use std::{fs, sync::Arc};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use crate::{
|
||||
auth::check_auth,
|
||||
error::AppError,
|
||||
models::{CreatePostRequest, PostDetail, PostInfo},
|
||||
AppState,
|
||||
};
|
||||
|
||||
pub async fn create_post(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
Json(payload): Json<CreatePostRequest>,
|
||||
) -> Result<Json<PostDetail>, AppError> {
|
||||
check_auth(&headers, &state.admin_token)?;
|
||||
|
||||
if payload.slug.contains('/') || payload.slug.contains('\\') || payload.slug.contains("..") {
|
||||
return Err(AppError::BadRequest("Invalid slug".to_string()));
|
||||
}
|
||||
|
||||
let file_path = state.data_dir.join("posts").join(format!("{}.md", payload.slug));
|
||||
|
||||
fs::write(&file_path, &payload.content).map_err(|e| {
|
||||
error!("Write error for post {}: {}", payload.slug, e);
|
||||
AppError::Internal("Write error".to_string(), Some(e.to_string()))
|
||||
})?;
|
||||
|
||||
info!("Post created/updated: {}", payload.slug);
|
||||
Ok(Json(PostDetail {
|
||||
slug: payload.slug,
|
||||
content: payload.content,
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn delete_post(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
Path(slug): Path<String>,
|
||||
) -> Result<StatusCode, AppError> {
|
||||
check_auth(&headers, &state.admin_token)?;
|
||||
|
||||
let file_path = state.data_dir.join("posts").join(format!("{}.md", slug));
|
||||
info!("Attempting to delete post at: {:?}", file_path);
|
||||
|
||||
if !file_path.exists() {
|
||||
warn!("Post not found for deletion: {}", slug);
|
||||
return Err(AppError::NotFound("Post not found".to_string()));
|
||||
}
|
||||
|
||||
fs::remove_file(file_path).map_err(|e| {
|
||||
error!("Delete error for post {}: {}", slug, e);
|
||||
AppError::Internal("Delete error".to_string(), Some(e.to_string()))
|
||||
})?;
|
||||
|
||||
info!("Post deleted: {}", slug);
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
pub async fn list_posts(State(state): State<Arc<AppState>>) -> impl IntoResponse {
|
||||
let posts_dir = state.data_dir.join("posts");
|
||||
let mut posts = Vec::new();
|
||||
|
||||
if let Ok(entries) = fs::read_dir(posts_dir) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.extension().and_then(|e| e.to_str()) == Some("md") {
|
||||
if let Some(slug) = path.file_stem().and_then(|s| s.to_str()) {
|
||||
let mut excerpt = String::new();
|
||||
if let Ok(content) = fs::read_to_string(&path) {
|
||||
let clean_content = content.replace("#", "").replace("\n", " ");
|
||||
excerpt = clean_content.chars().take(200).collect::<String>();
|
||||
if clean_content.len() > 200 {
|
||||
excerpt.push_str("...");
|
||||
}
|
||||
}
|
||||
posts.push(PostInfo {
|
||||
slug: slug.to_string(),
|
||||
excerpt: excerpt.trim().to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Json(posts)
|
||||
}
|
||||
|
||||
pub async fn get_post(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(slug): Path<String>,
|
||||
) -> Result<Json<PostDetail>, AppError> {
|
||||
let file_path = state.data_dir.join("posts").join(format!("{}.md", slug));
|
||||
|
||||
match fs::read_to_string(&file_path) {
|
||||
Ok(content) => Ok(Json(PostDetail { slug, content })),
|
||||
Err(_) => Err(AppError::NotFound("Post not found".to_string())),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user