diff --git a/Server/src/routes/web.rs b/Server/src/routes/web.rs index 1d7a804..e7e6713 100644 --- a/Server/src/routes/web.rs +++ b/Server/src/routes/web.rs @@ -16,6 +16,7 @@ use tower_http::cors::{AllowOrigin, CorsLayer}; use crate::folders::{FolderRegistry, WallState, QUOTA_BYTES, MAX_ITEMS}; use crate::photos::{SaveError, MAX_BYTES, MAX_PHOTOS}; +use crate::posts::{Post, PostDraft, PostError, MAX_POSTS}; use crate::routes::extract::AuthUser; use crate::{ @@ -35,7 +36,7 @@ pub fn router(state: AppState) -> Router { "http://localhost:5174".parse().unwrap(), "http://127.0.0.1:5174".parse().unwrap(), ])) - .allow_methods([Method::GET, Method::POST, Method::DELETE]) + .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE]) .allow_headers([header::CONTENT_TYPE, header::AUTHORIZATION]); // 照片接口接收 base64 上传,需放宽 body 上限到 16MB;其余接口保持 @@ -56,6 +57,11 @@ pub fn router(state: AppState) -> Router { let state_routes = Router::new() .route("/api/web/folder/{name}/state", get(get_folder_state).put(save_folder_state)); + // 博客文章路由(正文是文本,默认 2MB body 上限足够)。 + let post_routes = Router::new() + .route("/api/web/posts", get(list_posts).post(create_post)) + .route("/api/web/posts/{id}", get(get_post).put(update_post).delete(delete_post)); + Router::new() .route("/api/web/stats", get(stats)) .route("/api/web/gate", post(gate)) @@ -64,6 +70,7 @@ pub fn router(state: AppState) -> Router { .merge(photo_routes) .merge(folder_routes) .merge(state_routes) + .merge(post_routes) .layer(cors) .with_state(state) } @@ -465,3 +472,85 @@ async fn save_folder_state( Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR), } } + +/* ════════════════════════════════════════════════════════════════ + 博客文章接口(按用户隔离,全部 AuthUser 鉴权) + ════════════════════════════════════════════════════════════════ */ + +#[derive(Serialize)] +struct PostListResponse { + items: Vec, + max: usize, +} + +#[derive(Deserialize)] +struct PostInput { + title: String, + tag: String, + body: String, +} + +/// PostError → HTTP 状态码(与 photos 同构)。 +fn post_status(e: PostError) -> StatusCode { + match e { + PostError::Full => StatusCode::CONFLICT, + PostError::TooLarge => StatusCode::PAYLOAD_TOO_LARGE, + PostError::BadInput | PostError::BadUser => StatusCode::BAD_REQUEST, + PostError::NotFound => StatusCode::NOT_FOUND, + PostError::Io => StatusCode::INTERNAL_SERVER_ERROR, + } +} + +async fn list_posts(AuthUser(user): AuthUser, State(state): State) -> Json { + Json(PostListResponse { + items: state.posts.list(&user), + max: MAX_POSTS, + }) +} + +async fn create_post( + AuthUser(user): AuthUser, + State(state): State, + Json(req): Json, +) -> Result<(StatusCode, Json), StatusCode> { + let draft = PostDraft { title: req.title, tag: req.tag, body: req.body }; + state + .posts + .create(&user, draft) + .map(|p| (StatusCode::CREATED, Json(p))) + .map_err(post_status) +} + +async fn get_post( + AuthUser(user): AuthUser, + State(state): State, + AxPath(id): AxPath, +) -> Result, StatusCode> { + state.posts.read(&user, &id).map(Json).ok_or(StatusCode::NOT_FOUND) +} + +async fn update_post( + AuthUser(user): AuthUser, + State(state): State, + AxPath(id): AxPath, + Json(req): Json, +) -> Result { + let draft = PostDraft { title: req.title, tag: req.tag, body: req.body }; + state + .posts + .update(&user, &id, draft) + .map(|_| StatusCode::NO_CONTENT) + .map_err(post_status) +} + +async fn delete_post( + AuthUser(user): AuthUser, + State(state): State, + AxPath(id): AxPath, +) -> StatusCode { + if state.posts.delete(&user, &id) { + StatusCode::NO_CONTENT + } else { + StatusCode::NOT_FOUND + } +} diff --git a/Server/src/state.rs b/Server/src/state.rs index 3d3aeb9..2938f73 100644 --- a/Server/src/state.rs +++ b/Server/src/state.rs @@ -11,6 +11,7 @@ use crate::auth::{self, DevGate, UserStore, WallUserStore}; use crate::folders::{FolderRegistry, FolderStore}; use crate::monitor::Stats; use crate::photos::PhotoStore; +use crate::posts::PostStore; use crate::weather::WeatherService; #[derive(Clone)] @@ -31,6 +32,8 @@ pub struct AppState { pub folders: FolderRegistry, /// 特殊文件夹图片仓库(按文件夹隔离的磁盘目录)。 pub folder_photos: FolderStore, + /// 个人博客文章仓库(按用户隔离的磁盘目录)。 + pub posts: PostStore, } impl AppState { @@ -47,6 +50,7 @@ impl AppState { photos: PhotoStore::from_env(), folders: FolderRegistry::from_env(), folder_photos: FolderStore::from_env(), + posts: PostStore::from_env(), } } }