COSCUP Newsletter — 電子報訂閱管理系統(Rust 全新重寫),取代舊版 Python/Flask + MongoDB 系統。
- Language: Rust
- Web Framework: Axum 0.8
- Template Engine: Tera (SSR)
- Database: PostgreSQL (sqlx, auto-migration on startup)
- Email: SMTP via
lettre(相容 AWS SES SMTP、Mailgun 等任何 SMTP 服務) - Captcha: Cloudflare Turnstile
- Deployment: Docker Compose
# 完整驗證(每次改動必須通過)
cargo fmt --check && cargo clippy -- -D warnings && cargo test
# 本地開發環境(PostgreSQL + MailHog)
docker compose -f docker-compose.dev.yml up -d
# 啟動服務
cargo run- 零 warning 政策:
cargo clippy -- -D warnings必須通過,所有 clippy lint 視為 error - 格式化:
cargo fmt --check必須通過 - 測試:
cargo test所有測試必須通過 - unsafe 禁止:
unsafe_code = "forbid" - Clippy pedantic: 已啟用,部分過嚴的 lint 已在
Cargo.toml中放寬(module_name_repetitions、missing_errors_doc、missing_panics_doc)
- TDD (Test-Driven Development): 遵循 Red-Green-Refactor 流程
- Red — 先寫失敗的測試,定義預期行為
- Green — 寫最少的程式碼讓測試通過
- Refactor — 重構,確保測試仍通過
- 每個功能模組在
#[cfg(test)] mod tests中寫單元測試 - 外部依賴(email、captcha)透過 trait 抽象,測試中用 mock 實作
- 每次改動後執行完整驗證:
cargo fmt --check && cargo clippy -- -D warnings && cargo test
- Error Handling: 自定義
AppErrorenum 實作IntoResponse,統一錯誤回傳,隱藏內部細節 - State:
AppState包含 DB pool、config、tera、email service、captcha verifier,透過 AxumState注入 - Trait 抽象:
EmailService和CaptchaVerifier為 trait,方便測試中用 mock 實作 - Security: SHA256 for admin_link, HMAC-SHA256 for openhash,
subtlecrate for constant-time comparison - Legacy 相容: 舊使用者的
admin_link保留在legacy_admin_link欄位,驗證時優先比對
src/
├── main.rs # 啟動、路由註冊、graceful shutdown
├── config.rs # 環境變數讀取
├── error.rs # 統一錯誤處理
├── db.rs # PostgreSQL 連線池 + migration
├── security.rs # 雜湊、HMAC、token 產生/驗證
├── email.rs # SMTP 發信(trait 抽象)
├── captcha.rs # Cloudflare Turnstile 驗證(trait 抽象)
├── csv_handler.rs # CSV 匯入/匯出
├── routes/ # 路由 handlers
└── templates/ # Tera HTML 模板
- 新增套件前,必須確認其 License 與本專案 AGPL-3.0 相容
- 相容的 License:MIT、Apache-2.0、BSD-2-Clause、BSD-3-Clause、ISC、Zlib、MPL-2.0
- 不相容的 License:GPL-2.0-only(非 "or later")、proprietary、SSPL
- 可用
cargo license或查看 crate 的 crates.io 頁面確認
- Axum 0.8 路徑參數用
{param}語法(非:param) - Tera strict mode:模板中所有變數必須在 Context 中定義,或用
| default(value="")防禦 axum-extra需要cookiefeature 才能用 session cookie- Cookie
max_age需要timecrate(非std::time) - Axum 需要
multipartfeature 才能用Multipartextractor
AGPL-3.0,與原專案 COSCUP/subscribe 保持一致。