From 3bd555f3637aecdf1b8cb112b66bb79679d8a160 Mon Sep 17 00:00:00 2001 From: MapleLeaf <197728340@qq.com> Date: Sun, 23 Feb 2025 10:17:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docs.yml | 66 ++++++++++++ .gitignore | 4 + docs/docs/.vitepress/config.mts | 73 ++++++++++++++ docs/docs/.vitepress/theme/index.ts | 17 ++++ docs/docs/.vitepress/theme/style.css | 139 +++++++++++++++++++++++++ docs/docs/AuthoritySystem.md | 54 ++++++++++ docs/docs/Donate.md | 10 ++ docs/docs/FAQ.md | 12 +++ docs/docs/LocalDevelopment.md | 62 ++++++++++++ docs/docs/api-examples.md | 49 +++++++++ docs/docs/api.md | 129 ++++++++++++++++++++++++ docs/docs/index.md | 46 +++++++++ docs/docs/markdown-examples.md | 85 ++++++++++++++++ docs/docs/moemail.png | Bin 0 -> 6773 bytes docs/docs/public/favicon.ico | Bin 0 -> 7870 bytes docs/docs/start.md | 145 +++++++++++++++++++++++++++ docs/package.json | 7 ++ package.json | 1 + 18 files changed, 899 insertions(+) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/docs/.vitepress/config.mts create mode 100644 docs/docs/.vitepress/theme/index.ts create mode 100644 docs/docs/.vitepress/theme/style.css create mode 100644 docs/docs/AuthoritySystem.md create mode 100644 docs/docs/Donate.md create mode 100644 docs/docs/FAQ.md create mode 100644 docs/docs/LocalDevelopment.md create mode 100644 docs/docs/api-examples.md create mode 100644 docs/docs/api.md create mode 100644 docs/docs/index.md create mode 100644 docs/docs/markdown-examples.md create mode 100644 docs/docs/moemail.png create mode 100644 docs/docs/public/favicon.ico create mode 100644 docs/docs/start.md create mode 100644 docs/package.json diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..adef045 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,66 @@ +# 构建 VitePress 站点并将其部署到 GitHub Pages 的示例工作流程 +# +name: Deploy VitePress site to Pages + +on: + # 在针对 `main` 分支的推送上运行。如果你 + # 使用 `master` 分支作为默认分支,请将其更改为 `master` + push: + branches: [docs] + + # 允许你从 Actions 选项卡手动运行此工作流程 + workflow_dispatch: + +# 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# 只允许同时进行一次部署,跳过正在运行和最新队列之间的运行队列 +# 但是,不要取消正在进行的运行,因为我们希望允许这些生产部署完成 +concurrency: + group: pages + cancel-in-progress: false + +jobs: + # 构建工作 + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # 如果未启用 lastUpdated,则不需要 + # - uses: pnpm/action-setup@v3 # 如果使用 pnpm,请取消此区域注释 + # with: + # version: 9 + # - uses: oven-sh/setup-bun@v1 # 如果使用 Bun,请取消注释 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm # 或 pnpm / yarn + - name: Setup Pages + uses: actions/configure-pages@v4 + - name: Install dependencies + run: npm ci # 或 pnpm install / yarn install / bun install + - name: Build with VitePress + run: npm run docs:build # 或 pnpm docs:build / yarn docs:build / bun run docs:build + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/.vitepress/dist + + # 部署工作 + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + runs-on: ubuntu-latest + name: Deploy + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.gitignore b/.gitignore index f19465b..ee4c534 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +# docs +docs/docs/.vitepress/cache +docs/docs/.vitepress/dist + # dependencies /node_modules /.pnp diff --git a/docs/docs/.vitepress/config.mts b/docs/docs/.vitepress/config.mts new file mode 100644 index 0000000..959fcfe --- /dev/null +++ b/docs/docs/.vitepress/config.mts @@ -0,0 +1,73 @@ +import { defineConfig } from 'vitepress' + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + title: "MoeMail", + description: "一个基于 NextJS + Cloudflare 技术栈构建的可爱临时邮箱服务🎉", + head: [ + ['link', { rel: 'icon', href: '/favicon.ico' }] + ], + ignoreDeadLinks: true, + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + nav: [ + { text: '首页', link: '/' }, + { text: '快速开始', link: '/start' }, + { text: '开发', link: '/LocalDevelopment' }, + { text: 'API', link: '/api' }, + { text: '其他', link: '/FAQ' } + ], + outlineTitle: "本页目录", + logo: "/moemail.png", + search: { + provider: 'local' + }, + sidebar: [ + { + text: '使用', + items: [ + { + text: '快速开始', + link: '/start', + items: [ + { text: '准备', link: '/start#准备' }, + { text: 'Github Actions', link: '/start#github-actions-部署' }, + { text: '环境变量', link: '/start#环境变量' }, + { text: '邮件路由配置', link: '/start#cloudflare-邮件路由配置' } + ] + }, + { text: '权限系统', link: '/AuthoritySystem' } + ] + }, + { + text: '开发', + items: [ + { text: '本地开发', link: '/LocalDevelopment' }, + { + text: 'API', + link: '/api', + items: [ + { text: 'WebHook', link: '/api#webhook-集成' }, + { text: 'OpenAPI', link: '/api#openapi' } + ] + } + ] + }, + { + text: '其他', + items: [ + { text: 'FAQ', link: '/FAQ' }, + { text: '捐赠', link: '/Donate' } + ] + } + ], + + socialLinks: [ + { icon: 'github', link: 'https://github.com/beilunyang/moemail' } + ], + footer: { + message: "Released under the MIT License", + copyright: "Copyright © 2024-2025 MoeMail", + } + } +}) diff --git a/docs/docs/.vitepress/theme/index.ts b/docs/docs/.vitepress/theme/index.ts new file mode 100644 index 0000000..def4cfc --- /dev/null +++ b/docs/docs/.vitepress/theme/index.ts @@ -0,0 +1,17 @@ +// https://vitepress.dev/guide/custom-theme +import { h } from 'vue' +import type { Theme } from 'vitepress' +import DefaultTheme from 'vitepress/theme' +import './style.css' + +export default { + extends: DefaultTheme, + Layout: () => { + return h(DefaultTheme.Layout, null, { + // https://vitepress.dev/guide/extending-default-theme#layout-slots + }) + }, + enhanceApp({ app, router, siteData }) { + // ... + } +} satisfies Theme diff --git a/docs/docs/.vitepress/theme/style.css b/docs/docs/.vitepress/theme/style.css new file mode 100644 index 0000000..1a61cb1 --- /dev/null +++ b/docs/docs/.vitepress/theme/style.css @@ -0,0 +1,139 @@ +/** + * Customize default theme styling by overriding CSS variables: + * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css + */ + +/** + * Colors + * + * Each colors have exact same color scale system with 3 levels of solid + * colors with different brightness, and 1 soft color. + * + * - `XXX-1`: The most solid color used mainly for colored text. It must + * satisfy the contrast ratio against when used on top of `XXX-soft`. + * + * - `XXX-2`: The color used mainly for hover state of the button. + * + * - `XXX-3`: The color for solid background, such as bg color of the button. + * It must satisfy the contrast ratio with pure white (#ffffff) text on + * top of it. + * + * - `XXX-soft`: The color used for subtle background such as custom container + * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors + * on top of it. + * + * The soft color must be semi transparent alpha channel. This is crucial + * because it allows adding multiple "soft" colors on top of each other + * to create a accent, such as when having inline code block inside + * custom containers. + * + * - `default`: The color used purely for subtle indication without any + * special meanings attached to it such as bg color for menu hover state. + * + * - `brand`: Used for primary brand colors, such as link text, button with + * brand theme, etc. + * + * - `tip`: Used to indicate useful information. The default theme uses the + * brand color for this by default. + * + * - `warning`: Used to indicate warning to the users. Used in custom + * container, badges, etc. + * + * - `danger`: Used to show error, or dangerous message to the users. Used + * in custom container, badges, etc. + * -------------------------------------------------------------------------- */ + +:root { + --vp-c-default-1: var(--vp-c-gray-1); + --vp-c-default-2: var(--vp-c-gray-2); + --vp-c-default-3: var(--vp-c-gray-3); + --vp-c-default-soft: var(--vp-c-gray-soft); + + --vp-c-brand-1: var(--vp-c-indigo-1); + --vp-c-brand-2: var(--vp-c-indigo-2); + --vp-c-brand-3: var(--vp-c-indigo-3); + --vp-c-brand-soft: var(--vp-c-indigo-soft); + + --vp-c-tip-1: var(--vp-c-brand-1); + --vp-c-tip-2: var(--vp-c-brand-2); + --vp-c-tip-3: var(--vp-c-brand-3); + --vp-c-tip-soft: var(--vp-c-brand-soft); + + --vp-c-warning-1: var(--vp-c-yellow-1); + --vp-c-warning-2: var(--vp-c-yellow-2); + --vp-c-warning-3: var(--vp-c-yellow-3); + --vp-c-warning-soft: var(--vp-c-yellow-soft); + + --vp-c-danger-1: var(--vp-c-red-1); + --vp-c-danger-2: var(--vp-c-red-2); + --vp-c-danger-3: var(--vp-c-red-3); + --vp-c-danger-soft: var(--vp-c-red-soft); +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +:root { + --vp-button-brand-border: transparent; + --vp-button-brand-text: var(--vp-c-white); + --vp-button-brand-bg: var(--vp-c-brand-3); + --vp-button-brand-hover-border: transparent; + --vp-button-brand-hover-text: var(--vp-c-white); + --vp-button-brand-hover-bg: var(--vp-c-brand-2); + --vp-button-brand-active-border: transparent; + --vp-button-brand-active-text: var(--vp-c-white); + --vp-button-brand-active-bg: var(--vp-c-brand-1); +} + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #bd34fe 30%, + #41d1ff + ); + + --vp-home-hero-image-background-image: linear-gradient( + -45deg, + #bd34fe 50%, + #47caff 50% + ); + --vp-home-hero-image-filter: blur(44px); +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(68px); + } +} + +/** + * Component: Custom Block + * -------------------------------------------------------------------------- */ + +:root { + --vp-custom-block-tip-border: transparent; + --vp-custom-block-tip-text: var(--vp-c-text-1); + --vp-custom-block-tip-bg: var(--vp-c-brand-soft); + --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); +} + +/** + * Component: Algolia + * -------------------------------------------------------------------------- */ + +.DocSearch { + --docsearch-primary-color: var(--vp-c-brand-1) !important; +} + diff --git a/docs/docs/AuthoritySystem.md b/docs/docs/AuthoritySystem.md new file mode 100644 index 0000000..729d0b8 --- /dev/null +++ b/docs/docs/AuthoritySystem.md @@ -0,0 +1,54 @@ +::: tip +本项目采用基于角色的权限控制系统(RBAC),用户分为皇帝、公爵、骑士和平民四种角色,每个角色具有不同的权限。 +::: + +## 角色配置 + +新用户默认角色由皇帝在个人中心的网站设置中配置: +- 公爵:新用户将获得临时邮箱、Webhook 配置权限以及 API Key 管理权限 +- 骑士:新用户将获得临时邮箱和 Webhook 配置权限 +- 平民:新用户无任何权限,需要等待皇帝册封为骑士或公爵 + +## 角色等级 + +系统包含四个角色等级: + +1. **皇帝(Emperor)** + - 网站所有者 + - 拥有所有权限 + - 每个站点只能有一个皇帝 + +2. **公爵(Duke)** + - 超级用户 + - 可以使用临时邮箱功能 + - 可以配置 Webhook + - 可以使用创建 API Key 调用 OpenAPI + - 可以被皇帝贬为骑士或平民 + +3. **骑士(Knight)** + - 高级用户 + - 可以使用临时邮箱功能 + - 可以配置 Webhook + - 可以被皇帝贬为平民或册封为公爵 + +3. **平民(Civilian)** + - 普通用户 + - 无任何权限 + - 可以被皇帝册封为骑士或者公爵 + +## 角色升级 + +1. **成为皇帝** + - 第一个访问 `/api/roles/init-emperor` 接口的用户将成为皇帝,即网站所有者 + - 站点已有皇帝后,无法再提升其他用户为皇帝 + +2. **角色变更** + - 皇帝可以在个人中心页面将其他用户设为公爵、骑士或平民 + +## 权限说明 + +- **邮箱管理**:创建和管理临时邮箱 +- **Webhook 管理**:配置邮件通知的 Webhook +- **API Key 管理**:创建和管理 API 访问密钥 +- **用户管理**:升降用户角色 +- **系统配置**:管理系统全局设置 diff --git a/docs/docs/Donate.md b/docs/docs/Donate.md new file mode 100644 index 0000000..3c558b8 --- /dev/null +++ b/docs/docs/Donate.md @@ -0,0 +1,10 @@ +# 支持 + +如果你喜欢这个项目,欢迎给它一个 Star ⭐️ +或者进行赞助 +
+
+ +
+
+Buy Me A Coffee \ No newline at end of file diff --git a/docs/docs/FAQ.md b/docs/docs/FAQ.md new file mode 100644 index 0000000..7010714 --- /dev/null +++ b/docs/docs/FAQ.md @@ -0,0 +1,12 @@ +# 贡献 + +欢迎提交 Pull Request 或者 Issue来帮助改进这个项目 + +# 交流群 + +
+如二维码失效,请添加我的个人微信(hansenones),并备注 "MoeMail" 加入微信交流群 + +# 许可证 + +本项目采用 [MIT](LICENSE) 许可证 \ No newline at end of file diff --git a/docs/docs/LocalDevelopment.md b/docs/docs/LocalDevelopment.md new file mode 100644 index 0000000..4bd5c3a --- /dev/null +++ b/docs/docs/LocalDevelopment.md @@ -0,0 +1,62 @@ +### 前置要求 + +- Node.js 18+ +- Pnpm +- Wrangler CLI +- Cloudflare 账号 + +### 安装 + +1. 克隆仓库: +```bash +git clone https://github.com/beilunyang/moemail.git +cd moemail +``` + +2. 安装依赖: +```bash +pnpm install +``` + +3. 设置 wrangler: +```bash +cp wrangler.example.toml wrangler.toml +cp wrangler.email.example.toml wrangler.email.toml +cp wrangler.cleanup.example.toml wrangler.cleanup.toml +``` +设置 Cloudflare D1 数据库名以及数据库 ID + +4. 设置环境变量: +```bash +cp .env.example .env.local +``` +设置 AUTH_GITHUB_ID, AUTH_GITHUB_SECRET, AUTH_SECRET + +5. 创建本地数据库表结构 +```bash +pnpm db:migrate-local +``` + +### 开发 + +1. 启动开发服务器: +```bash +pnpm dev +``` + +2. 测试邮件 worker: +目前无法本地运行并测试,请使用 wrangler 部署邮件 worker 并测试 +```bash +pnpm deploy:email +``` + +3. 测试清理 worker: +```bash +pnpm dev:cleanup +pnpm test:cleanup +``` + +4. 生成 Mock 数据(邮箱以及邮件消息) +```bash +pnpm generate-test-data +``` \ No newline at end of file diff --git a/docs/docs/api-examples.md b/docs/docs/api-examples.md new file mode 100644 index 0000000..6bd8bb5 --- /dev/null +++ b/docs/docs/api-examples.md @@ -0,0 +1,49 @@ +--- +outline: deep +--- + +# Runtime API Examples + +This page demonstrates usage of some of the runtime APIs provided by VitePress. + +The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files: + +```md + + +## Results + +### Theme Data +
{{ theme }}
+ +### Page Data +
{{ page }}
+ +### Page Frontmatter +
{{ frontmatter }}
+``` + + + +## Results + +### Theme Data +
{{ theme }}
+ +### Page Data +
{{ page }}
+ +### Page Frontmatter +
{{ frontmatter }}
+ +## More + +Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata). diff --git a/docs/docs/api.md b/docs/docs/api.md new file mode 100644 index 0000000..573bae8 --- /dev/null +++ b/docs/docs/api.md @@ -0,0 +1,129 @@ +## Webhook 集成 + +当收到新邮件时,系统会向用户配置并且已启用的 Webhook URL 发送 POST 请求。 + +### 请求头 + +```http +Content-Type: application/json +X-Webhook-Event: new_message +``` + +### 请求体 + +```json +{ + "emailId": "email-uuid", + "messageId": "message-uuid", + "fromAddress": "sender@example.com", + "subject": "邮件主题", + "content": "邮件文本内容", + "html": "邮件HTML内容", + "receivedAt": "2024-01-01T12:00:00.000Z", + "toAddress": "your-email@moemail.app" +} +``` + +### 配置说明 + +1. 点击个人头像,进入个人中心 +2. 在个人中心启用 Webhook +3. 设置接收通知的 URL +4. 点击测试按钮验证配置 +5. 保存配置后即可接收新邮件通知 + +### 测试 + +项目提供了一个简单的测试服务器, 可以通过如下命令运行: + +```bash +pnpm webhook-test-server +``` + +测试服务器会在本地启动一个 HTTP 服务器,监听 3001 端口(http://localhost:3001), 并打印收到的 Webhook 消息详情。 + +如果需要进行外网测试,可以通过 Cloudflare Tunnel 将服务暴露到外网: +```bash +pnpx cloudflared tunnel --url http://localhost:3001 +``` + +### 注意事项 +- Webhook 接口应在 10 秒内响应 +- 非 2xx 响应码会触发重试 + +## OpenAPI + +本项目提供了 OpenAPI 接口,支持通过 API Key 进行访问。API Key 可以在个人中心页面创建(需要是公爵或皇帝角色)。 + +### 使用 API Key + +在请求头中添加 API Key: +```http +X-API-Key: YOUR_API_KEY +``` + +### API 接口 + +#### 创建临时邮箱 +```http +POST /api/emails/generate +Content-Type: application/json + +{ + "name": "test", + "expiryTime": 3600000, + "domain": "moemail.app" +} +``` +参数说明: +- `name`: 邮箱前缀,可选 +- `expiryTime`: 有效期(毫秒),可选值:3600000(1小时)、86400000(1天)、604800000(7天)、0(永久) +- `domain`: 邮箱域名,可通过 `/api/emails/domains` 获取可用域名列表 + +#### 获取邮箱列表 +```http +GET /api/emails?cursor=xxx +``` +参数说明: +- `cursor`: 分页游标,可选 + +#### 获取指定邮箱邮件列表 +```http +GET /api/emails/{emailId}?cursor=xxx +``` +参数说明: +- `cursor`: 分页游标,可选 + +#### 删除邮箱 +```http +DELETE /api/emails/{emailId} +``` + +#### 获取单封邮件内容 +```http +GET /api/emails/{emailId}/{messageId} +``` + +### 使用示例 + +使用 curl 创建临时邮箱: +```bash +curl -X POST https://your-domain.com/api/emails/generate \ + -H "X-API-Key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "test", + "expiryTime": 3600000, + "domain": "moemail.app" + }' +``` + +使用 JavaScript 获取邮件列表: +```javascript +const res = await fetch('https://your-domain.com/api/emails/your-email-id', { + headers: { + 'X-API-Key': 'YOUR_API_KEY' + } +}); +const data = await res.json(); +``` \ No newline at end of file diff --git a/docs/docs/index.md b/docs/docs/index.md new file mode 100644 index 0000000..c4258df --- /dev/null +++ b/docs/docs/index.md @@ -0,0 +1,46 @@ +--- +layout: home + +hero: + name: "MoeMail" + text: "萌萌哒临时邮箱服务" + tagline: 一个基于NextJS+Cloudflare技术栈构建的可爱临时邮箱服务🎉 + actions: + - theme: brand + text: 快速开始 + link: /start + - theme: alt + text: GitHub + link: https://github.com/beilunyang/moemail + - theme: alt + text: 在线演示 + link: https://moemail.app + image: + src: /moemail.png + alt: MoeMail + +features: + - title: 🔒 隐私保护 + details: 保护您的真实邮箱地址,远离垃圾邮件和不必要的订阅 + - title: ⚡ 实时收件 + details: 自动轮询,即时接收邮件通知 + - title: 🎨 主题切换 + details: 支持亮色和暗色模式 + - title: 📱 响应式设计 + details: 完美适配桌面和移动设备 + - title: 🔄 自动清理 + details: 自动清理过期的邮箱和邮件 + - title: 📱 PWA 支持 + details: 支持 PWA 安装 + - title: 💸 免费自部署 + details: 基于 Cloudflare 构建, 可实现免费自部署,无需任何费用 + - title: 🎉 可爱的 UI + details: 简洁可爱萌萌哒 UI 界面 + - title: 🔔 Webhook 通知 + details: 支持通过 webhook 接收新邮件通知 + - title: 🛡️ 权限系统 + details: 支持基于角色的权限控制系统 + - title: 🔑 OpenAPI + details: 支持通过 API Key 访问 OpenAPI +--- + diff --git a/docs/docs/markdown-examples.md b/docs/docs/markdown-examples.md new file mode 100644 index 0000000..f9258a5 --- /dev/null +++ b/docs/docs/markdown-examples.md @@ -0,0 +1,85 @@ +# Markdown Extension Examples + +This page demonstrates some of the built-in markdown extensions provided by VitePress. + +## Syntax Highlighting + +VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting: + +**Input** + +````md +```js{4} +export default { + data () { + return { + msg: 'Highlighted!' + } + } +} +``` +```` + +**Output** + +```js{4} +export default { + data () { + return { + msg: 'Highlighted!' + } + } +} +``` + +## Custom Containers + +**Input** + +```md +::: info +This is an info box. +::: + +::: tip +This is a tip. +::: + +::: warning +This is a warning. +::: + +::: danger +This is a dangerous warning. +::: + +::: details +This is a details block. +::: +``` + +**Output** + +::: info +This is an info box. +::: + +::: tip +This is a tip. +::: + +::: warning +This is a warning. +::: + +::: danger +This is a dangerous warning. +::: + +::: details +This is a details block. +::: + +## More + +Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown). diff --git a/docs/docs/moemail.png b/docs/docs/moemail.png new file mode 100644 index 0000000000000000000000000000000000000000..4045a5e2b13beb925acb14758443cedcf05d5d2f GIT binary patch literal 6773 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 V3OOoG#D63JNJ&evTHY$`{{d(`CW!z5 literal 0 HcmV?d00001 diff --git a/docs/docs/public/favicon.ico b/docs/docs/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..115e6f54ab8daaf51b19eeb3764d36d218283955 GIT binary patch literal 7870 zcmV;v9zo%WP)Py8cu7P-RCr$PT?cqnW!64-%AJ;()JZ0jLPF?4x(cXBEPyCR5n=-oaCO&JSJd5q z|NYlr{M~(a*A-A)*HvkPQlcP;L=ZKC$g&bT2_+$QO4_8%WctjV|2a3@DXCL#k_pUt z9v>ev_kQ2`&Ux=w&UXp{*9f^>F5U2<}91cou zw^CA@#U-@aC>})tIP4UG!{_liE)EBPBsmUJNLcs+j)BMHG;n!v(PnlX)3-X#CCCLP zNvu<^RK#ef&6YJ21mU_CL`Mr?Fw=KC8z!~1I`Xs{%Y?ROhmyl16cpu(w%aJuMX@t7 z1P71g*f<<$bvmF{EaucFr3fy?$~ik`Kcqa&PA>z~%Af*3ett1Y>7^qY)sAFYEPqLB zi~Zi_27AoFh_1a?Sq$G89mo5=P49d!d5BPzF;;PT$`ryrkURy__n-hkabbN_Nu~Xj z>QeJer^A(Pw^H#=3c`waE{AaOg`7q%7fLdQiwff;{8c%5iEV)>V<4UlD z2r@^AAvHq?5*d$HzEH#k{EZEA*j->TJHc$Cz^HdXO@#$2FPlJBVFrhzXE5*u91F=O z&JP_e`79<)u{I}%u&}`sTWzo-Ar`pV^?q4mlaW8$W})8H)!P)VZsdwejfvw!Vu}Dp zjf;Y;(GvQr9|*!(q3nVYE}d52#fAU=`Q3I&|s#Uu&7ng4{Mejs@6 z*yn%)prD{s(5e=_Ty@#}xYOnu>7-oTt^^~NaAEw^Xc#$8LQm$TP? zT^-p-AqNtZ1u%B96vp2y15z+BUwWVlvRNrOU!;e#KO3M6=LZA;h5PCirRPoSb#?Zcc01K+bI0Kjw9+Tt5)Da80U!_tBUvpJ z4S9aNW_=o3Xp5=(ha@_62FhNp3MPR~m-_tyaX z4FG$#RV%AX9se@6I_|OCT)d70%p56(nR62$IZXguUca%`Uvj4V!4pHR$^wVKZh|Wp zjGg-ClY~Poh4}t*PFo>^La7f`n;z#=-cT zWMQ+Z>kq+~oHD@SuXWH++j$Qdn?UNKWUwG(LdKDt9Kz9;{PZ~hK7Fs|wVDdc%Qh<| z^&SWw=ceYw!j#)$KpNd?Lg>pehfUjt76+U>+6w>vt_kcNj*lcs7*aEZ?<{&Q{nfr? zxX%Ev^#e_UrOm!hRb|Nque}d_9!?@**8D`s8r7?DuP?_Q9&NjTUJefKZiL1$tO@F*_clG1uo;-<=ZC z+>A>C&IsPbA;1+K&pS2tMnP`Q+*B3YAYdH;+t#TkYAZ~ix9O>&-pryQXXbqgF!`2f zw#RTywRhr33;a-^qwgfVNh;%&rDY297Ce=5ip>GD3V?k(G&71%7&lui)DZ7!KR%I! z`xhnAM>{;@N2q%VpreYW+5&sF)Y6wx-XxT8t0&wP{lu*K@X+PSOLIS z+cdXbIBnX|QTaFAD1*Bnkki+&5ei%aZF&bB+}!}@i+pdA5d`CIl07o}p~R5AkwON* z=j+rH%S%nWEhZ|R86xiVM^BK#y^kaf*iBvF;ttO@(473$_Byz5%HVTC5Q;g~8ClV} zc~2*v4u$iO0D!ywv&UKv8T8I9Z{^=OBL-&Pn*f4AYQqmjpndT!f}n6;BNQEO@qmB; zkV<)F**D2Eb z4oXh?E(bAk{)yBf!A3A2Lv1rK`Dm__bhuUeZC(}bb2IU02CIwc$yN`|6I9bTIqe%!`=SE<;uv0FBmNZ z@D6SaKU=S*-O=1c0Avl9ywOJ1zVXa6Qv&ix4j2F%*VN3cQ(M0Dw(a9N|FK`CM2>m^ zC^D2sTXc5#^q*=UI{_)+7!;|3`<{F;J)o08Kmgdkvmv>xSbwIe!69b`fL^u_Kb=C~ z<436fB%rRc!1fQ+J{H5cM6z~R1~F;Q;tZ|7ocIlZqeoq$qJ!l-S{fbmyc?gJmjn~1 z%OVy&eXVPf?qy9RT_zi%Kt1BO?RogyhoH_8UKWTb2 z%zZeCr6EO{2}S~6ZL6b!z}zCDQ;8ylzj*0OncMwo&o2NRE-=WCAE_%b8k`ACqv*)- z_^(s1+q-;!gxep_;4LEV2O6{UQ~+1R^slYFO#E*R<+0O-AIuP1iZs;u`g z@1rwE-cw52CqF_1FM+ZWBYd`A;}eshNE3dQIyQYlZ{Ehe0${_c%IS4#`$2E--P>lz z(U!glg^2`QKwAoreADD+2)&JyQuuc-dpYB1Ph9p0fP%H9f*K?5=Q@pToO|j|CgJgC zBU68vsNT(nz6F;86*PMJ7$&8VCvP2^F|DWIlO6%E?IYD6%FY}A>>af6z!M6%VTvr= z#vkDa0M7obhdrOvx@AFd07gxa{eI!nAbQbkLazmoQ8SB!TFfWnOx!V(o!V3aQN(ulE9uwYs8H5;@M z)>;jKISf9*6G2(AC-5XeL25H5W@L7CBIptTpKZ{-a`CkO?~HMQ;1KZCE9sy}6$HRa z`=i(f21;g*lt7d$;u++R0NeCVsJLW=u~TG#2Dd;-TVsXwZ&icqssgc%>DX4M=J)Jrkd_^5{odH-bl>xjyHN(Yi`|n!K>T3bJQ^yHm%DNkVE9-GoxvkQoh5*d zA9$ue_(cOPug9NFfkb8CA04*5>tn6YwXs;jIWcuwOwO$xGL>`!z(=bprs}kgLMAf@ zdi6Z|%M`%t-e73wMh%plYEPAeDO}Jq_|E$iAU!L%_JZMIFubFxG{bjaHiEjs0?w-` zXvR#8f`^uNil*~xIMr9$_X134I%X7bri2^?6(Za4CqA1s+Lt{BR5G|J;*uKqTRs$K_6@6h4eSynEzO;#?|HQQA1^^~utUgU1G#RKF%#RqhjT^UM2ve!ms@r#? zep6&N=p?G8E7C*CnSdM0V};G`cB5q6A7(t5HH%4R=?1{|b=705FIx*OW{TE0-uNwg zE)~);L))yq@xo=l^&ffAO9EFvhF8~)2uUm$S@g+!D)&vHK*VVnIVx)A1Iv<2uwgd< z-hI7dw!Yc^wbSXM|CW$M!V@o~fMc%b}z( z+WQR zMfzht0C;C*xxQ6rcW3dK|70>`Px49X5!6Un1Hg5btCwVz_NiJV>Itw~S)G<4_5c zR=)kma-X!eH{TIAW%h&e6SNd}e56@(>8#;JrX$R|iyJ^Ts3!nm`+X%iHhA~9jlKjj z8vwv@Y+kE^sxp&%NTVl6S1nwwe1*VD8~=E{;y|;`emC=L&V32=m&OM51^}iHr^5wj zkL%&|F+H?4+k5?jFdhM2(}s>5xLm<#W<`6);r&g3o2{M*FJv$o7G^`6FfAHzmi33& z0>F<4bab9VhGOOXZ=QQKa}M$CE`5B_zv~KZ7U~B45$*M8`pp;-xZ$Cz0r?96=3UI3 zb>_GpP8?~aXNR5$vyCLB2xuUr4C&v%6m5-#_QAuOH^z3~9j^pI!1$Y`FlkyeM8^e| zD1$8kR9rH_*7wzJneqkPvlDNU=MbN7(q@-mFcw-&PCD8L*Q3QRqyf5pvq6CZfax<> z0dH7S!v@>kw(-=<>HP{&wg7eDHpm&X0w-TTWUTL8dBlv~!;0LBy|VTl`| zgnKvf!J8E;>S}DO8HPrUkD|l8*&;RofV>2HTA=ywXWKzv98=i>0E#33BMn?S=P6^P zNE5z5eEd%Jj;b zKEM6zFjg*sm|h|P@WsJE0sxQGtN#0nn<0^iOA&v0=`v-vw>yC#{r;`tz$g8)Q~UEV z4SE0sxKxDm3}ClX@Ymm#xz)xeIWFRj-&}HeNB{ou?Gb=crEJll2S9*=2xkDGvG30> zdR+8S&(|CPy4!^LbGP8nuia~}qR;Efks)Zcpme*l1E!@Grgjg5|=4aCK4 zFwX!TkAYEm41Dj6a=otJ;|#j|m2_6Cb_NQ7-hkJe0(yJDuYdO!fd2BqrX2up0ylUY zf)YtR@!?-9Rkdns3d7JN&!oWc?1(AYUj=o1E}Q|-$rPL>R7Ea>g4J|4?}ak}PW;pg z1syMgK3=QZUR7#*m>EyxIyf{$gfjrJ{?M_G*TL)FtX`q1F^7#q$hUiXw)NkY+q12` zulESODL7RAJHn66JwQi?5M{dH`LJ^d3sry-0CYWrb?XxL@fy`Mt;YO=k5lOLslXQU z0zCyp0N|@Yw)B8bp%@&(6r)6^&;)b}{d`kZcKHPxCWNHtR!Bp#Gi#*x)&oHbMn(U+n=p2Zj)5C*CB= zX%7wizlsCRjh@gj%mj|nHf&G?0KQV}Z4=v9064r~2Zsv0L&L~#o_jT8PJ3|ZI@Rh+ zXN)VD&NGIKVe#|*6CB!cz-)g(Y=AhBHrVE$tvx`;;LtIXWPe|x<;V&(kS=l|C$K?MG=>7D8w)m{N2 zSOONaganhT)?3aI0EC9RPyx`MB1QujPJ1GP(y~Nbm%f<(ICADa1I#kCI50^R?GT1Z zqHrG&YLI9I00RpEI!V-8Pf#eAOHk3VygARkHuRtm06K<=(`n*qGU}o4601PYu zYOa{!lYe^B#9>%Cp8xYYrim*ma`6xCx^mEHaNf?aJnfEHxa)zQZSTDeF9LwxB-Hco zp)LX6>}r7j{Lmp$WO~c2DOYO@`=*NBsD7>ZWZO!HB~0s%9zvKgJXneVAXv$@zZVJs zx=Iy0X0r5;3!YT|nc>O@07ZLU;=-e4I&+&RSu~b|zi~$3$)Y1mKxm3*-VOx-KObs= z13Nv*qQw%9`G%Zf-6xC2PS>xleqUW-{w2cz#=JlFY--T6Kx7684G6&kpgmo@3RIQt z2_cYDGlU;3dwGakw?3Od=An=NRx!1)!BJ?pw$B2jfJ0Xb3$|cHXv2@Z9jpX`E*PGtX zeVT_ad_^1noZ*s<1*FmWgCU`qTRe88BxmljeRm(Cgf!t4dG(cDD} z7H-RimZ<6!aVakAenSf2d%?)dEy0LA$(^2n9SV|5zaM5ciR1qq9= ze0)skY0Se>h=Y|UEFX`hVVKGwI1WV$d3@4c8MiR?fdX0rn1|Q@y>eQ;*8Z*CPPv@~ zZ@D`T?zk@@vJ`{~T5yIg{Hl?zy}%Gj62>H@_^zcdr}`9%^y?Jh{ZK)HOQ1eqwM$!L zoy+_kZwm94Dn7IwAs_a3BQ$$$8sxPr`Ytq?sy&JwKFA*k9j|R-I6QTZ-04BJ=q8i?%VX^mEId6B;DDttq zyfjbI;2zitNNfAIG$dcCG@R1a+uff%mY03_X=TJf)PK6CdqRWHj*m2e9={C5Cz2XP z1~KKK#Ti2*3E|5ZZ2frU>gLv|t!g!$|7S#jQ%(-ibM>kYLJJrG%=_zCRezzaw%kuq?Ow!4W2Z>ro`p$( z6|W=2z67wgBBpyU@w(>aa0p77A=tHS#gK);^cXY%KK_R)-q2+Gx~ai_qj$%nCrDx5 zV@W}K5Ct=c>){>r@cC+69h98%O{N`}z%No}kaOlQN%aK&1t5^10f2q%%db~lK4bXN zU~mj$mIFK6~x7Oc!{)|4~Y;w9CNbv93lcg~0L3w0j7^H@9!*^g;gU_0eO^L)@ z&4im}58pjM@ko$OyI*4k0N7go2bxzg$I&@(}P{jKLtwtiwUQbW8uL_0z5q9jlb5k@SA zy`TUse3~jN?B1gFIp^b1PblV8kDn}iboRoeqk({_r)OCO06i0YsG6XzvVLaJJBE38 zERt|x=G=ICgBYRiC2;(33mpDh2bkE|o1`(k(v&noUf$EGr`ePr>i}p!9aJc;TyTA< zZ*olbwiDoQ6Q@PPjC$S&hKru=#v@1yVEW2RllmPu>98h?f`#Br;?jz#=?!a#ay>O1$dWVbhrinbEp~6 zo!guE0>Y4*DO$bw*Jsy`k7^f0%W-~^J=}jUQt-Mx)Pw*?RRaxNB*G*7<(G$4A$N`->qNLoflv&(o1G&-- zRO#N&_ZtAP)1&z{qRT33p02_6uCa|u?8rD?B4Oak?f1mfN5*T3>eN;!+}8-0u>-Ap z9Vtr2X^c%2JfATxZFf!%;R)&OVa{k&YGoY> zYxX57$g?ELe)kf9>n&ZKE!pQh?DZlZAF;SKR{9!wg9L_UOVB>vAF}Gy z2m0gT8UE5aBh+iG(AsS8be!`nYXEUcq(&s*z7ms2emDP#-d(o)V^9M=19Ti@e!h!@ zX3f(zmF6WjyK5|ysiZq$;E5q6Lj;*4M36dE1W{5Ry;KOrT;TKB86Fl}z%LsAYcn`$ zhfEB>#421)hwu5lBcFT$$3pUn^I4x@S*=wEkjSHgLI% zT6ET><~C1gT6f){YdR_)X9PS^V3JF`g~ZD!X*3TcQZ8K&K_KD)mrDSjY0Pmu%K(OL34v0@LJYvrmlj3qH7NJM&c;0IX;ox?65gEB_>#lbC-|u zb>-=;fqwcuYysds{sRYUC2d-2MOCTkPP^Te?XbDxnT)3WB&S}x!3>;ylGDiJyGqiB zi@wc>Pgyf%9^r}8?zM%1d02%eKqqy}+X@cUISi$Bw56MrmsgV9|ixmO&H`879 zhm}tbe5Q}J*i4Gn25WSa&LI*@`D2A5Vye^O8s)IL5*-doW;MHnb{oZYQb0KD6g_?( zpX1_k0F~?%iZ~V?kE7?2L_L>BT(nwT|8CXWixcB{=4iQ~RY*E&XD>``CJ3VUjX*dE crWYRmAEqWZ>J`(u3;+NC07*qoM6N<$f<}h>wEzGB literal 0 HcmV?d00001 diff --git a/docs/docs/start.md b/docs/docs/start.md new file mode 100644 index 0000000..d47e607 --- /dev/null +++ b/docs/docs/start.md @@ -0,0 +1,145 @@ +### 视频版部署教程 +https://www.bilibili.com/video/BV19wrXY2ESM/ + +## 准备 + +在开始部署之前,需要在 Cloudflare 控制台完成以下准备工作: + +1. **创建 D1 数据库** + - 登录 [Cloudflare 控制台](https://dash.cloudflare.com/) + - 选择 “存储与数据库” -> “D1 SQL 数据库” + - 创建一个数据库(例如:moemail) + - 记录下数据库名称和数据库 ID,后续配置需要用到 + +2. **创建 KV 命名空间** + - 登录 [Cloudflare 控制台](https://dash.cloudflare.com/) + - 选择 “存储与数据库” -> “KV” + - 创建一个 KV 命名空间(例如:moemail) + - 记录下命名空间 ID,后续配置需要用到 + +3. **创建 Pages 项目** + - 登录 [Cloudflare 控制台](https://dash.cloudflare.com/) + - 选择 “Workers 和 Pages” + - 点击 “创建” 并选择 “Pages” 标签 + - 选择 “使用直接上传创建” + - 点击 “上传资产” + - 输入项目名称 + ::: warning + 注意:项目名称必须为 moemail,否则无法正常部署 + ::: + - 输入项目名称后,点击 “创建项目” 即可,不需要上传任何文件以及点击“部署站点”,之后我们会通过 本地运行Wrangler 或者通过 Github Actions 自动部署 +4. **为 Pages 项目添加 AUTH 认证相关的 SECRETS** + - 在 Overview 中选择刚刚创建的 Pages 项目 + - 在 Settings 中选择变量和机密 + - 添加 AUTH_GITHUB_ID, AUTH_GITHUB_SECRET, AUTH_SECRET + +## Github Actions 部署 + +本项目可使用 GitHub Actions 实现自动化部署。支持以下触发方式: + +1. **自动触发**:推送新的 tag 时自动触发部署流程 +2. **手动触发**:在 GitHub Actions 页面手动触发,可选择以下部署选项: + - Run database migrations:执行数据库迁移 + - Deploy email Worker:重新部署邮件 Worker + - Deploy cleanup Worker:重新部署清理 Worker + +#### 部署步骤 + +1. 在 GitHub 仓库设置中添加以下 Secrets: + +| 环境变量 | 说明 | +|----------|------| +| `CLOUDFLARE_API_TOKEN` | Cloudflare API 令牌 | +| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare 账户 ID | +| `DATABASE_NAME` | D1 数据库名称 | +| `DATABASE_ID` | D1 数据库 ID | +| `KV_NAMESPACE_ID` | Cloudflare KV namespace ID,用于存储网站配置 | + +2. 选择触发方式: + + **方式一:推送 tag 触发** + ```bash + # 创建新的 tag + git tag v1.0.0 + ``` + + ```bash + # 推送 tag 到远程仓库 + git push origin v1.0.0 + ``` + + **方式二:手动触发** + - 进入仓库的 Actions 页面 + - 选择 "Deploy" workflow + - 点击 "Run workflow" + - 选择需要执行的部署选项 + - 点击 "Run workflow" 开始部署 + +3. GitHub Actions 会自动执行以下任务: + - 构建并部署主应用到 Cloudflare Pages + - 根据选项或文件变更执行数据库迁移 + - 根据选项或文件变更部署 Email Worker + - 根据选项或文件变更部署 Cleanup Worker + +4. 部署进度可以在仓库的 Actions 标签页查看 + +::: warning +- 使用 tag 触发时,tag 必须以 `v` 开头(例如:v1.0.0) +- 使用 tag 触发时,只有文件发生变更的部分会被部署 +- 手动触发时,可以选择性地执行特定的部署任务 +- 每次部署都会重新部署主应用 +::: + +## 环境变量 + +本项目使用以下环境变量: + +### 认证相关 + +| 环境变量 | 说明 | +|----------|------| +| `AUTH_GITHUB_ID` | GitHub OAuth App ID | +| `AUTH_GITHUB_SECRET` | GitHub OAuth App Secret | +| `AUTH_SECRET` | NextAuth Secret,用来加密 session,请设置一个随机字符串 | + +### Cloudflare 配置 + +| 环境变量 | 说明 | +|----------|------| +| `CLOUDFLARE_API_TOKEN` | Cloudflare API Token | +| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare Account ID | +| `DATABASE_NAME` | D1 数据库名称 | +| `DATABASE_ID` | D1 数据库 ID | +| `KV_NAMESPACE_ID` | Cloudflare KV namespace ID,用于存储网站配置 | + + +## Github OAuth App 配置 + +- 登录 [Github Developer](https://github.com/settings/developers) 创建一个新的 OAuth App +- 生成一个新的 `Client ID` 和 `Client Secret` +- 设置 `Application name` 为 `` +- 设置 `Homepage URL` 为 `https://` +- 设置 `Authorization callback URL` 为 `https:///api/auth/callback/github` + +[![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/beilunyang/moemail) + +## Cloudflare 邮件路由配置 + +为了使邮箱域名生效,还需要在 Cloudflare 控制台配置邮件路由,将收到的邮件转发给 Email Worker 处理。 + +1. 登录 [Cloudflare 控制台](https://dash.cloudflare.com/) +2. 选择您的域名 +3. 点击左侧菜单的 "电子邮件" -> "电子邮件路由" +4. 如果显示 “电子邮件路由当前被禁用,没有在路由电子邮件”,请点击 "启用电子邮件路由" +![启用电子邮件路由](https://pic.otaku.ren/20241223/AQADNcQxG_K0SVd-.jpg "启用电子邮件路由") +5. 点击后,会提示你添加电子邮件路由 DNS 记录,点击 “添加记录并启用” 即可 +![添加电子邮件路由 DNS 记录](https://pic.otaku.ren/20241223/AQADN8QxG_K0SVd-.jpg "添加电子邮件路由 DNS 记录") +6. 配置路由规则: + + ![配置路由规则](https://pic.otaku.ren/20241223/AQADNsQxG_K0SVd-.jpg "配置路由规则") + - Catch-all 地址: 启用 "Catch-all" + ::: warning + 如果Catch-All 状态不可用,请在点击`路由规则`旁边的`目标地址`进去绑定一个邮箱 + ::: + - 编辑 Catch-all 地址,选择 "发送到 Worker" + - 目标位置: 选择刚刚部署的 "email-receiver-worker" \ No newline at end of file diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..45e1db2 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,7 @@ +{ + "scripts": { + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" + } +} \ No newline at end of file diff --git a/package.json b/package.json index a3b37ba..df93b1e 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "tailwindcss": "^3.4.1", "typescript": "^5", "vercel": "39.1.1", + "vitepress": "^1.6.3", "wrangler": "^3.91.0" } }