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
+3 -1
View File
@@ -7,13 +7,15 @@ D1 讀取查詢層(`content.ts`)同後台網站設定欄位定義(`setting
## Ownership
- 擁有 `src/data/` 兩個檔案。
- Drizzle schema 本體喺 `src/db/schema.ts`(見兄弟 `db/AGENTS.md`),呢度只消費其型別
- Drizzle schema 本體喺 `src/db/schema.ts`(見兄弟 `db/AGENTS.md`),呢度只消費其 select 型別;表單同 AI 嘅**輸入**型別由 `src/schemas/`Zod `z.infer`)提供,兩者唔重覆
## Local Contracts
- **查詢集中**:所有前台/Blog 的 D1 查詢寫喺 `content.ts`,唔好散落喺頁面或元件。
- **只回前台可見**`getItems` / `getCases` / `getPublishedPosts` 只回 `status = "published"`,並按 `sortOrder``publishedAt DESC` 排序;`getPostBySlug` 同時要求 published。
- **首頁聚合**`getHomeData(db)``Promise.all` 一次過攞 settings + services/features/steps/faqs + cases,形狀係 `HomeData`
- **關鍵字佇列**`getKeywords(db)` 回全部 `blog_keywords`(按 `createdAt``id` 排序),供 `/admin/ai` 用。
- **Slug 去重**`uniqueSlug(db, base, selfId?)` 撞 slug 就加 `-2``-3`…;`selfId` 用喺更新自己時排除自己。admin 文章同 AI 生成都用。
- **`Db` 型別**`DrizzleD1Database<typeof schema>`
- **連結 helper**`whatsappHref` / `telHref` / `mailHref` / `digits` / `formatDate`;電話/WhatsApp 一律經呢啲 helper,唔好散寫。
- **設定欄位**`settings-fields.ts``SETTINGS_GROUPS``/admin/settings` 表單的唯一來源;新增一個 setting key 之後,要同步加落 `migrations/seed.sql`