使用 @param 验证接口

This commit is contained in:
undefined committed 2020-07-10 22:54:26 +08:00
1 parent 4e85b696c0
commit 19cf1a5304
22 files changed
+657 -393

No files matched your search

+118 -70
View File
@@ -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);