mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
使用 @param 验证接口
This commit is contained in:
1 parent
4e85b696c0
commit
19cf1a5304
22 files changed
+657
-393
No files matched your search
@@ -9,6 +9,11 @@ Hydro 的界面基于 Vijos 二次开发。
|
||||
|
||||
如果您认为本项目有价值,欢迎 star 。
|
||||
|
||||
## 联系我们
|
||||
|
||||
QQ [3402182471](tencent://AddContact/?uin=3402182471&subcmd=all)
|
||||
Telegram [@webpack_exports_undefined](https://t.me/webpack_exports_undefined)
|
||||
|
||||
## 鸣谢
|
||||
|
||||
排名不分先后,按照链接字典序
|
||||
|
||||
@@ -17,7 +17,7 @@ export async function load(call) {
|
||||
await call({ entry: 'setup' });
|
||||
require('../options');
|
||||
}
|
||||
const bus = require('../service/bus').default;
|
||||
const bus = require('../service/bus');
|
||||
await new Promise((resolve) => {
|
||||
const h = () => {
|
||||
console.log('Database connected');
|
||||
@@ -32,7 +32,7 @@ export async function load(call) {
|
||||
require('../service/gridfs');
|
||||
require('../service/monitor');
|
||||
const server = require('../service/server');
|
||||
await server.default.prepare();
|
||||
await server.prepare();
|
||||
await service(pending, fail);
|
||||
for (const i of builtinModel) require(`../model/${i}`);
|
||||
for (const i of builtinHandler) require(`../handler/${i}`);
|
||||
|
||||
@@ -27,7 +27,7 @@ export async function load() {
|
||||
for (const i of builtinLib) require(`../lib/${i}`);
|
||||
await lib(pending, fail);
|
||||
require('../service/gridfs');
|
||||
const server = require('../service/server').default;
|
||||
const server = require('../service/server');
|
||||
await server.prepare();
|
||||
await service(pending, fail);
|
||||
for (const i of builtinModel) require(`../model/${i}`);
|
||||
|
||||
+2
-5
@@ -31,6 +31,8 @@ const Err = (name: string, ...info: Array<IHydroError | Function | string | numb
|
||||
export class HydroError extends Error {
|
||||
params: any[];
|
||||
|
||||
code: number;
|
||||
|
||||
constructor(...params: any[]) {
|
||||
super();
|
||||
this.params = params;
|
||||
@@ -40,11 +42,6 @@ export class HydroError extends Error {
|
||||
msg() {
|
||||
return 'HydroError';
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
code() {
|
||||
return 500;
|
||||
}
|
||||
}
|
||||
|
||||
export const UserFacingError = Err('UserFacingError', HydroError, 'UserFacingError', 400);
|
||||
|
||||
+82
-39
@@ -1,10 +1,12 @@
|
||||
import moment from 'moment-timezone';
|
||||
import { ObjectID } from 'mongodb';
|
||||
import AdmZip from 'adm-zip';
|
||||
import { isSafeInteger } from 'lodash';
|
||||
import {
|
||||
ContestNotLiveError, ValidationError, ProblemNotFoundError,
|
||||
ContestNotAttendedError,
|
||||
} from '../error';
|
||||
import { isContent, isTitle } from '../lib/validator';
|
||||
import paginate from '../lib/paginate';
|
||||
import { PERM, PRIV } from '../model/builtin';
|
||||
import * as contest from '../model/contest';
|
||||
@@ -13,12 +15,16 @@ import * as problem from '../model/problem';
|
||||
import * as record from '../model/record';
|
||||
import * as user from '../model/user';
|
||||
import * as system from '../model/system';
|
||||
import { Route, Handler } from '../service/server';
|
||||
import {
|
||||
Route, Handler, Types, param,
|
||||
} from '../service/server';
|
||||
|
||||
const ContestHandler = contest.ContestHandlerMixin(Handler);
|
||||
|
||||
class ContestListHandler extends ContestHandler {
|
||||
async get({ domainId, rule = 0, page = 1 }) {
|
||||
@param('rule', Types.String, true)
|
||||
@param('page', Types.UnsignedInt, true)
|
||||
async get(domainId: string, rule = '', page = 1) {
|
||||
this.response.template = 'contest_main.html';
|
||||
let tdocs;
|
||||
let qs;
|
||||
@@ -47,15 +53,14 @@ class ContestListHandler extends ContestHandler {
|
||||
}
|
||||
|
||||
class ContestDetailHandler extends ContestHandler {
|
||||
async _prepare({ domainId, tid }) {
|
||||
this.tdoc = await contest.get(domainId, tid);
|
||||
}
|
||||
|
||||
async get({ domainId, page = 1 }) {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('page', Types.UnsignedInt, true)
|
||||
async get(domainId: string, tid: ObjectID, page = 1) {
|
||||
const tdoc = await contest.get(domainId, tid);
|
||||
this.response.template = 'contest_detail.html';
|
||||
const [tsdoc, pdict] = await Promise.all([
|
||||
contest.getStatus(domainId, this.tdoc.docId, this.user._id),
|
||||
problem.getList(domainId, this.tdoc.pids, true),
|
||||
contest.getStatus(domainId, tdoc.docId, this.user._id),
|
||||
problem.getList(domainId, tdoc.pids, true),
|
||||
]);
|
||||
const psdict = {};
|
||||
let rdict = {};
|
||||
@@ -63,7 +68,7 @@ class ContestDetailHandler extends ContestHandler {
|
||||
if (tsdoc) {
|
||||
attended = tsdoc.attend === 1;
|
||||
for (const pdetail of tsdoc.journal || []) psdict[pdetail.pid] = pdetail;
|
||||
if (this.canShowRecord(this.tdoc)) {
|
||||
if (this.canShowRecord(tdoc)) {
|
||||
const q = [];
|
||||
for (const i in psdict) q.push(psdict[i].rid);
|
||||
rdict = await record.getList(domainId, q);
|
||||
@@ -71,26 +76,29 @@ class ContestDetailHandler extends ContestHandler {
|
||||
for (const i in psdict) rdict[psdict[i].rid] = { _id: psdict[i].rid };
|
||||
}
|
||||
} else attended = false;
|
||||
const udict = await user.getList(domainId, [this.tdoc.owner]);
|
||||
const udict = await user.getList(domainId, [tdoc.owner]);
|
||||
const path = [
|
||||
['Hydro', 'homepage'],
|
||||
['contest_main', 'contest_main'],
|
||||
[this.tdoc.title, null, null, true],
|
||||
[tdoc.title, null, null, true],
|
||||
];
|
||||
this.response.body = {
|
||||
path, tdoc: this.tdoc, tsdoc, attended, udict, pdict, psdict, rdict, page,
|
||||
path, tdoc, tsdoc, attended, udict, pdict, psdict, rdict, page,
|
||||
};
|
||||
}
|
||||
|
||||
async postAttend({ domainId }) {
|
||||
if (contest.isDone(this.tdoc)) throw new ContestNotLiveError(this.tdoc.docId);
|
||||
await contest.attend(domainId, this.tdoc.docId, this.user._id);
|
||||
@param('tid', Types.ObjectID)
|
||||
async postAttend(domainId: string, tid: ObjectID) {
|
||||
const tdoc = await contest.get(domainId, tid);
|
||||
if (contest.isDone(tdoc)) throw new ContestNotLiveError(tid);
|
||||
await contest.attend(domainId, tid, this.user._id);
|
||||
this.back();
|
||||
}
|
||||
}
|
||||
|
||||
class ContestScoreboardHandler extends ContestDetailHandler {
|
||||
async get({ domainId, tid }) {
|
||||
class ContestScoreboardHandler extends ContestHandler {
|
||||
@param('tid', Types.ObjectID)
|
||||
async get(domainId: string, tid: ObjectID) {
|
||||
const [tdoc, rows, udict] = await this.getScoreboard(domainId, tid);
|
||||
const path = [
|
||||
['Hydro', 'homepage'],
|
||||
@@ -105,8 +113,10 @@ class ContestScoreboardHandler extends ContestDetailHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class ContestScoreboardDownloadHandler extends ContestDetailHandler {
|
||||
async get({ domainId, tid, ext }) {
|
||||
class ContestScoreboardDownloadHandler extends ContestHandler {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('ext', Types.String)
|
||||
async get(domainId: string, tid: ObjectID, ext: string) {
|
||||
const getContent = {
|
||||
csv: async (rows) => `\uFEFF${rows.map((c) => (c.map((i) => i.value).join(','))).join('\n')}`,
|
||||
html: (rows) => this.renderHTML('contest_scoreboard_download_html.html', { rows }),
|
||||
@@ -117,8 +127,10 @@ class ContestScoreboardDownloadHandler extends ContestDetailHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class ContestEditHandler extends ContestDetailHandler {
|
||||
async prepare() {
|
||||
class ContestEditHandler extends ContestHandler {
|
||||
@param('tid', Types.ObjectID)
|
||||
async prepare(domainId: string, tid: ObjectID) {
|
||||
this.tdoc = await contest.get(domainId, tid);
|
||||
if (this.tdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_CONTEST);
|
||||
else this.checkPerm(PERM.PERM_EDIT_CONTEST_SELF);
|
||||
}
|
||||
@@ -149,9 +161,22 @@ class ContestEditHandler extends ContestDetailHandler {
|
||||
};
|
||||
}
|
||||
|
||||
async post({
|
||||
domainId, beginAtDate, beginAtTime, duration, title, content, rule, pids, rated = false,
|
||||
}) {
|
||||
@param('beginAtDate', Types.Date)
|
||||
@param('beginAtTime', Types.Time)
|
||||
@param('duration', Types.Float)
|
||||
@param('title', Types.String, isTitle)
|
||||
@param('content', Types.String, isContent)
|
||||
@param('rule', Types.String)
|
||||
@param('pids', Types.String)
|
||||
@param('rated', Types.Boolean, true)
|
||||
async post(
|
||||
domainId: string, beginAtDate: string, beginAtTime: string, duration: number,
|
||||
title: string, content: string, rule: string, _pids: string, rated = false,
|
||||
) {
|
||||
let pids = _pids.split(',').map((i) => {
|
||||
if (isSafeInteger(parseInt(i, 10))) return parseInt(i, 10);
|
||||
return i;
|
||||
});
|
||||
const beginAtMoment = moment.tz(`${beginAtDate} ${beginAtTime}`, this.user.timeZone);
|
||||
if (!beginAtMoment.isValid()) {
|
||||
throw new ValidationError('beginAtDate', 'beginAtTime');
|
||||
@@ -172,8 +197,10 @@ class ContestEditHandler extends ContestDetailHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class ContestProblemHandler extends ContestDetailHandler {
|
||||
async prepare({ domainId, tid, pid }) {
|
||||
class ContestProblemHandler extends ContestHandler {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('pid', Types.UnsignedInt)
|
||||
async prepare(domainId: string, tid: ObjectID, pid: number) {
|
||||
[this.tdoc, this.pdoc] = await Promise.all([
|
||||
contest.get(domainId, tid),
|
||||
problem.get(domainId, pid, this.user._id),
|
||||
@@ -193,12 +220,11 @@ class ContestProblemHandler extends ContestDetailHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
async get({ tid }) {
|
||||
async get() {
|
||||
const path = [
|
||||
['Hydro', 'homepage'],
|
||||
['contest_main', 'contest_main'],
|
||||
[this.tdoc.title, 'contest_detail', { tid }, true],
|
||||
[this.tdoc.title, 'contest_detail', { tid: this.tdoc.docId }, true],
|
||||
[this.pdoc.title, null, null, true],
|
||||
];
|
||||
this.response.body = {
|
||||
@@ -213,7 +239,9 @@ class ContestProblemHandler extends ContestDetailHandler {
|
||||
}
|
||||
|
||||
class ContestProblemSubmitHandler extends ContestProblemHandler {
|
||||
async get({ domainId, tid, pid }) {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('pid', Types.UnsignedInt)
|
||||
async prepare(domainId: string, tid: ObjectID, pid: number) {
|
||||
let rdocs = [];
|
||||
if (this.canShowRecord(this.tdoc)) {
|
||||
rdocs = await record.getUserInProblemMulti(
|
||||
@@ -240,9 +268,10 @@ class ContestProblemSubmitHandler extends ContestProblemHandler {
|
||||
};
|
||||
}
|
||||
|
||||
async post({
|
||||
domainId, tid, lang, code,
|
||||
}) {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('lang', Types.String)
|
||||
@param('code', Types.String)
|
||||
async post(domainId: string, tid: ObjectID, lang: string, code: string) {
|
||||
await this.limitRate('add_record', 60, 100);
|
||||
const rid = await record.add(domainId, {
|
||||
pid: this.pdoc.docId,
|
||||
@@ -263,8 +292,9 @@ class ContestProblemSubmitHandler extends ContestProblemHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class ContestCodeHandler extends ContestDetailHandler {
|
||||
async get({ domainId, tid }) {
|
||||
class ContestCodeHandler extends ContestHandler {
|
||||
@param('tid', Types.ObjectID)
|
||||
async get(domainId: string, tid: ObjectID) {
|
||||
if (!this.user.hasPriv(PRIV.PRIV_READ_RECORD_CODE)) {
|
||||
this.checkPerm(PERM.PERM_READ_RECORD_CODE);
|
||||
}
|
||||
@@ -313,9 +343,22 @@ class ContestCreateHandler extends ContestHandler {
|
||||
};
|
||||
}
|
||||
|
||||
async post({
|
||||
domainId, title, content, rule, beginAtDate, beginAtTime, duration, pids, rated = false,
|
||||
}) {
|
||||
@param('beginAtDate', Types.Date)
|
||||
@param('beginAtTime', Types.Time)
|
||||
@param('duration', Types.Float)
|
||||
@param('title', Types.String, isTitle)
|
||||
@param('content', Types.String, isContent)
|
||||
@param('rule', Types.String)
|
||||
@param('pids', Types.String)
|
||||
@param('rated', Types.Boolean, true)
|
||||
async post(
|
||||
domainId: string, beginAtDate: string, beginAtTime: string, duration: number,
|
||||
title: string, content: string, rule: string, _pids: string, rated = false,
|
||||
) {
|
||||
let pids = _pids.split(',').map((i) => {
|
||||
if (isSafeInteger(parseInt(i, 10))) return parseInt(i, 10);
|
||||
return i;
|
||||
});
|
||||
const beginAt = moment.tz(`${beginAtDate} ${beginAtTime}`, this.user.timeZone);
|
||||
if (!beginAt.isValid()) {
|
||||
throw new ValidationError('beginAtDate', 'beginAtTime');
|
||||
|
||||
+44
-17
@@ -6,7 +6,10 @@ import * as user from '../model/user';
|
||||
import * as discussion from '../model/discussion';
|
||||
import * as document from '../model/document';
|
||||
import { PERM, PRIV } from '../model/builtin';
|
||||
import { Route, Handler } from '../service/server';
|
||||
import {
|
||||
Route, Handler, Types, param,
|
||||
} from '../service/server';
|
||||
import { isTitle, isContent } from '../lib/validator';
|
||||
|
||||
export const typeMapper = {
|
||||
problem: document.TYPE_PROBLEM,
|
||||
@@ -22,17 +25,20 @@ class DiscussionHandler extends Handler {
|
||||
}) {
|
||||
this.checkPerm(PERM.PERM_VIEW_DISCUSSION);
|
||||
if (did) {
|
||||
did = new ObjectID(did);
|
||||
this.ddoc = await discussion.get(domainId, did);
|
||||
if (!this.ddoc) throw new DiscussionNotFoundError(did);
|
||||
type = discussion.typeDisplay[this.ddoc.parentType];
|
||||
name = this.ddoc.parentId;
|
||||
if (drid) {
|
||||
drid = new ObjectID(drid);
|
||||
this.drdoc = await discussion.getReply(domainId, drid);
|
||||
if (!this.drdoc) throw new DiscussionNotFoundError(drid);
|
||||
if (!this.drdoc.parentId.equals(this.ddoc._id)) {
|
||||
throw new DocumentNotFoundError(drid);
|
||||
}
|
||||
if (drrid) {
|
||||
drrid = new ObjectID(drrid);
|
||||
[, this.drrdoc] = await discussion.getTailReply(domainId, drid, drrid);
|
||||
if (!this.drrdoc) throw new DiscussionNotFoundError(drrid);
|
||||
if (!this.drrdoc.parentId.equals(this.drdoc._id)) {
|
||||
@@ -57,7 +63,8 @@ class DiscussionHandler extends Handler {
|
||||
}
|
||||
|
||||
class DiscussionMainHandler extends Handler {
|
||||
async get({ domainId, page = 1 }) {
|
||||
@param('page', Types.UnsignedInt, true)
|
||||
async get(domainId: string, page = 1) {
|
||||
const [ddocs, dpcount] = await paginate(
|
||||
discussion.getMulti(domainId),
|
||||
page,
|
||||
@@ -83,6 +90,7 @@ class DiscussionNodeHandler extends DiscussionHandler {
|
||||
async get({
|
||||
domainId, type, name, page = 1,
|
||||
}) {
|
||||
if (ObjectID.isValid(name)) name = new ObjectID(name);
|
||||
const [ddocs, dpcount] = await paginate(
|
||||
discussion.getMulti(domainId, { parentType: typeMapper[type], parentId: name }),
|
||||
page,
|
||||
@@ -146,7 +154,9 @@ class DiscussionCreateHandler extends DiscussionHandler {
|
||||
}
|
||||
|
||||
class DiscussionDetailHandler extends DiscussionHandler {
|
||||
async get({ domainId, did, page = 1 }) {
|
||||
@param('did', Types.ObjectID)
|
||||
@param('page', Types.UnsignedInt, true)
|
||||
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;
|
||||
@@ -181,52 +191,64 @@ class DiscussionDetailHandler extends DiscussionHandler {
|
||||
this.checkPriv(PRIV.PRIV_USER_PROFILE);
|
||||
}
|
||||
|
||||
async postReply({ domainId, did, content }) {
|
||||
@param('did', Types.ObjectID)
|
||||
@param('content', Types.String, isContent)
|
||||
async postReply(domainId: string, did: ObjectID, content: string) {
|
||||
this.checkPerm(PERM.PERM_REPLY_DISCUSSION);
|
||||
this.limitRate('add_discussion', 3600, 30);
|
||||
await discussion.addReply(domainId, did, this.user._id, content, this.request.ip);
|
||||
this.back();
|
||||
}
|
||||
|
||||
async postTailReply({ domainId, drid, content }) {
|
||||
@param('drid', Types.ObjectID)
|
||||
@param('content', Types.String, isContent)
|
||||
async postTailReply(domainId: string, drid: ObjectID, content: string) {
|
||||
this.checkPerm(PERM.PERM_REPLY_DISCUSSION);
|
||||
this.limitRate('add_discussion', 3600, 30);
|
||||
await discussion.addTailReply(domainId, drid, this.user._id, content, this.request.ip);
|
||||
this.back();
|
||||
}
|
||||
|
||||
async postEditReply({ domainId, drid, content }) {
|
||||
@param('drid', Types.ObjectID)
|
||||
@param('content', Types.String, isContent)
|
||||
async postEditReply(domainId: string, drid: ObjectID, content: string) {
|
||||
if (this.drdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_DISCUSSION_REPLY);
|
||||
await discussion.editReply(domainId, drid, content);
|
||||
this.back();
|
||||
}
|
||||
|
||||
async postDeleteReply({ domainId, drid }) {
|
||||
@param('drid', Types.ObjectID)
|
||||
async postDeleteReply(domainId: string, drid: ObjectID) {
|
||||
if (this.drdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_DELETE_DISCUSSION_REPLY);
|
||||
await discussion.delReply(domainId, drid);
|
||||
this.back();
|
||||
}
|
||||
|
||||
async postEditTailReply({
|
||||
domainId, drid, drrid, content,
|
||||
}) {
|
||||
@param('drid', Types.ObjectID)
|
||||
@param('drrid', Types.ObjectID)
|
||||
@param('content', Types.String, isContent)
|
||||
async postEditTailReply(domainId: string, drid: ObjectID, drrid: ObjectID, content: string) {
|
||||
if (this.drdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_DISCUSSION_REPLY);
|
||||
await discussion.editTailReply(domainId, drid, drrid, content);
|
||||
this.back();
|
||||
}
|
||||
|
||||
async postDeleteTailReply({ domainId, drid, drrid }) {
|
||||
@param('drid', Types.ObjectID)
|
||||
@param('drrid', Types.ObjectID)
|
||||
async postDeleteTailReply(domainId: string, drid: ObjectID, drrid: ObjectID) {
|
||||
if (this.drrdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_DELETE_DISCUSSION_REPLY);
|
||||
await discussion.delTailReply(domainId, drid, drrid);
|
||||
this.back();
|
||||
}
|
||||
|
||||
async postStar({ domainId, did }) {
|
||||
@param('did', Types.ObjectID)
|
||||
async postStar(domainId: string, did: ObjectID) {
|
||||
await discussion.setStar(domainId, did, this.user._id, true);
|
||||
this.back({ star: true });
|
||||
}
|
||||
|
||||
async postUnstar({ domainId, did }) {
|
||||
@param('did', Types.ObjectID)
|
||||
async postUnstar(domainId: string, did: ObjectID) {
|
||||
await discussion.setStar(domainId, did, this.user._id, false);
|
||||
this.back({ star: false });
|
||||
}
|
||||
@@ -266,9 +288,13 @@ class DiscussionEditHandler extends DiscussionHandler {
|
||||
this.response.body = { ddoc: this.ddoc, path };
|
||||
}
|
||||
|
||||
async postUpdate({
|
||||
domainId, did, title, content, highlight,
|
||||
}) {
|
||||
@param('did', Types.ObjectID)
|
||||
@param('title', Types.String, isTitle)
|
||||
@param('content', Types.String, isContent)
|
||||
@param('highlight', Types.Boolean, true)
|
||||
async postUpdate(
|
||||
domainId: string, did: ObjectID, title: string, content: string, highlight = false,
|
||||
) {
|
||||
if (this.ddoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_DISCUSSION);
|
||||
if (highlight && !this.ddoc.highlight) this.checkPerm(PERM.PERM_HIGHLIGHT_DISCUSSION);
|
||||
await discussion.edit(domainId, did, title, content, highlight);
|
||||
@@ -276,7 +302,8 @@ class DiscussionEditHandler extends DiscussionHandler {
|
||||
this.response.redirect = this.url('discussion_detail', { did });
|
||||
}
|
||||
|
||||
async postDelete({ domainId, did }) {
|
||||
@param('did', Types.ObjectID)
|
||||
async postDelete(domainId: string, did: ObjectID) {
|
||||
if (this.ddoc.owner !== this.user._id) this.checkPerm(PERM.PERM_DELETE_DISCUSSION);
|
||||
await discussion.del(domainId, did);
|
||||
this.response.body = { type: this.ddoc.parentType, parent: this.ddoc.parentId };
|
||||
|
||||
@@ -5,10 +5,14 @@ import * as system from '../model/system';
|
||||
import { DOMAIN_SETTINGS, DOMAIN_SETTINGS_BY_KEY } from '../model/setting';
|
||||
import { PERM, PERMS_BY_FAMILY } from '../model/builtin';
|
||||
import paginate from '../lib/paginate';
|
||||
import { Route, Handler } from '../service/server';
|
||||
import {
|
||||
Route, Handler, Types, param,
|
||||
} from '../service/server';
|
||||
import { isTitle, isUname } from '../lib/validator';
|
||||
|
||||
class DomainRankHandler extends Handler {
|
||||
async get({ domainId, page = 1 }) {
|
||||
@param('page', Types.UnsignedInt, true)
|
||||
async get(domainId: string, page = 1) {
|
||||
const [dudocs, upcount, ucount] = await paginate(
|
||||
user.getMultiInDomain(domainId).sort({ rating: -1 }),
|
||||
page,
|
||||
@@ -46,7 +50,9 @@ class DomainCreateHandler extends Handler {
|
||||
this.response.template = 'domain_create.html';
|
||||
}
|
||||
|
||||
async post({ id, name }) {
|
||||
@param('id', Types.String, isUname)
|
||||
@param('name', Types.String, isTitle)
|
||||
async post(domainId: string, id: string, name: string) {
|
||||
await domain.add(id, this.user._id, name);
|
||||
this.response.body = { domainId: id };
|
||||
this.response.redirect = this.url('homepage', { domainId: id });
|
||||
|
||||
+79
-39
@@ -2,11 +2,14 @@ import yaml from 'js-yaml';
|
||||
import moment from 'moment-timezone';
|
||||
import { ObjectID } from 'mongodb';
|
||||
import AdmZip from 'adm-zip';
|
||||
import { isSafeInteger } from 'lodash';
|
||||
import {
|
||||
ValidationError, HomeworkNotLiveError, ProblemNotFoundError,
|
||||
HomeworkNotAttendedError,
|
||||
} from '../error';
|
||||
import { Route, Handler } from '../service/server';
|
||||
import {
|
||||
Route, Handler, Types, param,
|
||||
} from '../service/server';
|
||||
import { PERM } from '../model/builtin';
|
||||
import * as system from '../model/system';
|
||||
import * as user from '../model/user';
|
||||
@@ -16,6 +19,7 @@ import * as problem from '../model/problem';
|
||||
import * as record from '../model/record';
|
||||
import * as document from '../model/document';
|
||||
import paginate from '../lib/paginate';
|
||||
import { isTitle, isContent } from '../lib/validator';
|
||||
|
||||
const HomeworkHandler = contest.ContestHandlerMixin(Handler);
|
||||
|
||||
@@ -43,7 +47,9 @@ class HomeworkMainHandler extends HomeworkHandler {
|
||||
}
|
||||
|
||||
class HomeworkDetailHandler extends HomeworkHandler {
|
||||
async get({ domainId, tid, page = 1 }) {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('page', Types.UnsignedInt, true)
|
||||
async get(domainId: string, tid: ObjectID, page = 1) {
|
||||
const tdoc = await contest.get(domainId, tid, document.TYPE_HOMEWORK);
|
||||
const [tsdoc, pdict] = await Promise.all([
|
||||
contest.getStatus(domainId, tdoc.docId, this.user._id, document.TYPE_HOMEWORK),
|
||||
@@ -86,7 +92,8 @@ class HomeworkDetailHandler extends HomeworkHandler {
|
||||
};
|
||||
}
|
||||
|
||||
async postAttend({ domainId, tid }) {
|
||||
@param('tid', Types.ObjectID)
|
||||
async postAttend(domainId: string, tid: ObjectID) {
|
||||
this.checkPerm(PERM.PERM_ATTEND_HOMEWORK);
|
||||
const tdoc = await contest.get(domainId, tid, document.TYPE_HOMEWORK);
|
||||
if (contest.isDone(tdoc)) throw new HomeworkNotLiveError(tdoc.docId);
|
||||
@@ -96,7 +103,9 @@ class HomeworkDetailHandler extends HomeworkHandler {
|
||||
}
|
||||
|
||||
class HomeworkDetailProblemHandler extends HomeworkHandler {
|
||||
async prepare({ domainId, tid, pid }) {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('pid', Types.UnsignedInt)
|
||||
async prepare(domainId: string, tid: ObjectID, pid: number) {
|
||||
this.checkPerm(PERM.PERM_VIEW_PROBLEM);
|
||||
[this.tdoc, this.pdoc, this.tsdoc] = await Promise.all([
|
||||
contest.get(domainId, tid, document.TYPE_HOMEWORK),
|
||||
@@ -105,7 +114,9 @@ class HomeworkDetailProblemHandler extends HomeworkHandler {
|
||||
]);
|
||||
}
|
||||
|
||||
async get({ domainId, tid, pid }) {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('pid', Types.UnsignedInt)
|
||||
async get(domainId: string, tid: ObjectID, pid: number) {
|
||||
const udoc = await user.getById(domainId, this.tdoc.owner);
|
||||
const attended = this.tsdoc && this.tsdoc.attend === 1;
|
||||
if (!contest.isDone(this.tdoc)) {
|
||||
@@ -127,7 +138,9 @@ class HomeworkDetailProblemHandler extends HomeworkHandler {
|
||||
}
|
||||
|
||||
class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler {
|
||||
async get({ domainId, tid, pid }) {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('pid', Types.UnsignedInt)
|
||||
async get(domainId: string, tid: ObjectID, pid: number) {
|
||||
const udoc = await user.getById(domainId, this.tdoc.owner);
|
||||
const attended = this.tsdoc && this.tsdoc.attend === 1;
|
||||
if (!attended) throw new HomeworkNotAttendedError(tid);
|
||||
@@ -150,9 +163,11 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler {
|
||||
};
|
||||
}
|
||||
|
||||
async post({
|
||||
domainId, tid, pid, code, lang,
|
||||
}) {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('pid', Types.UnsignedInt)
|
||||
@param('code', Types.String)
|
||||
@param('lang', Types.String)
|
||||
async post(domainId: string, tid: ObjectID, pid: number, code: string, lang: string) {
|
||||
this.limitRate('add_record', 60, 100);
|
||||
const tsdoc = await contest.getStatus(domainId, tid, this.user._id, document.TYPE_HOMEWORK);
|
||||
if (!tsdoc.attend) throw new HomeworkNotAttendedError(tid);
|
||||
@@ -191,38 +206,44 @@ class HomeworkCreateHandler extends HomeworkHandler {
|
||||
};
|
||||
}
|
||||
|
||||
async post({
|
||||
domainId, title, content, beginAtDate, beginAtTime,
|
||||
penaltySinceDate, penaltySinceTime, extensionDays,
|
||||
penaltyRules, pids, rated = false,
|
||||
}) {
|
||||
let beginAt;
|
||||
let penaltySince;
|
||||
try {
|
||||
beginAt = moment.tz(`${beginAtDate} ${beginAtTime}`, this.user.timeZone);
|
||||
} catch (e) {
|
||||
throw new ValidationError('beginAtDate', 'beginAtTime');
|
||||
}
|
||||
try {
|
||||
penaltySince = moment.tz(`${penaltySinceDate} ${penaltySinceTime}`, this.user.timeZone);
|
||||
} catch (e) {
|
||||
throw new ValidationError('endAtDate', 'endAtTime');
|
||||
}
|
||||
@param('beginAtDate', Types.Date)
|
||||
@param('beginAtTime', Types.Time)
|
||||
@param('penaltySinceDate', Types.Date)
|
||||
@param('penaltySinceTime', Types.Time)
|
||||
@param('extensionDays', Types.Float)
|
||||
@param('penaltyRules', Types.String)
|
||||
@param('title', Types.String, isTitle)
|
||||
@param('content', Types.String, isContent)
|
||||
@param('pids', Types.String)
|
||||
@param('rated', Types.Boolean, true)
|
||||
async post(
|
||||
domainId: string, beginAtDate: string, beginAtTime: string,
|
||||
penaltySinceDate: string, penaltySinceTime: string, extensionDays: number,
|
||||
penaltyRules: string, title: string, content: string, _pids: string, rated = false,
|
||||
) {
|
||||
const pids = _pids.split(',').map((i) => {
|
||||
if (isSafeInteger(parseInt(i, 10))) return parseInt(i, 10);
|
||||
return i;
|
||||
});
|
||||
const beginAt = moment.tz(`${beginAtDate} ${beginAtTime}`, this.user.timeZone);
|
||||
if (!beginAt.isValid()) throw new ValidationError('beginAtDate', 'beginAtTime');
|
||||
const penaltySince = moment.tz(`${penaltySinceDate} ${penaltySinceTime}`, this.user.timeZone);
|
||||
if (!penaltySince.isValid()) throw new ValidationError('endAtDate', 'endAtTime');
|
||||
const endAt = penaltySince.clone().add(extensionDays, 'days');
|
||||
if (beginAt.isSameOrAfter(penaltySince)) throw new ValidationError('endAtDate', 'endAtTime');
|
||||
if (penaltySince.isAfter(endAt)) throw new ValidationError('extensionDays');
|
||||
penaltySince = penaltySince.toDate();
|
||||
await this.verifyProblems(domainId, pids);
|
||||
const tid = await contest.add(domainId, title, content, this.user._id,
|
||||
'homework', beginAt.toDate(), endAt.toDate(), pids, rated,
|
||||
{ penaltySince, penaltyRules }, document.TYPE_HOMEWORK);
|
||||
{ penaltySince: penaltySince.toDate(), penaltyRules }, document.TYPE_HOMEWORK);
|
||||
this.response.body = { tid };
|
||||
this.response.redirect = this.url('homework_detail', { tid });
|
||||
}
|
||||
}
|
||||
|
||||
class HomeworkEditHandler extends HomeworkHandler {
|
||||
async get({ domainId, tid }) {
|
||||
@param('tid', Types.ObjectID)
|
||||
async get(domainId: string, tid: ObjectID) {
|
||||
const tdoc = await contest.get(domainId, tid, document.TYPE_HOMEWORK);
|
||||
if (tdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_HOMEWORK);
|
||||
else this.checkPerm(PERM.PERM_EDIT_HOMEWORK_SELF);
|
||||
@@ -251,11 +272,26 @@ class HomeworkEditHandler extends HomeworkHandler {
|
||||
};
|
||||
}
|
||||
|
||||
async post({
|
||||
domainId, title, content, beginAtDate, beginAtTime,
|
||||
penaltySinceDate, penaltySinceTime, extensionDays,
|
||||
penaltyRules, pids, tid,
|
||||
}) {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('beginAtDate', Types.Date)
|
||||
@param('beginAtTime', Types.Time)
|
||||
@param('penaltySinceDate', Types.Date)
|
||||
@param('penaltySinceTime', Types.Time)
|
||||
@param('extensionDays', Types.Float)
|
||||
@param('penaltyRules', Types.String)
|
||||
@param('title', Types.String, isTitle)
|
||||
@param('content', Types.String, isContent)
|
||||
@param('pids', Types.String)
|
||||
@param('rated', Types.Boolean, true)
|
||||
async post(
|
||||
domainId: string, tid: ObjectID, beginAtDate: string, beginAtTime: string,
|
||||
penaltySinceDate: string, penaltySinceTime: string, extensionDays: number,
|
||||
penaltyRules: string, title: string, content: string, _pids: string, rated = false,
|
||||
) {
|
||||
const pids = _pids.split(',').map((i) => {
|
||||
if (isSafeInteger(parseInt(i, 10))) return parseInt(i, 10);
|
||||
return i;
|
||||
});
|
||||
const tdoc = await contest.get(domainId, tid, document.TYPE_HOMEWORK);
|
||||
if (tdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_HOMEWORK);
|
||||
else this.checkPerm(PERM.PERM_EDIT_HOMEWORK_SELF);
|
||||
@@ -278,7 +314,7 @@ class HomeworkEditHandler extends HomeworkHandler {
|
||||
endAt = endAt.toDate();
|
||||
penaltySince = penaltySince.toDate();
|
||||
await contest.edit(domainId, tid, {
|
||||
title, content, beginAt, endAt, pids, penaltySince, penaltyRules,
|
||||
title, content, beginAt, endAt, pids, penaltySince, penaltyRules, rated,
|
||||
}, document.TYPE_HOMEWORK);
|
||||
if (tdoc.beginAt !== beginAt
|
||||
|| tdoc.endAt !== endAt
|
||||
@@ -292,7 +328,8 @@ class HomeworkEditHandler extends HomeworkHandler {
|
||||
}
|
||||
|
||||
class HomeworkScoreboardHandler extends HomeworkHandler {
|
||||
async get({ domainId, tid }) {
|
||||
@param('tid', Types.ObjectID)
|
||||
async get(domainId: string, tid: ObjectID) {
|
||||
const [tdoc, rows, udict] = await this.getScoreboard(
|
||||
domainId, tid, false, document.TYPE_HOMEWORK,
|
||||
);
|
||||
@@ -310,9 +347,11 @@ class HomeworkScoreboardHandler extends HomeworkHandler {
|
||||
}
|
||||
|
||||
class HomeworkScoreboardDownloadHandler extends HomeworkHandler {
|
||||
async get({ domainId, tid, ext }) {
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('ext', Types.String)
|
||||
async get(domainId: string, tid: ObjectID, ext: string) {
|
||||
const getContent = {
|
||||
csv: async (rows) => `\uFEFF${rows.map((c) => (c.map((i) => i.value).join(','))).join('\n')}`,
|
||||
csv: (rows) => `\uFEFF${rows.map((c) => (c.map((i) => i.value).join(','))).join('\n')}`,
|
||||
html: (rows) => this.renderHTML('contest_scoreboard_download_html.html', { rows }),
|
||||
};
|
||||
if (!getContent[ext]) throw new ValidationError('ext');
|
||||
@@ -322,7 +361,8 @@ class HomeworkScoreboardDownloadHandler extends HomeworkHandler {
|
||||
}
|
||||
|
||||
class HomeworkCodeHandler extends HomeworkHandler {
|
||||
async get({ domainId, tid }) {
|
||||
@param('tid', Types.ObjectID)
|
||||
async get(domainId: string, tid: ObjectID) {
|
||||
this.checkPerm(PERM.PERM_READ_RECORD_CODE);
|
||||
this.limitRate('homework_code', 3600, 60);
|
||||
const [tdoc, tsdocs] = await contest.getAndListStatus(
|
||||
|
||||
@@ -4,7 +4,10 @@ import os from 'os';
|
||||
import { ValidationError } from '../error';
|
||||
import * as problem from '../model/problem';
|
||||
import { PERM } from '../model/builtin';
|
||||
import { Route, Handler } from '../service/server';
|
||||
import {
|
||||
Route, Handler, Types, param,
|
||||
} from '../service/server';
|
||||
import { isPid } from '../lib/validator';
|
||||
|
||||
class ProblemImportHandler extends Handler {
|
||||
async prepare() {
|
||||
@@ -22,9 +25,11 @@ class ProblemImportHandler extends Handler {
|
||||
};
|
||||
}
|
||||
|
||||
async post({
|
||||
domainId, url, pid, hidden, remoteType,
|
||||
}) {
|
||||
@param('url', Types.String)
|
||||
@param('pid', Types.String, isPid)
|
||||
@param('hidden', Types.Boolean)
|
||||
@param('remoteType', Types.String)
|
||||
async post(domainId: string, url: string, pid: string, hidden = false, remoteType: string) {
|
||||
if (typeof global.Hydro.lib[`import.${remoteType}`] !== 'function') {
|
||||
throw new ValidationError('remoteType');
|
||||
}
|
||||
|
||||
+14
-7
@@ -1,17 +1,23 @@
|
||||
import { ObjectID } from 'mongodb';
|
||||
import { PRIV } from '../model/builtin';
|
||||
import * as file from '../model/file';
|
||||
import * as user from '../model/user';
|
||||
import * as markdown from '../lib/markdown';
|
||||
import * as db from '../service/db';
|
||||
import { Route, Handler } from '../service/server';
|
||||
import {
|
||||
Route, Handler, Types, param,
|
||||
} from '../service/server';
|
||||
|
||||
const coll = db.collection('status');
|
||||
|
||||
class FileDownloadHandler extends Handler {
|
||||
async get({ docId, secret, name }) {
|
||||
@param('fileId', Types.ObjectID)
|
||||
@param('secret', Types.String)
|
||||
@param('name', Types.String, true)
|
||||
async get(domainId: string, fileId: ObjectID, secret: string, name: string) {
|
||||
if (name) name = Buffer.from(name, 'base64').toString();
|
||||
this.response.attachment(name || docId);
|
||||
this.response.body = await file.getWithSecret(docId, secret);
|
||||
this.response.attachment(name || fileId);
|
||||
this.response.body = await file.getWithSecret(fileId, secret);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +54,8 @@ class StatusUpdateHandler extends Handler {
|
||||
}
|
||||
|
||||
class SwitchLanguageHandler extends Handler {
|
||||
async get({ lang }) {
|
||||
@param('lang', Types.String)
|
||||
async get(domainId: string, lang: string) {
|
||||
if (this.user.hasPriv(PRIV.PRIV_USER_PROFILE)) {
|
||||
await user.setById(this.user._id, { viewLang: lang });
|
||||
} else this.session.viewLang = lang;
|
||||
@@ -71,8 +78,8 @@ class SockToken extends Handler {
|
||||
}
|
||||
|
||||
export async function apply() {
|
||||
Route('file_download', '/fs/:docId/:secret', FileDownloadHandler);
|
||||
Route('file_download_with_name', '/fs/:docId/:name/:secret', FileDownloadHandler);
|
||||
Route('file_download', '/fs/:fileId/:secret', FileDownloadHandler);
|
||||
Route('file_download_with_name', '/fs/:fileId/:name/:secret', FileDownloadHandler);
|
||||
Route('status', '/status', StatusHandler);
|
||||
Route('status_update', '/status/update', StatusUpdateHandler);
|
||||
Route('switch_language', '/language/:lang', SwitchLanguageHandler);
|
||||
|
||||
+118
-70
@@ -1,5 +1,9 @@
|
||||
import { isSafeInteger, flatten } from 'lodash';
|
||||
import { ObjectID } from 'mongodb';
|
||||
import paginate from '../lib/paginate';
|
||||
import * as validator from '../lib/validator';
|
||||
import {
|
||||
checkPid, isTitle, isContent, isPid,
|
||||
} from '../lib/validator';
|
||||
import * as file from '../model/file';
|
||||
import * as problem from '../model/problem';
|
||||
import * as record from '../model/record';
|
||||
@@ -9,19 +13,48 @@ import * as system from '../model/system';
|
||||
import { PERM, PRIV } from '../model/builtin';
|
||||
import * as bus from '../service/bus';
|
||||
import {
|
||||
Route, Connection, Handler, ConnectionHandler,
|
||||
Route, Connection, Handler, ConnectionHandler, Types, param,
|
||||
} from '../service/server';
|
||||
import {
|
||||
NoProblemError, ProblemDataNotFoundError, BadRequestError,
|
||||
SolutionNotFoundError,
|
||||
} from '../error';
|
||||
|
||||
const parseCategory = (value: string) => flatten(value.split('+').map((e) => e.split(','))).map((e) => e.trim());
|
||||
const parsePid = (value: string) => (isSafeInteger(value) ? parseInt(value, 10) : value);
|
||||
|
||||
class ProblemHandler extends Handler {
|
||||
async __prepare() {
|
||||
this.checkPerm(PERM.PERM_VIEW_PROBLEM);
|
||||
}
|
||||
|
||||
async get({ domainId, page = 1, q = '' }) {
|
||||
async cleanup() {
|
||||
if (this.response.template === 'problem_main.html' && this.request.json) {
|
||||
const {
|
||||
path, page, pcount, ppcount, pdocs, psdict, category,
|
||||
} = this.response.body;
|
||||
this.response.body = {
|
||||
title: this.renderTitle(category),
|
||||
fragments: (await Promise.all([
|
||||
this.renderHTML('partials/problem_list.html', {
|
||||
page, ppcount, pcount, pdocs, psdict,
|
||||
}),
|
||||
this.renderHTML('partials/problem_stat.html', { pcount }),
|
||||
this.renderHTML('partials/problem_lucky.html', { category }),
|
||||
this.renderHTML('partials/path.html', { path }),
|
||||
])).map((i) => ({ html: i })),
|
||||
raw: {
|
||||
path, page, pcount, ppcount, pdocs, psdict, category,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ProblemMainHandler extends ProblemHandler {
|
||||
@param('page', Types.UnsignedInt, true)
|
||||
@param('q', Types.String, true)
|
||||
async get(domainId: string, page = 1, q = '') {
|
||||
this.response.template = 'problem_main.html';
|
||||
const query: any = {};
|
||||
let psdict = {};
|
||||
@@ -51,41 +84,23 @@ class ProblemHandler extends Handler {
|
||||
};
|
||||
}
|
||||
|
||||
async postStar({ domainId, pid }) {
|
||||
@param('pid', Types.UnsignedInt)
|
||||
async postStar(domainId: string, pid: number) {
|
||||
await problem.setStar(domainId, pid, this.user._id, true);
|
||||
this.back({ star: true });
|
||||
}
|
||||
|
||||
async postUnstar({ domainId, pid }) {
|
||||
@param('pid', Types.UnsignedInt)
|
||||
async postUnstar(domainId: string, pid: number) {
|
||||
await problem.setStar(domainId, pid, this.user._id, false);
|
||||
this.back({ star: false });
|
||||
}
|
||||
|
||||
async cleanup() {
|
||||
if (this.response.template === 'problem_main.html' && this.request.json) {
|
||||
const {
|
||||
path, page, pcount, ppcount, pdocs, psdict, category,
|
||||
} = this.response.body;
|
||||
this.response.body = {
|
||||
title: this.renderTitle(category),
|
||||
fragments: (await Promise.all([
|
||||
this.renderHTML('partials/problem_list.html', {
|
||||
page, ppcount, pcount, pdocs, psdict,
|
||||
}),
|
||||
this.renderHTML('partials/problem_stat.html', { pcount }),
|
||||
this.renderHTML('partials/problem_lucky.html', { category }),
|
||||
this.renderHTML('partials/path.html', { path }),
|
||||
])).map((i) => ({ html: i })),
|
||||
raw: {
|
||||
path, page, pcount, ppcount, pdocs, psdict, category,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ProblemCategoryHandler extends ProblemHandler {
|
||||
async get({ domainId, page = 1, category }) {
|
||||
@param('page', Types.UnsignedInt, true)
|
||||
@param('category', Types.String, parseCategory)
|
||||
async get(domainId: string, page = 1, category: string[]) {
|
||||
this.response.template = 'problem_main.html';
|
||||
const q: any = { $and: [] };
|
||||
for (const name of category) {
|
||||
@@ -120,8 +135,9 @@ class ProblemCategoryHandler extends ProblemHandler {
|
||||
}
|
||||
|
||||
class ProblemRandomHandler extends ProblemHandler {
|
||||
async get({ domainId, category }) {
|
||||
const q: any = category[0] ? { $and: [] } : {};
|
||||
@param('category', Types.String, true, null, parseCategory)
|
||||
async get(domainId: string, category: string[]) {
|
||||
const q: any = category.length ? { $and: [] } : {};
|
||||
for (const name of category) {
|
||||
if (name) {
|
||||
q.$and.push({
|
||||
@@ -141,7 +157,8 @@ class ProblemRandomHandler extends ProblemHandler {
|
||||
}
|
||||
|
||||
class ProblemDetailHandler extends ProblemHandler {
|
||||
async _prepare({ domainId, pid }) {
|
||||
@param('pid', Types.String, null, parsePid)
|
||||
async _prepare(domainId: string, pid: number | string) {
|
||||
this.response.template = 'problem_detail.html';
|
||||
if (pid) {
|
||||
this.pdoc = await problem.get(domainId, pid, this.user._id);
|
||||
@@ -158,7 +175,7 @@ class ProblemDetailHandler extends ProblemHandler {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async get(args: any) {
|
||||
async get(...args: any) {
|
||||
this.response.body.path = [
|
||||
['Hydro', 'homepage'],
|
||||
['problem_main', 'problem_main'],
|
||||
@@ -168,9 +185,11 @@ class ProblemDetailHandler extends ProblemHandler {
|
||||
}
|
||||
|
||||
class ProblemSubmitHandler extends ProblemDetailHandler {
|
||||
async get({ domainId, pid }) {
|
||||
@param('pid', Types.String, null, parsePid)
|
||||
async get(domainId: string, pid: string | number) {
|
||||
this.response.template = 'problem_submit.html';
|
||||
const rdocs = await record.getUserInProblemMulti(domainId, this.user._id, this.pdoc.docId)
|
||||
const rdocs = await record
|
||||
.getUserInProblemMulti(domainId, this.user._id, this.pdoc.docId)
|
||||
.sort({ _id: -1 })
|
||||
.limit(10)
|
||||
.toArray();
|
||||
@@ -188,8 +207,9 @@ class ProblemSubmitHandler extends ProblemDetailHandler {
|
||||
};
|
||||
}
|
||||
|
||||
async post({ domainId }) {
|
||||
const { lang, code } = this.request.body;
|
||||
@param('lang', Types.String)
|
||||
@param('code', Types.String)
|
||||
async post(domainId: string, lang: string, code: string) {
|
||||
const rid = await record.add(domainId, {
|
||||
uid: this.user._id, lang, code, pid: this.pdoc.docId,
|
||||
}, true);
|
||||
@@ -200,9 +220,10 @@ class ProblemSubmitHandler extends ProblemDetailHandler {
|
||||
}
|
||||
|
||||
class ProblemPretestHandler extends ProblemDetailHandler {
|
||||
async post({
|
||||
domainId, lang, code, input,
|
||||
}) {
|
||||
@param('lang', Types.String)
|
||||
@param('code', Types.String)
|
||||
@param('input', Types.String)
|
||||
async post(domainId: string, lang: string, code: string, input: string) {
|
||||
this.limitRate('add_record', 60, 100);
|
||||
const rid = await record.add(domainId, {
|
||||
uid: this.user._id, lang, code, pid: this.pdoc.docId, input,
|
||||
@@ -213,8 +234,9 @@ class ProblemPretestHandler extends ProblemDetailHandler {
|
||||
}
|
||||
|
||||
class ProblemPretestConnectionHandler extends ConnectionHandler {
|
||||
async prepare({ domainId, pid }) {
|
||||
this.pid = pid.toString();
|
||||
@param('pid', Types.String)
|
||||
async prepare(domainId: string, pid: string) {
|
||||
this.pid = pid;
|
||||
this.domainId = domainId;
|
||||
bus.subscribe(['record_change'], this, 'onRecordChange');
|
||||
}
|
||||
@@ -236,7 +258,7 @@ class ProblemPretestConnectionHandler extends ConnectionHandler {
|
||||
}
|
||||
|
||||
class ProblemStatisticsHandler extends ProblemDetailHandler {
|
||||
async get({ domainId }) {
|
||||
async get(domainId: string) {
|
||||
const udoc = await user.getById(domainId, this.pdoc.owner);
|
||||
const path = [
|
||||
['problem_main', 'problem_main'],
|
||||
@@ -256,7 +278,8 @@ class ProblemManageHandler extends ProblemDetailHandler {
|
||||
}
|
||||
|
||||
class ProblemSettingsHandler extends ProblemManageHandler {
|
||||
async get({ pid }) {
|
||||
@param('pid', Types.String)
|
||||
async get(domainId: string, pid: string) {
|
||||
this.response.template = 'problem_settings.html';
|
||||
this.response.body.path = [
|
||||
['Hydro', 'homepage'],
|
||||
@@ -266,15 +289,24 @@ class ProblemSettingsHandler extends ProblemManageHandler {
|
||||
];
|
||||
}
|
||||
|
||||
async postConfig({ domainId, pid, yaml }) {
|
||||
await problem.edit(domainId, pid, { config: yaml });
|
||||
@param('pid', Types.String, null, parsePid)
|
||||
@param('yaml', Types.String)
|
||||
async postConfig(domainId: string, pid: string | number, yaml: string) {
|
||||
const pdoc = await problem.get(domainId, pid);
|
||||
await problem.edit(domainId, pdoc.docId, { config: yaml });
|
||||
this.back();
|
||||
}
|
||||
|
||||
async postSetting({
|
||||
domainId, pid, hidden = false, category, tag,
|
||||
}) {
|
||||
await problem.edit(domainId, pid, { hidden, category, tag });
|
||||
@param('pid', Types.String, null, parsePid)
|
||||
@param('hidden', Types.Boolean)
|
||||
@param('category', Types.String, null, parseCategory)
|
||||
@param('tag', Types.String, null, parseCategory)
|
||||
async postSetting(
|
||||
domainId: string, pid: string | number, hidden = false,
|
||||
category: string[], tag: string[],
|
||||
) {
|
||||
const pdoc = await problem.get(domainId, pid);
|
||||
await problem.edit(domainId, pdoc.docId, { hidden, category, tag });
|
||||
this.back();
|
||||
}
|
||||
}
|
||||
@@ -291,8 +323,10 @@ class ProblemEditHandler extends ProblemManageHandler {
|
||||
this.response.body.page_name = 'problem_edit';
|
||||
}
|
||||
|
||||
async post({ domainId, title, content }) {
|
||||
const pid = validator.checkPid(this.request.body.pid);
|
||||
@param('title', Types.String, isTitle)
|
||||
@param('content', Types.String, isContent)
|
||||
async post(domainId: string, title: string, content: string) {
|
||||
const pid = checkPid(this.request.body.pid);
|
||||
const pdoc = await problem.get(domainId, this.request.params.pid);
|
||||
await problem.edit(domainId, pdoc.docId, { title, content, pid });
|
||||
this.back();
|
||||
@@ -314,7 +348,9 @@ class ProblemDataUploadHandler extends ProblemManageHandler {
|
||||
|
||||
async post({ domainId }) {
|
||||
if (!this.request.files.file) throw new BadRequestError();
|
||||
await problem.setTestdata(domainId, this.pdoc.docId, this.request.files.file.path);
|
||||
await problem.setTestdata(
|
||||
domainId, this.pdoc.docId, this.request.files.file.path,
|
||||
);
|
||||
if (this.pdoc.data && typeof this.pdoc.data === 'object') {
|
||||
const f = await file.getMeta(this.pdoc.data);
|
||||
this.md5 = f.md5;
|
||||
@@ -337,7 +373,8 @@ class ProblemDataDownloadHandler extends ProblemDetailHandler {
|
||||
}
|
||||
|
||||
class ProblemSolutionHandler extends ProblemDetailHandler {
|
||||
async get({ domainId, page = 1 }) {
|
||||
@param('page', Types.UnsignedInt, true)
|
||||
async get(domainId: string, page = 1) {
|
||||
this.response.template = 'problem_solution.html';
|
||||
this.checkPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION);
|
||||
const [psdocs, pcount, pscount] = await paginate(
|
||||
@@ -367,21 +404,22 @@ class ProblemSolutionHandler extends ProblemDetailHandler {
|
||||
}
|
||||
|
||||
async post({ domainId, psid }) {
|
||||
if (psid) this.psdoc = await solution.get(domainId, psid);
|
||||
if (psid) this.psdoc = await solution.get(domainId, new ObjectID(psid));
|
||||
}
|
||||
|
||||
async postSubmit({ domainId, content }) {
|
||||
@param('content', Types.String, isContent)
|
||||
async postSubmit(domainId: string, content: string) {
|
||||
this.checkPerm(PERM.PERM_CREATE_PROBLEM_SOLUTION);
|
||||
await solution.add(domainId, this.pdoc.docId, this.user._id, content);
|
||||
this.back();
|
||||
}
|
||||
|
||||
async postEditSolution({ domainId, content }) {
|
||||
@param('content', Types.String, isContent)
|
||||
async postEditSolution(domainId: string, content: string) {
|
||||
if (this.psdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION);
|
||||
else this.checkPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION_SELF);
|
||||
this.psdoc = await solution.edit(domainId, this.psdoc.docId, content);
|
||||
this.ctx.body.psdoc = this.psdoc;
|
||||
this.back();
|
||||
const psdoc = await solution.edit(domainId, this.psdoc.docId, content);
|
||||
this.back({ psdoc });
|
||||
}
|
||||
|
||||
async postDeleteSolution({ domainId }) {
|
||||
@@ -391,16 +429,19 @@ class ProblemSolutionHandler extends ProblemDetailHandler {
|
||||
this.back();
|
||||
}
|
||||
|
||||
async postReply({ domainId, psid, content }) {
|
||||
@param('psid', Types.ObjectID)
|
||||
@param('content', Types.String, isContent)
|
||||
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();
|
||||
}
|
||||
|
||||
async postEditReply({
|
||||
domainId, content, psid, psrid,
|
||||
}) {
|
||||
@param('psid', Types.ObjectID)
|
||||
@param('psrid', Types.ObjectID)
|
||||
@param('content', Types.String, isContent)
|
||||
async postEditReply(domainId: string, psid: ObjectID, psrid: ObjectID, content: string) {
|
||||
const [psdoc, psrdoc] = await solution.getReply(domainId, psid, psrid);
|
||||
if ((!psdoc) || psdoc.pid !== this.pdoc.docId) throw new SolutionNotFoundError(psid);
|
||||
if (!(psdoc.owner === this.user._id
|
||||
@@ -414,7 +455,9 @@ class ProblemSolutionHandler extends ProblemDetailHandler {
|
||||
this.back();
|
||||
}
|
||||
|
||||
async postDeleteReply({ domainId, psid, psrid }) {
|
||||
@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.pid !== this.pdoc.docId) throw new SolutionNotFoundError(psid);
|
||||
if (!(psdoc.owner === this.user._id
|
||||
@@ -442,7 +485,8 @@ class ProblemSolutionHandler extends ProblemDetailHandler {
|
||||
}
|
||||
|
||||
class ProblemSolutionRawHandler extends ProblemDetailHandler {
|
||||
async get({ domainId, psid }) {
|
||||
@param('psid', Types.ObjectID)
|
||||
async get(domainId: string, psid: ObjectID) {
|
||||
this.checkPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION);
|
||||
const psdoc = await solution.get(domainId, psid);
|
||||
this.response.type = 'text/markdown';
|
||||
@@ -451,7 +495,9 @@ class ProblemSolutionRawHandler extends ProblemDetailHandler {
|
||||
}
|
||||
|
||||
class ProblemSolutionReplyRawHandler extends ProblemDetailHandler {
|
||||
async get({ domainId, psid, psrid }) {
|
||||
@param('psid', Types.ObjectID)
|
||||
@param('psrid', Types.ObjectID)
|
||||
async get(domainId: string, psid: ObjectID, psrid: ObjectID) {
|
||||
this.checkPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION);
|
||||
const [psdoc, psrdoc] = await solution.getReply(domainId, psid, psrid);
|
||||
if ((!psdoc) || psdoc.pid !== this.pdoc.docId) throw new SolutionNotFoundError(psid, psrid);
|
||||
@@ -474,9 +520,11 @@ class ProblemCreateHandler extends Handler {
|
||||
};
|
||||
}
|
||||
|
||||
async post({
|
||||
domainId, title, pid, content, hidden,
|
||||
}) {
|
||||
@param('title', Types.String, isTitle)
|
||||
@param('pid', Types.String, isPid)
|
||||
@param('content', Types.String, isContent)
|
||||
@param('hidden', Types.Boolean)
|
||||
async post(domainId: string, title: string, pid: string, content: string, hidden = false) {
|
||||
pid = await problem.add(domainId, title, content, this.user._id, {
|
||||
pid, hidden,
|
||||
});
|
||||
@@ -486,7 +534,7 @@ class ProblemCreateHandler extends Handler {
|
||||
}
|
||||
|
||||
export async function apply() {
|
||||
Route('problem_main', '/p', ProblemHandler);
|
||||
Route('problem_main', '/p', ProblemMainHandler);
|
||||
Route('problem_category', '/p/category/:category', ProblemCategoryHandler);
|
||||
Route('problem_random', '/problem/random', ProblemRandomHandler);
|
||||
Route('problem_detail', '/p/:pid', ProblemDetailHandler);
|
||||
|
||||
+16
-9
@@ -9,16 +9,19 @@ import * as user from '../model/user';
|
||||
import paginate from '../lib/paginate';
|
||||
import * as bus from '../service/bus';
|
||||
import {
|
||||
Route, Handler, Connection, ConnectionHandler,
|
||||
Route, Handler, Connection, ConnectionHandler, Types, param,
|
||||
} from '../service/server';
|
||||
|
||||
const RecordHandler = contest.ContestHandlerMixin(Handler);
|
||||
|
||||
class RecordListHandler extends RecordHandler {
|
||||
async get({
|
||||
domainId, page = 1, pid, tid, uidOrName,
|
||||
}) {
|
||||
@param('page', Types.UnsignedInt, true)
|
||||
@param('pid', Types.String, true)
|
||||
@param('tid', Types.ObjectID, true)
|
||||
@param('uidOrName', Types.String, true)
|
||||
async get(domainId: string, page = 1, pid?: string, tid?: ObjectID, uidOrName?: string) {
|
||||
this.response.template = 'record_main.html';
|
||||
if (tid) tid = new ObjectID(tid);
|
||||
const q: any = { tid, hidden: false };
|
||||
if (uidOrName) {
|
||||
q.$or = [
|
||||
@@ -54,7 +57,8 @@ class RecordListHandler extends RecordHandler {
|
||||
}
|
||||
|
||||
class RecordDetailHandler extends RecordHandler {
|
||||
async get({ domainId, rid }) {
|
||||
@param('rid', Types.ObjectID)
|
||||
async get(domainId: string, rid: ObjectID) {
|
||||
this.response.template = 'record_detail.html';
|
||||
const rdoc = await record.get(domainId, rid);
|
||||
if (rdoc.tid) {
|
||||
@@ -84,7 +88,8 @@ class RecordDetailHandler extends RecordHandler {
|
||||
}
|
||||
|
||||
class RecordRejudgeHandler extends Handler {
|
||||
async post({ domainId, rid }) {
|
||||
@param('rid', Types.ObjectID)
|
||||
async post(domainId: string, rid: ObjectID) {
|
||||
this.checkPerm(PERM.PERM_REJUDGE);
|
||||
const rdoc = await record.get(domainId, rid);
|
||||
if (rdoc) await record.rejudge(domainId, rid);
|
||||
@@ -95,10 +100,11 @@ class RecordRejudgeHandler extends Handler {
|
||||
const RecordConnectionHandler = contest.ContestHandlerMixin(ConnectionHandler);
|
||||
|
||||
class RecordMainConnectionHandler extends RecordConnectionHandler {
|
||||
async prepare({ domainId, tid }) {
|
||||
@param('tid', Types.ObjectID)
|
||||
async prepare(domainId: string, tid: ObjectID) {
|
||||
this.domainId = domainId;
|
||||
if (tid) {
|
||||
const tdoc = await contest.get(domainId, tid, -1);
|
||||
const tdoc = await contest.get(domainId, new ObjectID(tid), -1);
|
||||
if (this.canShowRecord(tdoc)) this.tid = tid;
|
||||
else {
|
||||
this.close();
|
||||
@@ -137,7 +143,8 @@ class RecordMainConnectionHandler extends RecordConnectionHandler {
|
||||
}
|
||||
|
||||
class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(ConnectionHandler) {
|
||||
async prepare({ domainId, rid }) {
|
||||
@param('rid', Types.ObjectID)
|
||||
async prepare(domainId: string, rid: ObjectID) {
|
||||
const rdoc = await record.get(domainId, rid);
|
||||
if (rdoc.tid) {
|
||||
const tdoc = await contest.get(domainId, rdoc.tid, -1);
|
||||
|
||||
+33
-15
@@ -1,6 +1,7 @@
|
||||
import assert from 'assert';
|
||||
import { ObjectID } from 'mongodb';
|
||||
import { ValidationError, ProblemNotFoundError } from '../error';
|
||||
import { isTitle, isContent, isDescription } from '../lib/validator';
|
||||
import paginate from '../lib/paginate';
|
||||
import { PERM, PRIV } from '../model/builtin';
|
||||
import * as problem from '../model/problem';
|
||||
@@ -8,7 +9,9 @@ import * as builtin from '../model/builtin';
|
||||
import * as training from '../model/training';
|
||||
import * as user from '../model/user';
|
||||
import * as system from '../model/system';
|
||||
import { Route, Handler } from '../service/server';
|
||||
import {
|
||||
Route, Handler, Types, param,
|
||||
} from '../service/server';
|
||||
|
||||
async function _parseDagJson(domainId, dag) {
|
||||
const parsed = [];
|
||||
@@ -51,8 +54,8 @@ async function _parseDagJson(domainId, dag) {
|
||||
}
|
||||
|
||||
class TrainingMainHandler extends Handler {
|
||||
async get({ domainId, sort, page }) {
|
||||
const qs = sort ? 'sort={0}'.format(sort) : '';
|
||||
@param('page', Types.UnsignedInt, true)
|
||||
async get(domainId: string, page = 1) {
|
||||
const [tdocs, tpcount] = await paginate(
|
||||
training.getMulti(domainId).sort('_id', 1),
|
||||
page,
|
||||
@@ -84,13 +87,14 @@ class TrainingMainHandler extends Handler {
|
||||
];
|
||||
this.response.template = 'training_main.html';
|
||||
this.response.body = {
|
||||
tdocs, page, tpcount, qs, tsdict, tdict, path,
|
||||
tdocs, page, tpcount, tsdict, tdict, path,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class TrainingDetailHandler extends Handler {
|
||||
async get({ domainId, tid }) {
|
||||
@param('tid', Types.ObjectID)
|
||||
async get(domainId: string, tid: ObjectID) {
|
||||
const tdoc = await training.get(domainId, tid);
|
||||
const pids = training.getPids(tdoc);
|
||||
const [owner, pdict] = await Promise.all([
|
||||
@@ -141,7 +145,8 @@ class TrainingDetailHandler extends Handler {
|
||||
};
|
||||
}
|
||||
|
||||
async postEnroll({ domainId, tid }) {
|
||||
@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);
|
||||
@@ -160,10 +165,15 @@ class TrainingCreateHandler extends Handler {
|
||||
this.response.body = { page_name: 'training_create', path };
|
||||
}
|
||||
|
||||
async post({
|
||||
domainId, title, content, dag, description,
|
||||
}) {
|
||||
dag = await _parseDagJson(domainId, dag);
|
||||
@param('title', Types.String, isTitle)
|
||||
@param('content', Types.String, isContent)
|
||||
@param('dag', Types.String, isContent)
|
||||
@param('description', Types.String, isDescription)
|
||||
async post(
|
||||
domainId: string, title: string, content: string,
|
||||
_dag: string, description: string,
|
||||
) {
|
||||
const dag = await _parseDagJson(domainId, _dag);
|
||||
const pids = training.getPids({ dag });
|
||||
assert(pids.length, new ValidationError('dag'));
|
||||
const pdocs = await problem.getMulti(domainId, {
|
||||
@@ -189,7 +199,8 @@ class TrainingCreateHandler extends Handler {
|
||||
}
|
||||
|
||||
class TrainingEditHandler extends Handler {
|
||||
async prepare({ domainId, tid }) {
|
||||
@param('tid', Types.ObjectID)
|
||||
async prepare(domainId: string, tid: ObjectID) {
|
||||
this.tdoc = await training.get(domainId, tid);
|
||||
if (this.tdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_TRAINING);
|
||||
else this.checkPerm(PERM.PERM_EDIT_TRAINING_SELF);
|
||||
@@ -209,10 +220,17 @@ class TrainingEditHandler extends Handler {
|
||||
};
|
||||
}
|
||||
|
||||
async post({
|
||||
domainId, tid, title, content, dag, description,
|
||||
}) {
|
||||
dag = await _parseDagJson(domainId, dag);
|
||||
@param('tid', Types.ObjectID)
|
||||
@param('title', Types.String, isTitle)
|
||||
@param('content', Types.String, isContent)
|
||||
@param('dag', Types.String, isContent)
|
||||
@param('description', Types.String, isDescription)
|
||||
async post(
|
||||
domainId: string, tid: ObjectID,
|
||||
title: string, content: string,
|
||||
_dag: string, description: string,
|
||||
) {
|
||||
const dag = await _parseDagJson(domainId, _dag);
|
||||
const pids = training.getPids({ dag });
|
||||
assert(pids.length, new ValidationError('dag'));
|
||||
const pdocs = await problem.getMulti(domainId, {
|
||||
|
||||
+29
-12
@@ -1,6 +1,8 @@
|
||||
import superagent from 'superagent';
|
||||
import moment from 'moment-timezone';
|
||||
import { Route, Handler } from '../service/server';
|
||||
import {
|
||||
Route, Handler, Types, param,
|
||||
} from '../service/server';
|
||||
import * as user from '../model/user';
|
||||
import * as token from '../model/token';
|
||||
import * as record from '../model/record';
|
||||
@@ -8,6 +10,7 @@ import * as problem from '../model/problem';
|
||||
import * as task from '../model/task';
|
||||
import * as system from '../model/system';
|
||||
import { PERM, PRIV } from '../model/builtin';
|
||||
import { isEmail, isPassword, isUname } from '../lib/validator';
|
||||
import { sendMail } from '../lib/mail';
|
||||
import * as misc from '../lib/misc';
|
||||
import {
|
||||
@@ -27,9 +30,10 @@ class UserLoginHandler extends Handler {
|
||||
this.response.template = 'user_login.html';
|
||||
}
|
||||
|
||||
async post({
|
||||
domainId, uname, password, rememberme = false,
|
||||
}) {
|
||||
@param('uname', Types.String)
|
||||
@param('password', Types.String)
|
||||
@param('rememberme', Types.Boolean, true)
|
||||
async post(domainId: string, uname: string, password: string, rememberme = false) {
|
||||
const udoc = await user.getByUname(domainId, uname);
|
||||
if (!udoc) throw new LoginError(uname);
|
||||
udoc.checkPassword(password);
|
||||
@@ -56,7 +60,8 @@ class UserRegisterHandler extends Handler {
|
||||
this.response.template = 'user_register.html';
|
||||
}
|
||||
|
||||
async post({ mail }) {
|
||||
@param('mail', Types.String, isEmail)
|
||||
async post(domainId: string, mail: string) {
|
||||
if (await user.getByEmail('system', mail, true)) throw new UserAlreadyExistError(mail);
|
||||
this.limitRate('send_mail', 3600, 30);
|
||||
const t = await token.add(
|
||||
@@ -85,9 +90,14 @@ class UserRegisterWithCodeHandler extends Handler {
|
||||
this.response.body = { mail };
|
||||
}
|
||||
|
||||
async post({
|
||||
code, password, verifyPassword, uname,
|
||||
}) {
|
||||
@param('password', Types.String, isPassword)
|
||||
@param('verifyPassword', Types.String)
|
||||
@param('uname', Types.String, isUname)
|
||||
@param('code', Types.String)
|
||||
async post(
|
||||
domainId: string, password: string, verifyPassword: string,
|
||||
uname: string, code: string,
|
||||
) {
|
||||
const { mail } = await token.get(code, token.TYPE_REGISTRATION);
|
||||
if (!mail) throw new InvalidTokenError(token.TYPE_REGISTRATION, code);
|
||||
if (password !== verifyPassword) throw new VerifyPasswordError();
|
||||
@@ -107,7 +117,8 @@ class UserLostPassHandler extends Handler {
|
||||
this.response.template = 'user_lostpass.html';
|
||||
}
|
||||
|
||||
async post({ mail }) {
|
||||
@param('mail', Types.String, isEmail)
|
||||
async post(domainId: string, mail: string) {
|
||||
if (!await system.get('smtp.user')) throw new SystemError('Cannot send mail');
|
||||
const udoc = await user.getByEmail('system', mail);
|
||||
if (!udoc) throw new UserNotFoundError(mail);
|
||||
@@ -130,7 +141,10 @@ class UserLostPassWithCodeHandler extends Handler {
|
||||
this.response.body = { uname: udoc.uname };
|
||||
}
|
||||
|
||||
async post({ code, password, verifyPassword }) {
|
||||
@param('code', Types.String)
|
||||
@param('password', Types.String, isPassword)
|
||||
@param('verifyPassword', Types.String)
|
||||
async post(domainId: string, code: string, password: string, verifyPassword: string) {
|
||||
const tdoc = await token.get(code, token.TYPE_LOSTPASS);
|
||||
if (!tdoc) throw new InvalidTokenError(token.TYPE_LOSTPASS, code);
|
||||
if (password !== verifyPassword) throw new VerifyPasswordError();
|
||||
@@ -141,7 +155,8 @@ class UserLostPassWithCodeHandler extends Handler {
|
||||
}
|
||||
|
||||
class UserDetailHandler extends Handler {
|
||||
async get({ domainId, uid }) {
|
||||
@param('uid', Types.Int)
|
||||
async get(domainId: string, uid: number) {
|
||||
const isSelfProfile = this.user._id === uid;
|
||||
const udoc = await user.getById(domainId, uid, true);
|
||||
const sdoc = await token.getMostRecentSessionByUid(uid);
|
||||
@@ -178,7 +193,9 @@ class UserDeleteHandler extends Handler {
|
||||
}
|
||||
|
||||
class UserSearchHandler extends Handler {
|
||||
async get({ domainId, q, exactMatch = false }) {
|
||||
@param('q', Types.String)
|
||||
@param('exectMatch', Types.Boolean, true)
|
||||
async get(domainId: string, q: string, exactMatch = false) {
|
||||
let udocs;
|
||||
if (exactMatch) udocs = [];
|
||||
else udocs = await user.getPrefixList(q, 20);
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { NotFoundError } from '../error';
|
||||
import { Route, Handler } from '../service/server';
|
||||
import {
|
||||
Route, Handler, Types, param,
|
||||
} from '../service/server';
|
||||
|
||||
class WikiHandler extends Handler {
|
||||
async get({ category = 'wiki', page }) {
|
||||
@param('category', Types.String, true)
|
||||
@param('page', Types.String)
|
||||
async get(domainId: string, category = 'wiki', page: string) {
|
||||
if (!global.Hydro.wiki[category]) throw new NotFoundError(category);
|
||||
if (!global.Hydro.wiki[category][page]) throw new NotFoundError(category, page);
|
||||
const contents = global.Hydro.wiki[category][page];
|
||||
|
||||
+31
-22
@@ -8,9 +8,12 @@ const RE_ROLE = /^[_0-9A-Za-z]{1,256}$/i;
|
||||
const RE_MAIL = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/i;
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
function _validate(scheme: any, arg: any, base: string) {
|
||||
function _validate(scheme: any, arg: any, base: string): void {
|
||||
if (!(scheme instanceof Object)) return _validate({ $type: scheme }, arg, base);
|
||||
if (scheme.$validate) scheme.$validate(arg);
|
||||
if (scheme.$validate) {
|
||||
const result = scheme.$validate(arg);
|
||||
if (result === false) throw new ValidationError(base);
|
||||
}
|
||||
if (scheme.$group) _validate(scheme.$group, arg, base);
|
||||
if (scheme.$or) {
|
||||
if (scheme.$or instanceof Array) {
|
||||
@@ -38,11 +41,15 @@ function _validate(scheme: any, arg: any, base: string) {
|
||||
if (scheme.$and) for (const s of scheme.$and) _validate(s, arg, base);
|
||||
if (scheme.$type) {
|
||||
if (typeof scheme.$type === 'string') {
|
||||
if (!typeof arg === scheme.$type) {
|
||||
throw new ValidationError();
|
||||
if (!scheme.$type.endsWith('?')) {
|
||||
if (!typeof arg === scheme.$type) {
|
||||
throw new ValidationError(base);
|
||||
}
|
||||
} else if (arg && !scheme.$type.includes(typeof arg)) {
|
||||
throw new ValidationError(base);
|
||||
}
|
||||
} else if (!(arg instanceof scheme.$type)) {
|
||||
throw new ValidationError();
|
||||
throw new ValidationError(base);
|
||||
}
|
||||
}
|
||||
if (scheme.$eq && scheme.$eq !== arg) throw new ValidationError(base);
|
||||
@@ -54,30 +61,32 @@ function _validate(scheme: any, arg: any, base: string) {
|
||||
if (arg instanceof Object) {
|
||||
_validate(scheme[key], arg[key], `${base}.${key}`);
|
||||
} else {
|
||||
throw new ValidationError();
|
||||
throw new ValidationError(base);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function descriptor(scheme: any) {
|
||||
return function desc(target: any, name: string, descriptor: any) {
|
||||
const originalMethod = descriptor.value;
|
||||
descriptor.value = function val(...args: any[]) {
|
||||
function descriptor(scheme: any): Function {
|
||||
return function desc(target: any, name: string, obj: any) {
|
||||
const originalMethod = obj.value;
|
||||
obj.value = function func(...args: any[]) {
|
||||
_validate(scheme, args[0], '');
|
||||
return originalMethod.call(this, ...args);
|
||||
};
|
||||
return descriptor;
|
||||
return obj;
|
||||
};
|
||||
}
|
||||
|
||||
export function validate(scheme: any, arg: any) {
|
||||
if (arguments.length === 1) return descriptor(scheme);
|
||||
return _validate(scheme, arg, '');
|
||||
export function validate(scheme: any): Function;
|
||||
export function validate(scheme: any, arg: any): void;
|
||||
export function validate(...args: any[]): any {
|
||||
if (args.length === 1) return descriptor(args[0]);
|
||||
return _validate(args[0], args[1], '');
|
||||
}
|
||||
|
||||
export const isTitle = (s) => s && s.length < 64;
|
||||
export const checkTitle = (s) => { if (!isTitle(s)) throw new ValidationError('title'); else return s; };
|
||||
export const checkTitle = (s) => { if (!(s && s.length < 64)) throw new ValidationError('title'); else return s; };
|
||||
export const isUid = (s) => RE_UID.test(s);
|
||||
export const checkUid = (s) => { if (!isUid(s)) throw new ValidationError('uid'); else return s; };
|
||||
export const isUname = (s) => RE_UNAME.test(s);
|
||||
@@ -85,22 +94,22 @@ export const checkUname = (s) => { if (!isUname(s)) throw new ValidationError('u
|
||||
export const isRole = (s) => RE_ROLE.test(s);
|
||||
export const checkRole = (s) => { if (!isRole(s)) throw new ValidationError('role'); else return s; };
|
||||
export const isPassword = (s) => s.length >= 5;
|
||||
export const checkPassword = (s) => { if (!isPassword(s)) throw new ValidationError('password'); else return s; };
|
||||
export const checkPassword = (s) => { if (!(s && s.length >= 5)) throw new ValidationError('password'); else return s; };
|
||||
export const isEmail = (s) => RE_MAIL.test(s);
|
||||
export const checkEmail = (s) => { if (!isEmail(s)) throw new ValidationError('mail'); else return s; };
|
||||
export const isContent = (s) => s && s.length < 65536;
|
||||
export const checkContent = (s) => { if (!isContent(s)) throw new ValidationError('content'); else return s; };
|
||||
export const checkEmail = (s) => { if (!RE_MAIL.test(s)) throw new ValidationError('mail'); else return s; };
|
||||
export const isContent = (s: any) => s && s.length < 65536;
|
||||
export const checkContent = (s) => { if (!(s && s.length < 65536)) throw new ValidationError('content'); else return s; };
|
||||
export const isName = (s) => s && s.length < 256;
|
||||
export const checkName = (s) => { if (!isName(s)) throw new ValidationError('name'); else return s; };
|
||||
export const isPid = (s) => RE_PID.test(s.toString());
|
||||
export const checkPid = (s) => { if (!RE_PID.test(s)) throw new ValidationError('pid'); else return s; };
|
||||
export const isIntro = () => true;
|
||||
export const checkIntro = (s) => { if (!isIntro()) throw new ValidationError('intro'); else return s; };
|
||||
export const isDescription = () => true;
|
||||
export const checkDescription = (s) => { if (!isDescription()) throw new ValidationError('description'); else return s; };
|
||||
export const isDescription = (s: any) => s && s.length < 65536;
|
||||
export const checkDescription = (s) => { if (!(s && s.length < 65536)) throw new ValidationError('description'); else return s; };
|
||||
|
||||
global.Hydro.lib.validator = {
|
||||
validate: _validate,
|
||||
validate,
|
||||
isTitle,
|
||||
checkTitle,
|
||||
isUid,
|
||||
|
||||
@@ -511,9 +511,11 @@ export const ContestHandlerMixin = (c) => class extends c {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
async verifyProblems(domainId: string, pids: number[]) {
|
||||
await problem.getList(domainId, pids, true);
|
||||
return pids;
|
||||
async verifyProblems(domainId: string, pids: Array<number | string>) {
|
||||
const pdict = await problem.getList(domainId, pids, true);
|
||||
const _pids: Set<number> = new Set();
|
||||
for (const i in pdict) _pids.add(pdict[i].docId);
|
||||
return Array.from(_pids);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -116,7 +116,10 @@ export async function random<T>(domainId: string, query: FilterQuery<T>) {
|
||||
} return null;
|
||||
}
|
||||
|
||||
export async function getList(domainId: string, pids: number[], getHidden = false, doThrow = true) {
|
||||
export async function getList(
|
||||
domainId: string, pids: Array<number | string>,
|
||||
getHidden = false, doThrow = true,
|
||||
) {
|
||||
pids = Array.from(new Set(pids));
|
||||
const r = {};
|
||||
const q: any = { $or: [{ docId: { $in: pids } }, { pid: { $in: pids } }] };
|
||||
|
||||
@@ -62,6 +62,7 @@ async function _user(
|
||||
}
|
||||
|
||||
export async function run({
|
||||
// eslint-disable-next-line no-shadow
|
||||
address = null, discuss = null, user = null, dryrun = true,
|
||||
}, report) {
|
||||
if (address) await _address(address, new Set(), new Set(), new Set(), dryrun, report);
|
||||
|
||||
+149
-129
@@ -1,31 +1,29 @@
|
||||
/* eslint-disable prefer-destructuring */
|
||||
import assert from 'assert';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import http from 'http';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment-timezone';
|
||||
import { isSafeInteger } from 'lodash';
|
||||
import { ObjectID } from 'mongodb';
|
||||
import Koa from 'koa';
|
||||
import yaml from 'js-yaml';
|
||||
import morgan from 'koa-morgan';
|
||||
import Body from 'koa-body';
|
||||
import Router from 'koa-router';
|
||||
import cache from 'koa-static-cache';
|
||||
import sockjs from 'sockjs';
|
||||
|
||||
import { render } from '../lib/template';
|
||||
import * as validator from '../lib/validator';
|
||||
import * as misc from '../lib/misc';
|
||||
import * as user from '../model/user';
|
||||
import * as system from '../model/system';
|
||||
import * as blacklist from '../model/blacklist';
|
||||
import * as token from '../model/token';
|
||||
import * as opcount from '../model/opcount';
|
||||
|
||||
const {
|
||||
import {
|
||||
UserNotFoundError, BlacklistedError, PermissionError,
|
||||
UserFacingError, ValidationError, PrivilegeError,
|
||||
CsrfTokenError, InvalidOperationError, MethodNotAllowedError,
|
||||
} = require('../error');
|
||||
} from '../error';
|
||||
|
||||
let enableLog = true;
|
||||
|
||||
@@ -33,86 +31,141 @@ const app = new Koa();
|
||||
let server;
|
||||
const router = new Router();
|
||||
|
||||
const _validateObjectId = (id: string | ObjectID, key: string) => {
|
||||
if (ObjectID.isValid(id)) return new ObjectID(id);
|
||||
throw new ValidationError(key);
|
||||
};
|
||||
const _bool = (val: any) => !!val;
|
||||
const _splitAndTrim = (val: string) => {
|
||||
const t = val.split(',').map((i) => i.trim().split('+'));
|
||||
return _.flatten(t);
|
||||
};
|
||||
const _date = (val: string) => {
|
||||
const d = val.split('-');
|
||||
assert(d.length === 3);
|
||||
return `${d[0]}-${d[1].length === 1 ? '0' : ''}${d[1]}-${d[2].length === 1 ? '0' : ''}${d[2]}`;
|
||||
};
|
||||
const _time = (val: string) => {
|
||||
const t = val.split(':');
|
||||
assert(t.length === 2);
|
||||
return `${(t[0].length === 1 ? '0' : '') + t[0]}:${t[1].length === 1 ? '0' : ''}${t[1]}`;
|
||||
};
|
||||
interface IConstructor {
|
||||
// @ts-ignore
|
||||
new(value: any): any;
|
||||
}
|
||||
interface IHandler {
|
||||
// @ts-ignore
|
||||
new(ctx: Koa.Context): Handler;
|
||||
}
|
||||
interface IConnectionHandler {
|
||||
// @ts-ignore
|
||||
new(conn: sockjs.Connection): ConnectionHandler;
|
||||
}
|
||||
type MethodDecorator = (target: any, name: string, obj: any) => any;
|
||||
type Converter = IConstructor | ((value: any) => any);
|
||||
type Validator = (value: any) => boolean;
|
||||
interface ParamOption {
|
||||
name: string,
|
||||
isOptional?: boolean,
|
||||
convert?: Converter,
|
||||
validate?: Validator,
|
||||
}
|
||||
|
||||
const validate = {
|
||||
tid: _validateObjectId,
|
||||
rid: _validateObjectId,
|
||||
did: _validateObjectId,
|
||||
drid: _validateObjectId,
|
||||
drrid: _validateObjectId,
|
||||
psid: _validateObjectId,
|
||||
psrid: _validateObjectId,
|
||||
docId: _validateObjectId,
|
||||
mongoId: _validateObjectId,
|
||||
hidden: _bool,
|
||||
rated: _bool,
|
||||
category: _splitAndTrim,
|
||||
tag: _splitAndTrim,
|
||||
beginAtDate: _date,
|
||||
beginAtTime: _time,
|
||||
pid: (pid: string) => (Number.isSafeInteger(parseInt(pid, 10)) ? parseInt(pid, 10) : pid),
|
||||
content: validator.checkContent,
|
||||
title: validator.checkTitle,
|
||||
uid: (uid: string) => parseInt(validator.checkUid(uid), 10),
|
||||
password: validator.checkPassword,
|
||||
mail: validator.checkEmail,
|
||||
uname: validator.checkUname,
|
||||
page: (_page: string) => {
|
||||
let page: number;
|
||||
if (Number.isSafeInteger(parseInt(_page, 10))) page = parseInt(_page, 10);
|
||||
if (page <= 0) throw new ValidationError('page');
|
||||
return page;
|
||||
},
|
||||
duration: (_duration: string) => {
|
||||
let duration: number;
|
||||
if (!Number.isNaN(parseFloat(_duration))) duration = parseFloat(_duration);
|
||||
if (duration <= 0) throw new ValidationError('duration');
|
||||
return duration;
|
||||
},
|
||||
pids: (pids: string) => {
|
||||
const src = pids.split(',').map((i) => i.trim());
|
||||
const d: number[] = [];
|
||||
for (const i in src) {
|
||||
if (Number.isSafeInteger(parseInt(src[i], 10))) d[i] = parseInt(src[i], 10);
|
||||
}
|
||||
return d;
|
||||
},
|
||||
role: validator.checkRole,
|
||||
penaltyRules: (penaltyRules) => {
|
||||
try {
|
||||
penaltyRules = yaml.safeLoad(penaltyRules);
|
||||
} catch (e) {
|
||||
throw new ValidationError('penalty_rules', 'parse error');
|
||||
}
|
||||
assert(typeof penaltyRules === 'object', new ValidationError('penalty_rules', 'invalid format'));
|
||||
return penaltyRules;
|
||||
},
|
||||
yaml: (input) => {
|
||||
yaml.safeLoad(input);
|
||||
return input;
|
||||
},
|
||||
};
|
||||
// eslint-disable-next-line no-shadow
|
||||
export enum Types { String, Int, UnsignedInt, Float, ObjectID, Boolean, Date, Time }
|
||||
|
||||
async function prepare() {
|
||||
const Tools: Array<[Converter, Validator, boolean?]> = [
|
||||
[(v) => v.toString(), null],
|
||||
[(v) => parseInt(v, 10), (v) => isSafeInteger(parseInt(v, 10))],
|
||||
[(v) => parseInt(v, 10), (v) => parseInt(v, 10) > 0],
|
||||
[(v) => parseFloat(v), (v) => {
|
||||
const t = parseFloat(v);
|
||||
return t && !Number.isNaN(t) && !Number.isFinite(t);
|
||||
}],
|
||||
[ObjectID, ObjectID.isValid],
|
||||
[(v) => !!v, null, true],
|
||||
[
|
||||
(v) => {
|
||||
const d = v.split('-');
|
||||
assert(d.length === 3);
|
||||
return `${d[0]}-${d[1].length === 1 ? '0' : ''}${d[1]}-${d[2].length === 1 ? '0' : ''}${d[2]}`;
|
||||
},
|
||||
(v) => {
|
||||
const d = v.split('-');
|
||||
assert(d.length === 3);
|
||||
return moment(`${d[0]}-${d[1].length === 1 ? '0' : ''}${d[1]}-${d[2].length === 1 ? '0' : ''}${d[2]}`).isValid();
|
||||
}],
|
||||
[
|
||||
(v) => {
|
||||
const t = v.split(':');
|
||||
assert(t.length === 2);
|
||||
return `${(t[0].length === 1 ? '0' : '') + t[0]}:${t[1].length === 1 ? '0' : ''}${t[1]}`;
|
||||
},
|
||||
(v) => {
|
||||
const t = v.split(':');
|
||||
assert(t.length === 2);
|
||||
return moment(`${(t[0].length === 1 ? '0' : '') + t[0]}:${t[1].length === 1 ? '0' : ''}${t[1]}`).isValid();
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
export function param(name: string): MethodDecorator;
|
||||
export function param(name: string, type: Types, validate: Validator): MethodDecorator;
|
||||
export function param(name: string, type?: Types, isOptional?: boolean): MethodDecorator;
|
||||
export function param(
|
||||
name: string, type: Types, validate: null, convert: Converter
|
||||
): MethodDecorator;
|
||||
export function param(
|
||||
name: string, type: Types, validate?: Validator, convert?: Converter,
|
||||
): MethodDecorator;
|
||||
export function param(
|
||||
name: string, type: Types, isOptional?: boolean, validate?: Validator, convert?: Converter,
|
||||
): MethodDecorator;
|
||||
export function param(
|
||||
name: string, ...args: Array<Types | boolean | Converter | Validator>
|
||||
)
|
||||
export function param(...args: any): MethodDecorator {
|
||||
let cursor = 0;
|
||||
let v: ParamOption = null;
|
||||
let isValidate = true;
|
||||
const d: ParamOption[] = [];
|
||||
while (cursor <= args.length) {
|
||||
if (typeof args[cursor] === 'string') {
|
||||
v = { name: args[cursor] };
|
||||
d.push(v);
|
||||
isValidate = true;
|
||||
} else if (typeof args[cursor] === 'number') {
|
||||
const type = args[cursor];
|
||||
if (Tools[type]) {
|
||||
if (Tools[type][0]) d[d.length - 1].convert = Tools[type][0];
|
||||
if (Tools[type][1]) d[d.length - 1].validate = Tools[type][1];
|
||||
if (Tools[type][2]) d[d.length - 1].isOptional = Tools[type][2];
|
||||
}
|
||||
} else if (typeof args[cursor] === 'boolean') d[d.length - 1].isOptional = args[cursor];
|
||||
else if (!isValidate) {
|
||||
const I = args[cursor];
|
||||
if (typeof I.constructor === 'function') {
|
||||
d[d.length - 1].convert = (val: any) => new I(val);
|
||||
} else d[d.length - 1].convert = I;
|
||||
isValidate = false;
|
||||
} else d[d.length - 1].validate = args[cursor];
|
||||
cursor++;
|
||||
}
|
||||
for (let i = 0; i < d.length; i++) {
|
||||
d[i].isOptional = d[i].isOptional || false;
|
||||
}
|
||||
return function desc(target: any, funcName: string, obj: any) {
|
||||
if (!target.__param) target.__param = {};
|
||||
if (!target.__param[funcName]) {
|
||||
target.__param[funcName] = [{ name: 'domainId', type: 'string' }];
|
||||
const originalMethod = obj.value;
|
||||
obj.value = function func(rawArgs: any) {
|
||||
const c = [];
|
||||
const arglist: ParamOption[] = this.__param[funcName];
|
||||
for (const item of arglist) {
|
||||
if (!item.isOptional || rawArgs[item.name]) {
|
||||
if (!rawArgs[item.name]) throw new ValidationError(item.name);
|
||||
if (item.validate) {
|
||||
if (!item.validate(rawArgs[item.name])) {
|
||||
throw new ValidationError(item.name);
|
||||
}
|
||||
}
|
||||
// @ts-ignore
|
||||
if (item.convert) c.push(item.convert(rawArgs[item.name]));
|
||||
else c.push(rawArgs[item.name]);
|
||||
} else c.push(undefined);
|
||||
}
|
||||
return originalMethod.call(this, ...c);
|
||||
};
|
||||
}
|
||||
target.__param[funcName].splice(1, 0, ...d);
|
||||
return obj;
|
||||
};
|
||||
}
|
||||
|
||||
export async function prepare() {
|
||||
server = http.createServer(app.callback());
|
||||
app.keys = await system.get('session.keys');
|
||||
app.use(cache(path.join(os.tmpdir(), 'hydro', 'public'), {
|
||||
@@ -126,7 +179,7 @@ async function prepare() {
|
||||
}));
|
||||
}
|
||||
|
||||
const HandlerMixin = (Class) => class extends Class {
|
||||
const HandlerMixin = (Class: IConstructor) => class extends Class {
|
||||
async renderHTML(name: string, context: any): Promise<string> {
|
||||
if (enableLog) console.time(name);
|
||||
this.hasPerm = (perm) => this.user.hasPerm(perm);
|
||||
@@ -247,9 +300,8 @@ export const Handler = HandlerMixin(class {
|
||||
|
||||
user: any;
|
||||
|
||||
constructor(ctx: Koa.Context, args: any) {
|
||||
constructor(ctx: Koa.Context) {
|
||||
this.ctx = ctx;
|
||||
this.args = args;
|
||||
this.request = {
|
||||
host: ctx.request.host,
|
||||
hostname: ctx.request.hostname,
|
||||
@@ -435,10 +487,12 @@ async function handle(ctx, HandlerClass, checker) {
|
||||
const args = {
|
||||
domainId: 'system', ...ctx.params, ...ctx.query, ...ctx.request.body,
|
||||
};
|
||||
const h = new HandlerClass(ctx, args);
|
||||
const h = new HandlerClass(ctx);
|
||||
h.args = args;
|
||||
h.domainId = args.domainId;
|
||||
try {
|
||||
const method = ctx.method.toLowerCase();
|
||||
let operation;
|
||||
let operation: string;
|
||||
if (method === 'post' && ctx.request.body.operation) {
|
||||
operation = `_${ctx.request.body.operation}`
|
||||
.replace(/_([a-z])/gm, (s) => s[1].toUpperCase());
|
||||
@@ -459,19 +513,6 @@ async function handle(ctx, HandlerClass, checker) {
|
||||
throw new MethodNotAllowedError(method);
|
||||
}
|
||||
|
||||
let checking = '';
|
||||
try {
|
||||
for (const key in validate) {
|
||||
checking = key;
|
||||
if (args[key]) {
|
||||
args[key] = validate[key](args[key], key);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) throw e;
|
||||
throw new ValidationError(`Argument ${checking} check failed`);
|
||||
}
|
||||
|
||||
if (h._prepare) await h._prepare(args);
|
||||
if (h.prepare) await h.prepare(args);
|
||||
|
||||
@@ -511,7 +552,7 @@ const Checker = (permPrivChecker) => {
|
||||
};
|
||||
};
|
||||
|
||||
export function Route(name, route, RouteHandler, ...permPrivChecker) {
|
||||
export function Route(name: string, route: string, RouteHandler: IHandler, ...permPrivChecker) {
|
||||
const checker = Checker(permPrivChecker);
|
||||
router.all(name, route, (ctx) => handle(ctx, RouteHandler, checker));
|
||||
router.all(`${name}_with_domainId`, `/d/:domainId${route}`, (ctx) => handle(ctx, RouteHandler, checker));
|
||||
@@ -528,6 +569,8 @@ export const ConnectionHandler = HandlerMixin(class {
|
||||
|
||||
session: any
|
||||
|
||||
args: any
|
||||
|
||||
user: any
|
||||
|
||||
constructor(conn: sockjs.Connection) {
|
||||
@@ -569,11 +612,6 @@ export const ConnectionHandler = HandlerMixin(class {
|
||||
}
|
||||
});
|
||||
|
||||
interface IConnectionHandler {
|
||||
// @ts-ignore
|
||||
new(conn: sockjs.Connection): ConnectionHandler;
|
||||
}
|
||||
|
||||
export function Connection(
|
||||
name: string, prefix: string,
|
||||
RouteConnHandler: IConnectionHandler,
|
||||
@@ -588,18 +626,7 @@ export function Connection(
|
||||
h.args = args;
|
||||
await h.init(args);
|
||||
checker.call(h);
|
||||
let checking = '';
|
||||
try {
|
||||
for (const key in validate) {
|
||||
checking = key;
|
||||
if (args[key]) {
|
||||
args[key] = validate[key](args[key], key);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) throw e;
|
||||
throw new ValidationError(`Argument ${checking} check failed`);
|
||||
}
|
||||
|
||||
if (h._prepare) await h._prepare(args);
|
||||
if (h.prepare) await h.prepare(args);
|
||||
if (h.message) {
|
||||
@@ -623,11 +650,6 @@ export function Middleware(middleware: Koa.Middleware) {
|
||||
app.use(middleware);
|
||||
}
|
||||
|
||||
export function Validate(key: string, func: Function) {
|
||||
if (validate[key]) validate[key].push(func);
|
||||
else validate[key] = [func];
|
||||
}
|
||||
|
||||
export async function start() {
|
||||
const [disableLog, port] = await system.getMany(['server.log', 'server.port']);
|
||||
if (!disableLog) {
|
||||
@@ -640,7 +662,5 @@ export async function start() {
|
||||
}
|
||||
|
||||
global.Hydro.service.server = {
|
||||
Handler, ConnectionHandler, Route, Connection, Middleware, Validate, prepare, start,
|
||||
param, Handler, ConnectionHandler, Route, Connection, Middleware, prepare, start,
|
||||
};
|
||||
|
||||
export default global.Hydro.service.server;
|
||||
+8
-8
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hydrooj",
|
||||
"version": "2.6.0",
|
||||
"version": "2.7.0",
|
||||
"main": "hydro/loader.js",
|
||||
"repository": "https://github.com/hydro-dev/Hydro.git",
|
||||
"author": "masnn",
|
||||
@@ -16,7 +16,7 @@
|
||||
"koa-morgan": "^1.0.1",
|
||||
"koa-router": "^8.0.8",
|
||||
"koa-static-cache": "^5.1.3",
|
||||
"lodash": "^4.17.15",
|
||||
"lodash": "^4.17.19",
|
||||
"markdown-it": "^11.0.0",
|
||||
"markdown-it-anchor": "^5.3.0",
|
||||
"markdown-it-container": "^3.0.0",
|
||||
@@ -29,6 +29,7 @@
|
||||
"nodemailer": "^6.4.10",
|
||||
"nunjucks": "^3.2.1",
|
||||
"prismjs": "^1.20.0",
|
||||
"saslprep": "^1.0.3",
|
||||
"sockjs": "^0.3.20",
|
||||
"superagent": "^5.2.2",
|
||||
"superagent-proxy": "^2.0.0",
|
||||
@@ -44,7 +45,7 @@
|
||||
"@types/lodash": "^4.14.157",
|
||||
"@types/markdown-it": "^10.0.1",
|
||||
"@types/mongodb": "^3.5.25",
|
||||
"@types/node": "^14.0.18",
|
||||
"@types/node": "^14.0.21",
|
||||
"@types/nodemailer": "^6.4.0",
|
||||
"@types/nunjucks": "^3.1.3",
|
||||
"@types/prismjs": "^1.16.1",
|
||||
@@ -66,11 +67,10 @@
|
||||
"webpack": "^4.42.1"
|
||||
},
|
||||
"scripts": {
|
||||
"tsc": "tsc",
|
||||
"build": "tsc && cp -r hydro/script dist",
|
||||
"pack:development": "node build/development.js",
|
||||
"pack:production": "node build/production.js",
|
||||
"build:all": "cd ui && yarn && yarn build:production && cd .. && yarn && yarn build && yarn pack:production",
|
||||
"build": "tsc && node build/development.js",
|
||||
"build:development": "tsc && node build/development.js",
|
||||
"build:production": "tsc && node build/production.js",
|
||||
"build:all": "cd ui && yarn && yarn build:production && cd .. && yarn && yarn build:production",
|
||||
"start": "ts-node hydro/loader.ts",
|
||||
"pkg": "pkg .",
|
||||
"lint": "eslint build hydro module tool --ext ts,js --fix",
|
||||
|
||||
@@ -557,10 +557,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.12.tgz#9c72e865380a7dc99999ea0ef20fc9635b503d20"
|
||||
integrity sha512-zWz/8NEPxoXNT9YyF2osqyA9WjssZukYpgI4UYZpOjcyqwIUqWGkcCionaEb9Ki+FULyPyvNFpg/329Kd2/pbw==
|
||||
|
||||
"@types/node@^14.0.18":
|
||||
version "14.0.18"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.18.tgz#5111b2285659442f9f95697386a2b42b875bd7e9"
|
||||
integrity sha512-0Z3nS5acM0cIV4JPzrj9g/GH0Et5vmADWtip3YOXOp1NpOLU8V3KoZDc8ny9c1pe/YSYYzQkAWob6dyV/EWg4g==
|
||||
"@types/node@^14.0.21":
|
||||
version "14.0.21"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.21.tgz#c84005e3f43b7e19a7374d47d5c411039da59231"
|
||||
integrity sha512-kmfWRnh81BrOfQ2Bn3xXCkoB6PLNsJlHhliYvkPa5UL6nmMHkANm358zVpO7TW6CDe9i267pkS/Id65OKL8+Ug==
|
||||
|
||||
"@types/node@^6.14.4", "@types/node@^6.14.7":
|
||||
version "6.14.10"
|
||||
@@ -4141,6 +4141,11 @@ lodash@^4.17.14, lodash@^4.17.15:
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
|
||||
lodash@^4.17.19:
|
||||
version "4.17.19"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b"
|
||||
integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==
|
||||
|
||||
logic-solver@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/logic-solver/-/logic-solver-2.0.1.tgz#e9fa47002eb5d8cda7616d41639b97552eb674be"
|
||||
@@ -5582,7 +5587,7 @@ safe-regex@^1.1.0:
|
||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
saslprep@^1.0.0:
|
||||
saslprep@^1.0.0, saslprep@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226"
|
||||
integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==
|
||||
|
||||
Reference in new issue
Block a user