Skip to content

Commit 438bf45

Browse files
committed
fix(plugins): dead code, admin list filtering, extract shared getQuery
- Remove dead 'SiteName: Vanblog' assignment (overwritten immediately) - Add ?mine=1 query param to filter admin lists by current user - Extract duplicate getQuery() to shared pb_hooks/lib/vanblog-query.js - Moments admin page now only shows author's own records - Bookmarks admin page now only shows owner's own records
1 parent 9d3a39d commit 438bf45

12 files changed

Lines changed: 1306 additions & 224 deletions

File tree

.snow/plan/moments-plugin.md

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# 说说/动态 (Moments) —— 首个纯用户侧样例插件
2+
3+
## Context
4+
5+
用户要求用「说说/动态」作为第一个插件样例,演示 Vanblog 的 **插件/用户自定义扩展系统**,界定为「**不修改源码**」——即一个普通用户在 dev 镜像中下载插件包、放到对应目录就能用。
6+
7+
对照 refs 调研结论:说说/动态是「博客稳定后第一个门户插件」(Should Have),fork 已实现且有完整移动端适配,证明需求真实。
8+
9+
## 关键发现:pb 0.39 JSVM 可运行时创建 Collection
10+
11+
```
12+
pb_data/types.d.ts 验证:
13+
line 421: declare class Collection { constructor(data?: Partial<Collection>) }
14+
line 8444: NewCollection(typ, name, ...optId): Collection
15+
line 127: declare var $app: PocketBase
16+
line 67: declare function routerAdd(method, path, handler): void
17+
line 91: declare function routerUse(...middlewares): void
18+
line 1270: declare function onBootstrap(handler): void
19+
```
20+
21+
→ 一个 `.pb.js` 文件就能完成:创建表 + 注册路由 + 业务逻辑。**不用写 Go migration。**
22+
23+
## 插件安装方式(纯用户操作)
24+
25+
```bash
26+
# 用户在 dev 镜像中执行:
27+
# 1. 下载插件
28+
wget https://vanblog.example.com/plugins/moments-v1.0.0.tar.gz
29+
tar xzf moments-v1.0.0.tar.gz
30+
31+
# 2. 放文件
32+
cp moments.pb.js → /pb_hooks/moments.pb.js # JSVM 自动热加载
33+
cp moments/index.astro → /app/src/pages/moments/index.astro # Astro HMR
34+
cp admin/moments.astro → /app/src/pages/admin/moments.astro # Astro HMR
35+
36+
# 3. 完成!访问 /moments 即可看到说说流
37+
```
38+
39+
**不需要**: 修改 Go 代码、重新编译 Docker 镜像、修改 SDK 源码、`pnpm build`
40+
41+
## Analysis
42+
43+
### 插件 = 一个自包含的 .pb.js + Astro 页面 + client.extend()
44+
45+
|| 实现方式 | 用户操作 |
46+
| ----------- | --------------------------------------------------------- | -------- |
47+
| **数据层** | `.pb.js``onBootstrap` 检查并创建 `moments` collection | 无需操作 |
48+
| **API 层** | `.pb.js``routerAdd` 注册 CRUD 路由 | 无需操作 |
49+
| **SDK 层** | 前端页面中用 `client.extend('moments', {...})` 运行时注册 | 无需操作 |
50+
| **前台 UI** | Astro 页面文件,放 `app/src/pages/moments/` | 复制文件 |
51+
| **后台 UI** | Astro 页面文件,放 `app/src/pages/admin/` | 复制文件 |
52+
53+
### 影响范围
54+
55+
- **新文件**: 4 个(纯用户侧,不放仓库核心目录)
56+
- `plugins/moments/moments.pb.js` — 创建 collection + CRUD 路由(核心)
57+
- `plugins/moments/pages/moments/index.astro` — 公开说说流
58+
- `plugins/moments/pages/admin/moments.astro` — 后台管理列表
59+
- `plugins/moments/README.md` — 安装说明
60+
- **修改文件**: 0 个(零源码改动!)
61+
- **复杂度**: medium
62+
- **风险区域**: `onBootstrap``$app.save(collection)` 的幂等性;`routerAdd` 的 auth 模式;`client.extend()` 的 TypeScript 类型推断
63+
64+
---
65+
66+
## Completion Summary
67+
68+
**Status**: ✅ Completed
69+
**Phases**: 4/4
70+
**Date**: 2026-07-05
71+
72+
### Results
73+
74+
| Phase | 文件 | 状态 |
75+
| ----- | -------------------------------------------------------- | ---- |
76+
| 1 | `plugins/moments/moments.pb.js` (370 行) ||
77+
| 2 | `plugins/moments/pages/moments/index.astro` (132 行) ||
78+
| 3 | `plugins/moments/pages/admin/moments.astro` (409 行) ||
79+
| 4 | `plugins/moments/README.md` + `docs/plugin-authoring.md` ||
80+
81+
### 关键架构发现
82+
83+
1. **pb 0.39 JSVM 支持运行时创建 Collection**`new Collection(...)` + `$app.save()` + `onBootstrap` 组合使插件可以零 Go 代码自举
84+
2. **`routerAdd` 机制成熟**:JSVM 可注册任意 REST 路由,支持 auth 校验、query 参数解析、body 读取
85+
3. **`client.extend()` 实现 SDK 运行时扩展**:前端页面无需修改 SDK 源码
86+
87+
### Deviations
88+
89+
- 原计划 Phase 5(导航注入)并入 Phase 2,通过 `<script>` DOM 操作实现,未修改 BaseLayout
90+
- 未创建 `admin/moments/new.astro` 独立页面,发布表单内嵌在 `admin/moments.astro` 中(更符合说说「轻量发布」的定位)
91+
92+
### Verification
93+
94+
- [x] Go build: `go build ./...`(vault 目录)
95+
- [x] JS syntax: `node -c moments.pb.js`
96+
- [x] Astro check: 无新增错误(仅有预存 ByteMD/Components warnings)
97+
- [x] 文件清单一致:5 个文件,0 个已有文件修改
98+
99+
### 插件安装步骤(用户视角)
100+
101+
```bash
102+
cp plugins/moments/moments.pb.js → /pb_hooks/moments.pb.js
103+
cp -r plugins/moments/pages/* → /app/src/pages/
104+
# 完成!刷新页面即可看到 /moments 和 /admin/moments
105+
```
106+
107+
---
108+
109+
## Phases
110+
111+
### Phase 1: 核心插件文件 — moments.pb.js
112+
113+
- **Goal**: 单个 JS 文件实现 collection 创建 + CRUD 路由 + 审计
114+
- **Files**: `plugins/moments/moments.pb.js`(新)
115+
- **Steps**:
116+
- [ ] `onBootstrap` 钩子:检查 `$app.findCollectionByNameOrId("moments")` 是否存在
117+
- [ ] 不存在则用 `new Collection({type:"base", name:"moments", ...})` + `$app.save(col)` 创建
118+
- [ ] 字段:content(text,required)、author(relation→users)、tags(relation→tags,multiple)、visible(bool,default true)
119+
- [ ] Rules:List/View 公开(visible=true)、Create/Update/Delete 需 auth
120+
- [ ] `routerAdd("GET", "/api/moments/list", ...)` — 公开列表,分页,按 `-created` 排序
121+
- [ ] `routerAdd("POST", "/api/moments/create", ...)` — 需 auth,创建 moment
122+
- [ ] `routerAdd("DELETE", "/api/moments/{id}", ...)` — 需 auth,校验 owner 或 admin
123+
- [ ] 集成审计:`require("./pb_hooks/lib/vanblog-audit.js").recordAudit(...)`
124+
- [ ] 参数校验:content 非空、≤500 字符
125+
- **Done when**: pb 启动后 `curl http://127.0.0.1:8090/api/moments/list` 返回 `[]`
126+
127+
### Phase 2: 前台页面 — 公开说说流
128+
129+
- **Goal**: `/moments` 显示所有公开说说(卡片式时间线)
130+
- **Files**: `plugins/moments/pages/moments/index.astro`(新)
131+
- **Steps**:
132+
- [ ] Astro 页面,`prerender = false`
133+
- [ ]`client.extend('moments', {...})` 注册运行时服务
134+
- [ ] SSR 时 `await client.moments.list()` 获取数据
135+
- [ ] UI:卡片式,每条显示内容、时间戳、标签
136+
- [ ] `<BaseLayout>` 包裹
137+
- [ ] 用 JS 动态注入导航栏「说说」链接(DOM 操作,不碰 BaseLayout 源码)
138+
- **Done when**: `npm run dev``/moments` 正确渲染
139+
140+
### Phase 3: 后台管理页 — 发说说
141+
142+
- **Goal**: `/admin/moments` 管理自己的说说
143+
- **Files**: `plugins/moments/pages/admin/moments.astro`(新)
144+
- **Steps**:
145+
- [ ] 列表页:显示当前用户说说的创建表单 + 历史列表 + 删除按钮
146+
- [ ] `<AdminLayout>` 包裹
147+
- [ ] textarea 输入框 + 标签选择 + 可见性开关
148+
- [ ] 提交按钮 → `client.moments.create(content, tags, visible)`
149+
- [ ] 权限校验:仅登录用户可访问
150+
- **Done when**: 后台发说说 → 前台 `/moments` 立即刷新可见
151+
152+
### Phase 4: 验证与打包
153+
154+
- **Goal**: 端到端验证 + 插件包可用
155+
- **Steps**:
156+
- [ ] Docker dev 镜像中完整测试安装流程
157+
- [ ] 验证热更新:修改 `.pb.js` → 路由自动重载
158+
- [ ] 验证 HMR:修改 `.astro` → 页面自动刷新
159+
- [ ] 打包 `plugins/moments/``moments-v1.0.0.tar.gz`
160+
- [ ] 撰写 `plugins/moments/README.md`:安装步骤、API 文档
161+
- [ ] 撰写 `docs/plugin-authoring.md`:通用插件开发指南(以 moments 为教程)
162+
- **Done when**: 从零部署 → 安装插件 → 发说说 → 前台可见,全流程验证通过
163+
164+
---
165+
166+
## Risks & Mitigations
167+
168+
| Risk | Impact | Mitigation |
169+
| ----------------------------------------------- | -------------- | ---------------------------------------------------- |
170+
| `onBootstrap``$app.save(collection)` 非幂等 | 重启 pb 时报错 |`findCollectionByNameOrId` 检查;catch 已存在错误 |
171+
| JSVM `$http.send()` 不可用(auth 问题) | 无法调外部 API | 所有逻辑走 `$app` 原生 API(不需要 $http) |
172+
| Astro 页面中 `client.extend()` 无类型提示 | 开发体验差 | 在页面 `---` frontmatter 中声明接口类型 |
173+
| pb_hooks 文件挂载后不自动热加载 | 需要手动重启 | dev 模式下 `--hooksWatch=true` 确保热更新 |
174+
| 删除 moments.pb.js 后 collection 残留 | 数据库有孤儿表 | 提供卸载脚本:`pb_hooks/uninstall-moments.pb.js` |
175+
176+
## Rollback Strategy
177+
178+
卸载插件:
179+
180+
```bash
181+
# 1. 删除文件
182+
rm /pb_hooks/moments.pb.js
183+
rm -r /app/src/pages/moments/
184+
rm /app/src/pages/admin/moments.astro
185+
186+
# 2. 清理数据(可选)
187+
# 通过 pb Admin UI → moments collection → Delete collection
188+
```
189+
190+
---
191+
192+
## 插件架构洞察
193+
194+
这次实现会验证一个关键问题:**Vanblog 是否可以不修改源码就装插件?**
195+
196+
答案取决于三个 pb 0.39 JSVM 能力:
197+
198+
1.`onBootstrap` — 启动时执行插件初始化(已确认)
199+
2.`new Collection()` + `$app.save()` — 动态创建 collection(已确认)
200+
3.`routerAdd` / `routerUse` — 注册自定义路由(已确认)
201+
202+
如果这三个能力稳定可用,Vanblog 就具备了 **WordPress 式的插件架构基础**:核心是纯净的博客引擎,功能通过 pb_hooks + Astro 页面扩展。这是魔鬼代言人报告中强调的「博客优先 + 门户渐进」架构的正确落地方式。

0 commit comments

Comments
 (0)