From 0685b88066320e7877b3a1f23351c6a4ff72b4c4 Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 27 Feb 2023 18:31:24 +0800 Subject: [PATCH] core: update to mongodb5 (#514) --- install/install.ts | 8 +- package.json | 14 +-- packages/hydrojudge/package.json | 2 +- packages/hydrojudge/src/hosts/hydro.ts | 6 +- packages/hydrooj/package.json | 13 ++- packages/hydrooj/src/entry/cli.ts | 6 +- packages/hydrooj/src/entry/setup.ts | 9 +- packages/hydrooj/src/handler/blog.ts | 26 ++--- packages/hydrooj/src/handler/contest.ts | 98 +++++++++---------- packages/hydrooj/src/handler/discussion.ts | 78 +++++++-------- packages/hydrooj/src/handler/home.ts | 12 +-- packages/hydrooj/src/handler/homework.ts | 26 ++--- packages/hydrooj/src/handler/judge.ts | 8 +- packages/hydrooj/src/handler/problem.ts | 78 +++++++-------- packages/hydrooj/src/handler/record.ts | 41 ++++---- packages/hydrooj/src/handler/status.ts | 10 +- packages/hydrooj/src/handler/training.ts | 48 ++++----- packages/hydrooj/src/handler/user.ts | 4 +- packages/hydrooj/src/interface.ts | 53 +++++----- packages/hydrooj/src/lib/paginate.ts | 15 ++- packages/hydrooj/src/lib/rank.ts | 4 +- packages/hydrooj/src/lib/validator.ts | 12 ++- packages/hydrooj/src/libs.ts | 5 +- packages/hydrooj/src/model/blog.ts | 24 ++--- packages/hydrooj/src/model/contest.ts | 42 ++++---- packages/hydrooj/src/model/discussion.ts | 58 +++++------ packages/hydrooj/src/model/document.ts | 50 +++++----- packages/hydrooj/src/model/domain.ts | 19 ++-- packages/hydrooj/src/model/message.ts | 27 +++-- packages/hydrooj/src/model/oplog.ts | 12 +-- packages/hydrooj/src/model/problem.ts | 30 +++--- packages/hydrooj/src/model/record.ts | 47 +++++---- packages/hydrooj/src/model/schedule.ts | 18 ++-- packages/hydrooj/src/model/solution.ts | 24 ++--- packages/hydrooj/src/model/task.ts | 16 +-- packages/hydrooj/src/model/token.ts | 11 ++- packages/hydrooj/src/model/training.ts | 24 ++--- packages/hydrooj/src/model/user.ts | 12 +-- packages/hydrooj/src/pipelineUtils.ts | 10 +- packages/hydrooj/src/script/blacklist.ts | 12 +-- packages/hydrooj/src/script/problemStat.ts | 16 +-- packages/hydrooj/src/script/rating.ts | 19 ++-- packages/hydrooj/src/service/bus.ts | 10 +- packages/hydrooj/src/service/db.ts | 14 ++- .../hydrooj/src/service/layers/renderer.ts | 6 +- packages/hydrooj/src/service/monitor.ts | 10 +- packages/hydrooj/src/typeutils.ts | 1 - packages/hydrooj/src/upgrade.ts | 21 ++-- packages/hydrooj/src/utils.ts | 6 +- packages/migrate/package.json | 3 +- packages/migrate/scripts/hustoj.ts | 4 +- packages/migrate/scripts/syzoj.ts | 4 +- packages/migrate/scripts/vijos.ts | 36 +++---- packages/ui-default/backendlib/misc.ts | 8 +- packages/ui-default/index.ts | 6 +- packages/ui-default/package.json | 2 +- packages/utils/lib/utils.ts | 4 +- packages/utils/package.json | 8 +- test/main.ts | 21 ++-- 59 files changed, 610 insertions(+), 601 deletions(-) diff --git a/install/install.ts b/install/install.ts index a0ffc1750..35fc5894d 100644 --- a/install/install.ts +++ b/install/install.ts @@ -335,13 +335,11 @@ connect-timeout = 10`); 'pm2 start mongod', () => sleep(3000), async () => { - // eslint-disable-next-line import/no-absolute-path - const { MongoClient } = require('/usr/local/share/.config/yarn/global/node_modules/mongodb'); + // eslint-disable-next-line + const { MongoClient, WriteConcern } = require('/usr/local/share/.config/yarn/global/node_modules/mongodb') as typeof import('mongodb'); const client = await MongoClient.connect('mongodb://127.0.0.1', { - useNewUrlParser: true, - useUnifiedTopology: true, readPreference: 'nearest', - writeConcern: 'majority', + writeConcern: new WriteConcern('majority'), }); await client.db('hydro').addUser('hydro', password, { roles: [{ role: 'readWrite', db: 'hydro' }], diff --git a/package.json b/package.json index e8825ee1c..f5b9ca20b 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "build:ui:production:webpack": "cross-env NODE_OPTIONS=--max_old_space_size=8192 node packages/ui-default/build --production", "test": "node test/entry.js", "benchmark": "cross-env BENCHMARK=true node test/entry.js", - "lint": "eslint packages plugins modules --ext ts --fix", - "lint:ci": "eslint packages plugins modules --ext ts", + "lint": "eslint packages plugins modules --ext .ts,.tsx,.jsx --fix", + "lint:ci": "eslint packages plugins modules --ext .ts,.tsx,.jsx", "lint:ui": "yarn workspace @hydrooj/ui-default lint --ext .js,.ts,.jsx,.tsx . --fix", "lint:ui:ci": "yarn workspace @hydrooj/ui-default lint --ext .js,.ts,.jsx,.tsx .", "debug": "node --trace-warnings --async-stack-traces --trace-deprecation packages/hydrooj/bin/hydrooj --debug --template", @@ -33,7 +33,7 @@ "@simplewebauthn/typescript-types": "^7.0.0", "@types/autocannon": "^7.9.0", "@types/cross-spawn": "^6.0.2", - "@types/node": "^18.11.18", + "@types/node": "^18.14.1", "@types/semver": "^7.3.13", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.53.0", @@ -46,9 +46,9 @@ "cross-env": "^7.0.3", "cross-spawn": "^7.0.3", "css-loader": "^6.7.3", - "esbuild": "0.16.16", + "esbuild": "0.17.10", "esbuild-loader": "^2.21.0", - "eslint": "^8.34.0", + "eslint": "^8.35.0", "eslint-import-resolver-typescript": "^3.5.3", "eslint-import-resolver-webpack": "^0.13.2", "friendly-errors-webpack-plugin": "^1.7.0", @@ -57,8 +57,8 @@ "latest-version": "7.0.0", "mini-css-extract-plugin": "^2.7.2", "monaco-editor-webpack-plugin": "^7.0.1", - "mongodb": "^3.7.3", - "mongodb-memory-server": "^8.11.4", + "mongodb": "^5.1.0", + "mongodb-memory-server": "^8.11.5", "nyc": "^15.1.0", "ora": "^6.1.2", "postcss-loader": "^7.0.2", diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 8f5f09b52..d6b2be8bc 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -9,7 +9,7 @@ "@hydrooj/utils": "workspace:*", "cac": "^6.7.14", "fs-extra": "^11.1.0", - "mongodb": "^3.7.3", + "mongodb": "^5.1.0", "p-queue": "^7.3.4", "schemastery": "^3.7.1", "shell-quote": "^1.8.0", diff --git a/packages/hydrojudge/src/hosts/hydro.ts b/packages/hydrojudge/src/hosts/hydro.ts index c5d904591..862ed96e5 100644 --- a/packages/hydrojudge/src/hosts/hydro.ts +++ b/packages/hydrojudge/src/hosts/hydro.ts @@ -1,7 +1,7 @@ /* eslint-disable no-await-in-loop */ import path from 'path'; import fs from 'fs-extra'; -import { ObjectID } from 'mongodb'; +import { ObjectId } from 'mongodb'; import PQueue from 'p-queue'; import superagent from 'superagent'; import WebSocket from 'ws'; @@ -143,8 +143,8 @@ export default class Hydro { return null; } - send(rid: string | ObjectID, key: 'next' | 'end', data: Partial) { - data.rid = new ObjectID(rid); + send(rid: string | ObjectId, key: 'next' | 'end', data: Partial) { + data.rid = new ObjectId(rid); data.key = key; if (data.case && typeof data.case.message === 'string') data.case.message = removeNixPath(data.case.message); if (typeof data.message === 'string') data.message = removeNixPath(data.message); diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index d8f49191f..2dcc15368 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -12,11 +12,11 @@ }, "preferUnplugged": true, "dependencies": { - "@aws-sdk/client-s3": "^3.272.0", - "@aws-sdk/lib-storage": "^3.272.0", + "@aws-sdk/client-s3": "^3.279.0", + "@aws-sdk/lib-storage": "^3.279.0", "@aws-sdk/middleware-endpoint": "^3.272.0", - "@aws-sdk/s3-presigned-post": "^3.275.0", - "@aws-sdk/s3-request-presigner": "^3.272.0", + "@aws-sdk/s3-presigned-post": "^3.279.0", + "@aws-sdk/s3-request-presigner": "^3.279.0", "@graphql-tools/schema": "^9.0.16", "@hydrooj/utils": "workspace:*", "@simplewebauthn/server": "^7.0.1", @@ -39,8 +39,8 @@ "lodash": "^4.17.21", "lru-cache": "7.14.1", "mime-types": "^2.1.35", - "moment-timezone": "^0.5.40", - "mongodb": "^3.7.3", + "moment-timezone": "^0.5.41", + "mongodb": "^5.1.0", "nanoid": "^4.0.1", "nodemailer": "^6.9.1", "notp": "^2.0.3", @@ -66,7 +66,6 @@ "@types/koa-static-cache": "^5.1.1", "@types/lodash": "^4.14.191", "@types/mime-types": "^2.1.1", - "@types/mongodb": "^3.6.20", "@types/nodemailer": "^6.4.7", "@types/notp": "^2.0.2", "@types/saslprep": "^1.0.1", diff --git a/packages/hydrooj/src/entry/cli.ts b/packages/hydrooj/src/entry/cli.ts index 5b63320c9..c40e30531 100644 --- a/packages/hydrooj/src/entry/cli.ts +++ b/packages/hydrooj/src/entry/cli.ts @@ -3,7 +3,7 @@ import path from 'path'; import cac from 'cac'; import fs from 'fs-extra'; -import { ObjectID } from 'mongodb'; +import { ObjectId } from 'mongodb'; import { Context } from '../context'; import db from '../service/db'; import { @@ -67,8 +67,8 @@ async function cli() { for (let i = 0; i < args.length; i++) { if ("'\"".includes(args[i][0]) && "'\"".includes(args[i][args[i].length - 1])) { args[i] = args[i].substr(1, args[i].length - 2); - } else if (args[i].length === 24 && ObjectID.isValid(args[i])) { - args[i] = new ObjectID(args[i]); + } else if (args[i].length === 24 && ObjectId.isValid(args[i])) { + args[i] = new ObjectId(args[i]); } else if ((+args[i]).toString() === args[i]) { args[i] = +args[i]; } else if (args[i].startsWith('~')) { diff --git a/packages/hydrooj/src/entry/setup.ts b/packages/hydrooj/src/entry/setup.ts index 3a541c3ff..d20a6df9b 100644 --- a/packages/hydrooj/src/entry/setup.ts +++ b/packages/hydrooj/src/entry/setup.ts @@ -5,7 +5,7 @@ import cac from 'cac'; import fs from 'fs-extra'; import Koa, { Context } from 'koa'; import Body from 'koa-body'; -import mongodb from 'mongodb'; +import { MongoClient, WriteConcern } from 'mongodb'; import { Logger } from '../logger'; const logger = new Logger('setup'); @@ -85,11 +85,12 @@ async function post(ctx: Context) { if (username) mongourl += `${username}:${password}@`; mongourl += `${host}:${port}/${name}`; try { - const Database = await mongodb.MongoClient.connect(mongourl, { - useNewUrlParser: true, useUnifiedTopology: true, + const Database = await MongoClient.connect(mongourl, { + readPreference: 'nearest', + writeConcern: new WriteConcern('majority'), }); const db = Database.db(name); - const coll = db.collection('system'); + const coll = db.collection('system'); await Promise.all([ coll.updateOne( { _id: 'server.url' }, diff --git a/packages/hydrooj/src/handler/blog.ts b/packages/hydrooj/src/handler/blog.ts index 8afe4e2fe..7f1b93283 100644 --- a/packages/hydrooj/src/handler/blog.ts +++ b/packages/hydrooj/src/handler/blog.ts @@ -1,4 +1,4 @@ -import { ObjectID } from 'mongodb'; +import { ObjectId } from 'mongodb'; import { DiscussionNotFoundError } from '../error'; import { BlogDoc } from '../interface'; import paginate from '../lib/paginate'; @@ -12,8 +12,8 @@ import { Handler, param, Types } from '../service/server'; class BlogHandler extends Handler { ddoc?: BlogDoc; - @param('did', Types.ObjectID, true) - async _prepare(domainId: string, did: ObjectID) { + @param('did', Types.ObjectId, true) + async _prepare(domainId: string, did: ObjectId) { if (did) { this.ddoc = await blog.get(did); if (!this.ddoc) throw new DiscussionNotFoundError(domainId, did); @@ -42,8 +42,8 @@ class BlogUserHandler extends BlogHandler { } class BlogDetailHandler extends BlogHandler { - @param('did', Types.ObjectID) - async get(domainId: string, did: ObjectID) { + @param('did', Types.ObjectId) + async get(domainId: string, did: ObjectId) { const dsdoc = this.user.hasPriv(PRIV.PRIV_USER_PROFILE) ? await blog.getStatus(did, this.user._id) : null; @@ -64,14 +64,14 @@ class BlogDetailHandler extends BlogHandler { this.checkPriv(PRIV.PRIV_USER_PROFILE); } - @param('did', Types.ObjectID) - async postStar(domainId: string, did: ObjectID) { + @param('did', Types.ObjectId) + async postStar(domainId: string, did: ObjectId) { await blog.setStar(did, this.user._id, true); this.back({ star: true }); } - @param('did', Types.ObjectID) - async postUnstar(domainId: string, did: ObjectID) { + @param('did', Types.ObjectId) + async postUnstar(domainId: string, did: ObjectId) { await blog.setStar(did, this.user._id, false); this.back({ star: false }); } @@ -92,10 +92,10 @@ class BlogEditHandler extends BlogHandler { this.response.redirect = this.url('blog_detail', { uid: this.user._id, did }); } - @param('did', Types.ObjectID) + @param('did', Types.ObjectId) @param('title', Types.Title) @param('content', Types.Content) - async postUpdate(domainId: string, did: ObjectID, title: string, content: string) { + async postUpdate(domainId: string, did: ObjectId, title: string, content: string) { if (!this.user.own(this.ddoc)) this.checkPriv(PRIV.PRIV_EDIT_SYSTEM); await Promise.all([ blog.edit(did, title, content), @@ -105,8 +105,8 @@ class BlogEditHandler extends BlogHandler { this.response.redirect = this.url('blog_detail', { uid: this.user._id, did }); } - @param('did', Types.ObjectID) - async postDelete(domainId: string, did: ObjectID) { + @param('did', Types.ObjectId) + async postDelete(domainId: string, did: ObjectId) { if (!this.user.own(this.ddoc)) this.checkPriv(PRIV.PRIV_EDIT_SYSTEM); await Promise.all([ blog.del(did), diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index 6294a60e6..e1680395b 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -2,7 +2,7 @@ import AdmZip from 'adm-zip'; import { statSync } from 'fs-extra'; import { pick } from 'lodash'; import moment from 'moment-timezone'; -import { ObjectID } from 'mongodb'; +import { ObjectId } from 'mongodb'; import { Counter, sortFiles, streamToBuffer, Time, } from '@hydrooj/utils/lib/utils'; @@ -47,8 +47,8 @@ registerResolver( 'Query', 'contest(id: ObjectID!)', 'Contest', async (arg, ctx) => { ctx.checkPerm(PERM.PERM_VIEW); - arg.id = new ObjectID(arg.id); - ctx.tdoc = await contest.get(ctx.args.domainId, new ObjectID(arg.id)); + arg.id = new ObjectId(arg.id); + ctx.tdoc = await contest.get(ctx.args.domainId, new ObjectId(arg.id)); if (!ctx.tdoc) throw new ContestNotFoundError(ctx.args.domainId, arg.id); return ctx.tdoc; }, @@ -92,8 +92,8 @@ export class ContestDetailBaseHandler extends Handler { tdoc?: Tdoc<30>; tsdoc?: any; - @param('tid', Types.ObjectID, true) - async __prepare(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId, true) + async __prepare(domainId: string, tid: ObjectId) { if (!tid) return; // ProblemDetailHandler also extends from ContestDetailBaseHandler [this.tdoc, this.tsdoc] = await Promise.all([ contest.get(domainId, tid), @@ -110,8 +110,8 @@ export class ContestDetailBaseHandler extends Handler { } } - @param('tid', Types.ObjectID, true) - async after(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId, true) + async after(domainId: string, tid: ObjectId) { if (!tid || this.tdoc.rule === 'homework') return; if (this.request.json || !this.response.template) return; const pdoc = 'pdoc' in this ? (this as any).pdoc : {}; @@ -151,8 +151,8 @@ export class ContestDetailHandler extends Handler { tdoc?: Tdoc<30>; tsdoc?: any; - @param('tid', Types.ObjectID) - async prepare(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId) + async prepare(domainId: string, tid: ObjectId) { [this.tdoc, this.tsdoc] = await Promise.all([ contest.get(domainId, tid), contest.getStatus(domainId, tid, this.user._id), @@ -169,8 +169,8 @@ export class ContestDetailHandler extends Handler { } } - @param('tid', Types.ObjectID) - async get(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId) + async get(domainId: string, tid: ObjectId) { this.response.template = 'contest_detail.html'; const udict = await user.getList(domainId, [this.tdoc.owner]); this.response.body = { @@ -216,9 +216,9 @@ export class ContestDetailHandler extends Handler { ]; } - @param('tid', Types.ObjectID) + @param('tid', Types.ObjectId) @param('code', Types.String, true) - async postAttend(domainId: string, tid: ObjectID, code = '') { + async postAttend(domainId: string, tid: ObjectId, code = '') { if (contest.isDone(this.tdoc)) throw new ContestNotLiveError(tid); if (this.tdoc._code && code !== this.tdoc._code) throw new InvalidTokenError('Contest Invitation', code); await contest.attend(domainId, tid, this.user._id); @@ -227,8 +227,8 @@ export class ContestDetailHandler extends Handler { } export class ContestProblemListHandler extends ContestDetailBaseHandler { - @param('tid', Types.ObjectID) - async get(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId) + async get(domainId: string, tid: ObjectId) { if (contest.isNotStarted(this.tdoc)) throw new ContestNotLiveError(domainId, tid); if (!this.tsdoc?.attend && !contest.isDone(this.tdoc)) throw new ContestNotAttendedError(domainId, tid); const [pdict, udict] = await Promise.all([ @@ -260,9 +260,9 @@ export class ContestProblemListHandler extends ContestDetailBaseHandler { } export class ContestScoreboardHandler extends ContestDetailBaseHandler { - @param('tid', Types.ObjectID) + @param('tid', Types.ObjectId) @param('ext', Types.Range(['csv', 'html', 'ghost']), true) - async get(domainId: string, tid: ObjectID, ext = '') { + async get(domainId: string, tid: ObjectId, ext = '') { if (!contest.canShowScoreboard.call(this, this.tdoc, true)) throw new ContestScoreboardHiddenError(tid); if (ext) { await this.exportScoreboard(domainId, tid, ext); @@ -279,7 +279,7 @@ export class ContestScoreboardHandler extends ContestDetailBaseHandler { }; } - async exportGhost(domainId: string, tid: ObjectID) { + async exportGhost(domainId: string, tid: ObjectId) { const tdoc = this.tdoc; const [pdict, teams] = await Promise.all([ problem.getList(domainId, tdoc.pids, true, false, problem.PROJECTION_LIST, true), @@ -288,7 +288,7 @@ export class ContestScoreboardHandler extends ContestDetailBaseHandler { const udict = await user.getList(domainId, teams.map((i) => i.uid)); const teamIds: Record = {}; for (let i = 1; i <= teams.length; i++) teamIds[teams[i - 1].uid] = i; - const time = (t: ObjectID) => Math.floor((t.getTimestamp().getTime() - tdoc.beginAt.getTime()) / Time.second); + const time = (t: ObjectId) => Math.floor((t.getTimestamp().getTime() - tdoc.beginAt.getTime()) / Time.second); const pid = (i: number) => String.fromCharCode(65 + i); const escape = (i: string) => i.replace(/[",]/g, ''); const unknownSchool = this.translate('Unknown School'); @@ -316,7 +316,7 @@ export class ContestScoreboardHandler extends ContestDetailBaseHandler { this.binary(res.join('\n'), `${this.tdoc.title}.ghost`); } - async exportScoreboard(domainId: string, tid: ObjectID, ext: string) { + async exportScoreboard(domainId: string, tid: ObjectId, ext: string) { await this.limitRate('scoreboard_download', 60, 3); if (ext === 'ghost') { await this.exportGhost(domainId, tid); @@ -330,8 +330,8 @@ export class ContestScoreboardHandler extends ContestDetailBaseHandler { this.binary(await getContent[ext](rows, this.tdoc), `${this.tdoc.title}.${ext}`); } - @param('tid', Types.ObjectID) - async postUnlock(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId) + async postUnlock(domainId: string, tid: ObjectId) { if (!this.user.own(this.tdoc)) this.checkPerm(PERM.PERM_EDIT_CONTEST); if (!contest.isDone(this.tdoc)) throw new ContestNotEndedError(domainId, tid); await contest.unlockScoreboard(domainId, tid); @@ -342,8 +342,8 @@ export class ContestScoreboardHandler extends ContestDetailBaseHandler { export class ContestEditHandler extends Handler { tdoc: Tdoc; - @param('tid', Types.ObjectID, true) - async prepare(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId, true) + async prepare(domainId: string, tid: ObjectId) { if (tid) { this.tdoc = await contest.get(domainId, tid); if (!this.tdoc) throw new ContestNotFoundError(domainId, tid); @@ -353,8 +353,8 @@ export class ContestEditHandler extends Handler { } else this.checkPerm(PERM.PERM_CREATE_CONTEST); } - @param('tid', Types.ObjectID, true) - async get(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId, true) + async get(domainId: string, tid: ObjectId) { this.response.template = 'contest_edit.html'; const rules = {}; for (const i in contest.RULES) { @@ -375,7 +375,7 @@ export class ContestEditHandler extends Handler { }; } - @param('tid', Types.ObjectID, true) + @param('tid', Types.ObjectId, true) @param('beginAtDate', Types.Date) @param('beginAtTime', Types.Time) @param('duration', Types.Float) @@ -392,7 +392,7 @@ export class ContestEditHandler extends Handler { @param('maintainer', Types.NumericArray, true) @param('allowViewCode', Types.Boolean) async postUpdate( - domainId: string, tid: ObjectID, beginAtDate: string, beginAtTime: string, duration: number, + domainId: string, tid: ObjectId, beginAtDate: string, beginAtTime: string, duration: number, title: string, content: string, rule: string, _pids: string, rated = false, _code = '', autoHide = false, assign: string[] = null, lock: number = null, contestDuration: number = null, maintainer: number[] = [], allowViewCode = false, @@ -442,8 +442,8 @@ export class ContestEditHandler extends Handler { this.response.redirect = this.url('contest_detail', { tid }); } - @param('tid', Types.ObjectID) - async postDelete(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId) + async postDelete(domainId: string, tid: ObjectId) { if (!this.user.own(this.tdoc)) this.checkPerm(PERM.PERM_EDIT_CONTEST); await contest.del(domainId, tid); await record.updateMulti(domainId, { domainId, contest: tid }, undefined, undefined, { contest: '' }); @@ -461,9 +461,9 @@ export class ContestManagementBaseHandler extends ContestDetailBaseHandler { } export class ContestCodeHandler extends Handler { - @param('tid', Types.ObjectID) + @param('tid', Types.ObjectId) @param('all', Types.Boolean) - async get(domainId: string, tid: ObjectID, all: boolean) { + async get(domainId: string, tid: ObjectId, all: boolean) { await this.limitRate('contest_code', 60, 10); const [tdoc, tsdocs] = await contest.getAndListStatus(domainId, tid); if (!this.user.own(tdoc) && !this.user.hasPriv(PRIV.PRIV_READ_RECORD_CODE)) { @@ -490,7 +490,7 @@ export class ContestCodeHandler extends Handler { } const zip = new AdmZip(); const rdocs = await record.getMulti(domainId, { - _id: { $in: Array.from(Object.keys(rnames)).map((id) => new ObjectID(id)) }, + _id: { $in: Array.from(Object.keys(rnames)).map((id) => new ObjectId(id)) }, }).toArray(); await Promise.all(rdocs.map(async (rdoc) => { if (rdoc.files?.code) { @@ -509,8 +509,8 @@ export class ContestCodeHandler extends Handler { } export class ContestManagementHandler extends ContestManagementBaseHandler { - @param('tid', Types.ObjectID) - async get(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId) + async get(domainId: string, tid: ObjectId) { this.response.body = { tdoc: this.tdoc, tsdoc: this.tsdoc, @@ -523,9 +523,9 @@ export class ContestManagementHandler extends ContestManagementBaseHandler { this.response.template = 'contest_manage.html'; } - @param('tid', Types.ObjectID) + @param('tid', Types.ObjectId) @param('content', Types.Content) - async postBroadcast(domainId: string, tid: ObjectID, content: string) { + async postBroadcast(domainId: string, tid: ObjectId, content: string) { const tsdocs = await contest.getMultiStatus(domainId, { docId: tid }).toArray(); const uids = Array.from(new Set(tsdocs.map((tsdoc) => tsdoc.uid))); const flag = contest.isOngoing(this.tdoc) ? message.FLAG_ALERT : message.FLAG_UNREAD; @@ -533,9 +533,9 @@ export class ContestManagementHandler extends ContestManagementBaseHandler { this.back(); } - @param('tid', Types.ObjectID) + @param('tid', Types.ObjectId) @post('filename', Types.Filename, true) - async postUploadFile(domainId: string, tid: ObjectID, filename: string) { + async postUploadFile(domainId: string, tid: ObjectId, filename: string) { if ((this.tdoc.files?.length || 0) >= system.get('limit.contest_files')) { throw new FileLimitExceededError('count'); } @@ -556,9 +556,9 @@ export class ContestManagementHandler extends ContestManagementBaseHandler { this.back(); } - @param('tid', Types.ObjectID) + @param('tid', Types.ObjectId) @post('files', Types.ArrayOf(Types.Filename)) - async postDeleteFiles(domainId: string, tid: ObjectID, files: string[]) { + async postDeleteFiles(domainId: string, tid: ObjectId, files: string[]) { await Promise.all([ storage.del(files.map((t) => `contest/${domainId}/${tid}/${t}`), this.user._id), contest.edit(domainId, tid, { files: this.tdoc.files.filter((i) => !files.includes(i.name)) }), @@ -568,10 +568,10 @@ export class ContestManagementHandler extends ContestManagementBaseHandler { } export class ContestFileDownloadHandler extends ContestDetailBaseHandler { - @param('tid', Types.ObjectID) + @param('tid', Types.ObjectId) @param('filename', Types.Filename) @param('noDisposition', Types.Boolean) - async get(domainId: string, tid: ObjectID, filename: string, noDisposition = false) { + async get(domainId: string, tid: ObjectId, filename: string, noDisposition = false) { this.response.addHeader('Cache-Control', 'public'); const target = `contest/${domainId}/${tid}/${filename}`; const file = await storage.getMeta(target); @@ -586,8 +586,8 @@ export class ContestFileDownloadHandler extends ContestDetailBaseHandler { } export class ContestUserHandler extends ContestManagementBaseHandler { - @param('tid', Types.ObjectID) - async get(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId) + async get(domainId: string, tid: ObjectId) { const tsdocs = await contest.getMultiStatus(domainId, { docId: tid }).toArray(); tsdocs.forEach((i) => { i.endAt = this.tdoc.duration ? moment(this.tsdoc.startAt).add(this.tdoc.duration, 'hours').toDate() : null; @@ -598,17 +598,17 @@ export class ContestUserHandler extends ContestManagementBaseHandler { this.response.template = 'contest_user.html'; } - @param('tid', Types.ObjectID) + @param('tid', Types.ObjectId) @param('uids', Types.NumericArray) @param('unrank', Types.Boolean) - async postAddUser(domainId: string, tid: ObjectID, uids: number[], unrank = false) { + async postAddUser(domainId: string, tid: ObjectId, uids: number[], unrank = false) { await Promise.all(uids.map((uid) => contest.attend(domainId, tid, uid, { unrank }))); this.back(); } - @param('tid', Types.ObjectID) + @param('tid', Types.ObjectId) @param('uid', Types.PositiveInt) - async postRank(domainId: string, tid: ObjectID, uid: number) { + async postRank(domainId: string, tid: ObjectId, uid: number) { const tsdoc = await contest.getStatus(domainId, tid, uid); if (!tsdoc) throw new ContestNotAttendedError(uid); await contest.setStatus(domainId, tid, uid, { unrank: !tsdoc.unrank }); diff --git a/packages/hydrooj/src/handler/discussion.ts b/packages/hydrooj/src/handler/discussion.ts index ebc90ef8e..c3c9005f1 100644 --- a/packages/hydrooj/src/handler/discussion.ts +++ b/packages/hydrooj/src/handler/discussion.ts @@ -1,5 +1,5 @@ import { isSafeInteger } from 'lodash'; -import { ObjectID } from 'mongodb'; +import { ObjectId } from 'mongodb'; import { DiscussionLockedError, DiscussionNotFoundError, DocumentNotFoundError, PermissionError, @@ -31,12 +31,12 @@ class DiscussionHandler extends Handler { @param('type', Types.Range(Object.keys(typeMapper)), true) @param('name', Types.Name, true) - @param('did', Types.ObjectID, true) - @param('drid', Types.ObjectID, true) - @param('drrid', Types.ObjectID, true) + @param('did', Types.ObjectId, true) + @param('drid', Types.ObjectId, true) + @param('drrid', Types.ObjectId, true) async _prepare( domainId: string, type: string, name: string, - did: ObjectID, drid: ObjectID, drrid: ObjectID, + did: ObjectId, drid: ObjectId, drrid: ObjectId, ) { this.checkPerm(PERM.PERM_VIEW_DISCUSSION); if (did) { @@ -98,8 +98,8 @@ class DiscussionNodeHandler extends DiscussionHandler { @param('name', Types.Name) @param('page', Types.PositiveInt, true) async get(domainId: string, type: string, _name: string, page = 1) { - let name: ObjectID | string | number; - if (ObjectID.isValid(_name)) name = new ObjectID(_name); + let name: ObjectId | string | number; + if (ObjectId.isValid(_name)) name = new ObjectId(_name); else if (isSafeInteger(parseInt(_name, 10))) name = parseInt(_name, 10); else name = _name; const [ddocs, dpcount] = await paginate( @@ -162,9 +162,9 @@ class DiscussionCreateHandler extends DiscussionHandler { } class DiscussionDetailHandler extends DiscussionHandler { - @param('did', Types.ObjectID) + @param('did', Types.ObjectId) @param('page', Types.PositiveInt, true) - async get(domainId: string, did: ObjectID, page = 1) { + async get(domainId: string, did: ObjectId, page = 1) { const dsdoc = this.user.hasPriv(PRIV.PRIV_USER_PROFILE) ? await discussion.getStatus(domainId, did, this.user._id) : null; @@ -208,19 +208,19 @@ class DiscussionDetailHandler extends DiscussionHandler { this.checkPriv(PRIV.PRIV_USER_PROFILE); } - @param('did', Types.ObjectID) + @param('did', Types.ObjectId) @param('lock', Types.Boolean) - async postSetLock(domainId: string, did: ObjectID, lock: boolean) { + async postSetLock(domainId: string, did: ObjectId, lock: boolean) { if (!this.user.own(this.ddoc)) this.checkPerm(PERM.PERM_LOCK_DISCUSSION); await discussion.edit(domainId, did, { lock }); this.back(); } @param('type', Types.Range(['did', 'drid'])) - @param('id', Types.ObjectID) + @param('id', Types.ObjectId) @param('emoji', Types.Emoji) @param('reverse', Types.Boolean) - async postReaction(domainId: string, type: string, did: ObjectID, id: string, reverse = false) { + async postReaction(domainId: string, type: string, did: ObjectId, id: string, reverse = false) { this.checkPerm(PERM.PERM_ADD_REACTION); const docType = type === 'did' ? document.TYPE_DISCUSSION : document.TYPE_DISCUSSION_REPLY; const [doc, sdoc] = await discussion.react(domainId, docType, did, id, this.user._id, reverse); @@ -228,9 +228,9 @@ class DiscussionDetailHandler extends DiscussionHandler { this.back(); } - @param('did', Types.ObjectID) + @param('did', Types.ObjectId) @param('content', Types.Content) - async postReply(domainId: string, did: ObjectID, content: string) { + async postReply(domainId: string, did: ObjectId, content: string) { this.checkPerm(PERM.PERM_REPLY_DISCUSSION); if (this.ddoc.lock) throw new DiscussionLockedError(domainId, did); await this.limitRate('add_discussion', 3600, 60); @@ -247,9 +247,9 @@ class DiscussionDetailHandler extends DiscussionHandler { this.back({ drid }); } - @param('drid', Types.ObjectID) + @param('drid', Types.ObjectId) @param('content', Types.Content) - async postTailReply(domainId: string, drid: ObjectID, content: string) { + async postTailReply(domainId: string, drid: ObjectId, content: string) { this.checkPerm(PERM.PERM_REPLY_DISCUSSION); if (this.ddoc.lock) throw new DiscussionLockedError(domainId, this.ddoc.docId); await this.limitRate('add_discussion', 3600, 60); @@ -266,9 +266,9 @@ class DiscussionDetailHandler extends DiscussionHandler { this.back(); } - @param('drid', Types.ObjectID) + @param('drid', Types.ObjectId) @param('content', Types.Content) - async postEditReply(domainId: string, drid: ObjectID, content: string) { + async postEditReply(domainId: string, drid: ObjectId, content: string) { this.checkPerm(PERM.PERM_EDIT_DISCUSSION_REPLY_SELF); if (!this.user.own(this.drdoc)) throw new PermissionError(PERM.PERM_EDIT_DISCUSSION_REPLY_SELF); await Promise.all([ @@ -278,8 +278,8 @@ class DiscussionDetailHandler extends DiscussionHandler { this.back(); } - @param('drid', Types.ObjectID) - async postDeleteReply(domainId: string, drid: ObjectID) { + @param('drid', Types.ObjectId) + async postDeleteReply(domainId: string, drid: ObjectId) { const deleteBy = this.user.own(this.drdoc) ? 'self' : this.user.own(this.ddoc) ? 'DiscussionOwner' : 'Admin'; if (!(this.user.own(this.ddoc) && this.user.hasPerm(PERM.PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION))) { @@ -305,10 +305,10 @@ class DiscussionDetailHandler extends DiscussionHandler { this.back(); } - @param('drid', Types.ObjectID) - @param('drrid', Types.ObjectID) + @param('drid', Types.ObjectId) + @param('drrid', Types.ObjectId) @param('content', Types.Content) - async postEditTailReply(domainId: string, drid: ObjectID, drrid: ObjectID, content: string) { + async postEditTailReply(domainId: string, drid: ObjectId, drrid: ObjectId, content: string) { this.checkPerm(PERM.PERM_EDIT_DISCUSSION_REPLY_SELF); if (!this.user.own(this.drrdoc)) throw new PermissionError(PERM.PERM_EDIT_DISCUSSION_REPLY_SELF); await Promise.all([ @@ -318,9 +318,9 @@ class DiscussionDetailHandler extends DiscussionHandler { this.back(); } - @param('drid', Types.ObjectID) - @param('drrid', Types.ObjectID) - async postDeleteTailReply(domainId: string, drid: ObjectID, drrid: ObjectID) { + @param('drid', Types.ObjectId) + @param('drrid', Types.ObjectId) + async postDeleteTailReply(domainId: string, drid: ObjectId, drrid: ObjectId) { const deleteBy = this.user.own(this.drrdoc) ? 'self' : 'Admin'; if (!this.user.own(this.drrdoc)) this.checkPerm(PERM.PERM_DELETE_DISCUSSION_REPLY); const msg = JSON.stringify({ @@ -340,26 +340,26 @@ class DiscussionDetailHandler extends DiscussionHandler { this.back(); } - @param('did', Types.ObjectID) - async postStar(domainId: string, did: ObjectID) { + @param('did', Types.ObjectId) + async postStar(domainId: string, did: ObjectId) { await discussion.setStar(domainId, did, this.user._id, true); this.back({ star: true }); } - @param('did', Types.ObjectID) - async postUnstar(domainId: string, did: ObjectID) { + @param('did', Types.ObjectId) + async postUnstar(domainId: string, did: ObjectId) { await discussion.setStar(domainId, did, this.user._id, false); this.back({ star: false }); } } class DiscussionRawHandler extends DiscussionHandler { - @param('did', Types.ObjectID, true) - @param('drid', Types.ObjectID, true) - @param('drrid', Types.ObjectID, true) + @param('did', Types.ObjectId, true) + @param('drid', Types.ObjectId, true) + @param('drrid', Types.ObjectId, true) @param('time', Types.UnsignedInt, true) @param('all', Types.Boolean) - async get(domainId: string, did: ObjectID, drid: ObjectID, drrid: ObjectID, ts: number, all = false) { + async get(domainId: string, did: ObjectId, drid: ObjectId, drrid: ObjectId, ts: number, all = false) { if (all) { this.response.body.history = await discussion.getHistory(domainId, drrid || drid || did); } else { @@ -384,13 +384,13 @@ class DiscussionEditHandler extends DiscussionHandler { this.response.body = { ddoc: this.ddoc, path }; } - @param('did', Types.ObjectID) + @param('did', Types.ObjectId) @param('title', Types.Title) @param('content', Types.Content) @param('highlight', Types.Boolean) @param('pin', Types.Boolean) async postUpdate( - domainId: string, did: ObjectID, title: string, content: string, + domainId: string, did: ObjectId, title: string, content: string, highlight = false, pin = false, ) { if (!this.user.own(this.ddoc)) this.checkPerm(PERM.PERM_EDIT_DISCUSSION); @@ -407,8 +407,8 @@ class DiscussionEditHandler extends DiscussionHandler { this.response.redirect = this.url('discussion_detail', { did }); } - @param('did', Types.ObjectID) - async postDelete(domainId: string, did: ObjectID) { + @param('did', Types.ObjectId) + async postDelete(domainId: string, did: ObjectId) { const deleteBy = this.user.own(this.ddoc) ? 'self' : 'Admin'; if (!this.user.own(this.ddoc)) this.checkPerm(PERM.PERM_DELETE_DISCUSSION); else this.checkPerm(PERM.PERM_DELETE_DISCUSSION_SELF); diff --git a/packages/hydrooj/src/handler/home.ts b/packages/hydrooj/src/handler/home.ts index 5a4933dd0..8d2546fff 100644 --- a/packages/hydrooj/src/handler/home.ts +++ b/packages/hydrooj/src/handler/home.ts @@ -2,7 +2,7 @@ import path from 'path'; import { generateRegistrationOptions, verifyRegistrationResponse } from '@simplewebauthn/server'; import yaml from 'js-yaml'; import { pick } from 'lodash'; -import { Binary, ObjectID } from 'mongodb'; +import { Binary, ObjectId } from 'mongodb'; import { Context } from '../context'; import { AuthOperationError, BlacklistedError, DomainAlreadyExistsError, InvalidTokenError, @@ -293,7 +293,7 @@ class HomeSecurityHandler extends Handler { @requireSudo @param('id', Types.String) async postDisableAuthn(domainId: string, id: string) { - const authenticators = this.user._authenticators?.filter((c) => c.credentialID.buffer.toString('base64') !== id); + const authenticators = this.user._authenticators?.filter((c) => Buffer.from(c.credentialID.buffer).toString('base64') !== id); if (this.user._authenticators?.length === authenticators?.length) throw new ValidationError('authenticator'); await user.setById(this.user._id, { authenticators }); this.back(); @@ -498,16 +498,16 @@ class HomeMessagesHandler extends Handler { this.back({ mdoc, udoc }); } - @param('messageId', Types.ObjectID) - async postDeleteMessage(domainId: string, messageId: ObjectID) { + @param('messageId', Types.ObjectId) + async postDeleteMessage(domainId: string, messageId: ObjectId) { const msg = await message.get(messageId); if ([msg.from, msg.to].includes(this.user._id)) await message.del(messageId); else throw new PermissionError(); this.back(); } - @param('messageId', Types.ObjectID) - async postRead(domainId: string, messageId: ObjectID) { + @param('messageId', Types.ObjectId) + async postRead(domainId: string, messageId: ObjectId) { const msg = await message.get(messageId); if ([msg.from, msg.to].includes(this.user._id)) { await message.setFlag(messageId, message.FLAG_UNREAD); diff --git a/packages/hydrooj/src/handler/homework.ts b/packages/hydrooj/src/handler/homework.ts index 8925ead2f..2296cf42a 100644 --- a/packages/hydrooj/src/handler/homework.ts +++ b/packages/hydrooj/src/handler/homework.ts @@ -1,6 +1,6 @@ import yaml from 'js-yaml'; import moment from 'moment-timezone'; -import { ObjectID } from 'mongodb'; +import { ObjectId } from 'mongodb'; import { Time } from '@hydrooj/utils/lib/utils'; import { ContestNotFoundError, HomeworkNotLiveError, NotAssignedError, ValidationError, @@ -42,8 +42,8 @@ class HomeworkMainHandler extends Handler { } class HomeworkDetailHandler extends Handler { - @param('tid', Types.ObjectID) - async prepare(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId) + async prepare(domainId: string, tid: ObjectId) { const tdoc = await contest.get(domainId, tid); if (tdoc.rule !== 'homework') throw new ContestNotFoundError(domainId, tid); if (tdoc.assign?.length && !this.user.own(tdoc)) { @@ -53,9 +53,9 @@ class HomeworkDetailHandler extends Handler { } } - @param('tid', Types.ObjectID) + @param('tid', Types.ObjectId) @param('page', Types.PositiveInt, true) - async get(domainId: string, tid: ObjectID, page = 1) { + async get(domainId: string, tid: ObjectId, page = 1) { const [tdoc, tsdoc] = await Promise.all([ contest.get(domainId, tid), contest.getStatus(domainId, tid, this.user._id), @@ -101,8 +101,8 @@ class HomeworkDetailHandler extends Handler { Object.assign(this.response.body, { pdict, psdict, rdict }); } - @param('tid', Types.ObjectID) - async postAttend(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId) + async postAttend(domainId: string, tid: ObjectId) { this.checkPerm(PERM.PERM_ATTEND_HOMEWORK); const tdoc = await contest.get(domainId, tid); if (contest.isDone(tdoc)) throw new HomeworkNotLiveError(tdoc.docId); @@ -112,8 +112,8 @@ class HomeworkDetailHandler extends Handler { } class HomeworkEditHandler extends Handler { - @param('tid', Types.ObjectID, true) - async get(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId, true) + async get(domainId: string, tid: ObjectId) { const tdoc = tid ? await contest.get(domainId, tid) : null; if (!tid) this.checkPerm(PERM.PERM_CREATE_HOMEWORK); else if (!this.user.own(tdoc)) this.checkPerm(PERM.PERM_EDIT_HOMEWORK); @@ -143,7 +143,7 @@ class HomeworkEditHandler extends Handler { }; } - @param('tid', Types.ObjectID, true) + @param('tid', Types.ObjectId, true) @param('beginAtDate', Types.Date) @param('beginAtTime', Types.Time) @param('penaltySinceDate', Types.Date) @@ -157,7 +157,7 @@ class HomeworkEditHandler extends Handler { @param('maintainer', Types.NumericArray, true) @param('assign', Types.CommaSeperatedArray, true) async postUpdate( - domainId: string, tid: ObjectID, beginAtDate: string, beginAtTime: string, + domainId: string, tid: ObjectId, beginAtDate: string, beginAtTime: string, penaltySinceDate: string, penaltySinceTime: string, extensionDays: number, penaltyRules: PenaltyRules, title: string, content: string, _pids: string, rated = false, maintainer: number[] = [], assign: string[] = [], @@ -203,8 +203,8 @@ class HomeworkEditHandler extends Handler { this.response.redirect = this.url('homework_detail', { tid }); } - @param('tid', Types.ObjectID) - async postDelete(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId) + async postDelete(domainId: string, tid: ObjectId) { const tdoc = await contest.get(domainId, tid); if (!this.user.own(tdoc)) this.checkPerm(PERM.PERM_EDIT_HOMEWORK); await contest.del(domainId, tid); diff --git a/packages/hydrooj/src/handler/judge.ts b/packages/hydrooj/src/handler/judge.ts index ed3b9d205..5f2c12d86 100644 --- a/packages/hydrooj/src/handler/judge.ts +++ b/packages/hydrooj/src/handler/judge.ts @@ -1,7 +1,7 @@ import assert from 'assert'; import yaml from 'js-yaml'; import { omit } from 'lodash'; -import { ObjectID } from 'mongodb'; +import { ObjectId } from 'mongodb'; import { JudgeResultBody, ProblemConfigFile, RecordDoc, TestCase, } from '../interface'; @@ -63,7 +63,7 @@ function processPayload(rdoc: RecordDoc, body: Partial) { } export async function next(body: Partial) { - body.rid = new ObjectID(body.rid); + body.rid = new ObjectId(body.rid); let rdoc = await record.get(body.rid); if (!rdoc) return; const { $set, $push } = processPayload(rdoc, body); @@ -113,7 +113,7 @@ export async function postJudge(rdoc: RecordDoc) { const rdocs = await record.getMulti(rdoc.domainId, { pid: rdoc.pid, status: STATUS.STATUS_ACCEPTED, - contest: { $ne: new ObjectID('0'.repeat(24)) }, + contest: { $ne: new ObjectId('0'.repeat(24)) }, }).project({ _id: 1, contest: 1 }).toArray(); const priority = await record.submissionPriority(rdoc.uid, -5000 - rdocs.length * 5 - 50); await record.judge(rdoc.domainId, rdocs.map((r) => r._id), priority, {}, { hackRejudge: input }); @@ -128,7 +128,7 @@ export async function postJudge(rdoc: RecordDoc) { } export async function end(body: Partial) { - body.rid &&= new ObjectID(body.rid); + body.rid &&= new ObjectId(body.rid); let rdoc = await record.get(body.rid); if (!rdoc) return; const { $set, $push } = processPayload(rdoc, body); diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index a794de15e..5d9b89210 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -3,7 +3,7 @@ import { readFile, statSync } from 'fs-extra'; import { escapeRegExp, flattenDeep, intersection, pick, uniqBy, } from 'lodash'; -import { FilterQuery, ObjectID } from 'mongodb'; +import { Filter, ObjectId } from 'mongodb'; import { nanoid } from 'nanoid'; import { sortFiles, streamToBuffer } from '@hydrooj/utils/lib/utils'; import { @@ -101,7 +101,7 @@ registerResolver( ); function buildQuery(udoc: User) { - const q: FilterQuery = {}; + const q: Filter = {}; if (!udoc.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) { q.$or = [ { hidden: false }, @@ -309,8 +309,8 @@ export class ProblemDetailHandler extends ContestDetailBaseHandler { psdoc: ProblemStatusDoc; @route('pid', Types.ProblemId, true) - @query('tid', Types.ObjectID, true) - async _prepare(domainId: string, pid: number | string, tid?: ObjectID) { + @query('tid', Types.ObjectId, true) + async _prepare(domainId: string, pid: number | string, tid?: ObjectId) { this.pdoc = await problem.get(domainId, pid); if (!this.pdoc) throw new ProblemNotFoundError(domainId, pid); if (tid) { @@ -375,7 +375,7 @@ export class ProblemDetailHandler extends ContestDetailBaseHandler { this.UiContext.extraTitleContent = this.pdoc.title; } - @query('tid', Types.ObjectID, true) + @query('tid', Types.ObjectId, true) @query('pjax', Types.Boolean) async get(...args: any[]) { // Navigate to current additional file download @@ -433,7 +433,7 @@ export class ProblemDetailHandler extends ContestDetailBaseHandler { this.checkPerm(PERM.PERM_REJUDGE_PROBLEM); const rdocs = await record.getMulti(domainId, { pid, - contest: { $ne: new ObjectID('0'.repeat(24)) }, + contest: { $ne: new ObjectId('0'.repeat(24)) }, status: { $ne: STATUS.STATUS_CANCELED }, 'files.hack': { $exists: false }, }).project({ _id: 1, contest: 1 }).toArray(); @@ -469,8 +469,8 @@ export class ProblemDetailHandler extends ContestDetailBaseHandler { } export class ProblemSubmitHandler extends ProblemDetailHandler { - @param('tid', Types.ObjectID, true) - async prepare(domainId: string, tid?: ObjectID) { + @param('tid', Types.ObjectId, true) + async prepare(domainId: string, tid?: ObjectId) { if (tid && !contest.isOngoing(this.tdoc, this.tsdoc)) throw new ContestNotLiveError(this.tdoc.docId); if (typeof this.pdoc.config === 'string') throw new ProblemConfigError(); } @@ -497,8 +497,8 @@ export class ProblemSubmitHandler extends ProblemDetailHandler { @param('code', Types.Content, true) @param('pretest', Types.Boolean) @param('input', Types.String, true) - @param('tid', Types.ObjectID, true) - async post(domainId: string, lang: string, code: string, pretest = false, input = '', tid?: ObjectID) { + @param('tid', Types.ObjectId, true) + async post(domainId: string, lang: string, code: string, pretest = false, input = '', tid?: ObjectId) { const config = this.pdoc.config; if (typeof config === 'string' || config === null) throw new ProblemConfigError(); if (['submit_answer', 'objective'].includes(config.type)) { @@ -558,9 +558,9 @@ export class ProblemSubmitHandler extends ProblemDetailHandler { export class ProblemHackHandler extends ProblemDetailHandler { rdoc: RecordDoc; - @param('rid', Types.ObjectID) - @param('tid', Types.ObjectID, true) - async prepare(domainId: string, rid: ObjectID, tid?: ObjectID) { + @param('rid', Types.ObjectId) + @param('tid', Types.ObjectId, true) + async prepare(domainId: string, rid: ObjectId, tid?: ObjectId) { if (typeof this.pdoc.config !== 'object' || !this.pdoc.config.hackable) throw new HackFailedError('This problem is not hackable.'); this.rdoc = await record.get(domainId, rid); if (!this.rdoc || this.rdoc.pid !== this.pdoc.docId @@ -586,8 +586,8 @@ export class ProblemHackHandler extends ProblemDetailHandler { } @param('input', Types.String, true) - @param('tid', Types.ObjectID, true) - async post(domainId: string, input = '', tid?: ObjectID) { + @param('tid', Types.ObjectId, true) + async post(domainId: string, input = '', tid?: ObjectId) { await this.limitRate('add_record', 60, system.get('limit.submission_user'), true); await this.limitRate('add_record', 60, system.get('limit.submission'), false); const id = `${this.user._id}/${nanoid()}`; @@ -814,9 +814,9 @@ export class ProblemFileDownloadHandler extends ProblemDetailHandler { export class ProblemSolutionHandler extends ProblemDetailHandler { @param('page', Types.PositiveInt, true) - @param('tid', Types.ObjectID, true) - @param('sid', Types.ObjectID, true) - async get(domainId: string, page = 1, tid?: ObjectID, sid?: ObjectID) { + @param('tid', Types.ObjectId, true) + @param('sid', Types.ObjectId, true) + async get(domainId: string, page = 1, tid?: ObjectId, sid?: ObjectId) { if (tid) throw new PermissionError(PERM.PERM_VIEW_PROBLEM_SOLUTION); this.response.template = 'problem_solution.html'; const accepted = this.tsdoc?.status === STATUS.STATUS_ACCEPTED; @@ -857,8 +857,8 @@ export class ProblemSolutionHandler extends ProblemDetailHandler { } @param('content', Types.Content) - @param('psid', Types.ObjectID) - async postEditSolution(domainId: string, content: string, psid: ObjectID) { + @param('psid', Types.ObjectId) + async postEditSolution(domainId: string, content: string, psid: ObjectId) { let psdoc = await solution.get(domainId, psid); if (!this.user.own(psdoc)) this.checkPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION); else this.checkPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION_SELF); @@ -866,8 +866,8 @@ export class ProblemSolutionHandler extends ProblemDetailHandler { this.back({ psdoc }); } - @param('psid', Types.ObjectID) - async postDeleteSolution(domainId: string, psid: ObjectID) { + @param('psid', Types.ObjectId) + async postDeleteSolution(domainId: string, psid: ObjectId) { const psdoc = await solution.get(domainId, psid); if (!this.user.own(psdoc)) this.checkPerm(PERM.PERM_DELETE_PROBLEM_SOLUTION); else this.checkPerm(PERM.PERM_DELETE_PROBLEM_SOLUTION_SELF); @@ -875,19 +875,19 @@ export class ProblemSolutionHandler extends ProblemDetailHandler { this.back(); } - @param('psid', Types.ObjectID) + @param('psid', Types.ObjectId) @param('content', Types.Content) - async postReply(domainId: string, psid: ObjectID, content: string) { + async postReply(domainId: string, psid: ObjectId, content: string) { this.checkPerm(PERM.PERM_REPLY_PROBLEM_SOLUTION); const psdoc = await solution.get(domainId, psid); await solution.reply(domainId, psdoc.docId, this.user._id, content); this.back(); } - @param('psid', Types.ObjectID) - @param('psrid', Types.ObjectID) + @param('psid', Types.ObjectId) + @param('psrid', Types.ObjectId) @param('content', Types.Content) - async postEditReply(domainId: string, psid: ObjectID, psrid: ObjectID, content: string) { + async postEditReply(domainId: string, psid: ObjectId, psrid: ObjectId, content: string) { const [psdoc, psrdoc] = await solution.getReply(domainId, psid, psrid); if ((!psdoc) || psdoc.parentId !== this.pdoc.docId) throw new SolutionNotFoundError(domainId, psid); if (!(this.user.own(psrdoc) @@ -898,9 +898,9 @@ export class ProblemSolutionHandler extends ProblemDetailHandler { this.back(); } - @param('psid', Types.ObjectID) - @param('psrid', Types.ObjectID) - async postDeleteReply(domainId: string, psid: ObjectID, psrid: ObjectID) { + @param('psid', Types.ObjectId) + @param('psrid', Types.ObjectId) + async postDeleteReply(domainId: string, psid: ObjectId, psrid: ObjectId) { const [psdoc, psrdoc] = await solution.getReply(domainId, psid, psrid); if ((!psdoc) || psdoc.parentId !== this.pdoc.docId) throw new SolutionNotFoundError(psid); if (!(this.user.own(psrdoc) @@ -911,24 +911,24 @@ export class ProblemSolutionHandler extends ProblemDetailHandler { this.back(); } - @param('psid', Types.ObjectID) - async postUpvote(domainId: string, psid: ObjectID) { + @param('psid', Types.ObjectId) + async postUpvote(domainId: string, psid: ObjectId) { const [psdoc, pssdoc] = await solution.vote(domainId, psid, this.user._id, 1); this.back({ vote: psdoc.vote, user_vote: pssdoc.vote }); } - @param('psid', Types.ObjectID) - async postDownvote(domainId: string, psid: ObjectID) { + @param('psid', Types.ObjectId) + async postDownvote(domainId: string, psid: ObjectId) { const [psdoc, pssdoc] = await solution.vote(domainId, psid, this.user._id, -1); this.back({ vote: psdoc.vote, user_vote: pssdoc.vote }); } } export class ProblemSolutionRawHandler extends ProblemDetailHandler { - @param('psid', Types.ObjectID) - @route('psrid', Types.ObjectID, true) - @param('tid', Types.ObjectID, true) - async get(domainId: string, psid: ObjectID, psrid?: ObjectID, tid?: ObjectID) { + @param('psid', Types.ObjectId) + @route('psrid', Types.ObjectId, true) + @param('tid', Types.ObjectId, true) + async get(domainId: string, psid: ObjectId, psrid?: ObjectId, tid?: ObjectId) { if (tid) throw new PermissionError(PERM.PERM_VIEW_PROBLEM_SOLUTION); const accepted = this.tsdoc?.status === STATUS.STATUS_ACCEPTED; if (!accepted || !this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION_ACCEPT)) { @@ -1001,7 +1001,7 @@ export class ProblemPrefixListHandler extends Handler { const search = global.Hydro.lib.problemSearch || defaultSearch; const result = await search(domainId, prefix, { limit: 20 - pdocs.length }); const docs = await problem.getMulti(domainId, { docId: { $in: result.hits.map((i) => +i.split('/')[1]) } }) - .project(buildProjection(projection)).toArray(); + .project(buildProjection(projection)).toArray(); pdocs.push(...docs); } this.response.body = uniqBy(pdocs, 'docId'); diff --git a/packages/hydrooj/src/handler/record.ts b/packages/hydrooj/src/handler/record.ts index 57b9d4e63..eeae84d8a 100644 --- a/packages/hydrooj/src/handler/record.ts +++ b/packages/hydrooj/src/handler/record.ts @@ -1,7 +1,7 @@ import { omit, pick, throttle, uniqBy, } from 'lodash'; -import { FilterQuery, ObjectID } from 'mongodb'; +import { Filter, ObjectId } from 'mongodb'; import { ContestNotAttendedError, ContestNotFoundError, HackRejudgeFailedError, PermissionError, ProblemConfigError, @@ -29,14 +29,14 @@ class RecordListHandler extends ContestDetailBaseHandler { @param('page', Types.PositiveInt, true) @param('pid', Types.ProblemId, true) - @param('tid', Types.ObjectID, true) + @param('tid', Types.ObjectId, true) @param('uidOrName', Types.UidOrName, true) @param('lang', Types.String, true) @param('status', Types.Int, true) @param('fullStatus', Types.Boolean) @param('allDomain', Types.Boolean) async get( - domainId: string, page = 1, pid?: string | number, tid?: ObjectID, + domainId: string, page = 1, pid?: string | number, tid?: ObjectId, uidOrName?: string, lang?: string, status?: number, full = false, all = false, ) { @@ -44,7 +44,7 @@ class RecordListHandler extends ContestDetailBaseHandler { let tdoc = null; let invalid = false; this.response.template = 'record_main.html'; - const q: FilterQuery = { contest: tid }; + const q: Filter = { contest: tid }; if (full) uidOrName = this.user._id.toString(); if (tid) { tdoc = await contest.get(domainId, tid); @@ -120,8 +120,8 @@ class RecordDetailHandler extends ContestDetailBaseHandler { rdoc: RecordDoc; tdoc?: Tdoc<30>; - @param('rid', Types.ObjectID) - async prepare(domainId: string, rid: ObjectID) { + @param('rid', Types.ObjectId) + async prepare(domainId: string, rid: ObjectId) { this.rdoc = await record.get(domainId, rid); if (!this.rdoc) throw new RecordNotFoundError(rid); } @@ -140,10 +140,10 @@ class RecordDetailHandler extends ContestDetailBaseHandler { this.response.disposition = `attachment; filename="${langs[lang]?.code_file || `foo.${this.rdoc.lang}`}"`; } - @param('rid', Types.ObjectID) + @param('rid', Types.ObjectId) @param('download', Types.Boolean) // eslint-disable-next-line consistent-return - async get(domainId: string, rid: ObjectID, download = false) { + async get(domainId: string, rid: ObjectId, download = false) { const rdoc = this.rdoc; let canViewDetail = true; if (rdoc.contest?.toString() === '000000000000000000000000') { @@ -187,14 +187,14 @@ class RecordDetailHandler extends ContestDetailBaseHandler { }; } - @param('rid', Types.ObjectID) + @param('rid', Types.ObjectId) async post() { this.checkPerm(PERM.PERM_REJUDGE); if (this.rdoc.files?.hack) throw new HackRejudgeFailedError(); } - @param('rid', Types.ObjectID) - async postRejudge(domainId: string, rid: ObjectID) { + @param('rid', Types.ObjectId) + async postRejudge(domainId: string, rid: ObjectId) { const pdoc = await problem.get(domainId, this.rdoc.pid); if (!pdoc?.config || typeof pdoc.config === 'string') throw new ProblemConfigError(); const priority = await record.submissionPriority(this.user._id, -20); @@ -205,8 +205,8 @@ class RecordDetailHandler extends ContestDetailBaseHandler { this.back(); } - @param('rid', Types.ObjectID) - async postCancel(domainId: string, rid: ObjectID) { + @param('rid', Types.ObjectId) + async postCancel(domainId: string, rid: ObjectId) { const $set = { status: STATUS.STATUS_CANCELED, score: 0, @@ -237,14 +237,14 @@ class RecordMainConnectionHandler extends ConnectionHandler { pretest = false; tdoc: Tdoc<30>; - @param('tid', Types.ObjectID, true) + @param('tid', Types.ObjectId, true) @param('pid', Types.ProblemId, true) @param('uidOrName', Types.UidOrName, true) @param('status', Types.Int, true) @param('pretest', Types.Boolean) @param('allDomain', Types.Boolean) async prepare( - domainId: string, tid?: ObjectID, pid?: string | number, uidOrName?: string, + domainId: string, tid?: ObjectId, pid?: string | number, uidOrName?: string, status?: number, pretest = false, all = false, ) { if (tid) { @@ -279,8 +279,9 @@ class RecordMainConnectionHandler extends ConnectionHandler { async message(msg: { rids: string[] }) { if (!(msg.rids instanceof Array)) return; - const rids = msg.rids.map((id) => new ObjectID(id)); - const rdocs = await record.getMulti(this.args.domainId, { _id: { $in: rids } }).project(buildProjection(record.PROJECTION_LIST)).toArray(); + const rids = msg.rids.map((id) => new ObjectId(id)); + const rdocs = await record.getMulti(this.args.domainId, { _id: { $in: rids } }) + .project(buildProjection(record.PROJECTION_LIST)).toArray(); for (const rdoc of rdocs) this.onRecordChange(rdoc); } @@ -303,7 +304,7 @@ class RecordMainConnectionHandler extends ConnectionHandler { user.getById(this.args.domainId, rdoc.uid), problem.get(rdoc.domainId, rdoc.pid), ]); - const tdoc = this.tid ? this.tdoc || await contest.get(rdoc.domainId, new ObjectID(this.tid)) : null; + const tdoc = this.tid ? this.tdoc || await contest.get(rdoc.domainId, new ObjectId(this.tid)) : null; if (pdoc && !rdoc.contest) { if (!problem.canViewBy(pdoc, this.user)) pdoc = null; if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM)) pdoc = null; @@ -325,8 +326,8 @@ class RecordDetailConnectionHandler extends ConnectionHandler { disconnectTimeout: NodeJS.Timeout; throttleSend: any; - @param('rid', Types.ObjectID) - async prepare(domainId: string, rid: ObjectID) { + @param('rid', Types.ObjectId) + async prepare(domainId: string, rid: ObjectId) { const rdoc = await record.get(domainId, rid); if (!rdoc) return; if (rdoc.contest && rdoc.input === undefined) { diff --git a/packages/hydrooj/src/handler/status.ts b/packages/hydrooj/src/handler/status.ts index ab729f1e1..0b3de992f 100644 --- a/packages/hydrooj/src/handler/status.ts +++ b/packages/hydrooj/src/handler/status.ts @@ -39,11 +39,11 @@ class AdminStatusHandler extends Handler { const record = await RecordModel.stat(); const status = await getStatus(); const [domainCount, userCount, problemCount, discussionCount, recordCount] = await Promise.all([ - DomainModel.getMulti().count(), - UserModel.getMulti().count(), - DocumentModel.coll.find({ docType: DocumentModel.TYPE_PROBLEM }).count(), - DocumentModel.coll.find({ docType: DocumentModel.TYPE_DISCUSSION }).count(), - RecordModel.coll.find().count(), + DomainModel.coll.countDocuments(), + UserModel.coll.countDocuments(), + DocumentModel.coll.countDocuments({ docType: DocumentModel.TYPE_PROBLEM }), + DocumentModel.coll.countDocuments({ docType: DocumentModel.TYPE_DISCUSSION }), + RecordModel.coll.countDocuments(), ]); this.response.body = { record, diff --git a/packages/hydrooj/src/handler/training.ts b/packages/hydrooj/src/handler/training.ts index dba8fa9ff..b565581e7 100644 --- a/packages/hydrooj/src/handler/training.ts +++ b/packages/hydrooj/src/handler/training.ts @@ -1,7 +1,7 @@ import assert from 'assert'; import { statSync } from 'fs-extra'; import { pick } from 'lodash'; -import { FilterQuery, ObjectID } from 'mongodb'; +import { Filter, ObjectId } from 'mongodb'; import { sortFiles } from '@hydrooj/utils/lib/utils'; import { FileLimitExceededError, FileUploadError, ProblemNotFoundError, ValidationError, @@ -62,19 +62,19 @@ async function _parseDagJson(domainId: string, _dag: string): Promise = {}; + const query: Filter = {}; await this.ctx.parallel('training/list', query, this); const [tdocs, tpcount] = await paginate( training.getMulti(domainId), page, system.get('pagination.training'), ); - const tids: Set = new Set(); + const tids: Set = new Set(); for (const tdoc of tdocs) tids.add(tdoc.docId); const tsdict = {}; let tdict = {}; if (this.user.hasPriv(PRIV.PRIV_USER_PROFILE)) { - const enrolledTids: Set = new Set(); + const enrolledTids: Set = new Set(); const tsdocs = await training.getMultiStatus(domainId, { uid: this.user._id, $or: [{ docId: { $in: Array.from(tids) } }, { enroll: 1 }], @@ -97,9 +97,9 @@ class TrainingMainHandler extends Handler { } class TrainingDetailHandler extends Handler { - @param('tid', Types.ObjectID) + @param('tid', Types.ObjectId) @param('uid', Types.PositiveInt, true) - async get(domainId: string, tid: ObjectID, uid = this.user._id) { + async get(domainId: string, tid: ObjectId, uid = this.user._id) { const tdoc = await training.get(domainId, tid); await this.ctx.parallel('training/get', tdoc, this); let enrollUsers: number[] = []; @@ -160,16 +160,16 @@ class TrainingDetailHandler extends Handler { this.response.template = 'training_detail.html'; } - @param('tid', Types.ObjectID) - async postEnroll(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId) + async postEnroll(domainId: string, tid: ObjectId) { this.checkPriv(PRIV.PRIV_USER_PROFILE); const tdoc = await training.get(domainId, tid); await training.enroll(domainId, tdoc.docId, this.user._id); this.back(); } - @param('tid', Types.ObjectID) - async postDelete(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId) + async postDelete(domainId: string, tid: ObjectId) { const tdoc = await training.get(domainId, tid); if (!this.user.own(tdoc)) this.checkPerm(PERM.PERM_EDIT_TRAINING); await training.del(domainId, tid); @@ -180,8 +180,8 @@ class TrainingDetailHandler extends Handler { class TrainingEditHandler extends Handler { tdoc: TrainingDoc; - @param('tid', Types.ObjectID, true) - async prepare(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId, true) + async prepare(domainId: string, tid: ObjectId) { if (tid) { this.tdoc = await training.get(domainId, tid); if (!this.user.own(this.tdoc)) this.checkPerm(PERM.PERM_EDIT_TRAINING); @@ -198,14 +198,14 @@ class TrainingEditHandler extends Handler { } } - @param('tid', Types.ObjectID, true) + @param('tid', Types.ObjectId, true) @param('title', Types.Title) @param('content', Types.Content) @param('dag', Types.Content) @param('pin', Types.Boolean) @param('description', Types.Content) async post( - domainId: string, tid: ObjectID, + domainId: string, tid: ObjectId, title: string, content: string, _dag: string, pin = false, description: string, ) { @@ -229,15 +229,15 @@ class TrainingEditHandler extends Handler { export class TrainingFilesHandler extends Handler { tdoc: TrainingDoc; - @param('tid', Types.ObjectID) - async prepare(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId) + async prepare(domainId: string, tid: ObjectId) { this.tdoc = await training.get(domainId, tid); if (!this.user.own(this.tdoc)) this.checkPerm(PERM.PERM_EDIT_TRAINING); else this.checkPerm(PERM.PERM_EDIT_TRAINING_SELF); } - @param('tid', Types.ObjectID) - async get(domainId: string, tid: ObjectID) { + @param('tid', Types.ObjectId) + async get(domainId: string, tid: ObjectId) { if (!this.user.own(this.tdoc)) this.checkPerm(PERM.PERM_EDIT_TRAINING); this.response.body = { tdoc: this.tdoc, @@ -250,9 +250,9 @@ export class TrainingFilesHandler extends Handler { this.response.template = 'training_files.html'; } - @param('tid', Types.ObjectID) + @param('tid', Types.ObjectId) @post('filename', Types.Filename, true) - async postUploadFile(domainId: string, tid: ObjectID, filename: string) { + async postUploadFile(domainId: string, tid: ObjectId, filename: string) { if ((this.tdoc.files?.length || 0) >= system.get('limit.contest_files')) { throw new FileLimitExceededError('count'); } @@ -271,9 +271,9 @@ export class TrainingFilesHandler extends Handler { this.back(); } - @param('tid', Types.ObjectID) + @param('tid', Types.ObjectId) @post('files', Types.ArrayOf(Types.Filename)) - async postDeleteFiles(domainId: string, tid: ObjectID, files: string[]) { + async postDeleteFiles(domainId: string, tid: ObjectId, files: string[]) { await Promise.all([ storage.del(files.map((t) => `contest/${domainId}/${tid}/${t}`), this.user._id), training.edit(domainId, tid, { files: this.tdoc.files.filter((i) => !files.includes(i.name)) }), @@ -282,10 +282,10 @@ export class TrainingFilesHandler extends Handler { } } export class TrainingFileDownloadHandler extends Handler { - @param('tid', Types.ObjectID) + @param('tid', Types.ObjectId) @param('filename', Types.Filename) @param('noDisposition', Types.Boolean) - async get(domainId: string, tid: ObjectID, filename: string, noDisposition = false) { + async get(domainId: string, tid: ObjectId, filename: string, noDisposition = false) { this.response.addHeader('Cache-Control', 'public'); const target = `training/${domainId}/${tid}/${filename}`; const file = await storage.getMeta(target); diff --git a/packages/hydrooj/src/handler/user.ts b/packages/hydrooj/src/handler/user.ts index 7f6d7bd18..8628606db 100644 --- a/packages/hydrooj/src/handler/user.ts +++ b/packages/hydrooj/src/handler/user.ts @@ -1,5 +1,6 @@ import { generateAuthenticationOptions, verifyAuthenticationResponse } from '@simplewebauthn/server'; import moment from 'moment-timezone'; +import type { Binary } from 'mongodb'; import { AuthOperationError, BlacklistedError, BuiltinLoginError, ForbiddenError, InvalidTokenError, SystemError, UserAlreadyExistError, UserFacingError, @@ -181,7 +182,8 @@ class UserWebauthnHandler extends Handler { const tdoc = await token.get(challenge, token.TYPE_WEBAUTHN); if (!tdoc) throw new InvalidTokenError(token.TYPE_TEXTS[token.TYPE_WEBAUTHN]); const udoc = await user.getById(domainId, tdoc.uid); - const authenticator = udoc._authenticators?.find((c) => c.credentialID.toString('base64url') === result.id); + const parseId = (id: Binary) => Buffer.from(id.toString('hex'), 'hex').toString('base64url'); + const authenticator = udoc._authenticators?.find((c) => parseId(c.credentialID) === result.id); if (!authenticator) throw new ValidationError('authenticator'); const verification = await verifyAuthenticationResponse({ response: result, diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index f9d7c2903..d1d0a57f4 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -3,7 +3,7 @@ import { AuthenticationExtensionsAuthenticatorOutputs } from '@simplewebauthn/se import { CredentialDeviceType } from '@simplewebauthn/typescript-types'; import type fs from 'fs'; import type { Dictionary, NumericDictionary } from 'lodash'; -import type { Binary, Cursor, ObjectID } from 'mongodb'; +import type { Binary, FindCursor, ObjectId } from 'mongodb'; import type { Context } from './context'; import type { ProblemDoc } from './model/problem'; import type { Handler } from './service/server'; @@ -76,7 +76,7 @@ export interface Authenticator { authenticatorAttachment: 'platform' | 'cross-platform'; } -export interface Udoc extends Dictionary { +export interface Udoc extends Record { _id: number; mail: string; mailLower: string; @@ -109,14 +109,14 @@ export interface VUdoc { } export interface GDoc { - _id: ObjectID; + _id: ObjectId; domainId: string; name: string; uids: number[]; } export interface UserPreferenceDoc { - _id: ObjectID; + _id: ObjectId; filename: string; uid: number; content: string; @@ -238,7 +238,7 @@ export type ContentNode = PlainContentNode | TextContentNode | SampleContentNode export type Content = string | ContentNode[] | Record; export interface Document { - _id: ObjectID; + _id: ObjectId; docId: any; docType: number; domainId: string; @@ -277,7 +277,7 @@ export type { ProblemDoc } from './model/problem'; export type ProblemDict = NumericDictionary; export interface StatusDocBase { - _id: ObjectID, + _id: ObjectId, docId: any, docType: number, domainId: string, @@ -287,7 +287,7 @@ export interface StatusDocBase { export interface ProblemStatusDoc extends StatusDocBase { docId: number; docType: 10; - rid?: ObjectID; + rid?: ObjectId; score?: number; status?: number; nSubmit?: number; @@ -306,7 +306,7 @@ export interface TestCase { } export interface RecordDoc { - _id: ObjectID; + _id: ObjectId; domainId: string; pid: number; uid: number; @@ -328,7 +328,7 @@ export interface RecordDoc { /** pretest & hack */ input?: string; /** 0 if pretest&script */ - contest?: ObjectID; + contest?: ObjectId; files?: Record } @@ -342,7 +342,7 @@ export interface JudgeMeta { export interface JudgeRequest extends Omit { priority: number; type: 'judge'; - rid: ObjectID; + rid: ObjectId; config: ProblemConfigFile; meta: JudgeMeta; data: FileInfo[]; @@ -369,7 +369,7 @@ export interface TrainingNode { } export interface Tdoc extends Document { - docId: ObjectID; + docId: ObjectId; docType: docType & number; beginAt: Date; endAt: Date; @@ -422,7 +422,7 @@ export interface DomainDoc extends Record { // Message export interface MessageDoc { - _id: ObjectID, + _id: ObjectId, from: number, to: number, content: string, @@ -444,9 +444,9 @@ export type { DiscussionDoc } from './model/discussion'; declare module './model/discussion' { interface DiscussionDoc { docType: document['TYPE_DISCUSSION']; - docId: ObjectID; + docId: ObjectId; parentType: number; - parentId: ObjectID | number | string; + parentId: ObjectId | number | string; title: string; content: string; ip: string; @@ -466,9 +466,9 @@ declare module './model/discussion' { export interface DiscussionReplyDoc extends Document { docType: document['TYPE_DISCUSSION_REPLY']; - docId: ObjectID; + docId: ObjectId; parentType: document['TYPE_DISCUSSION']; - parentId: ObjectID; + parentId: ObjectId; ip: string; content: string; reply: DiscussionTailReplyDoc[]; @@ -478,7 +478,7 @@ export interface DiscussionReplyDoc extends Document { } export interface DiscussionTailReplyDoc { - _id: ObjectID; + _id: ObjectId; owner: number; content: string; ip: string; @@ -488,7 +488,7 @@ export interface DiscussionTailReplyDoc { export interface BlogDoc { docType: document['TYPE_BLOG']; - docId: ObjectID; + docId: ObjectId; owner: number; title: string; content: string; @@ -510,7 +510,7 @@ export interface TokenDoc { } export interface OplogDoc extends Record { - _id: ObjectID, + _id: ObjectId, type: string, } @@ -541,9 +541,9 @@ export interface ContestRule { ) => Promise; scoreboard: ( this: ContestRule, isExport: boolean, _: (s: string) => string, - tdoc: Tdoc<30>, pdict: ProblemDict, cursor: Cursor, + tdoc: Tdoc<30>, pdict: ProblemDict, cursor: FindCursor, ) => Promise<[board: ScoreboardRow[], udict: BaseUserDict]>; - ranked: (tdoc: Tdoc<30>, cursor: Cursor) => Promise<[number, ContestStat & T][]>; + ranked: (tdoc: Tdoc<30>, cursor: FindCursor) => Promise<[number, ContestStat & T][]>; } export type ContestRules = Dictionary; @@ -564,7 +564,7 @@ export interface JudgeMessage { export interface JudgeResultBody { key: string; domainId: string; - rid: ObjectID; + rid: ObjectId; judger?: number; progress?: number; addProgress?: number; @@ -582,7 +582,7 @@ export interface JudgeResultBody { } export interface Task { - _id: ObjectID; + _id: ObjectId; type: string; subType?: string; priority: number; @@ -590,7 +590,7 @@ export interface Task { } export interface Schedule { - _id: ObjectID; + _id: ObjectId; type: string; subType?: string; executeAfter: Date; @@ -622,7 +622,7 @@ export interface EventDoc { } export interface OpCountDoc { - _id: ObjectID; + _id: ObjectId; op: string; ident: string; expireAt: Date; @@ -640,7 +640,7 @@ export interface DiscussionHistoryDoc { title?: string; content: string; domainId: string; - docId: ObjectID; + docId: ObjectId; /** Create time */ time: Date; uid: number; @@ -730,7 +730,6 @@ export interface Lib extends Record { difficulty: typeof import('./lib/difficulty'); buildContent: typeof import('./lib/content').buildContent; mail: typeof import('./lib/mail'); - paginate: typeof import('./lib/paginate'); rank: typeof import('./lib/rank'); rating: typeof import('./lib/rating'); testdataConfig: typeof import('./lib/testdataConfig'); diff --git a/packages/hydrooj/src/lib/paginate.ts b/packages/hydrooj/src/lib/paginate.ts index 47177cd4d..bf3da8fd7 100644 --- a/packages/hydrooj/src/lib/paginate.ts +++ b/packages/hydrooj/src/lib/paginate.ts @@ -1,12 +1,19 @@ -import { Cursor } from 'mongodb'; +import { FindCursor } from 'mongodb'; import { ValidationError } from '../error'; +import db from '../service/db'; async function paginate( - cursor: Cursor, page: number, pageSize: number, + cursor: FindCursor, page: number, pageSize: number, ): Promise<[docs: T[], numPages: number, count: number]> { if (page <= 0) throw new ValidationError('page'); + let filter = {}; + for (const key of Object.getOwnPropertySymbols(cursor)) { + if (key.toString() !== 'Symbol(filter)') continue; + filter = cursor[key]; + break; + } const [count, pageDocs] = await Promise.all([ - cursor.count(), + db.collection(cursor.namespace.collection as any).countDocuments(filter), cursor.skip((page - 1) * pageSize).limit(pageSize).toArray(), ]); const numPages = Math.floor((count + pageSize - 1) / pageSize); @@ -14,5 +21,3 @@ async function paginate( } export = paginate; - -global.Hydro.lib.paginate = paginate; diff --git a/packages/hydrooj/src/lib/rank.ts b/packages/hydrooj/src/lib/rank.ts index 08666a2ff..2f6fc17a9 100644 --- a/packages/hydrooj/src/lib/rank.ts +++ b/packages/hydrooj/src/lib/rank.ts @@ -1,6 +1,6 @@ -import { Cursor } from 'mongodb'; +import { FindCursor } from 'mongodb'; -type ranked = >(cursor: Cursor, equ: ((a: T, b: T) => boolean)) => Promise<[number, T][]>; +type ranked = >(cursor: FindCursor, equ: ((a: T, b: T) => boolean)) => Promise<[number, T][]>; const ranked: ranked = async (cursor, equ) => { let last = null; diff --git a/packages/hydrooj/src/lib/validator.ts b/packages/hydrooj/src/lib/validator.ts index 8863e85ae..fa690d021 100644 --- a/packages/hydrooj/src/lib/validator.ts +++ b/packages/hydrooj/src/lib/validator.ts @@ -2,7 +2,7 @@ import assert from 'assert'; import emojiRegex from 'emoji-regex'; import { isSafeInteger } from 'lodash'; import moment from 'moment-timezone'; -import { ObjectID } from 'mongodb'; +import { ObjectId } from 'mongodb'; import saslprep from 'saslprep'; type InputType = string | number | Record | any[]; @@ -35,7 +35,7 @@ export interface Types { Float: Type; // Other - ObjectID: Type; + ObjectId: Type; Boolean: Type; Date: Type; Time: Type; @@ -94,8 +94,8 @@ export const Types: Types = { PositiveInt: [(v) => +v, (v) => /^\+?[1-9][0-9]*$/.test(v.toString().trim()) && isSafeInteger(+v)], Float: [(v) => +v, (v) => Number.isFinite(+v)], - ObjectID: [(v) => new ObjectID(v), ObjectID.isValid], - Boolean: [(v) => !!(v && !['false', 'off'].includes(v)), null, 'convert'], + ObjectId: [(v) => new ObjectId(v), ObjectId.isValid], + Boolean: [(v) => !!(v && !['false', 'off'].includes(v)), null, true], Date: [ (v) => { const d = v.split('-'); @@ -186,3 +186,7 @@ export const Types: Types = { (v) => types.some((type) => type[1](v)), ] as any, }; + +// @ts-ignore +Types.ObjectID = Types.ObjectId; +// backward compatibility diff --git a/packages/hydrooj/src/libs.ts b/packages/hydrooj/src/libs.ts index 1ec83304d..b207256e6 100644 --- a/packages/hydrooj/src/libs.ts +++ b/packages/hydrooj/src/libs.ts @@ -1,15 +1,16 @@ import AdmZip from 'adm-zip'; import _ from 'lodash'; +import { ObjectId as ObjectID } from 'mongodb'; import Schema from 'schemastery'; import superagent from 'superagent'; import { Context } from './context'; -export { ObjectID, ObjectId, FilterQuery } from 'mongodb'; +export { ObjectId, Filter } from 'mongodb'; export { WebSocket, WebSocketServer } from 'ws'; export * from './utils'; export * from './interface'; export * from './typeutils'; export { - Schema, AdmZip, superagent, _, + Schema, AdmZip, superagent, _, ObjectID, }; export const definePlugin = (args: { using?: keyof Context[]; diff --git a/packages/hydrooj/src/model/blog.ts b/packages/hydrooj/src/model/blog.ts index ab3f6855b..477ca797d 100644 --- a/packages/hydrooj/src/model/blog.ts +++ b/packages/hydrooj/src/model/blog.ts @@ -1,5 +1,5 @@ import { omit } from 'lodash'; -import { FilterQuery, ObjectID } from 'mongodb'; +import { Filter, ObjectId } from 'mongodb'; import { BlogDoc } from '../interface'; import { NumberKeys } from '../typeutils'; import * as document from './document'; @@ -7,7 +7,7 @@ import * as document from './document'; export async function add( owner: number, title: string, content: string, ip: string | null = null, -): Promise { +): Promise { const payload: Partial = { content, owner, @@ -25,36 +25,36 @@ export async function add( return payload.docId; } -export async function get(did: ObjectID): Promise { +export async function get(did: ObjectId): Promise { return await document.get('system', document.TYPE_BLOG, did); } -export function edit(did: ObjectID, title: string, content: string): Promise { +export function edit(did: ObjectId, title: string, content: string): Promise { const payload = { title, content }; return document.set('system', document.TYPE_BLOG, did, payload); } -export function inc(did: ObjectID, key: NumberKeys, value: number): Promise { +export function inc(did: ObjectId, key: NumberKeys, value: number): Promise { return document.inc('system', document.TYPE_BLOG, did, key, value); } -export function del(did: ObjectID): Promise { +export function del(did: ObjectId): Promise { return Promise.all([ document.deleteOne('system', document.TYPE_BLOG, did), document.deleteMultiStatus('system', document.TYPE_BLOG, { docId: did }), ]) as any; } -export function count(query: FilterQuery) { +export function count(query: Filter) { return document.count('system', document.TYPE_BLOG, query); } -export function getMulti(query: FilterQuery = {}) { +export function getMulti(query: Filter = {}) { return document.getMulti('system', document.TYPE_BLOG, query) .sort({ _id: -1 }); } -export async function addReply(did: ObjectID, owner: number, content: string, ip: string): Promise { +export async function addReply(did: ObjectId, owner: number, content: string, ip: string): Promise { const [[, drid]] = await Promise.all([ document.push('system', document.TYPE_BLOG, did, 'reply', content, owner, { ip }), document.incAndSet('system', document.TYPE_BLOG, did, 'nReply', 1, { updateAt: new Date() }), @@ -62,15 +62,15 @@ export async function addReply(did: ObjectID, owner: number, content: string, ip return drid; } -export function setStar(did: ObjectID, uid: number, star: boolean) { +export function setStar(did: ObjectId, uid: number, star: boolean) { return document.setStatus('system', document.TYPE_BLOG, did, uid, { star }); } -export function getStatus(did: ObjectID, uid: number) { +export function getStatus(did: ObjectId, uid: number) { return document.getStatus('system', document.TYPE_BLOG, did, uid); } -export function setStatus(did: ObjectID, uid: number, $set) { +export function setStatus(did: ObjectId, uid: number, $set) { return document.setStatus('system', document.TYPE_BLOG, did, uid, $set); } diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index 703380458..a5eb6774f 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -1,5 +1,5 @@ import { sumBy } from 'lodash'; -import { FilterQuery, ObjectID } from 'mongodb'; +import { Filter, ObjectId } from 'mongodb'; import { Counter, formatSeconds, Time } from '@hydrooj/utils/lib/utils'; import { ContestAlreadyAttendedError, ContestNotFoundError, @@ -20,7 +20,7 @@ import RecordModel from './record'; import user, { User } from './user'; interface AcmJournal { - rid: ObjectID; + rid: ObjectId; pid: number; score: number; status: number; @@ -167,7 +167,7 @@ const acm = buildContestRule({ value, hover: accept ? formatSeconds(doc.time) : '', raw: doc.rid, - style: accept && doc.rid.generationTime === meta?.first?.[pid] + style: accept && doc.rid.getTimestamp().getTime() === meta?.first?.[pid] ? 'background-color: rgb(217, 240, 199);' : undefined, }); @@ -195,7 +195,7 @@ const acm = buildContestRule({ { $match: { 'r.v.status': STATUS.STATUS_ACCEPTED } }, { $group: { _id: '$r.v.pid', first: { $min: '$r.v.rid' } } }, ]).toArray() as any[]; - for (const t of data) first[t._id] = t.first.generationTime; + for (const t of data) first[t._id] = t.first.getTimestamp().getTime(); const columns = await this.scoreboardHeader(isExport, _, tdoc, pdict); const rows: ScoreboardRow[] = [ @@ -299,7 +299,7 @@ const oi = buildContestRule({ value: tsddict[pid]?.score ?? '-', raw: tsddict[pid]?.rid || null, }; - if (tsddict[pid]?.status === STATUS.STATUS_ACCEPTED && tsddict[pid]?.rid.generationTime === meta?.first?.[pid]) { + if (tsddict[pid]?.status === STATUS.STATUS_ACCEPTED && tsddict[pid]?.rid.getTimestamp().getTime() === meta?.first?.[pid]) { node.style = 'background-color: rgb(217, 240, 199);'; } row.push(node); @@ -319,7 +319,7 @@ const oi = buildContestRule({ docId: tdoc.docId, [`detail.${pid}.status`]: STATUS.STATUS_ACCEPTED, }).sort({ [`detail.${pid}.rid`]: 1 }).limit(1).toArray(); - first[pid] = data ? data.detail[pid].rid.generationTime : new ObjectID().generationTime; + first[pid] = data ? data.detail[pid].rid.getTimestamp().getTime() : Date.now() / 1000; })); // eslint-disable-next-line @typescript-eslint/no-use-before-define if (isDone(tdoc)) { @@ -580,7 +580,7 @@ export const RULES: ContestRules = { }; function _getStatusJournal(tsdoc) { - return tsdoc.journal.sort((a, b) => (a.rid.generationTime - b.rid.generationTime)); + return tsdoc.journal.sort((a, b) => (a.rid.getTimestamp() - b.rid.getTimestamp())); } export async function add( @@ -602,7 +602,7 @@ export async function add( return res; } -export async function edit(domainId: string, tid: ObjectID, $set: Partial) { +export async function edit(domainId: string, tid: ObjectId, $set: Partial) { if ($set.rule && !RULES[$set.rule]) throw new ValidationError('rule'); const tdoc = await document.get(domainId, document.TYPE_CONTEST, tid); if (!tdoc) throw new ContestNotFoundError(domainId, tid); @@ -610,7 +610,7 @@ export async function edit(domainId: string, tid: ObjectID, $set: Partial) return await document.set(domainId, document.TYPE_CONTEST, tid, $set); } -export async function del(domainId: string, tid: ObjectID) { +export async function del(domainId: string, tid: ObjectId) { await Promise.all([ document.deleteOne(domainId, document.TYPE_CONTEST, tid), document.deleteMultiStatus(domainId, document.TYPE_CONTEST, { docId: tid }), @@ -618,7 +618,7 @@ export async function del(domainId: string, tid: ObjectID) { ]); } -export async function get(domainId: string, tid: ObjectID): Promise> { +export async function get(domainId: string, tid: ObjectId): Promise> { const tdoc = await document.get(domainId, document.TYPE_CONTEST, tid); if (!tdoc) throw new ContestNotFoundError(tid); return tdoc; @@ -629,11 +629,11 @@ export async function getRelated(domainId: string, pid: number, rule?: string) { return await document.getMulti(domainId, document.TYPE_CONTEST, { pids: pid, rule: rule || { $in: rules } }).toArray(); } -export async function getStatus(domainId: string, tid: ObjectID, uid: number) { +export async function getStatus(domainId: string, tid: ObjectId, uid: number) { return await document.getStatus(domainId, document.TYPE_CONTEST, tid, uid); } -async function _updateStatus(tdoc: Tdoc<30>, uid: number, rid: ObjectID, pid: number, status: STATUS, score: number) { +async function _updateStatus(tdoc: Tdoc<30>, uid: number, rid: ObjectId, pid: number, status: STATUS, score: number) { // eslint-disable-next-line @typescript-eslint/no-use-before-define if (isLocked(tdoc)) { status = STATUS.STATUS_WAITING; @@ -648,21 +648,21 @@ async function _updateStatus(tdoc: Tdoc<30>, uid: number, rid: ObjectID, pid: nu } export async function updateStatus( - domainId: string, tid: ObjectID, uid: number, rid: ObjectID, pid: number, + domainId: string, tid: ObjectId, uid: number, rid: ObjectId, pid: number, status = STATUS.STATUS_WRONG_ANSWER, score = 0, ) { const tdoc = await get(domainId, tid); return await _updateStatus(tdoc, uid, rid, pid, status, score); } -export async function getListStatus(domainId: string, uid: number, tids: ObjectID[]) { +export async function getListStatus(domainId: string, uid: number, tids: ObjectId[]) { const r = {}; // eslint-disable-next-line no-await-in-loop for (const tid of tids) r[tid.toHexString()] = await getStatus(domainId, tid, uid); return r; } -export async function attend(domainId: string, tid: ObjectID, uid: number, payload: any = {}) { +export async function attend(domainId: string, tid: ObjectId, uid: number, payload: any = {}) { try { await document.cappedIncStatus(domainId, document.TYPE_CONTEST, tid, uid, 'attend', 1, 0, 1, payload); } catch (e) { @@ -715,7 +715,7 @@ export function isExtended(tdoc: Tdoc) { return tdoc.penaltySince.getTime() <= now && now < tdoc.endAt.getTime(); } -export function setStatus(domainId: string, tid: ObjectID, uid: number, $set: any) { +export function setStatus(domainId: string, tid: ObjectId, uid: number, $set: any) { return document.setStatus(domainId, document.TYPE_CONTEST, tid, uid, $set); } @@ -724,12 +724,12 @@ export function count(domainId: string, query: any) { } export function getMulti( - domainId: string, query: FilterQuery = {}, + domainId: string, query: Filter = {}, ) { return document.getMulti(domainId, document.TYPE_CONTEST, query).sort({ beginAt: -1 }); } -export async function getAndListStatus(domainId: string, tid: ObjectID): Promise<[Tdoc, any[]]> { +export async function getAndListStatus(domainId: string, tid: ObjectId): Promise<[Tdoc, any[]]> { // TODO(iceboy): projection, pagination. const tdoc = await get(domainId, tid); const tsdocs = await document.getMultiStatus(domainId, document.TYPE_CONTEST, { docId: tid }) @@ -737,7 +737,7 @@ export async function getAndListStatus(domainId: string, tid: ObjectID): Promise return [tdoc, tsdocs]; } -export async function recalcStatus(domainId: string, tid: ObjectID) { +export async function recalcStatus(domainId: string, tid: ObjectId) { const [tdoc, tsdocs] = await Promise.all([ document.get(domainId, document.TYPE_CONTEST, tid), document.getMultiStatus(domainId, document.TYPE_CONTEST, { docId: tid }).toArray(), @@ -758,7 +758,7 @@ export async function recalcStatus(domainId: string, tid: ObjectID) { return await Promise.all(tasks); } -export async function unlockScoreboard(domainId: string, tid: ObjectID) { +export async function unlockScoreboard(domainId: string, tid: ObjectId) { const tdoc = await document.get(domainId, document.TYPE_CONTEST, tid); if (!tdoc.lockAt || tdoc.unlocked) return; const rdocs = await RecordModel.getMulti(domainId, { contest: tid, _id: { $gte: Time.getObjectID(tdoc.lockAt) } }) @@ -802,7 +802,7 @@ export function canShowScoreboard(this: { user: User }, tdoc: Tdoc<30>, allowPer } export async function getScoreboard( - this: Handler, domainId: string, tid: ObjectID, isExport = false, + this: Handler, domainId: string, tid: ObjectId, isExport = false, ): Promise<[Tdoc<30>, ScoreboardRow[], BaseUserDict, ProblemDict]> { const tdoc = await get(domainId, tid); if (!canShowScoreboard.call(this, tdoc)) throw new ContestScoreboardHiddenError(tid); diff --git a/packages/hydrooj/src/model/discussion.ts b/packages/hydrooj/src/model/discussion.ts index ecb3b6fe4..a436ca1d4 100644 --- a/packages/hydrooj/src/model/discussion.ts +++ b/packages/hydrooj/src/model/discussion.ts @@ -1,5 +1,5 @@ import { omit } from 'lodash'; -import { FilterQuery, ObjectID } from 'mongodb'; +import { Filter, ObjectId } from 'mongodb'; import { Context } from '../context'; import { DiscussionNodeNotFoundError, DocumentNotFoundError } from '../error'; import { @@ -41,10 +41,10 @@ export const typeDisplay = { export const coll = db.collection('discussion.history'); export async function add( - domainId: string, parentType: number, parentId: ObjectID | number | string, + domainId: string, parentType: number, parentId: ObjectId | number | string, owner: number, title: string, content: string, ip: string | null = null, highlight: boolean, pin: boolean, -): Promise { +): Promise { const time = new Date(); const payload: Partial = { domainId, @@ -76,12 +76,12 @@ export async function add( } export async function get( - domainId: string, did: ObjectID, projection: T[] = PROJECTION_PUBLIC as any, + domainId: string, did: ObjectId, projection: T[] = PROJECTION_PUBLIC as any, ): Promise> { return await document.get(domainId, document.TYPE_DISCUSSION, did, projection); } -export async function edit(domainId: string, did: ObjectID, $set: Partial) { +export async function edit(domainId: string, did: ObjectId, $set: Partial) { await coll.insertOne({ domainId, docId: did, content: $set.content, uid: $set.editor, ip: $set.ip, time: new Date(), }); @@ -89,12 +89,12 @@ export async function edit(domainId: string, did: ObjectID, $set: Partial, value: number, + domainId: string, did: ObjectId, key: NumberKeys, value: number, ): Promise { return document.inc(domainId, document.TYPE_DISCUSSION, did, key, value); } -export async function del(domainId: string, did: ObjectID): Promise { +export async function del(domainId: string, did: ObjectId): Promise { const [ddoc, drdocs] = await Promise.all([ document.get(domainId, document.TYPE_DISCUSSION, did), document.getMulti(domainId, document.TYPE_DISCUSSION_REPLY, { @@ -111,20 +111,20 @@ export async function del(domainId: string, did: ObjectID): Promise { ]) as any; } -export function count(domainId: string, query: FilterQuery) { +export function count(domainId: string, query: Filter) { return document.count(domainId, document.TYPE_DISCUSSION, query); } -export function getMulti(domainId: string, query: FilterQuery = {}, projection = PROJECTION_LIST) { +export function getMulti(domainId: string, query: Filter = {}, projection = PROJECTION_LIST) { return document.getMulti(domainId, document.TYPE_DISCUSSION, query) .sort({ pin: -1, docId: -1 }) - .project(buildProjection(projection)); + .project(buildProjection(projection)); } export async function addReply( - domainId: string, did: ObjectID, owner: number, + domainId: string, did: ObjectId, owner: number, content: string, ip: string, -): Promise { +): Promise { const time = new Date(); const [drid] = await Promise.all([ document.add( @@ -139,12 +139,12 @@ export async function addReply( return drid; } -export function getReply(domainId: string, drid: ObjectID): Promise { +export function getReply(domainId: string, drid: ObjectId): Promise { return document.get(domainId, document.TYPE_DISCUSSION_REPLY, drid); } export async function editReply( - domainId: string, drid: ObjectID, content: string, uid: number, ip: string, + domainId: string, drid: ObjectId, content: string, uid: number, ip: string, ): Promise { await coll.insertOne({ domainId, docId: drid, content, uid, ip, time: new Date(), @@ -152,7 +152,7 @@ export async function editReply( return document.set(domainId, document.TYPE_DISCUSSION_REPLY, drid, { content, edited: true, editor: uid }); } -export async function delReply(domainId: string, drid: ObjectID) { +export async function delReply(domainId: string, drid: ObjectId) { const drdoc = await getReply(domainId, drid); if (!drdoc) throw new DocumentNotFoundError(domainId, drid); return await Promise.all([ @@ -162,18 +162,18 @@ export async function delReply(domainId: string, drid: ObjectID) { ]); } -export function getMultiReply(domainId: string, did: ObjectID) { +export function getMultiReply(domainId: string, did: ObjectId) { return document.getMulti( domainId, document.TYPE_DISCUSSION_REPLY, { parentType: document.TYPE_DISCUSSION, parentId: did }, ).sort('_id', -1); } -export function getListReply(domainId: string, did: ObjectID): Promise { +export function getListReply(domainId: string, did: ObjectId): Promise { return getMultiReply(domainId, did).toArray(); } -export async function react(domainId: string, docType: keyof document.DocType, did: ObjectID, id: string, uid: number, reverse = false) { +export async function react(domainId: string, docType: keyof document.DocType, did: ObjectId, id: string, uid: number, reverse = false) { let doc; const sdoc = await document.setIfNotStatus(domainId, docType, did, uid, `react.${id}`, reverse ? 0 : 1, reverse ? 0 : 1, {}); if (sdoc) doc = await document.inc(domainId, docType, did, `react.${id}`, reverse ? -1 : 1); @@ -181,15 +181,15 @@ export async function react(domainId: string, docType: keyof document.DocType, d return [doc, sdoc]; } -export async function getReaction(domainId: string, docType: keyof document.DocType, did: ObjectID, uid: number) { +export async function getReaction(domainId: string, docType: keyof document.DocType, did: ObjectId, uid: number) { const doc = await document.getStatus(domainId, docType, did, uid); return doc?.react || {}; } export async function addTailReply( - domainId: string, drid: ObjectID, + domainId: string, drid: ObjectId, owner: number, content: string, ip: string, -): Promise<[DiscussionReplyDoc, ObjectID]> { +): Promise<[DiscussionReplyDoc, ObjectId]> { const time = new Date(); const [drdoc, subId] = await document.push( domainId, document.TYPE_DISCUSSION_REPLY, drid, @@ -208,13 +208,13 @@ export async function addTailReply( } export function getTailReply( - domainId: string, drid: ObjectID, drrid: ObjectID, + domainId: string, drid: ObjectId, drrid: ObjectId, ): Promise<[DiscussionReplyDoc, DiscussionTailReplyDoc] | [null, null]> { return document.getSub(domainId, document.TYPE_DISCUSSION_REPLY, drid, 'reply', drrid); } export async function editTailReply( - domainId: string, drid: ObjectID, drrid: ObjectID, content: string, uid: number, ip: string, + domainId: string, drid: ObjectId, drrid: ObjectId, content: string, uid: number, ip: string, ): Promise { const [, drrdoc] = await Promise.all([ coll.insertOne({ @@ -226,7 +226,7 @@ export async function editTailReply( return drrdoc; } -export async function delTailReply(domainId: string, drid: ObjectID, drrid: ObjectID) { +export async function delTailReply(domainId: string, drid: ObjectId, drrid: ObjectId) { return Promise.all([ document.deleteSub(domainId, document.TYPE_DISCUSSION_REPLY, drid, 'reply', drrid), coll.deleteMany({ domainId, docId: drrid }), @@ -234,7 +234,7 @@ export async function delTailReply(domainId: string, drid: ObjectID, drrid: Obje } export function getHistory( - domainId: string, docId: ObjectID, query: FilterQuery = {}, + domainId: string, docId: ObjectId, query: Filter = {}, projection = HISTORY_PROJECTION_PUBLIC, ) { return coll.find({ domainId, docId, ...query }) @@ -242,15 +242,15 @@ export function getHistory( .toArray(); } -export function setStar(domainId: string, did: ObjectID, uid: number, star: boolean) { +export function setStar(domainId: string, did: ObjectId, uid: number, star: boolean) { return document.setStatus(domainId, document.TYPE_DISCUSSION, did, uid, { star }); } -export function getStatus(domainId: string, did: ObjectID, uid: number) { +export function getStatus(domainId: string, did: ObjectId, uid: number) { return document.getStatus(domainId, document.TYPE_DISCUSSION, did, uid); } -export function setStatus(domainId: string, did: ObjectID, uid: number, $set) { +export function setStatus(domainId: string, did: ObjectId, uid: number, $set) { return document.setStatus(domainId, document.TYPE_DISCUSSION, did, uid, $set); } @@ -278,7 +278,7 @@ export async function getVnode(domainId: string, type: number, id: string, uid?: } if ([document.TYPE_CONTEST, document.TYPE_TRAINING, document.TYPE_HOMEWORK].includes(type as any)) { const model = type === document.TYPE_TRAINING ? training : contest; - const _id = new ObjectID(id); + const _id = new ObjectId(id); const tdoc = await model.get(domainId, _id); if (!tdoc) throw new DiscussionNodeNotFoundError(id); if (uid) { diff --git a/packages/hydrooj/src/model/document.ts b/packages/hydrooj/src/model/document.ts index 6ba10457e..72eac4279 100644 --- a/packages/hydrooj/src/model/document.ts +++ b/packages/hydrooj/src/model/document.ts @@ -1,7 +1,7 @@ /* eslint-disable object-curly-newline */ import assert from 'assert'; import { - Cursor, FilterQuery, ObjectID, OnlyFieldsOfType, PushOperator, UpdateQuery, + Filter, FindCursor, ObjectId, OnlyFieldsOfType, PushOperator, UpdateFilter, } from 'mongodb'; import { Context } from '../context'; import { @@ -14,7 +14,7 @@ import db from '../service/db'; import { ArrayKeys, MaybeArray, NumberKeys, Projection } from '../typeutils'; import { buildProjection } from '../utils'; -type DocID = ObjectID | string | number; +type DocID = ObjectId | string | number; type NormalArrayKeys = Exclude, Symbol>; export const coll = db.collection('document'); @@ -60,14 +60,14 @@ export async function add( docType: T, docId: null, parentType?: DocType[T]['parentType'], parentId?: DocType[T]['parentId'], args?: Partial, -): Promise; +): Promise; export async function add( domainId: string, content: Content, owner: number, docType: number, docId: DocID = null, parentType: number | null = null, parentId: DocID = null, args: any = {}, ) { - const _id = new ObjectID(); + const _id = new ObjectId(); const doc: any = { _id, content, @@ -104,7 +104,7 @@ export async function set( $push?: PushOperator, ): Promise { await bus.parallel('document/set', domainId, docType, docId, $set, $unset); - const update: UpdateQuery = {}; + const update: UpdateFilter = {}; if ($set) update.$set = $set; if ($unset) update.$unset = $unset; if ($push) update.$push = $push; @@ -126,20 +126,20 @@ export function deleteOne( } export function deleteMulti( - domainId: string, docType: K, query?: FilterQuery, + domainId: string, docType: K, query?: Filter, ) { return coll.deleteMany({ ...query, domainId, docType }); } export function deleteMultiStatus( - domainId: string, docType: K, query?: FilterQuery, + domainId: string, docType: K, query?: Filter, ) { return collStatus.deleteMany({ ...query, domainId, docType }); } export function getMulti( - domainId: string, docType: K, query?: FilterQuery, projection?: Projection, -): Cursor { + domainId: string, docType: K, query?: Filter, projection?: Projection, +): FindCursor { let cursor = coll.find({ docType, domainId, ...query }); if (projection) cursor = cursor.project(buildProjection(projection)); return cursor; @@ -170,29 +170,30 @@ export async function incAndSet( } export function count( - domainId: string, docType: K, query?: FilterQuery, + domainId: string, docType: K, query?: Filter, ) { - return coll.find({ ...query, docType, domainId }).count(); + return coll.countDocuments({ ...query, docType, domainId }); } export async function push( domainId: string, docType: K, docId: DocType[K]['docId'], key: ArrayKeys, value: DocType[K][0], -): Promise<[DocType[K], ObjectID]>; +): Promise<[DocType[K], ObjectId]>; export async function push( domainId: string, docType: K, docId: DocType[K]['docId'], key: keyof DocType[K], content: string, owner: number, args?: DocType[K][T][0], -): Promise<[DocType[K], ObjectID]>; +): Promise<[DocType[K], ObjectId]>; export async function push( domainId: string, docType: number, docId: DocID, key: string, arg0: any, arg1?: any, arg2?: any, ) { - const _id = arg2?._id || arg0?._id || new ObjectID(); + const _id = arg2?._id || arg0?._id || new ObjectId(); const v = arg1 ? { _id, ...arg2, content: arg0, owner: arg1 } : { _id, ...arg0 }; const doc = await coll.findOneAndUpdate( { domainId, docType, docId }, + // @ts-ignore { $push: { [key]: v } }, { returnDocument: 'after' }, ); @@ -201,10 +202,11 @@ export async function push( export async function pull>( domainId: string, docType: K, docId: DocType[K]['docId'], - setKey: T, contents: FilterQuery, + setKey: T, contents: Filter, ): Promise { const res = await coll.findOneAndUpdate( { domainId, docType, docId }, + // @ts-ignore { $pull: { [setKey]: { $in: contents } } }, { returnDocument: 'after' }, ); @@ -218,6 +220,7 @@ export async function deleteSub( } export function getMultiStatus( - domainId: string, docType: K, args: FilterQuery, -): Cursor { + domainId: string, docType: K, args: Filter, +): FindCursor { return collStatus.find({ ...args, docType, domainId }); } export function getMultiStatusWithoutDomain( - docType: K, args: FilterQuery, -): Cursor { + docType: K, args: Filter, +): FindCursor { return collStatus.find({ ...args, docType }); } export async function setStatus( - domainId: string, docType: K, docId: DocStatusType[K]['docId'], uid: number, args: UpdateQuery['$set'], + domainId: string, docType: K, docId: DocStatusType[K]['docId'], uid: number, args: UpdateFilter['$set'], ): Promise { const res = await collStatus.findOneAndUpdate( { domainId, docType, docId, uid }, @@ -302,7 +306,7 @@ export async function setStatus( } export async function setMultiStatus( - domainId: string, docType: K, query: FilterQuery, args: Partial, + domainId: string, docType: K, query: Filter, args: Partial, ) { return await collStatus.updateMany( { domainId, docType, ...query }, @@ -335,7 +339,7 @@ export async function setIfNotStatus( domainId: string, docType: T, docId: DocStatusType[T]['docId'], uid: number, - filter: FilterQuery, args: Partial, + filter: Filter, args: Partial, ): Promise { const match = await collStatus.findOne({ domainId, docType, docId, uid, ...filter }); if (match) return null; @@ -354,7 +358,7 @@ export async function cappedIncStatus( ): Promise { assert(value !== 0); const $not = value > 0 ? { $gte: maxValue } : { $lte: minValue }; - const operation = { $inc: { [key]: value } } as UpdateQuery; + const operation = { $inc: { [key]: value } } as UpdateFilter; if (Object.keys(setPayload).length) operation.$set = setPayload; const res = await collStatus.findOneAndUpdate( { domainId, docType, docId, uid, [key]: { $not } }, diff --git a/packages/hydrooj/src/model/domain.ts b/packages/hydrooj/src/model/domain.ts index 5cb4da807..061bde1b1 100644 --- a/packages/hydrooj/src/model/domain.ts +++ b/packages/hydrooj/src/model/domain.ts @@ -1,6 +1,6 @@ import { Dictionary } from 'lodash'; import LRU from 'lru-cache'; -import { FilterQuery } from 'mongodb'; +import { Filter } from 'mongodb'; import { DomainDoc } from '../interface'; import * as bus from '../service/bus'; import db from '../service/db'; @@ -19,6 +19,9 @@ interface DomainUserArg { } class DomainModel { + static coll = coll; + static collUser = collUser; + static JOIN_METHOD_NONE = 0; static JOIN_METHOD_ALL = 1; static JOIN_METHOD_CODE = 2; @@ -64,7 +67,7 @@ class DomainModel { domainId = domainId.toLowerCase(); const key = `id::${domainId}`; if (cache.has(key)) return cache.get(key); - const query: FilterQuery = { lower: domainId }; + const query: Filter = { lower: domainId }; await bus.parallel('domain/before-get', query); const result = await coll.findOne(query); if (result) { @@ -79,7 +82,7 @@ class DomainModel { const key = `host::${host}`; // Note: cache by host might not be updated immediately if (cache.has(key)) return cache.get(key); - const query: FilterQuery = { host }; + const query: Filter = { host }; await bus.parallel('domain/before-get', query); const result = await coll.findOne(query); if (result) { @@ -89,7 +92,7 @@ class DomainModel { return result; } - static getMulti(query: FilterQuery = {}) { + static getMulti(query: Filter = {}) { return coll.find(query); } @@ -124,8 +127,8 @@ class DomainModel { } static async countUser(domainId: string, role?: string) { - if (role) return await collUser.find({ domainId, role }).count(); - return await collUser.find({ domainId }).count(); + if (role) return await collUser.countDocuments({ domainId, role }); + return await collUser.countDocuments({ domainId }); } @ArgMethod @@ -136,7 +139,9 @@ class DomainModel { deleteUserCache(udoc); return res; } - const affected = await UserModel.getMulti({ _id: { $in: uid } }).project({ mail: 1, uname: 1 }).toArray(); + const affected = await UserModel.getMulti({ _id: { $in: uid } }) + .project<{ _id: number, mail: string, uname: string }>({ mail: 1, uname: 1 }) + .toArray(); affected.forEach((udoc) => deleteUserCache(udoc)); return await collUser.updateMany({ domainId, uid: { $in: uid } }, { $set: { role } }, { upsert: true }); } diff --git a/packages/hydrooj/src/model/message.ts b/packages/hydrooj/src/model/message.ts index 44965d0c6..fc1009c20 100644 --- a/packages/hydrooj/src/model/message.ts +++ b/packages/hydrooj/src/model/message.ts @@ -1,4 +1,4 @@ -import { FilterQuery, ObjectID } from 'mongodb'; +import { Filter, ObjectId } from 'mongodb'; import { MessageDoc } from '../interface'; import * as bus from '../service/bus'; import db from '../service/db'; @@ -18,34 +18,33 @@ class MessageModel { static async send( from: number, to: number, content: string, flag: number = MessageModel.FLAG_UNREAD, - ): Promise { - const res = await coll.insertOne({ - from, to, content, flag, - }); - const mdoc = { - from, to, content, _id: res.insertedId, flag, + ) { + const _id = new ObjectId(); + const mdoc: MessageDoc = { + _id, from, to, content, flag, }; + await coll.insertOne(mdoc); if (from !== to) bus.broadcast('user/message', to, mdoc); if (flag & MessageModel.FLAG_UNREAD) await user.inc(to, 'unreadMsg', 1); return mdoc; } - static async get(_id: ObjectID): Promise { + static async get(_id: ObjectId) { return await coll.findOne({ _id }); } @ArgMethod - static async getByUser(uid: number): Promise { + static async getByUser(uid: number) { return await coll.find({ $or: [{ from: uid }, { to: uid }] }).sort('_id', -1).limit(1000).toArray(); } - static async getMany(query: FilterQuery, sort: any, page: number, limit: number): Promise { + static async getMany(query: Filter, sort: any, page: number, limit: number) { return await coll.find(query).sort(sort) .skip((page - 1) * limit).limit(limit) .toArray(); } - static async setFlag(messageId: ObjectID, flag: number): Promise { + static async setFlag(messageId: ObjectId, flag: number) { const result = await coll.findOneAndUpdate( { _id: messageId }, { $bit: { flag: { xor: flag } } }, @@ -54,13 +53,13 @@ class MessageModel { return result.value || null; } - static async del(_id: ObjectID) { + static async del(_id: ObjectId) { return await coll.deleteOne({ _id }); } @ArgMethod - static count(query: FilterQuery = {}) { - return coll.find(query).count(); + static count(query: Filter = {}) { + return coll.countDocuments(query); } static getMulti(uid: number) { diff --git a/packages/hydrooj/src/model/oplog.ts b/packages/hydrooj/src/model/oplog.ts index 23c4508eb..9b88e4450 100644 --- a/packages/hydrooj/src/model/oplog.ts +++ b/packages/hydrooj/src/model/oplog.ts @@ -1,5 +1,5 @@ import { cloneDeep } from 'lodash'; -import { ObjectID } from 'mongodb'; +import { ObjectId } from 'mongodb'; import { OplogDoc } from '../interface'; import * as bus from '../service/bus'; import db from '../service/db'; @@ -7,20 +7,20 @@ import type { Handler } from '../service/server'; export const coll = db.collection('oplog'); -export async function add(data: Partial & { type: string }): Promise { +export async function add(data: Partial & { type: string }): Promise { const _data = { ...data }; if (_data._id) { _data.id = _data._id; delete _data._id; } - const res = await coll.insertOne({ _id: new ObjectID(), ..._data }); + const res = await coll.insertOne({ _id: new ObjectId(), ..._data }); return res.insertedId; } function safeKeys(data: any) { if (['string', 'number', 'boolean'].includes(typeof data)) return data; if (data instanceof Array) data.forEach(safeKeys); - else if (data instanceof ObjectID) return data; + else if (data instanceof ObjectId) return data; else if (data instanceof Object) { for (const key in data) { if (['password', 'verifyPassword'].includes(key)) { @@ -42,7 +42,7 @@ export async function log(handler: T, type: string, data: any await bus.parallel('oplog/log', type, handler, args, data); const res = await coll.insertOne({ ...data, - _id: new ObjectID(), + _id: new ObjectId(), type, time: new Date(), domainId: handler.args.domainId, @@ -55,7 +55,7 @@ export async function log(handler: T, type: string, data: any return res.insertedId; } -export async function get(id: ObjectID): Promise { +export async function get(id: ObjectId) { return await coll.findOne({ _id: id }); } diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index 6f1e48f62..f7e3ac7bd 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -1,5 +1,5 @@ import { escapeRegExp, pick } from 'lodash'; -import { FilterQuery, ObjectID } from 'mongodb'; +import { Filter, ObjectId } from 'mongodb'; import type { Readable } from 'stream'; import { streamToBuffer } from '@hydrooj/utils/lib/utils'; import { FileUploadError, ProblemNotFoundError, ValidationError } from '../error'; @@ -50,7 +50,7 @@ export class ProblemModel { ]; static default = { - _id: new ObjectID(), + _id: new ObjectId(), domainId: 'system', docType: document.TYPE_PROBLEM, docId: 0, @@ -71,7 +71,7 @@ export class ProblemModel { }; static deleted = { - _id: new ObjectID(), + _id: new ObjectId(), domainId: 'system', docType: document.TYPE_PROBLEM, docId: -1, @@ -141,12 +141,12 @@ export class ProblemModel { return res; } - static getMulti(domainId: string, query: FilterQuery, projection = ProblemModel.PROJECTION_LIST) { + static getMulti(domainId: string, query: Filter, projection = ProblemModel.PROJECTION_LIST) { return document.getMulti(domainId, document.TYPE_PROBLEM, query, projection).sort({ sort: 1 }); } static async list( - domainId: string, query: FilterQuery, + domainId: string, query: Filter, page: number, pageSize: number, projection = ProblemModel.PROJECTION_LIST, uid?: number, ): Promise<[ProblemDoc[], number, number]> { @@ -162,7 +162,7 @@ export class ProblemModel { if (!udoc.hasPerm(PERM.PERM_VIEW_PROBLEM)) continue; } // eslint-disable-next-line no-await-in-loop - const ccount = await document.getMulti(id, document.TYPE_PROBLEM, query).count(); + const ccount = await document.count(id, document.TYPE_PROBLEM, query); if (pdocs.length < pageSize && (page - 1) * pageSize - count <= ccount) { // eslint-disable-next-line no-await-in-loop pdocs.push(...await document.getMulti(id, document.TYPE_PROBLEM, query, projection) @@ -178,7 +178,7 @@ export class ProblemModel { return document.getStatus(domainId, document.TYPE_PROBLEM, docId, uid); } - static getMultiStatus(domainId: string, query: FilterQuery) { + static getMultiStatus(domainId: string, query: Filter) { return document.getMultiStatus(domainId, document.TYPE_PROBLEM, query); } @@ -222,7 +222,7 @@ export class ProblemModel { return document.inc(domainId, document.TYPE_PROBLEM, _id, field as any, n); } - static count(domainId: string, query: FilterQuery) { + static count(domainId: string, query: Filter) { return document.count(domainId, document.TYPE_PROBLEM, query); } @@ -286,11 +286,11 @@ export class ProblemModel { await bus.emit('problem/delAdditionalFile', domainId, pid, names); } - static async random(domainId: string, query: FilterQuery) { - const cursor = document.getMulti(domainId, document.TYPE_PROBLEM, query); - const pcount = await cursor.count(); + static async random(domainId: string, query: Filter) { + const pcount = await document.count(domainId, document.TYPE_PROBLEM, query); if (!pcount) return null; - const pdoc = await cursor.skip(Math.floor(Math.random() * pcount)).limit(1).toArray(); + const pdoc = await document.getMulti(domainId, document.TYPE_PROBLEM, query) + .skip(Math.floor(Math.random() * pcount)).limit(1).toArray(); return pdoc[0].pid || pdoc[0].docId; } @@ -303,7 +303,7 @@ export class ProblemModel { const l: Record = {}; const q: any = { docId: { $in: pids } }; let pdocs = await document.getMulti(domainId, document.TYPE_PROBLEM, q) - .project(buildProjection(projection)).toArray(); + .project(buildProjection(projection)).toArray(); if (canViewHidden !== true) { pdocs = pdocs.filter((i) => i.owner === canViewHidden || i.maintainer?.includes(canViewHidden as any) || !i.hidden); } @@ -350,9 +350,9 @@ export class ProblemModel { static async updateStatus( domainId: string, pid: number, uid: number, - rid: ObjectID, status: number, score: number, + rid: ObjectId, status: number, score: number, ) { - const filter: FilterQuery = { rid: { $ne: rid }, status: STATUS.STATUS_ACCEPTED }; + const filter: Filter = { rid: { $ne: rid }, status: STATUS.STATUS_ACCEPTED }; const res = await document.setStatusIfNotCondition( domainId, document.TYPE_PROBLEM, pid, uid, filter, { rid, status, score }, diff --git a/packages/hydrooj/src/model/record.ts b/packages/hydrooj/src/model/record.ts index ed8a0846d..a552ed8a8 100644 --- a/packages/hydrooj/src/model/record.ts +++ b/packages/hydrooj/src/model/record.ts @@ -2,9 +2,8 @@ import { sum } from 'lodash'; import moment from 'moment-timezone'; import { - FilterQuery, MatchKeysAndValues, - ObjectID, OnlyFieldsOfType, PushOperator, - UpdateQuery, + Filter, MatchKeysAndValues, + ObjectId, OnlyFieldsOfType, PushOperator, UpdateFilter, } from 'mongodb'; import { Context } from '../context'; import { ProblemNotFoundError } from '../error'; @@ -36,9 +35,9 @@ export default class RecordModel { return base - (pending * 1000 + 1) * (sum(timeRecent.map((i) => i.time || 0)) / 10000 + 1); } - static async get(_id: ObjectID): Promise; - static async get(domainId: string, _id: ObjectID): Promise; - static async get(arg0: string | ObjectID, arg1?: any) { + static async get(_id: ObjectId): Promise; + static async get(domainId: string, _id: ObjectId): Promise; + static async get(arg0: string | ObjectId, arg1?: any) { const _id = arg1 || arg0; const domainId = arg1 ? arg0 : null; const res = await RecordModel.coll.findOne({ _id }); @@ -50,20 +49,20 @@ export default class RecordModel { @ArgMethod static async stat(domainId?: string) { const [d5min, d1h, day, week, month, year, total] = await Promise.all([ - RecordModel.coll.find({ _id: { $gte: Time.getObjectID(moment().add(-5, 'minutes')) }, ...domainId ? { domainId } : {} }).count(), - RecordModel.coll.find({ _id: { $gte: Time.getObjectID(moment().add(-1, 'hour')) }, ...domainId ? { domainId } : {} }).count(), - RecordModel.coll.find({ _id: { $gte: Time.getObjectID(moment().add(-1, 'day')) }, ...domainId ? { domainId } : {} }).count(), - RecordModel.coll.find({ _id: { $gte: Time.getObjectID(moment().add(-1, 'week')) }, ...domainId ? { domainId } : {} }).count(), - RecordModel.coll.find({ _id: { $gte: Time.getObjectID(moment().add(-1, 'month')) }, ...domainId ? { domainId } : {} }).count(), - RecordModel.coll.find({ _id: { $gte: Time.getObjectID(moment().add(-1, 'year')) }, ...domainId ? { domainId } : {} }).count(), - RecordModel.coll.find(domainId ? { domainId } : {}).count(), + RecordModel.coll.countDocuments({ _id: { $gte: Time.getObjectID(moment().add(-5, 'minutes')) }, ...domainId ? { domainId } : {} }), + RecordModel.coll.countDocuments({ _id: { $gte: Time.getObjectID(moment().add(-1, 'hour')) }, ...domainId ? { domainId } : {} }), + RecordModel.coll.countDocuments({ _id: { $gte: Time.getObjectID(moment().add(-1, 'day')) }, ...domainId ? { domainId } : {} }), + RecordModel.coll.countDocuments({ _id: { $gte: Time.getObjectID(moment().add(-1, 'week')) }, ...domainId ? { domainId } : {} }), + RecordModel.coll.countDocuments({ _id: { $gte: Time.getObjectID(moment().add(-1, 'month')) }, ...domainId ? { domainId } : {} }), + RecordModel.coll.countDocuments({ _id: { $gte: Time.getObjectID(moment().add(-1, 'year')) }, ...domainId ? { domainId } : {} }), + RecordModel.coll.countDocuments(domainId ? { domainId } : {}), ]); return { d5min, d1h, day, week, month, year, total, }; } - static async judge(domainId: string, rids: MaybeArray, priority = 0, config: ProblemConfigFile = {}, meta: Partial = {}) { + static async judge(domainId: string, rids: MaybeArray, priority = 0, config: ProblemConfigFile = {}, meta: Partial = {}) { rids = rids instanceof Array ? rids : [rids]; if (!rids.length) return null; const rdoc = await RecordModel.get(domainId, rids[0]); @@ -112,7 +111,7 @@ export default class RecordModel { domainId: string, pid: number, uid: number, lang: string, code: string, addTask: boolean, args: { - contest?: ObjectID, + contest?: ObjectId, input?: string, files?: Record, type: 'judge' | 'rejudge' | 'contest' | 'pretest' | 'hack', @@ -120,7 +119,7 @@ export default class RecordModel { ) { const data: RecordDoc = { status: STATUS.STATUS_WAITING, - _id: new ObjectID(), + _id: new ObjectId(), uid, code, lang, @@ -143,7 +142,7 @@ export default class RecordModel { data.rejudged = true; } else if (args.type === 'pretest') { data.input = args.input || ''; - data.contest = new ObjectID('000000000000000000000000'); + data.contest = new ObjectId('000000000000000000000000'); } const res = await RecordModel.coll.insertOne(data); if (addTask) { @@ -159,13 +158,13 @@ export default class RecordModel { } static async update( - domainId: string, _id: MaybeArray, + domainId: string, _id: MaybeArray, $set?: MatchKeysAndValues, $push?: PushOperator, $unset?: OnlyFieldsOfType, $inc?: Partial, number>>, ): Promise { - const $update: UpdateQuery = {}; + const $update: UpdateFilter = {}; 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; @@ -186,12 +185,12 @@ export default class RecordModel { } static async updateMulti( - domainId: string, $match: FilterQuery, + domainId: string, $match: Filter, $set?: MatchKeysAndValues, $push?: PushOperator, $unset?: OnlyFieldsOfType, ) { - const $update: UpdateQuery = {}; + const $update: UpdateFilter = {}; 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; @@ -199,7 +198,7 @@ export default class RecordModel { return res.modifiedCount; } - static async reset(domainId: string, rid: MaybeArray, isRejudge: boolean) { + static async reset(domainId: string, rid: MaybeArray, isRejudge: boolean) { const upd: any = { score: 0, status: STATUS.STATUS_WAITING, @@ -217,11 +216,11 @@ export default class RecordModel { } static count(domainId: string, query: any) { - return RecordModel.coll.find({ domainId, ...query }).count(); + return RecordModel.coll.countDocuments({ domainId, ...query }); } static async getList( - domainId: string, rids: ObjectID[], fields?: (keyof RecordDoc)[], + domainId: string, rids: ObjectId[], fields?: (keyof RecordDoc)[], ): Promise>> { const r: Record = {}; rids = Array.from(new Set(rids)); diff --git a/packages/hydrooj/src/model/schedule.ts b/packages/hydrooj/src/model/schedule.ts index 5d0917174..f671c9aee 100644 --- a/packages/hydrooj/src/model/schedule.ts +++ b/packages/hydrooj/src/model/schedule.ts @@ -1,5 +1,5 @@ import moment from 'moment-timezone'; -import { FilterQuery, ObjectID } from 'mongodb'; +import { Filter, ObjectId } from 'mongodb'; import { sleep } from '@hydrooj/utils/lib/utils'; import { Context, Service } from '../context'; import { Schedule } from '../interface'; @@ -10,7 +10,7 @@ import db from '../service/db'; const logger = new Logger('model/schedule'); const coll = db.collection('schedule'); -async function getFirst(query: FilterQuery) { +async function getFirst(query: Filter) { if (process.env.CI) return null; const q = { ...query }; q.executeAfter ||= { $lt: new Date() }; @@ -100,24 +100,24 @@ class ScheduleModel { const res = await coll.insertOne({ ...task, executeAfter: task.executeAfter || new Date(), - _id: new ObjectID(), + _id: new ObjectId(), }); return res.insertedId; } - static get(_id: ObjectID) { + static get(_id: ObjectId) { return coll.findOne({ _id }); } - static count(query: FilterQuery) { - return coll.find(query).count(); + static count(query: Filter) { + return coll.countDocuments(query); } - static del(_id: ObjectID) { + static del(_id: ObjectId) { return coll.deleteOne({ _id }); } - static deleteMany(query: FilterQuery) { + static deleteMany(query: Filter) { return coll.deleteMany(query); } @@ -136,7 +136,7 @@ export async function apply(ctx: Context) { ctx.worker = Worker; Worker.addHandler('task.daily', async () => { - await global.Hydro.model.record.coll.deleteMany({ contest: new ObjectID('000000000000000000000000') }); + await global.Hydro.model.record.coll.deleteMany({ contest: new ObjectId('000000000000000000000000') }); await global.Hydro.script.rp?.run({}, new Logger('task/rp').debug); await global.Hydro.script.problemStat?.run({}, new Logger('task/problem').debug); if (global.Hydro.model.system.get('server.checkUpdate') && !(new Date().getDay() % 3)) { diff --git a/packages/hydrooj/src/model/solution.ts b/packages/hydrooj/src/model/solution.ts index a32e36695..64bdec2f2 100644 --- a/packages/hydrooj/src/model/solution.ts +++ b/packages/hydrooj/src/model/solution.ts @@ -1,4 +1,4 @@ -import { ObjectID } from 'mongodb'; +import { ObjectId } from 'mongodb'; import { SolutionNotFoundError } from '../error'; import * as bus from '../service/bus'; import * as document from './document'; @@ -11,7 +11,7 @@ class SolutionModel { ); } - static async get(domainId: string, psid: ObjectID) { + static async get(domainId: string, psid: ObjectId) { const psdoc = await document.get(domainId, document.TYPE_PROBLEM_SOLUTION, psid); if (!psdoc) throw new SolutionNotFoundError(domainId, psid); return psdoc; @@ -24,11 +24,11 @@ class SolutionModel { .toArray(); } - static edit(domainId: string, psid: ObjectID, content: string) { + static edit(domainId: string, psid: ObjectId, content: string) { return document.set(domainId, document.TYPE_PROBLEM_SOLUTION, psid, { content }); } - static async del(domainId: string, psid: ObjectID) { + static async del(domainId: string, psid: ObjectId) { return await Promise.all([ document.deleteOne(domainId, document.TYPE_PROBLEM_SOLUTION, psid), document.deleteMultiStatus(domainId, document.TYPE_PROBLEM_SOLUTION, { docId: psid }), @@ -53,23 +53,23 @@ class SolutionModel { ).sort({ _id: -1 }); } - static reply(domainId: string, psid: ObjectID, owner: number, content: string) { + static reply(domainId: string, psid: ObjectId, owner: number, content: string) { return document.push(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', content, owner); } - static getReply(domainId: string, psid: ObjectID, psrid: ObjectID) { + static getReply(domainId: string, psid: ObjectId, psrid: ObjectId) { return document.getSub(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', psrid); } - static editReply(domainId: string, psid: ObjectID, psrid: ObjectID, content: string) { + static editReply(domainId: string, psid: ObjectId, psrid: ObjectId, content: string) { return document.setSub(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', psrid, { content }); } - static delReply(domainId: string, psid: ObjectID, psrid: ObjectID) { + static delReply(domainId: string, psid: ObjectId, psrid: ObjectId) { return document.deleteSub(domainId, document.TYPE_PROBLEM_SOLUTION, psid, 'reply', psrid); } - static async vote(domainId: string, psid: ObjectID, uid: number, value: number) { + static async vote(domainId: string, psid: ObjectId, uid: number, value: number) { let pssdoc = await document.getStatus(domainId, document.TYPE_PROBLEM_SOLUTION, psid, uid); await document.setStatus(domainId, document.TYPE_PROBLEM_SOLUTION, psid, uid, { vote: value }); if (pssdoc) value += -pssdoc.vote; @@ -78,11 +78,11 @@ class SolutionModel { return [psdoc, pssdoc]; } - static async getListStatus(domainId: string, psids: ObjectID[], uid: number) { - const result: Record = {}; + static async getListStatus(domainId: string, psids: ObjectId[], uid: number) { + const result: Record = {}; const res = await document.getMultiStatus( domainId, document.TYPE_PROBLEM_SOLUTION, { uid, docId: { $in: psids } }, - ).project({ docId: 1, vote: 1 }).toArray(); + ).project({ docId: 1, vote: 1 }).toArray(); for (const i of res) result[i.docId] = i; return result; } diff --git a/packages/hydrooj/src/model/task.ts b/packages/hydrooj/src/model/task.ts index 3261bfef2..4bc3c498e 100644 --- a/packages/hydrooj/src/model/task.ts +++ b/packages/hydrooj/src/model/task.ts @@ -1,6 +1,6 @@ import { hostname } from 'os'; import cac from 'cac'; -import { FilterQuery, ObjectID } from 'mongodb'; +import { Filter, ObjectId } from 'mongodb'; import { nanoid } from 'nanoid'; import { sleep } from '@hydrooj/utils/lib/utils'; import { Context } from '../context'; @@ -14,7 +14,7 @@ const coll = db.collection('task'); const collEvent = db.collection('event'); const argv = cac().parse(); -async function getFirst(query: FilterQuery) { +async function getFirst(query: Filter) { if (process.env.CI) return null; const q = { ...query }; const res = await coll.findOneAndDelete(q, { sort: { priority: -1 } }); @@ -66,7 +66,7 @@ class TaskModel { const t: Task = { ...task, priority: task.priority ?? 0, - _id: new ObjectID(), + _id: new ObjectId(), }; const res = await coll.insertOne(t); return res.insertedId; @@ -77,19 +77,19 @@ class TaskModel { return res.insertedIds; } - static get(_id: ObjectID) { + static get(_id: ObjectId) { return coll.findOne({ _id }); } - static count(query: FilterQuery) { - return coll.find(query).count(); + static count(query: Filter) { + return coll.countDocuments(query); } - static del(_id: ObjectID) { + static del(_id: ObjectId) { return coll.deleteOne({ _id }); } - static deleteMany(query: FilterQuery) { + static deleteMany(query: Filter) { return coll.deleteMany(query); } diff --git a/packages/hydrooj/src/model/token.ts b/packages/hydrooj/src/model/token.ts index e6897fcc3..21dade6aa 100644 --- a/packages/hydrooj/src/model/token.ts +++ b/packages/hydrooj/src/model/token.ts @@ -1,4 +1,4 @@ -import { FilterQuery } from 'mongodb'; +import { Filter } from 'mongodb'; import { TokenDoc } from '../interface'; import * as bus from '../service/bus'; import db from '../service/db'; @@ -29,15 +29,16 @@ class TokenModel { tokenType: number, expireSeconds: number, data: any, id = String.random(32), ): Promise<[string, TokenDoc]> { const now = new Date(); - const res = await TokenModel.coll.insertOne({ + const payload = { ...data, _id: id, tokenType, createAt: now, updateAt: now, expireAt: new Date(now.getTime() + expireSeconds * 1000), - }); - return [id, res.ops[0]]; + }; + await TokenModel.coll.insertOne(payload); + return [id, payload]; } @ArgMethod @@ -45,7 +46,7 @@ class TokenModel { return await TokenModel.coll.findOne({ _id: tokenId, tokenType }); } - static getMulti(tokenType: number, query: FilterQuery = {}) { + static getMulti(tokenType: number, query: Filter = {}) { return TokenModel.coll.find({ tokenType, ...query }); } diff --git a/packages/hydrooj/src/model/training.ts b/packages/hydrooj/src/model/training.ts index 5d78b51ca..ddda4b2ab 100644 --- a/packages/hydrooj/src/model/training.ts +++ b/packages/hydrooj/src/model/training.ts @@ -1,18 +1,18 @@ import { flatten } from 'lodash'; -import { FilterQuery, ObjectID } from 'mongodb'; +import { Filter, ObjectId } from 'mongodb'; import { TrainingAlreadyEnrollError, TrainingNotFoundError } from '../error'; import { TrainingDoc, TrainingNode } from '../interface'; import * as document from './document'; -export function getStatus(domainId: string, tid: ObjectID, uid: number) { +export function getStatus(domainId: string, tid: ObjectId, uid: number) { return document.getStatus(domainId, document.TYPE_TRAINING, tid, uid); } -export function getMultiStatus(domainId: string, query: FilterQuery) { +export function getMultiStatus(domainId: string, query: Filter) { return document.getMultiStatus(domainId, document.TYPE_TRAINING, query); } -export async function getListStatus(domainId: string, uid: number, tids: ObjectID[]) { +export async function getListStatus(domainId: string, uid: number, tids: ObjectId[]) { const tsdocs = await getMultiStatus( domainId, { uid, docId: { $in: Array.from(new Set(tids)) } }, ).toArray(); @@ -21,7 +21,7 @@ export async function getListStatus(domainId: string, uid: number, tids: ObjectI return r; } -export async function enroll(domainId: string, tid: ObjectID, uid: number) { +export async function enroll(domainId: string, tid: ObjectId, uid: number) { try { await document.setStatus(domainId, document.TYPE_TRAINING, tid, uid, { enroll: 1 }); } catch (e) { @@ -30,7 +30,7 @@ export async function enroll(domainId: string, tid: ObjectID, uid: number) { return await document.inc(domainId, document.TYPE_TRAINING, tid, 'attend', 1); } -export function setStatus(domainId: string, tid: ObjectID, uid: number, $set: any) { +export function setStatus(domainId: string, tid: ObjectId, uid: number, $set: any) { return document.setStatus(domainId, document.TYPE_TRAINING, tid, uid, $set); } @@ -46,11 +46,11 @@ export function add( }); } -export function edit(domainId: string, tid: ObjectID, $set: Partial) { +export function edit(domainId: string, tid: ObjectId, $set: Partial) { return document.set(domainId, document.TYPE_TRAINING, tid, $set); } -export function del(domainId: string, tid: ObjectID) { +export function del(domainId: string, tid: ObjectId) { return Promise.all([ document.deleteOne(domainId, document.TYPE_TRAINING, tid), document.deleteMultiStatus(domainId, document.TYPE_TRAINING, { docId: tid }), @@ -87,11 +87,11 @@ export function isOpen(node: TrainingNode, doneNids: Set | number[], don export const isInvalid = (node: TrainingNode, doneNids: Set | number[]) => !Set.isSuperset(new Set(doneNids), new Set(node.requireNids)); -export async function count(domainId: string, query: FilterQuery) { +export async function count(domainId: string, query: Filter) { return await document.count(domainId, document.TYPE_TRAINING, query); } -export async function get(domainId: string, tid: ObjectID) { +export async function get(domainId: string, tid: ObjectId) { const tdoc = await document.get(domainId, document.TYPE_TRAINING, tid); if (!tdoc) throw new TrainingNotFoundError(domainId, tid); for (const i in tdoc.dag) { @@ -104,10 +104,10 @@ export async function get(domainId: string, tid: ObjectID) { return tdoc; } -export const getMulti = (domainId: string, query: FilterQuery = {}) => +export const getMulti = (domainId: string, query: Filter = {}) => document.getMulti(domainId, document.TYPE_TRAINING, query).sort({ pin: -1, _id: -1 }); -export async function getList(domainId: string, tids: ObjectID[]) { +export async function getList(domainId: string, tids: ObjectId[]) { const tdocs = await getMulti( domainId, { _id: { $in: Array.from(new Set(tids)) } }, ).toArray(); diff --git a/packages/hydrooj/src/model/user.ts b/packages/hydrooj/src/model/user.ts index d2f199eb4..a6184a718 100644 --- a/packages/hydrooj/src/model/user.ts +++ b/packages/hydrooj/src/model/user.ts @@ -1,6 +1,6 @@ import { escapeRegExp, pick, uniq } from 'lodash'; import LRU from 'lru-cache'; -import { Collection, FilterQuery, ObjectID } from 'mongodb'; +import { Collection, Filter, ObjectId } from 'mongodb'; import { LoginError, UserAlreadyExistError, UserNotFoundError } from '../error'; import { Authenticator, BaseUserDict, FileInfo, GDoc, @@ -24,7 +24,7 @@ export const collV: Collection = db.collection('vuser'); export const collGroup: Collection = db.collection('user.group'); const cache = new LRU({ max: 10000, ttl: 300 * 1000 }); -export function deleteUserCache(udoc: User | Udoc | string | true | undefined | null, receiver = false) { +export function deleteUserCache(udoc: { _id: number, uname: string, mail: string } | string | true | undefined | null, receiver = false) { if (!udoc) return false; if (!receiver) { bus.broadcast( @@ -357,15 +357,15 @@ class UserModel { return uid; } - static getMulti(params: FilterQuery = {}, projection?: (keyof Udoc)[]) { - return projection ? coll.find(params).project(buildProjection(projection)) : coll.find(params); + static getMulti(params: Filter = {}, projection?: (keyof Udoc)[]) { + return projection ? coll.find(params).project(buildProjection(projection)) : coll.find(params); } static async getListForRender(domainId: string, uids: number[]) { const [udocs, vudocs, dudocs] = await Promise.all([ UserModel.getMulti({ _id: { $in: uids } }, ['_id', 'uname', 'mail', 'avatar', 'school', 'studentId']).toArray(), collV.find({ _id: { $in: uids } }).toArray(), - domain.getDomainUserMulti(domainId, uids).project({ uid: 1, displayName: 1 }).toArray(), + domain.getDomainUserMulti(domainId, uids).project({ uid: true, displayName: true }).toArray(), ]); const udict = {}; for (const udoc of udocs) udict[udoc._id] = udoc; @@ -426,7 +426,7 @@ class UserModel { const groups = await collGroup.find(typeof uid === 'number' ? { domainId, uids: uid } : { domainId }).toArray(); if (uid) { groups.push({ - _id: new ObjectID(), domainId, uids: [uid], name: uid.toString(), + _id: new ObjectId(), domainId, uids: [uid], name: uid.toString(), }); } return groups; diff --git a/packages/hydrooj/src/pipelineUtils.ts b/packages/hydrooj/src/pipelineUtils.ts index 6bae96673..249517527 100644 --- a/packages/hydrooj/src/pipelineUtils.ts +++ b/packages/hydrooj/src/pipelineUtils.ts @@ -1,5 +1,5 @@ /* eslint-disable no-await-in-loop */ -import { FilterQuery } from 'mongodb'; +import { Filter } from 'mongodb'; import type { DomainDoc, ProblemStatusDoc, RecordDoc, Tdoc, Udoc, @@ -18,7 +18,7 @@ export async function iterateAllDomain(cb: (ddoc: DomainDoc, current?: number, t } export async function iterateAllUser(cb: (udoc: Udoc, current?: number, total?: number) => Promise) { - const udocs = await user.getMulti().toArray(); + const udocs = await user.getMulti({}).toArray(); for (const i in udocs) await cb(udocs[i], +i, udocs.length); return true; } @@ -32,7 +32,7 @@ export async function iterateAllContest(cb: (tdoc: Tdoc) => Promise) { }); } -export async function iterateAllPsdoc(filter: FilterQuery, cb: (psdoc: ProblemStatusDoc) => Promise) { +export async function iterateAllPsdoc(filter: Filter, cb: (psdoc: ProblemStatusDoc) => Promise) { return await iterateAllDomain(async ({ _id: domainId }) => { const cursor = document.getMultiStatus(domainId, document.TYPE_PROBLEM, filter); while (await cursor.hasNext()) { @@ -54,7 +54,7 @@ export async function iterateAllProblemInDomain( 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(); + const total = await problem.count(domainId, {}); let i = 0; for await (const doc of cursor) { i++; @@ -76,7 +76,7 @@ export async function iterateAllProblem( export async function iterateAllRecord( cb: (rdoc: RecordDoc, current: number, total: number) => any, ) { - const total = await RecordModel.coll.count(); + const total = await RecordModel.coll.countDocuments(); let i = 0; const cursor = RecordModel.coll.find().sort('_id', 1); for await (const rdoc of cursor) { diff --git a/packages/hydrooj/src/script/blacklist.ts b/packages/hydrooj/src/script/blacklist.ts index 7ba295244..1fcfad896 100644 --- a/packages/hydrooj/src/script/blacklist.ts +++ b/packages/hydrooj/src/script/blacklist.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-use-before-define */ -import { ObjectID } from 'mongodb'; +import { ObjectId } from 'mongodb'; import Schema from 'schemastery'; import blacklist from '../model/blacklist'; import * as discussion from '../model/discussion'; @@ -9,7 +9,7 @@ import db from '../service/db'; async function _address( ip: string, - bset: Set, uset: Set, dset: Set, + bset: Set, uset: Set, dset: Set, dryrun: boolean, report: Function, ) { if (bset.has(ip)) return; @@ -25,8 +25,8 @@ async function _address( } async function _discussion( - domainId: string, did: ObjectID, - bset: Set, uset: Set, dset: Set, + domainId: string, did: ObjectId, + bset: Set, uset: Set, dset: Set, dryrun: boolean, report: Function, ) { if (dset.has(did)) return; @@ -41,7 +41,7 @@ async function _discussion( async function _user( uid: number, - bset: Set, uset: Set, dset: Set, + bset: Set, uset: Set, dset: Set, dryrun: boolean, report: Function, ) { if (uset.has(uid)) return; @@ -77,7 +77,7 @@ export const apply = (ctx) => ctx.addScript( if (address) await _address(address, new Set(), new Set(), new Set(), dryrun, report); if (discuss) { await _discussion( - discuss.domainId, new ObjectID(discuss.did), + discuss.domainId, new ObjectId(discuss.did), new Set(), new Set(), new Set(), dryrun, report, ); } diff --git a/packages/hydrooj/src/script/problemStat.ts b/packages/hydrooj/src/script/problemStat.ts index 65e3f9266..72b759e80 100644 --- a/packages/hydrooj/src/script/problemStat.ts +++ b/packages/hydrooj/src/script/problemStat.ts @@ -1,5 +1,5 @@ /* eslint-disable no-await-in-loop */ -import { ObjectID } from 'mongodb'; +import { ObjectId } from 'mongodb'; import Schema from 'schemastery'; import { STATUS } from '../model/builtin'; import * as document from '../model/document'; @@ -10,7 +10,7 @@ const sumStatus = (status) => ({ $sum: { $cond: [{ $eq: ['$status', status] }, 1 export async function udoc(report) { report({ message: 'Udoc' }); const pipeline = [ - { $match: { contest: { $ne: new ObjectID('000000000000000000000000') } } }, + { $match: { contest: { $ne: new ObjectId('000000000000000000000000') } } }, { $group: { _id: { domainId: '$domainId', pid: '$pid', uid: '$uid' }, @@ -38,18 +38,18 @@ export async function udoc(report) { nAccept: adoc.nAccept, }, }); - if (bulk.length > 100) { + if (bulk.batches.length > 100) { await bulk.execute(); bulk = db.collection('domain.user').initializeUnorderedBulkOp(); } } - if (bulk.length) await bulk.execute(); + if (bulk.batches.length) await bulk.execute(); } export async function psdoc(report) { report({ message: 'Psdoc' }); const pipeline = [ - { $match: { contest: { $ne: new ObjectID('000000000000000000000000') } } }, + { $match: { contest: { $ne: new ObjectId('000000000000000000000000') } } }, { $group: { _id: { domainId: '$domainId', pid: '$pid', uid: '$uid' }, @@ -68,7 +68,7 @@ export async function pdoc(report) { const pipeline = [ { $match: { - contest: { $ne: new ObjectID('000000000000000000000000') }, + contest: { $ne: new ObjectId('000000000000000000000000') }, status: { $ne: STATUS.STATUS_CANCELED }, }, }, @@ -139,14 +139,14 @@ export async function pdoc(report) { docType: document.TYPE_PROBLEM, docId: adoc._id.pid, }).updateOne({ $set }); - if (bulk.length > 100) { + if (bulk.batches.length > 100) { await bulk.execute(); cnt++; report({ message: `${cnt * 100} pdocs updated` }); bulk = db.collection('document').initializeUnorderedBulkOp(); } } - if (bulk.length) await bulk.execute(); + if (bulk.batches.length) await bulk.execute(); } export const apply = (ctx) => ctx.addScript( diff --git a/packages/hydrooj/src/script/rating.ts b/packages/hydrooj/src/script/rating.ts index 7aabe475d..2fc860728 100644 --- a/packages/hydrooj/src/script/rating.ts +++ b/packages/hydrooj/src/script/rating.ts @@ -1,7 +1,7 @@ /* eslint-disable no-cond-assign */ /* eslint-disable no-await-in-loop */ import { NumericDictionary, unionWith } from 'lodash'; -import { FilterQuery } from 'mongodb'; +import { Filter, ObjectId } from 'mongodb'; import Schema from 'schemastery'; import { Counter } from '@hydrooj/utils'; import { Tdoc, Udoc } from '../interface'; @@ -60,11 +60,12 @@ export const RpTypes: Record = { if (contests.length) await report({ message: `Found ${contests.length} contests in ${domainIds[0]}` }); for (const tdoc of contests.reverse()) { const start = Date.now(); - const cursor = contest.getMultiStatus(tdoc.domainId, { + const query = { docId: tdoc.docId, journal: { $ne: null }, - }).sort(contest.RULES[tdoc.rule].statusSort); - if (!await cursor.count()) continue; + }; + if (!await contest.count(tdoc.domainId, query)) continue; + const cursor = contest.getMultiStatus(tdoc.domainId, query).sort(contest.RULES[tdoc.rule].statusSort); const rankedTsdocs = await contest.RULES[tdoc.rule].ranked(tdoc, cursor); const users = rankedTsdocs.map((i) => ({ uid: i[1].uid, rank: i[0], old: udict[i[1].uid] })); // FIXME sum(rating.new) always less than sum(rating.old) @@ -107,7 +108,9 @@ export async function calcLevel(domainId: string, report: Function) { let count = 0; const coll = db.collection('domain.user'); const filter = { rp: { $gt: 0 }, uid: { $nin: [0, 1], $gt: -1000 } }; - const ducur = domain.getMultiUserInDomain(domainId, filter).project({ rp: 1 }).sort({ rp: -1 }); + const ducur = domain.getMultiUserInDomain(domainId, filter) + .project<{ _id: ObjectId, rp: number }>({ rp: 1 }) + .sort({ rp: -1 }); let bulk = coll.initializeUnorderedBulkOp(); for await (const dudoc of ducur) { count++; @@ -122,7 +125,7 @@ export async function calcLevel(domainId: string, report: Function) { const levels = global.Hydro.model.builtin.LEVELS; bulk = coll.initializeUnorderedBulkOp(); for (let i = 0; i < levels.length; i++) { - const query: FilterQuery = { + const query: Filter = { domainId, $and: [{ rank: { $lte: (levels[i] * count) / 100 } }], }; @@ -148,14 +151,14 @@ async function runInDomain(domainId: string, report: Function) { bulk.find({ domainId, uid: +uid }).updateOne({ $set: { [`rpInfo.${type}`]: results[type][uid] } }); udict[+uid] += results[type][uid]; } - if (bulk.length) await bulk.execute(); + if (bulk.batches.length) await bulk.execute(); } await domain.setMultiUserInDomain(domainId, {}, { rp: 0 }); const bulk = db.collection('domain.user').initializeUnorderedBulkOp(); for (const uid in udict) { bulk.find({ domainId, uid: +uid }).upsert().update({ $set: { rp: Math.max(0, udict[uid]) } }); } - if (bulk.length) await bulk.execute(); + if (bulk.batches.length) await bulk.execute(); await calcLevel(domainId, report); } diff --git a/packages/hydrooj/src/service/bus.ts b/packages/hydrooj/src/service/bus.ts index d3335c26b..60050d188 100644 --- a/packages/hydrooj/src/service/bus.ts +++ b/packages/hydrooj/src/service/bus.ts @@ -1,6 +1,6 @@ /* eslint-disable no-await-in-loop */ import type { - Db, FilterQuery, ObjectID, OnlyFieldsOfType, + Db, Filter, ObjectId, OnlyFieldsOfType, } from 'mongodb'; import pm2 from '@hydrooj/utils/lib/locate-pm2'; import type { ProblemSolutionHandler } from '../handler/problem'; @@ -54,7 +54,7 @@ export interface EventMap extends LifecycleEvents, HandlerEvents { 'user/delcache': (content: string | true) => void 'domain/create': (ddoc: DomainDoc) => VoidReturn - 'domain/before-get': (query: FilterQuery) => VoidReturn + 'domain/before-get': (query: Filter) => VoidReturn 'domain/get': (ddoc: DomainDoc) => VoidReturn 'domain/before-update': (domainId: string, $set: Partial) => VoidReturn 'domain/update': (domainId: string, $set: Partial, ddoc: DomainDoc) => VoidReturn @@ -80,7 +80,7 @@ export interface EventMap extends LifecycleEvents, HandlerEvents { 'problem/edit': (doc: ProblemDoc) => VoidReturn 'problem/before-del': (domainId: string, docId: number) => VoidReturn 'problem/del': (domainId: string, docId: number) => VoidReturn - 'problem/list': (query: FilterQuery, handler: any) => VoidReturn + 'problem/list': (query: Filter, handler: any) => VoidReturn 'problem/get': (doc: ProblemDoc, handler: any) => VoidReturn 'problem/delete': (domainId: string, docId: number) => VoidReturn 'problem/addTestdata': (domainId: string, docId: number, name: string, payload: Omit) => VoidReturn @@ -89,12 +89,12 @@ export interface EventMap extends LifecycleEvents, HandlerEvents { 'problem/delAdditionalFile': (domainId: string, docId: number, name: string[]) => VoidReturn 'contest/before-add': (payload: Partial>) => VoidReturn - 'contest/add': (payload: Partial>, id: ObjectID) => VoidReturn + 'contest/add': (payload: Partial>, id: ObjectId) => VoidReturn 'contest/scoreboard': (tdoc: Tdoc<30>, rows: ScoreboardRow[], udict: BaseUserDict, pdict: ProblemDict) => VoidReturn 'oplog/log': (type: string, handler: Handler, args: any, data: any) => VoidReturn; - 'training/list': (query: FilterQuery, handler: any) => VoidReturn + 'training/list': (query: Filter, handler: any) => VoidReturn 'training/get': (tdoc: TrainingDoc, handler: any) => VoidReturn 'record/change': (rdoc: RecordDoc, $set?: any, $push?: any) => void diff --git a/packages/hydrooj/src/service/db.ts b/packages/hydrooj/src/service/db.ts index 3e3be124b..26b8d0a32 100644 --- a/packages/hydrooj/src/service/db.ts +++ b/packages/hydrooj/src/service/db.ts @@ -1,6 +1,6 @@ /* eslint-disable no-await-in-loop */ import { - Collection, Db, IndexSpecification, MongoClient, + Collection, Db, IndexDescription, MongoClient, WriteConcern, } from 'mongodb'; import { Time } from '@hydrooj/utils'; import { Logger } from '../logger'; @@ -45,19 +45,17 @@ class MongoService { } this.opts = opts; this.client = await MongoClient.connect(mongourl, { - useNewUrlParser: true, - useUnifiedTopology: true, readPreference: 'nearest', - writeConcern: 'majority', + writeConcern: new WriteConcern('majority'), }); this.db = this.client.db(opts.name || 'hydro'); await bus.parallel('database/connect', this.db); setInterval(() => this.fixExpireAfter(), Time.hour); } - public collection(c: K): Collection { - if (this.opts.prefix) return this.db.collection(`${this.opts.prefix}.${c}`); - return this.db.collection(c); + public collection(c: K) { + if (this.opts.prefix) return this.db.collection(`${this.opts.prefix}.${c}`); + return this.db.collection(c); } public async fixExpireAfter() { @@ -74,7 +72,7 @@ class MongoService { } } - public async ensureIndexes(coll: Collection, ...args: IndexSpecification[]) { + public async ensureIndexes(coll: Collection, ...args: IndexDescription[]) { if (process.env.NODE_APP_INSTANCE !== '0') return; let existed: any[]; try { diff --git a/packages/hydrooj/src/service/layers/renderer.ts b/packages/hydrooj/src/service/layers/renderer.ts index 307ae4214..64a0d3b0a 100644 --- a/packages/hydrooj/src/service/layers/renderer.ts +++ b/packages/hydrooj/src/service/layers/renderer.ts @@ -1,4 +1,4 @@ -import { ObjectID } from 'mongodb'; +import { ObjectId } from 'mongodb'; import avatar from '../../lib/avatar'; import serializer from '../../lib/serializer'; import { PERM } from '../../model/builtin'; @@ -37,11 +37,11 @@ export default (router, logger) => async (ctx: KoaContext, next) => { const query: any = {}; for (const kwargs of kwargsList) { for (const key in kwargs) { - if (kwargs[key] instanceof ObjectID) args[key] = kwargs[key].toHexString(); + if (kwargs[key] instanceof ObjectId) args[key] = kwargs[key].toHexString(); else args[key] = kwargs[key].toString().replace(/\//g, '%2F'); } for (const key in kwargs.query || {}) { - if (query[key] instanceof ObjectID) query[key] = kwargs.query[key].toHexString(); + if (query[key] instanceof ObjectId) query[key] = kwargs.query[key].toHexString(); else query[key] = kwargs.query[key].toString(); } } diff --git a/packages/hydrooj/src/service/monitor.ts b/packages/hydrooj/src/service/monitor.ts index 328f6cff9..9134e4534 100644 --- a/packages/hydrooj/src/service/monitor.ts +++ b/packages/hydrooj/src/service/monitor.ts @@ -18,11 +18,11 @@ export async function feedback(): Promise<[string, StatusUpdate]> { const [mid, $update, inf] = await sysinfo.update(); const [installId, name, url] = system.getMany(['installid', 'server.name', 'server.url']); const [domainCount, userCount, problemCount, discussionCount, recordCount] = await Promise.all([ - domain.getMulti().count(), - user.getMulti().count(), - document.coll.find({ docType: document.TYPE_PROBLEM }).count(), - document.coll.find({ docType: document.TYPE_DISCUSSION }).count(), - record.coll.find().count(), + domain.coll.countDocuments(), + user.coll.countDocuments(), + document.coll.countDocuments({ docType: document.TYPE_PROBLEM }), + document.coll.countDocuments({ docType: document.TYPE_DISCUSSION }), + record.coll.countDocuments(), ]); const info: Record = { mid: mid.toString(), diff --git a/packages/hydrooj/src/typeutils.ts b/packages/hydrooj/src/typeutils.ts index 9c44841a1..5c1e44735 100644 --- a/packages/hydrooj/src/typeutils.ts +++ b/packages/hydrooj/src/typeutils.ts @@ -12,6 +12,5 @@ export type Value = { }; export type Projection = readonly (string & keyof O)[]; export type Omit = Pick>; -export type Filter = T extends U ? T : never; export type MaybeArray = T | T[]; export type UnionToIntersection = (U extends any ? (arg: U) => void : never) extends ((arg: infer I) => void) ? I : never; diff --git a/packages/hydrooj/src/upgrade.ts b/packages/hydrooj/src/upgrade.ts index 057e44a30..ec7229d6a 100644 --- a/packages/hydrooj/src/upgrade.ts +++ b/packages/hydrooj/src/upgrade.ts @@ -4,7 +4,7 @@ import yaml from 'js-yaml'; import { pick } from 'lodash'; import moment from 'moment-timezone'; -import { ObjectID } from 'mongodb'; +import { ObjectId } from 'mongodb'; import { sleep } from '@hydrooj/utils'; import { buildContent } from './lib/content'; import { Logger } from './logger'; @@ -53,16 +53,7 @@ const scripts: UpgradeScript[] = [ null, // Init ...new Array(26).fill(unsupportedUpgrade), - async function _27_28() { - const cursor = document.coll.find({ docType: document.TYPE_DISCUSSION }); - for await (const data of cursor) { - const t = Math.exp(-0.15 * (((new Date().getTime() / 1000) - data._id.generationTime) / 3600)); - const rCount = await discussion.getMultiReply(data.domainId, data.docId).count(); - const sort = ((data.sort || 100) + Math.max(rCount - (data.lastRCount || 0), 0) * 10) * t; - await document.coll.updateOne({ _id: data._id }, { $set: { sort, lastRCount: rCount } }); - } - return true; - }, + null, async function _28_29() { return await iterateAllProblem(['content', 'html'], async (pdoc) => { try { @@ -197,8 +188,8 @@ const scripts: UpgradeScript[] = [ parentId: { $type: 'string' }, }).toArray(); for (const doc of ddocs) { - if (ObjectID.isValid(doc.parentId)) { - await document.set(ddoc._id, document.TYPE_DISCUSSION, doc.docId, { parentId: new ObjectID(doc.parentId) }); + if (ObjectId.isValid(doc.parentId)) { + await document.set(ddoc._id, document.TYPE_DISCUSSION, doc.docId, { parentId: new ObjectId(doc.parentId) }); } } }); @@ -215,7 +206,7 @@ const scripts: UpgradeScript[] = [ return true; }, async function _48_49() { - await RecordModel.coll.updateMany({ input: { $exists: true } }, { $set: { contest: new ObjectID('000000000000000000000000') } }); + await RecordModel.coll.updateMany({ input: { $exists: true } }, { $set: { contest: new ObjectId('000000000000000000000000') } }); return true; }, async function _49_50() { @@ -316,7 +307,7 @@ const scripts: UpgradeScript[] = [ await iterateAllProblem(['pid', '_id'], async (pdoc) => { bulk.find({ _id: pdoc._id }).updateOne({ $set: { sort: sortable(pdoc.pid || `P${pdoc.docId}`) } }); }); - if (bulk.length) await bulk.execute(); + if (bulk.batches.length) await bulk.execute(); return true; }, async function _55_56() { diff --git a/packages/hydrooj/src/utils.ts b/packages/hydrooj/src/utils.ts index 063dfe1cd..eec55c4f4 100644 --- a/packages/hydrooj/src/utils.ts +++ b/packages/hydrooj/src/utils.ts @@ -1,6 +1,6 @@ -export function buildProjection(fields: readonly (string | number)[]): Record { - const o = {}; - for (const k of fields) o[k] = 1; +export function buildProjection(fields: readonly T[]): Record { + const o: Record = {} as any; + for (const k of fields) o[k] = true; return o; } diff --git a/packages/migrate/package.json b/packages/migrate/package.json index 9b0cbe643..fbb369575 100644 --- a/packages/migrate/package.json +++ b/packages/migrate/package.json @@ -6,13 +6,12 @@ "author": "undefined ", "license": "AGPL-3.0-or-later", "devDependencies": { - "@types/mongodb": "^3.6.20", "@types/mysql": "^2.15.21", "@types/turndown": "^5.0.1" }, "dependencies": { "mariadb": "^3.1.0", - "mongodb": "^3.7.3", + "mongodb": "^5.1.0", "mysql": "^2.18.1", "turndown": "^7.1.1" } diff --git a/packages/migrate/scripts/hustoj.ts b/packages/migrate/scripts/hustoj.ts index 358f17273..979b5fcaf 100644 --- a/packages/migrate/scripts/hustoj.ts +++ b/packages/migrate/scripts/hustoj.ts @@ -3,7 +3,7 @@ import mysql from 'mysql'; import TurndownService from 'turndown'; import { - buildContent, ContestModel, DomainModel, fs, noop, NotFoundError, ObjectID, postJudge, ProblemModel, + buildContent, ContestModel, DomainModel, fs, noop, NotFoundError, ObjectId, postJudge, ProblemModel, RecordDoc, RecordModel, SolutionModel, STATUS, SystemModel, Time, UserModel, } from 'hydrooj'; @@ -269,7 +269,7 @@ export async function run({ const [source] = await query(`SELECT \`source\` FROM \`source_code\` WHERE \`solution_id\` = ${rdoc.solution_id}`); if (source[0]?.source) data.code = source[0].source; if (rdoc.contest_id) { - data.contest = new ObjectID(tidMap[rdoc.contest_id]); + data.contest = new ObjectId(tidMap[rdoc.contest_id]); await ContestModel.attend(domainId, data.contest, uidMap[rdoc.user_id]).catch(noop); } await RecordModel.coll.insertOne(data); diff --git a/packages/migrate/scripts/syzoj.ts b/packages/migrate/scripts/syzoj.ts index 5cc86e4d9..dc82c9d98 100644 --- a/packages/migrate/scripts/syzoj.ts +++ b/packages/migrate/scripts/syzoj.ts @@ -2,7 +2,7 @@ import mariadb from 'mariadb'; import { buildContent, ContestModel, DiscussionDoc, DiscussionReplyDoc, DocumentModel, DomainModel, - fs, noop, NotFoundError, ObjectID, PERM, postJudge, ProblemModel, RecordDoc, RecordModel, + fs, noop, NotFoundError, ObjectId, PERM, postJudge, ProblemModel, RecordDoc, RecordModel, STATUS, SystemModel, Time, UserModel, yaml, } from 'hydrooj'; @@ -345,7 +345,7 @@ export async function run({ } } if (rdoc.type) { - data.contest = new ObjectID(tidMap[rdoc.type_info]); + data.contest = new ObjectId(tidMap[rdoc.type_info]); await ContestModel.attend(domainId, data.contest, uidMap[rdoc.user_id]).catch(noop); } await RecordModel.coll.insertOne(data); diff --git a/packages/migrate/scripts/vijos.ts b/packages/migrate/scripts/vijos.ts index 075f1d494..866316b11 100644 --- a/packages/migrate/scripts/vijos.ts +++ b/packages/migrate/scripts/vijos.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable no-await-in-loop */ -import mongodb, { Cursor, Db } from 'mongodb'; +import mongodb, { Db, FindCursor } from 'mongodb'; import { db as dst, DiscussionModel, DiscussionTailReplyDoc, DocumentModel, @@ -268,7 +268,7 @@ const tasks = { }), }; -type CursorGetter = (s: Db) => Cursor; +type CursorGetter = (s: Db) => FindCursor; const cursor: NodeJS.Dict = { user: (s) => s.collection('user').find(), @@ -283,7 +283,7 @@ const cursor: NodeJS.Dict = { }; async function discussionNode(src: Db, report: Function) { - const count = await src.collection('document').find({ doc_type: 20 }).count(); + const count = await src.collection('document').countDocuments({ doc_type: 20 }); await report({ progress: 1, message: `discussion.node: ${count}` }); const total = Math.floor(count / 5); for (let i = 0; i <= total; i++) { @@ -328,7 +328,7 @@ async function fix(doc) { } async function fixProblem(report: Function) { - const count = await dst.collection('document').find({ docType: 10 }).count(); + const count = await dst.collection('document').countDocuments({ docType: 10 }); await report({ progress: 1, message: `Fix pid: ${count}` }); const total = Math.floor(count / 50); for (let i = 0; i <= total; i++) { @@ -344,13 +344,13 @@ async function fixProblem(report: Function) { function objid(ts: Date) { const p = Math.floor(ts.getTime() / 1000).toString(16); - const id = new mongodb.ObjectID(); - return new mongodb.ObjectID(p + id.toHexString().slice(8, 8 + 6 + 4 + 6)); + const id = new mongodb.ObjectId(); + return new mongodb.ObjectId(p + id.toHexString().slice(8, 8 + 6 + 4 + 6)); } // FIXME this seems not working async function message(src: Db, report: Function) { - const count = await src.collection('message').find().count(); + const count = await src.collection('message').countDocuments(); await report({ progress: 1, message: `Messages: ${count}` }); const total = Math.floor(count / 50); for (let i = 0; i <= total; i++) { @@ -375,7 +375,7 @@ async function message(src: Db, report: Function) { } async function removeInvalidPid(report: Function) { - const count = await dst.collection('document').find({ docType: 10 }).count(); + const count = await dst.collection('document').countDocuments({ docType: 10 }); const bulk = dst.collection('document').initializeUnorderedBulkOp(); await report({ progress: 1, message: `Remove pid: ${count}` }); const total = Math.floor(count / 50); @@ -394,12 +394,11 @@ async function removeInvalidPid(report: Function) { } async function task(name: any, src: Db, report: Function) { - const count = await cursor[name](src).count(); - await report({ progress: 1, message: `${name}: ${count}` }); - const total = Math.floor(count / 50); + await report({ progress: 1, message: `${name}` }); let lastProgress = -1; - for (let i = 0; i <= total; i++) { + for (let i = 0; ; i++) { const docs = await cursor[name](src).skip(i * 50).limit(50).toArray(); + if (!docs.length) break; const res = []; for (const doc of docs) { let d: any = {}; @@ -459,10 +458,9 @@ async function task(name: any, src: Db, report: Function) { } } await Promise.all(res).catch((e) => report({ message: `${e}\n${e.stack}` })); - const progress = Math.round(100 * ((i + 1) / (total + 1))); - if (progress > lastProgress) { - await report({ progress }); - lastProgress = progress; + if (i > lastProgress) { + await report({ progress: i }); + lastProgress = i; } } } @@ -473,12 +471,10 @@ export async function run({ let mongourl = 'mongodb://'; if (username) mongourl += `${username}:${password}@`; mongourl += `${host}:${port}/${name}`; - const Database = await mongodb.MongoClient.connect(mongourl, { - useNewUrlParser: true, useUnifiedTopology: true, - }); + const Database = await mongodb.MongoClient.connect(mongourl, {}); const src = Database.db(name); await report({ progress: 0, message: 'Database connected.' }); - const userCounter = await src.collection('system').findOne({ _id: 'user_counter' }); + const userCounter = await src.collection('system').findOne({ _id: 'user_counter' }); if (!userCounter) { report({ message: 'No valid installation found' }); return false; diff --git a/packages/ui-default/backendlib/misc.ts b/packages/ui-default/backendlib/misc.ts index 23c599ca9..b6167a86e 100644 --- a/packages/ui-default/backendlib/misc.ts +++ b/packages/ui-default/backendlib/misc.ts @@ -1,5 +1,5 @@ import AnsiUp from 'ansi_up'; -import { moment, ObjectID } from 'hydrooj'; +import { moment, ObjectId } from 'hydrooj'; export { size, formatSeconds } from 'hydrooj'; @@ -10,10 +10,10 @@ export function ansiToHtml(str: string, whiteToBlack = true) { return whiteToBlack ? res.replace(/style="color:rgb\(255,255,255\)"/g, 'style="color:black"') : res; } -export function datetimeSpan(dt: Date | ObjectID, relative = true, format = 'YYYY-M-D H:mm:ss', tz = 'Asia/Shanghai') { +export function datetimeSpan(dt: Date | ObjectId, relative = true, format = 'YYYY-M-D H:mm:ss', tz = 'Asia/Shanghai') { if (!dt) return 'DATETIME_SPAN_ERROR'; - if (dt instanceof ObjectID) dt = dt.getTimestamp(); - else if (typeof dt === 'string' && ObjectID.isValid(dt)) dt = new Date(new ObjectID(dt).generationTime * 1000); + if (dt instanceof ObjectId) dt = dt.getTimestamp(); + else if (typeof dt === 'string' && ObjectId.isValid(dt)) dt = new ObjectId(dt).getTimestamp(); else if (typeof dt === 'number' || typeof dt === 'string') dt = new Date(dt); return '{2}'.format( relative ? ' relative' : '', diff --git a/packages/ui-default/index.ts b/packages/ui-default/index.ts index c4d115a98..fb9cebb17 100644 --- a/packages/ui-default/index.ts +++ b/packages/ui-default/index.ts @@ -1,6 +1,6 @@ /* eslint-disable global-require */ import { - ContestModel, Context, Handler, ObjectID, param, PERM, PRIV, ProblemModel, Schema, + ContestModel, Context, Handler, ObjectId, param, PERM, PRIV, ProblemModel, Schema, SettingModel, SystemModel, SystemSettings, Types, UserModel, } from 'hydrooj'; import convert from 'schemastery-jsonschema'; @@ -106,7 +106,7 @@ class RichMediaHandler extends Handler { async renderContest(domainId, payload) { const cur = payload.domainId ? await UserModel.getById(payload.domainId, this.user._id) : this.user; const tdoc = cur.hasPerm(PERM.PERM_VIEW | PERM.PERM_VIEW_CONTEST) - ? await ContestModel.get(payload.domainId || domainId, new ObjectID(payload.id)) + ? await ContestModel.get(payload.domainId || domainId, new ObjectId(payload.id)) : null; if (tdoc) return await this.renderHTML('partials/contest.html', { tdoc }); return ''; @@ -115,7 +115,7 @@ class RichMediaHandler extends Handler { async renderHomework(domainId, payload) { const cur = payload.domainId ? await UserModel.getById(payload.domainId, this.user._id) : this.user; const tdoc = cur.hasPerm(PERM.PERM_VIEW | PERM.PERM_VIEW_HOMEWORK) - ? await ContestModel.get(payload.domainId || domainId, new ObjectID(payload.id)) + ? await ContestModel.get(payload.domainId || domainId, new ObjectId(payload.id)) : null; if (tdoc) return await this.renderHTML('partials/homework.html', { tdoc }); return ''; diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 5fc0208a1..32f31ef2b 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -107,7 +107,7 @@ }, "dependencies": { "ansi_up": "^5.1.0", - "esbuild": "0.16.16", + "esbuild": "0.17.10", "fs-extra": "^11.1.0", "js-yaml": "^4.1.0", "jsesc": "^3.0.2", diff --git a/packages/utils/lib/utils.ts b/packages/utils/lib/utils.ts index 99deb1695..4b588d786 100644 --- a/packages/utils/lib/utils.ts +++ b/packages/utils/lib/utils.ts @@ -5,7 +5,7 @@ import { Duplex } from 'stream'; import { inspect } from 'util'; import fs from 'fs-extra'; import moment, { isMoment, Moment } from 'moment-timezone'; -import { ObjectID } from 'mongodb'; +import { ObjectId } from 'mongodb'; import Logger from 'reggol'; export * as yaml from 'js-yaml'; export * as fs from 'fs-extra'; @@ -128,7 +128,7 @@ export namespace Time { else if (isMoment(timestamp)) _timestamp = timestamp.toDate().getTime(); else _timestamp = timestamp.getTime(); const hexSeconds = Math.floor(_timestamp / 1000).toString(16); - return new ObjectID(`${hexSeconds}${allZero ? '0000000000000000' : new ObjectID().toHexString().substr(8)}`); + return new ObjectId(`${hexSeconds}${allZero ? '0000000000000000' : new ObjectId().toHexString().substr(8)}`); } } diff --git a/packages/utils/package.json b/packages/utils/package.json index 05fb66bb7..3c1190ba5 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -9,14 +9,14 @@ "preferUnplugged": true, "dependencies": { "cac": "^6.7.14", - "esbuild": "0.16.16", + "esbuild": "0.17.10", "fs-extra": "^11.1.0", "js-yaml": "^4.1.0", - "moment-timezone": "^0.5.40", - "mongodb": "^3.7.3", + "moment-timezone": "^0.5.41", + "mongodb": "^5.1.0", "reggol": "^1.3.5", "source-map-support": "^0.5.21", - "systeminformation": "^5.17.9" + "systeminformation": "^5.17.10" }, "devDependencies": { "@types/fs-extra": "^11.0.1", diff --git a/test/main.ts b/test/main.ts index 99095de82..4708c69fe 100644 --- a/test/main.ts +++ b/test/main.ts @@ -14,14 +14,19 @@ const Root = { describe('App', () => { let agent: supertest.SuperAgentTest; - before((done) => { - process.send = ((send) => (data) => { - if (data === 'ready') { - agent = supertest.agent(require('hydrooj').httpServer); - done(); - } - return send?.(data) || false; - })(process.send); + before(async () => { + const init = Date.now(); + await new Promise((resolve) => { + process.send = ((send) => (data) => { + console.log('send', data); + if (data === 'ready') { + agent = supertest.agent(require('hydrooj').httpServer); + resolve(null); + } + return send?.(data) || false; + })(process.send); + }); + console.log('Application inited in %d ms', Date.now() - init); }, { timeout: 30000 }); const routes = ['/', '/api', '/p', '/contest', '/homework', '/user/1', '/training'];