添加全局rate_limit,更换ui节点接口

This commit is contained in:
undefined committed 2020-09-05 22:17:21 +08:00
1 parent 02c1866963
commit 03dc8903e2
22 files changed
+231 -216

No files matched your search

+2
View File
@@ -10,6 +10,7 @@ import {
Route, Handler, param, Types,
} from 'hydrooj/dist/service/server';
import { streamToBuffer } from 'hydrooj/dist/utils';
import { ProblemAdd } from 'hydrooj/dist/lib/ui';
import * as file from 'hydrooj/dist/model/file';
import { PERM } from 'hydrooj/dist/model/builtin';
@@ -123,6 +124,7 @@ class FpsProblemImportHandler extends Handler {
export async function apply() {
Route('problem_import_fps', '/problem/import/fps', FpsProblemImportHandler, PERM.PERM_CREATE_PROBLEM);
ProblemAdd('problem_import_fps', {}, 'copy', 'From FPS File');
}
global.Hydro.handler.fpsImport = apply;
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/fps-importer",
"version": "1.0.8",
"version": "1.1.0",
"description": "Import FPS problems",
"main": "package.json",
"repository": "https://github.com/hydro-dev/Hydro.git",
@@ -16,7 +16,7 @@
"bson": "^4.1.0"
},
"peerDependencies": {
"hydrooj": "^2.12.22"
"hydrooj": "^2.13.0"
},
"scripts": {
"build": "tsc",
-1
View File
@@ -1,5 +1,4 @@
import 'hydrooj';
import 'hydrooj/src/lib/i18n';
import fs from 'fs';
import path from 'path';
import { Reader } from 'maxmind';
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/geoip",
"version": "1.1.13",
"version": "1.1.14",
"main": "package.json",
"repository": "git@github.com:hydro-dev/Hydro.git",
"author": "undefined <masnn0@outlook.com>",
@@ -9,6 +9,6 @@
"maxmind": "^4.1.3"
},
"peerDependencies": {
"hydrooj": "^2.12.0"
"hydrooj": "^2.12.0||^2.13.0"
}
}
+2
View File
@@ -1,4 +1,6 @@
jest.mock('hydrooj/src/loader');
import 'hydrooj';
import 'hydrooj/src/lib/i18n';
import * as lib from '@hydrooj/geoip/lib';
describe('geoip', () => {
+4 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/hydrojudge",
"version": "2.0.9",
"version": "2.0.10",
"main": "package.json",
"author": "masnn",
"os": [
@@ -10,13 +10,15 @@
"axios": "^0.19.0",
"bson": "^4.1.0",
"fs-extra": "^9.0.1",
"hydrooj": "^2.12.0",
"js-yaml": "^3.14.0",
"lodash": "^4.17.20",
"p-queue": "^6.6.1",
"shell-quote": "^1.7.2",
"systeminformation": "^4.27.3"
},
"peerDependencies": {
"hydrooj": "^2.12.0||^2.13.0"
},
"license": "AGPL-3.0-only",
"devDependencies": {
"@types/bson": "^4.0.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hydrooj",
"version": "2.12.27",
"version": "2.13.0",
"bin": "bin/hydrooj.js",
"main": "dist/loader.js",
"typings": "dist/loader.d.ts",
+2 -2
View File
@@ -8,7 +8,7 @@ import { argv } from 'yargs';
export const builtinLib = [
'jwt', 'download', 'i18n', 'mail', 'useragent',
'md5', 'misc', 'paginate', 'hash.hydro', 'rank',
'validator', 'nav', 'sysinfo', 'testdata.convert.ini', 'testdataConfig',
'validator', 'ui', 'sysinfo', 'testdata.convert.ini', 'testdataConfig',
'logger',
];
@@ -21,7 +21,7 @@ export const builtinModel = [
export const builtinHandler = [
'home', 'problem', 'record', 'judge', 'user',
'contest', 'training', 'discussion', 'manage', 'import',
'contest', 'training', 'discussion', 'manage', 'import.syzoj',
'misc', 'homework', 'domain',
];
@@ -0,0 +1,108 @@
import fs from 'fs';
import path from 'path';
import os from 'os';
import assert from 'assert';
import superagent from 'superagent';
import { ValidationError, RemoteOnlineJudgeError } from '../error';
import * as problem from '../model/problem';
import { PERM } from '../model/builtin';
import {
Route, Handler, Types, param,
} from '../service/server';
import { isPid } from '../lib/validator';
import download from '../lib/download';
const RE_SYZOJ = /https?:\/\/([a-zA-Z0-9.]+)\/problem\/([0-9]+)\/?/i;
class ProblemImportSYZOJHandler extends Handler {
async get() {
this.response.template = 'problem_import_syzoj.html';
this.response.body = {
path: [
['Hydro', 'homepage'],
['problem_main', 'problem_main'],
['problem_import_syzoj', null],
],
};
}
@param('url', Types.String)
@param('pid', Types.String, true, isPid)
@param('hidden', Types.Boolean)
async post(domainId: string, url: string, targetPid: string, hidden = false) {
assert(url.match(RE_SYZOJ), new ValidationError('url'));
if (!url.endsWith('/')) url += '/';
const [, host, pid] = RE_SYZOJ.exec(url);
const res = await superagent.get(`${url}export`);
assert(res.status === 200, new RemoteOnlineJudgeError('Cannot connect to target server'));
assert(res.body.success, new RemoteOnlineJudgeError((res.body.error || {}).message));
const p = res.body.obj;
const content = [];
if (p.description) {
content.push(
`## ${this.translate('Description')}`,
p.description,
);
}
if (p.input_format) {
content.push(
`## ${this.translate('Input Format')}`,
p.input_format,
);
}
if (p.output_format) {
content.push(
`## ${this.translate('Output Format')}`,
p.output_format,
);
}
if (p.example) content.push(p.example);
if (p.hint) {
content.push(
`## ${this.translate('Hint')}`,
p.hint,
);
}
if (p.limit_and_hint) {
content.push(
`## ${this.translate('Limit And Hint')}`,
p.limit_and_hint,
);
}
if (p.have_additional_file) {
content.push(
`## ${this.translate('Additional File')}`,
`${url}download/additional_file`,
);
}
const docId = await problem.add(
domainId, targetPid || `${host}_${pid}`, p.title, content.join(' \n'), this.user._id,
p.tags || [], [], null, hidden,
);
const r = download(`${url}testdata/download`);
const file = path.resolve(os.tmpdir(), 'hydro', `import_${domainId}_${docId}.zip`);
const w = fs.createWriteStream(file);
await new Promise((resolve, reject) => {
w.on('finish', resolve);
w.on('error', reject);
r.pipe(w);
});
await problem.setTestdata(domainId, docId, file);
await problem.edit(domainId, docId, {
config: {
time: `${p.time_limit}ms`,
memory: `${p.memory_limit}m`,
filename: p.file_io_input_name.split('.')[0],
type: p.type === 'traditional' ? 'default' : p.type,
},
});
this.response.body = { pid: pid || docId };
this.response.redirect = this.url('problem_settings', { pid: pid || docId });
}
}
export async function apply() {
Route('problem_import_syzoj', '/problem/import/syzoj', ProblemImportSYZOJHandler, PERM.PERM_CREATE_PROBLEM);
}
global.Hydro.handler['import.syzoj'] = apply;
-57
View File
@@ -1,57 +0,0 @@
import fs from 'fs';
import path from 'path';
import os from 'os';
import { ValidationError } from '../error';
import * as problem from '../model/problem';
import { PERM } from '../model/builtin';
import {
Route, Handler, Types, param,
} from '../service/server';
import { isPid } from '../lib/validator';
class ProblemImportHandler extends Handler {
async get() {
this.response.template = 'problem_import.html';
this.response.body = {
path: [
['Hydro', 'homepage'],
['problem_main', 'problem_main'],
['problem_import', null],
],
};
}
@param('url', Types.String)
@param('pid', Types.String, true, 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');
}
const [pdoc, testdata] = await global.Hydro.lib[`import.${remoteType}`](url, this);
if (hidden) pdoc.hidden = true;
const docId = await problem.add(
domainId, pid || pdoc.pid, pdoc.title, pdoc.content, this.user._id,
pdoc.tag || [], pdoc.category || [], null, pdoc.hidden,
);
if (testdata) {
const file = path.resolve(os.tmpdir(), 'hydro', `import_${domainId}_${pid || pdoc.pid || docId}.zip`);
const w = fs.createWriteStream(file);
await new Promise((resolve, reject) => {
w.on('finish', resolve);
w.on('error', reject);
testdata.pipe(w);
});
await problem.setTestdata(domainId, docId, file);
}
this.response.body = { pid: pid || docId };
this.response.redirect = this.url('problem_settings', { pid: pid || docId });
}
}
export async function apply() {
Route('problem_import', '/problem/import', ProblemImportHandler, PERM.PERM_CREATE_PROBLEM);
}
global.Hydro.handler.import = apply;
+13 -9
View File
@@ -101,10 +101,9 @@ export interface SubtaskConfig {
export interface LocalProblemConfig {
type?: LocalProblemType,
score?: number,
/** ms */
time?: string,
/** Megabytes */
memory?: string,
filename?: string,
checker_type?: string,
checker?: string,
interactor?: string,
@@ -415,13 +414,11 @@ export interface Lib {
download: typeof import('./lib/download'),
'hash.hydro': typeof import('./lib/hash.hydro'),
i18n: typeof import('./lib/i18n'),
'import.syzoj': typeof import('./lib/import.syzoj').syzoj,
jwt: typeof import('./lib/jwt'),
logger: typeof import('./lib/logger'),
mail: typeof import('./lib/mail'),
md5: typeof import('./lib/md5'),
misc: typeof import('./lib/misc'),
nav: typeof import('./lib/nav'),
paginate: typeof import('./lib/paginate'),
rank: typeof import('./lib/rank'),
rating: typeof import('./lib/rating'),
@@ -434,6 +431,17 @@ export interface Lib {
[key: string]: any
}
export interface UI {
manifest: Dictionary<string>,
template: Dictionary<string>,
nodes: {
nav: any[],
problem_add: any[],
},
Nav: typeof import('./lib/ui').Nav,
ProblemAdd: typeof import('./lib/ui').ProblemAdd,
}
declare global {
namespace NodeJS {
interface Global {
@@ -444,11 +452,7 @@ declare global {
service: Service,
lib: Lib,
stat: any,
ui: {
manifest: Dict<string>,
template: Dict<string>,
nav: any,
},
ui: UI,
error: typeof import('./error'),
locales: Dict<Dict<string>>,
postInit: Array<() => Promise<any>>,
+2 -1
View File
@@ -1,4 +1,4 @@
import superagent from 'superagent';
import superagent, { SuperAgentRequest } from 'superagent';
import fs from 'fs';
async function _download(url: string, path: string, retry: number) {
@@ -11,6 +11,7 @@ async function _download(url: string, path: string, retry: number) {
return path;
}
function download(url: string): SuperAgentRequest
function download(url: string, path?: string, retry = 3) {
if (path) return _download(url, path, retry);
return superagent.get(url).retry(retry);
-71
View File
@@ -1,71 +0,0 @@
import assert from 'assert';
import superagent from 'superagent';
import download from './download';
import { ValidationError, RemoteOnlineJudgeError } from '../error';
const RE_SYZOJ = /https?:\/\/([a-zA-Z0-9.]+)\/problem\/([0-9]+)\/?/i;
export async function syzoj(url: string, handler: any) {
assert(url.match(RE_SYZOJ), new ValidationError('url'));
if (!url.endsWith('/')) url += '/';
const [, host, pid] = RE_SYZOJ.exec(url);
const res = await superagent.get(`${url}export`);
assert(res.status === 200, new RemoteOnlineJudgeError('Cannot connect to target server'));
assert(res.body.success, new RemoteOnlineJudgeError((res.body.error || {}).message));
const p = res.body.obj;
const content = [];
if (p.description) {
content.push(
handler.translate('problem.import.problem_description'),
p.description,
);
}
if (p.input_format) {
content.push(
handler.translate('problem.import.input_format'),
p.input_format,
);
}
if (p.output_format) {
content.push(
handler.translate('problem.import.output_format'),
p.output_format,
);
}
if (p.hint) {
content.push(
handler.translate('problem.import.hint'),
p.hint,
);
}
if (p.limit_and_hint) {
content.push(
handler.translate('problem.import.limit_and_hint'),
p.limit_and_hint,
);
}
if (p.have_additional_file) {
content.push(
handler.translate('problem.import.additional_file'),
`${url}download/additional_file`,
);
}
const pdoc = {
title: p.title,
content: content.join(' \n'),
owner: handler.user._id,
from: url,
pid: `${host}_${pid}`,
tags: p.tags,
config: {
time: p.time_limit,
memory: p.memory_limit * 1024,
filename: p.file_io_input_name,
type: p.type,
},
};
const r = download(`${url}testdata/download`);
return [pdoc, r];
}
global.Hydro.lib['import.syzoj'] = syzoj;
-57
View File
@@ -1,57 +0,0 @@
import { isNull } from 'lodash';
import { PERM, PRIV } from '../model/builtin';
global.Hydro.ui.nav = [];
const trueChecker = () => true;
const Checker = (perm: bigint, priv: number, checker: Function = trueChecker) => (handler) => (
checker(handler)
&& (perm ? handler.user.hasPerm(perm) : true)
&& (priv ? handler.user.hasPriv(priv) : true)
);
const Item = (
name: string, args: any, prefix: string,
...permPrivChecker: Array<number | bigint | Function>
) => {
let _priv: number;
let _perm: bigint;
let checker: Function = trueChecker;
for (const item of permPrivChecker) {
if (item instanceof Object && !isNull(item)) {
if (item instanceof Array) {
if (typeof item[0] === 'number') {
// @ts-ignore
_priv = item;
} else if (typeof item[0] === 'bigint') {
// @ts-ignore
_perm = item;
}
} else if (typeof item.call !== 'undefined') {
checker = item;
}
} else if (typeof item === 'number') {
_priv = item;
} else if (typeof item === 'bigint') {
_perm = item;
}
}
checker = Checker(_perm, _priv, checker);
global.Hydro.ui.nav.push({
name, args: args || {}, prefix, checker,
});
};
Item('homepage', null, 'homepage');
Item('problem_main', null, 'problem', PERM.PERM_VIEW_PROBLEM);
Item('training_main', null, 'training', PERM.PERM_VIEW_TRAINING);
Item('homework_main', null, 'homework', PERM.PERM_VIEW_HOMEWORK);
Item('discussion_main', null, 'discussion', PERM.PERM_VIEW_DISCUSSION);
Item('contest_main', null, 'contest', PERM.PERM_VIEW_CONTEST);
Item('record_main', null, 'record');
Item('ranking', null, 'ranking', PERM.PERM_VIEW_RANKING);
Item('domain_dashboard', null, 'domain', PERM.PERM_EDIT_DOMAIN);
Item('manage_dashboard', null, 'manage', PRIV.PRIV_EDIT_SYSTEM);
global.Hydro.lib.nav = Item;
export = Item;
+67
View File
@@ -0,0 +1,67 @@
import { Dictionary, isNull } from 'lodash';
import { PERM, PRIV } from '../model/builtin';
const trueChecker = () => true;
const Checker = (perm: bigint, priv: number, checker: Function = trueChecker) => (handler) => (
checker(handler)
&& (perm ? handler.user.hasPerm(perm) : true)
&& (priv ? handler.user.hasPriv(priv) : true)
);
const buildChecker = (...permPrivChecker: Array<number | bigint | Function>) => {
let _priv: number;
let _perm: bigint;
let checker: Function = trueChecker;
for (const item of permPrivChecker) {
if (item instanceof Object && !isNull(item)) {
if (item instanceof Array) {
if (typeof item[0] === 'number') {
// @ts-ignore
_priv = item;
} else if (typeof item[0] === 'bigint') {
// @ts-ignore
_perm = item;
}
} else if (typeof item.call !== 'undefined') {
checker = item;
}
} else if (typeof item === 'number') {
_priv = item;
} else if (typeof item === 'bigint') {
_perm = item;
}
}
return Checker(_perm, _priv, checker);
};
export const Nav = (
name: string, args: Dictionary<any> = {}, prefix: string,
...permPrivChecker: Array<number | bigint | Function>
) => {
global.Hydro.ui.nodes.nav.push({
name, args: args || {}, prefix, checker: buildChecker(...permPrivChecker),
});
};
export const ProblemAdd = (
name: string, args: Dictionary<any> = {}, icon = 'add', text = 'Create Problem',
) => {
global.Hydro.ui.nodes.problem_add.push({
name, args, icon, text,
});
};
Nav('homepage', {}, 'homepage');
Nav('problem_main', {}, 'problem', PERM.PERM_VIEW_PROBLEM);
Nav('training_main', {}, 'training', PERM.PERM_VIEW_TRAINING);
Nav('homework_main', {}, 'homework', PERM.PERM_VIEW_HOMEWORK);
Nav('discussion_main', {}, 'discussion', PERM.PERM_VIEW_DISCUSSION);
Nav('contest_main', {}, 'contest', PERM.PERM_VIEW_CONTEST);
Nav('record_main', {}, 'record');
Nav('ranking', {}, 'ranking', PERM.PERM_VIEW_RANKING);
Nav('domain_dashboard', {}, 'domain', PERM.PERM_EDIT_DOMAIN);
Nav('manage_dashboard', {}, 'manage', PRIV.PRIV_EDIT_SYSTEM);
ProblemAdd('problem_create');
ProblemAdd('problem_import_syzoj', {}, 'copy', 'Import From SYZOJ');
global.Hydro.ui.Nav = Nav;
global.Hydro.ui.ProblemAdd = ProblemAdd;
+5 -1
View File
@@ -24,9 +24,13 @@ if (!global.Hydro) {
script: {},
// @ts-ignore
lib: {},
// @ts-ignore
ui: {
manifest: {},
nav: [],
nodes: {
nav: [],
problem_add: [],
},
template: {},
},
// @ts-ignore
+11
View File
@@ -807,6 +807,17 @@ export async function start() {
skip: (req, res) => res.hasHeader('nolog'),
}));
}
app.use(async (ctx, next) => {
const xff = await system.get('server.xff');
const ip = xff ? ctx.request.headers[xff] : ctx.request.ip;
try {
await opcount.inc('global', ip, 10, 100);
} catch (e) {
ctx.status = 429;
return null;
}
return await next();
});
app.use(router.routes()).use(router.allowedMethods());
server.listen(argv.port || port);
console.log('Server listening at: %s', argv.port || port);
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/login-with-github",
"version": "0.0.6",
"version": "0.0.7",
"main": "package.json",
"repository": "git@github.com:hydro-dev/Hydro.git",
"author": "undefined <masnn0@outlook.com>",
@@ -14,7 +14,7 @@
"@types/superagent-proxy": "^2.0.0"
},
"peerDependencies": {
"hydrooj": "^2.12.1"
"hydrooj": "^2.12.1||^2.13.0"
},
"dependencies": {
"superagent": "^6.1.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/login-with-google",
"version": "0.0.6",
"version": "0.0.7",
"main": "package.json",
"repository": "git@github.com:hydro-dev/Hydro.git",
"author": "undefined <masnn0@outlook.com>",
@@ -14,7 +14,7 @@
"@types/superagent-proxy": "^2.0.0"
},
"peerDependencies": {
"hydrooj": "^2.12.1"
"hydrooj": "^2.12.1||^2.13.0"
},
"dependencies": {
"superagent": "^6.1.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/login-with-osu",
"version": "0.0.6",
"version": "0.0.7",
"main": "package.json",
"repository": "git@github.com:hydro-dev/Hydro.git",
"author": "undefined <masnn0@outlook.com>",
@@ -14,7 +14,7 @@
"@types/superagent-proxy": "^2.0.0"
},
"peerDependencies": {
"hydrooj": "^2.12.1"
"hydrooj": "^2.12.1||^2.13.0"
},
"dependencies": {
"superagent": "^6.1.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/login-with-qq",
"version": "0.0.2",
"version": "0.0.3",
"main": "package.json",
"repository": "git@github.com:hydro-dev/Hydro.git",
"author": "undefined <masnn0@outlook.com>",
@@ -10,7 +10,7 @@
"build": "tsc"
},
"peerDependencies": {
"hydrooj": "^2.12.1"
"hydrooj": "^2.12.1||^2.13.0"
},
"dependencies": {
"ws": "^7.3.1"
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/migrate-vijos",
"version": "1.3.6",
"version": "1.3.7",
"main": "package.json",
"repository": "git@github.com:hydro-dev/Hydro.git",
"author": "undefined <masnn0@outlook.com>",
@@ -14,7 +14,7 @@
"@types/mongodb": "^3.5.27"
},
"peerDependencies": {
"hydrooj": "^2.12.3"
"hydrooj": "^2.13.0"
},
"dependencies": {
"js-yaml": "^3.14.0",