feat: category apis

This commit is contained in:
brian
2025-06-22 18:02:17 +08:00
parent 366862831c
commit 6834c66974
8 changed files with 241 additions and 8 deletions

View File

@@ -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<crate::AppState> {
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))
}

217
src/api/category.rs Normal file
View File

@@ -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<crate::AppState> {
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<AppState>,
claims: Claims,
) -> Result<Json<Vec<CategoryInfo>>, (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<CategoryInfo> = 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<AppState>,
claims: Claims,
payload: Json<CategoryInfo>,
) -> Result<Json<SimpleResponse>, (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<i64>,
state: State<AppState>,
claims: Claims,
) -> Result<Json<CategoryInfo>, (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<i64>,
state: State<AppState>,
claims: Claims,
payload: Json<CategoryInfo>,
) -> Result<Json<SimpleResponse>, (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))
}

View File

@@ -1 +1,2 @@
pub mod book;
pub mod category;

View File

@@ -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();

View File

@@ -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,
}

View File

@@ -1,2 +1,3 @@
pub mod book;
pub mod common;
pub mod category;

4
src/query/book.rs Normal file
View File

@@ -0,0 +1,4 @@
// use crate::model::db::prelude::Book;
// pub fn get_book_by_id(id:i64, uid:i64)->Option<Book> {
//
// }

1
src/query/mod.rs Normal file
View File

@@ -0,0 +1 @@
mod book;