docs: p1b acceptance record
- P1b-验收记录.md: A08 (viewer.spec + journey), A12 partial (viewer
i18n switch/fallback + admin template i18n; data-lang is P4b), R09
(assets.e2e + admin upload + viewer render + journey banner), P1a
final-review 4 items registered (47fb81a, absent from P1a record),
9-row deviation table (ports 5175/5176 + cms conflict disclosure,
staff-wide account lookup, art-design-pro v3.0.2 pin + lint ignore,
no thumbnails in AdminPhoto, session-scoped banner preview,
errorCode/pending_upload omissions, useCursorList, esbuild
onlyBuiltDependencies), P2 handover, per-package test stats
- master roadmap: P1 fully complete (P1a + P1b), tag p1; G1 formally
registered as skipped (no OSS credentials provided)
- P1b plan: all task checkboxes marked with completion notes
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -23,93 +23,93 @@
|
||||
- Create: `services/api/src/shared/pagination.ts`(parseCursor/parseLimit 从 albums.controller.ts 迁入;members/uploads/public 改引用;删除原处导出)
|
||||
- Modify: `infra/docker-compose.yml`(宿主绑定改 `127.0.0.1:3306:3306` 等四项——注意 Windows 下 Docker Desktop 需重建容器 `docker compose up -d --force-recreate`,数据卷保留;同步把 `.env.example` 各 localhost 改 127.0.0.1 防 ::1 解析边缘)
|
||||
|
||||
- [ ] Step 1: 失败测试先行(404 未知路由 → code NOT_FOUND;响应无 x-powered-by)
|
||||
- [ ] Step 2: 实现四项 + 契约 gen + 全量回归绿(api e2e 120 基线不降)
|
||||
- [ ] Step 3: Commit `chore(api): p1a final-review hygiene items`
|
||||
- [x] Step 1: 失败测试先行(404 未知路由 → code NOT_FOUND;响应无 x-powered-by)
|
||||
- [x] Step 2: 实现四项 + 契约 gen + 全量回归绿(api e2e 120 基线不降)
|
||||
- [x] Step 3: Commit `chore(api): p1a final-review hygiene items`
|
||||
|
||||
## Task 2: 契约扩展 —— assets 上传 / 管理照片列表 / 账号查询
|
||||
|
||||
**Files:**
|
||||
- Modify: `packages/contracts/openapi.yaml`(+gen 提交)
|
||||
|
||||
- [ ] Step 1: 新增路径(字段以技术方案 §4 album_assets 与既定模式为准):
|
||||
- [x] Step 1: 新增路径(字段以技术方案 §4 album_assets 与既定模式为准):
|
||||
- `POST /albums/{albumId}/assets`:{kind: banner|cover|logo, locale?, filename, bytes, contentType} → 201 {assetId, objectKey, credential(UploadCredential 同形)};`POST /albums/{albumId}/assets/{assetId}/complete` → 200 {assetId, url(短期签名)}(服务端 head 核验后挂到相册;同 kind+locale 替换语义=新行生效、旧行置空,响应取最新——公开响应已按此取 latest)
|
||||
- `GET /albums/{albumId}/photos`:管理列表(query: visibility?、categoryId?、cursor、limit)→ PhotoRecord 分页(含未发布/隐藏/删除外全部状态;上传人信息 account/displayName)
|
||||
- `GET /admin/users?accountPrefix=&limit=`:admin/owner 可用,按 account 前缀(≥2 字符)搜索激活用户,返回 {id, account, displayName} 脱敏列表(邀请协作者前置;不返回邮箱手机)
|
||||
- 语义与错误沿用既有码(ALBUM_NOT_FOUND/AUTH_FORBIDDEN/VALIDATION_FAILED/UPLOAD_VERIFY_FAILED)
|
||||
- [ ] Step 2: `gen` + `lint:api` 绿;⏸ 快速确认点:向用户展示三组新端点要点(可跳过逐字段评审)
|
||||
- [ ] Step 3: Commit `feat(contracts): p1b asset upload, management photo list, account lookup`
|
||||
- [x] Step 2: `gen` + `lint:api` 绿;⏸ 快速确认点:向用户展示三组新端点要点(可跳过逐字段评审)
|
||||
- [x] Step 3: Commit `feat(contracts): p1b asset upload, management photo list, account lookup`
|
||||
|
||||
## Task 3: assets 上传链路(后端)
|
||||
|
||||
**Files:**
|
||||
- Create: `services/api/src/albums/assets.service.ts` + controller 挂 AlbumsModule;`test/assets.e2e-spec.ts`
|
||||
|
||||
- [ ] Step 1: 失败 e2e:owner 上传 banner(presign PUT 真实 jpeg → complete)→ 201/200;DB album_assets 行 + MinIO 对象;`GET /public/albums/{token}` banner 返回可取回的签名 URL;替换上传(同 kind 新 complete)→ 公开响应取新 URL;非 owner 403;bytes/contentType 校验同 uploads 域;kind=logo locale 维度可选;audit album.asset.add
|
||||
- [ ] Step 2: 实现(复用 uploads 域的 presign/核验/大小白名单模式;对象键 `photo-live/dev/albums/{albumId}/assets/{assetId}.{ext}`;无幂等 clientTaskId 需求——按 assetId 一次性会话即可)
|
||||
- [ ] Step 3: 通过 + Commit `feat(api): album asset upload chain`
|
||||
- [x] Step 1: 失败 e2e:owner 上传 banner(presign PUT 真实 jpeg → complete)→ 201/200;DB album_assets 行 + MinIO 对象;`GET /public/albums/{token}` banner 返回可取回的签名 URL;替换上传(同 kind 新 complete)→ 公开响应取新 URL;非 owner 403;bytes/contentType 校验同 uploads 域;kind=logo locale 维度可选;audit album.asset.add
|
||||
- [x] Step 2: 实现(复用 uploads 域的 presign/核验/大小白名单模式;对象键 `photo-live/dev/albums/{albumId}/assets/{assetId}.{ext}`;无幂等 clientTaskId 需求——按 assetId 一次性会话即可)
|
||||
- [x] Step 3: 通过 + Commit `feat(api): album asset upload chain`
|
||||
|
||||
## Task 4: 管理照片列表 + 账号查询(后端)
|
||||
|
||||
**Files:**
|
||||
- Modify: `services/api/src/photos/`(新增 list 管理);Create `services/api/src/admin/users-lookup.*`;`test/photos-admin.e2e-spec.ts`
|
||||
|
||||
- [ ] Step 1: 失败 e2e:owner/admin 查管理列表(全状态可见、visibility 过滤、分页稳定、uploader 脱敏信息);collaborator → 403;账号查询:admin/owner 前缀搜索(<2 字符 400、不泄露凭据字段、限 20 条);outlier 401/403
|
||||
- [ ] Step 2: 实现(共享 keyset;账号查询 `WHERE account LIKE prefix%` 走 bin 校对大小写敏感——文档说明)
|
||||
- [ ] Step 3: 通过 + Commit `feat(api): management photo list and account lookup`
|
||||
- [x] Step 1: 失败 e2e:owner/admin 查管理列表(全状态可见、visibility 过滤、分页稳定、uploader 脱敏信息);collaborator → 403;账号查询:admin/owner 前缀搜索(<2 字符 400、不泄露凭据字段、限 20 条);outlier 401/403
|
||||
- [x] Step 2: 实现(共享 keyset;账号查询 `WHERE account LIKE prefix%` 走 bin 校对大小写敏感——文档说明)
|
||||
- [x] Step 3: 通过 + Commit `feat(api): management photo list and account lookup`(完成:8ba98ac;**用户裁决 2026-09-18**:账号查询维持"全体已认证 staff"策略;dev 端口改为 viewer 5175 / admin 5176——避免与用户 cms 项目 5173 冲突)
|
||||
|
||||
## Task 5: viewer 脚手架
|
||||
|
||||
**Files:**
|
||||
- Create: `apps/viewer/`(Vite + Vue3 + TS:package.json @photo/viewer、vite.config(proxy /v1 → :8080 dev)、router(`/a/:token` 活动页、`/a/:token/p/:photoId` 单图定位)、i18n(zh-CN/en 词条文件、localStorage 持久、检测浏览器语言)、api client(fetch 封装 + 契约生成类型 + 错误码→文案映射表)、Pinia 不引入(保持轻量))
|
||||
|
||||
- [ ] Step 1: `pnpm create vite` 起最小骨架并裁剪至需求;加入 workspace;lint/typecheck/build/**test(vitest)/test:e2e(Playwright)** 全部接入 turbo(Task 12 回归门依赖)
|
||||
- [ ] Step 2: 冒烟:dev server 起、路由可达、i18n 切换生效(vitest 组件测试 2-3 个)
|
||||
- [ ] Step 3: Commit `feat(viewer): vite vue3 scaffold with routing and i18n`
|
||||
- [x] Step 1: `pnpm create vite` 起最小骨架并裁剪至需求;加入 workspace;lint/typecheck/build/**test(vitest)/test:e2e(Playwright)** 全部接入 turbo(Task 12 回归门依赖)
|
||||
- [x] Step 2: 冒烟:dev server 起、路由可达、i18n 切换生效(vitest 组件测试 2-3 个)
|
||||
- [x] Step 3: Commit `feat(viewer): vite vue3 scaffold with routing and i18n`
|
||||
|
||||
## Task 6: viewer 活动页(信息区 + 空态 + 时区)
|
||||
|
||||
- [ ] Step 1: 组件测试 + 实现:宣传图(banner 签名 URL、无则优雅降级布局)、活动信息(标题/介绍/时间按相册 timezone 以 Intl.DateTimeFormat 展示、地点)、语言切换入口(易发现)、空相册本地化空态("照片即将上线")、viewCount 占位(null 不渲染)
|
||||
- [ ] Step 2: Playwright 前置:`apps/viewer/e2e/`(配置 + fixtures:seed 脚本起真实后端或 mock —— 决策:用真实后端 + Playwright,compose 依赖;fixture 走 API seed)
|
||||
- [ ] Step 3: Commit `feat(viewer): album page with locale fallback and timezone display`
|
||||
- [x] Step 1: 组件测试 + 实现:宣传图(banner 签名 URL、无则优雅降级布局)、活动信息(标题/介绍/时间按相册 timezone 以 Intl.DateTimeFormat 展示、地点)、语言切换入口(易发现)、空相册本地化空态("照片即将上线")、viewCount 占位(null 不渲染)
|
||||
- [x] Step 2: Playwright 前置:`apps/viewer/e2e/`(配置 + fixtures:seed 脚本起真实后端或 mock —— 决策:用真实后端 + Playwright,compose 依赖;fixture 走 API seed)
|
||||
- [x] Step 3: Commit `feat(viewer): album page with locale fallback and timezone display`
|
||||
|
||||
## Task 7: viewer 瀑布流
|
||||
|
||||
- [ ] Step 1: 组件/交互实现:双列瀑布流(CSS columns 或绝对定位两列——选绝对定位方案以支持滚动位置恢复;条目高按 width/height 预留)、IntersectionObserver 懒加载图片(签名 URL 加载失败重试一次=刷新签名)、分类筛选 chips、游标分页无限滚动(nextCursor 耗尽停)、新照片轮询提示(30s 间隔 polling 最新页,有新图显示顶部提示条"新照片"点击刷新——不自动插入避免跳动,需求 1.2 建议基线)
|
||||
- [ ] Step 2: Playwright:滚动加载两页、分类切换、(轮询提示用注入延时验证)
|
||||
- [ ] Step 3: Commit `feat(viewer): waterfall feed with lazy load, filter and new-photo hint`
|
||||
- [x] Step 1: 组件/交互实现:双列瀑布流(CSS columns 或绝对定位两列——选绝对定位方案以支持滚动位置恢复;条目高按 width/height 预留)、IntersectionObserver 懒加载图片(签名 URL 加载失败重试一次=刷新签名)、分类筛选 chips、游标分页无限滚动(nextCursor 耗尽停)、新照片轮询提示(30s 间隔 polling 最新页,有新图显示顶部提示条"新照片"点击刷新——不自动插入避免跳动,需求 1.2 建议基线)
|
||||
- [x] Step 2: Playwright:滚动加载两页、分类切换、(轮询提示用注入延时验证)
|
||||
- [x] Step 3: Commit `feat(viewer): waterfall feed with lazy load, filter and new-photo hint`
|
||||
|
||||
## Task 8: viewer 大图查看器
|
||||
|
||||
- [ ] Step 1: 实现:点击开大图(browse URL)、左右切换(保持列表浏览序列)、双指/滚轮缩放(CSS transform 受控)、关闭回原列表位置(滚动锚点)、单图定位路由(`/a/:token/p/:photoId`:先加载该照片再拉相邻页)、404 照片本地化提示、签名过期→静默重取单图接口刷新
|
||||
- [ ] Step 2: Playwright:开-关回位、切换序列、深链直达
|
||||
- [ ] Step 3: Commit `feat(viewer): photo viewer with zoom, navigation and deep link`
|
||||
- [x] Step 1: 实现:点击开大图(browse URL)、左右切换(保持列表浏览序列)、双指/滚轮缩放(CSS transform 受控)、关闭回原列表位置(滚动锚点)、单图定位路由(`/a/:token/p/:photoId`:先加载该照片再拉相邻页)、404 照片本地化提示、签名过期→静默重取单图接口刷新
|
||||
- [x] Step 2: Playwright:开-关回位、切换序列、深链直达
|
||||
- [x] Step 3: Commit `feat(viewer): photo viewer with zoom, navigation and deep link`
|
||||
|
||||
## Task 9: admin 引入 Art Design Pro
|
||||
|
||||
**Files:**
|
||||
- Create: `apps/admin/`(模板引入——git clone 指定版本或下载 release zip;锁定版本记录在 package.json + README 注明来源与裁剪说明)
|
||||
|
||||
- [ ] Step 1: 引入并裁剪(保留布局/登录/权限框架与依赖最小集;移除 demo 页;name @photo/admin 入 workspace;lint/typecheck/build 接 turbo——模板自带 eslint 需对齐根配置或豁免说明)
|
||||
- [ ] Step 2: 登录对接:模板登录页改调 `POST /v1/auth/password`(sessionToken 内存+sessionStorage、401 统一跳登录、refresh 定时静默续期);dev proxy /v1 → 8080
|
||||
- [ ] Step 3: 冒烟(手动 + vitest 冒烟);Commit `feat(admin): art-design-pro integration with local session auth`
|
||||
- [x] Step 1: 引入并裁剪(保留布局/登录/权限框架与依赖最小集;移除 demo 页;name @photo/admin 入 workspace;lint/typecheck/build 接 turbo——模板自带 eslint 需对齐根配置或豁免说明)
|
||||
- [x] Step 2: 登录对接:模板登录页改调 `POST /v1/auth/password`(sessionToken 内存+sessionStorage、401 统一跳登录、refresh 定时静默续期);dev proxy /v1 → 8080
|
||||
- [x] Step 3: 冒烟(手动 + vitest 冒烟);Commit `feat(admin): art-design-pro integration with local session auth`
|
||||
|
||||
## Task 10: admin 相册与成员页
|
||||
|
||||
- [ ] Step 1: 实现:相册列表(游标分页、开关状态展示)、创建(多语言表单 zh-CN/en、校验同契约)、编辑(开关独立、translations 全量替换语义提示);相册详情含成员管理(账号前缀搜索邀请、移除、角色展示)与分类管理(增改删)——用 Task 2 的账号查询与管理列表接口
|
||||
- [ ] Step 2: Playwright(admin 流:登录→建相册→邀成员→传 banner→管理列表可见照片状态)可与 Task 11 合并冒烟
|
||||
- [ ] Step 3: Commit `feat(admin): album management pages with members and categories`
|
||||
- [x] Step 1: 实现:相册列表(游标分页、开关状态展示)、创建(多语言表单 zh-CN/en、校验同契约)、编辑(开关独立、translations 全量替换语义提示);相册详情含成员管理(账号前缀搜索邀请、移除、角色展示)与分类管理(增改删)——用 Task 2 的账号查询与管理列表接口
|
||||
- [x] Step 2: Playwright(admin 流:登录→建相册→邀成员→传 banner→管理列表可见照片状态)可与 Task 11 合并冒烟
|
||||
- [x] Step 3: Commit `feat(admin): album management pages with members and categories`
|
||||
|
||||
## Task 11: admin 上传与 viewer 联调冒烟(Playwright 全流程)
|
||||
|
||||
- [ ] Step 1: Playwright 完整剧本(真实后端):admin 登录→建相册(zh+en)→传 banner→建分类→邀 staffB;staffB(API 或 UI 若简单)上传 3 张真图→worker 处理(compose 内起 dev:worker 或测试内直调);viewer:开活动页(en)→瀑布流 3 张→分类过滤→大图切换→深链;admin 隐藏 1 张→viewer 列表变 2;空相册态冒烟另起一例
|
||||
- [ ] Step 2: 全绿 + Commit `test(e2e): p1b cross-frontend playwright journey`
|
||||
- [x] Step 1: Playwright 完整剧本(真实后端):admin 登录→建相册(zh+en)→传 banner→建分类→邀 staffB;staffB(API 或 UI 若简单)上传 3 张真图→worker 处理(compose 内起 dev:worker 或测试内直调);viewer:开活动页(en)→瀑布流 3 张→分类过滤→大图切换→深链;admin 隐藏 1 张→viewer 列表变 2;空相册态冒烟另起一例(完成:40f8f37——`apps/viewer/e2e/journey.spec.ts`。staffB 走 API 上传、worker 测试内直调 PublishService 发布事务(BullMQ 队列消费路径由 api full-chain e2e 覆盖);viewer 以 zh 打开后切 en 断言内容随切再回切;admin dev 以 viewer playwright 第三个 webServer 拉起(5176 复用);空相册态并入同一剧本收尾)
|
||||
- [x] Step 2: 全绿 + Commit `test(e2e): p1b cross-frontend playwright journey`(完成:40f8f37)
|
||||
|
||||
## Task 12: P1b 回归门 + P1 阶段验收记录
|
||||
|
||||
- [ ] Step 1: 全量回归 `pnpm lint && pnpm typecheck && pnpm test && pnpm e2e && pnpm build` 全绿(含新增 viewer/admin 任务)
|
||||
- [ ] Step 2: 写 `docs/plans/progress/P1b-验收记录.md`(A08 基础级、A12 部分、R09、**P1a 终审修缮 4 项的落实登记**——P1a 记录偏差表未含、此处补登、偏差登记、P2 移交:outbox 消费/限速/热门排序);更新总纲 P1 行进度注记(P1 整体完成、G1 可选关口状态)
|
||||
- [ ] Step 3: `git tag p1`;Commit `docs: p1b acceptance record`
|
||||
- [x] Step 1: 全量回归 `pnpm lint && pnpm typecheck && pnpm test && pnpm e2e && pnpm build` 全绿(含新增 viewer/admin 任务)(完成:lint 7/7、typecheck 10/10、build 7/7;单测 148 + e2e 176 = 324)
|
||||
- [x] Step 2: 写 `docs/plans/progress/P1b-验收记录.md`(A08 基础级、A12 部分、R09、**P1a 终审修缮 4 项的落实登记**——P1a 记录偏差表未含、此处补登、偏差登记、P2 移交:outbox 消费/限速/热门排序);更新总纲 P1 行进度注记(P1 整体完成、G1 可选关口状态)(完成:G1 凭证未提供,正式登记跳过)
|
||||
- [x] Step 3: `git tag p1`;Commit `docs: p1b acceptance record`(完成:tag 打在本记录提交)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user