1 parent
23844ae301
commit
d25b5dbe6d
268 files changed
+3684
-8861
No files matched your search
@@ -42,12 +42,8 @@ const missing: Array<{ language: string; target: string; node: string }> = []
|
||||
for (const [language, table] of Object.entries(AST_NODE_TARGETS_BY_LANGUAGE)) {
|
||||
const wasmPath = WASM_BY_LANGUAGE[language]
|
||||
if (!wasmPath) {
|
||||
console.log(
|
||||
`⚠ ${language} 在 AST_NODE_TARGETS_BY_LANGUAGE 里,但这个脚本没有它的语法 wasm`,
|
||||
)
|
||||
console.log(
|
||||
` 加语言时记得同步 WASM_BY_LANGUAGE 和 judge/ast.ts 的 loadLanguage`,
|
||||
)
|
||||
console.log(`⚠ ${language} 在 AST_NODE_TARGETS_BY_LANGUAGE 里,但这个脚本没有它的语法 wasm`)
|
||||
console.log(` 加语言时记得同步 WASM_BY_LANGUAGE 和 judge/ast.ts 的 loadLanguage`)
|
||||
process.exit(2)
|
||||
}
|
||||
const loaded = await Language.load(wasmPath)
|
||||
@@ -59,8 +55,7 @@ for (const [language, table] of Object.entries(AST_NODE_TARGETS_BY_LANGUAGE)) {
|
||||
}
|
||||
for (const [target, entry] of Object.entries(table)) {
|
||||
checked++
|
||||
if (!declared.has(entry.node))
|
||||
missing.push({ language, target, node: entry.node })
|
||||
if (!declared.has(entry.node)) missing.push({ language, target, node: entry.node })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,11 +66,7 @@ if (missing.length === 0) {
|
||||
}
|
||||
for (const { language, target, node } of missing) {
|
||||
console.log(`\n⚠ ${language} 的 ${target} → "${node}"`)
|
||||
console.log(
|
||||
` 这个节点类型在语法里不存在,规则永远失败(或永远通过),且不报错`,
|
||||
)
|
||||
console.log(
|
||||
` 改法:在 packages/contract/src/problem.ts 把它的 node 改成语法里真实的名字`,
|
||||
)
|
||||
console.log(` 这个节点类型在语法里不存在,规则永远失败(或永远通过),且不报错`)
|
||||
console.log(` 改法:在 packages/contract/src/problem.ts 把它的 node 改成语法里真实的名字`)
|
||||
}
|
||||
process.exit(1)
|
||||
@@ -64,9 +64,7 @@ export function shadows(pattern: string, target: string) {
|
||||
function collect(): Route[] {
|
||||
const routerFile = new Map<string, string>()
|
||||
for (const file of walk(SRC)) {
|
||||
for (const m of readFileSync(file, "utf8").matchAll(
|
||||
/export const (\w+) = new Hono/g,
|
||||
)) {
|
||||
for (const m of readFileSync(file, "utf8").matchAll(/export const (\w+) = new Hono/g)) {
|
||||
routerFile.set(m[1]!, file)
|
||||
}
|
||||
}
|
||||
@@ -96,14 +94,10 @@ function collect(): Route[] {
|
||||
// 挂载顺序就是匹配顺序,所以必须按 index.ts 里出现的先后来摊平
|
||||
const index = readFileSync(join(SRC, "index.ts"), "utf8")
|
||||
const adminIndex = readFileSync(join(SRC, "routes/admin/index.ts"), "utf8")
|
||||
const adminMounts = [
|
||||
...adminIndex.matchAll(/\.route\(\s*"([^"]*)"\s*,\s*(\w+)\s*\)/g),
|
||||
]
|
||||
const adminMounts = [...adminIndex.matchAll(/\.route\(\s*"([^"]*)"\s*,\s*(\w+)\s*\)/g)]
|
||||
|
||||
const all: Route[] = []
|
||||
for (const m of index.matchAll(
|
||||
/app\.route\(\s*"([^"]+)"\s*,\s*(\w+)\s*\)/g,
|
||||
)) {
|
||||
for (const m of index.matchAll(/app\.route\(\s*"([^"]+)"\s*,\s*(\w+)\s*\)/g)) {
|
||||
const [, prefix, router] = m
|
||||
if (router === "adminRoutes") {
|
||||
for (const a of adminMounts) all.push(...routesOf(a[2]!, prefix! + a[1]!))
|
||||
@@ -119,8 +113,7 @@ const hits: [Route, Route][] = []
|
||||
for (let i = 0; i < routes.length; i++) {
|
||||
for (let j = i + 1; j < routes.length; j++) {
|
||||
if (routes[i]!.method !== routes[j]!.method) continue
|
||||
if (shadows(routes[i]!.path, routes[j]!.path))
|
||||
hits.push([routes[i]!, routes[j]!])
|
||||
if (shadows(routes[i]!.path, routes[j]!.path)) hits.push([routes[i]!, routes[j]!])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,9 +124,7 @@ if (hits.length === 0) {
|
||||
}
|
||||
for (const [first, second] of hits) {
|
||||
console.log(`\n⚠ ${second.method} ${second.path} (${second.file})`)
|
||||
console.log(
|
||||
` 进不去:被先注册的 ${first.method} ${first.path} 吃掉(${first.file})`,
|
||||
)
|
||||
console.log(` 进不去:被先注册的 ${first.method} ${first.path} 吃掉(${first.file})`)
|
||||
console.log(` 改法:把它挪到那条之前注册,或换一个不同形的路径`)
|
||||
}
|
||||
process.exit(1)
|
||||
@@ -3,11 +3,7 @@ import { eq, sql } from "drizzle-orm"
|
||||
import { db, schema } from "../db"
|
||||
import { JudgeStatus, isAccepted } from "../judge/status"
|
||||
import { asRecord } from "../routes/helpers"
|
||||
import {
|
||||
metaAchievements,
|
||||
refreshUnlockedCount,
|
||||
rescanAchievement,
|
||||
} from "../services/achievements"
|
||||
import { metaAchievements, refreshUnlockedCount, rescanAchievement } from "../services/achievements"
|
||||
|
||||
/**
|
||||
* 把反范式的计数列重算回与 submission 表一致。
|
||||
@@ -162,8 +158,8 @@ async function expectedProfiles() {
|
||||
function stable(value: unknown): string {
|
||||
if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`
|
||||
if (value && typeof value === "object") {
|
||||
const entries = Object.entries(value as Record<string, unknown>).sort(
|
||||
([a], [b]) => (a < b ? -1 : 1),
|
||||
const entries = Object.entries(value as Record<string, unknown>).sort(([a], [b]) =>
|
||||
a < b ? -1 : 1,
|
||||
)
|
||||
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stable(v)}`).join(",")}}`
|
||||
}
|
||||
@@ -210,13 +206,9 @@ async function unlockedCountPlan(plan: Plan) {
|
||||
achievementId: schema.userAchievement.achievementId,
|
||||
})
|
||||
.from(schema.userAchievement)
|
||||
.where(
|
||||
sql`${schema.userAchievement.achievementId} in ${metas.map((meta) => meta.id)}`,
|
||||
)
|
||||
.where(sql`${schema.userAchievement.achievementId} in ${metas.map((meta) => meta.id)}`)
|
||||
: []
|
||||
const held = new Set(
|
||||
holders.map((row) => `${row.userId}:${row.achievementId}`),
|
||||
)
|
||||
const held = new Set(holders.map((row) => `${row.userId}:${row.achievementId}`))
|
||||
|
||||
for (const row of rows) {
|
||||
const label = `用户 ${row.user_id}`
|
||||
@@ -231,9 +223,7 @@ async function unlockedCountPlan(plan: Plan) {
|
||||
}
|
||||
for (const meta of metas) {
|
||||
const met =
|
||||
meta.operator === "gte"
|
||||
? row.actual >= meta.threshold
|
||||
: row.actual <= meta.threshold
|
||||
meta.operator === "gte" ? row.actual >= meta.threshold : row.actual <= meta.threshold
|
||||
if (!met || held.has(`${row.user_id}:${meta.id}`)) continue
|
||||
plan.diffs.push({
|
||||
label,
|
||||
@@ -248,29 +238,28 @@ async function unlockedCountPlan(plan: Plan) {
|
||||
|
||||
/** 只算差异,不写库。预演和落库后的复核共用它 —— 两边口径必须是同一份代码 */
|
||||
async function computePlan(): Promise<Plan> {
|
||||
const [problems, profiles, expectedProblem, expectedProfile] =
|
||||
await Promise.all([
|
||||
db
|
||||
.select({
|
||||
id: schema.problem.id,
|
||||
displayId: schema.problem.displayId,
|
||||
submissionNumber: schema.problem.submissionNumber,
|
||||
acceptedNumber: schema.problem.acceptedNumber,
|
||||
statisticInfo: schema.problem.statisticInfo,
|
||||
})
|
||||
.from(schema.problem),
|
||||
db
|
||||
.select({
|
||||
id: schema.userProfile.id,
|
||||
userId: schema.userProfile.userId,
|
||||
submissionNumber: schema.userProfile.submissionNumber,
|
||||
acceptedNumber: schema.userProfile.acceptedNumber,
|
||||
acmProblemsStatus: schema.userProfile.acmProblemsStatus,
|
||||
})
|
||||
.from(schema.userProfile),
|
||||
expectedProblems(),
|
||||
expectedProfiles(),
|
||||
])
|
||||
const [problems, profiles, expectedProblem, expectedProfile] = await Promise.all([
|
||||
db
|
||||
.select({
|
||||
id: schema.problem.id,
|
||||
displayId: schema.problem.displayId,
|
||||
submissionNumber: schema.problem.submissionNumber,
|
||||
acceptedNumber: schema.problem.acceptedNumber,
|
||||
statisticInfo: schema.problem.statisticInfo,
|
||||
})
|
||||
.from(schema.problem),
|
||||
db
|
||||
.select({
|
||||
id: schema.userProfile.id,
|
||||
userId: schema.userProfile.userId,
|
||||
submissionNumber: schema.userProfile.submissionNumber,
|
||||
acceptedNumber: schema.userProfile.acceptedNumber,
|
||||
acmProblemsStatus: schema.userProfile.acmProblemsStatus,
|
||||
})
|
||||
.from(schema.userProfile),
|
||||
expectedProblems(),
|
||||
expectedProfiles(),
|
||||
])
|
||||
|
||||
const plan: Plan = {
|
||||
diffs: [],
|
||||
@@ -304,9 +293,7 @@ async function computePlan(): Promise<Plan> {
|
||||
after: want.acceptedNumber,
|
||||
})
|
||||
}
|
||||
if (
|
||||
stable(asRecord(problem.statisticInfo)) !== stable(want.statisticInfo)
|
||||
) {
|
||||
if (stable(asRecord(problem.statisticInfo)) !== stable(want.statisticInfo)) {
|
||||
rows.push({
|
||||
label,
|
||||
field: "statistic_info",
|
||||
@@ -332,8 +319,7 @@ async function computePlan(): Promise<Plan> {
|
||||
const merged: Record<string, unknown> = { ...existing }
|
||||
delete merged.problems
|
||||
delete merged.contest_problems
|
||||
for (const [bucket, value] of Object.entries(want.status))
|
||||
merged[bucket] = value
|
||||
for (const [bucket, value] of Object.entries(want.status)) merged[bucket] = value
|
||||
|
||||
const label = `用户 ${profile.userId}`
|
||||
const rows: Diff[] = []
|
||||
@@ -383,8 +369,7 @@ function report(plan: Plan) {
|
||||
` ${diff.label} ${diff.field}: ${JSON.stringify(diff.before)} → ${JSON.stringify(diff.after)}`,
|
||||
)
|
||||
}
|
||||
if (plan.diffs.length > 40)
|
||||
console.log(` ……另有 ${plan.diffs.length - 40} 处`)
|
||||
if (plan.diffs.length > 40) console.log(` ……另有 ${plan.diffs.length - 40} 处`)
|
||||
}
|
||||
|
||||
/** 退出码:0 = 一致或预演正常,1 = 落库后复核仍有差异 */
|
||||
@@ -427,8 +412,7 @@ export async function recount(options: { apply: boolean }) {
|
||||
// 补发幂等(唯一键 + 冲突忽略),重跑不会重复发
|
||||
const recounted = await refreshUnlockedCount(plan.unlockedCountFixes)
|
||||
if (plan.metaGrants.length) {
|
||||
for (const meta of await metaAchievements())
|
||||
await rescanAchievement(meta.id)
|
||||
for (const meta of await metaAchievements()) await rescanAchievement(meta.id)
|
||||
}
|
||||
console.log(
|
||||
`\n已订正题目 ${plan.problemFixes.length} 道、用户 ${plan.profileFixes.length} 人、已解锁数 ${recounted.length} 人,补发元成就 ${plan.metaGrants.length} 条,复核中……`,
|
||||
|
||||
@@ -9,8 +9,7 @@ import { db, schema } from "../db"
|
||||
* 所以这里按 DATABASE_URL 的主机名拦一道,需要绕过时显式设 OJ2_SEED_FORCE=true。
|
||||
*/
|
||||
const url =
|
||||
process.env.DATABASE_URL ??
|
||||
"postgres://onlinejudge:onlinejudge@localhost:5433/onlinejudge"
|
||||
process.env.DATABASE_URL ?? "postgres://onlinejudge:onlinejudge@localhost:5433/onlinejudge"
|
||||
const host = (() => {
|
||||
try {
|
||||
return new URL(url).hostname
|
||||
@@ -95,9 +94,7 @@ async function seed(account: SeedAccount) {
|
||||
})
|
||||
}
|
||||
|
||||
console.log(
|
||||
` ${account.adminType.padEnd(13)} ${user.username} / ${account.password}`,
|
||||
)
|
||||
console.log(` ${account.adminType.padEnd(13)} ${user.username} / ${account.password}`)
|
||||
}
|
||||
|
||||
console.log("Seeded development logins:")
|
||||
|
||||
Reference in new issue
Block a user