diff --git a/README.md b/README.md index 7bc33be..d47fb57 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 盈豐太陽能工程有限公司 — 官網(村屋太陽能) -**Astro 7 + React 19 + Chakra UI v3 + Cloudflare Workers + D1 + Drizzle。全部部署喺 Cloudflare,零維運。** +**Astro 7 + React 19 + Chakra UI v3 + Cloudflare Workers + D1 + KV + R2 + Drizzle。全部部署喺 Cloudflare,零維運。** 一頁式官網(村屋太陽能)+ Blog + 內容管理後台。 @@ -22,6 +22,7 @@ | Adapter | `@astrojs/cloudflare`(一個 Worker) | | DB | Cloudflare D1(SQLite) | | ORM | Drizzle | +| 物件儲存 | Cloudflare R2(後台圖片上傳,binding `MEDIA`) | | Auth | HMAC-signed cookie(`src/lib/auth.ts`) | | Markdown | `marked` | | Icons | `lucide-react` | @@ -53,7 +54,7 @@ npm run dev ```bash npm run login # 瀏覽器授權 -npm run setup # 建立 D1 / KV、寫入 wrangler.jsonc、設 secret、套 migration + seed +npm run setup # 建立 D1 / KV / R2、寫入 wrangler.jsonc、設 secret、套 migration + seed ``` `npm run setup` 會逐步問你:要唔要設定後台密碼、要唔要套 migration/seed、要唔要 build + deploy。若 `astro.config.mjs` 嘅 `site` 仍然係 `https://example.com`,會跳過 build + deploy 並提醒你改。 @@ -91,7 +92,7 @@ npm run deploy 1. 去 `/admin`,輸入密碼登入 2. **內容總覽** → 服務 / 特色 / 流程 / 常見問題 / 完成案例 / 網站設定 / AI 生成 3. 每個項目可新增、修改、刪除、用 ↑↓ 排序、「已發布 / 草稿」控制前台顯示 -4. 圖片:直接貼圖片 URL(暫時;之後可換成上傳) +4. 圖片:可揀檔即時上傳去 R2(瀏覽器端先縮圖轉 WebP),亦可貼圖片 URL 5. **AI 生成**:設定供應商同關鍵字佇列,生成草稿後去文章編輯頁覆核發布 ## 結構 @@ -110,9 +111,11 @@ src/ ├─ pages/ │ ├─ index.astro SSR 首頁 │ ├─ blog/… Blog -│ ├─ admin/… 後台(含 admin/ai) +│ ├─ admin/… 後台(含 admin/ai、admin/upload 上傳 endpoint) +│ ├─ media/[...key].ts R2 圖片出圖 endpoint │ └─ sitemap.xml.ts / robots.txt.ts -├─ lib/{auth,db,env,form,ai,markdown}.ts +├─ components/admin/ImageField.astro 後台圖片欄(URL + 上傳 + 預覽) +├─ lib/{auth,db,env,form,media,ai,markdown}.ts migrations/ ├─ 0000_init.sql … Drizzle migrations └─ seed.sql 種子資料(用 db:seed 另外執行) diff --git a/docs/superpowers/plans/2026-09-12-r2-image-upload.md b/docs/superpowers/plans/2026-09-12-r2-image-upload.md index fb5e73d..7fd978a 100644 --- a/docs/superpowers/plans/2026-09-12-r2-image-upload.md +++ b/docs/superpowers/plans/2026-09-12-r2-image-upload.md @@ -1,6 +1,6 @@ # R2 Image Upload Implementation Plan -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking. **Goal:** 後台 4 個圖片欄位(`hero_image`、`og_image`、案例 `imageUrl`、文章 `coverImage`)支援揀檔即時上傳去 Cloudflare R2,同時保留貼 URL。 @@ -44,7 +44,7 @@ - Modify: `scripts/setup.mjs` - Modify: `src/lib/env.ts` -- [ ] **Step 1: `wrangler.jsonc` 加 R2 binding** +- [x] **Step 1: `wrangler.jsonc` 加 R2 binding** 喺 `"kv_namespaces": [...]` 之後(結尾 `]` 加逗號)加: @@ -104,7 +104,7 @@ } ``` -- [ ] **Step 2: `package.json` 加 `r2:create`** +- [x] **Step 2: `package.json` 加 `r2:create`** 喺 `"kv:create"` 之後加一行: @@ -113,7 +113,7 @@ "r2:create": "wrangler r2 bucket create yingfung-solar-media", ``` -- [ ] **Step 3: `src/lib/env.ts` 加 `MEDIA`** +- [x] **Step 3: `src/lib/env.ts` 加 `MEDIA`** `AppEnv` 改成: @@ -128,7 +128,7 @@ export type AppEnv = { }; ``` -- [ ] **Step 4: `scripts/setup.mjs` 加建 R2 bucket 步驟** +- [x] **Step 4: `scripts/setup.mjs` 加建 R2 bucket 步驟** 喺檔案上方 constant 區(`const KV_PLACEHOLDER = ...` 之後)加: @@ -155,7 +155,7 @@ const R2_BUCKET = "yingfung-solar-media"; } ``` -- [ ] **Step 5: 重新產生 binding 型別** +- [x] **Step 5: 重新產生 binding 型別** Run: `npm run types` Expected: 成功,`worker-configuration.d.ts` 內 `MEDIA: R2Bucket;` 出現喺 `Env`。 @@ -163,12 +163,12 @@ Expected: 成功,`worker-configuration.d.ts` 內 `MEDIA: R2Bucket;` 出現喺 Run: `rg -n "MEDIA: R2Bucket" worker-configuration.d.ts` Expected: 有一行 match(若 `wrangler types` 讀唔到 R2 binding,手動加都唔會影響 build,但正常會出)。 -- [ ] **Step 6: Build 驗證** +- [x] **Step 6: Build 驗證** Run: `npm run build` Expected: 完成,冇 error。 -- [ ] **Step 7: Commit** +- [x] **Step 7: Commit** ```bash git add wrangler.jsonc package.json scripts/setup.mjs src/lib/env.ts worker-configuration.d.ts @@ -182,7 +182,7 @@ git commit -m "Add R2 MEDIA binding and setup script" **Files:** - Create: `src/lib/media.ts` -- [ ] **Step 1: 建立 `src/lib/media.ts`** +- [x] **Step 1: 建立 `src/lib/media.ts`** ```ts import type { AppEnv } from "./env"; @@ -215,12 +215,12 @@ export async function deleteMedia( } ``` -- [ ] **Step 2: Build 驗證** +- [x] **Step 2: Build 驗證** Run: `npm run build` Expected: 完成,冇 error。 -- [ ] **Step 3: Commit** +- [x] **Step 3: Commit** ```bash git add src/lib/media.ts @@ -235,7 +235,7 @@ git commit -m "Add media helper for R2 keys and cleanup" - Create: `src/pages/admin/upload.ts` - Create: `src/pages/media/[...key].ts` -- [ ] **Step 1: 建立 `src/pages/admin/upload.ts`** +- [x] **Step 1: 建立 `src/pages/admin/upload.ts`** ```ts import type { APIRoute } from "astro"; @@ -300,7 +300,7 @@ export const POST: APIRoute = async ({ request }) => { }; ``` -- [ ] **Step 2: 建立 `src/pages/media/[...key].ts`** +- [x] **Step 2: 建立 `src/pages/media/[...key].ts`** ```ts import type { APIRoute } from "astro"; @@ -329,12 +329,12 @@ export const GET: APIRoute = async ({ params, request }) => { }; ``` -- [ ] **Step 3: Build 驗證** +- [x] **Step 3: Build 驗證** Run: `npm run build` Expected: 完成,冇 error,而且 `dist/` 唔會 build 出 `/media` 靜態頁(endpoint 係 SSR)。 -- [ ] **Step 4: 手動測試 endpoints** +- [x] **Step 4: 手動測試 endpoints** Run: `npm run dev`(另一個 terminal) Expected: @@ -350,7 +350,7 @@ console.log(await (await fetch("/admin/upload", { method: "POST", body: fd })).j Expected: `{ ok: true, url: "/media/cases/.webp" }`,跟住開嗰個 url 有 response(body 係嗰 4 bytes)。 -- [ ] **Step 5: Commit** +- [x] **Step 5: Commit** ```bash git add src/pages/admin/upload.ts "src/pages/media/[...key].ts" @@ -365,7 +365,7 @@ git commit -m "Add R2 upload and media serving endpoints" - Create: `src/components/admin/ImageField.astro` - Modify: `src/layouts/AdminLayout.astro` -- [ ] **Step 1: 建立 `src/components/admin/ImageField.astro`** +- [x] **Step 1: 建立 `src/components/admin/ImageField.astro`** ```astro --- @@ -398,7 +398,7 @@ const { name, label, value = "", scope, id = name } = Astro.props; ``` -- [ ] **Step 2: `AdminLayout.astro` 加 client script** +- [x] **Step 2: `AdminLayout.astro` 加 client script** 喺 `` 之後、`` 之前加: @@ -472,7 +472,7 @@ const { name, label, value = "", scope, id = name } = Astro.props; ``` -- [ ] **Step 3: `AdminLayout.astro` 加樣式** +- [x] **Step 3: `AdminLayout.astro` 加樣式** 喺 `