Document R2 image upload in README and mark plan complete
This commit is contained in:
@@ -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 + 內容管理後台。
|
一頁式官網(村屋太陽能)+ Blog + 內容管理後台。
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
| Adapter | `@astrojs/cloudflare`(一個 Worker) |
|
| Adapter | `@astrojs/cloudflare`(一個 Worker) |
|
||||||
| DB | Cloudflare D1(SQLite) |
|
| DB | Cloudflare D1(SQLite) |
|
||||||
| ORM | Drizzle |
|
| ORM | Drizzle |
|
||||||
|
| 物件儲存 | Cloudflare R2(後台圖片上傳,binding `MEDIA`) |
|
||||||
| Auth | HMAC-signed cookie(`src/lib/auth.ts`) |
|
| Auth | HMAC-signed cookie(`src/lib/auth.ts`) |
|
||||||
| Markdown | `marked` |
|
| Markdown | `marked` |
|
||||||
| Icons | `lucide-react` |
|
| Icons | `lucide-react` |
|
||||||
@@ -53,7 +54,7 @@ npm run dev
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run login # 瀏覽器授權
|
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 並提醒你改。
|
`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`,輸入密碼登入
|
1. 去 `/admin`,輸入密碼登入
|
||||||
2. **內容總覽** → 服務 / 特色 / 流程 / 常見問題 / 完成案例 / 網站設定 / AI 生成
|
2. **內容總覽** → 服務 / 特色 / 流程 / 常見問題 / 完成案例 / 網站設定 / AI 生成
|
||||||
3. 每個項目可新增、修改、刪除、用 ↑↓ 排序、「已發布 / 草稿」控制前台顯示
|
3. 每個項目可新增、修改、刪除、用 ↑↓ 排序、「已發布 / 草稿」控制前台顯示
|
||||||
4. 圖片:直接貼圖片 URL(暫時;之後可換成上傳)
|
4. 圖片:可揀檔即時上傳去 R2(瀏覽器端先縮圖轉 WebP),亦可貼圖片 URL
|
||||||
5. **AI 生成**:設定供應商同關鍵字佇列,生成草稿後去文章編輯頁覆核發布
|
5. **AI 生成**:設定供應商同關鍵字佇列,生成草稿後去文章編輯頁覆核發布
|
||||||
|
|
||||||
## 結構
|
## 結構
|
||||||
@@ -110,9 +111,11 @@ src/
|
|||||||
├─ pages/
|
├─ pages/
|
||||||
│ ├─ index.astro SSR 首頁
|
│ ├─ index.astro SSR 首頁
|
||||||
│ ├─ blog/… Blog
|
│ ├─ blog/… Blog
|
||||||
│ ├─ admin/… 後台(含 admin/ai)
|
│ ├─ admin/… 後台(含 admin/ai、admin/upload 上傳 endpoint)
|
||||||
|
│ ├─ media/[...key].ts R2 圖片出圖 endpoint
|
||||||
│ └─ sitemap.xml.ts / robots.txt.ts
|
│ └─ 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/
|
migrations/
|
||||||
├─ 0000_init.sql … Drizzle migrations
|
├─ 0000_init.sql … Drizzle migrations
|
||||||
└─ seed.sql 種子資料(用 db:seed 另外執行)
|
└─ seed.sql 種子資料(用 db:seed 另外執行)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# R2 Image Upload Implementation Plan
|
# 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。
|
**Goal:** 後台 4 個圖片欄位(`hero_image`、`og_image`、案例 `imageUrl`、文章 `coverImage`)支援揀檔即時上傳去 Cloudflare R2,同時保留貼 URL。
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
- Modify: `scripts/setup.mjs`
|
- Modify: `scripts/setup.mjs`
|
||||||
- Modify: `src/lib/env.ts`
|
- Modify: `src/lib/env.ts`
|
||||||
|
|
||||||
- [ ] **Step 1: `wrangler.jsonc` 加 R2 binding**
|
- [x] **Step 1: `wrangler.jsonc` 加 R2 binding**
|
||||||
|
|
||||||
喺 `"kv_namespaces": [...]` 之後(結尾 `]` 加逗號)加:
|
喺 `"kv_namespaces": [...]` 之後(結尾 `]` 加逗號)加:
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: `package.json` 加 `r2:create`**
|
- [x] **Step 2: `package.json` 加 `r2:create`**
|
||||||
|
|
||||||
喺 `"kv:create"` 之後加一行:
|
喺 `"kv:create"` 之後加一行:
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
"r2:create": "wrangler r2 bucket create yingfung-solar-media",
|
"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` 改成:
|
`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 = ...` 之後)加:
|
喺檔案上方 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`
|
Run: `npm run types`
|
||||||
Expected: 成功,`worker-configuration.d.ts` 內 `MEDIA: R2Bucket;` 出現喺 `Env`。
|
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`
|
Run: `rg -n "MEDIA: R2Bucket" worker-configuration.d.ts`
|
||||||
Expected: 有一行 match(若 `wrangler types` 讀唔到 R2 binding,手動加都唔會影響 build,但正常會出)。
|
Expected: 有一行 match(若 `wrangler types` 讀唔到 R2 binding,手動加都唔會影響 build,但正常會出)。
|
||||||
|
|
||||||
- [ ] **Step 6: Build 驗證**
|
- [x] **Step 6: Build 驗證**
|
||||||
|
|
||||||
Run: `npm run build`
|
Run: `npm run build`
|
||||||
Expected: 完成,冇 error。
|
Expected: 完成,冇 error。
|
||||||
|
|
||||||
- [ ] **Step 7: Commit**
|
- [x] **Step 7: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add wrangler.jsonc package.json scripts/setup.mjs src/lib/env.ts worker-configuration.d.ts
|
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:**
|
**Files:**
|
||||||
- Create: `src/lib/media.ts`
|
- Create: `src/lib/media.ts`
|
||||||
|
|
||||||
- [ ] **Step 1: 建立 `src/lib/media.ts`**
|
- [x] **Step 1: 建立 `src/lib/media.ts`**
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import type { AppEnv } from "./env";
|
import type { AppEnv } from "./env";
|
||||||
@@ -215,12 +215,12 @@ export async function deleteMedia(
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Build 驗證**
|
- [x] **Step 2: Build 驗證**
|
||||||
|
|
||||||
Run: `npm run build`
|
Run: `npm run build`
|
||||||
Expected: 完成,冇 error。
|
Expected: 完成,冇 error。
|
||||||
|
|
||||||
- [ ] **Step 3: Commit**
|
- [x] **Step 3: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add src/lib/media.ts
|
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/admin/upload.ts`
|
||||||
- Create: `src/pages/media/[...key].ts`
|
- Create: `src/pages/media/[...key].ts`
|
||||||
|
|
||||||
- [ ] **Step 1: 建立 `src/pages/admin/upload.ts`**
|
- [x] **Step 1: 建立 `src/pages/admin/upload.ts`**
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import type { APIRoute } from "astro";
|
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
|
```ts
|
||||||
import type { APIRoute } from "astro";
|
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`
|
Run: `npm run build`
|
||||||
Expected: 完成,冇 error,而且 `dist/` 唔會 build 出 `/media` 靜態頁(endpoint 係 SSR)。
|
Expected: 完成,冇 error,而且 `dist/` 唔會 build 出 `/media` 靜態頁(endpoint 係 SSR)。
|
||||||
|
|
||||||
- [ ] **Step 4: 手動測試 endpoints**
|
- [x] **Step 4: 手動測試 endpoints**
|
||||||
|
|
||||||
Run: `npm run dev`(另一個 terminal)
|
Run: `npm run dev`(另一個 terminal)
|
||||||
Expected:
|
Expected:
|
||||||
@@ -350,7 +350,7 @@ console.log(await (await fetch("/admin/upload", { method: "POST", body: fd })).j
|
|||||||
|
|
||||||
Expected: `{ ok: true, url: "/media/cases/<uuid>.webp" }`,跟住開嗰個 url 有 response(body 係嗰 4 bytes)。
|
Expected: `{ ok: true, url: "/media/cases/<uuid>.webp" }`,跟住開嗰個 url 有 response(body 係嗰 4 bytes)。
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
- [x] **Step 5: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add src/pages/admin/upload.ts "src/pages/media/[...key].ts"
|
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`
|
- Create: `src/components/admin/ImageField.astro`
|
||||||
- Modify: `src/layouts/AdminLayout.astro`
|
- Modify: `src/layouts/AdminLayout.astro`
|
||||||
|
|
||||||
- [ ] **Step 1: 建立 `src/components/admin/ImageField.astro`**
|
- [x] **Step 1: 建立 `src/components/admin/ImageField.astro`**
|
||||||
|
|
||||||
```astro
|
```astro
|
||||||
---
|
---
|
||||||
@@ -398,7 +398,7 @@ const { name, label, value = "", scope, id = name } = Astro.props;
|
|||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: `AdminLayout.astro` 加 client script**
|
- [x] **Step 2: `AdminLayout.astro` 加 client script**
|
||||||
|
|
||||||
喺 `</main>` 之後、`</body>` 之前加:
|
喺 `</main>` 之後、`</body>` 之前加:
|
||||||
|
|
||||||
@@ -472,7 +472,7 @@ const { name, label, value = "", scope, id = name } = Astro.props;
|
|||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 3: `AdminLayout.astro` 加樣式**
|
- [x] **Step 3: `AdminLayout.astro` 加樣式**
|
||||||
|
|
||||||
喺 `<style is:global>` 內 `.row { ... }` 之前加:
|
喺 `<style is:global>` 內 `.row { ... }` 之前加:
|
||||||
|
|
||||||
@@ -485,12 +485,12 @@ const { name, label, value = "", scope, id = name } = Astro.props;
|
|||||||
.image-preview[hidden] { display: none; }
|
.image-preview[hidden] { display: none; }
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: Build 驗證**
|
- [x] **Step 4: Build 驗證**
|
||||||
|
|
||||||
Run: `npm run build`
|
Run: `npm run build`
|
||||||
Expected: 完成,冇 error。
|
Expected: 完成,冇 error。
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
- [x] **Step 5: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add src/components/admin/ImageField.astro src/layouts/AdminLayout.astro
|
git add src/components/admin/ImageField.astro src/layouts/AdminLayout.astro
|
||||||
@@ -505,7 +505,7 @@ git commit -m "Add ImageField component and client-side upload script"
|
|||||||
- Modify: `src/data/settings-fields.ts`
|
- Modify: `src/data/settings-fields.ts`
|
||||||
- Modify: `src/pages/admin/settings.astro`
|
- Modify: `src/pages/admin/settings.astro`
|
||||||
|
|
||||||
- [ ] **Step 1: `settings-fields.ts` 加 `image` 型別**
|
- [x] **Step 1: `settings-fields.ts` 加 `image` 型別**
|
||||||
|
|
||||||
將第一行改成:
|
將第一行改成:
|
||||||
|
|
||||||
@@ -523,7 +523,7 @@ export type FieldType = "text" | "textarea" | "url" | "image";
|
|||||||
{ key: "og_image", label: "分享圖片 (OG image)", type: "image" },
|
{ key: "og_image", label: "分享圖片 (OG image)", type: "image" },
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: `settings.astro` 加 import 同刪舊邏輯**
|
- [x] **Step 2: `settings.astro` 加 import 同刪舊邏輯**
|
||||||
|
|
||||||
將 frontmatter 改成:
|
將 frontmatter 改成:
|
||||||
|
|
||||||
@@ -584,7 +584,7 @@ const settings = await getSettings(db);
|
|||||||
---
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 3: `settings.astro` 欄位 render 加 ImageField**
|
- [x] **Step 3: `settings.astro` 欄位 render 加 ImageField**
|
||||||
|
|
||||||
將 template 內 `SETTINGS_GROUPS.map((group) => ( ... ))` 的欄位 map 改成(其餘不變):
|
將 template 內 `SETTINGS_GROUPS.map((group) => ( ... ))` 的欄位 map 改成(其餘不變):
|
||||||
|
|
||||||
@@ -621,17 +621,17 @@ const settings = await getSettings(db);
|
|||||||
))}
|
))}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: Build 驗證**
|
- [x] **Step 4: Build 驗證**
|
||||||
|
|
||||||
Run: `npm run build`
|
Run: `npm run build`
|
||||||
Expected: 完成,冇 error。
|
Expected: 完成,冇 error。
|
||||||
|
|
||||||
- [ ] **Step 5: 手動測試**
|
- [x] **Step 5: 手動測試**
|
||||||
|
|
||||||
Run: `npm run dev`
|
Run: `npm run dev`
|
||||||
Expected: `/admin/settings` 的「首頁 Hero」同「SEO 設定」入面,主視覺圖片同 OG 圖變咗有檔案選擇 + 預覽;上傳後 URL 欄填 `/media/settings/<uuid>.webp`;儲存後前台首頁圖片更新;再換一張,舊 `/media/...` 變 404。
|
Expected: `/admin/settings` 的「首頁 Hero」同「SEO 設定」入面,主視覺圖片同 OG 圖變咗有檔案選擇 + 預覽;上傳後 URL 欄填 `/media/settings/<uuid>.webp`;儲存後前台首頁圖片更新;再換一張,舊 `/media/...` 變 404。
|
||||||
|
|
||||||
- [ ] **Step 6: Commit**
|
- [x] **Step 6: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add src/data/settings-fields.ts src/pages/admin/settings.astro
|
git add src/data/settings-fields.ts src/pages/admin/settings.astro
|
||||||
@@ -645,7 +645,7 @@ git commit -m "Add image upload to site settings with old-file cleanup"
|
|||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `src/pages/admin/cases.astro`
|
- Modify: `src/pages/admin/cases.astro`
|
||||||
|
|
||||||
- [ ] **Step 1: 加 import 同 `env`**
|
- [x] **Step 1: 加 import 同 `env`**
|
||||||
|
|
||||||
喺 import 區加:
|
喺 import 區加:
|
||||||
|
|
||||||
@@ -661,7 +661,7 @@ const env = getEnv();
|
|||||||
const db = getDb(env.DB);
|
const db = getDb(env.DB);
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: save 時比對並刪舊圖**
|
- [x] **Step 2: save 時比對並刪舊圖**
|
||||||
|
|
||||||
將 `if (action === "add" || action === "save")` 內嘅 `} else {`(更新分支)改成:
|
將 `if (action === "add" || action === "save")` 內嘅 `} else {`(更新分支)改成:
|
||||||
|
|
||||||
@@ -688,7 +688,7 @@ const db = getDb(env.DB);
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 3: delete 時刪圖**
|
- [x] **Step 3: delete 時刪圖**
|
||||||
|
|
||||||
將 `} else if (action === "delete") { ... }` 改成:
|
將 `} else if (action === "delete") { ... }` 改成:
|
||||||
|
|
||||||
@@ -701,7 +701,7 @@ const db = getDb(env.DB);
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: 新增表單改用 ImageField**
|
- [x] **Step 4: 新增表單改用 ImageField**
|
||||||
|
|
||||||
將新增表單內:
|
將新增表單內:
|
||||||
|
|
||||||
@@ -720,7 +720,7 @@ const db = getDb(env.DB);
|
|||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 5: 每張卡改用 ImageField 並移除舊預覽**
|
- [x] **Step 5: 每張卡改用 ImageField 並移除舊預覽**
|
||||||
|
|
||||||
將每張卡內:
|
將每張卡內:
|
||||||
|
|
||||||
@@ -753,17 +753,17 @@ const db = getDb(env.DB);
|
|||||||
)}
|
)}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 6: Build 驗證**
|
- [x] **Step 6: Build 驗證**
|
||||||
|
|
||||||
Run: `npm run build`
|
Run: `npm run build`
|
||||||
Expected: 完成,冇 error。
|
Expected: 完成,冇 error。
|
||||||
|
|
||||||
- [ ] **Step 7: 手動測試**
|
- [x] **Step 7: 手動測試**
|
||||||
|
|
||||||
Run: `npm run dev`
|
Run: `npm run dev`
|
||||||
Expected: `/admin/cases` 新增同每張卡都有上傳;上傳後前台「完成案例」顯示新圖;換圖後舊圖 404;刪除案例後其圖 404。
|
Expected: `/admin/cases` 新增同每張卡都有上傳;上傳後前台「完成案例」顯示新圖;換圖後舊圖 404;刪除案例後其圖 404。
|
||||||
|
|
||||||
- [ ] **Step 8: Commit**
|
- [x] **Step 8: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add src/pages/admin/cases.astro
|
git add src/pages/admin/cases.astro
|
||||||
@@ -777,7 +777,7 @@ git commit -m "Add image upload to cases with cleanup"
|
|||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `src/pages/admin/post/[id].astro`
|
- Modify: `src/pages/admin/post/[id].astro`
|
||||||
|
|
||||||
- [ ] **Step 1: 加 import 同 `env`**
|
- [x] **Step 1: 加 import 同 `env`**
|
||||||
|
|
||||||
喺 import 區加:
|
喺 import 區加:
|
||||||
|
|
||||||
@@ -793,7 +793,7 @@ const env = getEnv();
|
|||||||
const db = getDb(env.DB);
|
const db = getDb(env.DB);
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: 刪文時刪封面**
|
- [x] **Step 2: 刪文時刪封面**
|
||||||
|
|
||||||
將:
|
將:
|
||||||
|
|
||||||
@@ -814,7 +814,7 @@ const db = getDb(env.DB);
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 3: 更新時比對並刪舊封面**
|
- [x] **Step 3: 更新時比對並刪舊封面**
|
||||||
|
|
||||||
喺 `} else {`(更新分支)內、`await db.update(posts)` 之前加:
|
喺 `} else {`(更新分支)內、`await db.update(posts)` 之前加:
|
||||||
|
|
||||||
@@ -838,7 +838,7 @@ const db = getDb(env.DB);
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: 封面欄改用 ImageField**
|
- [x] **Step 4: 封面欄改用 ImageField**
|
||||||
|
|
||||||
將:
|
將:
|
||||||
|
|
||||||
@@ -867,17 +867,17 @@ const db = getDb(env.DB);
|
|||||||
/>
|
/>
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 5: Build 驗證**
|
- [x] **Step 5: Build 驗證**
|
||||||
|
|
||||||
Run: `npm run build`
|
Run: `npm run build`
|
||||||
Expected: 完成,冇 error。
|
Expected: 完成,冇 error。
|
||||||
|
|
||||||
- [ ] **Step 6: 手動測試**
|
- [x] **Step 6: 手動測試**
|
||||||
|
|
||||||
Run: `npm run dev`
|
Run: `npm run dev`
|
||||||
Expected: `/admin/post/new` 同上傳封面;發布後 Blog 列表/文章頁顯示新封面;換封面舊圖 404;刪文封面 404。
|
Expected: `/admin/post/new` 同上傳封面;發布後 Blog 列表/文章頁顯示新封面;換封面舊圖 404;刪文封面 404。
|
||||||
|
|
||||||
- [ ] **Step 7: Commit**
|
- [x] **Step 7: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add "src/pages/admin/post/[id].astro"
|
git add "src/pages/admin/post/[id].astro"
|
||||||
@@ -894,7 +894,7 @@ git commit -m "Add cover image upload to blog posts with cleanup"
|
|||||||
- Modify: `src/data/AGENTS.md`
|
- Modify: `src/data/AGENTS.md`
|
||||||
- Modify: `AGENTS.md`
|
- Modify: `AGENTS.md`
|
||||||
|
|
||||||
- [ ] **Step 1: 更新 `src/AGENTS.md`**
|
- [x] **Step 1: 更新 `src/AGENTS.md`**
|
||||||
|
|
||||||
Admin 段「- **圖片**:一律以 URL 字串處理(暫無上傳)。」改成:
|
Admin 段「- **圖片**:一律以 URL 字串處理(暫無上傳)。」改成:
|
||||||
|
|
||||||
@@ -908,7 +908,7 @@ Admin 段「- **圖片**:一律以 URL 字串處理(暫無上傳)。」改
|
|||||||
- **圖片 endpoints**:`/admin/upload`(POST,上傳去 R2 `MEDIA`);`/media/[...key]`(GET,讀 R2 出圖,設 `Cache-Control: public, max-age=31536000, immutable` 同 ETag/304)。
|
- **圖片 endpoints**:`/admin/upload`(POST,上傳去 R2 `MEDIA`);`/media/[...key]`(GET,讀 R2 出圖,設 `Cache-Control: public, max-age=31536000, immutable` 同 ETag/304)。
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: 更新 `src/lib/AGENTS.md`**
|
- [x] **Step 2: 更新 `src/lib/AGENTS.md`**
|
||||||
|
|
||||||
Ownership 段改成「擁有 `src/lib/` 七個檔案:`env.ts`、`db.ts`、`auth.ts`、`form.ts`、`media.ts`、`ai.ts`、`markdown.ts`。」
|
Ownership 段改成「擁有 `src/lib/` 七個檔案:`env.ts`、`db.ts`、`auth.ts`、`form.ts`、`media.ts`、`ai.ts`、`markdown.ts`。」
|
||||||
|
|
||||||
@@ -920,7 +920,7 @@ Ownership 段改成「擁有 `src/lib/` 七個檔案:`env.ts`、`db.ts`、`aut
|
|||||||
- **`media.ts`**:`MEDIA_PREFIX`(`/media/`)、`mediaUrl(key)`、`mediaKey(url)`、`deleteMedia(env, url)`。`deleteMedia` 只處理 `/media/` 開頭嘅值,外部 URL/空值唔理、唔 throw。
|
- **`media.ts`**:`MEDIA_PREFIX`(`/media/`)、`mediaUrl(key)`、`mediaKey(url)`、`deleteMedia(env, url)`。`deleteMedia` 只處理 `/media/` 開頭嘅值,外部 URL/空值唔理、唔 throw。
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 3: 更新 `src/data/AGENTS.md`**
|
- [x] **Step 3: 更新 `src/data/AGENTS.md`**
|
||||||
|
|
||||||
`settings-fields.ts` 相關:Local Contracts 內「**設定欄位**」bullet 補一句:
|
`settings-fields.ts` 相關:Local Contracts 內「**設定欄位**」bullet 補一句:
|
||||||
|
|
||||||
@@ -928,7 +928,7 @@ Ownership 段改成「擁有 `src/lib/` 七個檔案:`env.ts`、`db.ts`、`aut
|
|||||||
- **圖片欄位**:`FieldType` 有 `"image"`;`hero_image` / `og_image` 用呢個型別,`/admin/settings` 會 render `ImageField`(可上傳去 R2)。
|
- **圖片欄位**:`FieldType` 有 `"image"`;`hero_image` / `og_image` 用呢個型別,`/admin/settings` 會 render `ImageField`(可上傳去 R2)。
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 4: 更新根 `AGENTS.md`**
|
- [x] **Step 4: 更新根 `AGENTS.md`**
|
||||||
|
|
||||||
「關鍵慣例與陷阱」內補一條(放喺環境變數 / 資料層附近):
|
「關鍵慣例與陷阱」內補一條(放喺環境變數 / 資料層附近):
|
||||||
|
|
||||||
@@ -938,12 +938,12 @@ Ownership 段改成「擁有 `src/lib/` 七個檔案:`env.ts`、`db.ts`、`aut
|
|||||||
|
|
||||||
`scripts/` 是 project-level files owned by root,唔使另加 Child DOX。
|
`scripts/` 是 project-level files owned by root,唔使另加 Child DOX。
|
||||||
|
|
||||||
- [ ] **Step 5: Build 驗證**
|
- [x] **Step 5: Build 驗證**
|
||||||
|
|
||||||
Run: `npm run build`
|
Run: `npm run build`
|
||||||
Expected: 完成,冇 error。
|
Expected: 完成,冇 error。
|
||||||
|
|
||||||
- [ ] **Step 6: Commit**
|
- [x] **Step 6: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add AGENTS.md src/AGENTS.md src/lib/AGENTS.md src/data/AGENTS.md
|
git add AGENTS.md src/AGENTS.md src/lib/AGENTS.md src/data/AGENTS.md
|
||||||
@@ -956,12 +956,12 @@ git commit -m "Update DOX docs for R2 image upload"
|
|||||||
|
|
||||||
**Files:** 無(只驗證)
|
**Files:** 無(只驗證)
|
||||||
|
|
||||||
- [ ] **Step 1: 全量 build**
|
- [x] **Step 1: 全量 build**
|
||||||
|
|
||||||
Run: `npm run build`
|
Run: `npm run build`
|
||||||
Expected: 成功。
|
Expected: 成功。
|
||||||
|
|
||||||
- [ ] **Step 2: 端到端手動檢查(`npm run dev`)**
|
- [x] **Step 2: 端到端手動檢查(`npm run dev`)**
|
||||||
|
|
||||||
逐項確認:
|
逐項確認:
|
||||||
|
|
||||||
@@ -974,7 +974,7 @@ Expected: 成功。
|
|||||||
7. 上傳 SVG 或 >8MB 檔 → 顯示中文錯誤、唔會寫入。
|
7. 上傳 SVG 或 >8MB 檔 → 顯示中文錯誤、唔會寫入。
|
||||||
8. 冇 JS(停用 JavaScript)→ URL 欄仍可手動貼,主表單可儲存。
|
8. 冇 JS(停用 JavaScript)→ URL 欄仍可手動貼,主表單可儲存。
|
||||||
|
|
||||||
- [ ] **Step 3: 確認 git 狀態乾淨**
|
- [x] **Step 3: 確認 git 狀態乾淨**
|
||||||
|
|
||||||
Run: `git status --short`
|
Run: `git status --short`
|
||||||
Expected: 冇未 commit 嘅改動(或只有預期內嘅檔案)。
|
Expected: 冇未 commit 嘅改動(或只有預期內嘅檔案)。
|
||||||
|
|||||||
Reference in New Issue
Block a user