init
This commit is contained in:
65
backend/src/routes/auth.rs
Normal file
65
backend/src/routes/auth.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use axum::{Router, routing::{post, get}, Json, extract::State};
|
||||
use axum::http::{HeaderMap, HeaderValue};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::{db::Db, response::ApiResponse, error::AppError};
|
||||
use crate::services::{auth_service, role_service, menu_service};
|
||||
use crate::middlewares::jwt::{AuthUser, decode_token};
|
||||
use crate::models::user;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct LoginReq { pub username: String, pub password: String }
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct UserInfo { pub id: i64, pub username: String, pub nickname: Option<String>, pub status: i32 }
|
||||
impl From<user::Model> for UserInfo {
|
||||
fn from(m: user::Model) -> Self { Self { id: m.id, username: m.username, nickname: m.nickname, status: m.status } }
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct LoginResp { pub access_token: String, pub user: UserInfo }
|
||||
|
||||
pub fn router() -> Router<Db> {
|
||||
Router::new()
|
||||
.route("/login", post(login))
|
||||
.route("/logout", post(logout))
|
||||
.route("/refresh", get(refresh))
|
||||
.route("/menus", get(current_user_menus))
|
||||
}
|
||||
|
||||
pub async fn login(State(db): State<Db>, Json(req): Json<LoginReq>) -> Result<(HeaderMap, Json<ApiResponse<LoginResp>>), AppError> {
|
||||
let res = auth_service::login(&db, req.username, req.password).await?;
|
||||
let mut headers = HeaderMap::new();
|
||||
let cookie = format!("refresh_token={}; HttpOnly; SameSite=Lax; Path=/; Max-Age=1209600", res.refresh);
|
||||
headers.insert(axum::http::header::SET_COOKIE, HeaderValue::from_str(&cookie).unwrap());
|
||||
Ok((headers, Json(ApiResponse::ok(LoginResp { access_token: res.access, user: res.user.into() }))))
|
||||
}
|
||||
|
||||
pub async fn logout(State(db): State<Db>, user: AuthUser) -> Result<Json<ApiResponse<serde_json::Value>>, AppError> {
|
||||
auth_service::logout(&db, user.uid).await?;
|
||||
Ok(Json(ApiResponse::ok(serde_json::json!({"success": true}))))
|
||||
}
|
||||
|
||||
pub async fn refresh(State(db): State<Db>, headers: HeaderMap) -> Result<(HeaderMap, Json<ApiResponse<serde_json::Value>>), AppError> {
|
||||
let cookie_header = headers.get(axum::http::header::COOKIE).and_then(|v| v.to_str().ok()).unwrap_or("");
|
||||
let refresh = cookie_header.split(';').find_map(|p| {
|
||||
let kv: Vec<&str> = p.trim().splitn(2, '=').collect();
|
||||
if kv.len()==2 && kv[0]=="refresh_token" { Some(kv[1].to_string()) } else { None }
|
||||
}).ok_or(AppError::Unauthorized)?;
|
||||
|
||||
let secret = std::env::var("JWT_SECRET").unwrap();
|
||||
let claims = decode_token(&refresh, &secret)?;
|
||||
if claims.typ != "refresh" { return Err(AppError::Unauthorized); }
|
||||
|
||||
let (access, new_refresh) = auth_service::rotate_refresh(&db, claims.uid, refresh).await?;
|
||||
let mut headers = HeaderMap::new();
|
||||
let cookie = format!("refresh_token={}; HttpOnly; SameSite=Lax; Path=/; Max-Age=1209600", new_refresh);
|
||||
headers.insert(axum::http::header::SET_COOKIE, HeaderValue::from_str(&cookie).unwrap());
|
||||
|
||||
Ok((headers, Json(ApiResponse::ok(serde_json::json!({"access_token": access})))) )
|
||||
}
|
||||
|
||||
pub async fn current_user_menus(State(db): State<Db>, user: AuthUser) -> Result<Json<ApiResponse<Vec<menu_service::MenuInfo>>>, AppError> {
|
||||
let ids = role_service::get_menu_ids_by_user_id(&db, user.uid).await?;
|
||||
let menus = menu_service::list_by_ids(&db, ids).await?;
|
||||
Ok(Json(ApiResponse::ok(menus)))
|
||||
}
|
||||
36
backend/src/routes/departments.rs
Normal file
36
backend/src/routes/departments.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use axum::{Router, routing::{get, put}, extract::{Path, Query, State}, Json};
|
||||
use serde::Deserialize;
|
||||
use crate::{db::Db, services::department_service, response::ApiResponse, error::AppError};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ListParams { keyword: Option<String> }
|
||||
|
||||
pub fn router() -> Router<Db> { Router::new()
|
||||
.route("/departments", get(list).post(create))
|
||||
.route("/departments/{id}", put(update).delete(delete_department))
|
||||
}
|
||||
|
||||
async fn list(State(db): State<Db>, Query(p): Query<ListParams>) -> Result<Json<ApiResponse<Vec<department_service::DepartmentInfo>>>, AppError> {
|
||||
let res = department_service::list_all(&db, p.keyword).await?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CreateReq { parent_id: Option<i64>, name: String, order_no: Option<i32>, status: Option<i32> }
|
||||
|
||||
async fn create(State(db): State<Db>, Json(req): Json<CreateReq>) -> Result<Json<ApiResponse<department_service::DepartmentInfo>>, AppError> {
|
||||
let res = department_service::create(&db, department_service::CreateDepartmentInput { parent_id: req.parent_id, name: req.name, order_no: req.order_no, status: req.status }).await?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UpdateReq { parent_id: Option<i64>, name: Option<String>, order_no: Option<i32>, status: Option<i32> }
|
||||
|
||||
async fn update(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<UpdateReq>) -> Result<Json<ApiResponse<department_service::DepartmentInfo>>, AppError> {
|
||||
let res = department_service::update(&db, id, department_service::UpdateDepartmentInput { parent_id: req.parent_id, name: req.name, order_no: req.order_no, status: req.status }).await?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
async fn delete_department(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<bool>>, AppError> {
|
||||
department_service::delete(&db, id).await?;
|
||||
Ok(Json(ApiResponse::ok(true)))
|
||||
}
|
||||
9
backend/src/routes/logs.rs
Normal file
9
backend/src/routes/logs.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use axum::{Router, routing::get, extract::{Query, State}, Json};
|
||||
use crate::{db::Db, response::ApiResponse, services::log_service, error::AppError};
|
||||
|
||||
pub fn router() -> Router<Db> { Router::new().route("/logs", get(list)) }
|
||||
|
||||
async fn list(State(db): State<Db>, Query(p): Query<log_service::ListParams>) -> Result<Json<ApiResponse<log_service::PageResp<log_service::LogInfo>>>, AppError> {
|
||||
let res = log_service::list(&db, p).await.map_err(|e| AppError::Anyhow(anyhow::anyhow!(e)))?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
36
backend/src/routes/menus.rs
Normal file
36
backend/src/routes/menus.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use axum::{Router, routing::{get, put}, extract::{Path, Query, State}, Json};
|
||||
use serde::Deserialize;
|
||||
use crate::{db::Db, services::menu_service, response::ApiResponse, error::AppError};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ListParams { keyword: Option<String> }
|
||||
|
||||
pub fn router() -> Router<Db> { Router::new()
|
||||
.route("/menus", get(list).post(create))
|
||||
.route("/menus/{id}", put(update).delete(delete_menu))
|
||||
}
|
||||
|
||||
async fn list(State(db): State<Db>, Query(p): Query<ListParams>) -> Result<Json<ApiResponse<Vec<menu_service::MenuInfo>>>, AppError> {
|
||||
let res = menu_service::list_all(&db, p.keyword).await?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CreateReq { parent_id: Option<i64>, name: String, path: Option<String>, component: Option<String>, r#type: i32, icon: Option<String>, order_no: Option<i32>, visible: Option<bool>, status: Option<i32>, keep_alive: Option<bool>, perms: Option<String> }
|
||||
|
||||
async fn create(State(db): State<Db>, Json(req): Json<CreateReq>) -> Result<Json<ApiResponse<menu_service::MenuInfo>>, AppError> {
|
||||
let res = menu_service::create(&db, menu_service::CreateMenuInput { parent_id: req.parent_id, name: req.name, path: req.path, component: req.component, r#type: req.r#type, icon: req.icon, order_no: req.order_no, visible: req.visible, status: req.status, keep_alive: req.keep_alive, perms: req.perms }).await?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UpdateReq { parent_id: Option<i64>, name: Option<String>, path: Option<String>, component: Option<String>, r#type: Option<i32>, icon: Option<String>, order_no: Option<i32>, visible: Option<bool>, status: Option<i32>, keep_alive: Option<bool>, perms: Option<String> }
|
||||
|
||||
async fn update(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<UpdateReq>) -> Result<Json<ApiResponse<menu_service::MenuInfo>>, AppError> {
|
||||
let res = menu_service::update(&db, id, menu_service::UpdateMenuInput { parent_id: req.parent_id, name: req.name, path: req.path, component: req.component, r#type: req.r#type, icon: req.icon, order_no: req.order_no, visible: req.visible, status: req.status, keep_alive: req.keep_alive, perms: req.perms }).await?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
async fn delete_menu(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<bool>>, AppError> {
|
||||
menu_service::delete(&db, id).await?;
|
||||
Ok(Json(ApiResponse::ok(true)))
|
||||
}
|
||||
23
backend/src/routes/mod.rs
Normal file
23
backend/src/routes/mod.rs
Normal file
@ -0,0 +1,23 @@
|
||||
pub mod auth;
|
||||
pub mod users;
|
||||
pub mod roles;
|
||||
pub mod menus;
|
||||
pub mod departments;
|
||||
pub mod logs;
|
||||
// 新增岗位
|
||||
pub mod positions;
|
||||
|
||||
use axum::Router;
|
||||
use crate::db::Db;
|
||||
|
||||
pub fn api_router() -> Router<Db> {
|
||||
Router::new()
|
||||
.nest("/auth", auth::router())
|
||||
.merge(users::router())
|
||||
.merge(roles::router())
|
||||
.merge(menus::router())
|
||||
.merge(departments::router())
|
||||
.merge(logs::router())
|
||||
// 合并岗位路由
|
||||
.merge(positions::router())
|
||||
}
|
||||
38
backend/src/routes/positions.rs
Normal file
38
backend/src/routes/positions.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use axum::{Router, routing::{get, put}, extract::{Path, Query, State}, Json};
|
||||
use serde::Deserialize;
|
||||
use crate::{db::Db, services::position_service, response::ApiResponse, error::AppError};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PageParams { page: Option<u64>, page_size: Option<u64>, keyword: Option<String> }
|
||||
|
||||
pub fn router() -> Router<Db> { Router::new()
|
||||
.route("/positions", get(list).post(create))
|
||||
.route("/positions/{id}", put(update).delete(delete_position))
|
||||
}
|
||||
|
||||
async fn list(State(db): State<Db>, Query(p): Query<PageParams>) -> Result<Json<ApiResponse<position_service::PageResp<position_service::PositionInfo>>>, AppError> {
|
||||
let page = p.page.unwrap_or(1); let page_size = p.page_size.unwrap_or(10);
|
||||
let res = position_service::list(&db, page, page_size, p.keyword).await?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CreateReq { name: String, code: String, description: Option<String>, status: Option<i32>, order_no: Option<i32> }
|
||||
|
||||
async fn create(State(db): State<Db>, Json(req): Json<CreateReq>) -> Result<Json<ApiResponse<position_service::PositionInfo>>, AppError> {
|
||||
let res = position_service::create(&db, position_service::CreatePositionInput { name: req.name, code: req.code, description: req.description, status: req.status, order_no: req.order_no }).await?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UpdateReq { name: Option<String>, description: Option<String>, status: Option<i32>, order_no: Option<i32> }
|
||||
|
||||
async fn update(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<UpdateReq>) -> Result<Json<ApiResponse<position_service::PositionInfo>>, AppError> {
|
||||
let res = position_service::update(&db, id, position_service::UpdatePositionInput { name: req.name, description: req.description, status: req.status, order_no: req.order_no }).await?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
|
||||
async fn delete_position(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<bool>>, AppError> {
|
||||
position_service::delete(&db, id).await?;
|
||||
Ok(Json(ApiResponse::ok(true)))
|
||||
}
|
||||
52
backend/src/routes/roles.rs
Normal file
52
backend/src/routes/roles.rs
Normal file
@ -0,0 +1,52 @@
|
||||
use axum::{Router, routing::{get, put}, extract::{Path, Query, State}, Json};
|
||||
use serde::Deserialize;
|
||||
use crate::{db::Db, services::role_service, response::ApiResponse, error::AppError};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PageParams { page: Option<u64>, page_size: Option<u64>, keyword: Option<String> }
|
||||
|
||||
pub fn router() -> Router<Db> { Router::new()
|
||||
.route("/roles", get(list).post(create))
|
||||
.route("/roles/{id}", put(update).delete(delete_role))
|
||||
.route("/roles/{id}/menus", get(get_menus).put(set_menus))
|
||||
}
|
||||
|
||||
async fn list(State(db): State<Db>, Query(p): Query<PageParams>) -> Result<Json<ApiResponse<role_service::PageResp<role_service::RoleInfo>>>, AppError> {
|
||||
let page = p.page.unwrap_or(1); let page_size = p.page_size.unwrap_or(10);
|
||||
let res = role_service::list(&db, page, page_size, p.keyword).await?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CreateReq { name: String, code: String, description: Option<String>, status: Option<i32> }
|
||||
|
||||
async fn create(State(db): State<Db>, Json(req): Json<CreateReq>) -> Result<Json<ApiResponse<role_service::RoleInfo>>, AppError> {
|
||||
let res = role_service::create(&db, role_service::CreateRoleInput { name: req.name, code: req.code, description: req.description, status: req.status }).await?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UpdateReq { name: Option<String>, description: Option<String>, status: Option<i32> }
|
||||
|
||||
async fn update(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<UpdateReq>) -> Result<Json<ApiResponse<role_service::RoleInfo>>, AppError> {
|
||||
let res = role_service::update(&db, id, role_service::UpdateRoleInput { name: req.name, description: req.description, status: req.status }).await?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct IdsReq { ids: Vec<i64> }
|
||||
|
||||
|
||||
async fn get_menus(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<Vec<i64>>>, AppError> {
|
||||
let ids = role_service::get_menu_ids(&db, id).await?;
|
||||
Ok(Json(ApiResponse::ok(ids)))
|
||||
}
|
||||
|
||||
async fn set_menus(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<IdsReq>) -> Result<Json<ApiResponse<bool>>, AppError> {
|
||||
role_service::set_menu_ids(&db, id, req.ids).await?;
|
||||
Ok(Json(ApiResponse::ok(true)))
|
||||
}
|
||||
async fn delete_role(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<bool>>, AppError> {
|
||||
role_service::delete(&db, id).await?;
|
||||
Ok(Json(ApiResponse::ok(true)))
|
||||
}
|
||||
88
backend/src/routes/users.rs
Normal file
88
backend/src/routes/users.rs
Normal file
@ -0,0 +1,88 @@
|
||||
use axum::{Router, routing::{get, post, put}, extract::{Path, Query, State}, Json};
|
||||
use serde::Deserialize;
|
||||
use crate::{db::Db, services::{user_service, role_service, department_service, position_service}, response::ApiResponse};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PageParams { page: Option<u64>, page_size: Option<u64>, keyword: Option<String> }
|
||||
|
||||
pub fn router() -> Router<Db> { Router::new()
|
||||
.route("/users", get(list).post(create))
|
||||
.route("/users/{id}", put(update).delete(delete_user))
|
||||
.route("/users/{id}/reset_password", post(reset_password))
|
||||
.route("/users/{id}/roles", get(get_roles).put(set_roles))
|
||||
.route("/users/{id}/departments", get(get_departments).put(set_departments))
|
||||
// 新增岗位关联
|
||||
.route("/users/{id}/positions", get(get_positions).put(set_positions))
|
||||
}
|
||||
|
||||
async fn list(State(db): State<Db>, Query(p): Query<PageParams>) -> Result<Json<ApiResponse<user_service::PageResp<user_service::UserInfo>>>, crate::error::AppError> {
|
||||
let page = p.page.unwrap_or(1); let page_size = p.page_size.unwrap_or(10);
|
||||
let res = user_service::list(&db, page, page_size, p.keyword).await?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CreateReq { username: String, password: String, nickname: Option<String>, status: Option<i32> }
|
||||
|
||||
async fn create(State(db): State<Db>, Json(req): Json<CreateReq>) -> Result<Json<ApiResponse<user_service::UserInfo>>, crate::error::AppError> {
|
||||
let input = user_service::CreateUserInput { username: req.username, password: req.password, nickname: req.nickname, status: req.status };
|
||||
let res = user_service::create(&db, input).await?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct UpdateReq { nickname: Option<String>, status: Option<i32> }
|
||||
|
||||
async fn update(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<UpdateReq>) -> Result<Json<ApiResponse<user_service::UserInfo>>, crate::error::AppError> {
|
||||
let res = user_service::update(&db, id, user_service::UpdateUserInput { nickname: req.nickname, status: req.status }).await?;
|
||||
Ok(Json(ApiResponse::ok(res)))
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct IdsReq { ids: Vec<i64> }
|
||||
|
||||
async fn get_roles(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<Vec<i64>>>, crate::error::AppError> {
|
||||
let ids = role_service::get_role_ids_by_user_id(&db, id).await?;
|
||||
Ok(Json(ApiResponse::ok(ids)))
|
||||
}
|
||||
|
||||
async fn set_roles(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<IdsReq>) -> Result<Json<ApiResponse<bool>>, crate::error::AppError> {
|
||||
role_service::set_role_ids_by_user_id(&db, id, req.ids).await?;
|
||||
Ok(Json(ApiResponse::ok(true)))
|
||||
}
|
||||
|
||||
// 新增:用户部门分配
|
||||
async fn get_departments(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<Vec<i64>>>, crate::error::AppError> {
|
||||
let ids = department_service::get_department_ids_by_user_id(&db, id).await?;
|
||||
Ok(Json(ApiResponse::ok(ids)))
|
||||
}
|
||||
|
||||
async fn set_departments(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<IdsReq>) -> Result<Json<ApiResponse<bool>>, crate::error::AppError> {
|
||||
department_service::set_department_ids_by_user_id(&db, id, req.ids).await?;
|
||||
Ok(Json(ApiResponse::ok(true)))
|
||||
}
|
||||
|
||||
// 新增:用户岗位分配
|
||||
async fn get_positions(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<Vec<i64>>>, crate::error::AppError> {
|
||||
let ids = position_service::get_position_ids_by_user_id(&db, id).await?;
|
||||
Ok(Json(ApiResponse::ok(ids)))
|
||||
}
|
||||
|
||||
async fn set_positions(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<IdsReq>) -> Result<Json<ApiResponse<bool>>, crate::error::AppError> {
|
||||
position_service::set_position_ids_by_user_id(&db, id, req.ids).await?;
|
||||
Ok(Json(ApiResponse::ok(true)))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ResetReq { password: String }
|
||||
|
||||
async fn reset_password(State(db): State<Db>, Path(id): Path<i64>, Json(req): Json<ResetReq>) -> Result<Json<ApiResponse<()>>, crate::error::AppError> {
|
||||
let input = user_service::ResetPasswordInput { password: req.password };
|
||||
user_service::reset_password(&db, id, input).await?;
|
||||
Ok(Json(ApiResponse::ok(())))
|
||||
}
|
||||
async fn delete_user(State(db): State<Db>, Path(id): Path<i64>) -> Result<Json<ApiResponse<bool>>, crate::error::AppError> {
|
||||
user_service::delete(&db, id).await?;
|
||||
Ok(Json(ApiResponse::ok(true)))
|
||||
}
|
||||
Reference in New Issue
Block a user