Files
citywalk-stamp/packages/server/src/routes/stamps.ts
YANG JIANKUAN 2c179cd19a feat: 落地页改造 + 集章弹窗全状态品牌/奖品说明
- 落地页:顶部改为活动海报,底部替换为「活动规则」5 条编号列表
- 集章收集弹窗 (StampPopup):新增奖品规则卡片展示 Prize 信息
- 集章册 (AlbumPage / StampGrid):所有状态图章均可点击查看详情
- 兑换弹窗 (RedeemModal):新增 uncollected 分支,统一承载未收集/
  已集齐/已兑换三种状态;新增可选品牌说明区
- 后端 /api/stamps/:id 补充返回 prize 字段
- 管理后台字段标签改名:备注 → 品牌说明;奖品描述 → 奖品说明
- 新增一次性脚本 update-brand-rules,批量写入 16 条品牌权益文案

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 17:16:50 +08:00

113 lines
3.2 KiB
TypeScript

import { Router } from "express";
import { prisma } from "@stamp/shared";
import { optionalAuth, requireAuth } from "../middleware/auth.js";
const router = Router();
router.get("/", optionalAuth, async (req, res) => {
const stamps = await prisma.stamp.findMany({
where: { enabled: true },
orderBy: { sortOrder: "asc" },
include: { prize: true },
});
const collectionMap = new Map<string, Date>();
const redeemedSet = new Set<string>();
if (req.userId) {
const [userCollections, userRedemptions] = await Promise.all([
prisma.collection.findMany({
where: { userId: req.userId },
select: { stampId: true, collectedAt: true },
}),
prisma.redemption.findMany({
where: { userId: req.userId },
select: { stampId: true },
}),
]);
userCollections.forEach((c) => collectionMap.set(c.stampId, c.collectedAt));
userRedemptions.forEach((r) => redeemedSet.add(r.stampId));
}
const data = stamps.map((s) => ({
id: s.id,
name: s.name,
note: s.note,
imageColor: s.imageColor,
imageGrey: s.imageGrey,
sortOrder: s.sortOrder,
collected: collectionMap.has(s.id),
collectedAt: collectionMap.get(s.id)?.toISOString() ?? null,
redeemed: redeemedSet.has(s.id),
prize: s.prize
? {
id: s.prize.id,
name: s.prize.name,
description: s.prize.description,
stock: s.prize.stock,
enabled: s.prize.enabled,
}
: null,
}));
res.json({ success: true, data });
});
router.get("/:id", async (req, res) => {
const stamp = await prisma.stamp.findUnique({
where: { id: req.params.id },
include: { prize: true },
});
if (!stamp) {
res.status(404).json({ success: false, error: { code: "NOT_FOUND", message: "图章不存在" } });
return;
}
res.json({
success: true,
data: {
id: stamp.id,
name: stamp.name,
note: stamp.note,
imageColor: stamp.imageColor,
imageGrey: stamp.imageGrey,
sortOrder: stamp.sortOrder,
prize: stamp.prize
? {
id: stamp.prize.id,
name: stamp.prize.name,
description: stamp.prize.description,
stock: stamp.prize.stock,
enabled: stamp.prize.enabled,
}
: null,
},
});
});
router.post("/:id/collect", requireAuth, async (req, res) => {
const stamp = await prisma.stamp.findUnique({ where: { id: req.params.id, enabled: true } });
if (!stamp) {
res.status(404).json({ success: false, error: { code: "NOT_FOUND", message: "图章不存在" } });
return;
}
const existing = await prisma.collection.findUnique({
where: { userId_stampId: { userId: req.userId!, stampId: stamp.id } },
});
if (existing) {
res.status(409).json({ success: false, error: { code: "ALREADY_COLLECTED", message: "你已经收集了这枚图章" } });
return;
}
const collection = await prisma.collection.create({
data: { userId: req.userId!, stampId: stamp.id },
});
res.json({
success: true,
data: { id: collection.id, stampId: collection.stampId, collectedAt: collection.collectedAt.toISOString() },
});
});
export default router;