diff --git a/.vscode/settings.json b/.vscode/settings.json index 61477ff1..1f5a59e8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,5 +5,9 @@ "editor.detectIndentation": true, "editor.formatOnSave": true, "editor.renderWhitespace": "boundary", - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "[json]": { + "files.insertFinalNewline": true, + "files.trimFinalNewlines": false, + } } \ No newline at end of file diff --git a/package.json b/package.json index 94165c8a..1a78e21b 100644 --- a/package.json +++ b/package.json @@ -31,8 +31,8 @@ "@types/jest": "^26.0.22", "@types/node": "^14.14.37", "@types/semver": "^7.3.4", - "@typescript-eslint/eslint-plugin": "^4.20.0", - "@typescript-eslint/parser": "^4.20.0", + "@typescript-eslint/eslint-plugin": "^4.21.0", + "@typescript-eslint/parser": "^4.21.0", "cross-spawn": "^7.0.3", "eslint": "^7.23.0", "eslint-config-airbnb-typescript": "^12.0.0", @@ -48,4 +48,4 @@ "typescript": "4.2.3", "yargs": "^16.2.0" } -} \ No newline at end of file +} diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index eb02d707..b86dddb3 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -24,8 +24,8 @@ "license": "AGPL-3.0-only", "devDependencies": { "@types/bson": "^4.0.2", - "@types/fs-extra": "^9.0.9", + "@types/fs-extra": "^9.0.10", "@types/js-yaml": "^4.0.0", "@types/shell-quote": "^1.7.0" } -} \ No newline at end of file +} diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index ea666970..0b0d5e21 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.21.8", + "version": "2.21.9", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", @@ -38,8 +38,8 @@ "yargs": "^16.2.0" }, "devDependencies": { - "@types/adm-zip": "^0.4.33", - "@types/fs-extra": "^9.0.9", + "@types/adm-zip": "^0.4.34", + "@types/fs-extra": "^9.0.10", "@types/inquirer": "^7.3.1", "@types/js-yaml": "^4.0.0", "@types/koa": "^2.13.1", @@ -47,7 +47,7 @@ "@types/koa-static-cache": "^5.1.0", "@types/lodash": "^4.14.168", "@types/minio": "^7.0.7", - "@types/mongodb": "^3.6.11", + "@types/mongodb": "^3.6.12", "@types/nodemailer": "^6.4.1", "@types/serialize-javascript": "^5.0.0", "@types/sockjs": "^0.3.32", @@ -55,4 +55,4 @@ "@types/yargs": "^16.0.1", "formidable": "^1.2.2" } -} \ No newline at end of file +} diff --git a/packages/hydrooj/src/handler/judge.ts b/packages/hydrooj/src/handler/judge.ts index 503533f3..d6479604 100644 --- a/packages/hydrooj/src/handler/judge.ts +++ b/packages/hydrooj/src/handler/judge.ts @@ -32,6 +32,8 @@ async function _postJudge(rdoc: Rdoc) { ? await problem.inc(rdoc.domainId, rdoc.pid, 'nAccept', 1) : await problem.get(rdoc.domainId, rdoc.pid); const difficulty = difficultyAlgorithm(pdoc.nSubmit, pdoc.nAccept); + if (!updated) await record.updateMulti(rdoc.domainId, { uid: rdoc.uid, status: builtin.STATUS.STATUS_ACCEPTED }, { effective: false }); + await record.update(rdoc.domainId, rdoc._id, { effective: true }); await problem.edit(pdoc.domainId, pdoc.docId, { difficulty }); await bus.serial('record/judge', rdoc, updated); } diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index a32f6629..40fc0f9d 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -247,6 +247,7 @@ export interface Rdoc { progress?: number, input?: string, contest?: ContestInfo, + effective?: boolean, } export interface ScoreboardNode { diff --git a/packages/hydrooj/src/model/record.ts b/packages/hydrooj/src/model/record.ts index 81f1aad1..6f47be7b 100644 --- a/packages/hydrooj/src/model/record.ts +++ b/packages/hydrooj/src/model/record.ts @@ -1,5 +1,7 @@ import { - ObjectID, Collection, UpdateQuery, PushOperator, MatchKeysAndValues, OnlyFieldsOfType, + ObjectID, Collection, UpdateQuery, + PushOperator, MatchKeysAndValues, OnlyFieldsOfType, + FilterQuery, } from 'mongodb'; import { Dictionary } from 'lodash'; import moment from 'moment'; @@ -113,6 +115,20 @@ class RecordModel { return await RecordModel.get(domainId, _id); } + static async updateMulti( + domainId: string, $match: FilterQuery, + $set?: MatchKeysAndValues, + $push?: PushOperator, + $unset?: OnlyFieldsOfType, + ) { + const $update: UpdateQuery = {}; + if ($set && Object.keys($set).length) $update.$set = $set; + if ($push && Object.keys($push).length) $update.$push = $push; + if ($unset && Object.keys($unset).length) $update.$unset = $unset; + const res = await RecordModel.coll.updateMany({ domainId, ...$match }, $update); + return res.modifiedCount; + } + static reset(domainId: string, rid: ObjectID, isRejudge: boolean) { const upd: any = { score: 0, diff --git a/packages/hydrooj/src/pipelineUtils.ts b/packages/hydrooj/src/pipelineUtils.ts new file mode 100644 index 00000000..d868cdb9 --- /dev/null +++ b/packages/hydrooj/src/pipelineUtils.ts @@ -0,0 +1,40 @@ +/* eslint-disable no-await-in-loop */ +import { DomainDoc } from './loader'; +import domain from './model/domain'; +import problem, { Field, Pdoc } from './model/problem'; + +export async function iterateAllDomain(cb: (ddoc: DomainDoc, current?: number, total?: number) => Promise) { + const ddocs = await domain.getMulti().project({ _id: 1, owner: 1 }).toArray(); + for (const i in ddocs) await cb(ddocs[i], +i, ddocs.length); +} + +interface PartialPdoc extends Pdoc { + [key: string]: any, +} + +export async function iterateAllProblemInDomain( + domainId: string, + fields: (Field | string)[], + cb: (pdoc: PartialPdoc, current?: number, total?: number) => Promise, +) { + if (!fields.includes('domainId')) fields.push('domainId'); + if (!fields.includes('docId')) fields.push('docId'); + const cursor = problem.getMulti(domainId, {}, fields as any); + const total = await problem.getMulti(domainId, {}).count(); + let i = 0; + while (await cursor.hasNext()) { + const doc = await cursor.next(); + i++; + const res = await cb(doc, i, total); + if (res) await problem.edit(doc.domainId, doc.docId, res); + } +} + +export async function iterateAllProblem( + fields: (Field | string)[], + cb: (pdoc: PartialPdoc, current?: number, total?: number) => Promise, +) { + await iterateAllDomain(async (d) => { + await iterateAllProblemInDomain(d._id, fields, cb); + }); +} diff --git a/packages/hydrooj/src/script/problemStat.ts b/packages/hydrooj/src/script/problemStat.ts index 1593c812..ceaa7aac 100644 --- a/packages/hydrooj/src/script/problemStat.ts +++ b/packages/hydrooj/src/script/problemStat.ts @@ -50,7 +50,7 @@ export async function udoc() { export async function pdoc() { const pipeline = [ - { $match: { hidden: false, type: { $ne: 'run' } } }, + { $match: { hidden: false, type: { $ne: 'run' } }, effective: true }, { $group: { _id: { domainId: '$domainId', pid: '$pid', uid: '$uid' }, diff --git a/packages/hydrooj/src/script/updateFilelist.ts b/packages/hydrooj/src/script/updateFilelist.ts index ad13f710..f2cafd05 100644 --- a/packages/hydrooj/src/script/updateFilelist.ts +++ b/packages/hydrooj/src/script/updateFilelist.ts @@ -1,5 +1,5 @@ import storage from '../service/storage'; -import { iterateAllProblem, iterateAllProblemInDomain } from '../upgrade'; +import { iterateAllProblem, iterateAllProblemInDomain } from '../pipelineUtils'; export const description = 'Sync problem filelist from s3 service'; diff --git a/packages/hydrooj/src/upgrade.ts b/packages/hydrooj/src/upgrade.ts index f738743a..1a6d6799 100644 --- a/packages/hydrooj/src/upgrade.ts +++ b/packages/hydrooj/src/upgrade.ts @@ -1,61 +1,28 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable no-await-in-loop */ -import { ObjectID } from 'mongodb'; +import { FilterQuery, ObjectID } from 'mongodb'; import AdmZip from 'adm-zip'; import Queue from 'p-queue'; import yaml from 'js-yaml'; import { Progress } from './ui'; import { Logger } from './logger'; -import { DomainDoc } from './loader'; import { streamToBuffer } from './utils'; +import { iterateAllDomain, iterateAllProblem } from './pipelineUtils'; import gridfs from './service/gridfs'; import storage from './service/storage'; import db from './service/db'; -import problem, { Field, Pdoc } from './model/problem'; +import problem from './model/problem'; import domain from './model/domain'; import * as document from './model/document'; import * as system from './model/system'; import difficultyAlgorithm from './lib/difficulty'; +import { STATUS } from './model/builtin'; +import RecordModel from './model/record'; +import { Rdoc } from './interface'; const logger = new Logger('upgrade'); type UpgradeScript = void | (() => Promise); -export async function iterateAllDomain(cb: (ddoc: DomainDoc, current?: number, total?: number) => Promise) { - const ddocs = await domain.getMulti().project({ _id: 1, owner: 1 }).toArray(); - for (const i in ddocs) await cb(ddocs[i], +i, ddocs.length); -} - -interface PartialPdoc extends Pdoc { - [key: string]: any, -} - -export async function iterateAllProblemInDomain( - domainId: string, - fields: (Field | string)[], - cb: (pdoc: PartialPdoc, current?: number, total?: number) => Promise, -) { - if (!fields.includes('domainId')) fields.push('domainId'); - if (!fields.includes('docId')) fields.push('docId'); - const cursor = problem.getMulti(domainId, {}, fields as any); - const total = await problem.getMulti(domainId, {}).count(); - let i = 0; - while (await cursor.hasNext()) { - const doc = await cursor.next(); - i++; - const res = await cb(doc, i, total); - if (res) await problem.edit(doc.domainId, doc.docId, res); - } -} - -export async function iterateAllProblem( - fields: (Field | string)[], - cb: (pdoc: PartialPdoc, current?: number, total?: number) => Promise, -) { - await iterateAllDomain(async (d) => { - await iterateAllProblemInDomain(d._id, fields, cb); - }); -} - const scripts: UpgradeScript[] = [ // Mark as used null, @@ -262,6 +229,36 @@ const scripts: UpgradeScript[] = [ }); return true; }, + async function _19_20() { + const data = db.collection('record').aggregate([ + { $match: { hidden: false, type: { $ne: 'run' } } }, + { + $group: { + _id: { domainId: '$domainId', pid: '$pid', uid: '$uid' }, + nAccept: { + $sum: { + $cond: [{ $eq: ['$status', STATUS.STATUS_ACCEPTED] }, 1, 0], + }, + }, + }, + }, + ]); + while (await data.hasNext()) { + const d: any = await data.next(); + logger.info('%o', d); + const filter: FilterQuery = { domainId: d._id.domainId, pid: d._id.pid, uid: d._id.uid }; + if (d.nAccept) { + const [first] = await db.collection('record') + .find({ ...filter, status: STATUS.STATUS_ACCEPTED }) + .sort({ _id: 1 }).limit(1) + .project({ _id: 1 }) + .toArray(); + filter._id = { $lte: first._id }; + } + await db.collection('record').updateMany(filter, { $set: { effective: true } }); + } + return true; + }, ]; export default scripts; diff --git a/packages/migrate-vijos/package.json b/packages/migrate-vijos/package.json index be11b130..cd687da6 100644 --- a/packages/migrate-vijos/package.json +++ b/packages/migrate-vijos/package.json @@ -6,9 +6,9 @@ "author": "undefined ", "license": "MIT", "devDependencies": { - "@types/mongodb": "^3.6.11" + "@types/mongodb": "^3.6.12" }, "dependencies": { "mongodb": "^3.6.5" } -} \ No newline at end of file +} diff --git a/packages/sonic/package.json b/packages/sonic/package.json index 59316458..c54ef0a6 100644 --- a/packages/sonic/package.json +++ b/packages/sonic/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/sonic", - "version": "1.0.0", + "version": "1.0.1", "description": "Sonic search service", "main": "service.js", "typings": "service.d.ts", @@ -10,4 +10,4 @@ "dependencies": { "sonic-channel": "^1.2.6" } -} \ No newline at end of file +} diff --git a/packages/sonic/script.ts b/packages/sonic/script.ts index 0ddd4ebe..89be81bf 100644 --- a/packages/sonic/script.ts +++ b/packages/sonic/script.ts @@ -1,4 +1,4 @@ -import { iterateAllProblem, iterateAllProblemInDomain } from 'hydrooj/dist/upgrade'; +import { iterateAllProblem, iterateAllProblemInDomain } from 'hydrooj/dist/pipelineUtils'; import sonic from './service'; export const description = 'Sonic problem search re-index'; diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index d12e8a2f..9d06361d 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -8,7 +8,7 @@ "devDependencies": { "@babel/cli": "^7.13.14", "@babel/core": "^7.13.14", - "@babel/eslint-parser": "7.13.10", + "@babel/eslint-parser": "7.13.14", "@babel/plugin-proposal-class-properties": "^7.13.0", "@babel/plugin-proposal-export-namespace-from": "^7.12.13", "@babel/plugin-proposal-function-sent": "^7.12.13", @@ -26,14 +26,14 @@ "acorn": "^8.0.5", "acorn-stage3": "^4.0.0", "acorn-walk": "^8.0.2", - "astring": "^1.6.2", + "astring": "^1.7.3", "autoprefixer": "^9.8.6", "babel-loader": "^8.2.2", "babel-plugin-lodash": "^3.3.4", "babel-plugin-prismjs": "^2.0.1", "base-64": "^1.0.0", "chalk": "^4.1.0", - "classnames": "^2.3.0", + "classnames": "^2.3.1", "clipboard": "^2.0.8", "codemirror": "^5.59.4", "copy-webpack-plugin": "^6.1.1", @@ -105,7 +105,7 @@ "dependencies": { "98.css": "^0.1.16", "js-yaml": "^4.0.0", - "katex": "^0.13.0", + "katex": "^0.13.1", "lodash": "^4.17.21", "markdown-it": "^12.0.4", "markdown-it-anchor": "^7.1.0",