From 84458b3d3eeb7be2e56b88c233dac464ea0a7dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=A4=A9?= Date: Sun, 4 Feb 2024 16:33:41 +0800 Subject: [PATCH] Init --- .env | 5 + app.sql | 52 +++++++ config/default.toml | 6 + config/dev.toml | 6 + src/config.rs | 46 ++++++ src/entity/article.rs | 49 +++++++ src/entity/category.rs | 36 +++++ src/entity/mod.rs | 2 + src/err.rs | 73 ++++++++++ src/form.rs | 12 ++ src/handler/article.rs | 52 +++++++ src/handler/category.rs | 233 +++++++++++++++++++++++++++++++ src/handler/index.rs | 8 ++ src/handler/mod.rs | 45 ++++++ src/lib.rs | 11 ++ src/param.rs | 103 ++++++++++++++ src/router.rs | 22 +++ src/state.rs | 6 + src/view.rs | 47 +++++++ templates/article-add.html | 39 ++++++ templates/article.html | 46 ++++++ templates/base.html | 160 +++++++++++++++++++++ templates/category-add.html | 20 +++ templates/category-articles.html | 42 ++++++ templates/category-edit.html | 19 +++ templates/category.html | 91 ++++++++++++ templates/index.html | 2 + 27 files changed, 1233 insertions(+) create mode 100644 .env create mode 100644 app.sql create mode 100644 config/default.toml create mode 100644 config/dev.toml create mode 100644 src/config.rs create mode 100644 src/entity/article.rs create mode 100644 src/entity/category.rs create mode 100644 src/entity/mod.rs create mode 100644 src/err.rs create mode 100644 src/form.rs create mode 100644 src/handler/article.rs create mode 100644 src/handler/category.rs create mode 100644 src/handler/index.rs create mode 100644 src/handler/mod.rs create mode 100644 src/lib.rs create mode 100644 src/param.rs create mode 100644 src/router.rs create mode 100644 src/state.rs create mode 100644 src/view.rs create mode 100644 templates/article-add.html create mode 100644 templates/article.html create mode 100644 templates/base.html create mode 100644 templates/category-add.html create mode 100644 templates/category-articles.html create mode 100644 templates/category-edit.html create mode 100644 templates/category.html create mode 100644 templates/index.html diff --git a/.env b/.env new file mode 100644 index 0000000..7db7232 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +# 日志级别 +RUST_LOG=INFO + +# 运行模式 +RUN_MODE=dev diff --git a/app.sql b/app.sql new file mode 100644 index 0000000..e00493e --- /dev/null +++ b/app.sql @@ -0,0 +1,52 @@ +CREATE TABLE categoies +( -- 分类 + id int unsigned PRIMARY KEY AUTO_INCREMENT, -- 自增主键 + name VARCHAR(20) NOT NULL UNIQUE, -- 分类名称 + is_del BOOLEAN NOT NULL DEFAULT FALSE -- 是否删除 +); + +CREATE TABLE articles +( -- 文章 + id int unsigned PRIMARY KEY AUTO_INCREMENT, -- 自增主键 + category_id INT unsigned NOT NULL REFERENCES categoies (id), -- 文章所属分类的ID,外键 + title VARCHAR(255) NOT NULL, -- 文章标题 + content TEXT NOT NULL, -- 文章内容 + dateline timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, -- 添加时间 + is_del BOOLEAN NOT NULL DEFAULT FALSE -- 是否删除 +); + +-- 插入示例数据 +INSERT INTO categoies (id, name) +VALUES (1, 'Rust'), + (2, 'Go'), + (3, 'Javascript'); + +INSERT INTO categoies (name) +VALUES ('U3A0CsWdiy'), + ('SWACTQFa0Y'), + ('GYqfhaKJ6J'), + ('0sjsXVArdZ'), + ('MiN8lR1g9B'), + ('oBorPeyIvH'), + ('cqS4jGnmxG'), + ('dc0qqvbDNP'), + ('jq8K6LgUFy'), + ('K1tKtlvzgf'), + ('Z5kEYZYEdp'), + ('y3K6ryqRMF'), + ('hwPu60bq1u'), + ('2Idzt9CmAV'), + ('vbLGfMJNHz'), + ('6tTPkRtpWB'), + ('sWBfrpOAIB'), + ('zgmXGcYsGt'), + ('WH2EBpojIS'), + ('m1rsNTknqS'); + +INSERT INTO articles (category_id, title, content) +VALUES (1, '标题-GLKUSroPOR', '内容-GLKUSroPOR'), + (1, '标题-hFQRulHJAk', '内容-hFQRulHJAk'), + (2, '标题-pM0TURxhwC', '内容-pM0TURxhwC'), + (1, '标题-svNJmWaqRo', '内容-svNJmWaqRo'), + (3, '标题-8XWiTUSfhB', '内容-8XWiTUSfhB'), + (2, '标题-yvwE32TLkg', '内容-yvwE32TLkg'); \ No newline at end of file diff --git a/config/default.toml b/config/default.toml new file mode 100644 index 0000000..7f08d84 --- /dev/null +++ b/config/default.toml @@ -0,0 +1,6 @@ +[web] +addr = "0.0.0.0:9527" + +[mysql] +max_cons = 5 +dsn = "mysql://root:mysql123!%40%23@127.0.0.1:3306/study" diff --git a/config/dev.toml b/config/dev.toml new file mode 100644 index 0000000..b6f9739 --- /dev/null +++ b/config/dev.toml @@ -0,0 +1,6 @@ +[web] +addr = "0.0.0.0:9527" + +[mysql] +max_cons = 5 +dns = "mysql://root:mysql123!%40%23@127.0.0.1:3306/study" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..dfd3b11 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,46 @@ +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 { + 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) + } +} + diff --git a/src/entity/article.rs b/src/entity/article.rs new file mode 100644 index 0000000..95526d0 --- /dev/null +++ b/src/entity/article.rs @@ -0,0 +1,49 @@ +use serde::{Deserialize, Serialize}; +use sea_orm::entity::prelude::*; + + +// Model是只读的,只能用来SELECT +#[derive(Debug, Clone, PartialEq, DeriveEntityModel, Serialize, Deserialize)] +#[sea_orm(table_name = "articles")] +pub struct Model { + #[sea_orm(primary_key)] + #[serde(skip_deserializing)] + pub id: u32, + pub category_id: u32, + pub title: String, + pub content: String, + pub dateline: chrono::DateTime, + pub is_del: bool, +} + +#[derive(Debug, Clone, Copy, EnumIter)] +pub enum Relation { + // 给文章实体定义了包含Category的关系: + Category, +} + +impl RelationTrait for Relation { + // 定义了 Relation 并没实际意义,毕竟它只是一个枚举值而已。我们需要为这个关系实现业务逻辑: + fn def(&self) -> RelationDef { + return match self { + // 声明当前模型属于category模型(多对一),从category_id关联对应表的id字段 + Relation::Category => { + Entity::belongs_to(super::category::Entity) + .from(Column::CategoryId) + .to(super::category::Column::Id) + .into() + } + } + } +} + +// 定义好了关系,我们还需要实现 Relate:告诉 SeaORM ,我们定义的 Relation::Category关系需要如何去建立 +impl Related for Entity { + fn to() -> RelationDef { + Relation::Category.def() + } +} + +// INSERT/UPDATE/DELETE 等属于写操作,需使用 ActiveModel。 +/// [DeriveEntityModel] 这个 derive 根据我们定义的[Model],生成了包括 [ActiveModel]在内的多个数据结构 +impl ActiveModelBehavior for ActiveModel {} \ No newline at end of file diff --git a/src/entity/category.rs b/src/entity/category.rs new file mode 100644 index 0000000..f5f5b66 --- /dev/null +++ b/src/entity/category.rs @@ -0,0 +1,36 @@ +use serde::{Deserialize, Serialize}; +use sea_orm::entity::prelude::*; + + +// Model是只读的,只能用来SELECT +#[derive(Debug, Clone, PartialEq, DeriveEntityModel, Serialize, Deserialize)] +#[sea_orm(table_name = "categoies")] +pub struct Model { + #[sea_orm(primary_key)] + #[serde(skip_deserializing)] + pub id: u32, + pub name: String, + pub is_del: bool, +} + +#[derive(Debug, Clone, Copy, EnumIter)] +pub enum Relation { + Articles, +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + Entity::has_many(super::article::Entity).into() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Articles.def() + } +} + + +// INSERT/UPDATE/DELETE 等属于写操作,需使用 ActiveModel。 +/// [DeriveEntityModel] 这个 derive 根据我们定义的[Model],生成了包括 [ActiveModel]在内的多个数据结构 +impl ActiveModelBehavior for ActiveModel {} \ No newline at end of file diff --git a/src/entity/mod.rs b/src/entity/mod.rs new file mode 100644 index 0000000..e4cb3ec --- /dev/null +++ b/src/entity/mod.rs @@ -0,0 +1,2 @@ +pub mod category; +pub mod article; \ No newline at end of file diff --git a/src/err.rs b/src/err.rs new file mode 100644 index 0000000..0ad6023 --- /dev/null +++ b/src/err.rs @@ -0,0 +1,73 @@ +#[derive(Debug)] +pub enum AppErrorType { + Config, + Database, + Notfound, + Template, +} + +type Cause = Box; + +#[derive(Debug)] +pub enum AppErrorItem { + Message(String), + Cause(Cause), +} + +#[derive(Debug)] +pub struct AppError { + pub types: AppErrorType, + pub error: AppErrorItem, +} + +impl AppError { + pub fn new(types: AppErrorType, error: AppErrorItem) -> Self { + Self { types, error } + } + pub fn from_err(cause: Cause, types: AppErrorType) -> Self { + Self::new(types, AppErrorItem::Cause(cause)) + } + + pub fn from_msg(msg: &str, types: AppErrorType) -> Self { + Self::new(types, AppErrorItem::Message(msg.to_string())) + } + pub fn notfound() -> Self { + Self::from_msg("不存在的记录", AppErrorType::Notfound) + } +} + +impl std::fmt::Display for AppError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for AppError {} + +impl From for AppError { + fn from(err: config::ConfigError) -> Self { + Self::from_err(Box::new(err), AppErrorType::Config) + } +} + +impl From for AppError { + fn from(err: askama::Error) -> Self { + Self::from_err(Box::new(err), AppErrorType::Template) + } +} + +impl From for AppError { + fn from(err: sea_orm::DbErr) -> Self { + Self::from_err(Box::new(err), AppErrorType::Database) + } +} + +impl axum::response::IntoResponse for AppError { + fn into_response(self) -> axum::response::Response { + let msg = match self.error { + AppErrorItem::Cause(err) => err.to_string(), + AppErrorItem::Message(msg) => msg.to_string(), + }; + msg.into_response() + } +} \ No newline at end of file diff --git a/src/form.rs b/src/form.rs new file mode 100644 index 0000000..69a66a3 --- /dev/null +++ b/src/form.rs @@ -0,0 +1,12 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct CategoryForm { + pub name: String, +} +#[derive(Deserialize)] +pub struct ArticleForm { + pub title: String, + pub category_id: u32, + pub content: String, +} \ No newline at end of file diff --git a/src/handler/article.rs b/src/handler/article.rs new file mode 100644 index 0000000..5519437 --- /dev/null +++ b/src/handler/article.rs @@ -0,0 +1,52 @@ +use std::sync::Arc; + +use axum::extract::{Query, State}; +use sea_orm::{ColumnTrait, Condition, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect}; + +use crate::entity::{article, category}; +use crate::err::AppError; +use crate::handler::{get_conn, HtmlResponse, log_error, render}; +use crate::param::ArticleParams; +use crate::Result; +use crate::state::AppState; +use crate::view; + +pub async fn index( + State(state): State>, + Query(params): Query, +) -> Result { + let handler_name = "article/index"; + let conn = get_conn(&state); + + // 构建条件: 所有未被删除的文章 + let condition = Condition::all().add(article::Column::IsDel.eq(false)); + let select = article::Entity::find().filter(condition); + let record_total = select + .clone() + .count(conn) + .await + .map_err(AppError::from) + .map_err(log_error(handler_name))?; + + let page_size = 15u64; + let page = 0u64; + + let page_total = f64::ceil(record_total as f64 / page_size as f64) as u64; + let offset = page_size * page; + + let list = select.find_also_related(category::Entity) + .order_by_desc(article::Column::Id) + .limit(page_size) + .offset(offset) + .all(conn) + .await + .map_err(AppError::from) + .map_err(log_error(handler_name))?; + + let tpl = view::ArticlesTemplate { + list, + page_total, + params, + }; + render(tpl, handler_name) +} \ No newline at end of file diff --git a/src/handler/category.rs b/src/handler/category.rs new file mode 100644 index 0000000..764cb75 --- /dev/null +++ b/src/handler/category.rs @@ -0,0 +1,233 @@ +use std::sync::Arc; + +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::param::{CategoryParams, DelParams}; +use crate::state::AppState; + +pub async fn index( + State(state): State>, + Query(params): Query, +) -> Result { + 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)) + ) + .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 { + 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, + }; + render(tpl, handler_name) +} + + +pub async fn find( + State(state): State>, + Path(id): Path, +) -> Result { + let id = u32::try_from(id).or(Err(AppError::notfound()))?; + + 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)), + } +} + +pub async fn add_ui() -> Result { + let handler_name = "category/add_ui"; + let tpl = view::CategoryAddTemplate; + render(tpl, handler_name) +} + +pub async fn add( + State(state): State>, + Form(frm): Form, +) -> Result { + 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); + + redirect(url.as_str()) +} + +pub async fn edit_ui( + State(state): State>, + Path(id): Path, +) -> Result { + let id = u32::try_from(id).or(Err(AppError::notfound()))?; + + 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 }; + render(tpl, handler_name) +} + +pub async fn edit( + State(state): State>, + Path(id): Path, + Form(frm): Form, +) -> Result { + 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))?; + + redirect("/category?msg=分类修改成功") +} + + +pub async fn del( + State(state): State>, + Path(params): Path, +) -> Result { + 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))?; + } + + redirect("/category?msg=分类删除成功") +} + +pub async fn articles( + State(state): State>, + Path(id): Path, + Query(params): Query, +) -> Result { + 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, + }; + render(tpl, handler_name) +} \ No newline at end of file diff --git a/src/handler/index.rs b/src/handler/index.rs new file mode 100644 index 0000000..145de9a --- /dev/null +++ b/src/handler/index.rs @@ -0,0 +1,8 @@ +use crate::handler::{HtmlResponse, render}; +use crate::{Result, view}; + +pub async fn index() -> Result { + let handler_name = "index"; + let tpl = view::IndexTemplate; + render(tpl, handler_name) +} \ No newline at end of file diff --git a/src/handler/mod.rs b/src/handler/mod.rs new file mode 100644 index 0000000..b529870 --- /dev/null +++ b/src/handler/mod.rs @@ -0,0 +1,45 @@ +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; +type RedirectResponse = (StatusCode, HeaderMap, ()); + +/// 渲染模板 +fn render(tpl: T, handler_name: &str) -> Result { + let out = tpl + .render() + .map_err(AppError::from) + .map_err(log_error(handler_name))?; + Ok(Html(out)) +} + +/// 记录错误 +fn log_error(handler_name: &str) -> Box AppError> { + let handler_name = handler_name.to_string(); + Box::new(move |err| { + tracing::error!("{}: {:?}", handler_name, err); + err + }) +} + +fn redirect(url: &str) -> Result { + let mut header = HeaderMap::new(); + header.insert(header::LOCATION, url.parse().unwrap()); + Ok((StatusCode::FOUND, header, ())) +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8e2b18e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +pub mod entity; +pub mod state; +pub mod config; +pub mod handler; +pub mod view; +pub mod err; +pub mod router; +pub mod param; +pub mod form; + +pub type Result = std::result::Result; \ No newline at end of file diff --git a/src/param.rs b/src/param.rs new file mode 100644 index 0000000..625310c --- /dev/null +++ b/src/param.rs @@ -0,0 +1,103 @@ +use serde::Deserialize; + +const DEFAULT_PAGE_SIZE: u64 = 15; + +#[derive(Debug, Clone, Deserialize)] +pub struct CategoryParams { + // 用于搜索的关键字 + pub keyword: Option, + // 是否删除 + pub is_del: Option, + // 排序 + pub sort: Option, + // 每页记录条数 + pub page_size: Option, + // 当前页码 + pub page: Option, + + pub msg: Option, +} + +impl Default for CategoryParams { + fn default() -> Self { + Self { + keyword: None, + is_del: None, + sort: None, + page_size: None, + page: None, + msg: None, + } + } +} + +impl CategoryParams { + pub fn keyword(&self) -> String { + self.keyword.clone().unwrap_or("".to_string()) + } + pub fn keyword_opt(&self) -> Option { + match &self.keyword { + Some(s) => { + if s.is_empty() { + None + } else { + Some(s.to_string()) + } + } + _ => None, + } + } + + pub fn is_del_opt(&self) -> Option { + match self.is_del { + Some(n) => match n { + 0 => Some(false), + 1 => Some(true), + _ => None, + }, + _ => None, + } + } + + pub fn is_del(&self) -> i32 { + match self.is_del_opt() { + Some(b) => { + if b { + 1 + } else { + 0 + } + } + _ => -1, + } + } + + pub fn sort(&self) -> String { + self.sort.clone().unwrap_or("".to_string()) + } + pub fn order(&self) -> Option { + match self.sort().as_str() { + "asc" => Some(sea_orm::Order::Asc), + "desc" => Some(sea_orm::Order::Desc), + _ => None, + } + } + pub fn page_size(&self) -> u64 { + let ps = self.page_size.unwrap_or(0); + if ps <= 0 { + return DEFAULT_PAGE_SIZE; + } + ps + } + pub fn page(&self) -> u64 { + self.page.unwrap_or(0) + } +} + +#[derive(Deserialize, Debug)] +pub struct DelParams { + pub id: u32, + pub real: Option, +} + +pub type ArticleParams = CategoryParams; diff --git a/src/router.rs b/src/router.rs new file mode 100644 index 0000000..89da0ac --- /dev/null +++ b/src/router.rs @@ -0,0 +1,22 @@ +use std::sync::Arc; +use axum::routing::get; +use crate::handler; +use crate::state::AppState; + +pub fn init() -> axum::Router> { + axum::Router::new() + .route("/", get(handler::index)) + .route("/category", get(handler::category::index)) + .route( + "/category/add", + get(handler::category::add_ui).post(handler::category::add), + ) + .route( + "/category/edit/:id", + get(handler::category::edit_ui).post(handler::category::edit), + ) + .route("/category/del/:id", get(handler::category::del)) + .route("/category/del/:id/:real", get(handler::category::del)) + .route("/category/articles/:id", get(handler::category::articles)) + .route("/article", get(handler::article::index)) +} \ No newline at end of file diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..f95a965 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,6 @@ + +use sea_orm::DatabaseConnection; + +pub struct AppState { + pub conn: DatabaseConnection, +} \ No newline at end of file diff --git a/src/view.rs b/src/view.rs new file mode 100644 index 0000000..c479c94 --- /dev/null +++ b/src/view.rs @@ -0,0 +1,47 @@ +use askama::Template; + +use crate::{entity, param}; + +#[derive(Template)] +#[template(path = "index.html")] +pub struct IndexTemplate; + + +#[derive(Template)] +#[template(path = "category.html")] +pub struct CategoryTemplate { + pub params: param::CategoryParams, + pub categories: Vec, + pub page_total: u64, +} + +#[derive(Template)] +#[template(path = "category-add.html")] +pub struct CategoryAddTemplate; + +#[derive(Template)] +#[template(path = "category-edit.html")] +pub struct CategoryEditTemplate { + pub category: entity::category::Model, +} + +#[derive(Template)] +#[template(path = "category-articles.html")] +pub struct CategoryArticlesTemplate { + pub params: param::CategoryParams, + pub page_total: u64, + pub category: entity::category::Model, + pub articles: Vec, +} +#[derive(Template)] +#[template(path = "article.html")] +pub struct ArticlesTemplate { + pub page_total: u64, + pub list: Vec<(entity::article::Model, Option)>, + pub params: param::ArticleParams, +} +#[derive(Template)] +#[template(path = "article-add.html")] +pub struct ArticleAddTemplate { + pub categies: Vec, +} \ No newline at end of file diff --git a/templates/article-add.html b/templates/article-add.html new file mode 100644 index 0000000..1ff1dc4 --- /dev/null +++ b/templates/article-add.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} {% block title %}添加文章{%endblock%} {% block bc %} + +{% endblock %} {% block content %} +
+ +
+ + +
+
+ + +
+
+ + +
+ +
+ +{%endblock%} \ No newline at end of file diff --git a/templates/article.html b/templates/article.html new file mode 100644 index 0000000..e955203 --- /dev/null +++ b/templates/article.html @@ -0,0 +1,46 @@ +{% extends "base.html" %} {% block title %}文章列表{%endblock%} {% block bc %} + +{% endblock %} {% block content %} {%if params.msg.is_some() %} + +{% endif %} + + + + + + + + + + + + {% for item in list %} {% let (article, category) = item %} + + + + + + + + {% endfor %} + +
#标题分类时间操作
{{ article.id }}{{ article.title }} + {% match category%} {% when Some with(category)%} {{ category.name }} {% + when None %} 没有分类 {% endmatch %} + {{ article.dateline }}作业
+ +{%endblock%} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..3c9d3a0 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,160 @@ + + + + + + + + axum使用SeaORM + + + + +
+
+
+

{% block title %}标题{%endblock%}

+
+
+ +
+
+
{% block content %} 在此输入内容 {%endblock %}
+
+ + + + + \ No newline at end of file diff --git a/templates/category-add.html b/templates/category-add.html new file mode 100644 index 0000000..b4ca5c4 --- /dev/null +++ b/templates/category-add.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} {% block title %}添加分类{%endblock%} {% block bc %} + +{% endblock %} {% block content %} +
+ +
+ + +
+ +
+ +{%endblock%} \ No newline at end of file diff --git a/templates/category-articles.html b/templates/category-articles.html new file mode 100644 index 0000000..79887a8 --- /dev/null +++ b/templates/category-articles.html @@ -0,0 +1,42 @@ +{% extends "base.html" %} {% block title %} +分类文章{%endblock%} {% block bc %} + +{%endblock%} +{%block content%} +

{{category.name}}的所有文章

+ + + + + + + + + + {%for article in articles%} + + + + + + {%endfor%} + +
文章ID文章标题时间
{{ article.id }}{{article.title}}{{article.dateline}}
+{% if page_total > 1 %} + +{% endif %} +{%endblock%} \ No newline at end of file diff --git a/templates/category-edit.html b/templates/category-edit.html new file mode 100644 index 0000000..e02d51b --- /dev/null +++ b/templates/category-edit.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} {% block title %}修改分类{%endblock%} {% block bc %} + +{% endblock %} {% block content %} +
+
+ + +
+ +
+{%endblock%} \ No newline at end of file diff --git a/templates/category.html b/templates/category.html new file mode 100644 index 0000000..d86c1f8 --- /dev/null +++ b/templates/category.html @@ -0,0 +1,91 @@ +{% extends "base.html" %} {% block title %}分类列表{%endblock%} {% block bc %} + +{% endblock %} {% block content %} +{%if params.msg.is_some() %} + +{% endif %} +
+
+ +
+
关键字
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + + + + + + + + + {% for category in categories %} + + + + + + {% endfor %} + +
#名称操作
{{ category.id }}{{ category.name }} + 文章列表 + 修改 +
+ + +
+
+ +{%endblock%} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..9a24070 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,2 @@ +{% extends "base.html" %} {% block title %}首页{%endblock%} {% block content %} +首页内容 {%endblock%} \ No newline at end of file