9d003a3b6f
fix(auth): persist generated OTP so real codes can verify (with in-memory fallback)
...
Build and Deploy / build-and-deploy (push) Has been cancelled
之前 send-otp 生成的码只在 Redis 可用时才存. dev 没配 Redis → 码生成即丢, 用户拿到
真实 SMS 验证码也登不进去 (除了万能码 123456). 这是上次"修任意 6 位绕过"留下的回归.
新增 src/lib/otp-store.ts:
- storeOtp / consumeOtp 双方法, 内部按 Redis 可用性自动路由
- Redis 可用 → 走 Redis (生产)
- Redis 缺失 → 走进程内 Map (dev / 联调), 通过 globalThis 抗 HMR
- consumeOtp 校验通过即 del, 防重放
send-otp 与 verifyOtp 改走 otp-store, 不再直接读写 Redis 句柄。
E2E (curl + NextAuth callback):
发码 → dev 日志拿 code=209988
错码 000000 → 拒绝, session=null
真码 209988 → 通过, session=粉丝_0099
重放 209988 → 拒绝 (一次性消费)
并在 NODE_ENV !== production 时把生成的 code 打到 dev 终端, 方便 QA。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 15:21:00 +08:00
8597957af4
fix(auth): reject wrong OTP codes when Redis is missing (security)
...
Build and Deploy / build-and-deploy (push) Successful in 5m8s
Bug: verifyOtp 里 dev 态 Redis 未配置时, 写了 /^\d{6}$/.test(code) 作为联调 fallback,
导致任意 6 位数字都能登录(包括恶意构造). 实际表现: 用户输入错误验证码也能直接登录。
修复:
- Redis 未配置时无论 dev/prod 一律拒绝, 不再做"任意 6 位"放行
- dev 联调若需要绕过短信, 用万能码 123456 (已保留, 仅 NODE_ENV !== production)
E2E 验证 (curl + NextAuth credentials callback):
错误码 999999 → /login?error=CredentialsSignin , session=null ✓
万能码 123456 → callbackUrl=/, session 有用户 ✓
新增 tools/test-verify-otp.mjs 作为该 bug 的回归测试。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 15:08:03 +08:00
0a7c1ec130
feat(auth): wire Aliyun SMS provider for phone OTP login
...
Build and Deploy / build-and-deploy (push) Successful in 4m33s
接入流程:
- src/lib/sms.ts: 封装 sendOtpSms(phone, code), 走 dysmsapi.aliyuncs.com 全局端点
- /api/auth/send-otp:
* 生成 6 位验证码 → Redis 5min TTL
* 调 Aliyun SDK 发送; OK → 200, isv.* 错误 → 422, 其它 → 500
* SMS_NOT_CONFIGURED 时 dev 仍能 console.log 验证码联调
- auth.ts verifyOtp:
* dev 万能码 123456 保留
* 否则 redis.get(sms:otp:phone) 比对, 通过后 del 防重放
* Redis 未配置时 prod 拒绝, dev 接受任意 6 位
环境变量 (.env.local, 不入仓库):
- SMS_ACCESS_KEY / SMS_SECRET_KEY (RAM 子账号)
- SMS_SIGN_NAME (例: 广州气元科技)
- SMS_TEMPLATE_CODE (例: SMS_506210397)
依赖:
+ @alicloud/dysmsapi20170525
+ @alicloud/openapi-client
+ @alicloud/tea-util
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:56:47 +08:00
15af8e1781
fix(image): disable next/image optimization for TOS-hosted assets
...
Build and Deploy / build-and-deploy (push) Successful in 5m10s
桶里资源已是 webp + 预压缩 + CDN, 不需要 Next 16 再做一次 image 优化:
- images.unoptimized=true: 直接走 <img src="https://..."/>, 浏览器走自己网络栈
- 顺带绕过 dev 时 fake-IP 代理 (Clash/V2Ray TUN 返回 198.18.0.x) 触发的
"resolved to private ip" 拦截
- remotePatterns 保留 (Next 16 即便不优化仍校验域名白名单)
生产 CPU 占用也省了, 不需要在 origin 再编码 webp。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:43:52 +08:00
8c88943a06
feat(tos): point all static assets to volcano TOS bucket
...
Build and Deploy / build-and-deploy (push) Successful in 5m0s
资源已上传到 https://cyberstar.tos-cn-shanghai.volces.com/cyber-star/
代码改动:
- 新增 src/lib/tos.ts 提供 tosUrl(path) 工具,读 NEXT_PUBLIC_TOS_DOMAIN
- mock-data.ts: portrait/gallery 切到 .webp, videoUrl 走 TOS, 全部通过 tosUrl()
- page.tsx Hero PV 走 tosUrl("videos/hero-pv.mp4")
- next.config.ts 把火山 TOS 域名(沪/京)+ 火山 CDN 加进 images.remotePatterns 白名单
- .env.example 更新 NEXT_PUBLIC_TOS_DOMAIN 示例为实际桶域名
体积影响 (与之前打包给运维的 cyber-star-assets.tar.gz 一致):
- 立绘 5MB png → 100-300KB webp (-95%)
- 单人 solo 5-10MB mp4 → 1-3MB (-70%)
- Hero PV 45MB → 12MB (-70%)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:37:46 +08:00
c0bce80dd1
chore(tools): asset compression pipeline for TOS bucket upload
...
Build and Deploy / build-and-deploy (push) Successful in 5m13s
新增 tools/asset-pipeline/ 用于把 public/portraits & videos 压缩成桶友好体积:
- sharp: PNG → WebP q82, 最大宽 1600 (-view 三视图 2400)
- ffmpeg: MP4 → libx264 CRF 28, 最大宽 1920, AAC 96k, faststart
- pack.mjs: tar -czf 整目录 → cyber-star-assets.tar.gz
效果 (146 portraits + 33 videos):
- 立绘: 768.6MB → 26.8MB (-96%)
- 视频: 251.7MB → 76.1MB (-70%)
- 总计: 1020MB → 103MB 压缩到 1/10, 95s 跑完
输出位于仓库外 ../assets-compressed/ 与 ../cyber-star-assets.tar.gz, 不入 git。
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:26:42 +08:00
7506372abd
fix(ci): hoisted node_modules + alpine binary target for Prisma in Docker
...
Build and Deploy / build-and-deploy (push) Successful in 5m45s
Root cause (from build log):
1. Prisma 6 generates client into @prisma/client package dir (not .prisma/client)
2. pnpm default isolated linker puts everything in .pnpm/ store with symlinks
at top-level — Docker COPY of @prisma followed broken/incomplete symlinks
3. node:22-alpine needs linux-musl-openssl-3.0.x engine binary
Fixes:
- .npmrc: node-linker=hoisted → flat node_modules, COPY behaves like npm
- schema.prisma: add linux-musl-openssl-3.0.x to binaryTargets
- Dockerfile: drop dead .prisma/client checks, copy only @prisma (where
Prisma 6 actually writes the client) plus standalone output
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:13:37 +08:00
409c4c4b50
docs(ops): TOS bucket + Aliyun SMS integration spec for team handoff
...
Build and Deploy / build-and-deploy (push) Successful in 4m8s
后端 / 运维同学填回 yaml 模板后,前端 (Claude) 直接接入。
内容覆盖:
- TOS 桶配置 / 目录结构 / 公共读 / CDN
- 阿里云短信签名 + 模板 + RAM AK
- Redis / MySQL / NextAuth 上下游依赖
- 验收清单 + 时间线
- 敏感信息 (AK/SK) 走密钥管理,不入文档
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:07:54 +08:00
d2b8c0afdc
fix(ci): explicit prisma generate + ignore-scripts in Docker build
...
Build and Deploy / build-and-deploy (push) Has been cancelled
Root cause: pnpm 10+ skips lifecycle scripts in root/CI environments by
default, so the project's postinstall (prisma generate) never ran. The
runner stage then failed to COPY node_modules/.prisma because builder
never produced it.
Fix: install with --ignore-scripts, then call `pnpm exec prisma generate`
explicitly in both deps and builder stages, with ls assertions to surface
any future regression early.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:06:47 +08:00
6155638549
fix(ci): bump base image to node:22-alpine
...
Build and Deploy / build-and-deploy (push) Successful in 6m21s
corepack-installed pnpm 11 requires node:sqlite (Node 22+).
Build was failing in deps stage with ERR_UNKNOWN_BUILTIN_MODULE.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 13:58:47 +08:00
c3863a4dab
Merge branch 'main' of https://gitea.airlabs.art/zyc/UI-UX
Build and Deploy / build-and-deploy (push) Has been cancelled
2026-05-13 13:58:22 +08:00
71a2672ff6
fix(data,ranking,ui): real dynamic ranking + data sync hardening
...
数据准确性
- 票数初始为 0,不再用 Math.sqrt 公式造假票
- 排序 tiebreaker 统一为 votes desc + no asc,确保稳定
- store.rank() 与 sortArtists() 行为对齐
Top12 / 出道位
- Top12Bar 仅收纳真正有票的人(votes > 0),0 票时显示"出道位尚未产生"空态
- ArtistCard / RankingRow / SearchModal / MyFanSupport / RankCard 的 inTop12 高亮全部加 votes > 0 守卫
- ArtistFilters 新增"实时排名 / 编号顺序"分段切换 + 首页 sortKey 状态
领奖台 (Top3Podium)
- 1 人有票即可显示领奖台(此前要求 >= 3 人才显示)
- 缺位的 #2/#3 用"虚位以待"占位卡片填充,与正式卡片同 3:4 比例对齐
- 全员 0 票时三张全部显示虚位以待
- 卡片间距拉大到 gap-8 sm:gap-12
排行榜页 (/ranking)
- API 票数与本地乐观投票取 max() 合并,避免 API 落后覆盖本地新票
- 合并后重新排序与赋 rank
- 仅 >= 12 人有票才显示出道线分隔与复活位标记
- 复活位 gapToDebut 计算修正
跨日额度
- selectRemaining 按 quotaDate 判断是否跨日,跨日自动回满额(此前会卡在昨日剩余值)
搜索 (SearchModal)
- 改为订阅 store 拿活的票数,投票后立即反映
- 默认"热门 Top12"只在真正有票时显示,否则降级为"推荐艺人 · 编号顺序"
- 票数显示统一走 formatVotes(0 票不再显示 0.0w)
人物详情
- 36 人真实数据接入,移除全部静态数据(slogan/birthday/cv/themeColor)
- 接入人物小传 docx 数据(年龄/身高/性格/口头禅/技能/赛道/座右铭/长简介)
- 视频区与版心同宽 + 首帧自动作为封面 + 整区点击播放/暂停 + 可拖拽进度条
- 表演图片改为三张氛围图竖向 3:4,左对齐
- 移除分享按钮,投票按钮全宽
个人页 (/me)
- 移除等级/邀请好友/签到/编辑资料等静态数据
- 退出登录按钮在移动端 icon-only 显示(此前 sm:hidden 导致移动端无法登出)
- 我的应援 list 基于真实 myVotesByArtist 派生,凯之类的投票真正同步过去
导航
- 余票徽章未登录态显示 0/0,已登录显示 N/10
- 登录/注册按钮样式与登录后头像胶囊保持一致(紫色实心)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:56:42 +08:00
c19b3b7b05
ci: add CI/CD pipeline for cyberstar.airlabs.art
...
Build and Deploy / build-and-deploy (push) Successful in 6m41s
- Dockerfile: multi-stage Next.js standalone build with pnpm + prisma
- k8s manifests: single web deployment + Traefik ingress + LE TLS
- Gitea workflow: build/push to Volcano CR, deploy to K3s, log-center failure reporting
- next.config: enable standalone output for slim container image
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 13:44:04 +08:00
d5ed43acbd
feat(ui): design overhaul, global login modal, design spec
...
- nav: center links (首页/排行榜/我的), right-side AuthMenu + RemainingVotesBadge; image logo with responsive sizing
- auth: replace /login route with global LoginModal triggered anywhere; "我的" intercepts unauth users with post-login redirect
- home: full-screen Hero, redesigned Top12 (12 pill cards, top-3 glow), scroll-snap mandatory between Hero/Top12/candidates
- home: candidates section with sticky filter that gains frosted-glass bg when stuck (matches nav)
- filter: simplified tags (全部/舞蹈/声乐/rap/全能型); ArtistCard uniform purple vote button
- ranking/me: remove Top12Bar; me header stacks 编辑资料/退出登录 vertically
- typography: font-logo set to Orbitron; ✦ glyph in CYBER ✦ STAR preserved
- layout: max-w-[1500px] unified across pages
- docs: add design-spec.md + design-spec.html with full visual spec (lucide SVG, zero emoji policy)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v0.1.0
2026-05-12 18:59:30 +08:00
bd5a361a18
feat(vote): remove all voting limits (no daily quota, no per-artist cap, unlimited votes)
2026-05-12 14:15:50 +08:00
9fe9fa914f
fix(ux): center modals with overlay; live vote with toast; deterministic mock data; cascade layer fix
2026-05-12 14:09:31 +08:00
7949f9bcd1
fix(nav,auth): trim nav to wireframe pages; auth gracefully degrades when DB unavailable in dev
2026-05-12 10:29:02 +08:00
854a162109
feat(live): real-time ranking polling hook + LiveBadge, ranking page falls back to mock when API unavailable
2026-05-12 10:06:16 +08:00
b7fbd5ac53
feat(auth): Auth.js v5 with phone OTP login, send-otp API, login page and user state in nav
2026-05-12 10:03:58 +08:00
175276a085
feat(api): add REST API routes (artists/ranking/me/vote/signin) + Redis rate limiting + Zod validation
2026-05-12 09:59:38 +08:00
91a0dd0f05
feat(db): Prisma 6 + MySQL schema with all models, seed script and env example
2026-05-12 09:51:17 +08:00
4f87a7d36b
feat(me): /me user center with quota card, sign-in calendar, stats and fan support
2026-05-12 09:45:47 +08:00
e7166ecf81
feat(ranking): /ranking page with Top3 podium, Top4-12 list, debut line divider and rescue zone
2026-05-12 09:43:41 +08:00
5f06b5122b
feat(artist): dynamic /artist/[id] page with hero, 15s video, gallery lightbox, bio and floating vote
2026-05-12 09:42:01 +08:00
28447c2e65
feat(home): add HeroBanner with PV video + ArtistFilters + 35-artist grid/list views
2026-05-12 09:39:21 +08:00
abce95aae8
feat(components): add Button, Countdown, ArtistCard, Top12Bar, VoteModal core components
2026-05-12 09:37:23 +08:00
c441ed7026
feat(layout): add Navigation, Footer and Logo components with root layout shell
2026-05-12 09:32:46 +08:00
ba5287add8
feat(theme): apply CYBER STAR design system (purple palette + Megrim/Audiowide/Cinzel/Inter fonts + ambient bg)
2026-05-12 09:30:51 +08:00
8a83815f1c
chore: bootstrap Next.js 16 + Tailwind v4 + TypeScript baseline
2026-05-12 09:26:46 +08:00