diff --git a/src/api/book.rs b/src/api/book.rs index 94e63d6..fed1b62 100644 --- a/src/api/book.rs +++ b/src/api/book.rs @@ -6,7 +6,6 @@ use axum::{ }; use axum_macros::debug_handler; -use crate::middleware::auth; use crate::middleware::auth::Claims; use crate::model::db::book::ActiveModel as BookActiveModel; use crate::model::db::book::Column as BookColumn; @@ -18,14 +17,12 @@ use crate::model::http_body::common::SimpleResponse; use crate::AppState; use sea_orm::sqlx::types::chrono::Local; use sea_orm::{entity::*, query::*}; -use sea_orm::{ColumnTrait, Iden}; +use sea_orm::{ColumnTrait}; pub fn get_nest_handlers() -> Router { Router::new() - .route( - "/{id}", - post(update_book_handler).get(get_book_by_id_handler), - ) + .route("/{id}/update",post(update_book_handler)) + .route("/{id}",get(get_book_by_id_handler)) .route("/", post(create_book_handler).get(get_all_books_handler)) } diff --git a/src/api/category.rs b/src/api/category.rs new file mode 100644 index 0000000..b855f56 --- /dev/null +++ b/src/api/category.rs @@ -0,0 +1,217 @@ +use crate::api::category; +use crate::middleware::auth::Claims; +use crate::model::db::prelude::Category; +use crate::model::db::{ + category::ActiveModel as CategoryActiveModel, category::Column as CategoryColumn, + category::Model as CategoryModel, +}; +use crate::model::http_body::category::CategoryInfo; +use crate::model::http_body::common::{OptionalI64, SimpleResponse}; +use crate::AppState; +use axum::extract::{Path, State}; +use axum::http::StatusCode; +use axum::routing::{get, post}; +use axum::{Json, Router}; +use axum_macros::debug_handler; + +use sea_orm::sqlx::types::chrono::Local; +use sea_orm::QueryFilter; +use sea_orm::{entity::*, query::*}; +use tokio::join; + +pub fn get_nested_handlers() -> Router { + Router::new() + .route("/{id}/update", post(update_category_by_id)) + .route("/{id}", get(get_category_by_id)) + .route("/", post(create_category).get(get_all_categories)) +} + +#[debug_handler] +async fn get_all_categories( + state: State, + claims: Claims, +) -> Result>, (StatusCode, String)> { + let uid = claims.uid.clone(); + let categories_query = Category::find() + .filter(CategoryColumn::Uid.eq(uid)) + .all(&state.conn) + .await; + let category_models = match categories_query { + Ok(categories) => categories, + Err(e) => return Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string())), + }; + let mut category_resp: Vec = Vec::new(); + for category in category_models { + let category_info = CategoryInfo { + id: category.id.into(), + name: category.name, + parent_id: category.parent_id.into(), + }; + category_resp.push(category_info); + } + Ok(Json(category_resp)) +} + +#[debug_handler] +async fn create_category( + state: State, + claims: Claims, + payload: Json, +) -> Result, (StatusCode, String)> { + let uid = claims.uid.clone(); + let parent_id: i64 = match payload.parent_id { + OptionalI64(pid_opt) => pid_opt.unwrap_or_else(|| 0), + }; + let category_active_model = CategoryActiveModel { + name: Set(payload.name.clone()), + uid: Set(uid), + parent_id: Set(parent_id), + ..Default::default() + }; + let insert_res = Category::insert(category_active_model) + .exec(&state.conn) + .await; + match insert_res { + Ok(_) => Ok(Json(SimpleResponse { + code: 0, + message: "success".to_string(), + })), + Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string())), + } +} + +#[debug_handler] +async fn get_category_by_id( + Path(id): Path, + state: State, + claims: Claims, +) -> Result, (StatusCode, String)> { + let uid = claims.uid.clone(); + let category_query_res = Category::find() + .filter(CategoryColumn::Uid.eq(uid)) + .filter(CategoryColumn::Id.eq(id)) + .one(&state.conn) + .await; + let category_query: CategoryModel = match category_query_res { + Ok(r) => match r { + Some(res) => res, + None => return Err((StatusCode::NOT_FOUND, "not found".to_string())), + }, + Err(e) => return Err((StatusCode::INTERNAL_SERVER_ERROR, e.to_string())), + }; + let category_resp = CategoryInfo { + id: category_query.id.into(), + name: category_query.name.clone(), + parent_id: category_query.parent_id.into(), + }; + Ok(Json(category_resp)) +} + +#[debug_handler] +async fn update_category_by_id( + Path(id): Path, + state: State, + claims: Claims, + payload: Json, +) -> Result, (StatusCode, String)> { + let uid = claims.uid.clone(); + let mut parent_category_required = false; + let mut parent_id: i64 = 0; + let category_query = Category::find() + .filter(CategoryColumn::Uid.eq(uid)) + .filter(CategoryColumn::Id.eq(id)) + .one(&state.conn); + let parent_query = match payload.parent_id { + OptionalI64(Some(cid)) => { + if cid > 0 { + parent_category_required = true; + parent_id = cid; + Some( + Category::find() + .filter(CategoryColumn::Uid.eq(uid)) + .filter(CategoryColumn::ParentId.eq(cid)) + .one(&state.conn), + ) + } else { + None + } + } + OptionalI64(None) => None, + }; + + let (category_result, parent_result) = if let Some(parent_query) = parent_query { + // 并发执行两个查询 + let (category, parent) = join!(category_query, parent_query); + // 处理查询结果 + ( + category.map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Database error: {}", e), + ) + })?, + parent.map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Database error: {}", e), + ) + })?, + ) + } else { + // 只查询 category + ( + category_query.await.map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Database error: {}", e), + ) + })?, + None, + ) + }; + + let category = match category_result { + Some(category) => { + category + } + None => { + return Err((StatusCode::NOT_FOUND, "Category not found".to_string())); + } + }; + let parent_category_valid = match parent_result { + Some(_) => { true } + None => { false } + }; + + let mut resp = SimpleResponse { + code: 0, + message: "success".to_string(), + }; + let mut category_active_model : CategoryActiveModel = category.into(); + category_active_model.name = Set(payload.name.clone()); + category_active_model.updated_at = Set(Local::now().naive_utc()); + if parent_category_required && parent_id > 0 { + category_active_model.parent_id = Set(parent_id.into()); + } + + let update_res = category_active_model.update(&state.conn).await; + match update_res { + Ok(_) => { + resp.code = 0; + resp.message = "ok".to_owned(); + } + Err(_) => { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "category update failed".to_string(), + )); + } + } + + if parent_category_required && !parent_category_valid { + resp.code = 1; + resp.message = "Parent category not found".to_string(); + return Err((StatusCode::NOT_FOUND, "Parent category not found".to_string())); + } + Ok(Json(resp)) +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 33369c6..03d6eae 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1 +1,2 @@ -pub mod book; \ No newline at end of file +pub mod book; +pub mod category; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index dc13b8b..6155d09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ mod api; mod middleware; mod model; mod util; +mod query; #[tokio::main] async fn main() { @@ -105,6 +106,7 @@ async fn start_server(config: &Config) { let app = Router::new() .nest("/api/v1/book", api::book::get_nest_handlers()) + .nest("/api/v1/category", api::category::get_nested_handlers()) .with_state(state) .layer(global_layer); let host = config.service.host.clone(); diff --git a/src/model/http_body/category.rs b/src/model/http_body/category.rs new file mode 100644 index 0000000..cf2e8ea --- /dev/null +++ b/src/model/http_body/category.rs @@ -0,0 +1,10 @@ +use serde::{Deserialize, Serialize}; +use super::common::{number_stringify, OptionalI64}; +#[derive(Serialize, Deserialize)] +pub struct CategoryInfo { + #[serde(with="number_stringify")] + pub id: OptionalI64, + pub name: String, + #[serde(with="number_stringify")] + pub parent_id: OptionalI64, +} \ No newline at end of file diff --git a/src/model/http_body/mod.rs b/src/model/http_body/mod.rs index 86fc162..9a4a27c 100644 --- a/src/model/http_body/mod.rs +++ b/src/model/http_body/mod.rs @@ -1,2 +1,3 @@ pub mod book; -pub mod common; \ No newline at end of file +pub mod common; +pub mod category; \ No newline at end of file diff --git a/src/query/book.rs b/src/query/book.rs new file mode 100644 index 0000000..3b8bb87 --- /dev/null +++ b/src/query/book.rs @@ -0,0 +1,4 @@ +// use crate::model::db::prelude::Book; +// pub fn get_book_by_id(id:i64, uid:i64)->Option { +// +// } \ No newline at end of file diff --git a/src/query/mod.rs b/src/query/mod.rs new file mode 100644 index 0000000..1aa588a --- /dev/null +++ b/src/query/mod.rs @@ -0,0 +1 @@ +mod book; \ No newline at end of file