feat: rebuild email platform and operations console

This commit is contained in:
2026-07-25 23:06:15 +08:00
parent c109abc6f5
commit 32e0969ca1
443 changed files with 56446 additions and 1585 deletions
+72
View File
@@ -0,0 +1,72 @@
use serde::Deserialize;
use std::fmt;
use validator::Validate;
/// 注册请求
#[derive(Deserialize, Validate)]
pub struct RegisterRequest {
#[validate(email)]
pub email: String,
#[validate(length(min = 8, max = 128))]
pub password: String,
/// 邀请码(配置 invite_code 为空时不校验)
pub invite_code: Option<String>,
}
// 实现 Debug trait,对密码进行脱敏
impl fmt::Debug for RegisterRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"RegisterRequest {{ email: {}, password: *** }}",
self.email
)
}
}
/// 登录请求
#[derive(Deserialize, Validate)]
pub struct LoginRequest {
#[validate(email)]
pub email: String,
#[validate(length(min = 1, max = 128))]
pub password: String,
}
// 实现 Debug trait
impl fmt::Debug for LoginRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "LoginRequest {{ email: {}, password: *** }}", self.email)
}
}
/// 删除用户请求
#[derive(Deserialize)]
pub struct DeleteUserRequest {
pub user_id: String,
pub password: String,
}
// 实现 Debug trait
impl fmt::Debug for DeleteUserRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"DeleteUserRequest {{ user_id: {}, password: *** }}",
self.user_id
)
}
}
/// 刷新令牌请求
#[derive(Deserialize)]
pub struct RefreshRequest {
pub refresh_token: String,
}
// RefreshRequest 的 refresh_token 是敏感字段,需要脱敏
impl fmt::Debug for RefreshRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RefreshRequest {{ refresh_token: *** }}")
}
}
+29
View File
@@ -0,0 +1,29 @@
use serde::Deserialize;
use validator::Validate;
/// 创建临时邮箱请求
#[derive(Deserialize, Validate)]
pub struct CreateMailboxRequest {
/// 自定义本地部分(不传则随机生成)
#[validate(length(min = 1, max = 64))]
pub local_part: Option<String>,
/// 自定义域名(不传则用配置的第一个 local_domain
#[validate(length(min = 1, max = 255))]
pub domain: Option<String>,
/// 用途备注
#[validate(length(max = 128))]
pub note: Option<String>,
/// 自定义有效期(小时),不传则用配置默认
pub ttl_hours: Option<i64>,
}
impl std::fmt::Debug for CreateMailboxRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CreateMailboxRequest")
.field("local_part", &self.local_part)
.field("domain", &self.domain)
.field("note", &self.note)
.field("ttl_hours", &self.ttl_hours)
.finish()
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod auth;
pub mod mailbox;
pub mod user;
+12
View File
@@ -0,0 +1,12 @@
use serde::Deserialize;
use validator::Validate;
#[derive(Debug, Deserialize, Validate)]
pub struct UpdateProfileRequest {
#[validate(length(max = 80))]
pub display_name: Option<String>,
#[validate(url)]
pub avatar_url: Option<String>,
#[validate(length(max = 500))]
pub bio: Option<String>,
}
+31
View File
@@ -0,0 +1,31 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
/// 滥用规则触发事件
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "abuse_events")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
pub rule_id: i64,
/// 触发的 domain / ip
pub target: String,
/// JSON 详情
pub detail: Option<String>,
pub created_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, insert: bool) -> Result<Self, DbErr> {
let mut value = self;
if insert {
value.created_at = Set(chrono::Utc::now().naive_utc());
}
Ok(value)
}
}
+25
View File
@@ -0,0 +1,25 @@
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
/// 滥用检测规则
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "abuse_rules")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
pub name: String,
/// domain_emails_10min | ip_connects_1min | ...
pub metric: String,
pub window_seconds: i64,
pub threshold: i64,
/// auto_block_sender | auto_block_ip | alert | quarantine
pub action: String,
pub block_ttl_seconds: i64,
pub enabled: bool,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {}
+44
View File
@@ -0,0 +1,44 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
/// 审计日志(SMTP 事件 + 管理操作)
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "audit_logs")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
/// smtp_connect | smtp_rcpt | smtp_accept | smtp_reject | admin_* | ...
pub event_type: String,
pub source_ip: Option<String>,
pub helo: Option<String>,
pub mail_from: Option<String>,
pub rcpt_to: Option<String>,
/// accept | defer | reject | quarantine | ...
pub action: String,
pub reason: Option<String>,
/// 管理操作时填 user_id
pub operator_id: Option<String>,
/// 被操作资源类型:user | mailbox | email | attachment | blacklist | credit_rule
pub target_type: Option<String>,
/// 被操作资源 ID,统一使用字符串兼容不同主键类型
pub target_id: Option<String>,
/// 扩展审计上下文与批次 ID
#[sea_orm(column_type = "Text", nullable)]
pub metadata_json: Option<String>,
pub created_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, insert: bool) -> Result<Self, DbErr> {
let mut value = self;
if insert {
value.created_at = Set(chrono::Utc::now().naive_utc());
}
Ok(value)
}
}
+31
View File
@@ -0,0 +1,31 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
/// 来源 IP 黑名单(单 IP 或 CIDR,应用层匹配)
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "blocked_ips")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
pub ip: String,
pub reason: String,
/// manual | auto
pub source: String,
pub expires_at: Option<DateTime>,
pub created_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, insert: bool) -> Result<Self, DbErr> {
let mut value = self;
if insert {
value.created_at = Set(chrono::Utc::now().naive_utc());
}
Ok(value)
}
}
@@ -0,0 +1,35 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
/// 发件黑名单(domain | address | pattern
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "blocked_senders")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
/// domain | address | pattern
pub kind: String,
/// 如 flova.ai / spam@x.com / *@spam.com
pub value: String,
pub reason: String,
/// manual | auto
pub source: String,
/// None=永久
pub expires_at: Option<DateTime>,
pub created_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, insert: bool) -> Result<Self, DbErr> {
let mut value = self;
if insert {
value.created_at = Set(chrono::Utc::now().naive_utc());
}
Ok(value)
}
}
@@ -0,0 +1,29 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
/// 用户积分账户(1:1 users
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "credit_accounts")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub user_id: String,
pub balance: i64,
/// 乐观锁版本号(并发扣减用)
pub version: i64,
pub total_granted: i64,
pub total_consumed: i64,
pub updated_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, _insert: bool) -> Result<Self, DbErr> {
let mut value = self;
value.updated_at = Set(chrono::Utc::now().naive_utc());
Ok(value)
}
}
@@ -0,0 +1,28 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "credit_check_ins")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub user_id: String,
#[sea_orm(primary_key, auto_increment = false)]
pub date: chrono::NaiveDate,
pub reward_granted: i64,
pub created_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, insert: bool) -> Result<Self, DbErr> {
let mut value = self;
if insert {
value.created_at = Set(chrono::Utc::now().naive_utc());
}
Ok(value)
}
}
+37
View File
@@ -0,0 +1,37 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "credit_rules")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: i32,
pub register_bonus: i64,
pub daily_check_in_reward: i64,
pub reward_balance_cap: i64,
pub create_mailbox_cost: i64,
pub receive_email_cost: i64,
pub daily_mailboxes_limit: i32,
pub daily_emails_limit: i32,
pub version: i64,
pub updated_by: Option<String>,
pub created_at: DateTime,
pub updated_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, insert: bool) -> Result<Self, DbErr> {
let mut value = self;
let now = chrono::Utc::now().naive_utc();
if insert {
value.created_at = Set(now);
}
value.updated_at = Set(now);
Ok(value)
}
}
@@ -0,0 +1,31 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "credit_rule_changes")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
pub operator_id: String,
#[sea_orm(column_type = "Text")]
pub previous_json: String,
#[sea_orm(column_type = "Text")]
pub next_json: String,
pub reason: String,
pub created_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, insert: bool) -> Result<Self, DbErr> {
let mut value = self;
if insert {
value.created_at = Set(chrono::Utc::now().naive_utc());
}
Ok(value)
}
}
@@ -0,0 +1,37 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
/// 积分流水(append-onlybalance_after 保证可审计)
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "credit_transactions")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
pub user_id: String,
/// 正=充值/赠送,负=消费
pub delta: i64,
/// 变更后余额(审计关键)
pub balance_after: i64,
/// register_bonus | create_mailbox | receive_email | admin_adjust | ...
pub reason: String,
pub related_mailbox_id: Option<i64>,
pub related_email_id: Option<i64>,
pub description: Option<String>,
pub operator_id: Option<String>,
pub created_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, insert: bool) -> Result<Self, DbErr> {
let mut value = self;
if insert {
value.created_at = Set(chrono::Utc::now().naive_utc());
}
Ok(value)
}
}
+29
View File
@@ -0,0 +1,29 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
/// 每日额度计数(复合主键 user_id + date
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "daily_quotas")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub user_id: String,
#[sea_orm(primary_key, auto_increment = false)]
pub date: chrono::NaiveDate,
pub mailboxes_created: i32,
pub emails_received: i32,
pub bytes_received: i64,
pub updated_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, _insert: bool) -> Result<Self, DbErr> {
let mut value = self;
value.updated_at = Set(chrono::Utc::now().naive_utc());
Ok(value)
}
}
+62
View File
@@ -0,0 +1,62 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
/// 接收到的邮件(含安全与审计字段)
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "emails")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
/// 所属邮箱 IDFK mailboxes.id
pub mailbox_id: i64,
/// 冗余:所属用户 ID,便于按用户聚合
pub user_id: String,
/// 完整收件地址
pub recipient: String,
/// envelope MAIL FROM
pub mail_from: String,
/// envelope RCPT TO
pub rcpt_to: String,
/// 发件人显示名
pub sender_name: Option<String>,
pub subject: Option<String>,
pub text_body: Option<String>,
pub html_body: Option<String>,
/// 来源 IP
pub source_ip: String,
/// SMTP HELO/EHLO
pub helo: Option<String>,
/// PTR 反查结果
pub ptr_result: Option<String>,
/// SPF 结果
pub spf_result: Option<String>,
/// DKIM 结果
pub dkim_result: Option<String>,
/// DMARC 结果
pub dmarc_result: Option<String>,
/// 滥用评分 0-100
pub abuse_score: i32,
/// 状态:received | quarantined | rejected
pub status: String,
pub size_bytes: i64,
/// Message-ID 头
pub message_id: Option<String>,
pub received_at: DateTime,
/// 邮件保留期截止时间(默认 received + email_ttl_days
pub expires_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, insert: bool) -> Result<Self, DbErr> {
let mut value = self;
if insert {
value.received_at = Set(chrono::Utc::now().naive_utc());
}
Ok(value)
}
}
@@ -0,0 +1,33 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
/// 邮件附件(小附件 inline 存 content;大附件后续可落盘存 storage_path
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "email_attachments")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
/// 所属邮件 IDFK emails.id
pub email_id: i64,
pub filename: Option<String>,
pub content_type: String,
pub size_bytes: i64,
/// 二进制内容(小附件)
pub content: Option<Vec<u8>>,
pub created_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, insert: bool) -> Result<Self, DbErr> {
let mut value = self;
if insert {
value.created_at = Set(chrono::Utc::now().naive_utc());
}
Ok(value)
}
}
+27
View File
@@ -0,0 +1,27 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "email_logs")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
pub user_id: Option<String>,
pub recipient: String,
pub kind: String,
pub status: String,
pub error: Option<String>,
pub created_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, insert: bool) -> Result<Self, DbErr> {
let mut value = self;
if insert {
value.created_at = Set(chrono::Utc::now().naive_utc());
}
Ok(value)
}
}
@@ -0,0 +1,35 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
/// 灰名单条目(tripletsender_ip + mail_from + rcpt_to
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "greylist_entries")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
pub sender_ip: String,
pub mail_from: String,
pub rcpt_to: String,
pub first_seen: DateTime,
/// 期望重试时间(首次 + retry_delay
pub retry_after: DateTime,
pub delivered: bool,
pub created_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, insert: bool) -> Result<Self, DbErr> {
let mut value = self;
let now = chrono::Utc::now().naive_utc();
if insert {
value.created_at = Set(now);
value.first_seen = Set(now);
}
Ok(value)
}
}
+50
View File
@@ -0,0 +1,50 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
/// 临时邮箱地址(用户创建,绑定 user,带过期与配额)
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "mailboxes")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
/// 所属用户 IDFK users.id
pub user_id: String,
/// 完整地址,如 abc123@mail.shenjianl.cn(唯一)
#[sea_orm(unique)]
pub address: String,
/// 本地部分,如 abc123
pub local_part: String,
/// 域名,如 mail.shenjianl.cn
pub domain: String,
/// 邮箱级 access_token 的 SHA-256 哈希
pub access_token_hash: String,
/// 用途备注(可选)
pub note: Option<String>,
/// 状态:active | expired | revoked
pub status: String,
/// 单邮箱容量上限(字节)
pub max_quota_bytes: i64,
/// 已用容量(字节)
pub used_bytes: i64,
/// 邮箱过期时间
pub expires_at: DateTime,
pub created_at: DateTime,
pub updated_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, insert: bool) -> Result<Self, DbErr> {
let mut value = self;
let now = chrono::Utc::now().naive_utc();
if insert {
value.created_at = Set(now);
}
value.updated_at = Set(now);
Ok(value)
}
}
+18
View File
@@ -0,0 +1,18 @@
pub mod abuse_event;
pub mod abuse_rule;
pub mod audit_log;
pub mod blocked_ip;
pub mod blocked_sender;
pub mod credit_account;
pub mod credit_check_in;
pub mod credit_rule;
pub mod credit_rule_change;
pub mod credit_transaction;
pub mod daily_quota;
pub mod email;
pub mod email_attachment;
pub mod email_logs;
pub mod greylist_entry;
pub mod mailbox;
pub mod user_profiles;
pub mod users;
@@ -0,0 +1,29 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "user_profiles")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub user_id: String,
pub display_name: Option<String>,
pub avatar_url: Option<String>,
pub bio: Option<String>,
pub created_at: DateTime,
pub updated_at: DateTime,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C: ConnectionTrait>(self, _: &C, insert: bool) -> Result<Self, DbErr> {
let mut value = self;
let now = chrono::Utc::now().naive_utc();
if insert {
value.created_at = Set(now);
}
value.updated_at = Set(now);
Ok(value)
}
}
+46
View File
@@ -0,0 +1,46 @@
use sea_orm::entity::prelude::*;
use sea_orm::Set;
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: String,
#[sea_orm(unique)]
pub email: String,
pub password_hash: String,
/// 角色:user | admin
pub role: String,
/// 状态:active | suspended | banned
pub status: String,
pub created_at: DateTime,
pub updated_at: DateTime,
pub deleted_at: Option<DateTime>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
/// 在保存前自动填充时间戳
async fn before_save<C>(self, _db: &C, insert: bool) -> Result<Self, DbErr>
where
C: ConnectionTrait,
{
let mut this = self;
let now = chrono::Utc::now().naive_utc();
if insert {
// 插入时:设置创建时间和更新时间
this.created_at = Set(now);
this.updated_at = Set(now);
} else {
// 更新时:只更新更新时间
this.updated_at = Set(now);
}
Ok(this)
}
}
+3
View File
@@ -0,0 +1,3 @@
pub mod dto;
pub mod entities;
pub mod vo;
+72
View File
@@ -0,0 +1,72 @@
use serde::Serialize;
/// 注册结果
#[derive(Debug, Serialize)]
pub struct RegisterResult {
pub email: String,
pub created_at: String, // ISO 8601 格式
pub access_token: String,
pub refresh_token: String,
}
impl From<(crate::domain::entities::users::Model, String, String)> for RegisterResult {
fn from(
(user_model, access_token, refresh_token): (
crate::domain::entities::users::Model,
String,
String,
),
) -> Self {
Self {
email: user_model.email,
created_at: user_model
.created_at
.format("%Y-%m-%dT%H:%M:%S%.3fZ")
.to_string(),
access_token,
refresh_token,
}
}
}
/// 登录结果
#[derive(Debug, Serialize)]
pub struct LoginResult {
pub id: String,
pub email: String,
pub role: String,
pub status: String,
pub created_at: String, // ISO 8601 格式
pub access_token: String,
pub refresh_token: String,
}
impl From<(crate::domain::entities::users::Model, String, String)> for LoginResult {
fn from(
(user_model, access_token, refresh_token): (
crate::domain::entities::users::Model,
String,
String,
),
) -> Self {
Self {
id: user_model.id,
email: user_model.email,
role: user_model.role,
status: user_model.status,
created_at: user_model
.created_at
.format("%Y-%m-%dT%H:%M:%S%.3fZ")
.to_string(),
access_token,
refresh_token,
}
}
}
/// 刷新 Token 结果
#[derive(Debug, Serialize)]
pub struct RefreshResult {
pub access_token: String,
pub refresh_token: String,
}
+63
View File
@@ -0,0 +1,63 @@
use crate::domain::entities::{email, email_attachment};
use serde::Serialize;
/// 邮件列表项(摘要)
#[derive(Debug, Serialize)]
pub struct EmailSummary {
pub id: i64,
pub mail_from: String,
pub sender_name: Option<String>,
pub subject: Option<String>,
pub size_bytes: i64,
pub status: String,
pub received_at: String,
}
impl From<email::Model> for EmailSummary {
fn from(m: email::Model) -> Self {
Self {
id: m.id,
mail_from: m.mail_from,
sender_name: m.sender_name,
subject: m.subject,
size_bytes: m.size_bytes,
status: m.status,
received_at: m.received_at.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string(),
}
}
}
/// 附件摘要
#[derive(Debug, Serialize)]
pub struct AttachmentSummary {
pub id: i64,
pub filename: Option<String>,
pub content_type: String,
pub size_bytes: i64,
}
impl From<email_attachment::Model> for AttachmentSummary {
fn from(a: email_attachment::Model) -> Self {
Self {
id: a.id,
filename: a.filename,
content_type: a.content_type,
size_bytes: a.size_bytes,
}
}
}
/// 邮件详情
#[derive(Debug, Serialize)]
pub struct EmailDetail {
pub id: i64,
pub mail_from: String,
pub sender_name: Option<String>,
pub subject: Option<String>,
pub text_body: Option<String>,
pub html_body: Option<String>,
pub size_bytes: i64,
pub status: String,
pub received_at: String,
pub attachments: Vec<AttachmentSummary>,
}
+42
View File
@@ -0,0 +1,42 @@
use crate::domain::entities::mailbox;
use serde::Serialize;
/// 邮箱视图
#[derive(Debug, Serialize)]
pub struct MailboxVO {
pub id: i64,
pub address: String,
pub local_part: String,
pub domain: String,
pub note: Option<String>,
pub status: String,
pub max_quota_bytes: i64,
pub used_bytes: i64,
pub expires_at: String,
pub created_at: String,
}
impl From<mailbox::Model> for MailboxVO {
fn from(m: mailbox::Model) -> Self {
Self {
id: m.id,
address: m.address,
local_part: m.local_part,
domain: m.domain,
note: m.note,
status: m.status,
max_quota_bytes: m.max_quota_bytes,
used_bytes: m.used_bytes,
expires_at: m.expires_at.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string(),
created_at: m.created_at.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string(),
}
}
}
/// 创建邮箱结果(含一次性返回的明文 access_token
#[derive(Debug, Serialize)]
pub struct CreateMailboxResult {
#[serde(flatten)]
pub mailbox: MailboxVO,
pub access_token: String,
}
+58
View File
@@ -0,0 +1,58 @@
pub mod auth;
pub mod email;
pub mod mailbox;
pub mod user;
use axum::http::StatusCode;
/// 统一的 API 响应结构
use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct ApiResponse<T> {
/// HTTP 状态码
pub code: u16,
/// 响应消息
pub message: String,
/// 响应数据
pub data: Option<T>,
}
impl<T: Serialize> ApiResponse<T> {
/// 成功响应(200
pub fn success(data: T) -> Self {
Self {
code: 200,
message: "Success".to_string(),
data: Some(data),
}
}
/// 成功响应(自定义消息)
pub fn success_with_message(data: T, message: &str) -> Self {
Self {
code: 200,
message: message.to_string(),
data: Some(data),
}
}
/// 错误响应
#[allow(dead_code)]
pub fn error(status_code: StatusCode, message: &str) -> ApiResponse<()> {
ApiResponse {
code: status_code.as_u16(),
message: message.to_string(),
data: None,
}
}
/// 错误响应(带数据)
#[allow(dead_code)]
pub fn error_with_data(status_code: StatusCode, message: &str, data: T) -> ApiResponse<T> {
ApiResponse {
code: status_code.as_u16(),
message: message.to_string(),
data: Some(data),
}
}
}
+1
View File
@@ -0,0 +1 @@
// 用户相关 VO(预留)