fix(vote): 站方处置覆盖投票链路

提问链路上 ActiveLevel 有 7 处调用,投票链路 0 处。于是 L4(整页停用)处置某人后:
/_/domain 已经 302 到 /restricted,但 /vote/{token} 照常渲染、照常收票;被处置者
还能发新投票;探索页的「热门/全部投票」也不做处置过滤 —— 同一张页面里
「箱主消失了、他的投票还在曝光」。这是运营口径不一致,不是越权。

补齐三处,与提问链路对齐:
- vote.Page      L4 → 302 /restricted(同箱主页;查询失败 fail-open,与 Pager 一致)
- vote.Submit    L4 → 拒绝(页面进不去不代表直接构造 POST 也拦得住)
- VotePublish    L2 起拒绝(L2 已是「收窄公开」,不该再新增公开内容)
- 探索页投票列表改用 Restrictions.ActiveLevels 批量过滤(与 GetShowInHome 同口径,
  一次查询避免 N+1),功能开关过滤逻辑保持不变

新增字典 key vote.err.restrictedPublish(四语言)。

验证(本地实例):
- L4 后 /vote/{token} 302 → /restricted;撤销后恢复 200
- 探索页该投票出现次数 0(处置中)→ 2(撤销后)
- 草稿投票在 L2 下发布后状态停留 draft,撤销后同一请求变为 published
This commit is contained in:
tamakyi
2026-10-29 15:53:49 +08:00
parent ced4c1635c
commit c971d99bdb
7 changed files with 68 additions and 6 deletions
+22
View File
@@ -607,6 +607,18 @@ func Page(ctx context.Context) {
return
}
// 站方处置 L4:整页停止对外访问(数据保留)。
// 与提问箱主页同一口径:此前投票链路完全不查处置,
// 于是 L4 后 /_/domain 已跳 /restricted,/vote/{token} 却照常可访。
// 查询失败不拦(fail-open):DB 异常时整站都不正常,
// 这里再 fail-closed 只会让所有投票一起白屏。
if level, _, err := db.Restrictions.ActiveLevel(ctx.Request().Context(), vote.UserID); err != nil {
logrus.WithContext(ctx.Request().Context()).WithError(err).Error("Failed to query vote owner restriction")
} else if level == db.RestrictionL4 {
ctx.Redirect("/restricted")
return
}
now := time.Now()
isOwner := ctx.IsLogged && ctx.User.ID == vote.UserID
@@ -703,6 +715,16 @@ func Submit(ctx context.Context, recaptcha recaptcha.RecaptchaV3) {
return
}
// 站方处置 L4:与 Page 同一口径。
// 页面进不去不代表 POST 也拦得住——直接构造请求可以绕过页面。
if level, _, err := db.Restrictions.ActiveLevel(ctx.Request().Context(), vote.UserID); err != nil {
logrus.WithContext(ctx.Request().Context()).WithError(err).Error("Failed to query vote owner restriction")
} else if level == db.RestrictionL4 {
ctx.SetErrorFlash(i18n.T(ctxLang(ctx), "vote.flash.notFound"))
ctx.Redirect("/")
return
}
// 发布者的语言与主题:提交成功/失败都回跳投票页,提示文案必须与页面语言一致。
owner, err := db.Users.GetByID(ctx.Request().Context(), vote.UserID)
if err != nil {