mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core: experimental hot reload support
This commit is contained in:
1 parent
632ae27b88
commit
fd7c9cbeb1
26 files changed
+428
-147
No files matched your search
@@ -63,8 +63,6 @@ class DataReportHandler extends Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function apply() {
|
export function apply(ctx) {
|
||||||
Route('data_report', '/center/report', DataReportHandler);
|
ctx.Route('data_report', '/center/report', DataReportHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Hydro.handler.center = apply;
|
|
||||||
+29
-26
@@ -1,6 +1,6 @@
|
|||||||
import { Client } from '@elastic/elasticsearch';
|
import { Client } from '@elastic/elasticsearch';
|
||||||
import { omit } from 'lodash';
|
import { omit } from 'lodash';
|
||||||
import { addScript, Schema } from 'hydrooj';
|
import { Schema } from 'hydrooj';
|
||||||
import DomainModel from 'hydrooj/src/model/domain';
|
import DomainModel from 'hydrooj/src/model/domain';
|
||||||
import ProblemModel, { ProblemDoc } from 'hydrooj/src/model/problem';
|
import ProblemModel, { ProblemDoc } from 'hydrooj/src/model/problem';
|
||||||
import * as system from 'hydrooj/src/model/system';
|
import * as system from 'hydrooj/src/model/system';
|
||||||
@@ -24,27 +24,6 @@ const processDocument = (doc: Partial<ProblemDoc>) => {
|
|||||||
return omit(doc, indexOmit);
|
return omit(doc, indexOmit);
|
||||||
};
|
};
|
||||||
|
|
||||||
bus.on('problem/add', async (doc, docId) => {
|
|
||||||
await client.index({
|
|
||||||
index: 'problem',
|
|
||||||
id: `${doc.domainId}/${docId}`,
|
|
||||||
document: processDocument(doc),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
bus.on('problem/edit', async (pdoc) => {
|
|
||||||
await client.index({
|
|
||||||
index: 'problem',
|
|
||||||
id: `${pdoc.domainId}/${pdoc.docId}`,
|
|
||||||
document: processDocument(pdoc),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
bus.on('problem/del', async (domainId, docId) => {
|
|
||||||
await client.delete({
|
|
||||||
index: 'problem',
|
|
||||||
id: `${domainId}/${docId}`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
global.Hydro.lib.problemSearch = async (domainId, q, opts) => {
|
global.Hydro.lib.problemSearch = async (domainId, q, opts) => {
|
||||||
const allowedSize = system.get('elasic-search.indexSize') || 10000;
|
const allowedSize = system.get('elasic-search.indexSize') || 10000;
|
||||||
const size = opts?.limit || system.get('pagination.problem');
|
const size = opts?.limit || system.get('pagination.problem');
|
||||||
@@ -98,8 +77,32 @@ async function run({ domainId }, report) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addScript('ensureElasticSearch', 'Elastic problem search re-index')
|
export const apply = (ctx) => {
|
||||||
.args(Schema.object({
|
bus.on('problem/add', async (doc, docId) => {
|
||||||
|
await client.index({
|
||||||
|
index: 'problem',
|
||||||
|
id: `${doc.domainId}/${docId}`,
|
||||||
|
document: processDocument(doc),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
bus.on('problem/edit', async (pdoc) => {
|
||||||
|
await client.index({
|
||||||
|
index: 'problem',
|
||||||
|
id: `${pdoc.domainId}/${pdoc.docId}`,
|
||||||
|
document: processDocument(pdoc),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
bus.on('problem/del', async (domainId, docId) => {
|
||||||
|
await client.delete({
|
||||||
|
index: 'problem',
|
||||||
|
id: `${domainId}/${docId}`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ctx.addScript(
|
||||||
|
'ensureElasticSearch', 'Elastic problem search re-index',
|
||||||
|
Schema.object({
|
||||||
domainId: Schema.string(),
|
domainId: Schema.string(),
|
||||||
}))
|
}),
|
||||||
.action(run);
|
run,
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -129,9 +129,8 @@ class FpsProblemImportHandler extends Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function apply() {
|
export const sideEffect = true;
|
||||||
Route('problem_import_fps', '/problem/import/fps', FpsProblemImportHandler, PERM.PERM_CREATE_PROBLEM);
|
export async function apply(ctx) {
|
||||||
|
ctx.Route('problem_import_fps', '/problem/import/fps', FpsProblemImportHandler, PERM.PERM_CREATE_PROBLEM);
|
||||||
ProblemAdd('problem_import_fps', {}, 'copy', 'From FPS File');
|
ProblemAdd('problem_import_fps', {}, 'copy', 'From FPS File');
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Hydro.handler.fpsImport = apply;
|
|
||||||
@@ -8,6 +8,7 @@ import yaml from 'js-yaml';
|
|||||||
import i18n from '../lib/i18n';
|
import i18n from '../lib/i18n';
|
||||||
import { Logger } from '../logger';
|
import { Logger } from '../logger';
|
||||||
import * as bus from '../service/bus';
|
import * as bus from '../service/bus';
|
||||||
|
import { Runtime } from '../service/module';
|
||||||
|
|
||||||
const logger = new Logger('common');
|
const logger = new Logger('common');
|
||||||
|
|
||||||
@@ -36,7 +37,11 @@ const getLoader = (type: string, filename: string) => async function loader(pend
|
|||||||
if (p && !fail.includes(i)) {
|
if (p && !fail.includes(i)) {
|
||||||
try {
|
try {
|
||||||
logger.info(`${type.replace(/^(.)/, (t) => t.toUpperCase())} init: %s`, i);
|
logger.info(`${type.replace(/^(.)/, (t) => t.toUpperCase())} init: %s`, i);
|
||||||
require(p);
|
const res = require(p);
|
||||||
|
if (res.apply) {
|
||||||
|
const runtime = new Runtime(p);
|
||||||
|
runtime.load(res);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
fail.push(i);
|
fail.push(i);
|
||||||
logger.info(`${type.replace(/^(.)/, (t) => t.toUpperCase())} load fail: %s`, i);
|
logger.info(`${type.replace(/^(.)/, (t) => t.toUpperCase())} load fail: %s`, i);
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ import { Logger } from '../logger';
|
|||||||
import options from '../options';
|
import options from '../options';
|
||||||
import * as bus from '../service/bus';
|
import * as bus from '../service/bus';
|
||||||
import db from '../service/db';
|
import db from '../service/db';
|
||||||
|
import { Runtime } from '../service/module';
|
||||||
import {
|
import {
|
||||||
handler, lib, locale, model, script, service, setting, template,
|
handler, lib, locale, model, script, service, setting, template,
|
||||||
} from './common';
|
} from './common';
|
||||||
|
|
||||||
const logger = new Logger('worker');
|
const logger = new Logger('worker');
|
||||||
const detail = cac().parse().options.loaderDetail;
|
const { loaderDetail: detail, watch } = cac().parse().options;
|
||||||
const tmpdir = path.resolve(os.tmpdir(), 'hydro');
|
const tmpdir = path.resolve(os.tmpdir(), 'hydro');
|
||||||
|
|
||||||
export async function load() {
|
export async function load() {
|
||||||
@@ -53,11 +54,18 @@ export async function load() {
|
|||||||
const server = require('../service/server');
|
const server = require('../service/server');
|
||||||
await server.prepare();
|
await server.prepare();
|
||||||
if (detail) logger.info('finish: server');
|
if (detail) logger.info('finish: server');
|
||||||
|
if (watch) require('../service/watcher');
|
||||||
await service(pending, fail);
|
await service(pending, fail);
|
||||||
if (detail) logger.info('finish: service.extra');
|
if (detail) logger.info('finish: service.extra');
|
||||||
require('../model/index');
|
require('../model/index');
|
||||||
if (detail) logger.info('finish: model.builtin');
|
if (detail) logger.info('finish: model.builtin');
|
||||||
require('../handler/index');
|
const handlerDir = path.resolve(__dirname, '..', 'handler');
|
||||||
|
const handlers = await fs.readdir(handlerDir);
|
||||||
|
for (const h of handlers) {
|
||||||
|
const f = path.resolve(handlerDir, h);
|
||||||
|
const m = require(f);
|
||||||
|
if (m.apply) new Runtime(f).load(m);
|
||||||
|
}
|
||||||
if (detail) logger.info('finish: handler.builtin');
|
if (detail) logger.info('finish: handler.builtin');
|
||||||
await model(pending, fail);
|
await model(pending, fail);
|
||||||
if (detail) logger.info('finish: model.extra');
|
if (detail) logger.info('finish: model.extra');
|
||||||
@@ -68,8 +76,6 @@ export async function load() {
|
|||||||
if (detail) logger.info('finish: handler.extra');
|
if (detail) logger.info('finish: handler.extra');
|
||||||
for (const i in global.Hydro.handler) await global.Hydro.handler[i]();
|
for (const i in global.Hydro.handler) await global.Hydro.handler[i]();
|
||||||
if (detail) logger.info('finish: handler.apply');
|
if (detail) logger.info('finish: handler.apply');
|
||||||
const notfound = require('../handler/notfound');
|
|
||||||
await notfound.apply();
|
|
||||||
require('../script/index');
|
require('../script/index');
|
||||||
if (detail) logger.info('finish: script.builtin');
|
if (detail) logger.info('finish: script.builtin');
|
||||||
await script(pending, fail);
|
await script(pending, fail);
|
||||||
@@ -98,5 +104,6 @@ export async function load() {
|
|||||||
}
|
}
|
||||||
logger.success('Server started');
|
logger.success('Server started');
|
||||||
process.send?.('ready');
|
process.send?.('ready');
|
||||||
|
await bus.serial('app/ready');
|
||||||
return { fail };
|
return { fail };
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ import { makeExecutableSchema } from '@graphql-tools/schema';
|
|||||||
import { graphql, GraphQLSchema } from 'graphql';
|
import { graphql, GraphQLSchema } from 'graphql';
|
||||||
import { resolvers, typeDefs } from 'graphql-scalars';
|
import { resolvers, typeDefs } from 'graphql-scalars';
|
||||||
import * as bus from '../service/bus';
|
import * as bus from '../service/bus';
|
||||||
import { Handler, Route } from '../service/server';
|
import { Handler } from '../service/server';
|
||||||
|
|
||||||
const types: Record<string, Record<string, string>> = {};
|
const types: Record<string, Record<string, string>> = {};
|
||||||
const unions: Record<string, string> = {};
|
const unions: Record<string, string> = {};
|
||||||
@@ -123,8 +123,7 @@ class ApiHandler extends Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function apply() {
|
export const sideEffect = true;
|
||||||
Route('api', '/api', ApiHandler);
|
export function apply(ctx) {
|
||||||
|
ctx.Route('api', '/api', ApiHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Hydro.handler.api = apply;
|
|
||||||
@@ -119,11 +119,9 @@ class BlogEditHandler extends BlogHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function apply() {
|
export async function apply(ctx) {
|
||||||
Route('blog_main', '/blog/:uid', BlogUserHandler);
|
ctx.Route('blog_main', '/blog/:uid', BlogUserHandler);
|
||||||
Route('blog_create', '/blog/:uid/create', BlogEditHandler, PRIV.PRIV_USER_PROFILE);
|
ctx.Route('blog_create', '/blog/:uid/create', BlogEditHandler, PRIV.PRIV_USER_PROFILE);
|
||||||
Route('blog_detail', '/blog/:uid/:did', BlogDetailHandler);
|
ctx.Route('blog_detail', '/blog/:uid/:did', BlogDetailHandler);
|
||||||
Route('blog_edit', '/blog/:uid/:did/edit', BlogEditHandler, PRIV.PRIV_USER_PROFILE);
|
ctx.Route('blog_edit', '/blog/:uid/:did/edit', BlogEditHandler, PRIV.PRIV_USER_PROFILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Hydro.handler.blog = apply;
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Handler, Route } from '../service/server';
|
import { Handler } from '../service/server';
|
||||||
|
|
||||||
class ProblemCategoryCompatHandler extends Handler {
|
class ProblemCategoryCompatHandler extends Handler {
|
||||||
async get({ category }) {
|
async get({ category }) {
|
||||||
@@ -6,6 +6,6 @@ class ProblemCategoryCompatHandler extends Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Hydro.handler.compat = () => {
|
export const apply = (ctx) => {
|
||||||
Route('problem_category_compat', '/p/category/:category', ProblemCategoryCompatHandler);
|
ctx.Route('problem_category_compat', '/p/category/:category', ProblemCategoryCompatHandler);
|
||||||
};
|
};
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import './home';
|
|
||||||
import './problem';
|
|
||||||
import './record';
|
|
||||||
import './judge';
|
|
||||||
import './user';
|
|
||||||
import './contest';
|
|
||||||
import './training';
|
|
||||||
import './discussion';
|
|
||||||
import './manage';
|
|
||||||
import './import';
|
|
||||||
import './misc';
|
|
||||||
import './homework';
|
|
||||||
import './domain';
|
|
||||||
import './status';
|
|
||||||
import './api';
|
|
||||||
import './blog';
|
|
||||||
import './compat';
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/* eslint-disable no-empty-function */
|
|
||||||
/* eslint-disable class-methods-use-this */
|
|
||||||
import { NotFoundError } from '../error';
|
|
||||||
import {
|
|
||||||
Handler, param, Route, Types,
|
|
||||||
} from '../service/server';
|
|
||||||
|
|
||||||
class NotFoundHandler extends Handler {
|
|
||||||
@param('bsod', Types.Boolean)
|
|
||||||
prepare(domainId: string, bsod: boolean) {
|
|
||||||
if (bsod) throw new Error(this.request.path);
|
|
||||||
throw new NotFoundError(this.request.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
get() { }
|
|
||||||
post() { }
|
|
||||||
head() { }
|
|
||||||
put() { }
|
|
||||||
delete() { }
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function apply() {
|
|
||||||
Route('notfound', '(/.*)+', NotFoundHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
global.Hydro.handler.notfound = apply;
|
|
||||||
@@ -51,6 +51,7 @@ export function addon(addonPath: string, prepend = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @deprecated */
|
||||||
export function addScript(name: string, description: string) {
|
export function addScript(name: string, description: string) {
|
||||||
if (global.Hydro.script[name]) throw new Error(`duplicate script ${name} registered.`);
|
if (global.Hydro.script[name]) throw new Error(`duplicate script ${name} registered.`);
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||||
import { ObjectID } from 'mongodb';
|
import { ObjectID } from 'mongodb';
|
||||||
import Schema from 'schemastery';
|
import Schema from 'schemastery';
|
||||||
import { addScript } from '../loader';
|
|
||||||
import blacklist from '../model/blacklist';
|
import blacklist from '../model/blacklist';
|
||||||
import * as discussion from '../model/discussion';
|
import * as discussion from '../model/discussion';
|
||||||
import * as document from '../model/document';
|
import * as document from '../model/document';
|
||||||
@@ -61,8 +60,9 @@ async function _user(
|
|||||||
if (!dryrun) await UserModel.ban(uid);
|
if (!dryrun) await UserModel.ban(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
addScript('blacklist', 'Add blacklist by ip, uid')
|
export const apply = (ctx) => ctx.addScript(
|
||||||
.args(Schema.object({
|
'blacklist', 'Add blacklist by ip, uid',
|
||||||
|
Schema.object({
|
||||||
address: Schema.string(),
|
address: Schema.string(),
|
||||||
discuss: Schema.object({
|
discuss: Schema.object({
|
||||||
domainId: Schema.string(),
|
domainId: Schema.string(),
|
||||||
@@ -70,8 +70,8 @@ addScript('blacklist', 'Add blacklist by ip, uid')
|
|||||||
}),
|
}),
|
||||||
user: Schema.number(),
|
user: Schema.number(),
|
||||||
dryrun: Schema.boolean(),
|
dryrun: Schema.boolean(),
|
||||||
}))
|
}),
|
||||||
.action(async ({
|
async ({
|
||||||
address = null, discuss = null, user = null, dryrun = true,
|
address = null, discuss = null, user = null, dryrun = true,
|
||||||
}, report) => {
|
}, report) => {
|
||||||
if (address) await _address(address, new Set(), new Set(), new Set(), dryrun, report);
|
if (address) await _address(address, new Set(), new Set(), new Set(), dryrun, report);
|
||||||
@@ -83,4 +83,5 @@ addScript('blacklist', 'Add blacklist by ip, uid')
|
|||||||
}
|
}
|
||||||
if (user) await _user(user, new Set(), new Set(), new Set(), dryrun, report);
|
if (user) await _user(user, new Set(), new Set(), new Set(), dryrun, report);
|
||||||
return true;
|
return true;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
import Schema from 'schemastery';
|
import Schema from 'schemastery';
|
||||||
import * as semver from 'semver';
|
import * as semver from 'semver';
|
||||||
import superagent from 'superagent';
|
import superagent from 'superagent';
|
||||||
import { addScript } from '../loader';
|
|
||||||
import MessageModel from '../model/message';
|
import MessageModel from '../model/message';
|
||||||
import * as system from '../model/system';
|
import * as system from '../model/system';
|
||||||
|
|
||||||
@@ -38,4 +37,4 @@ export async function run(_: {}, report: Function) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addScript('checkUpdate', 'Daily update check').args(Schema.any()).action(run);
|
export const apply = (ctx) => ctx.addScript('checkUpdate', 'Daily update check', Schema.any(), run);
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import Schema from 'schemastery';
|
import Schema from 'schemastery';
|
||||||
import { addScript } from '../loader';
|
|
||||||
import { PRIV } from '../model/builtin';
|
import { PRIV } from '../model/builtin';
|
||||||
import user from '../model/user';
|
import user from '../model/user';
|
||||||
import db from '../service/db';
|
import db from '../service/db';
|
||||||
@@ -9,7 +8,7 @@ const collDomainUser = db.collection('domain.user');
|
|||||||
const collRecord = db.collection('record');
|
const collRecord = db.collection('record');
|
||||||
const collMessage = db.collection('message');
|
const collMessage = db.collection('message');
|
||||||
|
|
||||||
addScript('deleteUser', 'Delete a user')
|
export const apply = (ctx) => ctx.addScript('deleteUser', 'Delete a user')
|
||||||
.args(Schema.object({
|
.args(Schema.object({
|
||||||
uid: Schema.number(),
|
uid: Schema.number(),
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import { ObjectID } from 'mongodb';
|
import { ObjectID } from 'mongodb';
|
||||||
import Schema from 'schemastery';
|
import Schema from 'schemastery';
|
||||||
import { addScript } from '../loader';
|
|
||||||
import { STATUS } from '../model/builtin';
|
import { STATUS } from '../model/builtin';
|
||||||
import * as document from '../model/document';
|
import * as document from '../model/document';
|
||||||
import db from '../service/db';
|
import db from '../service/db';
|
||||||
@@ -153,15 +152,17 @@ export async function pdoc(report) {
|
|||||||
if (bulk.length) await bulk.execute();
|
if (bulk.length) await bulk.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
addScript('problemStat', 'Recalculates nSubmit and nAccept in problem status.')
|
export const apply = (ctx) => ctx.addScript(
|
||||||
.args(Schema.object({
|
'problemStat', 'Recalculates nSubmit and nAccept in problem status.',
|
||||||
|
Schema.object({
|
||||||
udoc: Schema.boolean(),
|
udoc: Schema.boolean(),
|
||||||
pdoc: Schema.boolean(),
|
pdoc: Schema.boolean(),
|
||||||
psdoc: Schema.boolean(),
|
psdoc: Schema.boolean(),
|
||||||
}))
|
}),
|
||||||
.action(async (arg, report) => {
|
async (arg, report) => {
|
||||||
if (arg.pdoc === undefined || arg.pdoc) await pdoc(report);
|
if (arg.pdoc === undefined || arg.pdoc) await pdoc(report);
|
||||||
if (arg.udoc === undefined || arg.udoc) await udoc(report);
|
if (arg.udoc === undefined || arg.udoc) await udoc(report);
|
||||||
if (arg.psdoc === undefined || arg.psdoc) await psdoc(report);
|
if (arg.psdoc === undefined || arg.psdoc) await psdoc(report);
|
||||||
return true;
|
return true;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
@@ -6,7 +6,6 @@ import Schema from 'schemastery';
|
|||||||
import { Tdoc, Udoc } from '../interface';
|
import { Tdoc, Udoc } from '../interface';
|
||||||
import difficultyAlgorithm from '../lib/difficulty';
|
import difficultyAlgorithm from '../lib/difficulty';
|
||||||
import rating from '../lib/rating';
|
import rating from '../lib/rating';
|
||||||
import { addScript } from '../loader';
|
|
||||||
import { PRIV, STATUS } from '../model/builtin';
|
import { PRIV, STATUS } from '../model/builtin';
|
||||||
import * as contest from '../model/contest';
|
import * as contest from '../model/contest';
|
||||||
import domain from '../model/domain';
|
import domain from '../model/domain';
|
||||||
@@ -184,8 +183,7 @@ export async function run({ domainId }, report: Function) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addScript('rp', 'Calculate rp of a domain, or all domains')
|
export const apply = (ctx) => ctx.addScript(
|
||||||
.args(Schema.object({
|
'rp', 'Calculate rp of a domain, or all domains',
|
||||||
domainId: Schema.string(),
|
Schema.object({ domainId: Schema.string() }), run,
|
||||||
}))
|
);
|
||||||
.action(run);
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import Schema from 'schemastery';
|
import Schema from 'schemastery';
|
||||||
import { addScript } from '../loader';
|
|
||||||
import { STATUS } from '../model/builtin';
|
import { STATUS } from '../model/builtin';
|
||||||
import storage from '../model/storage';
|
import storage from '../model/storage';
|
||||||
|
|
||||||
@@ -37,6 +36,4 @@ export async function run(_, report) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addScript('storageUsage', 'Calculate storage usage')
|
export const apply = (ctx) => ctx.addScript('storageUsage', 'Calculate storage usage', Schema.any(), run);
|
||||||
.args(Schema.any())
|
|
||||||
.action(run);
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import Schema from 'schemastery';
|
||||||
|
import * as bus from './bus';
|
||||||
|
|
||||||
|
export const runtimes: Record<string, Runtime> = Object.create(null);
|
||||||
|
|
||||||
|
function addScript<K>(name: string, description: string, validate: Schema<K>, run: (args: K, report: any) => boolean | Promise<boolean>) {
|
||||||
|
if (global.Hydro.script[name]) throw new Error(`duplicate script ${name} registered.`);
|
||||||
|
global.Hydro.script[name] = { description, validate, run };
|
||||||
|
return () => delete global.Hydro.script[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Context {
|
||||||
|
Route: typeof import('./server').Route;
|
||||||
|
Connection: typeof import('./server').Connection;
|
||||||
|
on: typeof bus['on'];
|
||||||
|
once: typeof bus['once'];
|
||||||
|
off: typeof bus['off'];
|
||||||
|
addScript: typeof addScript;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Runtime {
|
||||||
|
private disposables = [];
|
||||||
|
public sideEffect = false;
|
||||||
|
public loaded = false;
|
||||||
|
|
||||||
|
constructor(public filename: string) {
|
||||||
|
runtimes[filename] = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
load(module: any) {
|
||||||
|
if (this.loaded) throw new Error(`Module ${this.filename} was already loaded`);
|
||||||
|
this.sideEffect = false;
|
||||||
|
if (typeof module.dispose === 'function') this.disposables.push(module.dispose);
|
||||||
|
const T = (origFunc) => (origFunc ? (...args) => {
|
||||||
|
const res = origFunc(...args);
|
||||||
|
this.disposables.push(res);
|
||||||
|
return res;
|
||||||
|
} : null);
|
||||||
|
module.apply({
|
||||||
|
Route: T(global.Hydro.service.server?.Route),
|
||||||
|
Connection: T(global.Hydro.service.server?.Connection),
|
||||||
|
on: T(bus.on),
|
||||||
|
once: T(bus.once),
|
||||||
|
off: bus.off,
|
||||||
|
addScript: T(addScript),
|
||||||
|
} as Context);
|
||||||
|
if (module.sideEffect) this.sideEffect = true;
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this.disposables.map((i) => i());
|
||||||
|
this.disposables = [];
|
||||||
|
this.loaded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,10 @@ import type WebSocket from 'ws';
|
|||||||
import type { KoaContext } from './server';
|
import type { KoaContext } from './server';
|
||||||
|
|
||||||
type WebSocketCallback = (socket: WebSocket, request: IncomingMessage, ctx: KoaContext) => void;
|
type WebSocketCallback = (socket: WebSocket, request: IncomingMessage, ctx: KoaContext) => void;
|
||||||
|
function remove<T>(list: T[], item: T) {
|
||||||
|
const index = list.indexOf(item);
|
||||||
|
if (index >= 0) list.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
export class WebSocketLayer {
|
export class WebSocketLayer {
|
||||||
clients = new Set<WebSocket>();
|
clients = new Set<WebSocket>();
|
||||||
@@ -34,20 +38,21 @@ export class WebSocketLayer {
|
|||||||
|
|
||||||
export class Router extends KoaRouter {
|
export class Router extends KoaRouter {
|
||||||
wsStack: WebSocketLayer[] = [];
|
wsStack: WebSocketLayer[] = [];
|
||||||
|
disposeLastOp = () => null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* hack into router methods to make sure that koa middlewares are disposable
|
* hack into router methods to make sure that koa middlewares are disposable
|
||||||
*/
|
*/
|
||||||
register(...args: Parameters<KoaRouter['register']>) {
|
register(...args: Parameters<KoaRouter['register']>) {
|
||||||
const layer = super.register(...args);
|
const layer = super.register(...args);
|
||||||
// TODO handle unregister
|
this.disposeLastOp = () => remove(this.stack, layer);
|
||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
ws(path: Parameters<typeof pathToRegexp>[0], callback?: WebSocketCallback) {
|
ws(path: Parameters<typeof pathToRegexp>[0], callback?: WebSocketCallback) {
|
||||||
const layer = new WebSocketLayer(this, path, callback);
|
const layer = new WebSocketLayer(this, path, callback);
|
||||||
this.wsStack.push(layer);
|
this.wsStack.push(layer);
|
||||||
// TODO handle unregistry
|
this.disposeLastOp = () => remove(this.wsStack, layer);
|
||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -363,6 +363,7 @@ const Checker = (permPrivChecker) => {
|
|||||||
|
|
||||||
export function Route(name: string, path: string, RouteHandler: any, ...permPrivChecker) {
|
export function Route(name: string, path: string, RouteHandler: any, ...permPrivChecker) {
|
||||||
router.all(name, path, (ctx) => handle(ctx as any, RouteHandler, Checker(permPrivChecker)));
|
router.all(name, path, (ctx) => handle(ctx as any, RouteHandler, Checker(permPrivChecker)));
|
||||||
|
return router.disposeLastOp;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ConnectionHandler extends HandlerCommon {
|
export class ConnectionHandler extends HandlerCommon {
|
||||||
@@ -429,11 +430,16 @@ export function Connection(
|
|||||||
|
|
||||||
let started = false;
|
let started = false;
|
||||||
|
|
||||||
|
class NotFoundHandler extends Handler {
|
||||||
|
prepare() { throw new NotFoundError(this.request.path); }
|
||||||
|
all() { }
|
||||||
|
}
|
||||||
|
|
||||||
// TODO use postInit?
|
// TODO use postInit?
|
||||||
export async function start() {
|
export async function start() {
|
||||||
if (started) return;
|
if (started) return;
|
||||||
const port = system.get('server.port');
|
const port = system.get('server.port');
|
||||||
app.use(router.routes()).use(router.allowedMethods());
|
app.use(router.routes()).use(router.allowedMethods()).use((ctx) => handle(ctx, NotFoundHandler, () => true));
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
httpServer.listen(argv.options.port || port, () => {
|
httpServer.listen(argv.options.port || port, () => {
|
||||||
logger.success('Server listening at: %d', argv.options.port || port);
|
logger.success('Server listening at: %d', argv.options.port || port);
|
||||||
|
|||||||
@@ -0,0 +1,235 @@
|
|||||||
|
/* eslint-disable consistent-return */
|
||||||
|
/* eslint-disable import/no-dynamic-require */
|
||||||
|
/* eslint-disable @typescript-eslint/no-shadow */
|
||||||
|
import { relative, resolve } from 'path';
|
||||||
|
import { FSWatcher, watch } from 'chokidar';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import { Logger } from '../logger';
|
||||||
|
import * as bus from './bus';
|
||||||
|
import { Runtime, runtimes } from './module';
|
||||||
|
|
||||||
|
function loadDependencies(filename: string, ignored: Set<string>) {
|
||||||
|
const dependencies = new Set<string>();
|
||||||
|
function traverse({ filename, children }: NodeJS.Module) {
|
||||||
|
if (ignored.has(filename) || dependencies.has(filename) || filename.includes('/node_modules/')) return;
|
||||||
|
dependencies.add(filename);
|
||||||
|
children.forEach(traverse);
|
||||||
|
}
|
||||||
|
traverse(require.cache[filename]);
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unwrapExports(module: any) {
|
||||||
|
return module?.default || module;
|
||||||
|
}
|
||||||
|
|
||||||
|
const logger = new Logger('watch');
|
||||||
|
|
||||||
|
export function coerce(val: any) {
|
||||||
|
// resolve error when stack is undefined, e.g. axios error with status code 401
|
||||||
|
const { message, stack } = val instanceof Error && val.stack ? val : new Error(val as any);
|
||||||
|
const lines = stack.split('\n');
|
||||||
|
const index = lines.findIndex((line) => line.endsWith(message));
|
||||||
|
return lines.slice(index).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
class Watcher {
|
||||||
|
private root: string;
|
||||||
|
private watcher: FSWatcher;
|
||||||
|
private externals: Set<string>;
|
||||||
|
private accepted: Set<string>;
|
||||||
|
private declined: Set<string>;
|
||||||
|
private stashed = new Set<string>();
|
||||||
|
|
||||||
|
constructor(private config: any) {
|
||||||
|
this.externals = new Set(Object.keys(require.cache));
|
||||||
|
bus.on('app/ready', () => this.start());
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.root = resolve(process.cwd());
|
||||||
|
this.watcher = watch(this.root, {
|
||||||
|
...this.config,
|
||||||
|
ignored: ['**/node_modules/**', '**/.git/**', '**/logs/**', '**/.cache/**'],
|
||||||
|
});
|
||||||
|
logger.info(`Start watching changes in ${this.root}`);
|
||||||
|
|
||||||
|
// files independent from any plugins will trigger a full reload
|
||||||
|
const triggerLocalReload = debounce(() => this.triggerLocalReload(), 1000);
|
||||||
|
|
||||||
|
this.watcher.on('change', (path) => {
|
||||||
|
logger.debug('change detected:', relative(this.root, path));
|
||||||
|
|
||||||
|
if (this.externals.has(path)) {
|
||||||
|
logger.warn('Require full reload');
|
||||||
|
} else if (require.cache[path]) {
|
||||||
|
this.stashed.add(path);
|
||||||
|
triggerLocalReload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
return this.watcher.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private analyzeChanges() {
|
||||||
|
/** files pending classification */
|
||||||
|
const pending: string[] = [];
|
||||||
|
|
||||||
|
this.accepted = new Set(this.stashed);
|
||||||
|
this.declined = new Set(this.externals);
|
||||||
|
|
||||||
|
this.stashed.forEach((filename) => {
|
||||||
|
const { children } = require.cache[filename];
|
||||||
|
for (const { filename } of children) {
|
||||||
|
if (this.accepted.has(filename) || this.declined.has(filename) || filename.includes('/node_modules/')) continue;
|
||||||
|
pending.push(filename);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while (pending.length) {
|
||||||
|
let index = 0;
|
||||||
|
let hasUpdate = false;
|
||||||
|
while (index < pending.length) {
|
||||||
|
const filename = pending[index];
|
||||||
|
const { children } = require.cache[filename];
|
||||||
|
let isDeclined = true;
|
||||||
|
let isAccepted = false;
|
||||||
|
for (const { filename } of children) {
|
||||||
|
if (this.declined.has(filename) || filename.includes('/node_modules/')) continue;
|
||||||
|
if (this.accepted.has(filename)) {
|
||||||
|
isAccepted = true;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
isDeclined = false;
|
||||||
|
if (!pending.includes(filename)) {
|
||||||
|
hasUpdate = true;
|
||||||
|
pending.push(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isAccepted || isDeclined) {
|
||||||
|
hasUpdate = true;
|
||||||
|
pending.splice(index, 1);
|
||||||
|
if (isAccepted) {
|
||||||
|
this.accepted.add(filename);
|
||||||
|
} else {
|
||||||
|
this.declined.add(filename);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// infinite loop
|
||||||
|
if (!hasUpdate) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const filename of pending) {
|
||||||
|
this.declined.add(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private triggerLocalReload() {
|
||||||
|
const start = Date.now();
|
||||||
|
this.analyzeChanges();
|
||||||
|
console.log(this.accepted);
|
||||||
|
|
||||||
|
/** plugins pending classification */
|
||||||
|
const pending = new Map<string, Runtime>();
|
||||||
|
|
||||||
|
/** plugins that should be reloaded */
|
||||||
|
const reloads = new Map<Runtime, string>();
|
||||||
|
|
||||||
|
// we assume that plugin entry files are "atomic"
|
||||||
|
// that is, reloading them will not cause any other reloads
|
||||||
|
for (const filename in require.cache) {
|
||||||
|
const module = require.cache[filename];
|
||||||
|
const plugin = unwrapExports(module.exports);
|
||||||
|
const runtime = runtimes[filename];
|
||||||
|
if (!runtime || this.declined.has(filename)) continue;
|
||||||
|
pending.set(filename, runtime);
|
||||||
|
if (!runtimes[filename]?.sideEffect && !plugin?.['sideEffect']) this.declined.add(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [filename, runtime] of pending) {
|
||||||
|
// check if it is a dependent of the changed file
|
||||||
|
this.declined.delete(filename);
|
||||||
|
const dependencies = [...loadDependencies(filename, this.declined)];
|
||||||
|
|
||||||
|
// we only detect reloads at plugin level
|
||||||
|
// a plugin will be reloaded if any of its dependencies are accepted
|
||||||
|
if (!dependencies.some((dep) => this.accepted.has(dep))) continue;
|
||||||
|
dependencies.forEach((dep) => this.accepted.add(dep));
|
||||||
|
|
||||||
|
// prepare for reload
|
||||||
|
let isMarked = false;
|
||||||
|
const visited = new Set<Runtime>();
|
||||||
|
const queued = [runtime];
|
||||||
|
while (queued.length) {
|
||||||
|
const runtime = queued.shift();
|
||||||
|
if (visited.has(runtime)) continue;
|
||||||
|
visited.add(runtime);
|
||||||
|
if (reloads.has(runtime)) {
|
||||||
|
isMarked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isMarked) reloads.set(runtime, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
const backup: Record<string, NodeJS.Module> = {};
|
||||||
|
for (const filename of this.accepted) {
|
||||||
|
backup[filename] = require.cache[filename];
|
||||||
|
delete require.cache[filename];
|
||||||
|
}
|
||||||
|
|
||||||
|
function rollback() {
|
||||||
|
for (const filename in backup) {
|
||||||
|
require.cache[filename] = backup[filename];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug('Will reload the following file(s): %o', this.accepted.keys());
|
||||||
|
const attempts = {};
|
||||||
|
try {
|
||||||
|
for (const [, filename] of reloads) {
|
||||||
|
attempts[filename] = unwrapExports(require(filename));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn(err);
|
||||||
|
return rollback();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const [runtime, filename] of reloads) {
|
||||||
|
const path = relative(this.root, filename);
|
||||||
|
|
||||||
|
try {
|
||||||
|
runtime.dispose();
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn(`failed to dispose plugin at %c\n${coerce(err)}`, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
runtime.load(attempts[filename]);
|
||||||
|
logger.info('reload plugin at %c', path);
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn(`failed to reload plugin at %c\n${coerce(err)}`, path);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// rollback require.cache and plugin states
|
||||||
|
rollback();
|
||||||
|
logger.warn('Rolling back changes');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset stashed files
|
||||||
|
this.stashed = new Set();
|
||||||
|
logger.success('Reload done in %d ms', Date.now() - start);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new Watcher({});
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
import './scripts/hustoj';
|
import { apply as hustoj } from './scripts/hustoj';
|
||||||
import './scripts/vijos';
|
import { apply as syzoj } from './scripts/syzoj';
|
||||||
import './scripts/syzoj';
|
import { apply as vijos } from './scripts/vijos';
|
||||||
|
|
||||||
|
export function apply(ctx) {
|
||||||
|
hustoj(ctx);
|
||||||
|
vijos(ctx);
|
||||||
|
syzoj(ctx);
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import mysql from 'mysql';
|
|||||||
import TurndownService from 'turndown';
|
import TurndownService from 'turndown';
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||||
import { noop, Time } from '@hydrooj/utils/lib/utils';
|
import { noop, Time } from '@hydrooj/utils/lib/utils';
|
||||||
import { addScript, Schema } from 'hydrooj';
|
import { Schema } from 'hydrooj';
|
||||||
import { NotFoundError } from 'hydrooj/src/error';
|
import { NotFoundError } from 'hydrooj/src/error';
|
||||||
import { postJudge } from 'hydrooj/src/handler/judge';
|
import { postJudge } from 'hydrooj/src/handler/judge';
|
||||||
import { RecordDoc } from 'hydrooj/src/interface';
|
import { RecordDoc } from 'hydrooj/src/interface';
|
||||||
@@ -311,8 +311,9 @@ export async function run({
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addScript('migrateHustoj', 'migrate from hustoj')
|
export const apply = (ctx) => ctx.addScript(
|
||||||
.args(Schema.object({
|
'migrateHustoj', 'migrate from hustoj',
|
||||||
|
Schema.object({
|
||||||
host: Schema.string().required(),
|
host: Schema.string().required(),
|
||||||
port: Schema.number().required(),
|
port: Schema.number().required(),
|
||||||
name: Schema.string().required(),
|
name: Schema.string().required(),
|
||||||
@@ -321,4 +322,6 @@ addScript('migrateHustoj', 'migrate from hustoj')
|
|||||||
domainId: Schema.string().required(),
|
domainId: Schema.string().required(),
|
||||||
contestType: Schema.string().required(),
|
contestType: Schema.string().required(),
|
||||||
dataDir: Schema.string().required(),
|
dataDir: Schema.string().required(),
|
||||||
})).action(run);
|
}),
|
||||||
|
run,
|
||||||
|
);
|
||||||
@@ -5,7 +5,7 @@ import mariadb from 'mariadb';
|
|||||||
import { ObjectID } from 'mongodb';
|
import { ObjectID } from 'mongodb';
|
||||||
import { STATUS } from '@hydrooj/utils/lib/status';
|
import { STATUS } from '@hydrooj/utils/lib/status';
|
||||||
import { noop, Time } from '@hydrooj/utils/lib/utils';
|
import { noop, Time } from '@hydrooj/utils/lib/utils';
|
||||||
import { addScript, Schema } from 'hydrooj';
|
import { Schema } from 'hydrooj';
|
||||||
import { NotFoundError } from 'hydrooj/src/error';
|
import { NotFoundError } from 'hydrooj/src/error';
|
||||||
import { postJudge } from 'hydrooj/src/handler/judge';
|
import { postJudge } from 'hydrooj/src/handler/judge';
|
||||||
import { DiscussionDoc, DiscussionReplyDoc, RecordDoc } from 'hydrooj/src/interface';
|
import { DiscussionDoc, DiscussionReplyDoc, RecordDoc } from 'hydrooj/src/interface';
|
||||||
@@ -227,7 +227,7 @@ export async function run({
|
|||||||
hint: pdoc.limit_and_hint,
|
hint: pdoc.limit_and_hint,
|
||||||
});
|
});
|
||||||
for (const match of content.matchAll(fileReg)) {
|
for (const match of content.matchAll(fileReg)) {
|
||||||
const [,origialPath, pid, filename] = match;
|
const [, origialPath, pid, filename] = match;
|
||||||
if (!problemAdditionalFile[`P${pdoc.id}`]) problemAdditionalFile[`P${pdoc.id}`] = [{ fromPid: pid, filename }];
|
if (!problemAdditionalFile[`P${pdoc.id}`]) problemAdditionalFile[`P${pdoc.id}`] = [{ fromPid: pid, filename }];
|
||||||
else problemAdditionalFile[`P${pdoc.id}`].push({ fromPid: pid, filename });
|
else problemAdditionalFile[`P${pdoc.id}`].push({ fromPid: pid, filename });
|
||||||
content = content.replace(origialPath, `file://${filename}`);
|
content = content.replace(origialPath, `file://${filename}`);
|
||||||
@@ -508,8 +508,9 @@ export async function run({
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addScript('migrateSyzoj', 'migrate from syzoj')
|
export const apply = (ctx) => ctx.addScript(
|
||||||
.args(Schema.object({
|
'migrateSyzoj', 'migrate from syzoj',
|
||||||
|
Schema.object({
|
||||||
host: Schema.string().required(),
|
host: Schema.string().required(),
|
||||||
port: Schema.number().required(),
|
port: Schema.number().required(),
|
||||||
name: Schema.string().required(),
|
name: Schema.string().required(),
|
||||||
@@ -517,4 +518,6 @@ addScript('migrateSyzoj', 'migrate from syzoj')
|
|||||||
password: Schema.string().required(),
|
password: Schema.string().required(),
|
||||||
domainId: Schema.string().default('system'),
|
domainId: Schema.string().default('system'),
|
||||||
dataDir: Schema.string().default('/opt/syzoj/web/uploads'),
|
dataDir: Schema.string().default('/opt/syzoj/web/uploads'),
|
||||||
})).action(run);
|
}),
|
||||||
|
run,
|
||||||
|
);
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import mongodb, { Cursor, Db } from 'mongodb';
|
import mongodb, { Cursor, Db } from 'mongodb';
|
||||||
import {
|
import {
|
||||||
addScript, DiscussionTailReplyDoc, MessageDoc,
|
DiscussionTailReplyDoc, MessageDoc,
|
||||||
RecordDoc, Schema, TestCase, TrainingNode,
|
RecordDoc, Schema, TestCase, TrainingNode,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
|
|
||||||
@@ -507,11 +507,14 @@ export async function run({
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addScript('migrateVijos', 'migrate from vijos')
|
export const apply = (ctx) => ctx.addScript(
|
||||||
.args(Schema.object({
|
'migrateVijos', 'migrate from vijos',
|
||||||
|
Schema.object({
|
||||||
host: Schema.string().required(),
|
host: Schema.string().required(),
|
||||||
port: Schema.number().required(),
|
port: Schema.number().required(),
|
||||||
name: Schema.string().required(),
|
name: Schema.string().required(),
|
||||||
username: Schema.string().required(),
|
username: Schema.string().required(),
|
||||||
password: Schema.string().required(),
|
password: Schema.string().required(),
|
||||||
})).action(run);
|
}),
|
||||||
|
run,
|
||||||
|
);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { addScript, Schema } from 'hydrooj';
|
import { Schema } from 'hydrooj';
|
||||||
import DomainModel from 'hydrooj/src/model/domain';
|
import DomainModel from 'hydrooj/src/model/domain';
|
||||||
import { iterateAllProblem, iterateAllProblemInDomain } from 'hydrooj/src/pipelineUtils';
|
import { iterateAllProblem, iterateAllProblemInDomain } from 'hydrooj/src/pipelineUtils';
|
||||||
import sonic from './service';
|
import sonic from './service';
|
||||||
@@ -28,8 +28,10 @@ async function run({ domainId }, report) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addScript('ensureSonicSearch', 'Sonic problem search re-index')
|
export const apply = (ctx) => ctx.addScript(
|
||||||
.args(Schema.object({
|
'ensureSonicSearch', 'Sonic problem search re-index',
|
||||||
|
Schema.object({
|
||||||
domainId: Schema.string(),
|
domainId: Schema.string(),
|
||||||
}))
|
}),
|
||||||
.action(run);
|
run,
|
||||||
|
);
|
||||||
Reference in new issue
Block a user