服务架构调整
This commit is contained in:
parent
5d75c53356
commit
3f1941667c
|
@ -252,9 +252,11 @@ dependencies = [
|
|||
"sea-orm",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"state",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"validator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -755,6 +757,19 @@ dependencies = [
|
|||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"log",
|
||||
"rustversion",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
|
@ -990,6 +1005,16 @@ dependencies = [
|
|||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
|
@ -1116,6 +1141,21 @@ version = "0.4.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"generator",
|
||||
"scoped-tls",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
|
@ -1864,6 +1904,12 @@ version = "1.0.16"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
|
@ -2363,6 +2409,15 @@ dependencies = [
|
|||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "state"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8"
|
||||
dependencies = [
|
||||
"loom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
|
@ -2768,7 +2823,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"idna 0.5.0",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
|
@ -2787,6 +2842,21 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validator"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b92f40481c04ff1f4f61f304d61793c7b56ff76ac1469f1beb199b1445b253bd"
|
||||
dependencies = [
|
||||
"idna 0.4.0",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
|
@ -2899,6 +2969,15 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
|
|
|
@ -17,3 +17,5 @@ tracing = "0.1.40"
|
|||
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "time", "local-time"] }
|
||||
serde_json = "1.0.113"
|
||||
chrono = "0.4.33"
|
||||
state = "0.6.0"
|
||||
validator = "0.16.1"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[web]
|
||||
addr = "0.0.0.0:9527"
|
||||
[server]
|
||||
port = 9527
|
||||
host = "0.0.0.0"
|
||||
|
||||
[mysql]
|
||||
max_cons = 5
|
||||
|
|
|
@ -71,3 +71,13 @@ impl axum::response::IntoResponse for AppError {
|
|||
msg.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 记录错误
|
||||
pub fn log_error(handler_name: &str) -> Box<dyn Fn(AppError) -> AppError> {
|
||||
let handler_name = handler_name.to_string();
|
||||
Box::new(move |err| {
|
||||
tracing::error!("{}: {:?}", handler_name, err);
|
||||
err
|
||||
})
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
pub mod template;
|
||||
pub mod err;
|
||||
pub mod response;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, err::AppError>;
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
use axum::http::{header, HeaderMap, StatusCode};
|
||||
use axum::response::Html;
|
||||
use super::Result;
|
||||
|
||||
pub type HtmlResponse = Html<String>;
|
||||
pub type RedirectResponse = (StatusCode, HeaderMap, ());
|
||||
|
||||
pub fn redirect(url: &str) -> Result<RedirectResponse> {
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(header::LOCATION, url.parse().unwrap());
|
||||
Ok((StatusCode::FOUND, header, ()))
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
use askama::Template;
|
||||
use axum::response::Html;
|
||||
|
||||
use crate::common::err::{AppError, log_error};
|
||||
use crate::common::response::HtmlResponse;
|
||||
use crate::common::Result;
|
||||
|
||||
/// 渲染模板
|
||||
pub fn render<T: Template>(tpl: T, handler_name: &str) -> Result<HtmlResponse> {
|
||||
let out = tpl
|
||||
.render()
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
Ok(Html(out))
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
use std::env;
|
||||
use config::File;
|
||||
use serde::Deserialize;
|
||||
use crate::err::AppError;
|
||||
use crate::Result;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MysqlConfig {
|
||||
pub dsn: String,
|
||||
pub max_cons: u32,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct WebConfig {
|
||||
pub addr: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AppConfig {
|
||||
pub mysql: MysqlConfig,
|
||||
pub web: WebConfig,
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
pub fn new() -> Result<Self> {
|
||||
let run_mode = env::var("RUN_MODE").unwrap_or_else(|_| "dev".into());
|
||||
|
||||
// https://github.com/mehcode/config-rs/blob/master/examples/hierarchical-env/settings.rs
|
||||
config::Config::builder()
|
||||
// Start off by merging in the "default" configuration file
|
||||
.add_source(File::with_name("config/default"))
|
||||
// Add in the current environment file
|
||||
// Default to 'dev' env
|
||||
// Note that this file is _optional_
|
||||
.add_source(
|
||||
File::with_name(&format!("config/{}", run_mode)).required(false),
|
||||
)
|
||||
.build()
|
||||
.map_err(AppError::from)?
|
||||
.try_deserialize()
|
||||
.map_err(AppError::from)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
use crate::config::database::MysqlConfig;
|
||||
use crate::config::server::ServerConfig;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AppConfig {
|
||||
pub mysql: MysqlConfig,
|
||||
pub server: ServerConfig,
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MysqlConfig {
|
||||
pub dsn: String,
|
||||
pub max_cons: u32,
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pub mod server;
|
||||
pub mod database;
|
||||
pub mod app;
|
|
@ -0,0 +1,8 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ServerConfig {
|
||||
///当前服务地址
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
}
|
|
@ -1,130 +1,40 @@
|
|||
use std::sync::Arc;
|
||||
use axum::{Form, Router};
|
||||
use axum::extract::Query;
|
||||
use axum::routing::get;
|
||||
|
||||
use axum::extract::{Query, State};
|
||||
use axum::Form;
|
||||
use sea_orm::{ActiveModelTrait, ColumnTrait, Condition, EntityTrait, NotSet, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect};
|
||||
use sea_orm::ActiveValue::Set;
|
||||
|
||||
use crate::entity::{article, category, tag};
|
||||
use crate::err::AppError;
|
||||
use crate::common::{response::HtmlResponse, Result};
|
||||
use crate::common::response::{redirect, RedirectResponse};
|
||||
use crate::common::template::render;
|
||||
use crate::form::ArticleForm;
|
||||
use crate::handler::{get_conn, HtmlResponse, log_error, redirect, RedirectResponse, render};
|
||||
use crate::param::ArticleParams;
|
||||
use crate::Result;
|
||||
use crate::state::AppState;
|
||||
use crate::view;
|
||||
use crate::service::article::ArticleService;
|
||||
|
||||
pub async fn index(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Query(params): Query<ArticleParams>,
|
||||
) -> Result<HtmlResponse> {
|
||||
pub fn init_router() -> Router {
|
||||
Router::new()
|
||||
.route("/", get(index))
|
||||
.route("/add", get(add_ui).post(add))
|
||||
.route("/tags", get(list_with_tags))
|
||||
}
|
||||
|
||||
pub async fn index(Query(params): Query<ArticleParams>) -> Result<HtmlResponse> {
|
||||
let handler_name = "article/index";
|
||||
let conn = get_conn(&state);
|
||||
|
||||
// 构建条件: 所有未被删除的文章
|
||||
let condition = Condition::all()
|
||||
.add(article::Column::IsDel.eq(false))
|
||||
.add_option(
|
||||
params.keyword_opt().map(|n| article::Column::Title.contains(&n))
|
||||
)
|
||||
.add_option(params.is_del_opt().map(|n| article::Column::IsDel.eq(n)));
|
||||
|
||||
let mut select = article::Entity::find()
|
||||
.filter(condition);
|
||||
|
||||
let page = params.page(); // 当前页码
|
||||
let page_size = params.page_size(); // 每页条数,默认15
|
||||
|
||||
if let Some(ord) = params.order() {
|
||||
select = select.order_by(category::Column::Id, ord);
|
||||
}
|
||||
|
||||
let paginator = select
|
||||
.find_also_related(category::Entity)
|
||||
.paginate(conn, page_size);
|
||||
|
||||
let page_total = paginator.num_pages().await.map_err(AppError::from)?;
|
||||
|
||||
let list: Vec<(article::Model, Option<category::Model>)> = paginator
|
||||
.fetch_page(page)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
let tpl = view::ArticlesTemplate {
|
||||
list,
|
||||
page_total,
|
||||
params,
|
||||
};
|
||||
let tpl = ArticleService::index(handler_name, params).await?;
|
||||
render(tpl, handler_name)
|
||||
}
|
||||
|
||||
pub async fn add_ui(State(state): State<Arc<AppState>>) -> Result<HtmlResponse> {
|
||||
pub async fn add_ui() -> Result<HtmlResponse> {
|
||||
let handler_name = "article/add_ui";
|
||||
let conn = get_conn(&state);
|
||||
|
||||
let categies = category::Entity::find()
|
||||
.filter(category::Column::IsDel.eq(false))
|
||||
.limit(100)
|
||||
.order_by_asc(category::Column::Id)
|
||||
.all(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
let tpl = view::ArticleAddTemplate { categies };
|
||||
let tpl = ArticleService::add_ui(handler_name).await?;
|
||||
render(tpl, handler_name)
|
||||
}
|
||||
|
||||
pub async fn add(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Form(frm): Form<ArticleForm>,
|
||||
) -> Result<RedirectResponse> {
|
||||
pub async fn add(Form(frm): Form<ArticleForm>) -> Result<RedirectResponse> {
|
||||
let handler_name = "article/add";
|
||||
let conn = get_conn(&state);
|
||||
|
||||
article::ActiveModel {
|
||||
id: NotSet,
|
||||
title: Set(frm.title),
|
||||
category_id: Set(frm.category_id),
|
||||
content: Set(frm.content),
|
||||
..Default::default()
|
||||
}.save(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
ArticleService::add(handler_name, frm).await?;
|
||||
redirect("/article?msg=文章添加成功")
|
||||
}
|
||||
|
||||
pub async fn list_with_tags( State(state): State<Arc<AppState>>) -> Result<String> {
|
||||
pub async fn list_with_tags() -> Result<String> {
|
||||
let handler_name = "article/list_with_tags";
|
||||
let conn = get_conn(&state);
|
||||
|
||||
let list = article::Entity::find()
|
||||
.find_with_related(tag::Entity)
|
||||
.all(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
let mut ss = vec![];
|
||||
|
||||
for item in list {
|
||||
let (article, tags) = item;
|
||||
let tags = tags
|
||||
.iter()
|
||||
.map(|tag| format!("【#{} - {}】", &tag.id, &tag.name))
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
.to_string();
|
||||
|
||||
let s = format!(
|
||||
"文章ID: {}, 文章标题: {}, 标签: {}",
|
||||
&article.id, &article.title, tags,
|
||||
);
|
||||
ss.push(s);
|
||||
}
|
||||
|
||||
Ok(ss.join("\n").to_string())
|
||||
ArticleService::list_with_tags(handler_name).await
|
||||
}
|
|
@ -1,86 +1,41 @@
|
|||
use std::sync::Arc;
|
||||
use axum::{Form, Router};
|
||||
use axum::extract::{Path, Query};
|
||||
use axum::routing::get;
|
||||
|
||||
use axum::extract::{Path, Query, State};
|
||||
use axum::Form;
|
||||
use sea_orm::{ActiveModelTrait, ColumnTrait, Condition, EntityTrait, IntoActiveModel, ModelTrait, PaginatorTrait, QueryFilter, QueryOrder};
|
||||
use sea_orm::ActiveValue::Set;
|
||||
|
||||
use crate::{form, Result, view};
|
||||
use crate::entity::{article, category};
|
||||
use crate::err::AppError;
|
||||
use crate::handler::{get_conn, HtmlResponse, log_error, redirect, RedirectResponse, render};
|
||||
use crate::{form, view};
|
||||
use crate::common::{response::HtmlResponse, Result};
|
||||
use crate::common::response::{redirect, RedirectResponse};
|
||||
use crate::common::template::render;
|
||||
use crate::param::{CategoryParams, DelParams};
|
||||
use crate::state::AppState;
|
||||
use crate::service::category::CategoryService;
|
||||
|
||||
pub async fn index(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Query(params): Query<CategoryParams>,
|
||||
) -> Result<HtmlResponse> {
|
||||
let handler_name = "category/index";
|
||||
let conn = get_conn(&state);
|
||||
|
||||
let page = params.page(); // 当前页码
|
||||
let page_size = params.page_size(); // 每页条数,默认15
|
||||
let mut select = category::Entity::find()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add_option(
|
||||
params.keyword_opt().map(|n| category::Column::Name.contains(&n))
|
||||
pub fn init_router() -> Router {
|
||||
Router::new()
|
||||
.route("/", get(index))
|
||||
.route(
|
||||
"/add",
|
||||
get(add_ui).post(add),
|
||||
)
|
||||
.add_option(params.is_del_opt().map(|n| category::Column::IsDel.eq(n)))
|
||||
);
|
||||
.route(
|
||||
"/edit/:id",
|
||||
get(edit_ui).post(edit),
|
||||
)
|
||||
.route("/del/:id", get(del))
|
||||
.route("/del/:id/:real", get(del))
|
||||
.route("/articles/:id", get(articles))
|
||||
}
|
||||
|
||||
if let Some(ord) = params.order() {
|
||||
select = select.order_by(category::Column::Id, ord);
|
||||
}
|
||||
|
||||
let paginator = select.paginate(conn, page_size);
|
||||
|
||||
let page_total = paginator.num_pages().await.map_err(AppError::from)?;
|
||||
|
||||
if page_total == 0 {
|
||||
let tpl = view::CategoryTemplate {
|
||||
categories: vec![],
|
||||
params,
|
||||
page_total,
|
||||
};
|
||||
return render(tpl, handler_name);
|
||||
}
|
||||
|
||||
let categories = paginator
|
||||
.fetch_page(page)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
let tpl = view::CategoryTemplate {
|
||||
categories,
|
||||
params,
|
||||
page_total,
|
||||
};
|
||||
pub async fn index(Query(params): Query<CategoryParams>) -> Result<HtmlResponse> {
|
||||
let handler_name = "category/index";
|
||||
let tpl = CategoryService::index(handler_name, params).await?;
|
||||
render(tpl, handler_name)
|
||||
}
|
||||
|
||||
|
||||
pub async fn find(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<i32>,
|
||||
) -> Result<String> {
|
||||
let id = u32::try_from(id).or(Err(AppError::notfound()))?;
|
||||
|
||||
pub async fn find(Path(id): Path<u32>) -> Result<String> {
|
||||
let handler_name = "category/find";
|
||||
let conn = get_conn(&state);
|
||||
|
||||
let cate = category::Entity::find_by_id(id)
|
||||
.one(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
match cate {
|
||||
None => Err(AppError::notfound()),
|
||||
Some(cate) => Ok(format!("id: {}, 名称: {}", cate.id, cate.name)),
|
||||
}
|
||||
let category_model = CategoryService::find(handler_name, id).await?;
|
||||
Ok(format!("id: {}, 名称: {}", category_model.id, category_model.name))
|
||||
}
|
||||
|
||||
pub async fn add_ui() -> Result<HtmlResponse> {
|
||||
|
@ -89,145 +44,37 @@ pub async fn add_ui() -> Result<HtmlResponse> {
|
|||
render(tpl, handler_name)
|
||||
}
|
||||
|
||||
pub async fn add(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Form(frm): Form<form::CategoryForm>,
|
||||
) -> Result<RedirectResponse> {
|
||||
pub async fn add(Form(frm): Form<form::CategoryForm>) -> Result<RedirectResponse> {
|
||||
let handler_name = "category/add";
|
||||
let conn = get_conn(&state);
|
||||
|
||||
let am = category::ActiveModel {
|
||||
name: Set(frm.name),
|
||||
// all other attributes are `NotSet`
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let added_category = am.insert(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
let url = format!("/category?msg=分类添加成功,ID是:{}", added_category.id);
|
||||
|
||||
let added_category_id = CategoryService::add(handler_name, frm).await?;
|
||||
let url = format!("/category?msg=分类添加成功,ID是:{}", added_category_id);
|
||||
redirect(url.as_str())
|
||||
}
|
||||
|
||||
pub async fn edit_ui(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<i32>,
|
||||
) -> Result<HtmlResponse> {
|
||||
let id = u32::try_from(id).or(Err(AppError::notfound()))?;
|
||||
|
||||
pub async fn edit_ui(Path(id): Path<u32>) -> Result<HtmlResponse> {
|
||||
let handler_name = "category/edit_ui";
|
||||
let conn = get_conn(&state);
|
||||
|
||||
let cate = category::Entity::find_by_id(id)
|
||||
.one(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?
|
||||
.ok_or(AppError::notfound())?;
|
||||
|
||||
let tpl = view::CategoryEditTemplate { category: cate };
|
||||
let tpl = CategoryService::edit_ui(handler_name, id).await?;
|
||||
render(tpl, handler_name)
|
||||
}
|
||||
|
||||
pub async fn edit(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<u32>,
|
||||
Form(frm): Form<form::CategoryForm>,
|
||||
) -> Result<RedirectResponse> {
|
||||
let id = u32::try_from(id).or(Err(AppError::notfound()))?;
|
||||
|
||||
let handler_name = "category/edit";
|
||||
let conn = get_conn(&state);
|
||||
|
||||
let cate = category::Entity::find_by_id(id)
|
||||
.one(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?
|
||||
.ok_or(AppError::notfound())?;
|
||||
|
||||
let mut am = cate.into_active_model();
|
||||
am.name = Set(frm.name);
|
||||
|
||||
am.update(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
CategoryService::edit(handler_name, id, frm).await?;
|
||||
redirect("/category?msg=分类修改成功")
|
||||
}
|
||||
|
||||
|
||||
pub async fn del(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(params): Path<DelParams>,
|
||||
) -> Result<RedirectResponse> {
|
||||
pub async fn del(Path(params): Path<DelParams>) -> Result<RedirectResponse> {
|
||||
let handler_name = "category/del";
|
||||
let conn = get_conn(&state);
|
||||
let real = params.real.unwrap_or(false);
|
||||
|
||||
let cate = category::Entity::find_by_id(params.id)
|
||||
.one(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?
|
||||
.ok_or(AppError::notfound())?;
|
||||
|
||||
if real {
|
||||
cate.delete(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
} else {
|
||||
let mut am = cate.into_active_model();
|
||||
am.is_del = Set(true);
|
||||
am.save(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
}
|
||||
|
||||
CategoryService::del(handler_name, params).await?;
|
||||
redirect("/category?msg=分类删除成功")
|
||||
}
|
||||
|
||||
pub async fn articles(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<u32>,
|
||||
Query(params): Query<CategoryParams>,
|
||||
) -> Result<HtmlResponse> {
|
||||
pub async fn articles(Path(id): Path<u32>, Query(params): Query<CategoryParams>) -> Result<HtmlResponse> {
|
||||
let handler_name = "category/articles";
|
||||
let conn = get_conn(&state);
|
||||
|
||||
let cate = category::Entity::find_by_id(id)
|
||||
.one(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?
|
||||
.ok_or(AppError::notfound())
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
let paginator = cate.find_related(article::Entity)
|
||||
.paginate(conn, params.page_size());
|
||||
|
||||
let articles = paginator.fetch_page(params.page())
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
let page_total = paginator
|
||||
.num_pages()
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
let tpl = view::CategoryArticlesTemplate {
|
||||
params,
|
||||
page_total,
|
||||
category: cate,
|
||||
articles,
|
||||
};
|
||||
let tpl = CategoryService::articles(handler_name, id, params).await?;
|
||||
render(tpl, handler_name)
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
use crate::handler::{HtmlResponse, render};
|
||||
use crate::{Result, view};
|
||||
use crate::common::response::HtmlResponse;
|
||||
use crate::common::Result;
|
||||
use crate::common::template::render;
|
||||
use crate::view;
|
||||
|
||||
pub async fn index() -> Result<HtmlResponse> {
|
||||
let handler_name = "index";
|
||||
|
|
|
@ -1,45 +1,7 @@
|
|||
use askama::Template;
|
||||
use axum::http::{header, HeaderMap, StatusCode};
|
||||
use axum::response::Html;
|
||||
use sea_orm::DatabaseConnection;
|
||||
|
||||
pub use index::index;
|
||||
|
||||
use crate::err::AppError;
|
||||
use crate::Result;
|
||||
use crate::state::AppState;
|
||||
|
||||
pub mod category;
|
||||
|
||||
mod index;
|
||||
pub mod article;
|
||||
|
||||
fn get_conn(state: &AppState) -> &DatabaseConnection {
|
||||
&state.conn
|
||||
}
|
||||
|
||||
type HtmlResponse = Html<String>;
|
||||
type RedirectResponse = (StatusCode, HeaderMap, ());
|
||||
|
||||
/// 渲染模板
|
||||
fn render<T: Template>(tpl: T, handler_name: &str) -> Result<HtmlResponse> {
|
||||
let out = tpl
|
||||
.render()
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
Ok(Html(out))
|
||||
}
|
||||
|
||||
/// 记录错误
|
||||
fn log_error(handler_name: &str) -> Box<dyn Fn(AppError) -> AppError> {
|
||||
let handler_name = handler_name.to_string();
|
||||
Box::new(move |err| {
|
||||
tracing::error!("{}: {:?}", handler_name, err);
|
||||
err
|
||||
})
|
||||
}
|
||||
|
||||
fn redirect(url: &str) -> Result<RedirectResponse> {
|
||||
let mut header = HeaderMap::new();
|
||||
header.insert(header::LOCATION, url.parse().unwrap());
|
||||
Ok((StatusCode::FOUND, header, ()))
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
use std::env;
|
||||
|
||||
use config::File;
|
||||
|
||||
use crate::APPLICATION_CONTEXT;
|
||||
use crate::config::app::AppConfig;
|
||||
|
||||
/// 初始化配置信息
|
||||
pub async fn init_config() {
|
||||
let run_mode = env::var("RUN_MODE").unwrap_or_else(|_| "dev".into());
|
||||
|
||||
// https://github.com/mehcode/config-rs/blob/master/examples/hierarchical-env/settings.rs
|
||||
let config: AppConfig = config::Config::builder()
|
||||
// Start off by merging in the "default" configuration file
|
||||
.add_source(File::with_name("config/default"))
|
||||
// Add in the current environment file
|
||||
// Default to 'dev' env
|
||||
// Note that this file is _optional_
|
||||
.add_source(
|
||||
File::with_name(&format!("config/{}", run_mode)).required(false),
|
||||
)
|
||||
.build()
|
||||
.map_err(|e| tracing::error!("config build failed: {}", e.to_string()))
|
||||
.unwrap()
|
||||
.try_deserialize()
|
||||
.map_err(|e| tracing::error!("config init failed: {}", e.to_string()))
|
||||
.unwrap();
|
||||
|
||||
APPLICATION_CONTEXT.set::<AppConfig>(config);
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::APPLICATION_CONTEXT;
|
||||
use crate::config::app::AppConfig;
|
||||
|
||||
pub async fn init_database() {
|
||||
let cfg = APPLICATION_CONTEXT.get::<AppConfig>();
|
||||
|
||||
let mut opt = ConnectOptions::new(&cfg.mysql.dsn);
|
||||
opt.max_connections(cfg.mysql.max_cons);
|
||||
|
||||
let sea_orm_conn = Database::connect(opt).await.unwrap();
|
||||
|
||||
sea_orm_conn.ping()
|
||||
.await
|
||||
.map_err(|e| error!("database init failed: {}", e.to_string()))
|
||||
.unwrap();
|
||||
|
||||
APPLICATION_CONTEXT.set::<DatabaseConnection>(sea_orm_conn);
|
||||
|
||||
info!("sea_orm[mysql] database success! {}", &cfg.mysql.dsn);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
use tracing_subscriber::{EnvFilter, fmt};
|
||||
use tracing_subscriber::fmt::time::LocalTime;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
pub fn init_trace_logger() {
|
||||
// 设置日志级别 根据RUST_LOG
|
||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
||||
|
||||
let timer = LocalTime::rfc_3339();
|
||||
// 构建layer 控制台打印
|
||||
let console_layer = fmt::layer().with_timer(timer);
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(env_filter)
|
||||
.with(console_layer)
|
||||
.init();
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
pub mod logger;
|
||||
pub mod config;
|
||||
pub mod database;
|
39
src/lib.rs
39
src/lib.rs
|
@ -1,11 +1,40 @@
|
|||
pub mod entity;
|
||||
use ::state::type_map::TypeMapSendSync;
|
||||
use tracing::info;
|
||||
|
||||
pub mod state;
|
||||
pub mod config;
|
||||
pub mod entity;
|
||||
// pub mod state;
|
||||
pub mod handler;
|
||||
pub mod view;
|
||||
pub mod err;
|
||||
pub mod router;
|
||||
pub mod param;
|
||||
pub mod form;
|
||||
pub mod service;
|
||||
pub mod config;
|
||||
pub mod router;
|
||||
pub mod initialize;
|
||||
pub mod common;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, err::AppError>;
|
||||
|
||||
/// 整个项目上下文 ApplicationContext
|
||||
pub static APPLICATION_CONTEXT: TypeMapSendSync = TypeMapSendSync::new();
|
||||
// pub static APPLICATION_CONTEXT: TypeMap![Send + Sync] = <TypeMap![Send + Sync]>::new();
|
||||
|
||||
pub async fn init_context() {
|
||||
// 环境变量初始化 解析 .env 文件
|
||||
dotenvy::dotenv().expect("解析.env文件失败");
|
||||
|
||||
// 日志初始化
|
||||
initialize::logger::init_trace_logger();
|
||||
|
||||
// 配置文件初始化
|
||||
initialize::config::init_config().await;
|
||||
|
||||
info!("ConfigContext init complete");
|
||||
|
||||
// 初始化数据源
|
||||
initialize::database::init_database().await;
|
||||
info!("DataBase init complete");
|
||||
|
||||
// 冻结 只能使用get 避免额外的开销
|
||||
// APPLICATION_CONTEXT.freeze();
|
||||
}
|
||||
|
|
48
src/main.rs
48
src/main.rs
|
@ -1,49 +1,23 @@
|
|||
use std::sync::Arc;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use sea_orm::{ConnectOptions, Database};
|
||||
use tokio::signal;
|
||||
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
|
||||
use tracing_subscriber::fmt::time::LocalTime;
|
||||
|
||||
use axum_with_seaorm::{config::AppConfig, router, state::AppState};
|
||||
|
||||
fn init_trace_logger() {
|
||||
// 设置日志级别 根据RUST_LOG
|
||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
||||
|
||||
let timer = LocalTime::rfc_3339();
|
||||
// 构建layer 控制台打印
|
||||
let console_layer = fmt::layer().with_timer(timer);
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(env_filter)
|
||||
.with(console_layer)
|
||||
.init();
|
||||
}
|
||||
// use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
use axum_with_seaorm::{APPLICATION_CONTEXT, init_context, router};
|
||||
use axum_with_seaorm::config::app::AppConfig;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// 解析 .env 文件
|
||||
dotenvy::dotenv().expect("解析.env文件失败");
|
||||
// 初始化
|
||||
init_context().await;
|
||||
|
||||
let _guard = init_trace_logger();
|
||||
let cfg = APPLICATION_CONTEXT.get::<AppConfig>();
|
||||
let ip_addr = cfg.server.host.parse::<IpAddr>().unwrap();
|
||||
|
||||
let cfg = AppConfig::new()
|
||||
.map_err(|e| tracing::error!("初始化配置失败:{}", e.to_string()))
|
||||
.unwrap();
|
||||
let app = router::init();
|
||||
let listener = tokio::net::TcpListener::bind((ip_addr, cfg.server.port)).await.unwrap();
|
||||
|
||||
let mut opt = ConnectOptions::new(&cfg.mysql.dsn);
|
||||
opt.max_connections(cfg.mysql.max_cons);
|
||||
|
||||
let app = router::init()
|
||||
.with_state(Arc::new(AppState {
|
||||
conn: Database::connect(opt).await.unwrap(),
|
||||
}));
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(&cfg.web.addr).await.unwrap();
|
||||
|
||||
tracing::info!("服务器运行于: {}", listener.local_addr().unwrap());
|
||||
tracing::info!("Server runs on: {}", listener.local_addr().unwrap());
|
||||
|
||||
axum::serve(listener, app).with_graceful_shutdown(shutdown_signal()).await.unwrap()
|
||||
}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
use axum::routing::get;
|
||||
use crate::handler;
|
||||
use crate::state::AppState;
|
||||
|
||||
pub fn init() -> axum::Router<Arc<AppState>> {
|
||||
let category_router = axum::Router::new()
|
||||
.route("/", get(handler::category::index))
|
||||
.route(
|
||||
"/add",
|
||||
get(handler::category::add_ui).post(handler::category::add),
|
||||
)
|
||||
.route(
|
||||
"/edit/:id",
|
||||
get(handler::category::edit_ui).post(handler::category::edit),
|
||||
)
|
||||
.route("/del/:id", get(handler::category::del))
|
||||
.route("/del/:id/:real", get(handler::category::del))
|
||||
.route("/articles/:id", get(handler::category::articles));
|
||||
|
||||
let article_router = axum::Router::new()
|
||||
.route("/", get(handler::article::index))
|
||||
.route("/add", get(handler::article::add_ui).post(handler::article::add))
|
||||
.route("/tags", get(handler::article::list_with_tags));
|
||||
|
||||
axum::Router::new()
|
||||
.route("/", get(handler::index))
|
||||
.nest("/category", category_router)
|
||||
.nest("/article", article_router)
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
use axum::routing::get;
|
||||
use crate::handler;
|
||||
use crate::handler::{category, article};
|
||||
|
||||
pub fn init() -> axum::Router {
|
||||
let category_router = category::init_router();
|
||||
let article_router = article::init_router();
|
||||
|
||||
axum::Router::new()
|
||||
.route("/", get(handler::index))
|
||||
.nest("/category", category_router)
|
||||
.nest("/article", article_router)
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, NotSet, PaginatorTrait};
|
||||
use sea_orm::{ActiveModelTrait, QueryFilter, QueryOrder, QuerySelect};
|
||||
use sea_orm::ActiveValue::Set;
|
||||
|
||||
use crate::{APPLICATION_CONTEXT, view};
|
||||
use crate::common::err::{AppError, log_error};
|
||||
use crate::common::Result;
|
||||
use crate::entity::{article, category, tag};
|
||||
use crate::form::ArticleForm;
|
||||
use crate::param::ArticleParams;
|
||||
|
||||
pub struct ArticleService;
|
||||
|
||||
impl ArticleService {
|
||||
pub async fn index(handler_name: &str, params: ArticleParams) -> Result<view::ArticlesTemplate> {
|
||||
let conn = APPLICATION_CONTEXT.get::<DatabaseConnection>();
|
||||
|
||||
let condition = Condition::all()
|
||||
.add(article::Column::IsDel.eq(false))
|
||||
.add_option(
|
||||
params.keyword_opt().map(|n| article::Column::Title.contains(&n))
|
||||
)
|
||||
.add_option(params.is_del_opt().map(|n| article::Column::IsDel.eq(n)));
|
||||
|
||||
let mut select = article::Entity::find()
|
||||
.filter(condition);
|
||||
|
||||
let page = params.page(); // 当前页码
|
||||
let page_size = params.page_size(); // 每页条数,默认15
|
||||
|
||||
if let Some(ord) = params.order() {
|
||||
select = select.order_by(category::Column::Id, ord);
|
||||
}
|
||||
|
||||
let paginator = select
|
||||
.find_also_related(category::Entity)
|
||||
.paginate(conn, page_size);
|
||||
|
||||
let page_total = paginator.num_pages().await.map_err(AppError::from)?;
|
||||
|
||||
let list: Vec<(article::Model, Option<category::Model>)> = paginator
|
||||
.fetch_page(page)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
Ok(view::ArticlesTemplate {
|
||||
list,
|
||||
page_total,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn add_ui(handler_name: &str) -> Result<view::ArticleAddTemplate> {
|
||||
let conn = APPLICATION_CONTEXT.get::<DatabaseConnection>();
|
||||
|
||||
let categies = category::Entity::find()
|
||||
.filter(category::Column::IsDel.eq(false))
|
||||
.limit(100)
|
||||
.order_by_asc(category::Column::Id)
|
||||
.all(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
Ok(view::ArticleAddTemplate { categies })
|
||||
}
|
||||
|
||||
pub async fn add(handler_name: &str, frm: ArticleForm) -> Result<article::ActiveModel> {
|
||||
let conn = APPLICATION_CONTEXT.get::<DatabaseConnection>();
|
||||
article::ActiveModel {
|
||||
id: NotSet,
|
||||
title: Set(frm.title),
|
||||
category_id: Set(frm.category_id),
|
||||
content: Set(frm.content),
|
||||
..Default::default()
|
||||
}.save(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))
|
||||
}
|
||||
pub async fn list_with_tags(handler_name: &str) -> Result<String> {
|
||||
let conn = APPLICATION_CONTEXT.get::<DatabaseConnection>();
|
||||
|
||||
let list = article::Entity::find()
|
||||
.find_with_related(tag::Entity)
|
||||
.all(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
let mut ss = vec![];
|
||||
|
||||
for item in list {
|
||||
let (article, tags) = item;
|
||||
let tags = tags
|
||||
.iter()
|
||||
.map(|tag| format!("【#{} - {}】", &tag.id, &tag.name))
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
.to_string();
|
||||
|
||||
let s = format!(
|
||||
"文章ID: {}, 文章标题: {}, 标签: {}",
|
||||
&article.id, &article.title, tags,
|
||||
);
|
||||
ss.push(s);
|
||||
}
|
||||
|
||||
Ok(ss.join("\n").to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
use sea_orm::{ActiveModelTrait, ColumnTrait, Condition, DatabaseConnection, EntityTrait, IntoActiveModel, ModelTrait, PaginatorTrait, QueryFilter, QueryOrder};
|
||||
use sea_orm::ActiveValue::Set;
|
||||
|
||||
use crate::{APPLICATION_CONTEXT, form, view};
|
||||
use crate::common::err::{AppError, log_error};
|
||||
use crate::common::Result;
|
||||
use crate::entity::{article, category};
|
||||
use crate::param::{CategoryParams, DelParams};
|
||||
|
||||
pub struct CategoryService;
|
||||
|
||||
impl CategoryService {
|
||||
pub async fn index(handler_name: &str, params: CategoryParams) -> Result<view::CategoryTemplate> {
|
||||
let conn = APPLICATION_CONTEXT.get::<DatabaseConnection>();
|
||||
|
||||
let page = params.page(); // 当前页码
|
||||
let page_size = params.page_size(); // 每页条数,默认15
|
||||
let mut select = category::Entity::find()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add_option(
|
||||
params.keyword_opt().map(|n| category::Column::Name.contains(&n))
|
||||
)
|
||||
.add_option(params.is_del_opt().map(|n| category::Column::IsDel.eq(n)))
|
||||
);
|
||||
|
||||
if let Some(ord) = params.order() {
|
||||
select = select.order_by(category::Column::Id, ord);
|
||||
}
|
||||
|
||||
let paginator = select.paginate(conn, page_size);
|
||||
|
||||
let page_total = paginator.num_pages().await.map_err(AppError::from)?;
|
||||
|
||||
if page_total == 0 {
|
||||
return Ok(view::CategoryTemplate {
|
||||
categories: vec![],
|
||||
params,
|
||||
page_total,
|
||||
});
|
||||
}
|
||||
|
||||
let categories = paginator
|
||||
.fetch_page(page)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
Ok(view::CategoryTemplate {
|
||||
categories,
|
||||
params,
|
||||
page_total,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn find(handler_name: &str, id: u32) -> Result<category::Model> {
|
||||
let conn = APPLICATION_CONTEXT.get::<DatabaseConnection>();
|
||||
|
||||
let cate = category::Entity::find_by_id(id)
|
||||
.one(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
match cate {
|
||||
None => Err(AppError::notfound()),
|
||||
Some(cate) => Ok(cate),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add(handler_name: &str, frm: form::CategoryForm) -> Result<u32> {
|
||||
let conn = APPLICATION_CONTEXT.get::<DatabaseConnection>();
|
||||
|
||||
let am = category::ActiveModel {
|
||||
name: Set(frm.name),
|
||||
// all other attributes are `NotSet`
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let added_category = am.insert(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
Ok(added_category.id)
|
||||
}
|
||||
|
||||
pub async fn edit_ui(handler_name: &str, id: u32) -> Result<view::CategoryEditTemplate> {
|
||||
let conn = APPLICATION_CONTEXT.get::<DatabaseConnection>();
|
||||
|
||||
let cate = category::Entity::find_by_id(id)
|
||||
.one(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?
|
||||
.ok_or(AppError::notfound())?;
|
||||
|
||||
Ok(view::CategoryEditTemplate { category: cate })
|
||||
}
|
||||
|
||||
pub async fn edit(handler_name: &str, id: u32, frm: form::CategoryForm) -> Result<()> {
|
||||
let conn = APPLICATION_CONTEXT.get::<DatabaseConnection>();
|
||||
|
||||
let cate = category::Entity::find_by_id(id)
|
||||
.one(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?
|
||||
.ok_or(AppError::notfound())?;
|
||||
|
||||
let mut am = cate.into_active_model();
|
||||
am.name = Set(frm.name);
|
||||
|
||||
am.save(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
// am.update(conn)
|
||||
// .await
|
||||
// .map_err(AppError::from)
|
||||
// .map_err(log_error(handler_name))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub async fn del(handler_name: &str, params: DelParams) -> Result<()> {
|
||||
let conn = APPLICATION_CONTEXT.get::<DatabaseConnection>();
|
||||
let real = params.real.unwrap_or(false);
|
||||
|
||||
let cate = category::Entity::find_by_id(params.id)
|
||||
.one(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?
|
||||
.ok_or(AppError::notfound())?;
|
||||
|
||||
if real {
|
||||
cate.delete(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
} else {
|
||||
let mut am = cate.into_active_model();
|
||||
am.is_del = Set(true);
|
||||
am.save(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn articles(handler_name: &str, id: u32, params: CategoryParams) -> Result<view::CategoryArticlesTemplate> {
|
||||
let conn = APPLICATION_CONTEXT.get::<DatabaseConnection>();
|
||||
|
||||
let cate = category::Entity::find_by_id(id)
|
||||
.one(conn)
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?
|
||||
.ok_or(AppError::notfound())
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
let paginator = cate.find_related(article::Entity)
|
||||
.paginate(conn, params.page_size());
|
||||
|
||||
let articles = paginator.fetch_page(params.page())
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
let page_total = paginator
|
||||
.num_pages()
|
||||
.await
|
||||
.map_err(AppError::from)
|
||||
.map_err(log_error(handler_name))?;
|
||||
|
||||
Ok(view::CategoryArticlesTemplate {
|
||||
params,
|
||||
page_total,
|
||||
category: cate,
|
||||
articles,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
pub mod article;
|
||||
pub mod category;
|
Loading…
Reference in New Issue