mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
Merge branch 'master' into scoreboard_ui
This commit is contained in:
75 files changed
+967
-856
No files matched your search
+20
-10
@@ -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' }],
|
||||
|
||||
+7
-7
@@ -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",
|
||||
|
||||
@@ -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<string, number>;
|
||||
}
|
||||
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<ObjectId> {
|
||||
const payload: Partial<BlogDoc> = {
|
||||
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<BlogDoc> {
|
||||
return await DocumentModel.get('system', TYPE_BLOG, did);
|
||||
}
|
||||
|
||||
static edit(did: ObjectId, title: string, content: string): Promise<BlogDoc> {
|
||||
const payload = { title, content };
|
||||
return DocumentModel.set('system', TYPE_BLOG, did, payload);
|
||||
}
|
||||
|
||||
static inc(did: ObjectId, key: NumberKeys<BlogDoc>, value: number): Promise<BlogDoc | null> {
|
||||
return DocumentModel.inc('system', TYPE_BLOG, did, key, value);
|
||||
}
|
||||
|
||||
static del(did: ObjectId): Promise<never> {
|
||||
return Promise.all([
|
||||
DocumentModel.deleteOne('system', TYPE_BLOG, did),
|
||||
DocumentModel.deleteMultiStatus('system', TYPE_BLOG, { docId: did }),
|
||||
]) as any;
|
||||
}
|
||||
|
||||
static count(query: Filter<BlogDoc>) {
|
||||
return DocumentModel.count('system', TYPE_BLOG, query);
|
||||
}
|
||||
|
||||
static getMulti(query: Filter<BlogDoc> = {}) {
|
||||
return DocumentModel.getMulti('system', TYPE_BLOG, query)
|
||||
.sort({ _id: -1 });
|
||||
}
|
||||
|
||||
static async addReply(did: ObjectId, owner: number, content: string, ip: string): Promise<ObjectId> {
|
||||
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',
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@hydrooj/blog",
|
||||
"version": "0.0.1",
|
||||
"license": "AGPL-3.0-or-later"
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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",
|
||||
|
||||
@@ -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<JudgeResultBody>) {
|
||||
data.rid = new ObjectID(rid);
|
||||
send(rid: string | ObjectId, key: 'next' | 'end', data: Partial<JudgeResultBody>) {
|
||||
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<JudgeResultBody>) => {
|
||||
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 {
|
||||
|
||||
@@ -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: 기여한 문제
|
||||
|
||||
@@ -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: 继续
|
||||
|
||||
@@ -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: 創建一個討論
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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('~')) {
|
||||
|
||||
@@ -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<any>('system');
|
||||
await Promise.all([
|
||||
coll.updateOne(
|
||||
{ _id: 'server.url' },
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<number, number> = {};
|
||||
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<number>(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 });
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<JudgeResultBody>) {
|
||||
}
|
||||
|
||||
export async function next(body: Partial<JudgeResultBody>) {
|
||||
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<JudgeResultBody>) {
|
||||
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);
|
||||
|
||||
@@ -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<ProblemDoc> = {};
|
||||
const q: Filter<ProblemDoc> = {};
|
||||
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<ProblemDoc>(buildProjection(projection)).toArray();
|
||||
pdocs.push(...docs);
|
||||
}
|
||||
this.response.body = uniqBy(pdocs, 'docId');
|
||||
|
||||
@@ -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<RecordDoc> = { contest: tid };
|
||||
const q: Filter<RecordDoc> = { 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<RecordDoc>(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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<Tdoc['dag'
|
||||
class TrainingMainHandler extends Handler {
|
||||
@param('page', Types.PositiveInt, true)
|
||||
async get(domainId: string, page = 1) {
|
||||
const query: FilterQuery<TrainingDoc> = {};
|
||||
const query: Filter<TrainingDoc> = {};
|
||||
await this.ctx.parallel('training/list', query, this);
|
||||
const [tdocs, tpcount] = await paginate(
|
||||
training.getMulti(domainId),
|
||||
page,
|
||||
system.get('pagination.training'),
|
||||
);
|
||||
const tids: Set<ObjectID> = new Set();
|
||||
const tids: Set<ObjectId> = 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<ObjectID> = new Set();
|
||||
const enrolledTids: Set<ObjectId> = 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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<any> {
|
||||
export interface Udoc extends Record<string, any> {
|
||||
_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<string, ContentNode[]>;
|
||||
|
||||
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<ProblemDoc>;
|
||||
|
||||
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<string, string>
|
||||
}
|
||||
@@ -336,12 +336,13 @@ export interface RecordDoc {
|
||||
export interface JudgeMeta {
|
||||
problemOwner: number;
|
||||
hackRejudge?: string;
|
||||
rejudge?: boolean;
|
||||
}
|
||||
|
||||
export interface JudgeRequest extends Omit<RecordDoc, '_id' | 'testCases'> {
|
||||
priority: number;
|
||||
type: 'judge';
|
||||
rid: ObjectID;
|
||||
rid: ObjectId;
|
||||
config: ProblemConfigFile;
|
||||
meta: JudgeMeta;
|
||||
data: FileInfo[];
|
||||
@@ -368,7 +369,7 @@ export interface TrainingNode {
|
||||
}
|
||||
|
||||
export interface Tdoc<docType = document['TYPE_CONTEST'] | document['TYPE_TRAINING']> extends Document {
|
||||
docId: ObjectID;
|
||||
docId: ObjectId;
|
||||
docType: docType & number;
|
||||
beginAt: Date;
|
||||
endAt: Date;
|
||||
@@ -421,7 +422,7 @@ export interface DomainDoc extends Record<string, any> {
|
||||
|
||||
// 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<string, number>;
|
||||
}
|
||||
|
||||
export interface TokenDoc {
|
||||
_id: string,
|
||||
tokenType: number,
|
||||
@@ -509,7 +496,7 @@ export interface TokenDoc {
|
||||
}
|
||||
|
||||
export interface OplogDoc extends Record<string, any> {
|
||||
_id: ObjectID,
|
||||
_id: ObjectId,
|
||||
type: string,
|
||||
}
|
||||
|
||||
@@ -540,9 +527,9 @@ export interface ContestRule<T = any> {
|
||||
) => Promise<ScoreboardRow>;
|
||||
scoreboard: (
|
||||
this: ContestRule<T>, isExport: boolean, _: (s: string) => string,
|
||||
tdoc: Tdoc<30>, pdict: ProblemDict, cursor: Cursor<ContestStat & T>,
|
||||
tdoc: Tdoc<30>, pdict: ProblemDict, cursor: FindCursor<ContestStat & T>,
|
||||
) => Promise<[board: ScoreboardRow[], udict: BaseUserDict]>;
|
||||
ranked: (tdoc: Tdoc<30>, cursor: Cursor<ContestStat & T>) => Promise<[number, ContestStat & T][]>;
|
||||
ranked: (tdoc: Tdoc<30>, cursor: FindCursor<ContestStat & T>) => Promise<[number, ContestStat & T][]>;
|
||||
}
|
||||
|
||||
export type ContestRules = Dictionary<ContestRule>;
|
||||
@@ -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<string, any> {
|
||||
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');
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { Cursor } from 'mongodb';
|
||||
import { FindCursor } from 'mongodb';
|
||||
import { ValidationError } from '../error';
|
||||
import db from '../service/db';
|
||||
|
||||
async function paginate<T>(
|
||||
cursor: Cursor<T>, page: number, pageSize: number,
|
||||
cursor: FindCursor<T>, 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<T>(
|
||||
}
|
||||
|
||||
export = paginate;
|
||||
|
||||
global.Hydro.lib.paginate = paginate;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Cursor } from 'mongodb';
|
||||
import { FindCursor } from 'mongodb';
|
||||
|
||||
type ranked = <T extends Record<string, any>>(cursor: Cursor<T>, equ: ((a: T, b: T) => boolean)) => Promise<[number, T][]>;
|
||||
type ranked = <T extends Record<string, any>>(cursor: FindCursor<T>, equ: ((a: T, b: T) => boolean)) => Promise<[number, T][]>;
|
||||
|
||||
const ranked: ranked = async (cursor, equ) => {
|
||||
let last = null;
|
||||
|
||||
@@ -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<string, any> | any[];
|
||||
@@ -35,7 +35,7 @@ export interface Types {
|
||||
Float: Type<number>;
|
||||
|
||||
// Other
|
||||
ObjectID: Type<ObjectID>;
|
||||
ObjectId: Type<ObjectId>;
|
||||
Boolean: Type<boolean>;
|
||||
Date: Type<string>;
|
||||
Time: Type<string>;
|
||||
@@ -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
|
||||
@@ -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 = <T = never>(args: {
|
||||
using?: keyof Context[];
|
||||
|
||||
@@ -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<ObjectID> {
|
||||
const payload: Partial<BlogDoc> = {
|
||||
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<BlogDoc> {
|
||||
return await document.get('system', document.TYPE_BLOG, did);
|
||||
}
|
||||
|
||||
export function edit(did: ObjectID, title: string, content: string): Promise<BlogDoc> {
|
||||
const payload = { title, content };
|
||||
return document.set('system', document.TYPE_BLOG, did, payload);
|
||||
}
|
||||
|
||||
export function inc(did: ObjectID, key: NumberKeys<BlogDoc>, value: number): Promise<BlogDoc | null> {
|
||||
return document.inc('system', document.TYPE_BLOG, did, key, value);
|
||||
}
|
||||
|
||||
export function del(did: ObjectID): Promise<never> {
|
||||
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<BlogDoc>) {
|
||||
return document.count('system', document.TYPE_BLOG, query);
|
||||
}
|
||||
|
||||
export function getMulti(query: FilterQuery<BlogDoc> = {}) {
|
||||
return document.getMulti('system', document.TYPE_BLOG, query)
|
||||
.sort({ _id: -1 });
|
||||
}
|
||||
|
||||
export async function addReply(did: ObjectID, owner: number, content: string, ip: string): Promise<ObjectID> {
|
||||
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,
|
||||
};
|
||||
@@ -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<Tdoc>) {
|
||||
export async function edit(domainId: string, tid: ObjectId, $set: Partial<Tdoc>) {
|
||||
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<Tdoc>)
|
||||
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<Tdoc<30>> {
|
||||
export async function get(domainId: string, tid: ObjectId): Promise<Tdoc<30>> {
|
||||
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<document.DocType['30']> = {},
|
||||
domainId: string, query: Filter<document.DocType['30']> = {},
|
||||
) {
|
||||
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);
|
||||
|
||||
@@ -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<ObjectID> {
|
||||
): Promise<ObjectId> {
|
||||
const time = new Date();
|
||||
const payload: Partial<DiscussionDoc> = {
|
||||
domainId,
|
||||
@@ -76,12 +76,12 @@ export async function add(
|
||||
}
|
||||
|
||||
export async function get<T extends Field>(
|
||||
domainId: string, did: ObjectID, projection: T[] = PROJECTION_PUBLIC as any,
|
||||
domainId: string, did: ObjectId, projection: T[] = PROJECTION_PUBLIC as any,
|
||||
): Promise<Pick<DiscussionDoc, T>> {
|
||||
return await document.get(domainId, document.TYPE_DISCUSSION, did, projection);
|
||||
}
|
||||
|
||||
export async function edit(domainId: string, did: ObjectID, $set: Partial<DiscussionDoc>) {
|
||||
export async function edit(domainId: string, did: ObjectId, $set: Partial<DiscussionDoc>) {
|
||||
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<Discus
|
||||
}
|
||||
|
||||
export function inc(
|
||||
domainId: string, did: ObjectID, key: NumberKeys<DiscussionDoc>, value: number,
|
||||
domainId: string, did: ObjectId, key: NumberKeys<DiscussionDoc>, value: number,
|
||||
): Promise<DiscussionDoc | null> {
|
||||
return document.inc(domainId, document.TYPE_DISCUSSION, did, key, value);
|
||||
}
|
||||
|
||||
export async function del(domainId: string, did: ObjectID): Promise<void> {
|
||||
export async function del(domainId: string, did: ObjectId): Promise<void> {
|
||||
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<void> {
|
||||
]) as any;
|
||||
}
|
||||
|
||||
export function count(domainId: string, query: FilterQuery<DiscussionDoc>) {
|
||||
export function count(domainId: string, query: Filter<DiscussionDoc>) {
|
||||
return document.count(domainId, document.TYPE_DISCUSSION, query);
|
||||
}
|
||||
|
||||
export function getMulti(domainId: string, query: FilterQuery<DiscussionDoc> = {}, projection = PROJECTION_LIST) {
|
||||
export function getMulti(domainId: string, query: Filter<DiscussionDoc> = {}, projection = PROJECTION_LIST) {
|
||||
return document.getMulti(domainId, document.TYPE_DISCUSSION, query)
|
||||
.sort({ pin: -1, docId: -1 })
|
||||
.project(buildProjection(projection));
|
||||
.project<DiscussionDoc>(buildProjection(projection));
|
||||
}
|
||||
|
||||
export async function addReply(
|
||||
domainId: string, did: ObjectID, owner: number,
|
||||
domainId: string, did: ObjectId, owner: number,
|
||||
content: string, ip: string,
|
||||
): Promise<ObjectID> {
|
||||
): Promise<ObjectId> {
|
||||
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<DiscussionReplyDoc | null> {
|
||||
export function getReply(domainId: string, drid: ObjectId): Promise<DiscussionReplyDoc | null> {
|
||||
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<DiscussionReplyDoc | null> {
|
||||
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<DiscussionReplyDoc[]> {
|
||||
export function getListReply(domainId: string, did: ObjectId): Promise<DiscussionReplyDoc[]> {
|
||||
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<DiscussionTailReplyDoc> {
|
||||
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<DiscussionHistoryDoc> = {},
|
||||
domainId: string, docId: ObjectId, query: Filter<DiscussionHistoryDoc> = {},
|
||||
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) {
|
||||
|
||||
@@ -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<O, P = any> = Exclude<ArrayKeys<O, P>, 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<T extends keyof DocType>(
|
||||
docType: T, docId: null,
|
||||
parentType?: DocType[T]['parentType'], parentId?: DocType[T]['parentId'],
|
||||
args?: Partial<DocType[T]>,
|
||||
): Promise<ObjectID>;
|
||||
): Promise<ObjectId>;
|
||||
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<K extends keyof DocType>(
|
||||
$push?: PushOperator<DocType[K]>,
|
||||
): Promise<DocType[K]> {
|
||||
await bus.parallel('document/set', domainId, docType, docId, $set, $unset);
|
||||
const update: UpdateQuery<DocType[K]> = {};
|
||||
const update: UpdateFilter<DocType[K]> = {};
|
||||
if ($set) update.$set = $set;
|
||||
if ($unset) update.$unset = $unset;
|
||||
if ($push) update.$push = $push;
|
||||
@@ -126,20 +124,20 @@ export function deleteOne<K extends keyof DocType>(
|
||||
}
|
||||
|
||||
export function deleteMulti<K extends keyof DocType>(
|
||||
domainId: string, docType: K, query?: FilterQuery<DocType[K]>,
|
||||
domainId: string, docType: K, query?: Filter<DocType[K]>,
|
||||
) {
|
||||
return coll.deleteMany({ ...query, domainId, docType });
|
||||
}
|
||||
|
||||
export function deleteMultiStatus<K extends keyof DocStatusType>(
|
||||
domainId: string, docType: K, query?: FilterQuery<DocStatusType[K]>,
|
||||
domainId: string, docType: K, query?: Filter<DocStatusType[K]>,
|
||||
) {
|
||||
return collStatus.deleteMany({ ...query, domainId, docType });
|
||||
}
|
||||
|
||||
export function getMulti<K extends keyof DocType>(
|
||||
domainId: string, docType: K, query?: FilterQuery<DocType[K]>, projection?: Projection<DocType[K]>,
|
||||
): Cursor<DocType[K]> {
|
||||
domainId: string, docType: K, query?: Filter<DocType[K]>, projection?: Projection<DocType[K]>,
|
||||
): FindCursor<DocType[K]> {
|
||||
let cursor = coll.find({ docType, domainId, ...query });
|
||||
if (projection) cursor = cursor.project(buildProjection(projection));
|
||||
return cursor;
|
||||
@@ -170,29 +168,30 @@ export async function incAndSet<K extends keyof DocType>(
|
||||
}
|
||||
|
||||
export function count<K extends keyof DocType>(
|
||||
domainId: string, docType: K, query?: FilterQuery<DocType[K]>,
|
||||
domainId: string, docType: K, query?: Filter<DocType[K]>,
|
||||
) {
|
||||
return coll.find({ ...query, docType, domainId }).count();
|
||||
return coll.countDocuments({ ...query, docType, domainId });
|
||||
}
|
||||
|
||||
export async function push<K extends keyof DocType>(
|
||||
domainId: string, docType: K, docId: DocType[K]['docId'],
|
||||
key: ArrayKeys<DocType[K]>, value: DocType[K][0],
|
||||
): Promise<[DocType[K], ObjectID]>;
|
||||
): Promise<[DocType[K], ObjectId]>;
|
||||
export async function push<K extends keyof DocType, T extends keyof DocType[K]>(
|
||||
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<K extends keyof DocType, T extends ArrayKeys<DocType[K]>>(
|
||||
domainId: string, docType: K, docId: DocType[K]['docId'],
|
||||
setKey: T, contents: FilterQuery<DocType[K][T][0]>,
|
||||
setKey: T, contents: Filter<DocType[K][T][0]>,
|
||||
): Promise<DocType[K]> {
|
||||
const res = await coll.findOneAndUpdate(
|
||||
{ domainId, docType, docId },
|
||||
// @ts-ignore
|
||||
{ $pull: { [setKey]: { $in: contents } } },
|
||||
{ returnDocument: 'after' },
|
||||
);
|
||||
@@ -218,6 +218,7 @@ export async function deleteSub<T extends keyof DocType, K extends ArrayKeys<Doc
|
||||
subId = (subId instanceof Array) ? subId : [subId];
|
||||
const res = await coll.findOneAndUpdate(
|
||||
{ domainId, docType, docId },
|
||||
// @ts-ignore
|
||||
{ $pull: { [key]: { _id: { $in: subId } } } },
|
||||
{ returnDocument: 'after' },
|
||||
);
|
||||
@@ -266,6 +267,7 @@ export async function addToSet<T extends keyof DocType, K extends ArrayKeys<DocT
|
||||
) {
|
||||
const res = await coll.findOneAndUpdate(
|
||||
{ domainId, docType, docId },
|
||||
// @ts-ignore
|
||||
{ $addToSet: { [setKey]: content } },
|
||||
{ returnDocument: 'after' },
|
||||
);
|
||||
@@ -279,19 +281,19 @@ export async function getStatus<K extends keyof DocStatusType>(
|
||||
}
|
||||
|
||||
export function getMultiStatus<K extends keyof DocStatusType>(
|
||||
domainId: string, docType: K, args: FilterQuery<DocStatusType[K]>,
|
||||
): Cursor<DocStatusType[K]> {
|
||||
domainId: string, docType: K, args: Filter<DocStatusType[K]>,
|
||||
): FindCursor<DocStatusType[K]> {
|
||||
return collStatus.find({ ...args, docType, domainId });
|
||||
}
|
||||
|
||||
export function getMultiStatusWithoutDomain<K extends keyof DocStatusType>(
|
||||
docType: K, args: FilterQuery<DocStatusType[K]>,
|
||||
): Cursor<DocStatusType[K]> {
|
||||
docType: K, args: Filter<DocStatusType[K]>,
|
||||
): FindCursor<DocStatusType[K]> {
|
||||
return collStatus.find({ ...args, docType });
|
||||
}
|
||||
|
||||
export async function setStatus<K extends keyof DocStatusType>(
|
||||
domainId: string, docType: K, docId: DocStatusType[K]['docId'], uid: number, args: UpdateQuery<DocStatusType[K]>['$set'],
|
||||
domainId: string, docType: K, docId: DocStatusType[K]['docId'], uid: number, args: UpdateFilter<DocStatusType[K]>['$set'],
|
||||
): Promise<DocStatusType[K]> {
|
||||
const res = await collStatus.findOneAndUpdate(
|
||||
{ domainId, docType, docId, uid },
|
||||
@@ -302,7 +304,7 @@ export async function setStatus<K extends keyof DocStatusType>(
|
||||
}
|
||||
|
||||
export async function setMultiStatus<K extends keyof DocStatusType>(
|
||||
domainId: string, docType: K, query: FilterQuery<DocStatusType[K]>, args: Partial<DocStatusType[K]>,
|
||||
domainId: string, docType: K, query: Filter<DocStatusType[K]>, args: Partial<DocStatusType[K]>,
|
||||
) {
|
||||
return await collStatus.updateMany(
|
||||
{ domainId, docType, ...query },
|
||||
@@ -335,7 +337,7 @@ export async function setIfNotStatus<T extends keyof DocStatusType, K extends ke
|
||||
|
||||
export async function setStatusIfNotCondition<T extends keyof DocStatusType>(
|
||||
domainId: string, docType: T, docId: DocStatusType[T]['docId'], uid: number,
|
||||
filter: FilterQuery<DocStatusType[T]>, args: Partial<DocStatusType[T]>,
|
||||
filter: Filter<DocStatusType[T]>, args: Partial<DocStatusType[T]>,
|
||||
): Promise<DocStatusType[T]> {
|
||||
const match = await collStatus.findOne({ domainId, docType, docId, uid, ...filter });
|
||||
if (match) return null;
|
||||
@@ -354,7 +356,7 @@ export async function cappedIncStatus<T extends keyof DocStatusType>(
|
||||
): Promise<DocStatusType[T]> {
|
||||
assert(value !== 0);
|
||||
const $not = value > 0 ? { $gte: maxValue } : { $lte: minValue };
|
||||
const operation = { $inc: { [key]: value } } as UpdateQuery<DocStatusType[T]>;
|
||||
const operation = { $inc: { [key]: value } } as UpdateFilter<DocStatusType[T]>;
|
||||
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,
|
||||
};
|
||||
@@ -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<DomainDoc> = { lower: domainId };
|
||||
const query: Filter<DomainDoc> = { 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<DomainDoc> = { host };
|
||||
const query: Filter<DomainDoc> = { 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<DomainDoc> = {}) {
|
||||
static getMulti(query: Filter<DomainDoc> = {}) {
|
||||
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 });
|
||||
}
|
||||
|
||||
@@ -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<MessageDoc> {
|
||||
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<MessageDoc | null> {
|
||||
static async get(_id: ObjectId) {
|
||||
return await coll.findOne({ _id });
|
||||
}
|
||||
|
||||
@ArgMethod
|
||||
static async getByUser(uid: number): Promise<MessageDoc[]> {
|
||||
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<MessageDoc>, sort: any, page: number, limit: number): Promise<MessageDoc[]> {
|
||||
static async getMany(query: Filter<MessageDoc>, 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<MessageDoc | null> {
|
||||
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<MessageDoc> = {}) {
|
||||
return coll.find(query).count();
|
||||
static count(query: Filter<MessageDoc> = {}) {
|
||||
return coll.countDocuments(query);
|
||||
}
|
||||
|
||||
static getMulti(uid: number) {
|
||||
|
||||
@@ -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<OplogDoc> & { type: string }): Promise<ObjectID> {
|
||||
export async function add(data: Partial<OplogDoc> & { type: string }): Promise<ObjectId> {
|
||||
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<T extends Handler>(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<T extends Handler>(handler: T, type: string, data: any
|
||||
return res.insertedId;
|
||||
}
|
||||
|
||||
export async function get(id: ObjectID): Promise<OplogDoc | null> {
|
||||
export async function get(id: ObjectId) {
|
||||
return await coll.findOne({ _id: id });
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ProblemDoc>, projection = ProblemModel.PROJECTION_LIST) {
|
||||
static getMulti(domainId: string, query: Filter<ProblemDoc>, projection = ProblemModel.PROJECTION_LIST) {
|
||||
return document.getMulti(domainId, document.TYPE_PROBLEM, query, projection).sort({ sort: 1 });
|
||||
}
|
||||
|
||||
static async list(
|
||||
domainId: string, query: FilterQuery<ProblemDoc>,
|
||||
domainId: string, query: Filter<ProblemDoc>,
|
||||
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<ProblemDoc>) {
|
||||
static getMultiStatus(domainId: string, query: Filter<ProblemStatusDoc>) {
|
||||
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<ProblemDoc>) {
|
||||
static count(domainId: string, query: Filter<ProblemDoc>) {
|
||||
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<ProblemDoc>) {
|
||||
const cursor = document.getMulti(domainId, document.TYPE_PROBLEM, query);
|
||||
const pcount = await cursor.count();
|
||||
static async random(domainId: string, query: Filter<ProblemDoc>) {
|
||||
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<string, ProblemDoc> = {};
|
||||
const q: any = { docId: { $in: pids } };
|
||||
let pdocs = await document.getMulti(domainId, document.TYPE_PROBLEM, q)
|
||||
.project(buildProjection(projection)).toArray();
|
||||
.project<ProblemDoc>(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<ProblemStatusDoc> = { rid: { $ne: rid }, status: STATUS.STATUS_ACCEPTED };
|
||||
const filter: Filter<ProblemStatusDoc> = { rid: { $ne: rid }, status: STATUS.STATUS_ACCEPTED };
|
||||
const res = await document.setStatusIfNotCondition(
|
||||
domainId, document.TYPE_PROBLEM, pid, uid,
|
||||
filter, { rid, status, score },
|
||||
|
||||
@@ -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<RecordDoc | null>;
|
||||
static async get(domainId: string, _id: ObjectID): Promise<RecordDoc | null>;
|
||||
static async get(arg0: string | ObjectID, arg1?: any) {
|
||||
static async get(_id: ObjectId): Promise<RecordDoc | null>;
|
||||
static async get(domainId: string, _id: ObjectId): Promise<RecordDoc | null>;
|
||||
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<ObjectID>, priority = 0, config: ProblemConfigFile = {}, meta: Partial<JudgeMeta> = {}) {
|
||||
static async judge(domainId: string, rids: MaybeArray<ObjectId>, priority = 0, config: ProblemConfigFile = {}, meta: Partial<JudgeMeta> = {}) {
|
||||
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<string, string>,
|
||||
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<ObjectID>,
|
||||
domainId: string, _id: MaybeArray<ObjectId>,
|
||||
$set?: MatchKeysAndValues<RecordDoc>,
|
||||
$push?: PushOperator<RecordDoc>,
|
||||
$unset?: OnlyFieldsOfType<RecordDoc, any, true | '' | 1>,
|
||||
$inc?: Partial<Record<NumberKeys<RecordDoc>, number>>,
|
||||
): Promise<RecordDoc | null> {
|
||||
const $update: UpdateQuery<RecordDoc> = {};
|
||||
const $update: UpdateFilter<RecordDoc> = {};
|
||||
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<RecordDoc>,
|
||||
domainId: string, $match: Filter<RecordDoc>,
|
||||
$set?: MatchKeysAndValues<RecordDoc>,
|
||||
$push?: PushOperator<RecordDoc>,
|
||||
$unset?: OnlyFieldsOfType<RecordDoc, any, true | '' | 1>,
|
||||
) {
|
||||
const $update: UpdateQuery<RecordDoc> = {};
|
||||
const $update: UpdateFilter<RecordDoc> = {};
|
||||
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<ObjectID>, isRejudge: boolean) {
|
||||
static async reset(domainId: string, rid: MaybeArray<ObjectId>, 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<Record<string, Partial<RecordDoc>>> {
|
||||
const r: Record<string, RecordDoc> = {};
|
||||
rids = Array.from(new Set(rids));
|
||||
|
||||
@@ -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<Schedule>) {
|
||||
async function getFirst(query: Filter<Schedule>) {
|
||||
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<Schedule>) {
|
||||
return coll.find(query).count();
|
||||
static count(query: Filter<Schedule>) {
|
||||
return coll.countDocuments(query);
|
||||
}
|
||||
|
||||
static del(_id: ObjectID) {
|
||||
static del(_id: ObjectId) {
|
||||
return coll.deleteOne({ _id });
|
||||
}
|
||||
|
||||
static deleteMany(query: FilterQuery<Schedule>) {
|
||||
static deleteMany(query: Filter<Schedule>) {
|
||||
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)) {
|
||||
|
||||
@@ -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 = <F extends (...args: any[]) => any>(origFunc: F, disposeFunc?) =>
|
||||
function method(this: cordis.Service, ...args: Parameters<F>) {
|
||||
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,
|
||||
|
||||
@@ -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<string, { docId: ObjectID, vote: number }> = {};
|
||||
static async getListStatus(domainId: string, psids: ObjectId[], uid: number) {
|
||||
const result: Record<string, { docId: ObjectId, vote: number }> = {};
|
||||
const res = await document.getMultiStatus(
|
||||
domainId, document.TYPE_PROBLEM_SOLUTION, { uid, docId: { $in: psids } },
|
||||
).project({ docId: 1, vote: 1 }).toArray();
|
||||
).project<any>({ docId: 1, vote: 1 }).toArray();
|
||||
for (const i of res) result[i.docId] = i;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -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<Task>) {
|
||||
async function getFirst(query: Filter<Task>) {
|
||||
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<Task>) {
|
||||
return coll.find(query).count();
|
||||
static count(query: Filter<Task>) {
|
||||
return coll.countDocuments(query);
|
||||
}
|
||||
|
||||
static del(_id: ObjectID) {
|
||||
static del(_id: ObjectId) {
|
||||
return coll.deleteOne({ _id });
|
||||
}
|
||||
|
||||
static deleteMany(query: FilterQuery<Task>) {
|
||||
static deleteMany(query: Filter<Task>) {
|
||||
return coll.deleteMany(query);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<TokenDoc> = {}) {
|
||||
static getMulti(tokenType: number, query: Filter<TokenDoc> = {}) {
|
||||
return TokenModel.coll.find({ tokenType, ...query });
|
||||
}
|
||||
|
||||
|
||||
@@ -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<TrainingDoc>) {
|
||||
export function getMultiStatus(domainId: string, query: Filter<TrainingDoc>) {
|
||||
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<TrainingDoc>) {
|
||||
export function edit(domainId: string, tid: ObjectId, $set: Partial<TrainingDoc>) {
|
||||
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> | number[], don
|
||||
export const isInvalid = (node: TrainingNode, doneNids: Set<number> | number[]) =>
|
||||
!Set.isSuperset(new Set(doneNids), new Set(node.requireNids));
|
||||
|
||||
export async function count(domainId: string, query: FilterQuery<TrainingDoc>) {
|
||||
export async function count(domainId: string, query: Filter<TrainingDoc>) {
|
||||
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<TrainingDoc> = {}) =>
|
||||
export const getMulti = (domainId: string, query: Filter<TrainingDoc> = {}) =>
|
||||
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();
|
||||
|
||||
@@ -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<VUdoc> = db.collection('vuser');
|
||||
export const collGroup: Collection<GDoc> = db.collection('user.group');
|
||||
const cache = new LRU<string, User>({ 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<Udoc> = {}, projection?: (keyof Udoc)[]) {
|
||||
return projection ? coll.find(params).project(buildProjection(projection)) : coll.find(params);
|
||||
static getMulti(params: Filter<Udoc> = {}, projection?: (keyof Udoc)[]) {
|
||||
return projection ? coll.find(params).project<Udoc>(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;
|
||||
|
||||
@@ -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<any>) {
|
||||
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<any>) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function iterateAllPsdoc(filter: FilterQuery<ProblemStatusDoc>, cb: (psdoc: ProblemStatusDoc) => Promise<any>) {
|
||||
export async function iterateAllPsdoc(filter: Filter<ProblemStatusDoc>, cb: (psdoc: ProblemStatusDoc) => Promise<any>) {
|
||||
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) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<string>, uset: Set<number>, dset: Set<ObjectID>,
|
||||
bset: Set<string>, uset: Set<number>, dset: Set<ObjectId>,
|
||||
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<string>, uset: Set<number>, dset: Set<ObjectID>,
|
||||
domainId: string, did: ObjectId,
|
||||
bset: Set<string>, uset: Set<number>, dset: Set<ObjectId>,
|
||||
dryrun: boolean, report: Function,
|
||||
) {
|
||||
if (dset.has(did)) return;
|
||||
@@ -41,7 +41,7 @@ async function _discussion(
|
||||
|
||||
async function _user(
|
||||
uid: number,
|
||||
bset: Set<string>, uset: Set<number>, dset: Set<ObjectID>,
|
||||
bset: Set<string>, uset: Set<number>, dset: Set<ObjectId>,
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<string, RpDef> = {
|
||||
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<Udoc> = {
|
||||
const query: Filter<Udoc> = {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<DomainDoc>) => VoidReturn
|
||||
'domain/before-get': (query: Filter<DomainDoc>) => VoidReturn
|
||||
'domain/get': (ddoc: DomainDoc) => VoidReturn
|
||||
'domain/before-update': (domainId: string, $set: Partial<DomainDoc>) => VoidReturn
|
||||
'domain/update': (domainId: string, $set: Partial<DomainDoc>, 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<ProblemDoc>, handler: any) => VoidReturn
|
||||
'problem/list': (query: Filter<ProblemDoc>, 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<FileInfo, '_id'>) => 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<Tdoc<30>>) => VoidReturn
|
||||
'contest/add': (payload: Partial<Tdoc<30>>, id: ObjectID) => VoidReturn
|
||||
'contest/add': (payload: Partial<Tdoc<30>>, 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<TrainingDoc>, handler: any) => VoidReturn
|
||||
'training/list': (query: Filter<TrainingDoc>, handler: any) => VoidReturn
|
||||
'training/get': (tdoc: TrainingDoc, handler: any) => VoidReturn
|
||||
|
||||
'record/change': (rdoc: RecordDoc, $set?: any, $push?: any) => void
|
||||
|
||||
@@ -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<K extends keyof Collections>(c: K): Collection<Collections[K]> {
|
||||
if (this.opts.prefix) return this.db.collection(`${this.opts.prefix}.${c}`);
|
||||
return this.db.collection(c);
|
||||
public collection<K extends keyof Collections>(c: K) {
|
||||
if (this.opts.prefix) return this.db.collection<Collections[K]>(`${this.opts.prefix}.${c}`);
|
||||
return this.db.collection<Collections[K]>(c);
|
||||
}
|
||||
|
||||
public async fixExpireAfter() {
|
||||
@@ -74,7 +72,7 @@ class MongoService {
|
||||
}
|
||||
}
|
||||
|
||||
public async ensureIndexes<T>(coll: Collection<T>, ...args: IndexSpecification[]) {
|
||||
public async ensureIndexes<T>(coll: Collection<T>, ...args: IndexDescription[]) {
|
||||
if (process.env.NODE_APP_INSTANCE !== '0') return;
|
||||
let existed: any[];
|
||||
try {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string, any> = {
|
||||
mid: mid.toString(),
|
||||
|
||||
@@ -12,6 +12,5 @@ export type Value<O, V = ''> = {
|
||||
};
|
||||
export type Projection<O> = readonly (string & keyof O)[];
|
||||
export type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
|
||||
export type Filter<T, U> = T extends U ? T : never;
|
||||
export type MaybeArray<T> = T | T[];
|
||||
export type UnionToIntersection<U> = (U extends any ? (arg: U) => void : never) extends ((arg: infer I) => void) ? I : never;
|
||||
@@ -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() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export function buildProjection(fields: readonly (string | number)[]): Record<string, 1> {
|
||||
const o = {};
|
||||
for (const k of fields) o[k] = 1;
|
||||
export function buildProjection<T extends string | number = string>(fields: readonly T[]): Record<T, true> {
|
||||
const o: Record<T, true> = {} as any;
|
||||
for (const k of fields) o[k] = true;
|
||||
return o;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
"author": "undefined <i@undefined.moe>",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<any>;
|
||||
type CursorGetter = (s: Db) => FindCursor<any>;
|
||||
|
||||
const cursor: NodeJS.Dict<CursorGetter> = {
|
||||
user: (s) => s.collection('user').find(),
|
||||
@@ -283,7 +283,7 @@ const cursor: NodeJS.Dict<CursorGetter> = {
|
||||
};
|
||||
|
||||
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<any>('system').findOne({ _id: 'user_counter' });
|
||||
if (!userCounter) {
|
||||
report({ message: 'No valid installation found' });
|
||||
return false;
|
||||
|
||||
@@ -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 '<span class="time{0}" data-timestamp="{1}">{2}</span>'.format(
|
||||
relative ? ' relative' : '',
|
||||
|
||||
@@ -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 '';
|
||||
|
||||
@@ -87,7 +87,6 @@ Begin Time: 시작 시각
|
||||
Belongs to: 소유
|
||||
Bio Visibility: 소개 공개
|
||||
Bio: 소개
|
||||
Blog: Blog
|
||||
Bold: 굵게
|
||||
Boom!: 펑!
|
||||
Browser: 브라우저
|
||||
|
||||
@@ -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: 递交作业题目
|
||||
|
||||
@@ -52,7 +52,6 @@ Begin Time: 開始時間
|
||||
Belongs to: 屬於
|
||||
Bio Visibility: 個人簡介可見性
|
||||
Bio: 個人簡介
|
||||
Blog: 部落格
|
||||
Boom!: 炸了!
|
||||
Browser: 瀏覽器
|
||||
Built-in: 內建
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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('')
|
||||
}) }}
|
||||
<div class="row">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="section__body">
|
||||
<form method="post">
|
||||
{{ form.form_text({
|
||||
columns:8,
|
||||
columns:12,
|
||||
label:'Title',
|
||||
name:'title',
|
||||
placeholder:_('title'),
|
||||
@@ -15,7 +15,7 @@
|
||||
}) }}
|
||||
<div class="row">
|
||||
{{ 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
|
||||
}) }}
|
||||
</div><div class="row">
|
||||
{{ 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
|
||||
}) }}
|
||||
</div>
|
||||
<div class="row">
|
||||
{{ form.form_text({
|
||||
row:false,
|
||||
columns:4,
|
||||
last_column:true,
|
||||
columns:2,
|
||||
label:'Extension (days)',
|
||||
name:'extensionDays',
|
||||
value:extensionDays
|
||||
}) }}
|
||||
</div>
|
||||
<div class="row">
|
||||
{{ 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('')
|
||||
}) }}
|
||||
</div>
|
||||
<div class="row"><div class="columns">
|
||||
<label>
|
||||
|
||||
@@ -79,18 +79,11 @@
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) and model.system.get('server.blog') %}
|
||||
<li class="menu__item">
|
||||
<a href="{{ url('blog_main', uid=handler.user._id) }}" class="menu__link">
|
||||
<span class="icon icon-book"></span> {{ _('Blog') }}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if ui.getNodes('UserDropdown').length %}
|
||||
<li class="menu__seperator"></li>
|
||||
{%- for item in ui.getNodes('UserDropdown') -%}
|
||||
<li class="menu__item nojs--hide">
|
||||
<a href="{{ url(item.name) }}" class="menu__link">
|
||||
<a href="{{ url(item.name, (item.args(handler) if typeof(item.args)=='function' else item.args) or {}) }}" class="menu__link">
|
||||
<span class="icon icon-{{ item.args.icon }}"></span> {{ _(item.args.displayName or item.name) }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Duplex } from 'stream';
|
||||
import { inspect } from 'util';
|
||||
import fs from 'fs-extra';
|
||||
import moment, { isMoment, Moment } from 'moment-timezone';
|
||||
import { ObjectID } from 'mongodb';
|
||||
import { ObjectId } from 'mongodb';
|
||||
import Logger from 'reggol';
|
||||
export * as yaml from 'js-yaml';
|
||||
export * as fs from 'fs-extra';
|
||||
@@ -128,7 +128,7 @@ export namespace Time {
|
||||
else if (isMoment(timestamp)) _timestamp = timestamp.toDate().getTime();
|
||||
else _timestamp = timestamp.getTime();
|
||||
const hexSeconds = Math.floor(_timestamp / 1000).toString(16);
|
||||
return new ObjectID(`${hexSeconds}${allZero ? '0000000000000000' : new ObjectID().toHexString().substr(8)}`);
|
||||
return new ObjectId(`${hexSeconds}${allZero ? '0000000000000000' : new ObjectId().toHexString().substr(8)}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
"preferUnplugged": true,
|
||||
"dependencies": {
|
||||
"cac": "^6.7.14",
|
||||
"esbuild": "0.16.16",
|
||||
"esbuild": "0.17.10",
|
||||
"fs-extra": "^11.1.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"moment-timezone": "^0.5.40",
|
||||
"mongodb": "^3.7.3",
|
||||
"moment-timezone": "^0.5.41",
|
||||
"mongodb": "^5.1.0",
|
||||
"reggol": "^1.3.5",
|
||||
"source-map-support": "^0.5.21",
|
||||
"systeminformation": "^5.17.9"
|
||||
"systeminformation": "^5.17.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
|
||||
+13
-8
@@ -14,14 +14,19 @@ const Root = {
|
||||
|
||||
describe('App', () => {
|
||||
let agent: supertest.SuperAgentTest;
|
||||
before((done) => {
|
||||
process.send = ((send) => (data) => {
|
||||
if (data === 'ready') {
|
||||
agent = supertest.agent(require('hydrooj').httpServer);
|
||||
done();
|
||||
}
|
||||
return send?.(data) || false;
|
||||
})(process.send);
|
||||
before(async () => {
|
||||
const init = Date.now();
|
||||
await new Promise((resolve) => {
|
||||
process.send = ((send) => (data) => {
|
||||
console.log('send', data);
|
||||
if (data === 'ready') {
|
||||
agent = supertest.agent(require('hydrooj').httpServer);
|
||||
resolve(null);
|
||||
}
|
||||
return send?.(data) || false;
|
||||
})(process.send);
|
||||
});
|
||||
console.log('Application inited in %d ms', Date.now() - init);
|
||||
}, { timeout: 30000 });
|
||||
|
||||
const routes = ['/', '/api', '/p', '/contest', '/homework', '/user/1', '/training'];
|
||||
|
||||
Reference in new issue
Block a user