-
-
{{ $t('howItWorks.title') }}
-
-
-
- @{{ domain }}
-
-
- - {{ $t('howItWorks.step2') }}
- - {{ $t('howItWorks.step3') }}
-
+
+
-
-
{{ $t('language') }}
-
-
+
+
+
{{ t("settings.language") }}
+
{{ t("settings.languageHint") }}
-
-
+
+
+
+
+
+
+
+
+
{{ t("settings.serviceStatus") }}
+
{{ t("settings.serviceHint") }}
+
+
+
+ {{ t("settings.database") }}
+
+ Redis
+
+ {{ t("settings.outboundEmail") }}
+
+
+
+
+
+
+
+
{{ t("settings.deleteAccount") }}
+
{{ t("settings.deleteHint") }}
+
+
+
+
+
+
+
+
-
-
-
-
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
index 88001e4..bd2f32e 100644
--- a/frontend/vite.config.js
+++ b/frontend/vite.config.js
@@ -1,46 +1,53 @@
-import { defineConfig } from 'vite'
-import vue from '@vitejs/plugin-vue'
-import { VitePWA } from 'vite-plugin-pwa'
-import { fileURLToPath, URL } from 'node:url'
+import { defineConfig, loadEnv } from "vite";
+import vue from "@vitejs/plugin-vue";
+import { VitePWA } from "vite-plugin-pwa";
+import { fileURLToPath, URL } from "node:url";
-export default defineConfig({
- plugins: [
- vue(),
- VitePWA({
- registerType: 'autoUpdate',
- manifest: {
- name: 'Email Unlimit',
- short_name: 'EmailUnlimit',
- description: 'Your Exclusive, Unlimited, and Private Temporary Email',
- theme_color: '#000000',
- icons: [
- {
- src: 'icons/icon.svg',
- sizes: 'any',
- type: 'image/svg+xml',
- },
- ],
+export default defineConfig(({ mode }) => {
+ const env = loadEnv(mode, process.cwd(), "");
+ const apiTarget = env.VITE_API_PROXY_TARGET || "http://127.0.0.1:3000";
+
+ return {
+ plugins: [
+ vue(),
+ VitePWA({
+ registerType: "autoUpdate",
+ manifest: {
+ name: "Email Unlimit",
+ short_name: "EmailUnlimit",
+ description: "Private temporary mailbox workspace",
+ theme_color: "#20231f",
+ icons: [
+ {
+ src: "icons/icon.svg",
+ sizes: "any",
+ type: "image/svg+xml",
+ },
+ ],
+ },
+ }),
+ ],
+ resolve: {
+ alias: {
+ "@": fileURLToPath(new URL("./src", import.meta.url)),
},
- }),
- ],
- resolve: {
- alias: {
- '@': fileURLToPath(new URL('./src', import.meta.url))
- }
- },
- server: {
- proxy: process.env.NODE_ENV === 'production' ? {} : {
- '/api': {
- target: 'http://localhost:5182',
- changeOrigin: true,
+ },
+ server: {
+ proxy: {
+ "/auth": {
+ target: apiTarget,
+ changeOrigin: true,
+ },
+ "/api": {
+ target: apiTarget,
+ changeOrigin: true,
+ ws: true,
+ },
+ "/health": {
+ target: apiTarget,
+ changeOrigin: true,
+ },
},
- '/ws': {
- target: 'ws://localhost:5182',
- ws: true,
- },
- }
- },
- define: {
- 'process.env': process.env
- }
-})
+ },
+ };
+});
diff --git a/nginx.full.conf b/nginx.full.conf
index fe8f4e6..afdcb98 100644
--- a/nginx.full.conf
+++ b/nginx.full.conf
@@ -10,7 +10,7 @@ http {
default_type application/octet-stream;
# 启用 SSL 协议,建议加上 TLSv1.3
- ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
+ ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
sendfile on;
@@ -33,35 +33,23 @@ http {
ssl_certificate /etc/nginx/certs/mail.shenjianl.cn_bundle.pem; # 改为你自己的证书文件
ssl_certificate_key /etc/nginx/certs/mail.shenjianl.cn.key; # 改为你自己的密钥文件
- root /usr/share/nginx/html;
- index index.html;
-
location / {
- try_files $uri $uri/ /index.html;
- }
-
- # 反向代理 /api 到后端服务
- location /api {
- proxy_pass http://email-backend:5182;
+ proxy_pass http://frontend:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
- # WebSocket 支持
- location /ws {
- proxy_pass http://email-backend:5182;
+ location ~ ^/(auth|api|admin)(/|$) {
+ proxy_pass http://backend:3000;
proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
-
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
-
- # 设置更长的超时时间以保持 WebSocket 连接
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6bd86e2
--- /dev/null
+++ b/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "email-unlimit",
+ "private": true,
+ "packageManager": "pnpm@11.3.0",
+ "dependencies": {
+ "@playwright/test": "^1.54.2"
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..b6bf946
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,52 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@playwright/test':
+ specifier: ^1.54.2
+ version: 1.61.1
+
+packages:
+
+ '@playwright/test@1.61.1':
+ resolution: {integrity: sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ fsevents@2.3.2:
+ resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ playwright-core@1.61.1:
+ resolution: {integrity: sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ playwright@1.61.1:
+ resolution: {integrity: sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+snapshots:
+
+ '@playwright/test@1.61.1':
+ dependencies:
+ playwright: 1.61.1
+
+ fsevents@2.3.2:
+ optional: true
+
+ playwright-core@1.61.1: {}
+
+ playwright@1.61.1:
+ dependencies:
+ playwright-core: 1.61.1
+ optionalDependencies:
+ fsevents: 2.3.2
diff --git a/server/.dockerignore b/server/.dockerignore
new file mode 100644
index 0000000..eb6c4f7
--- /dev/null
+++ b/server/.dockerignore
@@ -0,0 +1,4 @@
+target
+db.sqlite3
+.env
+.git
diff --git a/server/.env.example b/server/.env.example
new file mode 100644
index 0000000..73629c7
--- /dev/null
+++ b/server/.env.example
@@ -0,0 +1,40 @@
+# 环境变量参考配置
+#
+# ⚠️ 重要提示:
+# - 本项目不支持 .env 文件
+# - 开发环境和生产环境都请使用 config/ 目录下的 toml 配置文件
+# - 环境变量仅用于 Docker/Kubernetes/systemd 等部署场景
+#
+# ============================================
+
+# ============================================
+# CLI 参数环境变量
+# ============================================
+# 运行环境:development, production
+# ENV=development
+
+# 调试模式:true, false
+# DEBUG=false
+
+# 配置文件路径
+# CONFIG=config/production.toml
+
+# 可选能力与 Web 防护
+REDIS__ENABLED=false
+EMAIL__ENABLED=false
+EMAIL__SMTP_HOST=smtp.example.com
+EMAIL__SMTP_PORT=587
+EMAIL__SMTP_USERNAME=
+EMAIL__SMTP_PASSWORD=
+EMAIL__FROM_EMAIL=noreply@example.com
+EMAIL__QUEUE_ENABLED=false
+EMAIL__WORKER_POOL_SIZE=2
+SERVER__CORS_ORIGINS=http://localhost:5173
+SERVER__REQUEST_TIMEOUT_SECONDS=30
+SERVER__MAX_BODY_BYTES=1048576
+SERVER__CONCURRENCY_LIMIT=256
+SERVER__RATE_LIMIT_PER_MINUTE=120
+AUTH__JWT_SECRET=replace-with-at-least-32-random-characters
+AUTH__INVITE_CODE=replace-with-at-least-16-random-characters
+AUTH__BOOTSTRAP_ADMIN_EMAIL=admin@example.com
+SMTP__BODY_ENCRYPTION_KEY=0123456789abcdef0123456789abcdef
diff --git a/server/.gitignore b/server/.gitignore
new file mode 100644
index 0000000..beae8ea
--- /dev/null
+++ b/server/.gitignore
@@ -0,0 +1,24 @@
+# Rust
+/target/
+**/*.rs.bk
+*.pdb
+
+
+# IDE
+.idea/
+.vscode/
+*.swp
+*.swo
+*~
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Config (不要提交敏感配置)
+.env
+db.sqlite*
+data
+API_FLOW*
+SEAORM*
+.claude/settings.local.json
diff --git a/server/Cargo.lock b/server/Cargo.lock
new file mode 100644
index 0000000..d3d07d2
--- /dev/null
+++ b/server/Cargo.lock
@@ -0,0 +1,4372 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aead"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
+dependencies = [
+ "crypto-common",
+ "generic-array",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures 0.2.17",
+]
+
+[[package]]
+name = "aes-gcm"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
+name = "ahash"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
+dependencies = [
+ "getrandom 0.2.17",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "aliasable"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
+
+[[package]]
+name = "arc-swap"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "argon2"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
+dependencies = [
+ "base64ct",
+ "blake2",
+ "cpufeatures 0.2.17",
+ "password-hash",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "async-stream"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.89"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "atoi"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "axum"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
+dependencies = [
+ "async-trait",
+ "axum-core",
+ "base64 0.22.1",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sha1",
+ "sync_wrapper",
+ "tokio",
+ "tokio-tungstenite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "rustversion",
+ "sync_wrapper",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "backon"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef"
+dependencies = [
+ "fastrand",
+]
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "base64ct"
+version = "1.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
+
+[[package]]
+name = "bigdecimal"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695"
+dependencies = [
+ "autocfg",
+ "libm",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "bitvec"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
+dependencies = [
+ "funty",
+ "radium",
+ "tap",
+ "wyz",
+]
+
+[[package]]
+name = "blake2"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "borsh"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f"
+dependencies = [
+ "borsh-derive",
+ "cfg_aliases",
+]
+
+[[package]]
+name = "borsh-derive"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c"
+dependencies = [
+ "once_cell",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.19.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
+
+[[package]]
+name = "bytecheck"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2"
+dependencies = [
+ "bytecheck_derive",
+ "ptr_meta",
+ "simdutf8",
+]
+
+[[package]]
+name = "bytecheck_derive"
+version = "0.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+
+[[package]]
+name = "cc"
+version = "1.2.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "chacha20"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "chrono"
+version = "0.4.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "serde",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "chrono-tz"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3"
+dependencies = [
+ "chrono",
+ "phf",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+
+[[package]]
+name = "combine"
+version = "4.6.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "memchr",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "config"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca"
+dependencies = [
+ "async-trait",
+ "json5",
+ "lazy_static",
+ "nom 7.1.3",
+ "pathdiff",
+ "ron",
+ "rust-ini",
+ "serde",
+ "serde_json",
+ "toml",
+ "yaml-rust",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "rand_core 0.6.4",
+ "typenum",
+]
+
+[[package]]
+name = "ctr"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
+dependencies = [
+ "cipher",
+]
+
+[[package]]
+name = "dashmap"
+version = "6.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+ "hashbrown 0.14.5",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
+
+[[package]]
+name = "der"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
+dependencies = [
+ "const-oid",
+ "pem-rfc7468",
+ "zeroize",
+]
+
+[[package]]
+name = "deranged"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4"
+dependencies = [
+ "powerfmt",
+ "serde_core",
+]
+
+[[package]]
+name = "derive_more"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 2.0.115",
+ "unicode-xid",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "const-oid",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "dlv-list"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
+
+[[package]]
+name = "dotenvy"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "email-encoding"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6"
+dependencies = [
+ "base64 0.22.1",
+ "memchr",
+]
+
+[[package]]
+name = "email-unlimit-server"
+version = "0.1.0"
+dependencies = [
+ "aes-gcm",
+ "anyhow",
+ "argon2",
+ "async-trait",
+ "axum",
+ "base64 0.22.1",
+ "chrono",
+ "chrono-tz",
+ "clap",
+ "config",
+ "dashmap",
+ "hickory-resolver",
+ "http-body-util",
+ "jsonwebtoken",
+ "lettre",
+ "mail-parser",
+ "rand 0.8.5",
+ "redis",
+ "reqwest",
+ "sea-orm",
+ "serde",
+ "serde_json",
+ "sha2",
+ "thiserror 1.0.69",
+ "tokio",
+ "tower",
+ "tower-http 0.5.2",
+ "tracing",
+ "tracing-subscriber",
+ "uuid",
+ "validator",
+]
+
+[[package]]
+name = "email_address"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "enum-as-inner"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "etcetera"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
+dependencies = [
+ "cfg-if",
+ "home",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "event-listener"
+version = "5.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
+[[package]]
+name = "flume"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "spin",
+]
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "funty"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
+
+[[package]]
+name = "futures"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-intrusive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
+dependencies = [
+ "futures-core",
+ "lock_api",
+ "parking_lot",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+
+[[package]]
+name = "futures-task"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+
+[[package]]
+name = "futures-util"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 5.3.0",
+ "wasip2",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "r-efi 6.0.0",
+ "rand_core 0.10.1",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "ghash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+
+[[package]]
+name = "hashlink"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
+dependencies = [
+ "hashbrown 0.15.5",
+]
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hickory-proto"
+version = "0.24.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248"
+dependencies = [
+ "async-trait",
+ "cfg-if",
+ "data-encoding",
+ "enum-as-inner",
+ "futures-channel",
+ "futures-io",
+ "futures-util",
+ "idna 1.1.0",
+ "ipnet",
+ "once_cell",
+ "rand 0.8.5",
+ "thiserror 1.0.69",
+ "tinyvec",
+ "tokio",
+ "tracing",
+ "url",
+]
+
+[[package]]
+name = "hickory-resolver"
+version = "0.24.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "hickory-proto",
+ "ipconfig",
+ "lru-cache",
+ "once_cell",
+ "parking_lot",
+ "rand 0.8.5",
+ "resolv-conf",
+ "smallvec",
+ "thiserror 1.0.69",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "hkdf"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
+dependencies = [
+ "hmac",
+]
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest",
+]
+
+[[package]]
+name = "home"
+version = "0.5.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "http"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "hyper"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "pin-utils",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+ "webpki-roots 1.0.6",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2 0.6.2",
+ "tokio",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
+
+[[package]]
+name = "icu_properties"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
+
+[[package]]
+name = "icu_provider"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "if_chain"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb"
+
+[[package]]
+name = "indexmap"
+version = "2.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.16.1",
+]
+
+[[package]]
+name = "inherent"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c727f80bfa4a6c6e2508d2f05b6f4bfce242030bd88ed15ae5331c5b5d30fba7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "inout"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "ipconfig"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222"
+dependencies = [
+ "socket2 0.6.2",
+ "widestring",
+ "windows-registry",
+ "windows-result",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
+
+[[package]]
+name = "js-sys"
+version = "0.3.85"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "json5"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
+dependencies = [
+ "pest",
+ "pest_derive",
+ "serde",
+]
+
+[[package]]
+name = "jsonwebtoken"
+version = "9.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde"
+dependencies = [
+ "base64 0.22.1",
+ "js-sys",
+ "pem",
+ "ring",
+ "serde",
+ "serde_json",
+ "simple_asn1",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+dependencies = [
+ "spin",
+]
+
+[[package]]
+name = "lettre"
+version = "0.11.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0da65617f6cb926332d039cb578aad56178da86e128db6a1b09f4c94fa5b3349"
+dependencies = [
+ "async-trait",
+ "base64 0.22.1",
+ "email-encoding",
+ "email_address",
+ "fastrand",
+ "futures-io",
+ "futures-util",
+ "httpdate",
+ "idna 1.1.0",
+ "mime",
+ "nom 8.0.0",
+ "percent-encoding",
+ "quoted_printable",
+ "rustls",
+ "socket2 0.6.2",
+ "tokio",
+ "tokio-rustls",
+ "url",
+ "webpki-roots 1.0.6",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.181"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5"
+
+[[package]]
+name = "libm"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
+
+[[package]]
+name = "libredox"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
+dependencies = [
+ "bitflags 2.10.0",
+ "libc",
+ "redox_syscall 0.7.1",
+]
+
+[[package]]
+name = "libsqlite3-sys"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
+dependencies = [
+ "cc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+
+[[package]]
+name = "litemap"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+
+[[package]]
+name = "lru-cache"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
+dependencies = [
+ "linked-hash-map",
+]
+
+[[package]]
+name = "lru-slab"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
+
+[[package]]
+name = "mail-parser"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93c3b9e5d8b17faf573330bbc43b37d6e918c0a3bf8a88e7d0a220ebc84af9fc"
+dependencies = [
+ "encoding_rs",
+]
+
+[[package]]
+name = "matchers"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
+name = "matchit"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
+
+[[package]]
+name = "md-5"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
+dependencies = [
+ "cfg-if",
+ "digest",
+]
+
+[[package]]
+name = "memchr"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "mio"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "nom"
+version = "8.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.50.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint-dig"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
+dependencies = [
+ "lazy_static",
+ "libm",
+ "num-integer",
+ "num-iter",
+ "num-traits",
+ "rand 0.8.5",
+ "smallvec",
+ "zeroize",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+
+[[package]]
+name = "opaque-debug"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
+
+[[package]]
+name = "ordered-float"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "ordered-multimap"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a"
+dependencies = [
+ "dlv-list",
+ "hashbrown 0.12.3",
+]
+
+[[package]]
+name = "ouroboros"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59"
+dependencies = [
+ "aliasable",
+ "ouroboros_macro",
+ "static_assertions",
+]
+
+[[package]]
+name = "ouroboros_macro"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro2",
+ "proc-macro2-diagnostics",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "parking"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall 0.5.18",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "password-hash"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
+dependencies = [
+ "base64ct",
+ "rand_core 0.6.4",
+ "subtle",
+]
+
+[[package]]
+name = "pathdiff"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
+
+[[package]]
+name = "pem"
+version = "3.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
+dependencies = [
+ "base64 0.22.1",
+ "serde_core",
+]
+
+[[package]]
+name = "pem-rfc7468"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
+dependencies = [
+ "base64ct",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "pest"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662"
+dependencies = [
+ "memchr",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
+dependencies = [
+ "pest",
+ "sha2",
+]
+
+[[package]]
+name = "pgvector"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc58e2d255979a31caa7cabfa7aac654af0354220719ab7a68520ae7a91e8c0b"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "phf"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkcs1"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
+dependencies = [
+ "der",
+ "pkcs8",
+ "spki",
+]
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
+[[package]]
+name = "polyval"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "potential_utf"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
+dependencies = [
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "proc-macro-error2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
+dependencies = [
+ "proc-macro-error-attr2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "proc-macro2-diagnostics"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+ "version_check",
+ "yansi",
+]
+
+[[package]]
+name = "ptr_meta"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
+dependencies = [
+ "ptr_meta_derive",
+]
+
+[[package]]
+name = "ptr_meta_derive"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "quinn"
+version = "0.11.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2 0.6.2",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560"
+dependencies = [
+ "bytes",
+ "getrandom 0.4.3",
+ "lru-slab",
+ "rand 0.10.2",
+ "rand_pcg",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror 2.0.18",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2 0.6.2",
+ "tracing",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "quoted_printable"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "478e0585659a122aa407eb7e3c0e1fa51b1d8a870038bd29f0cf4a8551eea972"
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "radium"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
+dependencies = [
+ "chacha20",
+ "getrandom 0.4.3",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.17",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
+[[package]]
+name = "rand_pcg"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
+dependencies = [
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "redis"
+version = "0.27.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09d8f99a4090c89cc489a94833c901ead69bfbf3877b4867d5482e321ee875bc"
+dependencies = [
+ "arc-swap",
+ "async-trait",
+ "backon",
+ "bytes",
+ "combine",
+ "futures",
+ "futures-util",
+ "itertools",
+ "itoa",
+ "num-bigint",
+ "percent-encoding",
+ "pin-project-lite",
+ "ryu",
+ "sha1_smol",
+ "socket2 0.5.10",
+ "tokio",
+ "tokio-util",
+ "url",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags 2.10.0",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b"
+dependencies = [
+ "bitflags 2.10.0",
+]
+
+[[package]]
+name = "regex"
+version = "1.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c"
+
+[[package]]
+name = "rend"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
+dependencies = [
+ "bytecheck",
+]
+
+[[package]]
+name = "reqwest"
+version = "0.12.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "percent-encoding",
+ "pin-project-lite",
+ "quinn",
+ "rustls",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-rustls",
+ "tower",
+ "tower-http 0.6.11",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "webpki-roots 1.0.6",
+]
+
+[[package]]
+name = "resolv-conf"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7"
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rkyv"
+version = "0.7.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1"
+dependencies = [
+ "bitvec",
+ "bytecheck",
+ "bytes",
+ "hashbrown 0.12.3",
+ "ptr_meta",
+ "rend",
+ "rkyv_derive",
+ "seahash",
+ "tinyvec",
+ "uuid",
+]
+
+[[package]]
+name = "rkyv_derive"
+version = "0.7.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ron"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a"
+dependencies = [
+ "base64 0.13.1",
+ "bitflags 1.3.2",
+ "serde",
+]
+
+[[package]]
+name = "rsa"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
+dependencies = [
+ "const-oid",
+ "digest",
+ "num-bigint-dig",
+ "num-integer",
+ "num-traits",
+ "pkcs1",
+ "pkcs8",
+ "rand_core 0.6.4",
+ "signature",
+ "spki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rust-ini"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
+dependencies = [
+ "cfg-if",
+ "ordered-multimap",
+]
+
+[[package]]
+name = "rust_decimal"
+version = "1.40.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61f703d19852dbf87cbc513643fa81428361eb6940f1ac14fd58155d295a3eb0"
+dependencies = [
+ "arrayvec",
+ "borsh",
+ "bytes",
+ "num-traits",
+ "rand 0.8.5",
+ "rkyv",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
+dependencies = [
+ "log",
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
+dependencies = [
+ "web-time",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "sea-bae"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro-error2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "sea-orm"
+version = "1.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d945f62558fac19e5988680d2fdf747b734c2dbc6ce2cb81ba33ed8dde5b103"
+dependencies = [
+ "async-stream",
+ "async-trait",
+ "bigdecimal",
+ "chrono",
+ "derive_more",
+ "futures-util",
+ "log",
+ "ouroboros",
+ "pgvector",
+ "rust_decimal",
+ "sea-orm-macros",
+ "sea-query",
+ "sea-query-binder",
+ "serde",
+ "serde_json",
+ "sqlx",
+ "strum",
+ "thiserror 2.0.18",
+ "time",
+ "tracing",
+ "url",
+ "uuid",
+]
+
+[[package]]
+name = "sea-orm-macros"
+version = "1.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84c2e64a50a9cc8339f10a27577e10062c7f995488e469f2c95762c5ee847832"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro2",
+ "quote",
+ "sea-bae",
+ "syn 2.0.115",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sea-query"
+version = "0.32.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a5d1c518eaf5eda38e5773f902b26ab6d5e9e9e2bb2349ca6c64cf96f80448c"
+dependencies = [
+ "bigdecimal",
+ "chrono",
+ "inherent",
+ "ordered-float",
+ "rust_decimal",
+ "serde_json",
+ "time",
+ "uuid",
+]
+
+[[package]]
+name = "sea-query-binder"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608"
+dependencies = [
+ "bigdecimal",
+ "chrono",
+ "rust_decimal",
+ "sea-query",
+ "serde_json",
+ "sqlx",
+ "time",
+ "uuid",
+]
+
+[[package]]
+name = "seahash"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
+
+[[package]]
+name = "semver"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.149"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
+dependencies = [
+ "itoa",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "digest",
+]
+
+[[package]]
+name = "sha1_smol"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "digest",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno",
+ "libc",
+]
+
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "digest",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "simdutf8"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+
+[[package]]
+name = "simple_asn1"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "thiserror 2.0.18",
+ "time",
+]
+
+[[package]]
+name = "siphasher"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "smallvec"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "socket2"
+version = "0.5.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "socket2"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
+dependencies = [
+ "libc",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "sqlx"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
+dependencies = [
+ "sqlx-core",
+ "sqlx-macros",
+ "sqlx-mysql",
+ "sqlx-postgres",
+ "sqlx-sqlite",
+]
+
+[[package]]
+name = "sqlx-core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
+dependencies = [
+ "base64 0.22.1",
+ "bigdecimal",
+ "bytes",
+ "chrono",
+ "crc",
+ "crossbeam-queue",
+ "either",
+ "event-listener",
+ "futures-core",
+ "futures-intrusive",
+ "futures-io",
+ "futures-util",
+ "hashbrown 0.15.5",
+ "hashlink",
+ "indexmap",
+ "log",
+ "memchr",
+ "once_cell",
+ "percent-encoding",
+ "rust_decimal",
+ "rustls",
+ "serde",
+ "serde_json",
+ "sha2",
+ "smallvec",
+ "thiserror 2.0.18",
+ "time",
+ "tokio",
+ "tokio-stream",
+ "tracing",
+ "url",
+ "uuid",
+ "webpki-roots 0.26.11",
+]
+
+[[package]]
+name = "sqlx-macros"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sqlx-core",
+ "sqlx-macros-core",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "sqlx-macros-core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
+dependencies = [
+ "dotenvy",
+ "either",
+ "heck 0.5.0",
+ "hex",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "sha2",
+ "sqlx-core",
+ "sqlx-mysql",
+ "sqlx-postgres",
+ "sqlx-sqlite",
+ "syn 2.0.115",
+ "tokio",
+ "url",
+]
+
+[[package]]
+name = "sqlx-mysql"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
+dependencies = [
+ "atoi",
+ "base64 0.22.1",
+ "bigdecimal",
+ "bitflags 2.10.0",
+ "byteorder",
+ "bytes",
+ "chrono",
+ "crc",
+ "digest",
+ "dotenvy",
+ "either",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "generic-array",
+ "hex",
+ "hkdf",
+ "hmac",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "once_cell",
+ "percent-encoding",
+ "rand 0.8.5",
+ "rsa",
+ "rust_decimal",
+ "serde",
+ "sha1",
+ "sha2",
+ "smallvec",
+ "sqlx-core",
+ "stringprep",
+ "thiserror 2.0.18",
+ "time",
+ "tracing",
+ "uuid",
+ "whoami",
+]
+
+[[package]]
+name = "sqlx-postgres"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
+dependencies = [
+ "atoi",
+ "base64 0.22.1",
+ "bigdecimal",
+ "bitflags 2.10.0",
+ "byteorder",
+ "chrono",
+ "crc",
+ "dotenvy",
+ "etcetera",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "hex",
+ "hkdf",
+ "hmac",
+ "home",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "num-bigint",
+ "once_cell",
+ "rand 0.8.5",
+ "rust_decimal",
+ "serde",
+ "serde_json",
+ "sha2",
+ "smallvec",
+ "sqlx-core",
+ "stringprep",
+ "thiserror 2.0.18",
+ "time",
+ "tracing",
+ "uuid",
+ "whoami",
+]
+
+[[package]]
+name = "sqlx-sqlite"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
+dependencies = [
+ "atoi",
+ "chrono",
+ "flume",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-intrusive",
+ "futures-util",
+ "libsqlite3-sys",
+ "log",
+ "percent-encoding",
+ "serde",
+ "serde_urlencoded",
+ "sqlx-core",
+ "thiserror 2.0.18",
+ "time",
+ "tracing",
+ "url",
+ "uuid",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "stringprep"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+ "unicode-properties",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "strum"
+version = "0.26.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.115"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "tap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+dependencies = [
+ "thiserror-impl 2.0.18",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "time"
+version = "0.3.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
+dependencies = [
+ "deranged",
+ "itoa",
+ "num-conv",
+ "powerfmt",
+ "serde_core",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
+
+[[package]]
+name = "time-macros"
+version = "0.2.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.49.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2 0.6.2",
+ "tokio-macros",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9"
+dependencies = [
+ "futures-util",
+ "log",
+ "tokio",
+ "tungstenite",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.7.5+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.23.10+spec-1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "toml_parser",
+ "winnow",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.0.8+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "tower"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tokio-util",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
+dependencies = [
+ "bitflags 2.10.0",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "pin-project-lite",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
+dependencies = [
+ "bitflags 2.10.0",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "url",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex-automata",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "tungstenite"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
+dependencies = [
+ "byteorder",
+ "bytes",
+ "data-encoding",
+ "http",
+ "httparse",
+ "log",
+ "rand 0.8.5",
+ "sha1",
+ "thiserror 1.0.69",
+ "utf-8",
+]
+
+[[package]]
+name = "typenum"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-properties"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
+name = "universal-hash"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
+dependencies = [
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna 1.1.0",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "uuid"
+version = "1.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
+dependencies = [
+ "getrandom 0.3.4",
+ "js-sys",
+ "serde_core",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "validator"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b92f40481c04ff1f4f61f304d61793c7b56ff76ac1469f1beb199b1445b253bd"
+dependencies = [
+ "idna 0.4.0",
+ "lazy_static",
+ "regex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "url",
+ "validator_derive",
+]
+
+[[package]]
+name = "validator_derive"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc44ca3088bb3ba384d9aecf40c6a23a676ce23e09bdaca2073d99c207f864af"
+dependencies = [
+ "if_chain",
+ "lazy_static",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "syn 1.0.109",
+ "validator_types",
+]
+
+[[package]]
+name = "validator_types"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "111abfe30072511849c5910134e8baf8dc05de4c0e5903d681cbd5c9c4d611e3"
+dependencies = [
+ "proc-macro2",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "valuable"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.2+wasi-0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "js-sys",
+ "once_cell",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.85"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.26.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
+dependencies = [
+ "webpki-roots 1.0.6",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "whoami"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d"
+dependencies = [
+ "libredox",
+ "wasite",
+]
+
+[[package]]
+name = "widestring"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-registry"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
+dependencies = [
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets 0.53.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm 0.52.6",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
+dependencies = [
+ "windows-link",
+ "windows_aarch64_gnullvm 0.53.1",
+ "windows_aarch64_msvc 0.53.1",
+ "windows_i686_gnu 0.53.1",
+ "windows_i686_gnullvm 0.53.1",
+ "windows_i686_msvc 0.53.1",
+ "windows_x86_64_gnu 0.53.1",
+ "windows_x86_64_gnullvm 0.53.1",
+ "windows_x86_64_msvc 0.53.1",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
+
+[[package]]
+name = "winnow"
+version = "0.7.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
+
+[[package]]
+name = "writeable"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
+
+[[package]]
+name = "wyz"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
+dependencies = [
+ "tap",
+]
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
+
+[[package]]
+name = "yansi"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+
+[[package]]
+name = "yoke"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
+
+[[package]]
+name = "zerotrie"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.115",
+]
+
+[[package]]
+name = "zmij"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
diff --git a/server/Cargo.toml b/server/Cargo.toml
new file mode 100644
index 0000000..faf716c
--- /dev/null
+++ b/server/Cargo.toml
@@ -0,0 +1,70 @@
+[package]
+name = "email-unlimit-server"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+# ===== Web 框架 =====
+axum = { version = "0.7", features = ["ws"] }
+tokio = { version = "1", features = ["full"] }
+tower = { version = "0.5", features = ["limit", "timeout", "util"] }
+tower-http = { version = "0.5", features = ["catch-panic", "cors", "limit", "set-header", "timeout", "trace"] }
+
+# ===== 数据库(支持 MySQL、SQLite、PostgreSQL) =====
+# SeaORM - 数据库 ORM(替代 SQLX 直接使用)
+sea-orm = { version = "1.1", features = [
+ "runtime-tokio-rustls",
+ "sqlx-mysql",
+ "sqlx-sqlite",
+ "sqlx-postgres",
+ "macros",
+ "with-chrono",
+ "with-uuid",
+] }
+# ===== 序列化 =====
+serde = { version = "1", features = ["derive"] }
+serde_json = "1"
+
+# ===== 认证与加密 =====
+jsonwebtoken = "9"
+argon2 = "0.5"
+sha2 = "0.10"
+base64 = "0.22"
+aes-gcm = "0.10"
+
+# ===== 邮件发送 =====
+lettre = { version = "0.11", default-features = false, features = ["tokio1", "tokio1-rustls", "builder", "smtp-transport", "webpki-roots", "ring"] }
+
+# ===== 邮件接收(临时邮箱 SMTP 收信) =====
+mail-parser = "0.9"
+hickory-resolver = "0.24"
+dashmap = "6"
+
+# ===== Redis =====
+redis = { version = "0.27", features = ["tokio-comp", "connection-manager"] }
+
+# ===== HTTP 客户端(告警 webhook) =====
+reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
+
+# ===== 工具库 =====
+uuid = { version = "1", features = ["v4", "serde"] }
+chrono = { version = "0.4", features = ["serde"] }
+chrono-tz = "0.10"
+anyhow = "1"
+thiserror = "1"
+async-trait = "0.1"
+tracing = "0.1"
+tracing-subscriber = { version = "0.3", features = ["env-filter"] }
+config = "0.13"
+rand = "0.8"
+clap = { version = "4", features = ["derive", "env"] }
+validator = { version = "0.16", features = ["derive"] }
+http-body-util = "0.1"
+
+# 优化发布版本
+[profile.release]
+opt-level = "z"
+lto = true
+codegen-units = 1
+strip = true
+panic = "abort"
diff --git a/server/Dockerfile b/server/Dockerfile
new file mode 100644
index 0000000..c2db2af
--- /dev/null
+++ b/server/Dockerfile
@@ -0,0 +1,20 @@
+FROM rust:1.95-bookworm AS builder
+
+WORKDIR /app
+COPY Cargo.toml Cargo.lock ./
+COPY src ./src
+RUN cargo build --locked --release
+
+FROM debian:bookworm-slim
+
+RUN apt-get update \
+ && apt-get install -y --no-install-recommends ca-certificates curl \
+ && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /app
+COPY --from=builder /app/target/release/email-unlimit-server /usr/local/bin/email-unlimit-server
+COPY config ./config
+
+EXPOSE 25 3000
+ENTRYPOINT ["email-unlimit-server"]
+CMD ["--env", "production"]
diff --git a/server/README.md b/server/README.md
new file mode 100644
index 0000000..9aafc3d
--- /dev/null
+++ b/server/README.md
@@ -0,0 +1,56 @@
+# Email Unlimited Server
+
+Rust + Axum 实现的临时邮箱 HTTP API 与 SMTP 收信服务。
+
+## 启动
+
+开发环境默认使用 SQLite:
+
+```powershell
+cargo run
+```
+
+指定配置与环境:
+
+```powershell
+cargo run -- --env production --config config/production.toml
+```
+
+嵌套环境变量使用双下划线分隔:
+
+```powershell
+$env:DATABASE__DATABASE_TYPE = "mysql"
+$env:DATABASE__HOST = "localhost"
+$env:DATABASE__PORT = "3306"
+$env:DATABASE__USER = "root"
+$env:DATABASE__PASSWORD = "your-password"
+$env:DATABASE__DATABASE = "email_unlimit"
+$env:AUTH__JWT_SECRET = "replace-with-at-least-32-random-characters"
+$env:AUTH__INVITE_CODE = "replace-with-at-least-16-random-characters"
+$env:AUTH__BOOTSTRAP_ADMIN_EMAIL = "admin@example.com"
+$env:SMTP__BODY_ENCRYPTION_KEY = "0123456789abcdef0123456789abcdef"
+cargo run -- --env production
+```
+
+## 核心接口
+
+- `GET /health`
+- `POST /auth/register`
+- `POST /auth/login`
+- `POST /auth/refresh`
+- `GET|POST /api/mailboxes`
+- `GET /api/mailboxes/:id/emails`
+- `GET /api/mailboxes/:id/ws`
+- `GET /admin/stats`
+- `GET|PATCH /admin/users`
+
+`/api` 路由使用用户或邮箱令牌,`/admin` 路由同时要求有效用户 JWT 与 `admin` 角色。
+
+## 验证
+
+```powershell
+cargo test
+cargo check
+```
+
+更完整的资料位于 `docs/`。
diff --git a/server/config/development.toml b/server/config/development.toml
new file mode 100644
index 0000000..9a4b761
--- /dev/null
+++ b/server/config/development.toml
@@ -0,0 +1,90 @@
+# 开发环境配置 - SQLite 数据库
+[server]
+host = "0.0.0.0"
+port = 3000
+request_timeout_seconds = 30
+max_body_bytes = 1048576
+concurrency_limit = 256
+rate_limit_per_minute = 120
+cors_origins = ["http://localhost:3000", "http://localhost:5173"]
+
+[database]
+# 数据库类型: mysql, sqlite, postgresql
+database_type = "sqlite"
+
+# MySQL/PostgreSQL 配置
+# host = "localhost"
+# port = 3306
+# user = "root"
+# password = "root"
+# database = "web_template"
+
+# SQLite 配置(当 database_type = "sqlite" 时使用)
+path = "data/app.db"
+
+# 连接池配置
+max_connections = 10
+
+[auth]
+jwt_secret = "9f7d3c7a564dfkopp26smb2644nqzfvbsao9f7d3c7a1a8f28544b5e6d7a"
+# 分开配置两个 token 的过期时间
+access_token_expiration_minutes = 15 # access_token 15 分钟
+refresh_token_expiration_days = 7 # refresh_token 7 天
+registration_open = true # 开发环境开放注册
+invite_code = "" # 空=不校验邀请码
+bootstrap_admin_email = "" # 本地需要时填写管理员邮箱
+
+[redis]
+enabled = false
+host = "localhost"
+port = 6379
+password = "" # 可选
+db = 0
+
+[email]
+enabled = false
+smtp_host = "smtp.example.com"
+smtp_port = 587
+smtp_username = ""
+smtp_password = ""
+from_email = "noreply@example.com"
+from_name = "Email Unlimited"
+verification_code_ttl_seconds = 600
+queue_enabled = false
+worker_pool_size = 2
+
+# SMTP 收信服务(临时邮箱核心)
+[smtp]
+enabled = true
+listen_host = "0.0.0.0"
+listen_port = 25
+hostname = "mail.shenjianl.cn"
+local_domains = ["mail.shenjianl.cn", "shenjianl.cn"]
+max_message_bytes = 1048576
+connection_timeout_seconds = 30
+max_recipients_per_message = 1
+ip_connect_per_min = 20
+domain_per_min = 10
+rcpt_per_min = 10
+require_ptr = false # 开发环境关闭 PTR(本地无 PTR 记录)
+email_ttl_days = 7
+mailbox_ttl_hours = 24
+mailbox_max_quota_bytes = 10485760
+address_local_part_len = 10
+sender_per_min = 10
+pair_per_10min = 5
+greylist_enabled = false # 开发环境关闭灰名单(避免本地测试被挡)
+greylist_retry_delay_seconds = 300
+greylist_entry_ttl_seconds = 86400
+quarantine_threshold = 60
+score_weight_subject_code = 30
+score_weight_spf_fail = 20
+score_weight_ptr_missing = 15
+score_weight_bad_attachment = 25
+
+[abuse]
+domain_emails_threshold_10min = 50
+ip_connects_threshold_1min = 60
+auto_block_ttl_seconds = 3600
+honeypot_addresses = ["admin@mail.shenjianl.cn", "noreply@mail.shenjianl.cn", "test@mail.shenjianl.cn"]
+alert_webhook_url = ""
diff --git a/server/config/production.toml b/server/config/production.toml
new file mode 100644
index 0000000..7981a89
--- /dev/null
+++ b/server/config/production.toml
@@ -0,0 +1,91 @@
+# 生产环境配置 - PostgreSQL 数据库
+
+[server]
+host = "0.0.0.0" # 服务器监听地址(0.0.0.0=允许所有网络访问)
+port = 3000 # 服务器监听端口(确保防火墙已开放)
+request_timeout_seconds = 30
+max_body_bytes = 1048576
+concurrency_limit = 256
+rate_limit_per_minute = 120
+cors_origins = ["https://example.com"]
+
+[database]
+database_type = "postgresql" # 数据库类型:sqlite/mysql/postgresql
+host = "localhost" # PostgreSQL 服务器地址
+port = 5432 # PostgreSQL 端口(默认 5432)
+user = "postgres" # PostgreSQL 用户名(请创建专用用户)
+password = "postgres" # PostgreSQL 密码(请修改为强密码)
+database = "web_template" # 数据库名称(不存在会自动创建)
+max_connections = 20 # 最大连接数(生产环境建议 20-100)
+
+[auth]
+jwt_secret = "CHANGE_ME_WITH_AT_LEAST_32_RANDOM_CHARACTERS" # 必须通过部署配置替换
+access_token_expiration_minutes = 15 # Access Token 过期时间(分钟)
+refresh_token_expiration_days = 7 # Refresh Token 过期时间(天)
+registration_open = true # 生产建议改为 false 或配合邀请码
+invite_code = "" # 设置非空值则注册需邀请码(如 "shenjianl-2026")
+bootstrap_admin_email = "" # 首次部署可配置管理员邮箱,并同时设置强邀请码
+
+[redis]
+enabled = true
+host = "localhost" # Redis 服务器地址
+port = 6379 # Redis 端口(默认 6379)
+password = "" # Redis 密码(强烈建议设置密码)
+db = 0 # Redis 数据库编号(0-15)
+
+[email]
+enabled = false
+smtp_host = "smtp.example.com"
+smtp_port = 587
+smtp_username = ""
+smtp_password = ""
+from_email = "noreply@example.com"
+from_name = "Email Unlimited"
+verification_code_ttl_seconds = 600
+queue_enabled = true
+worker_pool_size = 4
+
+# SMTP 收信服务(临时邮箱核心)
+[smtp]
+enabled = true
+listen_host = "0.0.0.0"
+listen_port = 25
+hostname = "mail.shenjianl.cn"
+local_domains = ["mail.shenjianl.cn", "shenjianl.cn"]
+max_message_bytes = 1048576
+connection_timeout_seconds = 30
+max_recipients_per_message = 1
+ip_connect_per_min = 20
+domain_per_min = 10
+rcpt_per_min = 10
+require_ptr = true # 生产环境要求 PTR 记录
+email_ttl_days = 7
+mailbox_ttl_hours = 24
+mailbox_max_quota_bytes = 10485760
+address_local_part_len = 10
+sender_per_min = 10
+pair_per_10min = 5
+greylist_enabled = true # 生产环境启用灰名单
+greylist_retry_delay_seconds = 300
+greylist_entry_ttl_seconds = 86400
+quarantine_threshold = 60
+score_weight_subject_code = 30
+score_weight_spf_fail = 20
+score_weight_ptr_missing = 15
+score_weight_bad_attachment = 25
+body_encryption_key = "" # 生产环境必须通过 SMTP__BODY_ENCRYPTION_KEY 注入 32 字节随机密钥
+
+[abuse]
+domain_emails_threshold_10min = 50
+ip_connects_threshold_1min = 60
+auto_block_ttl_seconds = 3600
+honeypot_addresses = ["admin@mail.shenjianl.cn", "noreply@mail.shenjianl.cn", "test@mail.shenjianl.cn"]
+alert_webhook_url = ""
+
+# 安全检查清单:部署前请确认
+# ✅ 1. 已修改 jwt_secret 为强随机字符串
+# ✅ 2. 已修改数据库密码为强密码
+# ✅ 3. 已设置 Redis 密码
+# ✅ 4. 已配置防火墙规则
+# ✅ 5. 已启用 HTTPS(使用 Nginx/Caddy 等反向代理)
+# ✅ 6. 已设置数据库定期备份
diff --git a/server/docs/README.md b/server/docs/README.md
new file mode 100644
index 0000000..1344e4b
--- /dev/null
+++ b/server/docs/README.md
@@ -0,0 +1,117 @@
+# Email Unlimited 文档中心
+
+欢迎使用 Email Unlimited 文档!本模板项目提供了生产级的 Rust Web 服务器基础架构,采用 DDD 分层设计,包含完整的认证、数据库、缓存等功能。
+
+## 快速导航
+
+### 按角色查找文档
+
+#### 前端开发者
+- [API 接口概览](api/api-overview.md) - 快速了解所有可用的 API 接口
+- [公开接口文档](api/endpoints/public.md) - 注册、登录等公开接口的详细说明
+- [前端集成示例](api/examples/frontend-integration.md) - JavaScript/TypeScript/React/Vue 集成代码示例
+- [认证机制详解](api/authentication.md) - JWT 认证流程和最佳实践
+
+#### 后端开发者
+- [快速开始指南](development/getting-started.md) - 安装、配置和运行项目
+- [项目结构详解](development/project-structure.md) - DDD 分层架构说明
+- [DDD 架构规范](development/ddd-architecture.md) - 各层设计原则和开发规范
+- [代码风格规范](development/code-style.md) - Rust 代码风格和命名规范
+- [Git 提交规范](development/git-workflow.md) - 提交信息规范和分支策略
+- [测试规范](development/testing.md) - 单元测试和集成测试指南
+
+#### 运维人员
+- [环境变量配置](deployment/environment-variables.md) - 完整的环境变量列表和说明
+- [配置文件详解](deployment/configuration.md) - 多环境配置文件组织
+- [生产环境部署指南](deployment/production-guide.md) - 安全配置和部署最佳实践
+
+## 文档结构
+
+```
+docs/
+├── README.md # 本文档
+├── api/ # API 接口文档
+│ ├── api-overview.md # API 概览和快速参考
+│ ├── authentication.md # 认证机制详解
+│ ├── endpoints/
+│ │ ├── public.md # 公开接口
+│ │ └── protected.md # 需要认证的接口
+│ └── examples/
+│ └── frontend-integration.md # 前端集成代码示例
+├── development/ # 开发指南
+│ ├── getting-started.md # 快速开始
+│ ├── project-structure.md # 项目结构详解
+│ ├── ddd-architecture.md # DDD 分层架构规范
+│ ├── code-style.md # 代码风格和命名规范
+│ ├── git-workflow.md # Git 提交规范
+│ └── testing.md # 测试规范
+└── deployment/ # 部署文档
+ ├── environment-variables.md # 环境变量配置说明
+ ├── configuration.md # 配置文件详解
+ └── production-guide.md # 生产环境部署指南
+```
+
+## 核心概念
+
+### DDD 分层架构
+
+本项目采用领域驱动设计(DDD)分层架构:
+
+```
+┌─────────────────────────────────────┐
+│ Interface Layer (handlers) │ HTTP 处理器层
+└──────────────┬──────────────────────┘
+ │
+┌──────────────▼──────────────────────┐
+│ Application Layer (services) │ 业务逻辑层
+└──────────────┬──────────────────────┘
+ │
+ ┌───────┴────────┐
+ │ │
+┌──────▼──────┐ ┌─────▼──────────┐
+│ Domain │ │ Infrastructure│
+│ Layer │ │ Layer │
+└─────────────┘ └────────────────┘
+```
+
+### 双 Token 认证机制
+
+- **Access Token**:15 分钟有效期,用于 API 请求认证
+- **Refresh Token**:7 天有效期,存储在 Redis,用于获取新的 Access Token
+- **Token 轮换**:每次刷新会生成新的 Refresh Token,旧 Token 自动失效
+
+### 多数据库支持
+
+支持 MySQL、PostgreSQL、SQLite 三种数据库,通过简单的环境变量配置即可切换。
+
+## 技术栈
+
+| 组件 | 技术 | 版本 |
+|------|------|------|
+| Web 框架 | Axum | 0.7 |
+| 异步运行时 | Tokio | 1.x |
+| 数据库 ORM | SeaORM | 1.1 |
+| 认证 | JWT | 9.x |
+| 密码哈希 | Argon2 | 0.5 |
+| 缓存 | Redis | 0.27 |
+| 日志 | tracing | 0.1 |
+
+## 快速链接
+
+- [项目 README](../README.md) - 返回项目主页
+- [API 接口文档](api/api-overview.md) - 完整的 API 接口说明
+- [快速开始指南](development/getting-started.md) - 安装和配置指南
+- [开发规范](development/ddd-architecture.md) - DDD 架构和代码规范
+- [部署文档](deployment/configuration.md) - 配置和部署指南
+
+## 获取帮助
+
+如果您在阅读文档时有任何疑问,请:
+1. 查看相关主题的详细文档
+2. 检查 [常见问题](deployment/production-guide.md#常见问题)
+3. 提交 Issue 到项目仓库
+
+---
+
+**提示**:建议按照"快速开始指南"→"API 接口文档"→"开发规范"的顺序阅读文档。
+
diff --git a/server/docs/api/api-overview.md b/server/docs/api/api-overview.md
new file mode 100644
index 0000000..f1a00f6
--- /dev/null
+++ b/server/docs/api/api-overview.md
@@ -0,0 +1,199 @@
+# API 接口概览
+
+本文档提供所有 API 接口的快速参考。
+
+## 基础信息
+
+### Base URL
+
+```
+开发环境: http://localhost:3000
+生产环境: https://api.yourdomain.com
+```
+
+### 认证方式
+
+本 API 使用 JWT(JSON Web Token)进行认证:
+
+- **Access Token**:有效期 15 分钟,用于 API 请求认证
+- **Refresh Token**:有效期 7 天,用于获取新的 Access Token
+
+### 认证 Header 格式
+
+```http
+Authorization: Bearer
+```
+
+### 响应格式
+
+所有接口返回统一的 JSON 格式:
+
+**成功响应**:
+```json
+{
+ "code": 200,
+ "message": "Success",
+ "data": { }
+}
+```
+
+**错误响应**:
+```json
+{
+ "code": 400,
+ "message": "错误信息",
+ "data": null
+}
+```
+
+### 通用错误码
+
+| 错误码 | 说明 |
+|-------|------|
+| 200 | 成功 |
+| 400 | 请求参数错误 |
+| 401 | 未授权(Token 无效或过期) |
+| 404 | 资源不存在 |
+| 500 | 服务器内部错误 |
+
+## 接口列表
+
+### 公开接口(无需认证)
+
+| 方法 | 路径 | 说明 | 详细文档 |
+|------|------|------|----------|
+| GET | `/health` | 健康检查 | [查看详情](endpoints/public.md#get-health) |
+| GET | `/info` | 服务器信息 | [查看详情](endpoints/public.md#get-info) |
+| POST | `/auth/register` | 用户注册 | [查看详情](endpoints/public.md#post-authregister) |
+| POST | `/auth/login` | 用户登录 | [查看详情](endpoints/public.md#post-authlogin) |
+| POST | `/auth/refresh` | 刷新 Token | [查看详情](endpoints/public.md#post-authrefresh) |
+
+### 需要认证的接口
+
+| 方法 | 路径 | 说明 | 详细文档 |
+|------|------|------|----------|
+| POST | `/auth/delete` | 删除账号 | [查看详情](endpoints/protected.md#post-authdelete) |
+| POST | `/auth/delete-refresh-token` | 删除 Refresh Token | [查看详情](endpoints/protected.md#post-authdelete-refresh-token) |
+
+## 认证流程简述
+
+### 1. 注册/登录
+
+用户注册或登录成功后,会返回 Access Token 和 Refresh Token:
+
+```json
+{
+ "code": 200,
+ "message": "Success",
+ "data": {
+ "access_token": "eyJhbGciOiJIUzI1NiIs...",
+ "refresh_token": "eyJhbGciOiJIUzI1NiIs..."
+ }
+}
+```
+
+### 2. 使用 Access Token
+
+将 Access Token 添加到请求头:
+
+```http
+GET /auth/delete
+Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
+```
+
+### 3. 刷新 Token
+
+当 Access Token 过期时,使用 Refresh Token 获取新的 Token:
+
+```bash
+POST /auth/refresh
+Content-Type: application/json
+
+{
+ "refresh_token": "eyJhbGciOiJIUzI1NiIs..."
+}
+```
+
+> 查看 [认证机制详解](authentication.md) 了解完整流程
+
+## 快速示例
+
+### 注册用户
+
+```bash
+curl -X POST http://localhost:3000/auth/register \
+ -H "Content-Type: application/json" \
+ -d '{
+ "email": "user@example.com",
+ "password": "password123"
+ }'
+```
+
+### 用户登录
+
+```bash
+curl -X POST http://localhost:3000/auth/login \
+ -H "Content-Type: application/json" \
+ -d '{
+ "email": "user@example.com",
+ "password": "password123"
+ }'
+```
+
+### 访问受保护接口
+
+```bash
+curl -X POST http://localhost:3000/auth/delete \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer " \
+ -d '{
+ "user_id": "1234567890",
+ "password": "password123"
+ }'
+```
+
+### 健康检查
+
+```bash
+curl http://localhost:3000/health
+```
+
+## 详细文档
+
+- [公开接口详细文档](endpoints/public.md) - 所有公开接口的详细说明
+- [受保护接口详细文档](endpoints/protected.md) - 需要认证的接口详细说明
+- [认证机制详解](authentication.md) - JWT 认证流程和安全最佳实践
+- [前端集成示例](examples/frontend-integration.md) - JavaScript/TypeScript/React/Vue 集成代码示例
+
+## 相关文档
+
+- [快速开始指南](../development/getting-started.md) - 安装和运行项目
+- [环境变量配置](../deployment/environment-variables.md) - 配置 API 服务器
+- [前端集成指南](examples/frontend-integration.md) - 前端开发集成示例
+
+## 获取帮助
+
+如果您在使用 API 时遇到问题:
+
+1. 检查请求格式是否正确
+2. 确认 Token 是否有效(未过期)
+3. 查看日志输出获取详细错误信息
+4. 参考 [认证机制详解](authentication.md) 了解认证流程
+
+---
+
+**提示**:建议使用 Postman、Insomnia 或类似工具测试 API 接口。
+# 新增通用接口
+
+所有受保护接口使用 `Authorization: Bearer `。Refresh Token 不能访问受保护接口。
+
+| 方法 | 路径 | 认证 | 条件 |
+|---|---|---|---|
+| POST | `/auth/logout` | 是 | Redis 可用 |
+| POST | `/auth/request-verification-code` | 否 | 邮件启用且 Redis 可用 |
+| POST | `/auth/reset-password` | 否 | 邮件启用且 Redis 可用 |
+| GET/PUT/DELETE | `/api/user/profile` | 是 | 始终注册 |
+| GET | `/api/email/latest-log` | 是 | 邮件启用 |
+| GET | `/api/email/queue-status` | 是 | 邮件启用 |
+
+请求可通过 `Accept-Language: zh-CN` 或 `Accept-Language: en` 选择认证错误语言。
diff --git a/server/docs/api/authentication.md b/server/docs/api/authentication.md
new file mode 100644
index 0000000..2ef840b
--- /dev/null
+++ b/server/docs/api/authentication.md
@@ -0,0 +1,609 @@
+# 认证机制详解
+
+本文档详细说明 Email Unlimited 的 JWT 认证机制、安全特性和最佳实践。
+
+## 目录
+
+- [认证架构概述](#认证架构概述)
+- [双 Token 机制](#双-token-机制)
+- [认证流程](#认证流程)
+- [Token 管理](#token-管理)
+- [安全特性](#安全特性)
+- [最佳实践](#最佳实践)
+
+---
+
+## 认证架构概述
+
+本系统采用 **JWT (JSON Web Token)** 进行用户认证,使用 **双 Token 机制**:
+
+1. **Access Token**:短期有效,用于 API 请求认证
+2. **Refresh Token**:长期有效,用于获取新的 Access Token
+
+### 架构特点
+
+- ✅ **无状态认证**:服务器不存储会话信息,易于扩展
+- ✅ **安全性**:Token 泄露影响可控,自动过期
+- ✅ **用户体验**:Refresh Token 可减少用户登录次数
+- ✅ **可撤销性**:通过 Redis 存储 Refresh Token,支持主动撤销
+
+---
+
+## 双 Token 机制
+
+### Access Token
+
+**用途**:访问需要认证的 API 接口
+
+**特点**:
+- 有效期:15 分钟(可配置)
+- 包含用户 ID 和 Token 类型信息
+- 不存储在服务器端(无状态)
+- 每次请求都通过 HTTP Header 传递
+
+**格式**:
+```http
+Authorization: Bearer
+```
+
+### Refresh Token
+
+**用途**:获取新的 Access Token
+
+**特点**:
+- 有效期:7 天(可配置)
+- 存储在 Redis 中
+- 支持撤销和主动登出
+- 每次刷新会生成新的 Refresh Token
+
+**存储位置**:
+- 前端:localStorage 或 sessionStorage
+- 后端:Redis(Key:`auth:refresh_token:`)
+
+---
+
+## 认证流程
+
+### 1. 用户注册流程
+
+```mermaid
+sequenceDiagram
+ participant User as 用户
+ participant Frontend as 前端应用
+ participant API as API 服务器
+ participant DB as 数据库
+ participant Redis as Redis
+
+ User->>Frontend: 输入邮箱和密码
+ Frontend->>API: POST /auth/register
+ API->>API: 验证邮箱格式
+ API->>API: 生成用户 ID
+ API->>API: 哈希密码(Argon2)
+ API->>DB: 创建用户记录
+ DB-->>API: 用户创建成功
+ API->>API: 生成 Access Token (15min)
+ API->>API: 生成 Refresh Token (7days)
+ API->>Redis: 存储 Refresh Token
+ Redis-->>API: 存储成功
+ API-->>Frontend: 返回 Access Token + Refresh Token
+ Frontend->>Frontend: 存储 Token 到 localStorage
+ Frontend-->>User: 注册成功,自动登录
+```
+
+**关键点**:
+- 密码使用 Argon2 算法哈希,不可逆
+- Refresh Token 存储在 Redis,支持撤销
+- 注册成功后自动登录,返回 Token
+
+### 2. 用户登录流程
+
+```mermaid
+sequenceDiagram
+ participant User as 用户
+ participant Frontend as 前端应用
+ participant API as API 服务器
+ participant DB as 数据库
+ participant Redis as Redis
+
+ User->>Frontend: 输入邮箱和密码
+ Frontend->>API: POST /auth/login
+ API->>DB: 查询用户记录
+ DB-->>API: 返回用户信息
+ API->>API: 验证密码(Argon2)
+ API->>API: 生成 Access Token (15min)
+ API->>API: 生成 Refresh Token (7days)
+ API->>Redis: 存储/更新 Refresh Token
+ Redis-->>API: 存储成功
+ API-->>Frontend: 返回 Access Token + Refresh Token
+ Frontend->>Frontend: 存储 Token 到 localStorage
+ Frontend-->>User: 登录成功
+```
+
+**安全特性**:
+- 登录失败不返回具体错误信息(防止账号枚举)
+- 密码错误会记录日志用于风控
+- Refresh Token 每次登录都会更新
+
+### 3. 访问受保护接口流程
+
+```mermaid
+sequenceDiagram
+ participant Frontend as 前端应用
+ participant API as API 服务器
+ participant Redis as Redis
+
+ Frontend->>API: GET /protected
Authorization: Bearer
+ API->>API: 验证 JWT 签名
+ API->>API: 检查 Token 类型
+ API->>API: 检查 Token 过期时间
+ alt Token 有效
+ API-->>Frontend: 200 OK 返回数据
+ else Token 无效或过期
+ API-->>Frontend: 401 Unauthorized
+ Frontend->>API: POST /auth/refresh
+ API->>Redis: 获取 Refresh Token
+ Redis-->>API: 返回 Refresh Token
+ API->>API: 验证 Refresh Token
+ API->>API: 生成新的 Token 对
+ API->>Redis: 更新 Refresh Token
+ API-->>Frontend: 返回新的 Token
+ Frontend->>API: 重试原请求
+ API-->>Frontend: 200 OK 返回数据
+ end
+```
+
+**关键点**:
+- 所有受保护接口都需要在 Header 中携带 Access Token
+- Token 过期时前端自动刷新并重试请求
+- 刷新成功后旧 Refresh Token 立即失效
+
+### 4. Token 刷新流程
+
+```mermaid
+sequenceDiagram
+ participant Frontend as 前端应用
+ participant API as API 服务器
+ participant Redis as Redis
+
+ Frontend->>API: POST /auth/refresh
{"refresh_token": "..."}
+ API->>API: 验证 Refresh Token 签名
+ API->>API: 检查 Token 类型(必须是 Refresh Token)
+ API->>API: 检查 Token 过期时间
+ API->>Redis: 检查 Refresh Token 是否存在
+ alt Token 有效
+ API->>API: 生成新的 Access Token (15min)
+ API->>API: 生成新的 Refresh Token (7days)
+ API->>Redis: 删除旧的 Refresh Token
+ API->>Redis: 存储新的 Refresh Token
+ API-->>Frontend: 返回新的 Token 对
+ else Token 无效或过期
+ API-->>Frontend: 401 Unauthorized
+ Frontend->>Frontend: 清除 Token
+ Frontend->>Frontend: 跳转到登录页
+ end
+```
+
+**Token 轮换**:
+- 每次刷新都会生成新的 Refresh Token
+- 旧的 Refresh Token 立即失效
+- 防止 Token 重放攻击
+
+### 5. 用户登出流程
+
+```mermaid
+sequenceDiagram
+ participant User as 用户
+ participant Frontend as 前端应用
+ participant API as API 服务器
+ participant Redis as Redis
+
+ User->>Frontend: 点击登出按钮
+ Frontend->>API: POST /auth/delete-refresh-token
Authorization: Bearer
+ API->>API: 验证 Access Token
+ API->>API: 从 Token 中提取 user_id
+ API->>Redis: 删除 Refresh Token
+ Redis-->>API: 删除成功
+ API-->>Frontend: 200 OK
+ Frontend->>Frontend: 清除本地 Token
+ Frontend->>Frontend: 跳转到登录页
+ Frontend-->>User: 登出成功
+```
+
+---
+
+## Token 管理
+
+### Token 生成
+
+```rust
+// src/utils/jwt.rs
+
+// 生成 Access Token
+pub fn generate_access_token(
+ user_id: &str,
+ expiration_minutes: u64,
+ jwt_secret: &str,
+) -> Result {
+ let expiration = Utc::now()
+ .checked_add_signed(Duration::minutes(expiration_minutes as i64))
+ .expect("invalid expiration timestamp")
+ .timestamp() as usize;
+
+ let claims = Claims {
+ sub: user_id.to_string(),
+ exp: expiration,
+ token_type: TokenType::Access,
+ };
+
+ let token = encode(
+ &Header::default(),
+ &claims,
+ &EncodingKey::from_secret(jwt_secret.as_ref()),
+ )?;
+
+ Ok(token)
+}
+
+// 生成 Refresh Token
+pub fn generate_refresh_token(
+ user_id: &str,
+ expiration_days: i64,
+ jwt_secret: &str,
+) -> Result {
+ let expiration = Utc::now()
+ .checked_add_signed(Duration::days(expiration_days))
+ .expect("invalid expiration timestamp")
+ .timestamp() as usize;
+
+ let claims = Claims {
+ sub: user_id.to_string(),
+ exp: expiration,
+ token_type: TokenType::Refresh,
+ };
+
+ let token = encode(
+ &Header::default(),
+ &claims,
+ &EncodingKey::from_secret(jwt_secret.as_ref()),
+ )?;
+
+ Ok(token)
+}
+```
+
+### Token 验证
+
+```rust
+// src/infra/middleware/auth.rs
+
+pub async fn auth_middleware(
+ State(state): State,
+ mut request: Request,
+ next: Next,
+) -> Result {
+ // 1. 提取 Authorization header
+ let auth_header = request
+ .headers()
+ .get("Authorization")
+ .and_then(|h| h.to_str().ok())
+ .ok_or_else(|| ErrorResponse::new("缺少 Authorization header".to_string()))?;
+
+ // 2. 验证 Bearer 格式
+ if !auth_header.starts_with("Bearer ") {
+ return Err(ErrorResponse::new("Authorization header 格式错误".to_string()));
+ }
+
+ let token = &auth_header[7..]; // 跳过 "Bearer "
+
+ // 3. 验证 JWT 签名和过期时间
+ let claims = decode_token(token, &state.config.auth.jwt_secret)?;
+
+ // 4. 检查 Token 类型(必须是 Access Token)
+ if claims.token_type != TokenType::Access {
+ return Err(ErrorResponse::new("Token 类型错误".to_string()));
+ }
+
+ // 5. 将 user_id 添加到请求扩展中
+ let user_id = claims.sub;
+ request.extensions_mut().insert(user_id);
+
+ // 6. 继续处理请求
+ Ok(next.run(request).await)
+}
+```
+
+### Refresh Token 存储
+
+```rust
+// src/services/auth_service.rs
+
+async fn save_refresh_token(&self, user_id: &str, refresh_token: &str, expiration_days: i64) -> Result<()> {
+ let key = RedisKey::new(BusinessType::Auth)
+ .add_identifier("refresh_token")
+ .add_identifier(user_id);
+
+ let expiration_seconds = expiration_days * 24 * 3600;
+
+ self.redis_client
+ .set_ex(&key.build(), refresh_token, expiration_seconds as u64)
+ .await
+ .map_err(|e| anyhow::anyhow!("Redis 保存失败: {}", e))?;
+
+ Ok(())
+}
+```
+
+**Redis Key 设计**:
+```
+auth:refresh_token:
+```
+
+**过期时间**:7 天(与 Refresh Token 有效期一致)
+
+---
+
+## 安全特性
+
+### 1. 密码安全
+
+**Argon2 哈希**:
+- 使用 Argon2 算法(内存 hard,抗 GPU/ASIC 破解)
+- 自动生成随机盐值
+- 哈希结果不可逆
+
+```rust
+// src/services/auth_service.rs
+
+pub fn hash_password(&self, password: &str) -> Result {
+ let salt = SaltString::generate(&mut OsRng);
+ let argon2 = Argon2::default();
+ let password_hash = argon2
+ .hash_password(password.as_bytes(), &salt)
+ .map_err(|e| anyhow::anyhow!("密码哈希失败: {}", e))?
+ .to_string();
+ Ok(password_hash)
+}
+```
+
+### 2. JWT 安全
+
+**签名算法**:HS256 (HMAC-SHA256)
+
+**Claims 结构**:
+```rust
+pub struct Claims {
+ pub sub: String, // 用户 ID
+ pub exp: usize, // 过期时间(Unix 时间戳)
+ pub token_type: TokenType, // Token 类型(Access/Refresh)
+}
+```
+
+**安全措施**:
+- 使用强密钥(至少 32 位随机字符串)
+- Token 包含过期时间
+- Token 类型区分(防止混用)
+- 签名验证防止篡改
+
+### 3. Refresh Token 安全
+
+**存储安全**:
+- 存储在 Redis 中,支持快速撤销
+- 每次刷新生成新 Token,旧 Token 失效
+- 支持主动登出,删除 Refresh Token
+
+**使用限制**:
+- Refresh Token 只能使用一次
+- 过期后无法续期
+- 需要重新登录
+
+### 4. 防护措施
+
+**防重放攻击**:
+- Refresh Token 单次使用
+- 刷新后立即失效
+
+**防 Token 泄露**:
+- Access Token 短期有效(15 分钟)
+- 只通过 HTTPS 传输
+- 不在 URL 中传递
+
+**防暴力破解**:
+- 限制登录频率(可选实现)
+- 记录失败尝试(日志)
+- 密码哈希使用 Argon2
+
+---
+
+## 最佳实践
+
+### 前端集成
+
+#### 1. Token 存储
+
+**推荐方案**:
+```typescript
+// 存储 Token
+localStorage.setItem('access_token', access_token);
+localStorage.setItem('refresh_token', refresh_token);
+
+// 读取 Token
+const accessToken = localStorage.getItem('access_token');
+const refreshToken = localStorage.getItem('refresh_token');
+
+// 清除 Token
+localStorage.removeItem('access_token');
+localStorage.removeItem('refresh_token');
+```
+
+#### 2. 请求拦截器
+
+```typescript
+// 添加 Token 到请求头
+api.interceptors.request.use((config) => {
+ const accessToken = localStorage.getItem('access_token');
+ if (accessToken) {
+ config.headers.Authorization = `Bearer ${accessToken}`;
+ }
+ return config;
+});
+```
+
+#### 3. 响应拦截器(自动刷新 Token)
+
+```typescript
+// 处理 401 错误并自动刷新
+api.interceptors.response.use(
+ (response) => response,
+ async (error) => {
+ const originalRequest = error.config;
+
+ if (error.response?.status === 401 && !originalRequest._retry) {
+ originalRequest._retry = true;
+
+ try {
+ const refreshToken = localStorage.getItem('refresh_token');
+ const response = await axios.post('/auth/refresh', {
+ refresh_token: refreshToken,
+ });
+
+ const { access_token, refresh_token } = response.data.data;
+
+ localStorage.setItem('access_token', access_token);
+ localStorage.setItem('refresh_token', refresh_token);
+
+ // 重试原请求
+ originalRequest.headers.Authorization = `Bearer ${access_token}`;
+ return axios(originalRequest);
+ } catch (refreshError) {
+ // 刷新失败,跳转登录页
+ localStorage.clear();
+ window.location.href = '/login';
+ return Promise.reject(refreshError);
+ }
+ }
+
+ return Promise.reject(error);
+ }
+);
+```
+
+### 后端开发
+
+#### 1. 密码强度要求
+
+```rust
+// 验证密码强度
+fn validate_password(password: &str) -> Result<()> {
+ if password.len() < 8 {
+ return Err(anyhow!("密码长度至少 8 位"));
+ }
+ if password.len() > 100 {
+ return Err(anyhow!("密码长度最多 100 位"));
+ }
+ // 可添加更多规则(如必须包含大小写、数字等)
+ Ok(())
+}
+```
+
+#### 2. JWT 密钥管理
+
+**开发环境**:
+
+使用 `config/` 目录下的配置文件:
+
+```bash
+# 方式1:使用默认配置(推荐)
+# JWT 密钥已在 config/default.toml 中配置
+
+# 方式2:创建本地配置文件
+cp config/default.toml config/local.toml
+# 编辑 config/local.toml,修改 jwt_secret
+nano config/local.toml
+
+# 运行
+cargo run -- -c config/local.toml
+```
+
+**生产环境**:
+```bash
+# 使用强随机密钥
+AUTH_JWT_SECRET=$(openssl rand -base64 32)
+```
+
+#### 3. Token 过期时间配置
+
+```bash
+# Access Token:15 分钟(推荐)
+AUTH_ACCESS_TOKEN_EXPIRATION_MINUTES=15
+
+# Refresh Token:7 天(推荐)
+AUTH_REFRESH_TOKEN_EXPIRATION_DAYS=7
+```
+
+**建议**:
+- Access Token:5-30 分钟(权衡安全性和用户体验)
+- Refresh Token:7-30 天(根据应用安全要求)
+
+### 生产部署
+
+#### 1. HTTPS 强制
+
+```nginx
+server {
+ listen 80;
+ server_name api.yourdomain.com;
+ return 301 https://$server_name$request_uri;
+}
+
+server {
+ listen 443 ssl;
+ server_name api.yourdomain.com;
+ # SSL 配置...
+}
+```
+
+#### 2. CORS 配置
+
+开发环境可以允许所有来源:
+
+```rust
+CorsLayer::new()
+ .allow_origin(Any)
+ .allow_methods(Any)
+ .allow_headers(Any)
+```
+
+生产环境应该限制允许的来源:
+
+```rust
+CorsLayer::new()
+ .allow_origin("https://yourdomain.com".parse::().unwrap())
+ .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
+ .allow_headers([HeaderName::from_static("content-type"), HeaderName::from_static("authorization")])
+```
+
+#### 3. 速率限制
+
+防止暴力破解和 DDoS 攻击(需要额外实现):
+
+```rust
+// 使用 governor 库实现速率限制
+use governor::{Quota, RateLimiter};
+
+let limiter = RateLimiter::direct(Quota::per_second(5));
+// 每秒最多 5 个请求
+```
+
+---
+
+## 相关文档
+
+- [公开接口文档](endpoints/public.md) - 注册、登录、刷新 Token 接口
+- [受保护接口文档](endpoints/protected.md) - 需要认证的接口
+- [前端集成示例](examples/frontend-integration.md) - 完整的前端集成代码
+- [环境变量配置](../deployment/environment-variables.md) - 认证相关配置说明
+
+---
+
+**提示**:生产环境部署前务必修改 JWT 密钥为强随机字符串!
+
diff --git a/server/docs/api/endpoints/public.md b/server/docs/api/endpoints/public.md
new file mode 100644
index 0000000..58c5e27
--- /dev/null
+++ b/server/docs/api/endpoints/public.md
@@ -0,0 +1,369 @@
+# 公开接口文档
+
+本文档详细说明所有无需认证即可访问的 API 接口。
+
+## 目录
+
+- [GET /health - 健康检查](#get-health)
+- [GET /info - 服务器信息](#get-info)
+- [POST /auth/register - 用户注册](#post-authregister)
+- [POST /auth/login - 用户登录](#post-authlogin)
+- [POST /auth/refresh - 刷新 Token](#post-authrefresh)
+
+---
+
+## GET /health
+
+健康检查端点,用于检查服务是否正常运行。
+
+### 请求
+
+```http
+GET /health
+```
+
+**请求参数**:无
+
+**请求头**:无特殊要求
+
+### 响应
+
+**成功响应 (200)**:
+
+```json
+{
+ "status": "ok"
+}
+```
+
+或服务不可用时:
+
+```json
+{
+ "status": "unavailable"
+}
+```
+
+### 示例
+
+```bash
+curl http://localhost:3000/health
+```
+
+### 错误码
+
+| 错误码 | 说明 |
+|-------|------|
+| 500 | 服务器内部错误 |
+
+---
+
+## GET /info
+
+获取服务器基本信息,包括应用名称、版本、状态等。
+
+### 请求
+
+```http
+GET /info
+```
+
+**请求参数**:无
+
+**请求头**:无特殊要求
+
+### 响应
+
+**成功响应 (200)**:
+
+```json
+{
+ "name": "email-unlimit-server",
+ "version": "0.1.0",
+ "status": "running",
+ "timestamp": 1704112800
+}
+```
+
+### 字段说明
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| name | string | 应用名称 |
+| version | string | 应用版本 |
+| status | string | 运行状态 |
+| timestamp | number | 当前时间戳(Unix 时间戳) |
+
+### 示例
+
+```bash
+curl http://localhost:3000/info
+```
+
+### 错误码
+
+| 错误码 | 说明 |
+|-------|------|
+| 500 | 服务器内部错误 |
+
+---
+
+## POST /auth/register
+
+创建新用户账户。注册成功后自动登录,返回 Access Token 和 Refresh Token。
+
+### 请求
+
+```http
+POST /auth/register
+Content-Type: application/json
+```
+
+**请求参数**:
+
+```json
+{
+ "email": "user@example.com",
+ "password": "password123"
+}
+```
+
+### 字段说明
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| email | string | 是 | 用户邮箱,必须是有效的邮箱格式 |
+| password | string | 是 | 用户密码,建议长度至少 8 位 |
+
+### 响应
+
+**成功响应 (200)**:
+
+```json
+{
+ "code": 200,
+ "message": "Success",
+ "data": {
+ "email": "user@example.com",
+ "created_at": "2026-02-13T12:00:00.000Z",
+ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+ "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
+ }
+}
+```
+
+### 响应字段说明
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| email | string | 用户邮箱 |
+| created_at | string | 账号创建时间(ISO 8601 格式) |
+| access_token | string | Access Token,有效期 15 分钟 |
+| refresh_token | string | Refresh Token,有效期 7 天 |
+
+### 示例
+
+```bash
+curl -X POST http://localhost:3000/auth/register \
+ -H "Content-Type: application/json" \
+ -d '{
+ "email": "user@example.com",
+ "password": "password123"
+ }'
+```
+
+### 错误码
+
+| 错误码 | 说明 |
+|-------|------|
+| 400 | 请求参数错误(邮箱格式错误、密码长度不够) |
+| 409 | 邮箱已被注册 |
+| 500 | 服务器内部错误 |
+
+### 注意事项
+
+- 邮箱地址将作为用户的唯一标识符
+- 密码会使用 Argon2 算法进行哈希存储
+- 注册成功后会自动生成 Access Token 和 Refresh Token
+- Refresh Token 会存储在 Redis 中,用于后续刷新 Token
+
+---
+
+## POST /auth/login
+
+用户登录。验证成功后返回 Access Token 和 Refresh Token。
+
+### 请求
+
+```http
+POST /auth/login
+Content-Type: application/json
+```
+
+**请求参数**:
+
+```json
+{
+ "email": "user@example.com",
+ "password": "password123"
+}
+```
+
+### 字段说明
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| email | string | 是 | 用户邮箱 |
+| password | string | 是 | 用户密码 |
+
+### 响应
+
+**成功响应 (200)**:
+
+```json
+{
+ "code": 200,
+ "message": "Success",
+ "data": {
+ "id": "1234567890",
+ "email": "user@example.com",
+ "created_at": "2026-02-13T12:00:00.000Z",
+ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+ "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
+ }
+}
+```
+
+### 响应字段说明
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| id | string | 用户 ID(10 位数字) |
+| email | string | 用户邮箱 |
+| created_at | string | 账号创建时间(ISO 8601 格式) |
+| access_token | string | Access Token,有效期 15 分钟 |
+| refresh_token | string | Refresh Token,有效期 7 天 |
+
+### 示例
+
+```bash
+curl -X POST http://localhost:3000/auth/login \
+ -H "Content-Type: application/json" \
+ -d '{
+ "email": "user@example.com",
+ "password": "password123"
+ }'
+```
+
+### 错误码
+
+| 错误码 | 说明 |
+|-------|------|
+| 400 | 请求参数错误 |
+| 401 | 邮箱或密码错误 |
+| 500 | 服务器内部错误 |
+
+### 注意事项
+
+- 登录失败不会返回具体的错误信息(如"邮箱不存在"或"密码错误"),统一返回"邮箱或密码错误"
+- 密码错误次数过多可能会被临时限制(取决于具体实现)
+- 登录成功后会生成新的 Token 对,旧的 Token 会失效
+
+---
+
+## POST /auth/refresh
+
+使用 Refresh Token 获取新的 Access Token 和 Refresh Token。
+
+### 请求
+
+```http
+POST /auth/refresh
+Content-Type: application/json
+```
+
+**请求参数**:
+
+```json
+{
+ "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
+}
+```
+
+### 字段说明
+
+| 字段 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| refresh_token | string | 是 | Refresh Token |
+
+### 响应
+
+**成功响应 (200)**:
+
+```json
+{
+ "code": 200,
+ "message": "Success",
+ "data": {
+ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
+ "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
+ }
+}
+```
+
+### 响应字段说明
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| access_token | string | 新的 Access Token,有效期 15 分钟 |
+| refresh_token | string | 新的 Refresh Token,有效期 7 天 |
+
+### 示例
+
+```bash
+curl -X POST http://localhost:3000/auth/refresh \
+ -H "Content-Type: application/json" \
+ -d '{
+ "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
+ }'
+```
+
+### 错误码
+
+| 错误码 | 说明 |
+|-------|------|
+| 400 | 请求参数错误 |
+| 401 | Refresh Token 无效或已过期 |
+| 500 | 服务器内部错误 |
+
+### 注意事项
+
+- 每次刷新会生成新的 Refresh Token,旧的 Refresh Token 会立即失效
+- Refresh Token 只能使用一次,重复使用会返回错误
+- Refresh Token 有效期为 7 天,过期后需要重新登录
+- Refresh Token 存储在 Redis 中,服务器重启不会丢失(如果 Redis 持久化配置正确)
+
+### Token 刷新策略建议
+
+**前端实现建议**:
+
+1. 在每次 API 请求失败(401 错误)时尝试刷新 Token
+2. 刷新成功后重试原请求
+3. 刷新失败则跳转到登录页
+4. 不要在 Token 即将过期时主动刷新,而是在使用时检查有效性
+
+查看 [前端集成示例](../examples/frontend-integration.md) 了解完整的实现代码。
+
+---
+
+## 相关文档
+
+- [受保护接口文档](protected.md) - 需要认证的接口说明
+- [认证机制详解](../authentication.md) - 完整的认证流程说明
+- [API 概览](../api-overview.md) - 所有接口快速索引
+- [前端集成示例](../examples/frontend-integration.md) - 前端集成代码示例
+
+---
+
+**提示**:建议使用 Postman、Insomnia 或类似工具测试 API 接口。
+
diff --git a/server/docs/api/examples/frontend-integration.md b/server/docs/api/examples/frontend-integration.md
new file mode 100644
index 0000000..f9186f5
--- /dev/null
+++ b/server/docs/api/examples/frontend-integration.md
@@ -0,0 +1,768 @@
+# 前端集成示例
+
+本文档提供完整的前端集成代码示例,包括 JavaScript/TypeScript、React 和 Vue。
+
+## 目录
+
+- [TypeScript 基础示例](#typescript-基础示例)
+- [React 集成示例](#react-集成示例)
+- [Vue 集成示例](#vue-集成示例)
+- [Token 存储建议](#token-存储建议)
+- [错误处理](#错误处理)
+
+---
+
+## TypeScript 基础示例
+
+### 认证客户端类
+
+以下是一个完整的 TypeScript 认证客户端实现,包含注册、登录、Token 刷新等功能:
+
+```typescript
+interface ApiResponse {
+ code: number;
+ message: string;
+ data: T;
+}
+
+interface RegisterData {
+ email: string;
+ password: string;
+}
+
+interface LoginData {
+ email: string;
+ password: string;
+}
+
+interface RegisterResponse {
+ email: string;
+ created_at: string;
+ access_token: string;
+ refresh_token: string;
+}
+
+interface LoginResponse {
+ id: string;
+ email: string;
+ created_at: string;
+ access_token: string;
+ refresh_token: string;
+}
+
+interface RefreshResponse {
+ access_token: string;
+ refresh_token: string;
+}
+
+class AuthClient {
+ private baseURL: string;
+ private accessToken: string | null = null;
+ private refreshToken: string | null = null;
+
+ constructor(baseURL: string = 'http://localhost:3000') {
+ this.baseURL = baseURL;
+ // 从 localStorage 加载 Token
+ this.accessToken = localStorage.getItem('access_token');
+ this.refreshToken = localStorage.getItem('refresh_token');
+ }
+
+ /**
+ * 用户注册
+ */
+ async register(email: string, password: string): Promise {
+ const response = await fetch(`${this.baseURL}/auth/register`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email, password }),
+ });
+
+ const result: ApiResponse = await response.json();
+
+ if (result.code === 200) {
+ this.saveTokens(result.data.access_token, result.data.refresh_token);
+ return result.data;
+ }
+
+ throw new Error(result.message);
+ }
+
+ /**
+ * 用户登录
+ */
+ async login(email: string, password: string): Promise {
+ const response = await fetch(`${this.baseURL}/auth/login`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email, password }),
+ });
+
+ const result: ApiResponse = await response.json();
+
+ if (result.code === 200) {
+ this.saveTokens(result.data.access_token, result.data.refresh_token);
+ return result.data;
+ }
+
+ throw new Error(result.message);
+ }
+
+ /**
+ * 刷新 Token
+ */
+ async refreshTokens(): Promise {
+ if (!this.refreshToken) {
+ throw new Error('No refresh token available');
+ }
+
+ const response = await fetch(`${this.baseURL}/auth/refresh`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ refresh_token: this.refreshToken }),
+ });
+
+ const result: ApiResponse = await response.json();
+
+ if (result.code === 200) {
+ this.saveTokens(result.data.access_token, result.data.refresh_token);
+ } else {
+ this.clearTokens();
+ throw new Error(result.message);
+ }
+ }
+
+ /**
+ * 发起需要认证的请求
+ */
+ async authenticatedFetch(url: string, options?: RequestInit): Promise {
+ if (!this.accessToken) {
+ throw new Error('No access token available');
+ }
+
+ let response = await fetch(url, {
+ ...options,
+ headers: {
+ ...options?.headers,
+ 'Authorization': `Bearer ${this.accessToken}`,
+ },
+ });
+
+ // Token 过期,尝试刷新
+ if (response.status === 401) {
+ try {
+ await this.refreshTokens();
+ // 重试原请求
+ response = await fetch(url, {
+ ...options,
+ headers: {
+ ...options?.headers,
+ 'Authorization': `Bearer ${this.accessToken}`,
+ },
+ });
+ } catch (error) {
+ // 刷新失败,清除 Token 并抛出错误
+ this.clearTokens();
+ throw error;
+ }
+ }
+
+ return response;
+ }
+
+ /**
+ * 保存 Token 到 localStorage
+ */
+ private saveTokens(accessToken: string, refreshToken: string): void {
+ this.accessToken = accessToken;
+ this.refreshToken = refreshToken;
+ localStorage.setItem('access_token', accessToken);
+ localStorage.setItem('refresh_token', refreshToken);
+ }
+
+ /**
+ * 清除 Token
+ */
+ private clearTokens(): void {
+ this.accessToken = null;
+ this.refreshToken = null;
+ localStorage.removeItem('access_token');
+ localStorage.removeItem('refresh_token');
+ }
+
+ /**
+ * 登出
+ */
+ logout(): void {
+ this.clearTokens();
+ }
+
+ /**
+ * 检查是否已登录
+ */
+ isAuthenticated(): boolean {
+ return this.accessToken !== null;
+ }
+}
+
+// 使用示例
+const authClient = new AuthClient();
+
+// 注册
+try {
+ const result = await authClient.register('user@example.com', 'password123');
+ console.log('注册成功:', result);
+} catch (error) {
+ console.error('注册失败:', error);
+}
+
+// 登录
+try {
+ const result = await authClient.login('user@example.com', 'password123');
+ console.log('登录成功:', result);
+} catch (error) {
+ console.error('登录失败:', error);
+}
+
+// 访问受保护接口
+try {
+ const response = await authClient.authenticatedFetch(
+ 'http://localhost:3000/auth/delete',
+ {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ user_id: '1234567890', password: 'password123' }),
+ }
+ );
+ const data = await response.json();
+ console.log('请求成功:', data);
+} catch (error) {
+ console.error('请求失败:', error);
+}
+
+// 登出
+authClient.logout();
+```
+
+---
+
+## React 集成示例
+
+### AuthContext Provider
+
+```typescript
+// AuthContext.tsx
+import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
+
+interface User {
+ id: string;
+ email: string;
+ created_at: string;
+}
+
+interface AuthContextType {
+ user: User | null;
+ accessToken: string | null;
+ isAuthenticated: boolean;
+ login: (email: string, password: string) => Promise;
+ register: (email: string, password: string) => Promise;
+ logout: () => void;
+ loading: boolean;
+}
+
+const AuthContext = createContext(undefined);
+
+export function AuthProvider({ children }: { children: ReactNode }) {
+ const [user, setUser] = useState(null);
+ const [accessToken, setAccessToken] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ // 从 localStorage 加载 Token
+ const storedAccessToken = localStorage.getItem('access_token');
+ const storedUser = localStorage.getItem('user');
+
+ if (storedAccessToken && storedUser) {
+ setAccessToken(storedAccessToken);
+ setUser(JSON.parse(storedUser));
+ }
+ setLoading(false);
+ }, []);
+
+ const login = async (email: string, password: string) => {
+ const response = await fetch('http://localhost:3000/auth/login', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email, password }),
+ });
+
+ const result = await response.json();
+
+ if (result.code === 200) {
+ const userData: User = {
+ id: result.data.id,
+ email: result.data.email,
+ created_at: result.data.created_at,
+ };
+
+ setUser(userData);
+ setAccessToken(result.data.access_token);
+
+ localStorage.setItem('access_token', result.data.access_token);
+ localStorage.setItem('refresh_token', result.data.refresh_token);
+ localStorage.setItem('user', JSON.stringify(userData));
+ } else {
+ throw new Error(result.message);
+ }
+ };
+
+ const register = async (email: string, password: string) => {
+ const response = await fetch('http://localhost:3000/auth/register', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email, password }),
+ });
+
+ const result = await response.json();
+
+ if (result.code === 200) {
+ const userData: User = {
+ id: result.data.id || '',
+ email: result.data.email,
+ created_at: result.data.created_at,
+ };
+
+ setUser(userData);
+ setAccessToken(result.data.access_token);
+
+ localStorage.setItem('access_token', result.data.access_token);
+ localStorage.setItem('refresh_token', result.data.refresh_token);
+ localStorage.setItem('user', JSON.stringify(userData));
+ } else {
+ throw new Error(result.message);
+ }
+ };
+
+ const logout = () => {
+ setUser(null);
+ setAccessToken(null);
+ localStorage.removeItem('access_token');
+ localStorage.removeItem('refresh_token');
+ localStorage.removeItem('user');
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useAuth() {
+ const context = useContext(AuthContext);
+ if (!context) {
+ throw new Error('useAuth must be used within AuthProvider');
+ }
+ return context;
+}
+```
+
+### API Hook(带 Token 刷新)
+
+```typescript
+// useApi.ts
+import { useCallback } from 'react';
+import { useAuth } from './AuthContext';
+
+export function useApi() {
+ const { accessToken, setAccessToken, logout } = useAuth();
+
+ const fetchWithAuth = useCallback(
+ async (url: string, options?: RequestInit): Promise => {
+ if (!accessToken) {
+ throw new Error('Not authenticated');
+ }
+
+ let response = await fetch(url, {
+ ...options,
+ headers: {
+ ...options?.headers,
+ 'Authorization': `Bearer ${accessToken}`,
+ },
+ });
+
+ // Token 过期,尝试刷新
+ if (response.status === 401) {
+ const refreshToken = localStorage.getItem('refresh_token');
+
+ if (refreshToken) {
+ const refreshResponse = await fetch('http://localhost:3000/auth/refresh', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ refresh_token: refreshToken }),
+ });
+
+ const refreshResult = await refreshResponse.json();
+
+ if (refreshResult.code === 200) {
+ setAccessToken(refreshResult.data.access_token);
+ localStorage.setItem('access_token', refreshResult.data.access_token);
+ localStorage.setItem('refresh_token', refreshResult.data.refresh_token);
+
+ // 重试原请求
+ response = await fetch(url, {
+ ...options,
+ headers: {
+ ...options?.headers,
+ 'Authorization': `Bearer ${refreshResult.data.access_token}`,
+ },
+ });
+ } else {
+ // 刷新失败,登出
+ logout();
+ throw new Error('Session expired');
+ }
+ } else {
+ logout();
+ throw new Error('Session expired');
+ }
+ }
+
+ return response;
+ },
+ [accessToken, setAccessToken, logout]
+ );
+
+ return { fetchWithAuth };
+}
+```
+
+### 登录组件示例
+
+```typescript
+// Login.tsx
+import React, { useState } from 'react';
+import { useAuth } from './AuthContext';
+
+export function Login() {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [error, setError] = useState('');
+ const [loading, setLoading] = useState(false);
+ const { login } = useAuth();
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setError('');
+ setLoading(true);
+
+ try {
+ await login(email, password);
+ // 登录成功,路由跳转
+ } catch (err) {
+ setError(err instanceof Error ? err.message : '登录失败');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+ );
+}
+```
+
+---
+
+## Vue 集成示例
+
+### Auth Composable
+
+```typescript
+// composables/useAuth.ts
+import { ref, computed } from 'vue';
+import { useRouter } from 'vue-router';
+
+interface User {
+ id: string;
+ email: string;
+ created_at: string;
+}
+
+export function useAuth() {
+ const user = ref(null);
+ const accessToken = ref(null);
+ const router = useRouter();
+
+ const isAuthenticated = computed(() => !!accessToken.value);
+
+ // 初始化:从 localStorage 加载
+ const init = () => {
+ const storedAccessToken = localStorage.getItem('access_token');
+ const storedUser = localStorage.getItem('user');
+
+ if (storedAccessToken && storedUser) {
+ accessToken.value = storedAccessToken;
+ user.value = JSON.parse(storedUser);
+ }
+ };
+
+ const login = async (email: string, password: string) => {
+ const response = await fetch('http://localhost:3000/auth/login', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email, password }),
+ });
+
+ const result = await response.json();
+
+ if (result.code === 200) {
+ const userData: User = {
+ id: result.data.id,
+ email: result.data.email,
+ created_at: result.data.created_at,
+ };
+
+ user.value = userData;
+ accessToken.value = result.data.access_token;
+
+ localStorage.setItem('access_token', result.data.access_token);
+ localStorage.setItem('refresh_token', result.data.refresh_token);
+ localStorage.setItem('user', JSON.stringify(userData));
+ } else {
+ throw new Error(result.message);
+ }
+ };
+
+ const register = async (email: string, password: string) => {
+ const response = await fetch('http://localhost:3000/auth/register', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email, password }),
+ });
+
+ const result = await response.json();
+
+ if (result.code === 200) {
+ const userData: User = {
+ id: result.data.id || '',
+ email: result.data.email,
+ created_at: result.data.created_at,
+ };
+
+ user.value = userData;
+ accessToken.value = result.data.access_token;
+
+ localStorage.setItem('access_token', result.data.access_token);
+ localStorage.setItem('refresh_token', result.data.refresh_token);
+ localStorage.setItem('user', JSON.stringify(userData));
+ } else {
+ throw new Error(result.message);
+ }
+ };
+
+ const logout = () => {
+ user.value = null;
+ accessToken.value = null;
+ localStorage.removeItem('access_token');
+ localStorage.removeItem('refresh_token');
+ localStorage.removeItem('user');
+ router.push('/login');
+ };
+
+ return {
+ user,
+ accessToken,
+ isAuthenticated,
+ login,
+ register,
+ logout,
+ init,
+ };
+}
+```
+
+### Axios 拦截器示例
+
+```typescript
+// api/axios.ts
+import axios from 'axios';
+
+const api = axios.create({
+ baseURL: 'http://localhost:3000',
+});
+
+// 请求拦截器:添加 Authorization header
+api.interceptors.request.use((config) => {
+ const accessToken = localStorage.getItem('access_token');
+ if (accessToken) {
+ config.headers.Authorization = `Bearer ${accessToken}`;
+ }
+ return config;
+});
+
+// 响应拦截器:处理 401 错误并刷新 Token
+api.interceptors.response.use(
+ (response) => response,
+ async (error) => {
+ const originalRequest = error.config;
+
+ if (error.response?.status === 401 && !originalRequest._retry) {
+ originalRequest._retry = true;
+
+ const refreshToken = localStorage.getItem('refresh_token');
+
+ if (refreshToken) {
+ try {
+ const response = await axios.post('/auth/refresh', {
+ refresh_token: refreshToken,
+ });
+
+ if (response.data.code === 200) {
+ const { access_token, refresh_token } = response.data.data;
+
+ localStorage.setItem('access_token', access_token);
+ localStorage.setItem('refresh_token', refresh_token);
+
+ // 重试原请求
+ originalRequest.headers.Authorization = `Bearer ${access_token}`;
+ return axios(originalRequest);
+ }
+ } catch (refreshError) {
+ // 刷新失败,清除 Token
+ localStorage.removeItem('access_token');
+ localStorage.removeItem('refresh_token');
+ window.location.href = '/login';
+ return Promise.reject(refreshError);
+ }
+ } else {
+ // 没有 Refresh Token,跳转到登录页
+ window.location.href = '/login';
+ }
+ }
+
+ return Promise.reject(error);
+ }
+);
+
+export default api;
+```
+
+---
+
+## Token 存储建议
+
+### localStorage vs sessionStorage vs Cookie
+
+| 存储方式 | 优点 | 缺点 | 推荐场景 |
+|---------|------|------|----------|
+| localStorage | 数据持久化,刷新页面不丢失 | 容易受到 XSS 攻击 | Access Token、Refresh Token |
+| sessionStorage | 关闭标签页自动清除 | 刷新页面会丢失 | 不推荐 |
+| Cookie | 可设置 HttpOnly 防止 XSS | 容易受到 CSRF 攻击 | 服务器渲染场景 |
+
+### 推荐方案
+
+**前端应用(SPA)**:
+- Access Token:localStorage
+- Refresh Token:localStorage
+- 添加适当的 XSS 防护(内容安全策略、输入验证)
+
+**安全性要求高的场景**:
+- Access Token:内存(React Context/Vue Reactive)
+- Refresh Token:HttpOnly Cookie(需要后端支持)
+
+---
+
+## 错误处理
+
+### 通用错误处理
+
+```typescript
+async function handleApiCall(
+ apiCall: () => Promise,
+ onError?: (error: Error) => void
+): Promise {
+ try {
+ return await apiCall();
+ } catch (error) {
+ if (onError) {
+ onError(error as Error);
+ } else {
+ console.error('API 调用失败:', error);
+ }
+ return null;
+ }
+}
+
+// 使用示例
+const result = await handleApiCall(
+ () => authClient.login('user@example.com', 'password123'),
+ (error) => {
+ alert(`登录失败: ${error.message}`);
+ }
+);
+```
+
+### 网络错误重试
+
+```typescript
+async function fetchWithRetry(
+ url: string,
+ options?: RequestInit,
+ maxRetries: number = 3
+): Promise {
+ for (let i = 0; i < maxRetries; i++) {
+ try {
+ return await fetch(url, options);
+ } catch (error) {
+ if (i === maxRetries - 1) {
+ throw error;
+ }
+ // 等待后重试
+ await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
+ }
+ }
+ throw new Error('Max retries reached');
+}
+```
+
+---
+
+## 相关文档
+
+- [公开接口文档](../endpoints/public.md) - API 接口详细说明
+- [认证机制详解](../authentication.md) - JWT 认证流程
+- [受保护接口文档](../endpoints/protected.md) - 需要认证的接口
+
+---
+
+**提示**:以上示例代码仅供参考,实际使用时请根据项目需求调整。
diff --git a/server/docs/deployment/environment-variables.md b/server/docs/deployment/environment-variables.md
new file mode 100644
index 0000000..479bc67
--- /dev/null
+++ b/server/docs/deployment/environment-variables.md
@@ -0,0 +1,557 @@
+# 环境变量配置说明
+
+本文档提供所有可配置的环境变量说明。
+
+## 目录
+
+- [配置优先级](#配置优先级)
+- [服务器配置](#服务器配置)
+- [数据库配置](#数据库配置)
+- [认证配置](#认证配置)
+- [Redis 配置](#redis-配置)
+- [配置示例](#配置示例)
+
+---
+
+## 配置优先级
+
+配置的加载优先级从高到低为:
+
+1. **环境变量**(最高优先级)
+2. **配置文件**(config/ 目录)
+3. **默认值**(代码中硬编码)
+
+这意味着:
+- 环境变量会覆盖配置文件中的设置
+- 配置文件会覆盖代码中的默认值
+
+---
+
+## 服务器配置
+
+### SERVER_HOST
+
+服务器监听地址。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 字符串 |
+| 默认值 | `0.0.0.0` |
+| 说明 | `0.0.0.0` 表示监听所有网络接口 |
+
+**示例**:
+```bash
+SERVER_HOST=127.0.0.1 # 仅本地访问
+SERVER_HOST=0.0.0.0 # 允许外部访问
+```
+
+### SERVER_PORT
+
+服务器监听端口。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 整数 |
+| 默认值 | `3000` |
+| 说明 | 1-65535 之间的有效端口 |
+
+**示例**:
+```bash
+SERVER_PORT=3000 # 开发环境
+SERVER_PORT=8080 # 生产环境
+SERVER_PORT=80 # HTTP 标准端口
+```
+
+---
+
+## 数据库配置
+
+### DATABASE__DATABASE_TYPE
+
+数据库类型,支持 MySQL、PostgreSQL、SQLite。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 字符串 |
+| 默认值 | `sqlite` |
+| 可选值 | `mysql`、`postgresql`、`sqlite` |
+
+**示例**:
+```bash
+DATABASE__DATABASE_TYPE=sqlite # SQLite 数据库
+DATABASE__DATABASE_TYPE=mysql # MySQL 数据库
+DATABASE__DATABASE_TYPE=postgresql # PostgreSQL 数据库
+```
+
+### MySQL 配置
+
+当 `DATABASE__DATABASE_TYPE=mysql` 时使用。
+
+#### DATABASE__HOST
+
+MySQL 服务器地址。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 字符串 |
+| 默认值 | `localhost` |
+
+**示例**:
+```bash
+DATABASE__HOST=localhost
+DATABASE__HOST=192.168.1.100
+DATABASE__HOST=mysql.example.com
+```
+
+#### DATABASE__PORT
+
+MySQL 服务器端口。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 整数 |
+| 默认值 | `3306` |
+
+**示例**:
+```bash
+DATABASE__PORT=3306
+```
+
+#### DATABASE__USER
+
+MySQL 用户名。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 字符串 |
+| 默认值 | - |
+| 必填 | 是 |
+
+**示例**:
+```bash
+DATABASE__USER=root
+DATABASE__USER=webapp
+```
+
+#### DATABASE__PASSWORD
+
+MySQL 密码。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 字符串 |
+| 默认值 | - |
+| 必填 | 是 |
+
+**示例**:
+```bash
+DATABASE__PASSWORD=your-password
+```
+
+#### DATABASE__DATABASE
+
+MySQL 数据库名称。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 字符串 |
+| 默认值 | - |
+| 必填 | 是 |
+
+**示例**:
+```bash
+DATABASE__DATABASE=web_template
+```
+
+### PostgreSQL 配置
+
+当 `DATABASE__DATABASE_TYPE=postgresql` 时使用,配置项与 MySQL 相同。
+
+| 环境变量 | 说明 | 默认值 |
+|---------|------|--------|
+| DATABASE__HOST | PostgreSQL 服务器地址 | localhost |
+| DATABASE__PORT | PostgreSQL 服务器端口 | 5432 |
+| DATABASE__USER | PostgreSQL 用户名 | - |
+| DATABASE__PASSWORD | PostgreSQL 密码 | - |
+| DATABASE__DATABASE | PostgreSQL 数据库名称 | - |
+
+### SQLite 配置
+
+当 `DATABASE__DATABASE_TYPE=sqlite` 时使用。
+
+#### DATABASE__PATH
+
+SQLite 数据库文件路径。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 字符串 |
+| 默认值 | - |
+| 必填 | 是 |
+
+**示例**:
+```bash
+DATABASE__PATH=data/app.db
+DATABASE__PATH=/var/data/webapp.db
+```
+
+**注意**:
+- 目录必须存在,程序不会自动创建目录
+- 文件不存在时会自动创建
+
+### DATABASE__MAX_CONNECTIONS
+
+数据库连接池最大连接数。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 整数 |
+| 默认值 | `10` |
+
+**示例**:
+```bash
+DATABASE__MAX_CONNECTIONS=10 # 开发环境
+DATABASE__MAX_CONNECTIONS=100 # 生产环境
+```
+
+**建议**:
+- 开发环境:5-10
+- 生产环境:根据应用负载调整(通常是 CPU 核心数的 2-4 倍)
+
+---
+
+## 认证配置
+
+### AUTH__JWT_SECRET
+
+JWT 签名密钥。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 字符串 |
+| 默认值 | - |
+| 必填 | 是 |
+
+**安全建议**:
+- 生产环境使用至少 32 位的随机字符串
+- 定期更换密钥
+- 不要在代码中硬编码
+
+**生成强密钥**:
+```bash
+# 使用 OpenSSL
+openssl rand -base64 32
+
+# 使用 Python
+python -c "import secrets; print(secrets.token_urlsafe(32))"
+
+# 使用 Node.js
+node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
+```
+
+**示例**:
+```bash
+# 开发环境(不安全)
+AUTH__JWT_SECRET=dev-secret-key
+
+# 生产环境(安全)
+AUTH__JWT_SECRET=Kx7Yn2Zp9qR8wF4tL6mN3vB5xC8zD1sE9aH2jK7
+```
+
+### AUTH__ACCESS_TOKEN_EXPIRATION_MINUTES
+
+Access Token 过期时间(分钟)。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 整数 |
+| 默认值 | `15` |
+
+**示例**:
+```bash
+AUTH__ACCESS_TOKEN_EXPIRATION_MINUTES=15 # 15 分钟(推荐)
+AUTH__ACCESS_TOKEN_EXPIRATION_MINUTES=30 # 30 分钟
+AUTH__ACCESS_TOKEN_EXPIRATION_MINUTES=60 # 1 小时
+```
+
+**建议**:
+- 安全性要求高:5-15 分钟
+- 用户体验优先:30-60 分钟
+- 权衡安全性和用户体验
+
+### AUTH__REFRESH_TOKEN_EXPIRATION_DAYS
+
+Refresh Token 过期时间(天)。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 整数 |
+| 默认值 | `7` |
+
+**示例**:
+```bash
+AUTH__REFRESH_TOKEN_EXPIRATION_DAYS=7 # 7 天(推荐)
+AUTH__REFRESH_TOKEN_EXPIRATION_DAYS=30 # 30 天
+AUTH__REFRESH_TOKEN_EXPIRATION_DAYS=90 # 90 天
+```
+
+**建议**:
+- Web 应用:7-30 天
+- 移动应用:30-90 天
+- 安全性要求高的应用:7 天或更短
+
+---
+
+## Redis 配置
+
+### REDIS__HOST
+
+Redis 服务器地址。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 字符串 |
+| 默认值 | `localhost` |
+
+**示例**:
+```bash
+REDIS__HOST=localhost
+REDIS__HOST=192.168.1.100
+REDIS__HOST=redis.example.com
+```
+
+### REDIS__PORT
+
+Redis 服务器端口。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 整数 |
+| 默认值 | `6379` |
+
+**示例**:
+```bash
+REDIS__PORT=6379
+```
+
+### REDIS__PASSWORD
+
+Redis 密码(如果设置了密码)。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 字符串 |
+| 默认值 | - |
+| 必填 | 否 |
+
+**示例**:
+```bash
+REDIS__PASSWORD=your-redis-password
+```
+
+### REDIS__DB
+
+Redis 数据库编号。
+
+| 属性 | 值 |
+|------|-----|
+| 类型 | 整数 |
+| 默认值 | `0` |
+| 范围 | 0-15 |
+
+**示例**:
+```bash
+REDIS__DB=0 # 默认数据库
+REDIS__DB=1 # 数据库 1
+```
+
+---
+
+## 配置示例
+
+### 开发环境(SQLite)
+
+**重要**:本项目不支持 .env 文件。开发环境请使用 `config/` 目录下的 toml 配置文件。
+
+**方式一:使用默认配置(最简单)**
+
+无需任何配置,直接运行即可:
+
+```bash
+cargo run
+```
+
+**方式二:修改配置文件**
+
+如果需要修改配置,编辑 `config/default.toml` 或创建 `config/local.toml`:
+
+```bash
+# 复制默认配置
+cp config/default.toml config/local.toml
+
+# 编辑配置文件
+nano config/local.toml # 或使用其他编辑器
+
+# 运行
+cargo run -- -c config/local.toml
+```
+
+### 开发环境(MySQL)
+
+**重要**:本项目不支持 .env 文件。开发环境请使用 `config/` 目录下的 toml 配置文件。
+
+编辑 MySQL 配置文件:
+
+```bash
+# 编辑开发环境配置文件
+nano config/development.toml
+
+# 设置 database.type = "mysql" 并修改连接信息
+
+# 运行
+cargo run
+```
+
+或使用环境变量(适用于 Docker/Kubernetes):
+
+```bash
+DATABASE__DATABASE_TYPE=mysql \
+DATABASE__HOST=localhost \
+DATABASE__PORT=3306 \
+DATABASE__USER=root \
+DATABASE__PASSWORD=root \
+DATABASE__DATABASE=web_template_dev \
+cargo run
+```
+
+### 生产环境
+
+**重要**:本项目不支持 .env 文件。生产环境请使用 `config/` 目录下的 toml 配置文件或环境变量。
+
+**方式一:使用配置文件**
+
+修改 `config/production.toml` 中的配置:
+
+```bash
+# 编辑生产环境配置文件
+nano config/production.toml
+
+# 运行
+cargo run -- -e production -c config/production.toml
+```
+
+**方式二:使用环境变量(Docker/Kubernetes 推荐)**
+
+```bash
+DATABASE__DATABASE_TYPE=mysql \
+DATABASE__HOST=mysql.production.example.com \
+DATABASE__PORT=3306 \
+DATABASE__USER=webapp \
+DATABASE__PASSWORD=strong-password-here \
+DATABASE__DATABASE=web_template_prod \
+DATABASE__MAX_CONNECTIONS=100 \
+AUTH__JWT_SECRET=Kx7Yn2Zp9qR8wF4tL6mN3vB5xC8zD1sE9aH2jK7 \
+REDIS__HOST=redis.production.example.com \
+REDIS__PORT=6379 \
+REDIS__PASSWORD=strong-redis-password \
+REDIS__DB=0 \
+cargo run -- -e production
+```
+
+### Docker Compose 配置
+
+```yaml
+# docker-compose.yml
+version: '3.8'
+
+services:
+ web:
+ image: email-unlimit-server:latest
+ ports:
+ - "3000:3000"
+ environment:
+ - SERVER_HOST=0.0.0.0
+ - SERVER_PORT=3000
+ - DATABASE__DATABASE_TYPE=postgresql
+ - DATABASE__HOST=db
+ - DATABASE__PORT=5432
+ - DATABASE__USER=webapp
+ - DATABASE__PASSWORD=password
+ - DATABASE__DATABASE=web_template
+ - DATABASE__MAX_CONNECTIONS=10
+ - AUTH__JWT_SECRET=${JWT_SECRET}
+ - AUTH__ACCESS_TOKEN_EXPIRATION_MINUTES=15
+ - AUTH__REFRESH_TOKEN_EXPIRATION_DAYS=7
+ - REDIS__HOST=redis
+ - REDIS__PORT=6379
+ - REDIS__DB=0
+ depends_on:
+ - db
+ - redis
+
+ db:
+ image: postgres:15
+ environment:
+ - POSTGRES_USER=webapp
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=web_template
+ volumes:
+ - postgres_data:/var/lib/postgresql/data
+
+ redis:
+ image: redis:7
+ volumes:
+ - redis_data:/data
+
+volumes:
+ postgres_data:
+ redis_data:
+```
+
+---
+
+## 安全检查清单
+
+生产环境部署前检查:
+
+- [ ] JWT 密钥使用强随机字符串(至少 32 位)
+- [ ] 数据库密码使用强密码
+- [ ] Redis 设置密码(如果可从外部访问)
+- [ ] 服务器监听地址根据需求配置(0.0.0.0 或 127.0.0.1)
+- [ ] 数据库连接数根据负载调整
+- [ ] Token 过期时间根据安全要求配置
+- [ ] 环境变量文件不提交到版本控制
+
+---
+
+## 相关文档
+
+- [配置文件详解](configuration.md) - 配置文件组织说明
+- [快速开始指南](../development/getting-started.md) - 安装和配置指南
+- [生产环境部署](production-guide.md) - 生产部署最佳实践
+
+---
+
+**提示**:使用 `.env.example` 作为模板,不要提交包含敏感信息的 `.env` 文件到版本控制。
+# 可选基础设施与 Web 防护
+
+Redis 和邮件能力默认关闭。常用环境变量:
+
+```text
+REDIS__ENABLED=false
+EMAIL__ENABLED=false
+EMAIL__SMTP_HOST=smtp.example.com
+EMAIL__SMTP_PORT=587
+EMAIL__SMTP_USERNAME=
+EMAIL__SMTP_PASSWORD=
+EMAIL__FROM_EMAIL=noreply@example.com
+SERVER__REQUEST_TIMEOUT_SECONDS=30
+SERVER__MAX_BODY_BYTES=1048576
+SERVER__CONCURRENCY_LIMIT=256
+SERVER__RATE_LIMIT_PER_MINUTE=120
+SERVER__CORS_ORIGINS=["https://app.example.com"]
+```
+
+生产环境必须设置非默认 JWT 密钥,且 `cors_origins` 不允许包含 `*`。
+
+
diff --git a/server/docs/development/getting-started.md b/server/docs/development/getting-started.md
new file mode 100644
index 0000000..81bad50
--- /dev/null
+++ b/server/docs/development/getting-started.md
@@ -0,0 +1,473 @@
+# 快速开始指南
+
+本文档将指导你完成 Email Unlimited 项目的安装、配置和运行。
+
+## 目录
+
+- [环境要求](#环境要求)
+- [安装步骤](#安装步骤)
+- [配置说明](#配置说明)
+- [运行项目](#运行项目)
+- [验证安装](#验证安装)
+- [常见问题](#常见问题)
+
+---
+
+## 环境要求
+
+### 必需环境
+
+- **Rust**:1.70 或更高版本
+ - 安装方法:访问 [rustup.rs](https://rustup.rs/) 或使用 `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`
+- **Git**:用于克隆项目
+
+### 数据库(任选其一)
+
+- **SQLite**:默认选项,无需额外安装
+- **MySQL**:5.7 或更高版本
+- **PostgreSQL**:12 或更高版本
+
+### 可选环境
+
+- **Redis**:用于存储 Refresh Token(推荐)
+ - Windows:下载 [Redis for Windows](https://github.com/microsoftarchive/redis/releases)
+ - macOS:`brew install redis`
+ - Linux:`sudo apt-get install redis-server`
+
+### 检查环境
+
+```bash
+# 检查 Rust 版本
+rustc --version
+
+# 检查 Cargo 版本
+cargo --version
+
+# 检查 Git 版本
+git --version
+
+# 检查 MySQL(如果使用)
+mysql --version
+
+# 检查 PostgreSQL(如果使用)
+psql --version
+
+# 检查 Redis(如果使用)
+redis-cli --version
+```
+
+---
+
+## 安装步骤
+
+### 1. 克隆项目
+
+```bash
+git clone
+cd email-unlimit-server
+```
+
+### 2. 安装依赖
+
+使用 Cargo 构建项目(会自动下载依赖):
+
+```bash
+cargo build
+```
+
+### 3. 配置项目
+
+#### 方式一:使用默认配置(SQLite,最简单)
+
+**无需任何配置!** 直接运行即可:
+
+```bash
+cargo run
+```
+
+默认配置:
+- 数据库:SQLite(自动创建 `db.sqlite3`)
+- 服务器:`127.0.0.1:3000`
+- Redis:`localhost:6379`
+
+#### 方式二:使用 MySQL/PostgreSQL
+
+**步骤 1**:修改配置文件
+
+编辑 `config/development.toml`,修改数据库类型和连接信息:
+
+```toml
+[database]
+# 修改数据库类型:mysql, postgresql 或 sqlite
+type = "mysql" # 或 "postgresql"
+
+# MySQL 配置
+host = "localhost"
+port = 3306
+user = "root"
+password = "your-password"
+database = "web_template_dev"
+
+# 或 PostgreSQL 配置
+# type = "postgresql"
+# host = "localhost"
+# port = 5432
+# user = "postgres"
+# password = "your-password"
+# database = "web_template_dev"
+```
+
+**步骤 2**:运行项目
+
+```bash
+cargo run
+```
+
+#### 方式三:通过环境变量覆盖(适用于 Docker/Kubernetes)
+
+```bash
+# 使用环境变量
+DATABASE_TYPE=postgresql \
+DATABASE_HOST=localhost \
+DATABASE_PORT=5432 \
+DATABASE_USER=postgres \
+DATABASE_PASSWORD=password \
+DATABASE_DATABASE=web_template_dev \
+cargo run
+```
+
+---
+
+## 配置说明
+
+### 数据库配置
+
+#### SQLite(默认,推荐用于开发)
+
+**优点**:无需额外安装,文件存储,易于测试
+
+**缺点**:不支持高并发写入
+
+**适用场景**:开发环境、小型应用
+
+**使用方法**:无需配置,直接运行
+
+#### MySQL
+
+**优点**:成熟稳定,支持高并发
+
+**缺点**:需要额外安装和配置
+
+**适用场景**:生产环境、大型应用
+
+**配置方法**:
+
+**选项 1**:修改配置文件
+
+编辑 `config/development.toml`,设置数据库类型为 `mysql` 并修改连接信息:
+
+```toml
+[database]
+type = "mysql"
+host = "localhost"
+port = 3306
+user = "root"
+password = "your-password"
+database = "web_template_dev"
+```
+
+**选项 2**:使用环境变量
+
+```bash
+DATABASE_TYPE=mysql \
+DATABASE_HOST=localhost \
+DATABASE_PORT=3306 \
+DATABASE_USER=root \
+DATABASE_PASSWORD=your-password \
+DATABASE_DATABASE=web_template_dev \
+cargo run
+```
+
+#### PostgreSQL
+
+**优点**:功能强大,支持高级特性
+
+**缺点**:资源占用较大
+
+**适用场景**:需要高级数据库功能的应用
+
+**配置方法**:
+
+编辑 `config/development.toml`,设置数据库类型为 `postgresql` 并修改连接信息:
+
+```toml
+[database]
+type = "postgresql"
+host = "localhost"
+port = 5432
+user = "postgres"
+password = "your-password"
+database = "web_template_dev"
+```
+
+或使用环境变量(格式与 MySQL 相同)。
+
+### 认证配置
+
+**开发环境**:使用默认配置即可(JWT 密钥已在配置文件中)
+
+**生产环境**:必须修改配置文件中的 JWT 密钥
+
+```toml
+[auth]
+# 生产环境必须使用强密钥
+jwt_secret = "Kx7Yn2Zp9qR8wF4tL6mN3vB5xC8zD1sE9aH2jK7"
+```
+
+生成强密钥:
+```bash
+openssl rand -base64 32
+```
+
+### Redis 配置
+
+**开发环境**:默认连接 `localhost:6379`,无需配置
+
+**生产环境**:修改配置文件或设置环境变量
+
+```bash
+REDIS_HOST=your-redis-host \
+REDIS_PORT=6379 \
+REDIS_PASSWORD=your-password \
+cargo run
+```
+
+---
+
+## 运行项目
+
+### 开发模式
+
+**使用默认配置(SQLite)**:
+```bash
+cargo run
+```
+
+**使用指定配置文件**:
+```bash
+# 修改 config/development.toml 后运行
+cargo run
+```
+
+**使用环境变量**:
+```bash
+DATABASE_TYPE=mysql DATABASE_HOST=localhost cargo run
+```
+
+### 指定环境
+
+```bash
+# 开发环境
+cargo run -- -e development
+
+# 生产环境
+cargo run -- -e production
+```
+
+### 后台运行(生产环境)
+
+```bash
+# 使用 nohup
+nohup cargo run -- -e production > app.log 2>&1 &
+
+# 使用 screen
+screen -S email-unlimit-server
+cargo run -- -e production
+# 按 Ctrl+A 然后 D 分离会话
+```
+
+---
+
+## 验证安装
+
+### 1. 健康检查
+
+```bash
+curl http://localhost:3000/health
+```
+
+预期响应:
+```json
+{
+ "status": "ok"
+}
+```
+
+### 2. 服务器信息
+
+```bash
+curl http://localhost:3000/info
+```
+
+预期响应:
+```json
+{
+ "name": "email-unlimit-server",
+ "version": "0.1.0",
+ "status": "running",
+ "timestamp": 1704112800
+}
+```
+
+### 3. 用户注册
+
+```bash
+curl -X POST http://localhost:3000/auth/register \
+ -H "Content-Type: application/json" \
+ -d '{
+ "email": "test@example.com",
+ "password": "password123"
+ }'
+```
+
+预期响应:
+```json
+{
+ "code": 200,
+ "message": "Success",
+ "data": {
+ "email": "test@example.com",
+ "created_at": "2026-02-13T12:00:00.000Z",
+ "access_token": "eyJhbGciOiJIUzI1NiIs...",
+ "refresh_token": "eyJhbGciOiJIUzI1NiIs..."
+ }
+}
+```
+
+### 4. 用户登录
+
+```bash
+curl -X POST http://localhost:3000/auth/login \
+ -H "Content-Type: application/json" \
+ -d '{
+ "email": "test@example.com",
+ "password": "password123"
+ }'
+```
+
+预期响应:
+```json
+{
+ "code": 200,
+ "message": "Success",
+ "data": {
+ "id": "1234567890",
+ "email": "test@example.com",
+ "created_at": "2026-02-13T12:00:00.000Z",
+ "access_token": "eyJhbGciOiJIUzI1NiIs...",
+ "refresh_token": "eyJhbGciOiJIUzI1NiIs..."
+ }
+}
+```
+
+---
+
+## 常见问题
+
+### 1. 端口被占用
+
+**错误信息**:`Os { code: 10048, kind: AddrInUse }` 或 `Address already in use`
+
+**解决方案**:
+
+**选项 1**:修改配置文件中的端口
+
+```toml
+[server]
+port = 3001
+```
+
+**选项 2**:通过环境变量覆盖
+
+```bash
+SERVER_PORT=3001 cargo run
+```
+
+**选项 3**:停止占用端口的进程
+
+```bash
+# Windows
+netstat -ano | findstr :3000
+taskkill /PID /F
+
+# macOS/Linux
+lsof -ti:3000 | xargs kill -9
+```
+
+### 2. 数据库连接失败
+
+**错误信息**:`Database connection failed`
+
+**解决方案**:
+
+- 检查数据库服务是否启动
+- 检查配置文件中的数据库配置是否正确
+- 确认数据库用户权限
+- SQLite:检查是否有写入权限
+
+### 3. Redis 连接失败
+
+**错误信息**:`Redis 连接失败`
+
+**解决方案**:
+
+- 检查 Redis 服务是否启动:`redis-cli ping`
+- 检查配置文件中的 Redis 配置是否正确
+- 如果不需要 Redis 功能,可以暂时禁用(需要修改代码)
+
+### 4. 编译错误
+
+**错误信息**:`error: linking with link.exe failed`
+
+**解决方案**:
+
+- Windows 用户需要安装 [C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/)
+- 或使用 `cargo install cargo-vcpkg` 安装依赖
+
+### 5. 权限错误
+
+**错误信息**:`Permission denied`
+
+**解决方案**:
+
+```bash
+# Linux/macOS
+chmod +x target/debug/email-unlimit-server
+
+# 或使用 sudo 运行(不推荐生产环境)
+sudo cargo run
+```
+
+---
+
+## 下一步
+
+安装成功后,你可以:
+
+1. 阅读 [API 接口文档](../api/api-overview.md) 了解所有可用的 API
+2. 查看 [项目结构详解](project-structure.md) 了解代码组织
+3. 学习 [DDD 架构规范](ddd-architecture.md) 了解设计原则
+4. 参考 [前端集成示例](../api/examples/frontend-integration.md) 集成前端应用
+
+---
+
+## 相关文档
+
+- [配置文件详解](../deployment/configuration.md) - 配置文件组织说明
+- [环境变量配置](../deployment/environment-variables.md) - 完整的环境变量列表
+- [API 接口文档](../api/api-overview.md) - 完整的 API 接口说明
+
+---
+
+**提示**:遇到问题?查看 [常见问题](#常见问题) 或提交 Issue 到项目仓库。
+
diff --git a/server/docs/development/project-structure.md b/server/docs/development/project-structure.md
new file mode 100644
index 0000000..6c73265
--- /dev/null
+++ b/server/docs/development/project-structure.md
@@ -0,0 +1,614 @@
+# 项目结构详解
+
+本文档详细说明 Email Unlimited 的项目结构、DDD 分层架构和各层职责。
+
+## 目录
+
+- [DDD 分层架构](#ddd-分层架构)
+- [项目目录结构](#项目目录结构)
+- [各层职责说明](#各层职责说明)
+- [数据流转](#数据流转)
+- [核心组件](#核心组件)
+
+---
+
+## DDD 分层架构
+
+本系统采用**领域驱动设计(DDD)**的分层架构,将代码划分为不同的职责层次。
+
+```
+┌─────────────────────────────────────────┐
+│ Interface Layer (handlers) │ HTTP 处理器层
+│ 路由定义、请求处理、响应封装 │
+└──────────────┬───────────────────────┘
+ │
+┌──────────────▼───────────────────────┐
+│ Application Layer (services) │ 业务逻辑层
+│ 业务逻辑、Token 生成、认证 │
+└──────────────┬───────────────────────┘
+ │
+ ┌───────┴────────┐
+ │ │
+┌──────▼──────┐ ┌─────▼──────────┐
+│ Domain │ │ Infrastructure│
+│ Layer │ │ Layer │
+│ │ │ │
+│ - DTO │ │ - Middleware │
+│ - Entities │ │ - Redis │
+│ - VO │ │ - Repositories│
+└─────────────┘ └────────────────┘
+```
+
+### 分层优势
+
+| 优势 | 说明 |
+|------|------|
+| 职责清晰 | 每层只关注自己的职责,降低耦合 |
+| 易于测试 | 每层可独立测试,Mock 依赖 |
+| 易于维护 | 修改某层不影响其他层 |
+| 易于扩展 | 添加新功能只需扩展相应层 |
+
+---
+
+## 项目目录结构
+
+```
+email-unlimit-server/
+├── src/ # 源代码目录
+│ ├── main.rs # 应用入口
+│ ├── cli.rs # 命令行参数解析
+│ ├── config.rs # 配置模块导出
+│ ├── db.rs # 数据库连接池
+│ ├── error.rs # 错误处理
+│ │
+│ ├── config/ # 配置模块
+│ │ ├── app.rs # 主配置结构
+│ │ ├── auth.rs # 认证配置
+│ │ ├── database.rs # 数据库配置
+│ │ ├── redis.rs # Redis 配置
+│ │ └── server.rs # 服务器配置
+│ │
+│ ├── domain/ # 领域层(DDD)
+│ │ ├── dto/ # 数据传输对象(Data Transfer Object)
+│ │ │ └── auth.rs # 认证相关 DTO
+│ │ ├── entities/ # 实体(数据库模型)
+│ │ │ └── users.rs # 用户实体
+│ │ └── vo/ # 视图对象(View Object)
+│ │ └── auth.rs # 认证相关 VO
+│ │
+│ ├── handlers/ # HTTP 处理器层(接口层)
+│ │ ├── auth.rs # 认证接口
+│ │ └── health.rs # 健康检查接口
+│ │
+│ ├── infra/ # 基础设施层
+│ │ ├── middleware/ # 中间件
+│ │ │ ├── auth.rs # JWT 认证中间件
+│ │ │ └── logging.rs # 日志中间件
+│ │ └── redis/ # Redis 客户端封装
+│ │ ├── redis_client.rs
+│ │ └── redis_key.rs
+│ │
+│ ├── repositories/ # 数据访问层
+│ │ └── user_repository.rs # 用户数据访问
+│ │
+│ ├── services/ # 业务逻辑层
+│ │ └── auth_service.rs # 认证业务逻辑
+│ │
+│ └── utils/ # 工具函数
+│ └── jwt.rs # JWT 工具类
+│
+├── config/ # 配置文件目录
+│ ├── default.toml # 默认配置
+│ ├── development.toml # 开发环境配置(支持 MySQL/PostgreSQL/SQLite)
+│ └── production.toml # 生产环境配置
+│
+├── sql/ # SQL 脚本
+│ └── init.sql # 数据库初始化脚本
+│
+├── tests/ # 测试目录
+│ └── integration_test.rs # 集成测试
+│
+├── docs/ # 文档目录
+│ ├── README.md
+│ ├── api/
+│ ├── development/
+│ └── deployment/
+│
+├── .env.example # 环境变量参考(仅用于 Docker/Kubernetes 等部署场景)
+├── .gitignore # Git 忽略文件
+├── Cargo.toml # 项目依赖定义
+├── README.md # 项目说明
+└── rust-toolchain.toml # Rust 工具链配置
+```
+
+---
+
+## 各层职责说明
+
+### 1. 接口层(handlers/)
+
+**职责**:处理 HTTP 请求和响应
+
+**位置**:`src/handlers/`
+
+**关键文件**:
+- `auth.rs`:认证相关接口(注册、登录、刷新 Token、删除账号)
+- `health.rs`:健康检查和服务器信息接口
+
+**示例**:
+```rust
+// src/handlers/auth.rs
+
+pub async fn register(
+ Extension(request_id): Extension,
+ State(state): State,
+ Json(payload): Json,
+) -> Result>, ErrorResponse> {
+ // 1. 记录日志
+ log_info(&request_id, "注册请求参数", &payload);
+
+ // 2. 调用服务层处理业务逻辑
+ let user_repo = UserRepository::new(state.pool.clone());
+ let service = AuthService::new(user_repo, state.redis_client.clone(), state.config.auth.clone());
+
+ // 3. 调用业务逻辑
+ match service.register(payload).await {
+ Ok((user_model, access_token, refresh_token)) => {
+ let data = RegisterResult::from((user_model, access_token, refresh_token));
+ let response = ApiResponse::success(data);
+ log_info(&request_id, "注册成功", &response);
+ Ok(Json(response))
+ }
+ Err(e) => {
+ log_info(&request_id, "注册失败", &e.to_string());
+ Err(ErrorResponse::new(e.to_string()))
+ }
+ }
+}
+```
+
+**职责边界**:
+- ✅ 接收 HTTP 请求
+- ✅ 提取请求参数
+- ✅ 调用服务层处理业务逻辑
+- ✅ 封装响应数据
+- ❌ 不包含业务逻辑
+- ❌ 不直接访问数据库
+
+### 2. 业务逻辑层(services/)
+
+**职责**:实现核心业务逻辑
+
+**位置**:`src/services/`
+
+**关键文件**:
+- `auth_service.rs`:认证业务逻辑(注册、登录、Token 刷新、密码哈希)
+
+**示例**:
+```rust
+// src/services/auth_service.rs
+
+pub struct AuthService {
+ user_repo: UserRepository,
+ redis_client: RedisClient,
+ auth_config: AuthConfig,
+}
+
+impl AuthService {
+ /// 用户注册
+ pub async fn register(&self, payload: RegisterRequest) -> Result<(Model, String, String)> {
+ // 1. 验证邮箱格式
+ if !payload.email.contains('@') {
+ return Err(anyhow!("邮箱格式错误"));
+ }
+
+ // 2. 生成唯一用户 ID
+ let user_id = self.generate_unique_user_id().await?;
+
+ // 3. 哈希密码
+ let password_hash = self.hash_password(&payload.password)?;
+
+ // 4. 创建用户实体
+ let user_model = users::Model {
+ id: user_id,
+ email: payload.email.clone(),
+ password_hash,
+ created_at: chrono::Utc::now().naive_utc(),
+ updated_at: chrono::Utc::now().naive_utc(),
+ };
+
+ // 5. 保存到数据库
+ let created_user = self.user_repo.create(user_model).await?;
+
+ // 6. 生成 Token
+ let (access_token, refresh_token) = TokenService::generate_token_pair(
+ &created_user.id,
+ self.auth_config.access_token_expiration_minutes,
+ self.auth_config.refresh_token_expiration_days,
+ &self.auth_config.jwt_secret,
+ )?;
+
+ // 7. 保存 Refresh Token 到 Redis
+ self.save_refresh_token(&created_user.id, &refresh_token, self.auth_config.refresh_token_expiration_days).await?;
+
+ Ok((created_user, access_token, refresh_token))
+ }
+}
+```
+
+**职责边界**:
+- ✅ 实现业务逻辑
+- ✅ 协调 Repository 和基础设施
+- ✅ 事务管理
+- ❌ 不处理 HTTP 请求/响应
+- ❌ 不直接访问外部资源(通过 Repository)
+
+### 3. 数据访问层(repositories/)
+
+**职责**:封装数据库访问逻辑
+
+**位置**:`src/repositories/`
+
+**关键文件**:
+- `user_repository.rs`:用户数据访问(增删改查)
+
+**示例**:
+```rust
+// src/repositories/user_repository.rs
+
+pub struct UserRepository {
+ pool: DbPool,
+}
+
+impl UserRepository {
+ pub fn new(pool: DbPool) -> Self {
+ Self { pool }
+ }
+
+ /// 创建用户
+ pub async fn create(&self, user_model: users::Model) -> Result {
+ let result = users::Entity::insert(user_model.into_active_model())
+ .exec(&self.pool)
+ .await
+ .map_err(|e| anyhow!("创建用户失败: {}", e))?;
+
+ Ok(users::Entity::find_by_id(result.last_insert_id))
+ .one(&self.pool)
+ .await
+ .map_err(|e| anyhow!("查询用户失败: {}", e))?
+ .ok_or_else(|| anyhow!("用户不存在"))
+ }
+
+ /// 根据邮箱查询用户
+ pub async fn find_by_email(&self, email: &str) -> Result