Add Zod validation layer and AI blog generation

Introduce a schema-first validation layer and an AI blog generation
feature, plus one-command Cloudflare provisioning.

- src/schemas/ holds Zod input schemas for post, content, case,
  settings, AI, and keywords; parseForm() in src/lib/form.ts validates
  FormData and returns per-field errors.
- Migrate all admin POST handlers to parseForm, showing field-level
  errors and only redirecting once validation passes.
- Add blog_keywords table and posts.focus_keyword (migration 0002);
  uniqueSlug() centralised in src/data/content.ts.
- Add src/lib/ai.ts (OpenAI-compatible chat completions + optional
  Tavily research) and /admin/ai for AI settings, keyword queue, and
  draft generation.
- Add scripts/setup.mjs (npm run setup) to provision D1/KV, set
  secrets, and optionally migrate, seed, and deploy.
- Document AI secrets, Workers Builds deploy flow, and new schemas
  across README and AGENTS docs.
This commit is contained in:
2026-09-11 23:49:18 +08:00
parent 5b69bc818a
commit fdee1aabd7
41 changed files with 3240 additions and 296 deletions
+8 -4
View File
@@ -13,6 +13,7 @@
- `config.ts` — build-time 靜態頁用的 `SITE_NAME` / `SITE_TAGLINE`(可管理內容一律放 D1,唔放呢度)。
- `middleware.ts` — 保護 `/admin`:設 `locals.isAdmin`,無 `ADMIN_PASSWORD` 回 503,未登入導向 `/admin/login`
- `env.d.ts``App.Locals.isAdmin` 型別宣告。
- `schemas/` — Zod 驗證層:admin 表單輸入(post / content / case / settings / ai / keyword)同 AI 輸出格式;配合 `lib/form.ts``parseForm``lib/ai.ts` 的生成流程使用(`lib/` 細節由 `lib/AGENTS.md` 擁有)。
- `pages/` — 路由層同 `/admin` 後台(詳見下面 Routes & SSR / Admin,因 Astro 限制冇獨立 child doc)。
## Local Contracts
@@ -32,6 +33,7 @@
- **前台動態頁**:讀 D1 → 傳 props 畀單一 React island`client:load`),並設邊緣快取 `Cache-Control: public, s-maxage=60, stale-while-revalidate=300`
- **404**`blog/[slug].astro` 找唔到文章 → 設 `Astro.response.status = 404` 並 render noindex 頁。
- **Endpoint**:用 `APIRoute``sitemap.xml.ts` 由 D1 讀已發布文章並設 `s-maxage=3600`
- **AI 生成(同步)**`/admin/ai` 的生成喺 admin POST 內同步 `await` 完成先 redirect。如日後要改做背景處理,可用 `Astro.locals.cfContext`Cloudflare `ExecutionContext`)嘅 `waitUntil`,唔使改現有流程。
- **SEO**:用 `Base.astro`canonical / OG / sitemap 全部靠 `astro.config.mjs``site`(現為佔位 `https://example.com`,上線前要改)。
### Admin`pages/admin/`
@@ -39,11 +41,13 @@
- 所有 `/admin` 路由**必須** `export const prerender = false`
- **認證**由 `middleware.ts` 統一處理;登入喺 `admin/login.astro``checkPassword` + `createSession`),登出喺 `logout.ts`
- **UI**:全部用 `AdminLayout.astro`,純 Astro SSR 表單(`POST` + `formData`),**唔引入** React / Chakra。
- **流程**:每個 POST 處理完 `Astro.redirect` 返對應列表頁。
- **流程**:每個 POST 處理完 `Astro.redirect` 返對應列表頁`/admin/ai` 用 POST/Redirect/Get + `?ok=<key>` + `okMessages` 顯示成功提示(避免重複提交)
- **表單驗證**:所有 admin 表單經 `parseForm(form, schema)``src/schemas/`),失敗時以 `errors` 逐欄顯示,唔好手寫逐欄檢查。
- **通用動作**`add` / `save` / `delete` / `up` / `down`(排序以交換 `sortOrder` 實作)。
- **通用內容編輯器**`content/[kind].astro``kind ∈ service | feature | step | faq`(見 `db/schema.ts``CONTENT_KINDS`),欄位標籤由檔案內 `META` 定義。
- **網站設定**`settings.astro``data/settings-fields.ts``SETTINGS_GROUPS` / `ALL_SETTING_KEYS` 產生表單,逐 key upsert。
- **文章**`index.astro` 列表、`post/[id].astro` 新增/編輯(`id === "new"` 為新增);slug 自動 `slugify` 並用 `uniqueSlug` 去重。
- **AI 生成**`ai.astro``/admin/ai`)管 AI 設定 + 關鍵字佇列,同步呼叫 `generateBlogPost`;成功會 redirect 去新草稿 `/admin/post/<id>`。生成按鈕用 inline `onsubmit` 顯示「生成中…」。
- **圖片**:一律以 URL 字串處理(暫無上傳)。
- 前台可見性靠 `status``published` / `draft`);列表頁顯示全部,前台只顯示 published。
@@ -51,8 +55,8 @@
- 改前台視覺先睇 `theme/system.ts` 的 tokens,優先重用語意 token,唔好散落硬編色值。
- 新增 endpoint 或 SSR 頁後,確認 `prerender = false` 同相應快取 header 都有。
- 新增一個內容欄位:先改 `db/schema.ts``npm run db:generate`,再改對應 admin 表單同 `data/content.ts`
- **唔好喺 `src/pages/``.md` 文件**(例如 AGENTS.md):Astro 會將佢變成公開路由(今次已實測 `/AGENTS``/admin/AGENTS`)。Astro 只支援用 `_` 前綴豁免,但同 DOX 的 `AGENTS.md` 命名衝突,所以 `pages/` 的合約一律寫喺本文件。其餘 `src/` 子目錄的 `AGENTS.md`data/db/lib/components)唔會被路由。
- 新增一個內容欄位:先改 `db/schema.ts``npm run db:generate`,再改對應 `schemas/` 輸入 schema、admin 表單同 `data/content.ts`
- **唔好喺 `src/pages/``.md` 文件**(例如 AGENTS.md):Astro 會將佢變成公開路由(例如 `/AGENTS``/admin/AGENTS`)。Astro 只支援用 `_` 前綴豁免,但同 DOX 的 `AGENTS.md` 命名衝突,所以 `pages/` 的合約一律寫喺本文件。其餘 `src/` 子目錄的 `AGENTS.md`data/db/lib/components)唔會被路由。
## Verification
@@ -66,4 +70,4 @@
| `components/site/AGENTS.md` | 前台 React island 與 Chakra UI 元件 |
| `data/AGENTS.md` | D1 讀取查詢層與後台設定欄位定義 |
| `db/AGENTS.md` | Drizzle schemamigration 的唯一來源) |
| `lib/AGENTS.md` | auth / env / db / markdown 基礎工具 |
| `lib/AGENTS.md` | auth / env / db / form / ai / markdown 基礎工具 |