core: schemastery validator

This commit is contained in:
undefined committed 2022-07-31 23:53:49 +08:00
1 parent 2d4d99c80c
commit 1f113e7a08
17 files changed
+145 -136

No files matched your search

+8 -3
View File
@@ -105,12 +105,17 @@ class SystemScriptHandler extends SystemHandler {
@param('args', Types.Content, true)
async post(domainId: string, id: string, raw = '{}') {
if (!global.Hydro.script[id]) throw new ValidationError('id');
const args = JSON.parse(raw);
validate(global.Hydro.script[id].validate, args);
let args = JSON.parse(raw);
if (typeof global.Hydro.script[id].validate === 'function') {
args = global.Hydro.script[id].validate(args);
} else {
logger.warn('You are using the legacy script validation API, which will be dropped in the future.');
validate(global.Hydro.script[id].validate, args);
}
const rid = await record.add(domainId, -1, this.user._id, '-', id, false, raw);
const report = (data) => judge.next({ domainId, rid, ...data });
report({ message: `Running script: ${id} `, status: STATUS.STATUS_JUDGING });
const start = new Date().getTime();
const start = Date.now();
// Maybe async?
global.Hydro.script[id].run(args, report)
.then((ret: any) => {
+17
View File
@@ -3,6 +3,7 @@
/* eslint-disable no-eval */
import './init';
import './interface';
import Schema from 'schemastery';
import path from 'path';
import fs from 'fs-extra';
import './utils';
@@ -10,7 +11,10 @@ import cac from 'cac';
import { Logger } from './logger';
import './ui';
// This is the main entry. So let's re-export some modules.
export * from './interface';
export { Schema, Logger };
const argv = cac().parse();
if (argv.options.debug) {
console.log('Debug mode enabled');
@@ -53,6 +57,19 @@ export function addon(addonPath: string, prepend = false) {
} else logger.error(`Addon not found: ${addonPath}`);
}
export function addScript(name: string, description: string) {
if (global.Hydro.script[name]) throw new Error(`duplicate script ${name} registered.`);
return {
args: <K extends Schema>(validate: K) => ({
action: (run: (args: ReturnType<K>, report: any) => boolean | Promise<boolean>) => {
global.Hydro.script[name] = {
description, validate, run,
};
},
}),
};
}
export async function load() {
addon(path.resolve(__dirname, '..'), true);
Error.stackTraceLimit = 50;
+28 -27
View File
@@ -1,13 +1,13 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import { ObjectID } from 'mongodb';
import Schema from 'schemastery';
import { addScript } from '../loader';
import blacklist from '../model/blacklist';
import * as discussion from '../model/discussion';
import * as document from '../model/document';
import user from '../model/user';
import UserModel from '../model/user';
import db from '../service/db';
export const description = 'Add blacklist by ip, uid';
async function _address(
ip: string,
bset: Set<string>, uset: Set<number>, dset: Set<ObjectID>,
@@ -47,7 +47,7 @@ async function _user(
) {
if (uset.has(uid)) return;
uset.add(uid);
const udoc = await user.getById('system', uid);
const udoc = await UserModel.getById('system', uid);
if (!udoc) return;
report({ message: `user ${udoc._id} ${udoc.uname}` });
await _address(udoc._loginip, bset, uset, dset, dryrun, report);
@@ -58,28 +58,29 @@ async function _user(
tasks.push(_discussion(ddoc.domainId, ddoc.docId, bset, uset, dset, dryrun, report));
}
await Promise.all(tasks);
if (!dryrun) await user.ban(uid);
if (!dryrun) await UserModel.ban(uid);
}
export async function run({
// eslint-disable-next-line @typescript-eslint/no-shadow
address = null, discuss = null, user = null, dryrun = true,
}, report) {
if (address) await _address(address, new Set(), new Set(), new Set(), dryrun, report);
if (discuss) {
await _discussion(
discuss.domainId, new ObjectID(discuss.did),
new Set(), new Set(), new Set(), dryrun, report,
);
}
if (user) await _user(user, new Set(), new Set(), new Set(), dryrun, report);
}
export const validate = {
address: 'string?',
discuss: 'string?',
user: 'number?',
dryrun: 'boolean?',
};
global.Hydro.script.blacklist = { run, description, validate };
addScript('blacklist', 'Add blacklist by ip, uid')
.args(Schema.object({
address: Schema.string(),
discuss: Schema.object({
domainId: Schema.string(),
did: Schema.string(),
}),
user: Schema.number(),
dryrun: Schema.boolean(),
}))
.action(async ({
address = null, discuss = null, user = null, dryrun = true,
}, report) => {
if (address) await _address(address, new Set(), new Set(), new Set(), dryrun, report);
if (discuss) {
await _discussion(
discuss.domainId, new ObjectID(discuss.did),
new Set(), new Set(), new Set(), dryrun, report,
);
}
if (user) await _user(user, new Set(), new Set(), new Set(), dryrun, report);
return true;
});
+3 -5
View File
@@ -1,11 +1,11 @@
/* eslint-disable no-await-in-loop */
import Schema from 'schemastery';
import * as semver from 'semver';
import superagent from 'superagent';
import { addScript } from '../loader';
import MessageModel from '../model/message';
import * as system from '../model/system';
export const description = 'Daily update check';
async function getRemoteVersion(id: string) {
try {
const res = await superagent.get(`https://registry.yarnpkg.com/${id}`);
@@ -38,6 +38,4 @@ export async function run(_: {}, report: Function) {
return true;
}
export const validate = {};
global.Hydro.script.checkUpdate = { run, description, validate };
addScript('checkUpdate', 'Daily update check').args(Schema.any()).action(run);
+14 -16
View File
@@ -1,25 +1,23 @@
import Schema from 'schemastery';
import { addScript } from '../loader';
import { PRIV } from '../model/builtin';
import user from '../model/user';
import db from '../service/db';
export const description = 'Delete a user';
const collDocument = db.collection('document');
const collDomainUser = db.collection('domain.user');
const collRecord = db.collection('record');
const collMessage = db.collection('message');
export async function run({ uid }) {
await collDocument.deleteMany({ owner: uid });
await collRecord.deleteMany({ uid });
await collDomainUser.deleteMany({ uid });
await collMessage.deleteMany({ $or: [{ from: uid }, { to: uid }] });
await user.setPriv(uid, PRIV.PRIV_NONE);
return uid;
}
export const validate = {
uid: 'number',
};
global.Hydro.script.deleteUser = { run, description, validate };
addScript('deleteUser', 'Delete a user')
.args(Schema.object({
uid: Schema.number(),
}))
.action(async ({ uid }) => {
await collDocument.deleteMany({ owner: uid });
await collRecord.deleteMany({ uid });
await collDomainUser.deleteMany({ uid });
await collMessage.deleteMany({ $or: [{ from: uid }, { to: uid }] });
await user.setPriv(uid, PRIV.PRIV_NONE);
return true;
});
+14 -16
View File
@@ -1,12 +1,12 @@
/* eslint-disable no-constant-condition */
/* eslint-disable no-await-in-loop */
import { ObjectID } from 'mongodb';
import Schema from 'schemastery';
import { addScript } from '../loader';
import { STATUS } from '../model/builtin';
import * as document from '../model/document';
import db from '../service/db';
export const description = 'Recalculates nSubmit and nAccept in problem status.';
const sumStatus = (status) => ({ $sum: { $cond: [{ $eq: ['$status', status] }, 1, 0] } });
const $match = { contest: { $ne: new ObjectID('000000000000000000000000') } };
@@ -144,17 +144,15 @@ export async function pdoc(report) {
if (bulk.length) await bulk.execute();
}
export async function run(arg, report) {
if (arg.pdoc === undefined || arg.pdoc) await pdoc(report);
if (arg.udoc === undefined || arg.udoc) await udoc(report);
if (arg.psdoc === undefined || arg.psdoc) await psdoc(report);
return true;
}
export const validate = {
udoc: 'boolean?',
pdoc: 'boolean?',
psdoc: 'boolean?',
};
global.Hydro.script.problemStat = { run, description, validate };
addScript('problemStat', 'Recalculates nSubmit and nAccept in problem status.')
.args(Schema.object({
udoc: Schema.boolean(),
pdoc: Schema.boolean(),
psdoc: Schema.boolean(),
}))
.action(async (arg, report) => {
if (arg.pdoc === undefined || arg.pdoc) await pdoc(report);
if (arg.udoc === undefined || arg.udoc) await udoc(report);
if (arg.psdoc === undefined || arg.psdoc) await psdoc(report);
return true;
});
+7 -5
View File
@@ -2,9 +2,11 @@
/* eslint-disable no-await-in-loop */
import { NumericDictionary, unionWith } from 'lodash';
import { FilterQuery } from 'mongodb';
import Schema from 'schemastery';
import { Tdoc, Udoc } from '../interface';
import difficultyAlgorithm from '../lib/difficulty';
import rating from '../lib/rating';
import { addScript } from '../loader';
import { PRIV, STATUS } from '../model/builtin';
import * as contest from '../model/contest';
import domain from '../model/domain';
@@ -182,8 +184,8 @@ export async function run({ domainId }, report: Function) {
return true;
}
export const validate = {
domainId: 'string?',
};
global.Hydro.script.rp = { run, description, validate };
addScript('rp', 'Calculate rp of a domain, or all domains')
.args(Schema.object({
domainId: Schema.string(),
}))
.action(run);
+7 -6
View File
@@ -1,10 +1,10 @@
/* eslint-disable no-await-in-loop */
import Schema from 'schemastery';
import { addScript } from '../loader';
import { STATUS } from '../model/builtin';
import storage from '../model/storage';
export const description = 'Calculate storage usage';
export async function run(_: {}, report: Function) {
export async function run(_, report) {
const cursor = storage.coll.find({ path: { $regex: /^problem\//i } }).sort({ path: 1 });
const count = await cursor.count();
report({ message: `Total ${count} files` });
@@ -34,8 +34,9 @@ export async function run(_: {}, report: Function) {
}
memory += doc.size || 0;
}
return true;
}
export const validate = {};
global.Hydro.script.storageUsage = { run, description, validate };
addScript('storageUsage', 'Calculate storage usage')
.args(Schema.any())
.action(run);