diff --git a/install/install.ts b/install/install.ts index b0be30f82..35fc5894d 100644 --- a/install/install.ts +++ b/install/install.ts @@ -38,7 +38,11 @@ const locales = { 'extra.dbUser': '数据库用户名: hydro', 'extra.dbPassword': '数据库密码: %s', 'info.skip': '步骤已跳过。', - 'warn.bt': '检测到宝塔面板,安装脚本很可能无法正常工作。建议您使用纯净的 Ubuntu 22.04 系统进行安装。', + 'error.bt': `检测到宝塔面板,安装脚本很可能无法正常工作。建议您使用纯净的 Ubuntu 22.04 系统进行安装。 +要忽略该警告,请使用 --shamefully-unsafe-bt-panel 参数重新运行此脚本。`, + 'warn.bt': `检测到宝塔面板,这会对系统安全性与稳定性造成影响。建议使用纯净 Ubuntu 22.04 系统进行安装。 +开发者对因为使用宝塔面板的数据丢失不承担任何责任。 +要取消安装,请使用 Ctrl-C 退出。安装程序将在五秒后继续。`, }, en: { 'install.start': 'Starting Hydro installation tool', @@ -57,7 +61,11 @@ const locales = { 'extra.dbUser': 'Database username: hydro', 'extra.dbPassword': 'Database password: %s', 'info.skip': 'Step skipped.', - 'warn.bt': 'BT-Panel detected, the installation script may not work properly. It is recommended to use a pure Ubuntu 22.04 OS.', + 'error.bt': `BT-Panel detected, this script may not work properly. It is recommended to use a pure Ubuntu 22.04 OS. +To ignore this warning, please run this script again with '--shamefully-unsafe-bt-panel' flag.`, + 'warn.bt': `BT-Panel detected, this will affect system security and stability. It is recommended to use a pure Ubuntu 22.04 OS. +The developer is not responsible for any data loss caused by using BT-Panel. +To cancel the installation, please use Ctrl-C to exit. The installation program will continue in five seconds.`, }, }; @@ -68,7 +76,7 @@ const installTarget = installAsJudge : 'hydrooj @hydrooj/hydrojudge @hydrooj/ui-default @hydrooj/fps-importer'; const addons = ['@hydrooj/ui-default', '@hydrooj/hydrojudge', '@hydrooj/fps-importer']; -let locale = process.env.LANG?.includes('zh') ? 'zh' : 'en'; +let locale = (process.env.LANG?.includes('zh') || process.env.LOCALE?.includes('zh')) ? 'zh' : 'en'; if (process.env.TERM === 'linux') locale = 'en'; const processLog = (orig) => (str, ...args) => (orig(locales[locale][str] || str, ...args), 0); const log = { @@ -224,8 +232,12 @@ const Steps = () => [ if (process.env.IGNORE_BT) return; const res = exec('bt default'); if (!res.code) { - log.warn('warn.bt'); - process.exit(1); + if (!process.argv.includes('--shamefully-unsafe-bt-panel')) { + log.warn('error.bt'); + process.exit(1); + } else { + log.warn('warn.bt'); + } } }, () => { @@ -323,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/blog/index.ts b/packages/blog/index.ts new file mode 100644 index 000000000..50f0d6377 --- /dev/null +++ b/packages/blog/index.ts @@ -0,0 +1,244 @@ +import { + _, Context, DiscussionNotFoundError, DocumentModel, Filter, + Handler, NumberKeys, ObjectId, OplogModel, paginate, + param, PRIV, Types, UserModel, +} from 'hydrooj'; + +export const TYPE_BLOG: 70 = 70; +export interface BlogDoc { + docType: 70; + docId: ObjectId; + owner: number; + title: string; + content: string; + ip: string; + updateAt: Date; + nReply: number; + views: number; + reply: any[]; + react: Record; +} +declare module 'hydrooj' { + interface Model { + blog: typeof BlogModel; + } + interface DocType { + [TYPE_BLOG]: BlogDoc; + } +} + +export class BlogModel { + static async add( + owner: number, title: string, content: string, ip?: string, + ): Promise { + const payload: Partial = { + content, + owner, + title, + ip, + nReply: 0, + updateAt: new Date(), + views: 0, + }; + const res = await DocumentModel.add( + 'system', payload.content!, payload.owner!, TYPE_BLOG, + null, null, null, _.omit(payload, ['domainId', 'content', 'owner']), + ); + payload.docId = res; + return payload.docId; + } + + static async get(did: ObjectId): Promise { + return await DocumentModel.get('system', TYPE_BLOG, did); + } + + static edit(did: ObjectId, title: string, content: string): Promise { + const payload = { title, content }; + return DocumentModel.set('system', TYPE_BLOG, did, payload); + } + + static inc(did: ObjectId, key: NumberKeys, value: number): Promise { + return DocumentModel.inc('system', TYPE_BLOG, did, key, value); + } + + static del(did: ObjectId): Promise { + return Promise.all([ + DocumentModel.deleteOne('system', TYPE_BLOG, did), + DocumentModel.deleteMultiStatus('system', TYPE_BLOG, { docId: did }), + ]) as any; + } + + static count(query: Filter) { + return DocumentModel.count('system', TYPE_BLOG, query); + } + + static getMulti(query: Filter = {}) { + return DocumentModel.getMulti('system', TYPE_BLOG, query) + .sort({ _id: -1 }); + } + + static async addReply(did: ObjectId, owner: number, content: string, ip: string): Promise { + const [[, drid]] = await Promise.all([ + DocumentModel.push('system', TYPE_BLOG, did, 'reply', content, owner, { ip }), + DocumentModel.incAndSet('system', TYPE_BLOG, did, 'nReply', 1, { updateAt: new Date() }), + ]); + return drid; + } + + static setStar(did: ObjectId, uid: number, star: boolean) { + return DocumentModel.setStatus('system', TYPE_BLOG, did, uid, { star }); + } + + static getStatus(did: ObjectId, uid: number) { + return DocumentModel.getStatus('system', TYPE_BLOG, did, uid); + } + + static setStatus(did: ObjectId, uid: number, $set) { + return DocumentModel.setStatus('system', TYPE_BLOG, did, uid, $set); + } +} + +global.Hydro.model.blog = BlogModel; + +class BlogHandler extends Handler { + ddoc?: BlogDoc; + + @param('did', Types.ObjectId, true) + async _prepare(domainId: string, did: ObjectId) { + if (did) { + this.ddoc = await BlogModel.get(did); + if (!this.ddoc) throw new DiscussionNotFoundError(domainId, did); + } + } +} + +class BlogUserHandler extends BlogHandler { + @param('uid', Types.Int) + @param('page', Types.PositiveInt, true) + async get(domainId: string, uid: number, page = 1) { + const [ddocs, dpcount] = await paginate( + BlogModel.getMulti({ owner: uid }), + page, + 10, + ); + const udoc = await UserModel.getById(domainId, uid); + this.response.template = 'blog_main.html'; + this.response.body = { + ddocs, + dpcount, + udoc, + page, + }; + } +} + +class BlogDetailHandler extends BlogHandler { + @param('did', Types.ObjectId) + async get(domainId: string, did: ObjectId) { + const dsdoc = this.user.hasPriv(PRIV.PRIV_USER_PROFILE) + ? await BlogModel.getStatus(did, this.user._id) + : null; + const udoc = await UserModel.getById(domainId, this.ddoc!.owner); + if (!dsdoc?.view) { + await Promise.all([ + BlogModel.inc(did, 'views', 1), + BlogModel.setStatus(did, this.user._id, { view: true }), + ]); + } + this.response.template = 'blog_detail.html'; + this.response.body = { + ddoc: this.ddoc, dsdoc, udoc, + }; + } + + async post() { + this.checkPriv(PRIV.PRIV_USER_PROFILE); + } + + @param('did', Types.ObjectId) + async postStar(domainId: string, did: ObjectId) { + await BlogModel.setStar(did, this.user._id, true); + this.back({ star: true }); + } + + @param('did', Types.ObjectId) + async postUnstar(domainId: string, did: ObjectId) { + await BlogModel.setStar(did, this.user._id, false); + this.back({ star: false }); + } +} + +class BlogEditHandler extends BlogHandler { + async get() { + this.response.template = 'blog_edit.html'; + this.response.body = { ddoc: this.ddoc }; + } + + @param('title', Types.Title) + @param('content', Types.Content) + async postCreate(domainId: string, title: string, content: string) { + await this.limitRate('add_blog', 3600, 60); + const did = await BlogModel.add(this.user._id, title, content, this.request.ip); + this.response.body = { did }; + this.response.redirect = this.url('blog_detail', { uid: this.user._id, did }); + } + + @param('did', Types.ObjectId) + @param('title', Types.Title) + @param('content', Types.Content) + 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([ + BlogModel.edit(did, title, content), + OplogModel.log(this, 'blog.edit', this.ddoc), + ]); + this.response.body = { did }; + this.response.redirect = this.url('blog_detail', { uid: this.user._id, did }); + } + + @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([ + BlogModel.del(did), + OplogModel.log(this, 'blog.delete', this.ddoc), + ]); + this.response.redirect = this.url('blog_main', { uid: this.ddoc!.owner }); + } +} + +export async function apply(ctx: Context) { + ctx.Route('blog_main', '/blog/:uid', BlogUserHandler); + ctx.Route('blog_create', '/blog/:uid/create', BlogEditHandler, PRIV.PRIV_USER_PROFILE); + ctx.Route('blog_detail', '/blog/:uid/:did', BlogDetailHandler); + ctx.Route('blog_edit', '/blog/:uid/:did/edit', BlogEditHandler, PRIV.PRIV_USER_PROFILE); + ctx.inject('UserDropdown', 'blog_main', { + icon: 'book', displayName: 'Blog', args: (h) => ({ uid: h.user._id }), + }, PRIV.PRIV_USER_PROFILE); + ctx.i18n.load('zh', { + "{0}'s blog": '{0} 的博客', + Blog: '博客', + blog_detail: '博客详情', + blog_edit: '编辑博客', + blog_main: '博客', + }); + ctx.i18n.load('zh_TW', { + "{0}'s blog": '{0} 的部落格', + Blog: '部落格', + blog_detail: '部落格詳情', + blog_edit: '編輯部落格', + blog_main: '部落格', + }); + ctx.i18n.load('kr', { + "{0}'s blog": '{0}의 블로그', + Blog: '블로그', + blog_main: '블로그', + blog_detail: '블로그 상세', + blog_edit: '블로그 수정', + }); + ctx.i18n.load('en', { + blog_main: 'Blog', + blog_detail: 'Blog Detail', + blog_edit: 'Edit Blog', + }); +} diff --git a/packages/blog/package.json b/packages/blog/package.json new file mode 100644 index 000000000..50d81c0fd --- /dev/null +++ b/packages/blog/package.json @@ -0,0 +1,5 @@ +{ + "name": "@hydrooj/blog", + "version": "0.0.1", + "license": "AGPL-3.0-or-later" +} diff --git a/packages/ui-default/templates/blog_detail.html b/packages/blog/templates/blog_detail.html similarity index 100% rename from packages/ui-default/templates/blog_detail.html rename to packages/blog/templates/blog_detail.html diff --git a/packages/ui-default/templates/blog_edit.html b/packages/blog/templates/blog_edit.html similarity index 100% rename from packages/ui-default/templates/blog_edit.html rename to packages/blog/templates/blog_edit.html diff --git a/packages/ui-default/templates/blog_main.html b/packages/blog/templates/blog_main.html similarity index 100% rename from packages/ui-default/templates/blog_main.html rename to packages/blog/templates/blog_main.html 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 0ea5b6a29..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); @@ -155,7 +155,8 @@ export default class Hydro { getNext(t: JudgeTask) { return (data: Partial) => { log.debug('Next: %o', data); - if (getConfig('performance') && data.case && !data.compilerText && !data.message) { + const performanceMode = getConfig('performance') || t.meta.rejudge || t.meta.hackRejudge; + if (performanceMode && data.case && !data.compilerText && !data.message) { t.callbackCache ||= []; t.callbackCache.push(data.case); } else { diff --git a/packages/hydrooj/locales/ko.yaml b/packages/hydrooj/locales/ko.yaml index eddfe3118..988e69b7b 100644 --- a/packages/hydrooj/locales/ko.yaml +++ b/packages/hydrooj/locales/ko.yaml @@ -84,7 +84,6 @@ Begin Time: 시작 시각 Belongs to: 소유 Bio Visibility: 소개 공개 Bio: 소개 -Blog: Blog Bold: 굵게 Boom!: 펑! Browser: 브라우저 @@ -141,6 +140,7 @@ contest_detail_problem_submit: 대회 상세 문제 제출 contest_edit: 대회 수정 contest_main: 대회 contest_scoreboard: 대회 점수판 +contest: 대회 Contest: 대회 Continue: 계속 Contributed Problems: 기여한 문제 diff --git a/packages/hydrooj/locales/zh.yaml b/packages/hydrooj/locales/zh.yaml index dc1c0cb28..36935acf9 100644 --- a/packages/hydrooj/locales/zh.yaml +++ b/packages/hydrooj/locales/zh.yaml @@ -97,10 +97,6 @@ Begin Time: 开始时间 Belongs to: 属于 Bio Visibility: 个人简介可见性 Bio: 个人简介 -blog_detail: 博客详情 -blog_edit: 编辑博客 -blog_main: 博客 -Blog: 博客 Bold: 加粗 Boom!: 炸了! Browser: 浏览器 @@ -165,6 +161,7 @@ contest_manage: 比赛管理 contest_problemlist: 题目列表 contest_scoreboard: 比赛成绩表 contest_user: 参赛者管理 +contest: 比赛 Contest: 比赛 Contests per page: 每页展示的比赛数量 Continue: 继续 diff --git a/packages/hydrooj/locales/zh_TW.yaml b/packages/hydrooj/locales/zh_TW.yaml index 8219f7490..51b604ef2 100644 --- a/packages/hydrooj/locales/zh_TW.yaml +++ b/packages/hydrooj/locales/zh_TW.yaml @@ -50,7 +50,6 @@ Begin Time: 開始時間 Belongs to: 屬於 Bio Visibility: 個人簡介可見性 Bio: 個人簡介 -Blog: 部落格 Boom!: 炸了! Browser: 瀏覽器 Built-in: 內建 @@ -86,6 +85,7 @@ contest_detail_problem_submit: 遞交比賽程式碼 contest_edit: 編輯比賽 contest_main: 比賽 contest_scoreboard: 比賽成績表 +contest: 比賽 Contest: 比賽 Create a contest: 創建一個比賽 Create a Discussion: 創建一個討論 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 deleted file mode 100644 index 8afe4e2fe..000000000 --- a/packages/hydrooj/src/handler/blog.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { ObjectID } from 'mongodb'; -import { DiscussionNotFoundError } from '../error'; -import { BlogDoc } from '../interface'; -import paginate from '../lib/paginate'; -import * as blog from '../model/blog'; -import { PRIV } from '../model/builtin'; -import * as oplog from '../model/oplog'; -import * as system from '../model/system'; -import user from '../model/user'; -import { Handler, param, Types } from '../service/server'; - -class BlogHandler extends Handler { - ddoc?: BlogDoc; - - @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); - } - } -} - -class BlogUserHandler extends BlogHandler { - @param('uid', Types.Int) - @param('page', Types.PositiveInt, true) - async get(domainId: string, uid: number, page = 1) { - const [ddocs, dpcount] = await paginate( - blog.getMulti({ owner: uid }), - page, - 10, - ); - const udoc = await user.getById(domainId, uid); - this.response.template = 'blog_main.html'; - this.response.body = { - ddocs, - dpcount, - udoc, - page, - }; - } -} - -class BlogDetailHandler extends BlogHandler { - @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; - const udoc = await user.getById(domainId, this.ddoc.owner); - if (!dsdoc?.view) { - await Promise.all([ - blog.inc(did, 'views', 1), - blog.setStatus(did, this.user._id, { view: true }), - ]); - } - this.response.template = 'blog_detail.html'; - this.response.body = { - ddoc: this.ddoc, dsdoc, udoc, - }; - } - - async post() { - this.checkPriv(PRIV.PRIV_USER_PROFILE); - } - - @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) { - await blog.setStar(did, this.user._id, false); - this.back({ star: false }); - } -} - -class BlogEditHandler extends BlogHandler { - async get() { - this.response.template = 'blog_edit.html'; - this.response.body = { ddoc: this.ddoc }; - } - - @param('title', Types.Title) - @param('content', Types.Content) - async postCreate(domainId: string, title: string, content: string) { - await this.limitRate('add_blog', 3600, 60); - const did = await blog.add(this.user._id, title, content, this.request.ip); - this.response.body = { did }; - this.response.redirect = this.url('blog_detail', { uid: this.user._id, did }); - } - - @param('did', Types.ObjectID) - @param('title', Types.Title) - @param('content', Types.Content) - 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), - oplog.log(this, 'blog.edit', this.ddoc), - ]); - this.response.body = { did }; - this.response.redirect = this.url('blog_detail', { uid: this.user._id, did }); - } - - @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), - oplog.log(this, 'blog.delete', this.ddoc), - ]); - this.response.redirect = this.url('blog_main', { uid: this.ddoc.owner }); - } -} - -export async function apply(ctx) { - if (!system.get('server.blog')) return; - ctx.Route('blog_main', '/blog/:uid', BlogUserHandler); - ctx.Route('blog_create', '/blog/:uid/create', BlogEditHandler, PRIV.PRIV_USER_PROFILE); - ctx.Route('blog_detail', '/blog/:uid/:did', BlogDetailHandler); - ctx.Route('blog_edit', '/blog/:uid/:did/edit', BlogEditHandler, PRIV.PRIV_USER_PROFILE); -} diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index 6d7463b15..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,10 +392,10 @@ 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[] = null, allowViewCode = false, + contestDuration: number = null, maintainer: number[] = [], allowViewCode = false, ) { if (autoHide) this.checkPerm(PERM.PERM_EDIT_PROBLEM); const pids = _pids.replace(/,/g, ',').split(',').map((i) => +i).filter((i) => i); @@ -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 d9b06c953..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) @@ -154,12 +154,13 @@ class HomeworkEditHandler extends Handler { @param('content', Types.Content) @param('pids', Types.Content) @param('rated', Types.Boolean) + @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, - assign: string[] = [], + maintainer: number[] = [], assign: string[] = [], ) { const pids = _pids.replace(/,/g, ',').split(',').map((i) => +i).filter((i) => i); const tdoc = tid ? await contest.get(domainId, tid) : null; @@ -188,6 +189,7 @@ class HomeworkEditHandler extends Handler { penaltySince: penaltySince.toDate(), penaltyRules, rated, + maintainer, assign, }); if (tdoc.beginAt !== beginAt.toDate() @@ -201,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 e97b1fd94..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(); @@ -441,8 +441,8 @@ export class ProblemDetailHandler extends ContestDetailBaseHandler { const priority = await record.submissionPriority(this.user._id, -10000 - rdocs.length * 5 - 50); await record.reset(domainId, rdocs.map((rdoc) => rdoc._id), true); await Promise.all([ - record.judge(domainId, rdocs.filter((i) => i.contest).map((i) => i._id), priority, { detail: false }), - record.judge(domainId, rdocs.filter((i) => !i.contest).map((i) => i._id), priority, {}), + record.judge(domainId, rdocs.filter((i) => i.contest).map((i) => i._id), priority, { detail: false }, { rejudge: true }), + record.judge(domainId, rdocs.filter((i) => !i.contest).map((i) => i._id), priority, {}, { rejudge: true }), ]); this.back(); } @@ -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 3b1af5fb3..e1d0d880a 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 } @@ -336,12 +336,13 @@ export interface RecordDoc { export interface JudgeMeta { problemOwner: number; hackRejudge?: string; + rejudge?: boolean; } export interface JudgeRequest extends Omit { priority: number; type: 'judge'; - rid: ObjectID; + rid: ObjectId; config: ProblemConfigFile; meta: JudgeMeta; data: FileInfo[]; @@ -368,7 +369,7 @@ export interface TrainingNode { } export interface Tdoc extends Document { - docId: ObjectID; + docId: ObjectId; docType: docType & number; beginAt: Date; endAt: Date; @@ -421,7 +422,7 @@ export interface DomainDoc extends Record { // Message export interface MessageDoc { - _id: ObjectID, + _id: ObjectId, from: number, to: number, content: string, @@ -443,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; @@ -465,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[]; @@ -477,7 +478,7 @@ export interface DiscussionReplyDoc extends Document { } export interface DiscussionTailReplyDoc { - _id: ObjectID; + _id: ObjectId; owner: number; content: string; ip: string; @@ -485,20 +486,6 @@ export interface DiscussionTailReplyDoc { editor?: number; } -export interface BlogDoc { - docType: document['TYPE_BLOG']; - docId: ObjectID; - owner: number; - title: string; - content: string; - ip: string; - updateAt: Date; - nReply: number; - views: number; - reply: any[]; - react: Record; -} - export interface TokenDoc { _id: string, tokenType: number, @@ -509,7 +496,7 @@ export interface TokenDoc { } export interface OplogDoc extends Record { - _id: ObjectID, + _id: ObjectId, type: string, } @@ -540,9 +527,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; @@ -563,7 +550,7 @@ export interface JudgeMessage { export interface JudgeResultBody { key: string; domainId: string; - rid: ObjectID; + rid: ObjectId; judger?: number; progress?: number; addProgress?: number; @@ -581,7 +568,7 @@ export interface JudgeResultBody { } export interface Task { - _id: ObjectID; + _id: ObjectId; type: string; subType?: string; priority: number; @@ -589,7 +576,7 @@ export interface Task { } export interface Schedule { - _id: ObjectID; + _id: ObjectId; type: string; subType?: string; executeAfter: Date; @@ -621,7 +608,7 @@ export interface EventDoc { } export interface OpCountDoc { - _id: ObjectID; + _id: ObjectId; op: string; ident: string; expireAt: Date; @@ -639,7 +626,7 @@ export interface DiscussionHistoryDoc { title?: string; content: string; domainId: string; - docId: ObjectID; + docId: ObjectId; /** Create time */ time: Date; uid: number; @@ -676,7 +663,6 @@ declare module './service/db' { export interface Model { blacklist: typeof import('./model/blacklist').default, - blog: typeof import('./model/blog'), builtin: typeof import('./model/builtin'), contest: typeof import('./model/contest'), discussion: typeof import('./model/discussion'), @@ -729,7 +715,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 deleted file mode 100644 index ab3f6855b..000000000 --- a/packages/hydrooj/src/model/blog.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { omit } from 'lodash'; -import { FilterQuery, ObjectID } from 'mongodb'; -import { BlogDoc } from '../interface'; -import { NumberKeys } from '../typeutils'; -import * as document from './document'; - -export async function add( - owner: number, title: string, content: string, - ip: string | null = null, -): Promise { - const payload: Partial = { - content, - owner, - title, - ip, - nReply: 0, - updateAt: new Date(), - views: 0, - }; - const res = await document.add( - 'system', payload.content!, payload.owner!, document.TYPE_BLOG, - null, null, null, omit(payload, ['domainId', 'content', 'owner']), - ); - payload.docId = res; - return payload.docId; -} - -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 { - const payload = { title, content }; - return document.set('system', document.TYPE_BLOG, did, payload); -} - -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 { - 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) { - return document.count('system', document.TYPE_BLOG, query); -} - -export function getMulti(query: FilterQuery = {}) { - return document.getMulti('system', document.TYPE_BLOG, query) - .sort({ _id: -1 }); -} - -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() }), - ]); - return drid; -} - -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) { - return document.getStatus('system', document.TYPE_BLOG, did, uid); -} - -export function setStatus(did: ObjectID, uid: number, $set) { - return document.setStatus('system', document.TYPE_BLOG, did, uid, $set); -} - -global.Hydro.model.blog = { - add, - get, - inc, - edit, - del, - count, - getMulti, - addReply, - setStar, - getStatus, - setStatus, -}; 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..9987308e1 100644 --- a/packages/hydrooj/src/model/document.ts +++ b/packages/hydrooj/src/model/document.ts @@ -1,11 +1,11 @@ /* 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 { - BlogDoc, Content, DiscussionDoc, + Content, DiscussionDoc, DiscussionReplyDoc, ProblemDoc, ProblemStatusDoc, Tdoc, TrainingDoc, } from '../interface'; @@ -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'); @@ -30,7 +30,6 @@ export const TYPE_CONTEST: 30 = 30; export const TYPE_TRAINING: 40 = 40; /** @deprecated use `TYPE_CONTEST` with rule `homework` instead. */ export const TYPE_HOMEWORK: 60 = 60; -export const TYPE_BLOG: 70 = 70; export interface DocType { [TYPE_PROBLEM]: ProblemDoc; @@ -41,7 +40,6 @@ export interface DocType { [TYPE_DISCUSSION_REPLY]: DiscussionReplyDoc; [TYPE_CONTEST]: Tdoc; [TYPE_TRAINING]: TrainingDoc; - [TYPE_BLOG]: BlogDoc; } export interface DocStatusType { @@ -60,14 +58,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 +102,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 +124,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 +168,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 +200,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 +218,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 +304,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 +337,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 +356,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 } }, @@ -495,5 +497,4 @@ global.Hydro.model.document = { TYPE_PROBLEM_LIST, TYPE_PROBLEM_SOLUTION, TYPE_TRAINING, - TYPE_BLOG, }; 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 85ba8bc7c..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,15 +111,15 @@ 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' | 'contest' | 'pretest' | 'hack', + type: 'judge' | 'rejudge' | 'contest' | 'pretest' | 'hack', } = { type: 'judge' }, ) { const data: RecordDoc = { status: STATUS.STATUS_WAITING, - _id: new ObjectID(), + _id: new ObjectId(), uid, code, lang, @@ -138,14 +137,17 @@ export default class RecordModel { }; if (args.contest) data.contest = args.contest; if (args.files) data.files = args.files; - if (args.type === 'pretest') { + if (args.type === 'rejudge') { + args.type = 'judge'; + 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) { const priority = await RecordModel.submissionPriority(uid, args.type === 'pretest' ? -20 : (args.type === 'contest' ? 50 : 0)); - await RecordModel.judge(domainId, res.insertedId, priority, args.type === 'contest' ? { detail: false } : {}); + await RecordModel.judge(domainId, res.insertedId, priority, args.type === 'contest' ? { detail: false } : {}, { rejudge: data.rejudged }); } return res.insertedId; } @@ -156,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; @@ -183,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; @@ -196,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, @@ -214,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/setting.ts b/packages/hydrooj/src/model/setting.ts index 710a8ba6f..4d51f8c28 100644 --- a/packages/hydrooj/src/model/setting.ts +++ b/packages/hydrooj/src/model/setting.ts @@ -1,11 +1,12 @@ /* eslint-disable max-len */ /* eslint-disable no-await-in-loop */ +import cordis from 'cordis'; import yaml from 'js-yaml'; import { Dictionary } from 'lodash'; import moment from 'moment-timezone'; import { LangConfig, parseLang } from '@hydrooj/utils/lib/lang'; import { retry } from '@hydrooj/utils/lib/utils'; -import { Context } from '../context'; +import { Context, Service } from '../context'; import { Setting as _Setting } from '../interface'; import { Logger } from '../logger'; import * as builtin from './builtin'; @@ -75,6 +76,17 @@ export const PreferenceSetting = (...settings: _Setting[]) => { SETTINGS.push(setting); SETTINGS_BY_KEY[setting.key] = setting; } + return () => { + for (const setting of settings) { + delete SETTINGS_BY_KEY[setting.key]; + if (PREFERENCE_SETTINGS.includes(setting)) { + PREFERENCE_SETTINGS.splice(PREFERENCE_SETTINGS.indexOf(setting), 1); + } + if (SETTINGS.includes(setting)) { + SETTINGS.splice(SETTINGS.indexOf(setting), 1); + } + } + }; }; export const AccountSetting = (...settings: _Setting[]) => { for (const setting of settings) { @@ -83,6 +95,17 @@ export const AccountSetting = (...settings: _Setting[]) => { SETTINGS.push(setting); SETTINGS_BY_KEY[setting.key] = setting; } + return () => { + for (const setting of settings) { + delete SETTINGS_BY_KEY[setting.key]; + if (ACCOUNT_SETTINGS.includes(setting)) { + ACCOUNT_SETTINGS.splice(ACCOUNT_SETTINGS.indexOf(setting), 1); + } + if (SETTINGS.includes(setting)) { + SETTINGS.splice(SETTINGS.indexOf(setting), 1); + } + } + }; }; export const DomainUserSetting = (...settings: _Setting[]) => { for (const setting of settings) { @@ -90,6 +113,14 @@ export const DomainUserSetting = (...settings: _Setting[]) => { DOMAIN_USER_SETTINGS.push(setting); DOMAIN_USER_SETTINGS_BY_KEY[setting.key] = setting; } + return () => { + for (const setting of settings) { + delete DOMAIN_USER_SETTINGS_BY_KEY[setting.key]; + if (DOMAIN_USER_SETTINGS.includes(setting)) { + DOMAIN_USER_SETTINGS.splice(DOMAIN_USER_SETTINGS.indexOf(setting), 1); + } + } + }; }; export const DomainSetting = (...settings: _Setting[]) => { for (const setting of settings) { @@ -97,6 +128,14 @@ export const DomainSetting = (...settings: _Setting[]) => { DOMAIN_SETTINGS.push(setting); DOMAIN_SETTINGS_BY_KEY[setting.key] = setting; } + return () => { + for (const setting of settings) { + delete DOMAIN_SETTINGS_BY_KEY[setting.key]; + if (DOMAIN_SETTINGS.includes(setting)) { + DOMAIN_SETTINGS.splice(DOMAIN_SETTINGS.indexOf(setting), 1); + } + } + }; }; export const SystemSetting = (...settings: _Setting[]) => { for (const setting of settings) { @@ -104,6 +143,14 @@ export const SystemSetting = (...settings: _Setting[]) => { SYSTEM_SETTINGS.push(setting); SYSTEM_SETTINGS_BY_KEY[setting.key] = setting; } + return () => { + for (const setting of settings) { + delete SYSTEM_SETTINGS_BY_KEY[setting.key]; + if (SYSTEM_SETTINGS.includes(setting)) { + SYSTEM_SETTINGS.splice(SYSTEM_SETTINGS.indexOf(setting), 1); + } + } + }; }; const LangSettingNode = { @@ -207,7 +254,6 @@ SystemSetting( Setting('setting_server', 'server.language', 'zh_CN', langRange, 'server.language', 'Default display language'), Setting('setting_server', 'server.login', true, 'boolean', 'server.login', 'Allow builtin-login', FLAG_PRO), Setting('setting_server', 'server.message', true, 'boolean', 'server.message', 'Allow users send messages'), - Setting('setting_server', 'server.blog', true, 'boolean', 'server.blog', 'Allow users post blog'), Setting('setting_server', 'server.checkUpdate', true, 'boolean', 'server.checkUpdate', 'Daily update check'), Setting('setting_server', 'server.ignoreUA', ignoreUA, 'textarea', 'server.ignoreUA', 'ignoredUA'), ServerLangSettingNode, @@ -273,8 +319,34 @@ export async function apply(ctx: Context) { }); } +declare module '../context' { + interface Context { + setting: SettingService; + } +} + +const T = any>(origFunc: F, disposeFunc?) => + function method(this: cordis.Service, ...args: Parameters) { + const res = origFunc(...args); + this.caller?.on('dispose', () => (disposeFunc ? disposeFunc(res) : res())); + }; + +export class SettingService extends Service { + static readonly methods = ['PreferenceSetting', 'AccountSetting', 'DomainSetting', 'DomainUserSetting', 'SystemSetting']; + PreferenceSetting = T(PreferenceSetting); + AccountSetting = T(AccountSetting); + DomainSetting = T(DomainSetting); + DomainUserSetting = T(DomainUserSetting); + SystemSetting = T(SystemSetting); + constructor(ctx: Context) { + super(ctx, 'setting', true); + } +} +Context.service('setting', SettingService); + global.Hydro.model.setting = { apply, + SettingService, Setting, PreferenceSetting, AccountSetting, 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/plugin-api.ts b/packages/hydrooj/src/plugin-api.ts index cefc15f8f..26289ead7 100644 --- a/packages/hydrooj/src/plugin-api.ts +++ b/packages/hydrooj/src/plugin-api.ts @@ -12,7 +12,6 @@ export * as SystemModel from './model/system'; export * as TrainingModel from './model/training'; export * as OpcountModel from './model/opcount'; export * as OplogModel from './model/oplog'; -export * as BlogModel from './model/blog'; export * as SettingModel from './model/setting'; export * as DiscussionModel from './model/discussion'; export * as DocumentModel from './model/document'; 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/locales/ko.yaml b/packages/ui-default/locales/ko.yaml index a480d1651..2e8758716 100644 --- a/packages/ui-default/locales/ko.yaml +++ b/packages/ui-default/locales/ko.yaml @@ -87,7 +87,6 @@ Begin Time: 시작 시각 Belongs to: 소유 Bio Visibility: 소개 공개 Bio: 소개 -Blog: Blog Bold: 굵게 Boom!: 펑! Browser: 브라우저 diff --git a/packages/ui-default/locales/zh.yaml b/packages/ui-default/locales/zh.yaml index 95b425fb9..460def7a0 100644 --- a/packages/ui-default/locales/zh.yaml +++ b/packages/ui-default/locales/zh.yaml @@ -41,7 +41,6 @@ __langname: 简体中文 'Solved {0} problems, RP: {1} (No. {2})': '解决了 {0} 道题目,RP: {1} (No. {2})' 'The value `{1}` of {0} already exists.': '{0} 的值 `{1}` 已经存在。' 'Yes': 是 -"{0}'s blog": "{0} 的博客" "Effects only when Difficulty is not 'Use algorithm calculated'.": 仅当难度不为“使用算法计算”时才起效。 "Split by ', '.": 由“, ”或“,”分隔。 "What's file?": 什么是文件? @@ -81,7 +80,7 @@ All Submissions: 所有递交 All Training Plans: 所有训练计划 All Users: 所有用户 All: 全部 -Allow to maintain this contest: 允许管理此比赛 +Allow to maintain this {0}: 允许管理此{0} Allow user view others code after contest: 允许用户赛后查看其他人的代码 Always granted all privileges: 总是拥有所有权限 An error has occurred.: 发生了一个错误。 @@ -112,7 +111,6 @@ Begin Time: 开始时间 Belongs to: 属于 Bio Visibility: 个人简介可见性 Bio: 个人简介 -Blog: 博客 Bold: 加粗 Boom!: 炸了! Browser: 浏览器 @@ -406,6 +404,7 @@ home_preference: 偏好设置 home_security: 安全设置 homepage: 首页 Homework Introduction: 作业介绍 +Homework Maintainer: 作业管理员 Homework scoreboard is not visible.: 目前无法查看作业成绩表。 homework_create: 创建作业 homework_detail_problem_submit: 递交作业题目 diff --git a/packages/ui-default/locales/zh_TW.yaml b/packages/ui-default/locales/zh_TW.yaml index b83bbd57c..d9ce6715b 100644 --- a/packages/ui-default/locales/zh_TW.yaml +++ b/packages/ui-default/locales/zh_TW.yaml @@ -52,7 +52,6 @@ Begin Time: 開始時間 Belongs to: 屬於 Bio Visibility: 個人簡介可見性 Bio: 個人簡介 -Blog: 部落格 Boom!: 炸了! Browser: 瀏覽器 Built-in: 內建 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/ui-default/templates/contest_edit.html b/packages/ui-default/templates/contest_edit.html index 07dce57db..7d6f73b3e 100644 --- a/packages/ui-default/templates/contest_edit.html +++ b/packages/ui-default/templates/contest_edit.html @@ -87,7 +87,7 @@ columns:12, label:'Contest Maintainer', name:'maintainer', - help_text:_('Allow to maintain this contest'), + help_text:_('Allow to maintain this {0}').format(_('contest')), value:tdoc.maintainer|default('') }) }}
diff --git a/packages/ui-default/templates/homework_edit.html b/packages/ui-default/templates/homework_edit.html index 6f26e4b67..f57760480 100644 --- a/packages/ui-default/templates/homework_edit.html +++ b/packages/ui-default/templates/homework_edit.html @@ -6,7 +6,7 @@
{{ form.form_text({ - columns:8, + columns:12, label:'Title', name:'title', placeholder:_('title'), @@ -15,7 +15,7 @@ }) }}
{{ form.form_text({ - columns:4, + columns:3, label:'Begin Date', name:'beginAtDate', placeholder:'YYYY-mm-dd', @@ -24,8 +24,7 @@ row:false }) }} {{ form.form_text({ - columns:4, - last_column:true, + columns:2, label:'Begin Time', name:'beginAtTime', placeholder:'HH:MM', @@ -33,9 +32,8 @@ time:true, row:false }) }} -
{{ form.form_text({ - columns:4, + columns:3, label:'End Date', name:'penaltySinceDate', placeholder:'YYYY-mm-dd', @@ -44,8 +42,7 @@ row:false }) }} {{ form.form_text({ - columns:4, - last_column:true, + columns:2, label:'End Time', name:'penaltySinceTime', placeholder:'HH:MM', @@ -53,22 +50,33 @@ time:true, row:false }) }} -
-
{{ form.form_text({ row:false, - columns:4, + last_column:true, + columns:2, label:'Extension (days)', name:'extensionDays', value:extensionDays }) }} +
+
{{ form.form_text({ - columns:4, + row:false, + columns:6, label:'Assign', name:'assign', placeholder:_('Group / UID'), value:tdoc.assign|default('') }) }} + {{ form.form_text({ + row:false, + last_column:true, + columns:6, + label:'Homework Maintainer', + name:'maintainer', + help_text:_('Allow to maintain this {0}').format(_('homework')), + value:tdoc.maintainer|default('') + }) }}