Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
530e1a3d5d
commit
195069450f
480 files changed
+15507
-24650
No files matched your search
@@ -20,17 +20,17 @@
|
||||
* 这种,语法里两个都存在,机器看不出来)。语义那一层还是得靠实跑。
|
||||
*/
|
||||
|
||||
import { AST_NODE_TARGETS_BY_LANGUAGE } from "@oj2/contract"
|
||||
import { Language, Parser } from "web-tree-sitter"
|
||||
import { AST_NODE_TARGETS_BY_LANGUAGE } from '@oj2/contract'
|
||||
import { Language, Parser } from 'web-tree-sitter'
|
||||
|
||||
import cWasmPath from "tree-sitter-c/tree-sitter-c.wasm" with { type: "file" }
|
||||
import cppWasmPath from "tree-sitter-cpp/tree-sitter-cpp.wasm" with { type: "file" }
|
||||
import pythonWasmPath from "tree-sitter-python/tree-sitter-python.wasm" with { type: "file" }
|
||||
import treeSitterWasmPath from "web-tree-sitter/web-tree-sitter.wasm" with { type: "file" }
|
||||
import cWasmPath from 'tree-sitter-c/tree-sitter-c.wasm' with { type: 'file' }
|
||||
import cppWasmPath from 'tree-sitter-cpp/tree-sitter-cpp.wasm' with { type: 'file' }
|
||||
import pythonWasmPath from 'tree-sitter-python/tree-sitter-python.wasm' with { type: 'file' }
|
||||
import treeSitterWasmPath from 'web-tree-sitter/web-tree-sitter.wasm' with { type: 'file' }
|
||||
|
||||
const WASM_BY_LANGUAGE: Record<string, string> = {
|
||||
C: cWasmPath,
|
||||
"C++": cppWasmPath,
|
||||
'C++': cppWasmPath,
|
||||
Python: pythonWasmPath,
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ for (const [language, table] of Object.entries(AST_NODE_TARGETS_BY_LANGUAGE)) {
|
||||
|
||||
console.log(`检查了 ${checked} 个 AST target 的节点类型`)
|
||||
if (missing.length === 0) {
|
||||
console.log("✓ 每个 target 的 node 都在对应语言的语法里真实存在")
|
||||
console.log('✓ 每个 target 的 node 都在对应语言的语法里真实存在')
|
||||
process.exit(0)
|
||||
}
|
||||
for (const { language, target, node } of missing) {
|
||||
|
||||
@@ -21,10 +21,10 @@
|
||||
* 动态拼出来的路径看不见 —— 但本仓库没有那种写法,加的时候请保持。
|
||||
*/
|
||||
|
||||
import { readFileSync, readdirSync, statSync } from "node:fs"
|
||||
import { join, resolve } from "node:path"
|
||||
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
||||
import { join, resolve } from 'node:path'
|
||||
|
||||
const SRC = resolve(import.meta.dir, "..")
|
||||
const SRC = resolve(import.meta.dir, '..')
|
||||
|
||||
interface Route {
|
||||
method: string
|
||||
@@ -36,23 +36,23 @@ function walk(dir: string, out: string[] = []) {
|
||||
for (const entry of readdirSync(dir)) {
|
||||
const path = join(dir, entry)
|
||||
if (statSync(path).isDirectory()) walk(path, out)
|
||||
else if (entry.endsWith(".ts")) out.push(path)
|
||||
else if (entry.endsWith('.ts')) out.push(path)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** 先注册的 pattern 会不会把后注册的 target 吃掉 */
|
||||
export function shadows(pattern: string, target: string) {
|
||||
const a = pattern.split("/").filter(Boolean)
|
||||
const b = target.split("/").filter(Boolean)
|
||||
const a = pattern.split('/').filter(Boolean)
|
||||
const b = target.split('/').filter(Boolean)
|
||||
if (a.length !== b.length) return false
|
||||
let usedParam = false
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
const seg = a[i]!
|
||||
const other = b[i]!
|
||||
if (seg.startsWith(":")) {
|
||||
if (seg.startsWith(':')) {
|
||||
// 参数段吃得掉任何字面量段;两边都是参数说明本来就是同一条,不算遮蔽
|
||||
if (other.startsWith(":")) continue
|
||||
if (other.startsWith(':')) continue
|
||||
usedParam = true
|
||||
continue
|
||||
}
|
||||
@@ -64,7 +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)
|
||||
}
|
||||
}
|
||||
@@ -72,34 +72,31 @@ function collect(): Route[] {
|
||||
const routesOf = (router: string, prefix: string): Route[] => {
|
||||
const file = routerFile.get(router)
|
||||
if (!file) return []
|
||||
const text = readFileSync(file, "utf8")
|
||||
const text = readFileSync(file, 'utf8')
|
||||
// 直接注册的路由和嵌套挂载(`router.route("/", child)`)放在一起按出现位置排序:
|
||||
// 子路由挂在哪个位置,它的路由就在哪个位置参与匹配
|
||||
const pattern = new RegExp(
|
||||
`${router}\\.(get|post|put|delete|patch)\\(\\s*"([^"]+)"|${router}\\.route\\(\\s*"([^"]*)"\\s*,\\s*(\\w+)\\s*\\)`,
|
||||
"g",
|
||||
)
|
||||
const pattern = new RegExp(`${router}\\.(get|post|put|delete|patch)\\(\\s*"([^"]+)"|${router}\\.route\\(\\s*"([^"]*)"\\s*,\\s*(\\w+)\\s*\\)`, 'g')
|
||||
return [...text.matchAll(pattern)].flatMap((m) => {
|
||||
if (m[4]) return routesOf(m[4], prefix + m[3]!)
|
||||
return [
|
||||
{
|
||||
method: m[1]!.toUpperCase(),
|
||||
path: (prefix + m[2]!).replace(/\/+/g, "/").replace(/\/$/, "") || "/",
|
||||
file: file.replace(SRC + "/", ""),
|
||||
path: (prefix + m[2]!).replace(/\/+/g, '/').replace(/\/$/, '') || '/',
|
||||
file: file.replace(SRC + '/', ''),
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
// 挂载顺序就是匹配顺序,所以必须按 index.ts 里出现的先后来摊平
|
||||
const index = readFileSync(join(SRC, "index.ts"), "utf8")
|
||||
const adminIndex = readFileSync(join(SRC, "routes/admin/index.ts"), "utf8")
|
||||
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 all: Route[] = []
|
||||
for (const m of index.matchAll(/app\.route\(\s*"([^"]+)"\s*,\s*(\w+)\s*\)/g)) {
|
||||
const [, prefix, router] = m
|
||||
if (router === "adminRoutes") {
|
||||
if (router === 'adminRoutes') {
|
||||
for (const a of adminMounts) all.push(...routesOf(a[2]!, prefix! + a[1]!))
|
||||
} else {
|
||||
all.push(...routesOf(router!, prefix!))
|
||||
@@ -119,7 +116,7 @@ for (let i = 0; i < routes.length; i++) {
|
||||
|
||||
console.log(`按注册顺序检查了 ${routes.length} 条路由`)
|
||||
if (hits.length === 0) {
|
||||
console.log("✓ 没有路由被遮蔽")
|
||||
console.log('✓ 没有路由被遮蔽')
|
||||
process.exit(0)
|
||||
}
|
||||
for (const [first, second] of hits) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { eq, sql } from "drizzle-orm"
|
||||
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 { db, schema } from '../db'
|
||||
import { JudgeStatus, isAccepted } from '../judge/status'
|
||||
import { asRecord } from '../routes/helpers'
|
||||
import { metaAchievements, refreshUnlockedCount, rescanAchievement } from '../services/achievements'
|
||||
|
||||
/**
|
||||
* 把反范式的计数列重算回与 submission 表一致。
|
||||
@@ -144,7 +144,7 @@ async function expectedProfiles() {
|
||||
}
|
||||
for (const row of perProblem) {
|
||||
const current = expected.get(row.user_id) ?? blank()
|
||||
const bucket = row.is_public ? "problems" : "contest_problems"
|
||||
const bucket = row.is_public ? 'problems' : 'contest_problems'
|
||||
current.status[bucket] ??= {}
|
||||
current.status[bucket]![String(row.problem_id)] = {
|
||||
status: row.ever_accepted ? JudgeStatus.ACCEPTED : row.last_result,
|
||||
@@ -157,14 +157,12 @@ async function expectedProfiles() {
|
||||
|
||||
/** 稳定序列化,用来比对 jsonb —— 键序不同不该被当成差异 */
|
||||
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,
|
||||
)
|
||||
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stable(v)}`).join(",")}}`
|
||||
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))
|
||||
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stable(v)}`).join(',')}}`
|
||||
}
|
||||
return JSON.stringify(value) ?? "null"
|
||||
return JSON.stringify(value) ?? 'null'
|
||||
}
|
||||
|
||||
type Diff = { label: string; field: string; before: unknown; after: unknown }
|
||||
@@ -216,21 +214,20 @@ async function unlockedCountPlan(plan: Plan) {
|
||||
if (row.counter !== row.actual) {
|
||||
plan.diffs.push({
|
||||
label,
|
||||
field: "achievement_unlocked_count",
|
||||
field: 'achievement_unlocked_count',
|
||||
before: row.counter ?? null,
|
||||
after: row.actual,
|
||||
})
|
||||
plan.unlockedCountFixes.push(row.user_id)
|
||||
}
|
||||
for (const meta of metas) {
|
||||
const met =
|
||||
meta.operator === "gte" ? row.actual >= meta.threshold : row.actual <= meta.threshold
|
||||
const met = 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,
|
||||
field: `成就「${meta.name}」`,
|
||||
before: "未发",
|
||||
after: "补发",
|
||||
before: '未发',
|
||||
after: '补发',
|
||||
})
|
||||
plan.metaGrants.push({ userId: row.user_id, achievementId: meta.id })
|
||||
}
|
||||
@@ -281,7 +278,7 @@ async function computePlan(): Promise<Plan> {
|
||||
if (problem.submissionNumber !== want.submissionNumber) {
|
||||
rows.push({
|
||||
label,
|
||||
field: "submission_number",
|
||||
field: 'submission_number',
|
||||
before: problem.submissionNumber,
|
||||
after: want.submissionNumber,
|
||||
})
|
||||
@@ -289,7 +286,7 @@ async function computePlan(): Promise<Plan> {
|
||||
if (problem.acceptedNumber !== want.acceptedNumber) {
|
||||
rows.push({
|
||||
label,
|
||||
field: "accepted_number",
|
||||
field: 'accepted_number',
|
||||
before: problem.acceptedNumber,
|
||||
after: want.acceptedNumber,
|
||||
})
|
||||
@@ -297,7 +294,7 @@ async function computePlan(): Promise<Plan> {
|
||||
if (stable(asRecord(problem.statisticInfo)) !== stable(want.statisticInfo)) {
|
||||
rows.push({
|
||||
label,
|
||||
field: "statistic_info",
|
||||
field: 'statistic_info',
|
||||
before: problem.statisticInfo,
|
||||
after: want.statisticInfo,
|
||||
})
|
||||
@@ -327,7 +324,7 @@ async function computePlan(): Promise<Plan> {
|
||||
if (profile.submissionNumber !== want.submissionNumber) {
|
||||
rows.push({
|
||||
label,
|
||||
field: "submission_number",
|
||||
field: 'submission_number',
|
||||
before: profile.submissionNumber,
|
||||
after: want.submissionNumber,
|
||||
})
|
||||
@@ -335,19 +332,16 @@ async function computePlan(): Promise<Plan> {
|
||||
if (profile.acceptedNumber !== want.acceptedNumber) {
|
||||
rows.push({
|
||||
label,
|
||||
field: "accepted_number",
|
||||
field: 'accepted_number',
|
||||
before: profile.acceptedNumber,
|
||||
after: want.acceptedNumber,
|
||||
})
|
||||
}
|
||||
if (stable(existing) !== stable(merged)) {
|
||||
const keys = new Set([
|
||||
...Object.keys(asRecord(existing.problems)),
|
||||
...Object.keys(want.status.problems ?? {}),
|
||||
])
|
||||
const keys = new Set([...Object.keys(asRecord(existing.problems)), ...Object.keys(want.status.problems ?? {})])
|
||||
rows.push({
|
||||
label,
|
||||
field: "acm_problems_status",
|
||||
field: 'acm_problems_status',
|
||||
before: `${Object.keys(asRecord(existing.problems)).length} 题`,
|
||||
after: `${keys.size} 题(含比赛桶重建)`,
|
||||
})
|
||||
@@ -366,9 +360,7 @@ function report(plan: Plan) {
|
||||
`发现 ${plan.diffs.length} 处不一致(题目 ${plan.problemFixes.length} 道 / 用户 ${plan.profileFixes.length} 人 / 已解锁数 ${plan.unlockedCountFixes.length} 人 / 元成就补发 ${plan.metaGrants.length} 条):`,
|
||||
)
|
||||
for (const diff of plan.diffs.slice(0, 40)) {
|
||||
console.log(
|
||||
` ${diff.label} ${diff.field}: ${JSON.stringify(diff.before)} → ${JSON.stringify(diff.after)}`,
|
||||
)
|
||||
console.log(` ${diff.label} ${diff.field}: ${JSON.stringify(diff.before)} → ${JSON.stringify(diff.after)}`)
|
||||
}
|
||||
if (plan.diffs.length > 40) console.log(` ……另有 ${plan.diffs.length - 40} 处`)
|
||||
}
|
||||
@@ -377,13 +369,13 @@ function report(plan: Plan) {
|
||||
export async function recount(options: { apply: boolean }) {
|
||||
const plan = await computePlan()
|
||||
if (plan.diffs.length === 0) {
|
||||
console.log("计数列与 submission / user_achievement 一致,没有要订正的。")
|
||||
console.log('计数列与 submission / user_achievement 一致,没有要订正的。')
|
||||
return 0
|
||||
}
|
||||
report(plan)
|
||||
|
||||
if (!options.apply) {
|
||||
console.log("\n以上为预演,没有写库。确认无误后加 --apply 落库。")
|
||||
console.log('\n以上为预演,没有写库。确认无误后加 --apply 落库。')
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -423,7 +415,7 @@ export async function recount(options: { apply: boolean }) {
|
||||
// 必须让部署脚本看见非零退出码,而不是打一行字了事。
|
||||
const after = await computePlan()
|
||||
if (after.diffs.length === 0) {
|
||||
console.log("复核通过:计数列与 submission / user_achievement 一致")
|
||||
console.log('复核通过:计数列与 submission / user_achievement 一致')
|
||||
return 0
|
||||
}
|
||||
console.error(`复核未通过,仍有 ${after.diffs.length} 处差异:`)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { and, eq, inArray, isNull, sql } from "drizzle-orm"
|
||||
import { and, eq, inArray, isNull, sql } from 'drizzle-orm'
|
||||
|
||||
import { db, schema } from "../db"
|
||||
import { diagnoseRuntimeError, templateForLanguage, type JudgeCase } from "../judge/run"
|
||||
import { JudgeStatus } from "../judge/status"
|
||||
import { parseProblemTemplate } from "../judge/template"
|
||||
import { asRecord } from "../routes/helpers"
|
||||
import { db, schema } from '../db'
|
||||
import { diagnoseRuntimeError, templateForLanguage, type JudgeCase } from '../judge/run'
|
||||
import { JudgeStatus } from '../judge/status'
|
||||
import { parseProblemTemplate } from '../judge/template'
|
||||
import { asRecord } from '../routes/helpers'
|
||||
|
||||
/**
|
||||
* 把库里运行时错误的诊断(`statistic_info.runtime_error`)按现在的规则重做一遍。
|
||||
@@ -30,7 +30,7 @@ import { asRecord } from "../routes/helpers"
|
||||
/** jsonb 读回来的键顺序和新拼的对象不一样,按排好序的键比。诊断是一层的平对象 */
|
||||
function sameKeys(value: unknown) {
|
||||
const record = asRecord(value)
|
||||
return value ? JSON.stringify(record, Object.keys(record).sort()) : "null"
|
||||
return value ? JSON.stringify(record, Object.keys(record).sort()) : 'null'
|
||||
}
|
||||
|
||||
export async function rediagnose({ apply }: { apply: boolean }) {
|
||||
@@ -42,13 +42,11 @@ export async function rediagnose({ apply }: { apply: boolean }) {
|
||||
and(
|
||||
eq(schema.submission.result, JudgeStatus.RUNTIME_ERROR),
|
||||
isNull(schema.submission.contestId),
|
||||
inArray(schema.submission.language, ["Python", "C", "C++"]),
|
||||
inArray(schema.submission.language, ['Python', 'C', 'C++']),
|
||||
sql`(${schema.submission.language} <> 'Python' or ${schema.submission.statisticInfo} ? 'runtime_error')`,
|
||||
),
|
||||
)
|
||||
console.log(
|
||||
`${rows.length} 条运行时错误要重新诊断${apply ? "" : "(只读预演,加 --apply 才写)"}`,
|
||||
)
|
||||
console.log(`${rows.length} 条运行时错误要重新诊断${apply ? '' : '(只读预演,加 --apply 才写)'}`)
|
||||
|
||||
const changes = new Map<string, number>()
|
||||
let changed = 0
|
||||
@@ -61,14 +59,10 @@ export async function rediagnose({ apply }: { apply: boolean }) {
|
||||
|
||||
const rawTemplate = templateForLanguage(row.problem.template, row.submission.language)
|
||||
const template = rawTemplate ? parseProblemTemplate(rawTemplate) : null
|
||||
const source = template
|
||||
? `${template.prepend}\n${row.submission.code}\n${template.append}`
|
||||
: row.submission.code
|
||||
const prependLines = template ? template.prepend.split("\n").length : 0
|
||||
const source = template ? `${template.prepend}\n${row.submission.code}\n${template.append}` : row.submission.code
|
||||
const prependLines = template ? template.prepend.split('\n').length : 0
|
||||
|
||||
const next = failure
|
||||
? await diagnoseRuntimeError(row, source, prependLines, failure).catch(() => null)
|
||||
: null
|
||||
const next = failure ? await diagnoseRuntimeError(row, source, prependLines, failure).catch(() => null) : null
|
||||
const previous = asRecord(row.submission.statisticInfo).runtime_error
|
||||
if (!next) {
|
||||
failed++
|
||||
@@ -76,8 +70,8 @@ export async function rediagnose({ apply }: { apply: boolean }) {
|
||||
changed++
|
||||
const label = (value: unknown) => {
|
||||
const info = asRecord(value)
|
||||
if (!value) return "(无)"
|
||||
if (info.type) return `${String(info.type)}/${String(info.kind ?? "-")}`
|
||||
if (!value) return '(无)'
|
||||
if (info.type) return `${String(info.type)}/${String(info.kind ?? '-')}`
|
||||
return `信号 ${String(info.signal ?? 0)} 退出码 ${String(info.exit_code ?? 0)}`
|
||||
}
|
||||
const key = `${row.submission.language} ${label(previous)} → ${label(next)}`
|
||||
@@ -97,8 +91,6 @@ export async function rediagnose({ apply }: { apply: boolean }) {
|
||||
for (const [key, count] of [...changes].sort((left, right) => right[1] - left[1])) {
|
||||
console.log(`${String(count).padStart(5)} ${key}`)
|
||||
}
|
||||
console.log(
|
||||
`变了 ${changed} 条${apply ? ",已写入" : ""};没变 ${rows.length - changed - failed} 条;诊断不出来、保留原样 ${failed} 条`,
|
||||
)
|
||||
console.log(`变了 ${changed} 条${apply ? ',已写入' : ''};没变 ${rows.length - changed - failed} 条;诊断不出来、保留原样 ${failed} 条`)
|
||||
return 0
|
||||
}
|
||||
@@ -1,24 +1,23 @@
|
||||
import { eq, sql } from "drizzle-orm"
|
||||
import { eq, sql } from 'drizzle-orm'
|
||||
|
||||
import { hashPassword } from "../auth/password"
|
||||
import { db, schema } from "../db"
|
||||
import { hashPassword } from '../auth/password'
|
||||
import { db, schema } from '../db'
|
||||
|
||||
/**
|
||||
* 本机开发用的账号。**只能对本地库跑** —— 它会重置账号密码并把明文写进
|
||||
* raw_password,其中一个还是超管。对着生产库跑一次就是把超管密码改掉,
|
||||
* 所以这里按 DATABASE_URL 的主机名拦一道,需要绕过时显式设 OJ2_SEED_FORCE=true。
|
||||
*/
|
||||
const url =
|
||||
process.env.DATABASE_URL ?? "postgres://onlinejudge:onlinejudge@localhost:5433/onlinejudge"
|
||||
const url = process.env.DATABASE_URL ?? 'postgres://onlinejudge:onlinejudge@localhost:5433/onlinejudge'
|
||||
const host = (() => {
|
||||
try {
|
||||
return new URL(url).hostname
|
||||
} catch {
|
||||
return ""
|
||||
return ''
|
||||
}
|
||||
})()
|
||||
const LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "::1", ""])
|
||||
if (!LOCAL_HOSTS.has(host) && process.env.OJ2_SEED_FORCE !== "true") {
|
||||
const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '::1', ''])
|
||||
if (!LOCAL_HOSTS.has(host) && process.env.OJ2_SEED_FORCE !== 'true') {
|
||||
console.error(
|
||||
`[seed:dev] DATABASE_URL 指向 ${host},不是本机。这个脚本会重置账号密码` +
|
||||
`(含一个超管)并写明文 raw_password,拒绝执行。\n` +
|
||||
@@ -30,15 +29,13 @@ if (!LOCAL_HOSTS.has(host) && process.env.OJ2_SEED_FORCE !== "true") {
|
||||
const now = new Date().toISOString()
|
||||
|
||||
// 阶段 1 是带着显式 id 导入的,PostgreSQL 的序列没跟着走,先对齐
|
||||
await db.execute(
|
||||
sql`select setval(pg_get_serial_sequence('"user"', 'id'), coalesce(max(${schema.user.id}), 1), true) from ${schema.user}`,
|
||||
)
|
||||
await db.execute(sql`select setval(pg_get_serial_sequence('"user"', 'id'), coalesce(max(${schema.user.id}), 1), true) from ${schema.user}`)
|
||||
|
||||
interface SeedAccount {
|
||||
username: string
|
||||
password: string
|
||||
adminType: "Regular User" | "Super Admin"
|
||||
problemPermission: "None" | "All"
|
||||
adminType: 'Regular User' | 'Super Admin'
|
||||
problemPermission: 'None' | 'All'
|
||||
realName: string
|
||||
}
|
||||
|
||||
@@ -79,17 +76,13 @@ async function seed(account: SeedAccount) {
|
||||
|
||||
if (!user) throw new Error(`Failed to seed ${account.username}`)
|
||||
|
||||
const [profile] = await db
|
||||
.select({ id: schema.userProfile.id })
|
||||
.from(schema.userProfile)
|
||||
.where(eq(schema.userProfile.userId, user.id))
|
||||
.limit(1)
|
||||
const [profile] = await db.select({ id: schema.userProfile.id }).from(schema.userProfile).where(eq(schema.userProfile.userId, user.id)).limit(1)
|
||||
|
||||
if (!profile) {
|
||||
await db.insert(schema.userProfile).values({
|
||||
userId: user.id,
|
||||
acmProblemsStatus: { problems: {}, contest_problems: {} },
|
||||
avatar: "/public/avatar/default.png",
|
||||
avatar: '/public/avatar/default.png',
|
||||
realName: account.realName,
|
||||
})
|
||||
}
|
||||
@@ -97,19 +90,19 @@ async function seed(account: SeedAccount) {
|
||||
console.log(` ${account.adminType.padEnd(13)} ${user.username} / ${account.password}`)
|
||||
}
|
||||
|
||||
console.log("Seeded development logins:")
|
||||
console.log('Seeded development logins:')
|
||||
await seed({
|
||||
username: process.env.OJ2_DEV_USERNAME ?? "student",
|
||||
password: process.env.OJ2_DEV_PASSWORD ?? "student123",
|
||||
adminType: "Regular User",
|
||||
problemPermission: "None",
|
||||
realName: "开发用学生",
|
||||
username: process.env.OJ2_DEV_USERNAME ?? 'student',
|
||||
password: process.env.OJ2_DEV_PASSWORD ?? 'student123',
|
||||
adminType: 'Regular User',
|
||||
problemPermission: 'None',
|
||||
realName: '开发用学生',
|
||||
})
|
||||
await seed({
|
||||
username: process.env.OJ2_DEV_ADMIN_USERNAME ?? "devadmin",
|
||||
password: process.env.OJ2_DEV_ADMIN_PASSWORD ?? "devadmin123",
|
||||
adminType: "Super Admin",
|
||||
problemPermission: "All",
|
||||
realName: "开发用超管",
|
||||
username: process.env.OJ2_DEV_ADMIN_USERNAME ?? 'devadmin',
|
||||
password: process.env.OJ2_DEV_ADMIN_PASSWORD ?? 'devadmin123',
|
||||
adminType: 'Super Admin',
|
||||
problemPermission: 'All',
|
||||
realName: '开发用超管',
|
||||
})
|
||||
process.exit(0)
|
||||
Reference in new issue
Block a user