refactor(P2/P3): 完成全部7批重构 — 模块化、SSE退避、无障碍、Zod校验、Server组件、Room关系化

批次A:重命名 + 路由拆分
- store.ts → roomRepository.ts,更新全部 import
- blindbox/plan/route.ts 精简为薄路由,业务逻辑抽取到 planActions.ts / planQueries.ts

批次B:blindboxPlanGen.ts 拆分(710行 → src/lib/plan/)
- agentPlan.ts:Agent 工具调用与系统提示
- legacyPlan.ts:非 Agent 备用生成逻辑
- ideaSelection.ts:Idea 筛选与 Slot 映射
- transitEnrichment.ts:交通信息查询与填充
- index.ts:runPlanGeneration 主入口

批次C:SSE 连接稳定性
- useRoomPolling.ts 加入指数退避重连(上限60s,含Jitter)
- plan/stream/route.ts 添加30s心跳 + abort信号清理

批次D:无障碍修复
- Modal:role=dialog、aria-modal、aria-labelledby
- AuthModal:aria-label关闭按钮、tablist/tab/aria-selected
- PlanItemEditModal、QrInviteModal:补全aria-label
- BlindboxPlan:图标按钮aria-label

批次E:Zod 引入
- src/lib/schemas/ai.ts:AI返回值 Schema(IdeaTagsSchema等5个)
- src/lib/schemas/requests.ts:请求体 Schema
- ai.ts 手工验证替换为 Zod safeParse

批次F:Server Components
- achievements/page.tsx → Server Component + AchievementsClient.tsx
- profile/page.tsx → Server Component + ProfileClient.tsx

批次G:Room 关系化模型
- prisma/schema.prisma:新增 RoomMember、RoomRestaurant、RoomLike、RoomSwipe 4张表
- migration:20260302010000_room_relational_model
- roomRepository.ts 完整重写(关系查询+应用锁)
- buildRoomStatus.ts 适配关系查询

测试:全部329个用例通过,修复68个因auth mock缺失导致的测试失败
This commit is contained in:
2026-03-02 20:27:06 +08:00
parent 6bb0e65d4c
commit 4f4220652e
59 changed files with 2369 additions and 1999 deletions
@@ -0,0 +1,69 @@
-- Drop old Room table and recreate with new relational schema
-- Room data is ephemeral (24h TTL), so no data migration needed.
DROP TABLE IF EXISTS "Room";
-- CreateTable
CREATE TABLE "Room" (
"id" TEXT NOT NULL PRIMARY KEY,
"creatorId" TEXT NOT NULL DEFAULT '',
"scene" TEXT NOT NULL DEFAULT 'eat',
"locked" BOOLEAN NOT NULL DEFAULT false,
"match" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expiresAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- CreateIndex
CREATE INDEX "Room_expiresAt_idx" ON "Room"("expiresAt");
-- CreateTable
CREATE TABLE "RoomMember" (
"id" TEXT NOT NULL PRIMARY KEY,
"roomId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"kicked" BOOLEAN NOT NULL DEFAULT false,
"joinedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "RoomMember_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "Room" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "RoomMember_roomId_userId_key" ON "RoomMember"("roomId", "userId");
CREATE INDEX "RoomMember_roomId_idx" ON "RoomMember"("roomId");
-- CreateTable
CREATE TABLE "RoomRestaurant" (
"id" TEXT NOT NULL PRIMARY KEY,
"roomId" TEXT NOT NULL,
"restaurantData" TEXT NOT NULL,
CONSTRAINT "RoomRestaurant_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "Room" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateIndex
CREATE INDEX "RoomRestaurant_roomId_idx" ON "RoomRestaurant"("roomId");
-- CreateTable
CREATE TABLE "RoomLike" (
"id" TEXT NOT NULL PRIMARY KEY,
"roomId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"restaurantId" TEXT NOT NULL,
CONSTRAINT "RoomLike_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "Room" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "RoomLike_roomId_userId_restaurantId_key" ON "RoomLike"("roomId", "userId", "restaurantId");
CREATE INDEX "RoomLike_roomId_idx" ON "RoomLike"("roomId");
-- CreateTable
CREATE TABLE "RoomSwipe" (
"id" TEXT NOT NULL PRIMARY KEY,
"roomId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"count" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "RoomSwipe_roomId_fkey" FOREIGN KEY ("roomId") REFERENCES "Room" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "RoomSwipe_roomId_userId_key" ON "RoomSwipe"("roomId", "userId");
CREATE INDEX "RoomSwipe_roomId_idx" ON "RoomSwipe"("roomId");