feat(auth): add SeaORM entities and DTOs for auth module

- 11 entity files mapping to all auth migration tables
- DTOs with validation: LoginReq, CreateUserReq, CreateRoleReq, etc.
- Response DTOs: UserResp, RoleResp, PermissionResp, OrganizationResp, etc.
- Added workspace dependencies: jsonwebtoken, argon2, validator, thiserror, utoipa
This commit is contained in:
iven
2026-04-11 02:53:41 +08:00
parent d98e0d383c
commit 411a07caa1
14 changed files with 680 additions and 1 deletions

View File

@@ -5,6 +5,7 @@ edition.workspace = true
[dependencies] [dependencies]
erp-core.workspace = true erp-core.workspace = true
erp-common.workspace = true
tokio.workspace = true tokio.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
@@ -14,3 +15,8 @@ axum.workspace = true
sea-orm.workspace = true sea-orm.workspace = true
tracing.workspace = true tracing.workspace = true
anyhow.workspace = true anyhow.workspace = true
thiserror.workspace = true
jsonwebtoken.workspace = true
argon2.workspace = true
validator.workspace = true
utoipa.workspace = true

185
crates/erp-auth/src/dto.rs Normal file
View File

@@ -0,0 +1,185 @@
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;
use validator::Validate;
// --- Auth DTOs ---
#[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct LoginReq {
#[validate(length(min = 1, message = "用户名不能为空"))]
pub username: String,
#[validate(length(min = 1, message = "密码不能为空"))]
pub password: String,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct LoginResp {
pub access_token: String,
pub refresh_token: String,
pub expires_in: u64,
pub user: UserResp,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct RefreshReq {
pub refresh_token: String,
}
// --- User DTOs ---
#[derive(Debug, Serialize, ToSchema)]
pub struct UserResp {
pub id: Uuid,
pub username: String,
pub email: Option<String>,
pub phone: Option<String>,
pub display_name: Option<String>,
pub avatar_url: Option<String>,
pub status: String,
pub roles: Vec<RoleResp>,
}
#[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct CreateUserReq {
#[validate(length(min = 1, max = 50))]
pub username: String,
#[validate(length(min = 6, max = 128))]
pub password: String,
#[validate(email)]
pub email: Option<String>,
pub phone: Option<String>,
pub display_name: Option<String>,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct UpdateUserReq {
pub email: Option<String>,
pub phone: Option<String>,
pub display_name: Option<String>,
pub status: Option<String>,
}
// --- Role DTOs ---
#[derive(Debug, Serialize, ToSchema)]
pub struct RoleResp {
pub id: Uuid,
pub name: String,
pub code: String,
pub description: Option<String>,
pub is_system: bool,
}
#[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct CreateRoleReq {
#[validate(length(min = 1, max = 50))]
pub name: String,
#[validate(length(min = 1, max = 50))]
pub code: String,
pub description: Option<String>,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct UpdateRoleReq {
pub name: Option<String>,
pub description: Option<String>,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct AssignRolesReq {
pub role_ids: Vec<Uuid>,
}
// --- Permission DTOs ---
#[derive(Debug, Serialize, ToSchema)]
pub struct PermissionResp {
pub id: Uuid,
pub code: String,
pub name: String,
pub resource: String,
pub action: String,
pub description: Option<String>,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct AssignPermissionsReq {
pub permission_ids: Vec<Uuid>,
}
// --- Organization DTOs ---
#[derive(Debug, Serialize, ToSchema)]
pub struct OrganizationResp {
pub id: Uuid,
pub name: String,
pub code: Option<String>,
pub parent_id: Option<Uuid>,
pub path: Option<String>,
pub level: i32,
pub sort_order: i32,
pub children: Vec<OrganizationResp>,
}
#[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct CreateOrganizationReq {
#[validate(length(min = 1))]
pub name: String,
pub code: Option<String>,
pub parent_id: Option<Uuid>,
pub sort_order: Option<i32>,
}
#[derive(Debug, Deserialize, ToSchema)]
pub struct UpdateOrganizationReq {
pub name: Option<String>,
pub code: Option<String>,
pub sort_order: Option<i32>,
}
// --- Department DTOs ---
#[derive(Debug, Serialize, ToSchema)]
pub struct DepartmentResp {
pub id: Uuid,
pub org_id: Uuid,
pub name: String,
pub code: Option<String>,
pub parent_id: Option<Uuid>,
pub manager_id: Option<Uuid>,
pub path: Option<String>,
pub sort_order: i32,
pub children: Vec<DepartmentResp>,
}
#[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct CreateDepartmentReq {
#[validate(length(min = 1))]
pub name: String,
pub code: Option<String>,
pub parent_id: Option<Uuid>,
pub manager_id: Option<Uuid>,
pub sort_order: Option<i32>,
}
// --- Position DTOs ---
#[derive(Debug, Serialize, ToSchema)]
pub struct PositionResp {
pub id: Uuid,
pub dept_id: Uuid,
pub name: String,
pub code: Option<String>,
pub level: i32,
pub sort_order: i32,
}
#[derive(Debug, Deserialize, Validate, ToSchema)]
pub struct CreatePositionReq {
#[validate(length(min = 1))]
pub name: String,
pub code: Option<String>,
pub level: Option<i32>,
pub sort_order: Option<i32>,
}

View File

@@ -0,0 +1,68 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "departments")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid,
pub tenant_id: Uuid,
pub org_id: Uuid,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub manager_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
pub sort_order: i32,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
pub created_by: Uuid,
pub updated_by: Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<DateTimeUtc>,
pub version: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::organization::Entity",
from = "Column::OrgId",
to = "super::organization::Column::Id",
on_delete = "Restrict"
)]
Organization,
#[sea_orm(has_many = "super::position::Entity")]
Position,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::ManagerId",
to = "super::user::Column::Id",
on_delete = "SetNull"
)]
Manager,
}
impl Related<super::organization::Entity> for Entity {
fn to() -> RelationDef {
Relation::Organization.def()
}
}
impl Related<super::position::Entity> for Entity {
fn to() -> RelationDef {
Relation::Position.def()
}
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::Manager.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -0,0 +1,10 @@
pub mod user;
pub mod user_credential;
pub mod user_token;
pub mod role;
pub mod permission;
pub mod role_permission;
pub mod user_role;
pub mod organization;
pub mod department;
pub mod position;

View File

@@ -0,0 +1,40 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "organizations")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid,
pub tenant_id: Uuid,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_id: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
pub level: i32,
pub sort_order: i32,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
pub created_by: Uuid,
pub updated_by: Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<DateTimeUtc>,
pub version: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::department::Entity")]
Department,
}
impl Related<super::department::Entity> for Entity {
fn to() -> RelationDef {
Relation::Department.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -0,0 +1,37 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "permissions")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid,
pub tenant_id: Uuid,
pub code: String,
pub name: String,
pub resource: String,
pub action: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
pub created_by: Uuid,
pub updated_by: Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<DateTimeUtc>,
pub version: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::role_permission::Entity")]
RolePermission,
}
impl Related<super::role_permission::Entity> for Entity {
fn to() -> RelationDef {
Relation::RolePermission.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -0,0 +1,42 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "positions")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid,
pub tenant_id: Uuid,
pub dept_id: Uuid,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
pub level: i32,
pub sort_order: i32,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
pub created_by: Uuid,
pub updated_by: Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<DateTimeUtc>,
pub version: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::department::Entity",
from = "Column::DeptId",
to = "super::department::Column::Id",
on_delete = "Restrict"
)]
Department,
}
impl Related<super::department::Entity> for Entity {
fn to() -> RelationDef {
Relation::Department.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -0,0 +1,44 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "roles")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid,
pub tenant_id: Uuid,
pub name: String,
pub code: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub is_system: bool,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
pub created_by: Uuid,
pub updated_by: Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<DateTimeUtc>,
pub version: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::role_permission::Entity")]
RolePermission,
#[sea_orm(has_many = "super::user_role::Entity")]
UserRole,
}
impl Related<super::role_permission::Entity> for Entity {
fn to() -> RelationDef {
Relation::RolePermission.def()
}
}
impl Related<super::user_role::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserRole.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -0,0 +1,51 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "role_permissions")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub role_id: Uuid,
#[sea_orm(primary_key, auto_increment = false)]
pub permission_id: Uuid,
pub tenant_id: Uuid,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
pub created_by: Uuid,
pub updated_by: Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<DateTimeUtc>,
pub version: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::role::Entity",
from = "Column::RoleId",
to = "super::role::Column::Id",
on_delete = "Cascade"
)]
Role,
#[sea_orm(
belongs_to = "super::permission::Entity",
from = "Column::PermissionId",
to = "super::permission::Column::Id",
on_delete = "Cascade"
)]
Permission,
}
impl Related<super::role::Entity> for Entity {
fn to() -> RelationDef {
Relation::Role.def()
}
}
impl Related<super::permission::Entity> for Entity {
fn to() -> RelationDef {
Relation::Permission.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -0,0 +1,59 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "users")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid,
pub tenant_id: Uuid,
pub username: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub phone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub avatar_url: Option<String>,
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_login_at: Option<DateTimeUtc>,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
pub created_by: Uuid,
pub updated_by: Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<DateTimeUtc>,
pub version: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::user_credential::Entity")]
UserCredential,
#[sea_orm(has_many = "super::user_token::Entity")]
UserToken,
#[sea_orm(has_many = "super::user_role::Entity")]
UserRole,
}
impl Related<super::user_credential::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserCredential.def()
}
}
impl Related<super::user_token::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserToken.def()
}
}
impl Related<super::user_role::Entity> for Entity {
fn to() -> RelationDef {
Relation::UserRole.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -0,0 +1,41 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "user_credentials")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid,
pub tenant_id: Uuid,
pub user_id: Uuid,
pub credential_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub credential_data: Option<serde_json::Value>,
pub verified: bool,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
pub created_by: Uuid,
pub updated_by: Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<DateTimeUtc>,
pub version: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Id",
on_delete = "Cascade"
)]
User,
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -0,0 +1,51 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "user_roles")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub user_id: Uuid,
#[sea_orm(primary_key, auto_increment = false)]
pub role_id: Uuid,
pub tenant_id: Uuid,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
pub created_by: Uuid,
pub updated_by: Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<DateTimeUtc>,
pub version: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Id",
on_delete = "Cascade"
)]
User,
#[sea_orm(
belongs_to = "super::role::Entity",
from = "Column::RoleId",
to = "super::role::Column::Id",
on_delete = "Cascade"
)]
Role,
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl Related<super::role::Entity> for Entity {
fn to() -> RelationDef {
Relation::Role.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -0,0 +1,44 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "user_tokens")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Uuid,
pub tenant_id: Uuid,
pub user_id: Uuid,
pub token_hash: String,
pub token_type: String,
pub expires_at: DateTimeUtc,
#[serde(skip_serializing_if = "Option::is_none")]
pub revoked_at: Option<DateTimeUtc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_info: Option<String>,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
pub created_by: Uuid,
pub updated_by: Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
pub deleted_at: Option<DateTimeUtc>,
pub version: i32,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::UserId",
to = "super::user::Column::Id",
on_delete = "Cascade"
)]
User,
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@@ -1 +1,2 @@
// erp-auth: 身份与权限模块 (Phase 2) pub mod dto;
pub mod entity;