aura

轮次管理

你现在的 /rounds / /rounds/r1 若是 Lovable 上的演示页,要变成「真轮次」,核心不是把 UI 做更炫,而是在后端确立「轮次」这一层数据,再让提交、规则、排行榜都挂在它下面。

下面分「怎么管」和「怎么落在你这套 Aura 上」两部分说。


1. 轮次在业务上管什么

把一轮黑客松想成一条有生命周期的容器,里面装四样东西:

维度 建议
时间 报名/提交截止、评审窗口、结果公布;一律用明确时区(例如 Asia/Shanghai)存 UTC 或带 offset。
规则 本轮绑定的评审 YAML(维度、权重、等级带);换轮次可以换规则,旧结果仍指向旧规则版本。
项目 每条提交/每个 README 属于唯一 round_id;禁止「同一项目同时参加两轮」除非你们明确支持复制/晋级。
可见性 草稿 / 进行中 / 已归档;排行榜是否公开、是否隐藏分数细项,按状态切换。

状态机可以尽量简单,例如:draftopen(收作品)→ judgingclosed(只读展示)。复杂活动再加 extended(延期)即可。


2. 和「排行榜」的关系(避免再造假数据)

排行榜预览要真,只需要约定:

  • 查询维度GET /api/ranking?round_id=xxx(或路径 /api/rounds/:id/ranking)。
  • 数据怎么筛:只统计 file_name / submission 属于该 round_id 的裁决 JSON;或裁决结果里写入 round_id 冗余字段便于过滤。
  • 和现有 prefer_search=1 兼容:先按 round_id 过滤,再在子集上做「每条 readme 优选带搜索元数据的那次运行」。

这样 /rounds/r1 只是前端用 r1 去拉真实 API,而不是本地 mock。


3. 落在你当前后端上的几种做法(由简到繁)

A. 最简(适合快速上线)

  • submission.json(或提交接口)里增加 round_id(或 hackathon_id)。
  • 磁盘目录仍可用现有 submissions/<id>,只是在元数据里带轮次。
  • 排行榜、管理台列表:先读所有 submission / 结果,再按 round_id 过滤
  • 轮次列表存在单独 rounds.json 或小表即可(id、名称、起止时间、状态、绑定的 rule_version_id)。

B. 目录隔离(适合运维喜欢「一整包拷走」)

  • submissions/<round_id>/<submission_id>/word/<round_id>/...judge-result/<round_id>/...
  • 备份、清空一轮数据很直观;实现改动比 A 大。

C. 上数据库(多届、多主办方、权限细)

  • roundssubmissionsaudit_runs 分表;适合以后要 SaaS 或多活动并行。

对你现在以文件为主的 Aura 裁决服务,多数团队会先选 A,必要时再演进到 B


4. 管理后台建议功能(按优先级)

  1. 轮次 CRUD + 状态(草稿/开放/评审/结束)。
  2. 绑定规则版本:创建轮次时选「生效规则」或上传本轮专用规则(可复用你们已有的 rules API)。
  3. 项目列表:按轮次筛 submission,显示是否已生成 word 里的 readme、是否已有裁决结果。
  4. 只读排行榜链接/rounds/:slugOrId 仅展示公开字段;管理页可看明细。
  5. 导出:CSV(项目、分数、GitHub);后期再加评委人工分与 AI 分合并若需要。

5. 常见坑(提前规避)

  • 没有轮次字段:所有提交堆在一起,历史届次无法拆分排行榜。
  • 规则与轮次脱钩:换了全局 active 规则后,旧轮次结果难以解释「按哪套规则打的」。
  • 时间只写在 UI:应用层应对「已过截止仍允许提交」做服务端校验(至少管理员可覆盖)。
  • slug 与 id:对外链接用可读 slug(如 spring-2025),内部持久化用稳定 id(UUID 或 snowflake),避免改名断链。

6. 小结

更好管理黑客松轮次,本质是:轮次 = 时间边界 + 规则版本 + 项目集合 + 可见性;技术上用 round_id 贯穿提交与裁决结果,排行榜 API 按轮次过滤,前端 /rounds 只做配置与列表,/rounds/xxx 只消费真实接口。

若你打算在现有 /root/aura Go 服务里落地,下一步可以是:定 rounds 的 JSON schema、扩展 /api/submit/api/ranking,我可以按你选的方案(A/B)拆成具体接口清单与字段设计。

已完成

目录隔离已按方案 B 落地,说明如下。

目录结构(相对原来的三个根路径)

根(环境变量不变) 新结构
AURA_SUBMISSION_DIR …/submissions/<round_id>/<submission_id>/submission.json
AURA_WORD_DIR …/word/<round_id>/<文件名>
AURA_RESULT_DIR …/judge-result/<round_id>/*.json

后端行为

  • AURA_DEFAULT_ROUND_ID(默认 default):请求里没有 round_id 时使用该轮次。
  • round_id 校验:仅允许字母、数字、._-,最长 80,禁止 .. 与路径分隔符。
  • 新接口 GET /api/rounds:返回 rounds(轮次子目录列表)和 default_round_id
    若某目录下直接submission.json,视为旧版扁平提交目录,不会当作轮次(避免把纯数字 submission_id 当成 round)。
  • /api/admin-config 增加字段 default_round_id
  • 提交SubmissionRecord / 响应里的 round_id 会写入或回传;GitHub 自动审计在对应 word/<round_id>/ 下写 readme 并打分。

需带 round_id 的接口(可省略则用默认轮次)

GET/POST 通过 queryJSON/formround_id(与现有文档一致):rankingfilesresultsfile-contentjudge-resultfile-github-urlssubmission/:idDELETE submission/:idsubmissions(管理员)、audit(JSON 里 round_id)、submit(form 字段 round_id)。

前端

  • 设置 VITE_ROUND_ID(例如 r1)后,会通过 withRoundQuery 自动给上述请求带上 round_id;提交表单也会追加 round_id
  • 未设置时依赖后端 AURA_DEFAULT_ROUND_ID(一般为 default)。

数据迁移(旧数据在根下平铺时)

备份后,把旧提交迁进默认轮次(名称需与 AURA_DEFAULT_ROUND_ID 一致,例如 default):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ROUND=default
mkdir -p "/root/aura/submissions/$ROUND" "/root/aura/word/$ROUND" "/root/aura/judge-result/$ROUND"

# 仅移动「根下直接含 submission.json」的目录(旧提交)
cd /root/aura/submissions
for d in */; do
[ ! -d "$d" ] && continue
name="${d%/}"
[ "$name" = "$ROUND" ] && continue
[ -f "${d}submission.json" ] && mv "$d" "$ROUND/"
done

# word / judge-result 根下的散文件迁入该轮次
find /root/aura/word -maxdepth 1 -type f -exec mv -t "/root/aura/word/$ROUND" {} +
find /root/aura/judge-result -maxdepth 1 -type f -name '*.json' -exec mv -t "/root/aura/judge-result/$ROUND" {} +

(路径按你机器上的 AURA_* 调整。)

运维

  • 新的一轮:在三个根下建同名子目录即可(例如 mkdir -p submissions/spring25 word/spring25 judge-result/spring25),或等第一次向该 round_id 提交时由 ensureRoundDirs 自动创建。
  • 整轮打包:打包 submissions/<round_id>word/<round_id>judge-result/<round_id> 即可。

go build 已通过;若需要,我可以再帮你加一条 GET /api/rounds 的前端封装或在管理页展示轮次下拉框。