一些优化

优化 Date.prototype.format
修复题目分类解析错误的问题
Interface Tdoc
X-Forwarded-For 支持
This commit is contained in:
undefined committed 2020-07-12 18:59:00 +08:00
1 parent 1722fbfaca
commit 82c193b5ee
19 files changed
+286 -179

No files matched your search

+2 -5
View File
@@ -52,6 +52,7 @@ export const ForbiddenError = Err('ForbiddenError', UserFacingError, 'ForbiddenE
export const NotFoundError = Err('NotFoundError', UserFacingError, 'NotFoundError', 404); export const NotFoundError = Err('NotFoundError', UserFacingError, 'NotFoundError', 404);
export const MethodNotAllowedError = Err('MethodNotAllowedError', UserFacingError, 'MethodNotAllowedError', 405); export const MethodNotAllowedError = Err('MethodNotAllowedError', UserFacingError, 'MethodNotAllowedError', 405);
export const RemoteOnlineJudgeError = Err('RemoteOnlineJudgeError', UserFacingError, 'RemoteOnlineJudgeError', 500); export const RemoteOnlineJudgeError = Err('RemoteOnlineJudgeError', UserFacingError, 'RemoteOnlineJudgeError', 500);
export const SendMailError = Err('SendMailError', UserFacingError, 'Failed to send mail to {0}.', 500);
export const AlreadyVotedError = Err('AlreadyVotedError', ForbiddenError, "You've already voted."); export const AlreadyVotedError = Err('AlreadyVotedError', ForbiddenError, "You've already voted.");
export const LoginError = Err('LoginError', ForbiddenError, 'Invalid password for user {0}.'); export const LoginError = Err('LoginError', ForbiddenError, 'Invalid password for user {0}.');
@@ -103,6 +104,7 @@ global.Hydro.error = {
BlacklistedError, BlacklistedError,
ForbiddenError, ForbiddenError,
NotFoundError, NotFoundError,
SendMailError,
LoginError, LoginError,
CsrfTokenError, CsrfTokenError,
MethodNotAllowedError, MethodNotAllowedError,
@@ -188,9 +190,4 @@ class InvalidArgumentError(BadRequestError):
@property @property
def message(self): def message(self):
return 'Argument {0} is invalid.' return 'Argument {0} is invalid.'
class SendMailError(UserFacingError):
@property
def message(self):
return 'Failed to send mail to {0}.'
*/ */
+4 -4
View File
@@ -155,8 +155,8 @@ class ContestEditHandler extends ContestHandler {
duration, duration,
path, path,
pids: this.tdoc.pids.join(','), pids: this.tdoc.pids.join(','),
date_text: `${dt.getFullYear()}-${dt.getMonth() + 1}-${dt.getDate()}`, date_text: dt.format('%Y-%m-%d'),
time_text: `${dt.getHours()}:${dt.getMinutes()}`, time_text: dt.format('%H:%M'),
page_name: 'contest_edit', page_name: 'contest_edit',
}; };
} }
@@ -337,8 +337,8 @@ class ContestCreateHandler extends ContestHandler {
rules, rules,
path, path,
page_name: 'contest_create', page_name: 'contest_create',
date_text: `${dt.getFullYear()}-${dt.getMonth() + 1}-${dt.getDate()}`, date_text: dt.format('%Y-%m-%d'),
time_text: `${dt.getHours()}:${dt.getMinutes()}`, time_text: dt.format('%M:%S'),
pids: '1000, 1001', pids: '1000, 1001',
}; };
} }
+8
View File
@@ -21,6 +21,14 @@ export const typeMapper = {
}; };
class DiscussionHandler extends Handler { class DiscussionHandler extends Handler {
ddoc?: any;
drdoc?: any;
drrdoc?: any;
vnode?: any;
async _prepare({ async _prepare({
domainId, type, name, did, drid, drrid, domainId, type, name, did, drid, drrid,
}) { }) {
+2
View File
@@ -60,6 +60,8 @@ class DomainCreateHandler extends Handler {
} }
class ManageHandler extends Handler { class ManageHandler extends Handler {
domain: any;
async prepare({ domainId }) { async prepare({ domainId }) {
this.checkPerm(PERM.PERM_EDIT_DOMAIN); this.checkPerm(PERM.PERM_EDIT_DOMAIN);
this.domain = await domain.get(domainId); this.domain = await domain.get(domainId);
+2 -2
View File
@@ -147,10 +147,10 @@ class HomeSecurityHandler extends Handler {
const [code] = await token.add( const [code] = await token.add(
token.TYPE_CHANGEMAIL, token.TYPE_CHANGEMAIL,
await system.get('changemail_token_expire_seconds'), await system.get('changemail_token_expire_seconds'),
{ uid: this.udoc._id, mail }, { uid: this.user._id, mail },
); );
await mail.sendMail(mail, 'Change Email', 'user_changemail_mail.html', { await mail.sendMail(mail, 'Change Email', 'user_changemail_mail.html', {
url: this.url('user_changemail_with_code', { code }), uname: this.udoc.uname, url: this.url('user_changemail_with_code', { code }), uname: this.user.uname,
}); });
this.response.template = 'user_changemail_mail_sent.html'; this.response.template = 'user_changemail_mail_sent.html';
} }
+2
View File
@@ -155,6 +155,8 @@ class JudgeHandler extends Handler {
} }
class JudgeConnectionHandler extends ConnectionHandler { class JudgeConnectionHandler extends ConnectionHandler {
processing: any;
async message(msg) { async message(msg) {
if (msg.key === 'next') await next(msg); if (msg.key === 'next') await next(msg);
else if (msg.key === 'end') { else if (msg.key === 'end') {
+4 -1
View File
@@ -15,6 +15,7 @@ function set(key, value) {
if (setting.SYSTEM_SETTINGS_BY_KEY[key]) { if (setting.SYSTEM_SETTINGS_BY_KEY[key]) {
const s = setting.SYSTEM_SETTINGS_BY_KEY[key]; const s = setting.SYSTEM_SETTINGS_BY_KEY[key];
if (s.flag & setting.FLAG_DISABLED) return undefined; if (s.flag & setting.FLAG_DISABLED) return undefined;
if (s.flag & setting.FLAG_SECRET && !value) return undefined;
if (s.type === 'boolean') { if (s.type === 'boolean') {
if (value === 'on') return true; if (value === 'on') return true;
return false; return false;
@@ -48,6 +49,8 @@ class SystemMainHandler extends SystemHandler {
} }
class SystemCheckConnHandler extends ConnectionHandler { class SystemCheckConnHandler extends ConnectionHandler {
id: any;
async prepare() { async prepare() {
this.checkPriv(PRIV.PRIV_EDIT_SYSTEM); this.checkPriv(PRIV.PRIV_EDIT_SYSTEM);
await this.check(); await this.check();
@@ -61,7 +64,7 @@ class SystemCheckConnHandler extends ConnectionHandler {
} }
async cleanup() { async cleanup() {
check.cancel(this.token); check.cancel(this.id);
} }
} }
+1 -1
View File
@@ -17,7 +17,7 @@ class FileDownloadHandler extends Handler {
@param('name', Types.String, true) @param('name', Types.String, true)
async get(domainId: string, fileId: ObjectID, secret: string, name: string) { async get(domainId: string, fileId: ObjectID, secret: string, name: string) {
if (name) name = Buffer.from(name, 'base64').toString(); if (name) name = Buffer.from(name, 'base64').toString();
this.response.attachment(name || fileId); this.response.attachment(name || fileId.toHexString());
this.response.body = await file.getWithSecret(fileId, secret); this.response.body = await file.getWithSecret(fileId, secret);
} }
} }
+34 -22
View File
@@ -19,8 +19,13 @@ import {
NoProblemError, ProblemDataNotFoundError, BadRequestError, NoProblemError, ProblemDataNotFoundError, BadRequestError,
SolutionNotFoundError, SolutionNotFoundError,
} from '../error'; } from '../error';
import { Pdoc, Udoc, Rdoc } from '../interface';
const parseCategory = (value: string) => flatten(value.split('+').map((e) => e.split(','))).map((e) => e.trim()); const parseCategory = (value: string) => {
if (!value) return [];
console.log(value);
return flatten(value.split('+').map((e) => e.split(','))).map((e) => e.trim());
};
const parsePid = (value: string) => (isSafeInteger(value) ? parseInt(value, 10) : value); const parsePid = (value: string) => (isSafeInteger(value) ? parseInt(value, 10) : value);
class ProblemHandler extends Handler { class ProblemHandler extends Handler {
@@ -99,7 +104,7 @@ class ProblemMainHandler extends ProblemHandler {
class ProblemCategoryHandler extends ProblemHandler { class ProblemCategoryHandler extends ProblemHandler {
@param('page', Types.UnsignedInt, true) @param('page', Types.UnsignedInt, true)
@param('category', Types.String, parseCategory) @param('category', Types.String, null, parseCategory)
async get(domainId: string, page = 1, category: string[]) { async get(domainId: string, page = 1, category: string[]) {
this.response.template = 'problem_main.html'; this.response.template = 'problem_main.html';
const q: any = { $and: [] }; const q: any = { $and: [] };
@@ -136,7 +141,7 @@ class ProblemCategoryHandler extends ProblemHandler {
class ProblemRandomHandler extends ProblemHandler { class ProblemRandomHandler extends ProblemHandler {
@param('category', Types.String, true, null, parseCategory) @param('category', Types.String, true, null, parseCategory)
async get(domainId: string, category: string[]) { async get(domainId: string, category: string[] = []) {
const q: any = category.length ? { $and: [] } : {}; const q: any = category.length ? { $and: [] } : {};
for (const name of category) { for (const name of category) {
if (name) { if (name) {
@@ -157,31 +162,32 @@ class ProblemRandomHandler extends ProblemHandler {
} }
class ProblemDetailHandler extends ProblemHandler { class ProblemDetailHandler extends ProblemHandler {
pdoc: Pdoc;
udoc: Udoc;
@param('pid', Types.String, null, parsePid) @param('pid', Types.String, null, parsePid)
async _prepare(domainId: string, pid: number | string) { async _prepare(domainId: string, pid: number | string) {
this.response.template = 'problem_detail.html'; this.response.template = 'problem_detail.html';
if (pid) { this.pdoc = await problem.get(domainId, pid, this.user._id, true);
this.pdoc = await problem.get(domainId, pid, this.user._id);
if (this.pdoc.hidden && this.pdoc.owner !== this.user._id) { if (this.pdoc.hidden && this.pdoc.owner !== this.user._id) {
this.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN); this.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
} }
if (this.pdoc) this.udoc = await user.getById(domainId, this.pdoc.owner); this.udoc = await user.getById(domainId, this.pdoc.owner);
}
this.response.body = { this.response.body = {
pdoc: this.pdoc, pdoc: this.pdoc,
udoc: this.udoc, udoc: this.udoc,
title: (this.pdoc || {}).title || '', title: this.pdoc.title,
}; path: [
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async get(...args: any) {
this.response.body.path = [
['Hydro', 'homepage'], ['Hydro', 'homepage'],
['problem_main', 'problem_main'], ['problem_main', 'problem_main'],
[this.pdoc.title, null, true], [this.pdoc.title, null, true],
]; ],
};
} }
// eslint-disable-next-line
async get(...args: any[]) { }
} }
class ProblemSubmitHandler extends ProblemDetailHandler { class ProblemSubmitHandler extends ProblemDetailHandler {
@@ -234,6 +240,10 @@ class ProblemPretestHandler extends ProblemDetailHandler {
} }
class ProblemPretestConnectionHandler extends ConnectionHandler { class ProblemPretestConnectionHandler extends ConnectionHandler {
pid: string;
domainId: string;
@param('pid', Types.String) @param('pid', Types.String)
async prepare(domainId: string, pid: string) { async prepare(domainId: string, pid: string) {
this.pid = pid; this.pid = pid;
@@ -242,7 +252,7 @@ class ProblemPretestConnectionHandler extends ConnectionHandler {
} }
async onRecordChange(data) { async onRecordChange(data) {
const rdoc = data.value; const rdoc: Rdoc = data.value;
if ( if (
rdoc.uid !== this.user._id rdoc.uid !== this.user._id
|| rdoc.pid.toString() !== this.pid || rdoc.pid.toString() !== this.pid
@@ -339,23 +349,25 @@ class ProblemDataUploadHandler extends ProblemManageHandler {
} }
async get() { async get() {
let md5;
if (this.pdoc.data && typeof this.pdoc.data === 'object') { if (this.pdoc.data && typeof this.pdoc.data === 'object') {
const f = await file.getMeta(this.pdoc.data); const f = await file.getMeta(this.pdoc.data);
this.md5 = f.md5; md5 = f.md5;
} }
this.response.body.md5 = this.md5; this.response.body.md5 = md5;
} }
async post({ domainId }) { async post({ domainId }) {
let md5;
if (!this.request.files.file) throw new BadRequestError(); if (!this.request.files.file) throw new BadRequestError();
await problem.setTestdata( await problem.setTestdata(
domainId, this.pdoc.docId, this.request.files.file.path, domainId, this.pdoc.docId, this.request.files.file.path,
); );
if (this.pdoc.data && typeof this.pdoc.data === 'object') { if (this.pdoc.data && typeof this.pdoc.data === 'object') {
const f = await file.getMeta(this.pdoc.data); const f = await file.getMeta(this.pdoc.data);
this.md5 = f.md5; md5 = f.md5;
} }
this.response.body.md5 = this.md5; this.response.body.md5 = md5;
} }
} }
@@ -368,7 +380,7 @@ class ProblemDataDownloadHandler extends ProblemDetailHandler {
} }
if (!this.pdoc.data) throw new ProblemDataNotFoundError(pid); if (!this.pdoc.data) throw new ProblemDataNotFoundError(pid);
else if (typeof this.pdoc.data === 'string') [, this.response.redirect] = this.pdoc.data.split('from:'); else if (typeof this.pdoc.data === 'string') [, this.response.redirect] = this.pdoc.data.split('from:');
this.response.redirect = await file.url(this.pdoc.data, `${this.pdoc.title}.zip`); else this.response.redirect = await file.url(this.pdoc.data, `${this.pdoc.title}.zip`);
} }
} }
@@ -416,7 +428,7 @@ class ProblemSolutionHandler extends ProblemDetailHandler {
let psdoc = await solution.get(domainId, psid); let psdoc = await solution.get(domainId, psid);
if (psdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION); if (psdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION);
else this.checkPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION_SELF); else this.checkPerm(PERM.PERM_EDIT_PROBLEM_SOLUTION_SELF);
psdoc = await solution.edit(domainId, this.psdoc.docId, content); psdoc = await solution.edit(domainId, psdoc.docId, content);
this.back({ psdoc }); this.back({ psdoc });
} }
+3
View File
@@ -12,6 +12,7 @@ import * as system from '../model/system';
import { import {
Route, Handler, Types, param, Route, Handler, Types, param,
} from '../service/server'; } from '../service/server';
import { Tdoc } from '../interface';
async function _parseDagJson(domainId, dag) { async function _parseDagJson(domainId, dag) {
const parsed = []; const parsed = [];
@@ -199,6 +200,8 @@ class TrainingCreateHandler extends Handler {
} }
class TrainingEditHandler extends Handler { class TrainingEditHandler extends Handler {
tdoc: Tdoc;
@param('tid', Types.ObjectID) @param('tid', Types.ObjectID)
async prepare(domainId: string, tid: ObjectID) { async prepare(domainId: string, tid: ObjectID) {
this.tdoc = await training.get(domainId, tid); this.tdoc = await training.get(domainId, tid);
+13 -4
View File
@@ -2,10 +2,12 @@
import { ObjectID, GridFSBucket } from 'mongodb'; import { ObjectID, GridFSBucket } from 'mongodb';
import fs from 'fs'; import fs from 'fs';
export type Dict<T> = { [key: string]: T };
export interface Setting { export interface Setting {
family: string, family: string,
key: string, key: string,
range: Array<[string, string]> | { [key: string]: string }, range: Array<[string, string]> | Dict<string>,
value: any, value: any,
type: string, type: string,
name: string, name: string,
@@ -51,7 +53,8 @@ export interface Pdoc {
nAccept: number, nAccept: number,
tag: string[], tag: string[],
category: string[], category: string[],
data?: ObjectID, // ObjectID for built-in files, string for other source (RemoteJudge, etc.)
data?: ObjectID | string,
hidden: boolean, hidden: boolean,
config: string, config: string,
} }
@@ -108,15 +111,21 @@ export interface Tdoc {
domainId: string, domainId: string,
docId: ObjectID, docId: ObjectID,
docType: number, docType: number,
owner: number,
beginAt: Date, beginAt: Date,
endAt: Date, endAt: Date,
penaltySince?: Date,
penaltyRules?: PenaltyRules,
attend: number, attend: number,
title: string, title: string,
content: string, content: string,
rule: string, rule: string,
pids: number[], pids: number[],
// For homework
penaltySince?: Date,
penaltyRules?: PenaltyRules,
// For training
dag: any
} }
interface ContestStat { interface ContestStat {
+1 -8
View File
@@ -1,13 +1,6 @@
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
import { SendMailError } from '../error';
const { SendMailError } = require('../error');
/**
* @param {string} to
* @param {string} subject
* @param {string} text
* @param {string} html
*/
// eslint-disable-next-line import/prefer-default-export // eslint-disable-next-line import/prefer-default-export
export async function sendMail(to: string, subject: string, text: string, html: string) { export async function sendMail(to: string, subject: string, text: string, html: string) {
let t: unknown; let t: unknown;
+1 -1
View File
@@ -278,7 +278,7 @@ export const BUILTIN_USERS = [
regip: '127.0.0.1', regip: '127.0.0.1',
loginat: new Date(0), loginat: new Date(0),
loginip: '127.0.0.1', loginip: '127.0.0.1',
priv: PRIV.PRIV_NONE, priv: PRIV.PRIV_REGISTER_USER,
}, },
{ {
_id: 1, _id: 1,
+5 -3
View File
@@ -390,8 +390,10 @@ export async function edit(
return await document.set(domainId, type, tid, $set); return await document.set(domainId, type, tid, $set);
} }
export async function get(domainId: string, tid: ObjectID, type = document.TYPE_CONTEST) { export async function get(
let tdoc; domainId: string, tid: ObjectID, type = document.TYPE_CONTEST,
) {
let tdoc: Tdoc;
if (type === -1) { if (type === -1) {
tdoc = await document.get(domainId, document.TYPE_CONTEST, tid); tdoc = await document.get(domainId, document.TYPE_CONTEST, tid);
if (!tdoc) tdoc = await document.get(domainId, document.TYPE_HOMEWORK, tid); if (!tdoc) tdoc = await document.get(domainId, document.TYPE_HOMEWORK, tid);
@@ -535,7 +537,7 @@ export function getMulti(domainId: string, query = {}, type = document.TYPE_CONT
export async function getAndListStatus( export async function getAndListStatus(
domainId: string, tid: ObjectID, docType = document.TYPE_CONTEST, domainId: string, tid: ObjectID, docType = document.TYPE_CONTEST,
) { ): Promise<[Tdoc, any[]]> {
// TODO(iceboy): projection, pagination. // TODO(iceboy): projection, pagination.
const tdoc = await get(domainId, tid, docType); const tdoc = await get(domainId, tid, docType);
const tsdocs = await document.getMultiStatus(domainId, docType, { docId: tid }) const tsdocs = await document.getMultiStatus(domainId, docType, { docId: tid })
+3 -5
View File
@@ -1,4 +1,4 @@
import { ObjectID, FilterQuery } from 'mongodb'; import { ObjectID } from 'mongodb';
import { STATUS } from './builtin'; import { STATUS } from './builtin';
import * as file from './file'; import * as file from './file';
import * as document from './document'; import * as document from './document';
@@ -47,8 +47,6 @@ export async function get(
): Promise<Pdoc> { ): Promise<Pdoc> {
if (typeof pid !== 'number') { if (typeof pid !== 'number') {
if (!Number.isNaN(parseInt(pid, 10))) pid = parseInt(pid, 10); if (!Number.isNaN(parseInt(pid, 10))) pid = parseInt(pid, 10);
else if (doThrow) throw new ProblemNotFoundError(domainId, pid);
else return null;
} }
const pdoc = Number.isInteger(pid) const pdoc = Number.isInteger(pid)
? await document.get(domainId, document.TYPE_PROBLEM, pid) ? await document.get(domainId, document.TYPE_PROBLEM, pid)
@@ -87,11 +85,11 @@ export function inc(domainId: string, _id: number, field: string, n: number) {
return document.inc(domainId, document.TYPE_PROBLEM, _id, field, n); return document.inc(domainId, document.TYPE_PROBLEM, _id, field, n);
} }
export function count<T>(domainId: string, query: FilterQuery<T>) { export function count(domainId: string, query: any) {
return document.count(domainId, document.TYPE_PROBLEM, query); return document.count(domainId, document.TYPE_PROBLEM, query);
} }
export async function random<T>(domainId: string, query: FilterQuery<T>) { export async function random(domainId: string, query: any) {
const cursor = document.getMulti(domainId, document.TYPE_PROBLEM, query); const cursor = document.getMulti(domainId, document.TYPE_PROBLEM, query);
const pcount = await cursor.count(); const pcount = await cursor.count();
if (pcount) { if (pcount) {
+1
View File
@@ -116,6 +116,7 @@ SystemSetting(
Setting('setting_server', 'server.host', null, null, 'text', 'Server Host'), Setting('setting_server', 'server.host', null, null, 'text', 'Server Host'),
Setting('setting_server', 'server.url', null, null, 'text', 'Server BaseURL'), Setting('setting_server', 'server.url', null, null, 'text', 'Server BaseURL'),
Setting('setting_server', 'server.port', null, 8888, 'number', 'Server Port'), Setting('setting_server', 'server.port', null, 8888, 'number', 'Server Port'),
Setting('setting_server', 'server.xff', null, null, 'text', 'IP Header'),
Setting('setting_server', 'server.log', null, false, 'boolean', 'Disable Access Log'), Setting('setting_server', 'server.log', null, false, 'boolean', 'Disable Access Log'),
Setting('setting_ui', 'ui.header', null, null, 'text', 'Header Logo'), Setting('setting_ui', 'ui.header', null, null, 'text', 'Header Logo'),
Setting('setting_ui', 'ui.headerBackground', null, null, 'text', 'Header Background'), Setting('setting_ui', 'ui.headerBackground', null, null, 'text', 'Header Background'),
+184 -111
View File
@@ -31,10 +31,11 @@ const app = new Koa();
let server; let server;
const router = new Router(); const router = new Router();
interface IConstructor { // TODO fix this ugly hack
// @ts-ignore interface H {
new(value: any): any; [key: string]: any,
} }
interface IHandler { interface IHandler {
// @ts-ignore // @ts-ignore
new(ctx: Koa.Context): Handler; new(ctx: Koa.Context): Handler;
@@ -44,7 +45,7 @@ interface IConnectionHandler {
new(conn: sockjs.Connection): ConnectionHandler; new(conn: sockjs.Connection): ConnectionHandler;
} }
type MethodDecorator = (target: any, name: string, obj: any) => any; type MethodDecorator = (target: any, name: string, obj: any) => any;
type Converter = IConstructor | ((value: any) => any); type Converter = (value: any) => any;
type Validator = (value: any) => boolean; type Validator = (value: any) => boolean;
interface ParamOption { interface ParamOption {
name: string, name: string,
@@ -64,7 +65,7 @@ const Tools: Array<[Converter, Validator, boolean?]> = [
const t = parseFloat(v); const t = parseFloat(v);
return t && !Number.isNaN(t) && !Number.isFinite(t); return t && !Number.isNaN(t) && !Number.isFinite(t);
}], }],
[ObjectID, ObjectID.isValid], [(v) => new ObjectID(v), ObjectID.isValid],
[(v) => !!v, null, true], [(v) => !!v, null, true],
[ [
(v) => { (v) => {
@@ -105,36 +106,28 @@ export function param(
): MethodDecorator; ): MethodDecorator;
export function param( export function param(
name: string, ...args: Array<Types | boolean | Converter | Validator> name: string, ...args: Array<Types | boolean | Converter | Validator>
) ): MethodDecorator;
export function param(...args: any): MethodDecorator { export function param(name: string, ...args: any): MethodDecorator {
let cursor = 0; let cursor = 0;
let v: ParamOption = null; const v: ParamOption = { name };
let isValidate = true; let isValidate = true;
const d: ParamOption[] = []; while (cursor < args.length) {
while (cursor <= args.length) { if (typeof args[cursor] === 'number') {
if (typeof args[cursor] === 'string') {
v = { name: args[cursor] };
d.push(v);
isValidate = true;
} else if (typeof args[cursor] === 'number') {
const type = args[cursor]; const type = args[cursor];
if (Tools[type]) { if (Tools[type]) {
if (Tools[type][0]) d[d.length - 1].convert = Tools[type][0]; if (Tools[type][0]) v.convert = Tools[type][0];
if (Tools[type][1]) d[d.length - 1].validate = Tools[type][1]; if (Tools[type][1]) v.validate = Tools[type][1];
if (Tools[type][2]) d[d.length - 1].isOptional = Tools[type][2]; if (Tools[type][2]) v.isOptional = Tools[type][2];
} }
} else if (typeof args[cursor] === 'boolean') d[d.length - 1].isOptional = args[cursor]; } else if (typeof args[cursor] === 'boolean') v.isOptional = args[cursor];
else if (!isValidate) { else if (isValidate) {
const I = args[cursor]; if (args[cursor] !== null) v.validate = 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; isValidate = false;
} else d[d.length - 1].validate = args[cursor]; } else {
cursor++; const I = args[cursor];
v.convert = I;
} }
for (let i = 0; i < d.length; i++) { cursor++;
d[i].isOptional = d[i].isOptional || false;
} }
return function desc(target: any, funcName: string, obj: any) { return function desc(target: any, funcName: string, obj: any) {
if (!target.__param) target.__param = {}; if (!target.__param) target.__param = {};
@@ -153,7 +146,6 @@ export function param(...args: any): MethodDecorator {
throw new ValidationError(item.name); throw new ValidationError(item.name);
} }
} }
// @ts-ignore
if (item.convert) c.push(item.convert(rawArgs[item.name])); if (item.convert) c.push(item.convert(rawArgs[item.name]));
else c.push(rawArgs[item.name]); else c.push(rawArgs[item.name]);
} else c.push(undefined); } else c.push(undefined);
@@ -161,7 +153,7 @@ export function param(...args: any): MethodDecorator {
return originalMethod.call(this, ...c); return originalMethod.call(this, ...c);
}; };
} }
target.__param[target.constructor.name][funcName].splice(1, 0, ...d); target.__param[target.constructor.name][funcName].splice(1, 0, v);
return obj; return obj;
}; };
} }
@@ -180,11 +172,80 @@ export async function prepare() {
})); }));
} }
const HandlerMixin = (Class: IConstructor) => class extends Class { export class Handler {
UIContext: any;
args: any;
ctx: Koa.Context;
request: {
host: string,
hostname: string,
ip: string,
headers: any,
cookies: any,
body: any,
files: any,
query: any,
path: string,
params: any,
referer: any,
json: boolean,
};
response: {
body: any,
type: string,
status: number,
template: string | undefined,
redirect: string | undefined,
disposition: string | undefined,
attachment: (name: string) => void,
};
session: any;
csrfToken: string;
user: any;
constructor(ctx: Koa.Context) {
this.ctx = ctx;
this.request = {
host: ctx.request.host,
hostname: ctx.request.hostname,
ip: ctx.request.ip,
headers: ctx.request.headers,
cookies: ctx.cookies,
body: ctx.request.body,
files: ctx.request.files,
query: ctx.query,
path: ctx.path,
params: ctx.params,
referer: ctx.request.headers.referer || '/',
json: (ctx.request.headers.accept || '').includes('application/json'),
};
this.response = {
body: {},
type: '',
status: null,
template: null,
redirect: null,
attachment: (name) => ctx.attachment(name),
disposition: null,
};
this.UIContext = {
cdn_prefix: '/',
url_prefix: '/',
};
this.session = {};
}
async renderHTML(name: string, context: any): Promise<string> { async renderHTML(name: string, context: any): Promise<string> {
if (enableLog) console.time(name); if (enableLog) console.time(name);
this.hasPerm = (perm) => this.user.hasPerm(perm);
// FIXME fix this ugly hack // FIXME fix this ugly hack
// @ts-ignore
this._user = { ...this.user, gravatar: misc.gravatar(this.user.gravatar, 128) }; this._user = { ...this.user, gravatar: misc.gravatar(this.user.gravatar, 128) };
const res = await render(name, Object.assign(context, { const res = await render(name, Object.assign(context, {
handler: this, handler: this,
@@ -261,77 +322,6 @@ const HandlerMixin = (Class: IConstructor) => class extends Class {
} }
return res; return res;
} }
};
export const Handler = HandlerMixin(class {
UIContext: any;
args: any;
ctx: Koa.Context;
request: {
host: string,
hostname: string,
ip: string,
headers: any,
cookies: any,
body: any,
files: any,
query: any,
path: string,
params: any,
referer: any,
json: boolean,
};
response: {
body: any,
type: string,
status: number,
template: string | undefined,
redirect: string | undefined,
disposition: string | undefined,
attachment: (name: string) => void,
};
session: any;
csrfToken: string;
user: any;
constructor(ctx: Koa.Context) {
this.ctx = ctx;
this.request = {
host: ctx.request.host,
hostname: ctx.request.hostname,
ip: ctx.request.ip,
headers: ctx.request.headers,
cookies: ctx.cookies,
body: ctx.request.body,
files: ctx.request.files,
query: ctx.query,
path: ctx.path,
params: ctx.params,
referer: ctx.request.headers.referer || '/',
json: (ctx.request.headers.accept || '').includes('application/json'),
};
this.response = {
body: {},
type: '',
status: null,
template: null,
redirect: null,
attachment: (name) => ctx.attachment(name),
disposition: null,
};
this.UIContext = {
cdn_prefix: '/',
url_prefix: '/',
};
this.session = {};
}
async render(name: string, context: any) { async render(name: string, context: any) {
// @ts-ignore // @ts-ignore
@@ -339,7 +329,7 @@ export const Handler = HandlerMixin(class {
this.response.type = 'text/html'; this.response.type = 'text/html';
} }
back(body: any) { back(body?: any) {
this.response.body = body || this.response.body || {}; this.response.body = body || this.response.body || {};
this.response.redirect = this.request.headers.referer || '/'; this.response.redirect = this.request.headers.referer || '/';
} }
@@ -371,6 +361,8 @@ export const Handler = HandlerMixin(class {
} }
async init({ domainId }) { async init({ domainId }) {
const xff = await system.get('server.xff');
if (xff) this.request.ip = this.request.headers[xff.toLowerCase()];
await Promise.all([ await Promise.all([
this.getSession(), this.getSession(),
this.getBdoc(), this.getBdoc(),
@@ -481,7 +473,7 @@ export const Handler = HandlerMixin(class {
}; };
await this.finish().catch(() => { }); await this.finish().catch(() => { });
} }
}); }
async function handle(ctx, HandlerClass, checker) { async function handle(ctx, HandlerClass, checker) {
global.Hydro.stat.reqCount++; global.Hydro.stat.reqCount++;
@@ -553,13 +545,13 @@ const Checker = (permPrivChecker) => {
}; };
}; };
export function Route(name: string, route: string, RouteHandler: IHandler, ...permPrivChecker) { export function Route(name: string, route: string, RouteHandler: any, ...permPrivChecker) {
const checker = Checker(permPrivChecker); const checker = Checker(permPrivChecker);
router.all(name, route, (ctx) => handle(ctx, RouteHandler, checker)); router.all(name, route, (ctx) => handle(ctx, RouteHandler, checker));
router.all(`${name}_with_domainId`, `/d/:domainId${route}`, (ctx) => handle(ctx, RouteHandler, checker)); router.all(`${name}_with_domainId`, `/d/:domainId${route}`, (ctx) => handle(ctx, RouteHandler, checker));
} }
export const ConnectionHandler = HandlerMixin(class { export class ConnectionHandler {
conn: sockjs.Connection; conn: sockjs.Connection;
request: { request: {
@@ -587,7 +579,88 @@ export const ConnectionHandler = HandlerMixin(class {
for (const i in p) this.request.params[p[i][0]] = decodeURIComponent(p[i][1]); for (const i in p) this.request.params[p[i][0]] = decodeURIComponent(p[i][1]);
} }
send(data: JSON) { async renderHTML(name: string, context: any): Promise<string> {
if (enableLog) console.time(name);
// FIXME fix this ugly hack
// @ts-ignore
this._user = { ...this.user, gravatar: misc.gravatar(this.user.gravatar, 128) };
const res = await render(name, Object.assign(context, {
handler: this,
// @ts-ignore
url: (...args) => this.url(...args),
_: (str: string) => (str ? str.toString().translate(this.user.viewLang || this.session.viewLang) : ''),
user: this.user,
}));
if (enableLog) console.timeEnd(name);
return res;
}
async limitRate(op, periodSecs, maxOperations) {
await opcount.inc(op, this.request.ip, periodSecs, maxOperations);
}
translate(str) {
return str ? str.toString().translate(this.user.viewLang || this.session.viewLang) : '';
}
renderTitle(str) {
return `${this.translate(str)} - Hydro`;
}
checkPerm(...args) {
for (const i in args) {
if (args[i] instanceof Array) {
let p = false;
for (const j in args) {
if (this.user.hasPerm(args[i][j])) {
p = true;
break;
}
}
if (!p) throw new PermissionError([args[i]]);
} else if (!this.user.hasPerm(args[i])) {
throw new PermissionError([[args[i]]]);
}
}
}
checkPriv(...args) {
for (const i in args) {
if (args[i] instanceof Array) {
let p = false;
for (const j in args) {
if (this.user.hasPriv(args[i][j])) {
p = true;
break;
}
}
if (!p) throw new PrivilegeError([args[i]]);
} else if (!this.user.hasPriv(args[i])) {
throw new PrivilegeError([[args[i]]]);
}
}
}
url(name, kwargs = {}) {
let res = '#';
const args: any = { ...kwargs };
try {
if (this.args.domainId !== 'system') {
name += '_with_domainId';
args.domainId = args.domainId || this.args.domainId;
}
const { anchor, query } = args;
if (query) res = router.url(name, args, { query });
else res = router.url(name, args);
if (anchor) return `${res}#${anchor}`;
} catch (e) {
console.error(e.message);
console.log(name, args);
}
return res;
}
send(data: any) {
this.conn.write(JSON.stringify(data)); this.conn.write(JSON.stringify(data));
} }
@@ -611,17 +684,17 @@ export const ConnectionHandler = HandlerMixin(class {
this.user = await user.getById(domainId, this.session.uid); this.user = await user.getById(domainId, this.session.uid);
if (!this.user) throw new UserNotFoundError(this.session.uid); if (!this.user) throw new UserNotFoundError(this.session.uid);
} }
}); }
export function Connection( export function Connection(
name: string, prefix: string, name: string, prefix: string,
RouteConnHandler: IConnectionHandler, RouteConnHandler: any,
...permPrivChecker: Array<number | string | Function> ...permPrivChecker: Array<number | string | Function>
) { ) {
const sock = sockjs.createServer({ prefix }); const sock = sockjs.createServer({ prefix });
const checker = Checker(permPrivChecker); const checker = Checker(permPrivChecker);
sock.on('connection', async (conn) => { sock.on('connection', async (conn) => {
const h = new RouteConnHandler(conn); const h: H = new RouteConnHandler(conn);
try { try {
const args = { domainId: 'system', ...h.request.params }; const args = { domainId: 'system', ...h.request.params };
h.args = args; h.args = args;
+14 -10
View File
@@ -50,19 +50,23 @@ Array.isDiff = function isDiff(a, b) {
}; };
Date.prototype.format = function formatDate(fmt = '%Y-%m-%d %H:%M:%S') { Date.prototype.format = function formatDate(fmt = '%Y-%m-%d %H:%M:%S') {
let h = this.getHours(); let m = this.getMonth() + 1;
if (h < 10) h = `0${h}`;
let m = this.getMinutes();
if (m < 10) m = `0${m}`; if (m < 10) m = `0${m}`;
let s = this.getSeconds(); let d = this.getDate();
if (s < 10) s = `0${s}`; if (d < 10) d = `0${d}`;
let H = this.getHours();
if (H < 10) H = `0${H}`;
let M = this.getMinutes();
if (M < 10) M = `0${M}`;
let S = this.getSeconds();
if (S < 10) S = `0${S}`;
return fmt return fmt
.replace('%Y', this.getFullYear()) .replace('%Y', this.getFullYear())
.replace('%m', this.getMonth() + 1) .replace('%m', m)
.replace('%d', this.getDate()) .replace('%d', d)
.replace('%H', h) .replace('%H', H)
.replace('%M', m) .replace('%M', M)
.replace('%S', s); .replace('%S', S);
}; };
Math.sum = function sum(...args) { Math.sum = function sum(...args) {
+2 -2
View File
@@ -1,7 +1,7 @@
{ {
"name": "hydrooj", "name": "hydrooj",
"version": "2.7.0", "version": "2.7.2",
"main": "hydro/loader.js", "main": "hydro/loader.ts",
"repository": "https://github.com/hydro-dev/Hydro.git", "repository": "https://github.com/hydro-dev/Hydro.git",
"author": "masnn", "author": "masnn",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",