代码架构
分层
| 层 | 位置 | 职责 |
|---|---|---|
| 认证 | auth.ts | Provider、Adapter、JWT、authorized |
| 门禁 | lib/user-login-gate.ts | 邮箱白名单 |
| 页面 | app/**/page.tsx | Server Component 读库、鉴权、把数据交给 Client |
| 交互 | *-client.tsx、vote-form.tsx | MUI 表单,调用 Server Actions |
| 写入 | **/actions.ts、*-action.ts | 权限、校验、事务、revalidatePath |
| 规则 | lib/campaign-*.ts | 权重、名簿、规则 JSON |
| 数据 | lib/neon.ts | @neondatabase/serverless 的 neon(url) |
投票提交(核心写路径)
submitCastVote取 session,拒绝无 id 与role=admin。- 确认
campaigns.ongoing。 getCampaignVoteWeightForUser得到应投总票。- 合并同一候选人、校验正整数、合计等于权重、选项属于本场。
- 确认存在
(campaign, voter)选票且尚无分票。 - 事务:更新
ballots.weight、清空旧 allocations、插入新行。
UI 壳
根布局用 Toolpad AppProvider + MUI 红色主题。导航数组在 Server Component 里拼好:普通成员看进行中且自己已参与的场次;admin/moderator 看全部竞选的排名与选票管理。
安全注意
新增页面或 Action 必须在服务端再查 session 与 ongoing。只改 layout.tsx 不够。结果页目前缺少角色守卫,若要限制公布范围需补校验。