diff --git a/src/template/src/dtos/user.hbs b/src/template/src/dtos/user.hbs index c45fea8..ddbcfd2 100644 --- a/src/template/src/dtos/user.hbs +++ b/src/template/src/dtos/user.hbs @@ -1,4 +1,4 @@ -use salvo::prelude::ToSchema; +use salvo::prelude::{ToSchema, Extractible}; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Debug, ToSchema, Default)] @@ -13,17 +13,15 @@ pub struct UserLoginRequest { pub password: String, } -#[derive(Debug, Deserialize, ToSchema, Default)] +#[derive(Debug, Deserialize,Extractible,ToSchema, Default)] +#[salvo(extract(default_source(from = "body", format = "json")))] pub struct UserUpdateRequest { + #[salvo(extract(source(from = "param")))] pub id: String, pub username: String, pub password: String, } -#[derive(Debug, Deserialize, ToSchema, Default)] -pub struct UserDeleteRequest { - pub id: String, -} #[derive(Debug, Serialize, ToSchema, Default)] pub struct UserResponse { diff --git a/src/template/src/main_template.hbs b/src/template/src/main_template.hbs index a262acf..fa997b0 100644 --- a/src/template/src/main_template.hbs +++ b/src/template/src/main_template.hbs @@ -45,6 +45,11 @@ async fn main() { "swagger-ui: https://{}/swagger-ui", &CFG.server.address.replace("0.0.0.0", "127.0.0.1") ); + {{#if is_web_site}} + {{#if need_db_conn}} + println!("login page: https://{}/login", &CFG.server.address.replace("0.0.0.0", "127.0.0.1")); + {{/if}} + {{/if}} let config = RustlsConfig::new( Keycert::new() .cert(CERT_KEY.cert.clone()) @@ -68,6 +73,11 @@ async fn main() { "swagger-ui: http://{}/swagger-ui", &CFG.server.address.replace("0.0.0.0", "127.0.0.1") ); + {{#if is_web_site}} + {{#if need_db_conn}} + println!("login page: http://{}/login", &CFG.server.address.replace("0.0.0.0", "127.0.0.1")); + {{/if}} + {{/if}} let acceptor = TcpListener::new(&CFG.server.address).bind().await; let server = Server::new(acceptor).serve_with_graceful_shutdown( service, diff --git a/src/template/src/middleware/jwt.rs b/src/template/src/middleware/jwt.rs index 164e650..cac61b0 100644 --- a/src/template/src/middleware/jwt.rs +++ b/src/template/src/middleware/jwt.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use jsonwebtoken::EncodingKey; +use jsonwebtoken::{decode, Algorithm, DecodingKey, EncodingKey, Validation}; use salvo::jwt_auth::{ConstDecoder, CookieFinder, HeaderFinder, QueryFinder}; use salvo::prelude::*; use serde::{Deserialize, Serialize}; @@ -35,10 +35,21 @@ pub fn get_token(username: String, user_id: String) -> Result<(String, i64)> { user_id, exp: exp.unix_timestamp(), }; - let token = jsonwebtoken::encode( + let token: String = jsonwebtoken::encode( &jsonwebtoken::Header::default(), &claim, &EncodingKey::from_secret(CFG.jwt.jwt_secret.as_bytes()), )?; Ok((token, exp.unix_timestamp())) } + +#[allow(dead_code)] +pub fn decode_token(token: &str) -> bool { + let validation = Validation::new(Algorithm::HS256); + decode::( + token, + &DecodingKey::from_secret(CFG.jwt.jwt_secret.as_bytes()), + &validation, + ) + .is_ok() +} diff --git a/src/template/src/routers/mod.hbs b/src/template/src/routers/mod.hbs index 04ae14c..39b1a1c 100644 --- a/src/template/src/routers/mod.hbs +++ b/src/template/src/routers/mod.hbs @@ -1,6 +1,6 @@ {{#if need_db_conn}} + {{#if is_web_site}} use crate::middleware::jwt::jwt_hoop; -{{/if}} use salvo::{ prelude::{CatchPanic, Logger, OpenApi, SwaggerUi}, Router, @@ -8,54 +8,126 @@ use salvo::{ use self::{ demo::hello, -{{#if need_db_conn}} -{{#if is_web_site}} user::{ - delete_user,login_page, post_add_user, post_login, post_update_user, + delete_user, get_users, login_page, post_add_user, post_login, put_update_user, user_list_page, }, -{{else}} +}; +pub mod demo; +pub mod user; + +pub fn router() -> Router { + let mut no_auth_routers = vec![ + Router::with_path("login").get(login_page), + Router::with_path("/api/login").post(post_login), + ]; + + let mut need_auth_routers = vec![ + Router::with_path("users") + .get(user_list_page), + Router::with_path("/api/users").get(get_users) + .post(post_add_user) + .push( + Router::with_path("") + .put(put_update_user) + .delete(delete_user), + ), + ]; + let router = Router::new() + .hoop(Logger::new()) + .hoop(CatchPanic::new()) + .get(hello) + .append(&mut no_auth_routers) + .push( + Router::new() + .append(&mut need_auth_routers) + .hoop(jwt_hoop()), + ); + let doc = OpenApi::new("salvo web api", "0.0.1").merge_router(&router); + router + .push(doc.into_router("/api-doc/openapi.json")) + .push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui")) +} + {{else}} +use crate::middleware::jwt::jwt_hoop; +use salvo::{ + prelude::{CatchPanic, Logger, OpenApi, SwaggerUi}, + Router, +}; + +use self::{ + demo::hello, user::{ - delete_user, get_users, post_add_user, post_login, post_update_user, + delete_user, get_users, post_add_user, post_login, put_update_user, }, -{{/if}} -{{/if}} }; pub mod demo; -{{#if need_db_conn}} pub mod user; -{{/if}} pub fn router() -> Router { + let mut no_auth_routers = vec![ + Router::with_path("/api/login").post(post_login), + ]; + + let mut need_auth_routers = vec![ + Router::with_path("/api/users").get(get_users) + .post(post_add_user) + .push( + Router::with_path("") + .put(put_update_user) + .delete(delete_user), + ), + ]; let router = Router::new() .hoop(Logger::new()) .hoop(CatchPanic::new()) - {{#if need_db_conn}} .get(hello) - {{#if is_web_site}} - .push(Router::with_path("login").get(login_page).post(post_login)) - {{else}} - .push(Router::with_path("login").post(post_login)) - {{/if}} - .push(user_router().hoop(jwt_hoop())); - {{else}} + .append(&mut no_auth_routers) + .push( + Router::new() + .append(&mut need_auth_routers) + .hoop(jwt_hoop()), + ); + let doc = OpenApi::new("salvo web api", "0.0.1").merge_router(&router); + router + .push(doc.into_router("/api-doc/openapi.json")) + .push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui")) +} + {{/if}} +{{else}} + {{#if is_web_site}} +use salvo::{ + prelude::{CatchPanic, Logger, OpenApi, SwaggerUi}, + Router, +}; +use self::demo::hello; +pub mod demo; +pub fn router() -> Router { + let router = Router::new() + .hoop(Logger::new()) + .hoop(CatchPanic::new()) .get(hello); - {{/if}} let doc = OpenApi::new("salvo web api", "0.0.1").merge_router(&router); router .push(doc.into_router("/api-doc/openapi.json")) .push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui")) } -{{#if need_db_conn}} -pub fn user_router() -> Router { - Router::with_path("user") - .post(post_add_user) - .put(post_update_user) - .delete(delete_user) - {{#if is_web_site}} - .get(user_list_page) - {{else}} - .get(get_users) - {{/if}} + {{else}} +use salvo::{ + prelude::{CatchPanic, Logger, OpenApi, SwaggerUi}, + Router, +}; +use self::demo::hello; +pub mod demo; +pub fn router() -> Router { + let router = Router::new() + .hoop(Logger::new()) + .hoop(CatchPanic::new()) + .get(hello); + let doc = OpenApi::new("salvo web api", "0.0.1").merge_router(&router); + router + .push(doc.into_router("/api-doc/openapi.json")) + .push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui")) } + {{/if}} {{/if}} \ No newline at end of file diff --git a/src/template/src/routers/user.hbs b/src/template/src/routers/user.hbs index 8ad1089..4144290 100644 --- a/src/template/src/routers/user.hbs +++ b/src/template/src/routers/user.hbs @@ -1,50 +1,57 @@ {{#if is_web_site}} -use askama::Template; -{{/if}} -{{#if is_web_site}} -use salvo::{ - endpoint, - http::cookie::Cookie, - oapi::extract::{FormBody, JsonBody}, - writing::{Redirect, Text}, - Response, -}; use crate::{ app_error::AppResult, app_response::{ErrRes, Res}, - dtos::user::{ - UserAddRequest, UserDeleteRequest, UserLoginRequest, UserResponse, UserUpdateRequest, - }, + dtos::user::{UserAddRequest, UserLoginRequest, UserLoginResponse, UserUpdateRequest}, + middleware::jwt::decode_token, services::user, }; -{{else}} +use askama::Template; use salvo::{ endpoint, - oapi::extract::{FormBody, JsonBody}, - Response, -}; -use crate::{ - app_response::{ErrRes, Res}, - dtos::user::{ - UserAddRequest, UserDeleteRequest, UserLoginRequest, UserUpdateRequest, - }, - services::user, + http::cookie::Cookie, + oapi::extract::{JsonBody, PathParam}, + writing::{Redirect, Text}, + Request, Response, }; -{{/if}} -{{#if is_web_site}} + #[derive(Template)] #[template(path = "login.html")] struct LoginTemplate {} -#[endpoint] +#[endpoint( tags("comm"),)] pub async fn login_page(res: &mut Response) -> AppResult<()> { + let cookies = res.cookies(); + let cookie = cookies.get("jwt_token"); + if let Some(cookie) = cookie { + let token = cookie.value().to_string(); + if decode_token(&token) { + res.render(Redirect::other("/users")); + return Ok(()); + } else { + } + } let hello_tmpl = LoginTemplate {}; res.render(Text::Html(hello_tmpl.render().unwrap())); Ok(()) } -{{/if}} +#[endpoint( tags("comm"),)] +pub async fn post_login(req: JsonBody, res: &mut Response) { + let result: AppResult = user::login(req.0).await; + match result { + Ok(data) => { + let jwt_token = data.token.clone(); + let cookie = Cookie::build("jwt_token", jwt_token) + .path("/") + .http_only(true) + .finish(); + res.add_cookie(cookie); + } + Err(e) => ErrRes::with_err(&e.to_string()).into_response(res), + } +} -#[endpoint] +#[endpoint( tags("users"))] pub async fn post_add_user(req: JsonBody, res: &mut Response) { let result = user::add_user(req.0).await; match result { @@ -53,25 +60,29 @@ pub async fn post_add_user(req: JsonBody, res: &mut Response) { } } -#[endpoint] -pub async fn post_update_user(req: JsonBody, res: &mut Response) { - let result = user::update_user(req.0).await; +#[endpoint( tags("users"), +parameters( + ("id", description = "user id"), +))] +pub async fn put_update_user(req: &mut Request, res: &mut Response) { + let req: UserUpdateRequest = req.extract().await.unwrap(); + let result = user::update_user(req).await; match result { Ok(data) => Res::with_data(data).into_response(res), Err(e) => ErrRes::with_err(&e.to_string()).into_response(res), } } -#[endpoint] -pub async fn delete_user(req: JsonBody, res: &mut Response) { - let result = user::delete_user(req.0).await; +#[endpoint( tags("users"),)] +pub async fn delete_user(id: PathParam, res: &mut Response) { + let result = user::delete_user(id.0).await; match result { Ok(_) => Res::with_data(()).into_response(res), Err(e) => ErrRes::with_err(&e.to_string()).into_response(res), } } -#[endpoint] +#[endpoint( tags("users"),)] pub async fn get_users(res: &mut Response) { let result = user::users().await; match result { @@ -79,26 +90,47 @@ pub async fn get_users(res: &mut Response) { Err(e) => ErrRes::with_err(&e.to_string()).into_response(res), } } -{{#if is_web_site}} #[derive(Template)] #[template(path = "user_list_page.html")] -pub struct UserListPageTemplate { - users: Vec, -} +pub struct UserListPageTemplate {} -#[endpoint] -pub async fn user_list_page(res: &mut Response) -> AppResult<()> { - let users = user::users().await?; - let hello_tmpl = UserListPageTemplate { users }; +#[derive(Template)] +#[template(path = "user_list.html")] +pub struct UserListTemplate {} - res.render(Text::Html(hello_tmpl.render().unwrap())); +#[endpoint] +pub async fn user_list_page(req: &mut Request, res: &mut Response) -> AppResult<()> { + let is_fragment = req.headers().get("X-Fragment-Header"); + match is_fragment { + Some(_) => { + let hello_tmpl = UserListTemplate {}; + res.render(Text::Html(hello_tmpl.render().unwrap())); + } + None => { + let hello_tmpl = UserListPageTemplate {}; + res.render(Text::Html(hello_tmpl.render().unwrap())); + } + } Ok(()) } +{{else}} +use crate::{ + app_error::AppResult, + app_response::{ErrRes, Res}, + dtos::user::{UserAddRequest, UserLoginRequest, UserLoginResponse, UserUpdateRequest}, + services::user, +}; +use salvo::{ + endpoint, + http::cookie::Cookie, + oapi::extract::{JsonBody, PathParam}, + Request, Response, +}; -#[endpoint] -pub async fn post_login(req: FormBody, res: &mut Response) { - let result = user::login(req.0).await; +#[endpoint( tags("comm"),)] +pub async fn post_login(req: JsonBody, res: &mut Response) { + let result: AppResult = user::login(req.0).await; match result { Ok(data) => { let jwt_token = data.token.clone(); @@ -107,15 +139,45 @@ pub async fn post_login(req: FormBody, res: &mut Response) { .http_only(true) .finish(); res.add_cookie(cookie); - res.render(Redirect::other("/user")); } Err(e) => ErrRes::with_err(&e.to_string()).into_response(res), } } -{{else}} -#[endpoint] -pub async fn post_login(req: FormBody, res: &mut Response) { - let result = user::login(req.0).await; + +#[endpoint( tags("users"))] +pub async fn post_add_user(req: JsonBody, res: &mut Response) { + let result = user::add_user(req.0).await; + match result { + Ok(data) => Res::with_data(data).into_response(res), + Err(e) => ErrRes::with_err(&e.to_string()).into_response(res), + } +} + +#[endpoint( tags("users"), +parameters( + ("id", description = "user id"), +))] +pub async fn put_update_user(req: &mut Request, res: &mut Response) { + let req: UserUpdateRequest = req.extract().await.unwrap(); + let result = user::update_user(req).await; + match result { + Ok(data) => Res::with_data(data).into_response(res), + Err(e) => ErrRes::with_err(&e.to_string()).into_response(res), + } +} + +#[endpoint( tags("users"),)] +pub async fn delete_user(id: PathParam, res: &mut Response) { + let result = user::delete_user(id.0).await; + match result { + Ok(_) => Res::with_data(()).into_response(res), + Err(e) => ErrRes::with_err(&e.to_string()).into_response(res), + } +} + +#[endpoint( tags("users"),)] +pub async fn get_users(res: &mut Response) { + let result = user::users().await; match result { Ok(data) => Res::with_data(data).into_response(res), Err(e) => ErrRes::with_err(&e.to_string()).into_response(res), diff --git a/src/template/src/services/user.hbs b/src/template/src/services/user.hbs index 9ef7eff..c8f9e22 100644 --- a/src/template/src/services/user.hbs +++ b/src/template/src/services/user.hbs @@ -3,7 +3,7 @@ use crate::{ app_error::AppResult, db::DB, dtos::user::{ - UserAddRequest, UserDeleteRequest, UserLoginRequest, UserLoginResponse, UserResponse, + UserAddRequest, UserLoginRequest, UserLoginResponse, UserResponse, UserUpdateRequest, }, middleware::jwt::get_token, @@ -16,7 +16,7 @@ use crate::{ app_error::AppResult, db::DB, dtos::user::{ - UserAddRequest, UserDeleteRequest, UserLoginRequest, UserLoginResponse, UserResponse, + UserAddRequest, UserLoginRequest, UserLoginResponse, UserResponse, UserUpdateRequest, }, middleware::jwt::get_token, @@ -103,14 +103,14 @@ pub async fn update_user(req: UserUpdateRequest) -> AppResult { }) } -pub async fn delete_user(req: UserDeleteRequest) -> AppResult<()> { +pub async fn delete_user(id: String) -> AppResult<()> { let db = DB.get().ok_or(anyhow::anyhow!("{{database_connection_failed}}"))?; sqlx::query!( r#" DELETE FROM users WHERE id = $1 "#, - req.id, + id, ) .execute(db) .await?; @@ -196,9 +196,9 @@ pub async fn update_user(req: UserUpdateRequest) -> AppResult { }) } -pub async fn delete_user(req: UserDeleteRequest) -> AppResult<()> { - let db = DB.get().ok_or(anyhow::anyhow!("{{database_connection_failed}}"))?; - User::delete_by_id(req.id, ).exec(db).await?; +pub async fn delete_user(id: String) -> AppResult<()> { + let db = DB.get().ok_or(anyhow::anyhow!("数据库连接失败"))?; + User::delete_by_id(id).exec(db).await?; Ok(()) } diff --git a/src/template/templates/login.hbs b/src/template/templates/login.hbs index 53133a3..cfe249d 100644 --- a/src/template/templates/login.hbs +++ b/src/template/templates/login.hbs @@ -7,105 +7,204 @@ salvo -
-
-
-

- {{login}} -

-
-
- -
-
- - -
+
+ +
- - - - + diff --git a/src/template/templates/user_list.hbs b/src/template/templates/user_list.hbs index f17a058..55ce689 100644 --- a/src/template/templates/user_list.hbs +++ b/src/template/templates/user_list.hbs @@ -1,96 +1,62 @@ -
-
-
-
-

- {{user_list}} -

-
+
+
+
+
+

Users

+

A list of all the users

-
-
-
- - - - - - - - - - {% for user in users %} +
+ +
+ +
+
+
+
- {{username}} - - {{delete}} - {{operation}} -
+ + + + + + + + + +
+ Name + + Delete +
+
diff --git a/src/template/templates/user_list_page.hbs b/src/template/templates/user_list_page.hbs index d7f65dc..df98780 100644 --- a/src/template/templates/user_list_page.hbs +++ b/src/template/templates/user_list_page.hbs @@ -6,35 +6,91 @@ salvo - {% include "user_list.html" %} + {% include "user_list.html" %} - - - - + diff --git a/src/utils/create_project.rs b/src/utils/create_project.rs index 6695414..ef2ee71 100644 --- a/src/utils/create_project.rs +++ b/src/utils/create_project.rs @@ -83,7 +83,7 @@ fn write_project_file( "jsonwebtoken": "8.3.0", "once_cell": "1.18.0", "salvo": { - "version": "*", + "version": "0.57", "features": ["anyhow", "logging", "cors", "oapi", "jwt-auth", "rustls", "catch-panic","cookie"] }, "serde": "1.0.188", diff --git a/src/utils/get_selection.rs b/src/utils/get_selection.rs index 49796d5..1a305a6 100644 --- a/src/utils/get_selection.rs +++ b/src/utils/get_selection.rs @@ -18,8 +18,8 @@ pub fn get_user_selected() -> Result> { ..ColorfulTheme::default() }; let selections = &[ - t!("salvo_web_api"), t!("salvo_web_site"), + t!("salvo_web_api"), // "custom", ]; let selection = Select::with_theme(&theme) @@ -28,8 +28,8 @@ pub fn get_user_selected() -> Result> { .items(&selections[..]) .interact()?; let template_type = match selection { - 0 => TemplateType::SalvoWebApi, - 1 => TemplateType::SalvoWebSite, + 0 => TemplateType::SalvoWebSite, + 1 => TemplateType::SalvoWebApi, _ => anyhow::bail!("Invalid selection"), }; let db_conn_types = &[