core: use common plugin-api interface

This commit is contained in:
undefined committed 2022-09-30 19:02:40 +08:00
1 parent c61c875bee
commit 70a96aecac
54 files changed
+360 -539

No files matched your search

+1 -1
View File
@@ -9,7 +9,6 @@ const compilerOptionsBase = {
module: 'commonjs',
esModuleInterop: true,
moduleResolution: 'node',
// declaration: true,
sourceMap: false,
composite: true,
strictBindCallApply: true,
@@ -48,6 +47,7 @@ const configFlat = (name) => (name === 'packages/ui-default' ? {
allowSyntheticDefaultImports: true,
target: 'es2020',
baseUrl: '.',
outDir: path.join(baseOutDir, name),
moduleResolution: 'node',
types,
paths: {
@@ -1,9 +1,8 @@
import assert from 'assert';
import crypto from 'crypto';
import yaml from 'js-yaml';
import * as bus from 'hydrooj/src/service/bus';
import db from 'hydrooj/src/service/db';
import { Handler, post, Types } from 'hydrooj/src/service/server';
import {
Context, db, Handler, post, Types, yaml,
} from 'hydrooj';
function decrypt(encrypted: string) {
if (!encrypted) throw new Error();
@@ -13,7 +12,7 @@ function decrypt(encrypted: string) {
return decrypted;
}
declare module 'hydrooj/src/interface' {
declare module 'hydrooj' {
interface Collections {
dataReport: any;
}
@@ -61,6 +60,6 @@ class DataReportHandler extends Handler {
}
}
export function apply(ctx) {
export function apply(ctx: Context) {
ctx.Route('data_report', '/center/report', DataReportHandler);
}
-7
View File
@@ -6,13 +6,6 @@
"author": "undefined <i@undefined.moe>",
"license": "AGPL-3.0-or-later",
"preferUnplugged": true,
"dependencies": {
"js-yaml": "^4.1.0",
"superagent": "^8.0.0"
},
"devDependencies": {
"@types/js-yaml": "^4.0.5"
},
"peerDependencies": {
"hydrooj": "*"
}
@@ -1,13 +1,11 @@
import { Client } from '@elastic/elasticsearch';
import { omit } from 'lodash';
import { Schema } from 'hydrooj';
import DomainModel from 'hydrooj/src/model/domain';
import ProblemModel, { ProblemDoc } from 'hydrooj/src/model/problem';
import * as system from 'hydrooj/src/model/system';
import { iterateAllProblem, iterateAllProblemInDomain } from 'hydrooj/src/pipelineUtils';
import * as bus from 'hydrooj/src/service/bus';
import {
Context, DomainModel, iterateAllProblem, iterateAllProblemInDomain,
ProblemDoc, ProblemModel, Schema, SystemModel,
} from 'hydrooj';
const client = new Client({ node: system.get('elastic-search.url') || 'http://127.0.0.1:9200' });
const client = new Client({ node: SystemModel.get('elastic-search.url') || 'http://127.0.0.1:9200' });
const indexOmit = ['_id', 'docType', 'data', 'additional_file', 'config', 'stats', 'assign'];
const processDocument = (doc: Partial<ProblemDoc>) => {
@@ -25,8 +23,8 @@ const processDocument = (doc: Partial<ProblemDoc>) => {
};
global.Hydro.lib.problemSearch = async (domainId, q, opts) => {
const allowedSize = system.get('elasic-search.indexSize') || 10000;
const size = opts?.limit || system.get('pagination.problem');
const allowedSize = SystemModel.get('elasic-search.indexSize') || 10000;
const size = opts?.limit || SystemModel.get('pagination.problem');
const from = Math.min(allowedSize - size, opts?.skip || 0);
const union = await DomainModel.getUnion(domainId);
const domainIds = [domainId, ...(union?.union || [])];
@@ -77,22 +75,22 @@ async function run({ domainId }, report) {
return true;
}
export const apply = (ctx) => {
bus.on('problem/add', async (doc, docId) => {
export const apply = (ctx: Context) => {
ctx.on('problem/add', async (doc, docId) => {
await client.index({
index: 'problem',
id: `${doc.domainId}/${docId}`,
document: processDocument(doc),
});
});
bus.on('problem/edit', async (pdoc) => {
ctx.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) => {
ctx.on('problem/del', async (domainId, docId) => {
await client.delete({
index: 'problem',
id: `${domainId}/${docId}`,
@@ -1,16 +1,11 @@
/* eslint-disable no-await-in-loop */
import AdmZip from 'adm-zip';
import decodeHTML from 'decode-html';
import fs from 'fs-extra';
import { filter } from 'lodash';
import xml2js from 'xml2js';
import type { ContentNode, PluginContext, ProblemConfigFile } from 'hydrooj';
import { FileTooLargeError, ValidationError } from 'hydrooj/src/error';
import { buildContent } from 'hydrooj/src/lib/content';
import { PERM } from 'hydrooj/src/model/builtin';
import problem from 'hydrooj/src/model/problem';
import solution from 'hydrooj/src/model/solution';
import { Handler } from 'hydrooj/src/service/server';
import {
AdmZip, buildContent, ContentNode, Context, FileTooLargeError, fs,
Handler, PERM, ProblemConfigFile, ProblemModel, SolutionModel, ValidationError,
} from 'hydrooj';
class FpsProblemImportHandler extends Handler {
async get() {
@@ -65,8 +60,8 @@ class FpsProblemImportHandler extends Handler {
};
const title = decodeHTML(p.title.join(' '));
const tags = filter(p.source, (i: string) => i.trim());
const pid = await problem.add(domainId, null, title, buildContent(content, 'html'), this.user._id, tags);
await problem.addTestdata(domainId, pid, 'config.yaml', Buffer.from(`time: ${config.time}\nmemory: ${config.memory}`));
const pid = await ProblemModel.add(domainId, null, title, buildContent(content, 'html'), this.user._id, tags);
await ProblemModel.addTestdata(domainId, pid, 'config.yaml', Buffer.from(`time: ${config.time}\nmemory: ${config.memory}`));
if (p.test_output) {
for (let i = 0; i < p.test_input.length; i++) {
const input = typeof p.test_input[i]?._ === 'string'
@@ -79,22 +74,22 @@ class FpsProblemImportHandler extends Handler {
: typeof p.test_output[i] === 'string'
? p.test_output[i]
: '';
await problem.addTestdata(domainId, pid, `${i + 1}.in`, Buffer.from(input));
await problem.addTestdata(domainId, pid, `${i + 1}.out`, Buffer.from(output));
await ProblemModel.addTestdata(domainId, pid, `${i + 1}.in`, Buffer.from(input));
await ProblemModel.addTestdata(domainId, pid, `${i + 1}.out`, Buffer.from(output));
}
} else if (p.test_input) {
for (let i = 0; i < p.test_input.length / 2; i++) {
await problem.addTestdata(domainId, pid, `${i + 1}.in`, Buffer.from(p.test_input[2 * i]));
await problem.addTestdata(domainId, pid, `${i + 1}.out`, Buffer.from(p.test_input[2 * i + 1]));
await ProblemModel.addTestdata(domainId, pid, `${i + 1}.in`, Buffer.from(p.test_input[2 * i]));
await ProblemModel.addTestdata(domainId, pid, `${i + 1}.out`, Buffer.from(p.test_input[2 * i + 1]));
}
}
await problem.edit(domainId, pid, { html: true });
await ProblemModel.edit(domainId, pid, { html: true });
if (p.solution) {
let s = '';
for (const sol of p.solution) {
s += `**${sol.$.language}** : \n\`\`\`\n${sol._}\n\`\`\`\n`;
}
await solution.add(domainId, pid, this.user._id, s);
await SolutionModel.add(domainId, pid, this.user._id, s);
}
}
}
@@ -114,8 +109,8 @@ class FpsProblemImportHandler extends Handler {
let zip: AdmZip;
try {
zip = new AdmZip(this.request.files.file.filepath);
} catch (e) {
throw new ValidationError('zip', null, e.message);
} catch (err) {
throw new ValidationError('zip', null, err.message);
}
for (const entry of zip.getEntries()) {
try {
@@ -134,7 +129,7 @@ class FpsProblemImportHandler extends Handler {
}
export const sideEffect = true;
export async function apply(ctx: PluginContext) {
export async function apply(ctx: Context) {
ctx.Route('problem_import_fps', '/problem/import/fps', FpsProblemImportHandler, PERM.PERM_CREATE_PROBLEM);
ctx.inject('ProblemAdd', 'problem_import_fps', { icon: 'copy', text: 'From FPS File' });
}
+1 -3
View File
@@ -2,7 +2,7 @@
"name": "@hydrooj/fps-importer",
"version": "1.3.4",
"description": "Import FPS problems",
"main": "package.json",
"main": "index.ts",
"repository": "https://github.com/hydro-dev/Hydro.git",
"author": "undefined <i@undefined.moe>",
"license": "SEE LICENSE IN LICENSE",
@@ -10,8 +10,6 @@
"dependencies": {
"adm-zip": "0.5.5",
"decode-html": "^2.0.0",
"fs-extra": "^10.1.0",
"mongodb": "^3.7.3",
"xml2js": "^0.4.23"
},
"devDependencies": {
+11 -13
View File
@@ -3,12 +3,10 @@
import path from 'path';
import fs from 'fs-extra';
import type { JudgeResultBody } from 'hydrooj';
import {
Logger, SettingModel, StorageModel, SystemModel, TaskModel,
} from 'hydrooj';
import { end, next } from 'hydrooj/src/handler/judge';
import { Logger } from 'hydrooj/src/logger';
import * as setting from 'hydrooj/src/model/setting';
import storage from 'hydrooj/src/model/storage';
import * as system from 'hydrooj/src/model/system';
import task from 'hydrooj/src/model/task';
import { processTestdata } from '../cases';
import { getConfig } from '../config';
import { FormatError, SystemError } from '../error';
@@ -18,11 +16,11 @@ import { JudgeTask } from '../task';
const logger = new Logger('judge');
const session = {
config: { detail: system.get('hydrojudge.detail') },
config: { detail: SystemModel.get('hydrojudge.detail') },
async fetchFile(name: string) {
name = name.split('#')[0];
const target = path.join(getConfig('tmp_dir'), name.replace(/\//g, '_'));
await storage.get(`submission/${name}`, target);
await StorageModel.get(`submission/${name}`, target);
return target;
},
getNext(t: Context) {
@@ -42,8 +40,8 @@ const session = {
};
},
getLang(lang: string, doThrow = true) {
if (setting.langs[lang]) return setting.langs[lang];
if (lang === 'cpp' && setting.langs['cc']) return setting.langs['cc'];
if (SettingModel.langs[lang]) return SettingModel.langs[lang];
if (lang === 'cpp' && SettingModel.langs['cc']) return SettingModel.langs['cc'];
if (doThrow) throw new SystemError('Unsupported language {0}.', [lang]);
return null;
},
@@ -61,7 +59,7 @@ const session = {
filenames.add(file.name);
version[file.name] = file.etag + file.lastModified;
if (etags[file.name] !== file.etag + file.lastModified) {
await storage.get(`problem/${source}/testdata/${file.name}`, path.join(filePath, file.name));
await StorageModel.get(`problem/${source}/testdata/${file.name}`, path.join(filePath, file.name));
}
}
for (const name in etags) {
@@ -77,8 +75,8 @@ const session = {
};
export async function postInit() {
if (system.get('hydrojudge.disable')) return;
if (SystemModel.get('hydrojudge.disable')) return;
const handle = (t) => (new JudgeTask(session, t)).handle().catch(logger.error);
task.consume({ type: 'judge' }, handle);
task.consume({ type: 'judge', priority: { $gt: -50 } }, handle);
TaskModel.consume({ type: 'judge' }, handle);
TaskModel.consume({ type: 'judge', priority: { $gt: -50 } }, handle);
}
+5 -3
View File
@@ -1,6 +1,6 @@
import * as bus from 'hydrooj/src/service/bus';
import { Context } from 'hydrooj';
declare module 'hydrooj/src/interface' {
declare module 'hydrooj' {
interface SystemKeys {
'hydrojudge.cache_dir': string;
'hydrojudge.tmp_dir': string;
@@ -15,6 +15,8 @@ declare module 'hydrooj/src/interface' {
}
}
export function apply(ctx: Context) {
if (process.env.NODE_APP_INSTANCE === '0') {
bus.once('app/started', () => require('./hosts/builtin').postInit());
ctx.once('app/started', () => require('./hosts/builtin').postInit());
}
}
+2 -2
View File
@@ -2,8 +2,8 @@
"name": "hydrooj",
"version": "3.17.12",
"bin": "bin/hydrooj.js",
"main": "src/loader",
"module": "src/loader",
"main": "src/plugin-api",
"module": "src/plugin-api",
"repository": "https://github.com/hydro-dev/Hydro.git",
"author": "undefined <i@undefined.moe>",
"license": "AGPL-3.0-or-later",
+2 -1
View File
@@ -1,7 +1,8 @@
import * as cordis from 'cordis';
import Schema from 'schemastery';
import { ModuleInterfaces } from './interface';
import { inject } from './lib/ui';
import { Loader, ModuleInterfaces } from './loader';
import { Loader } from './loader';
import { EventMap } from './service/bus';
export interface Events<C extends Context = Context> extends cordis.Events<C>, EventMap {
+1 -1
View File
@@ -101,7 +101,7 @@ export async function load(ctx: Context) {
model(pending, fail, ctx),
setting(pending, fail, require('../model/setting')),
]);
await require('hydrooj/src/service/server').apply(ctx);
await require('../service/server').apply(ctx);
await addon(pending, fail, ctx);
const scriptDir = path.resolve(__dirname, '..', 'script');
for (const h of await fs.readdir(scriptDir)) {
+1 -1
View File
@@ -38,7 +38,7 @@ export async function apply(ctx: Context) {
await modelSystem.runConfig();
const storage = require('../service/storage');
await storage.loadStorageService();
await require('hydrooj/src/service/server').apply(ctx);
await require('../service/server').apply(ctx);
// Make sure everything is ready and then start main entry
if (argv.options.watch) await ctx.loader.reloadPlugin(ctx, 'hydrooj/src/service/watcher', {});
await ctx.root.start();
+2 -2
View File
@@ -4,8 +4,8 @@ import path from 'path';
import AdmZip from 'adm-zip';
import fs from 'fs-extra';
import yaml from 'js-yaml';
import { Context } from '../context';
import { ValidationError } from '../error';
import { PluginContext } from '../loader';
import { PERM, PRIV } from '../model/builtin';
import problem, { ProblemDoc } from '../model/problem';
import { Handler } from '../service/server';
@@ -98,7 +98,7 @@ class ProblemImportHydroHandler extends Handler {
}
}
export async function apply(ctx: PluginContext) {
export async function apply(ctx: Context) {
ctx.Route('problem_import_hydro', '/problem/import/hydro', ProblemImportHydroHandler, PERM.PERM_CREATE_PROBLEM);
ctx.inject('ProblemAdd', 'problem_import_hydro', { icon: 'copy', text: 'Import From Hydro' });
}
+5 -6
View File
@@ -1,7 +1,7 @@
import type fs from 'fs';
import type { Dictionary, NumericDictionary } from 'lodash';
import type { Cursor, ObjectID } from 'mongodb';
import { Context } from './context';
import type { Context } from './context';
import type { ProblemDoc } from './model/problem';
import type { Handler } from './service/server';
@@ -637,7 +637,7 @@ export interface Model {
blog: typeof import('./model/blog'),
builtin: typeof import('./model/builtin'),
contest: typeof import('./model/contest'),
discussion: typeof import('./model/discussion'),
DiscussionModel: typeof import('./model/discussion'),
document: typeof import('./model/document'),
domain: typeof import('./model/domain').default,
message: typeof import('./model/message').default,
@@ -657,7 +657,7 @@ export interface Model {
rp: typeof import('./script/rating').RpTypes,
}
export interface Service {
export interface HydroService {
/** @deprecated */
bus: Context,
db: typeof import('./service/db'),
@@ -683,8 +683,6 @@ export interface ProblemSearchOptions {
export type ProblemSearch = (domainId: string, q: string, options?: ProblemSearchOptions) => Promise<ProblemSearchResponse>;
export { Context as PluginContext } from './context';
export interface Lib extends Record<string, any> {
difficulty: typeof import('./lib/difficulty'),
buildContent: typeof import('./lib/content').buildContent,
@@ -725,7 +723,7 @@ export interface HydroGlobal {
/** @deprecated */
handler: Record<string, Function>;
script: Record<string, Script>;
service: Service;
service: HydroService;
lib: Lib;
module: { [K in keyof ModuleInterfaces]: Record<string, ModuleInterfaces[K]> };
ui: UI;
@@ -742,6 +740,7 @@ declare global {
addons: string[],
}
}
var bus: any; // eslint-disable-line
var app: Context; // eslint-disable-line
var Hydro: HydroGlobal; // eslint-disable-line
var addons: string[]; // eslint-disable-line
-3
View File
@@ -16,9 +16,6 @@ import { Logger } from './logger';
import { Context } from './context';
// eslint-disable-next-line import/no-duplicates
import { unwrapExports } from './utils';
// This is the main entry. So let's re-export some modules.
export * from './interface';
export { Schema, Logger };
const argv = cac().parse();
const logger = new Logger('loader');
+1 -1
View File
@@ -302,7 +302,7 @@ bus.on('app/ready', async () => {
}
});
global.Hydro.model.discussion = {
global.Hydro.model.DiscussionModel = {
typeDisplay,
PROJECTION_LIST,
PROJECTION_PUBLIC,
+48
View File
@@ -0,0 +1,48 @@
import AdmZip from 'adm-zip';
import fs from 'fs-extra';
import yaml from 'js-yaml';
import Schema from 'schemastery';
import superagent from 'superagent';
import * as db from './service/db';
export { ObjectID, ObjectId } from 'mongodb';
export * from './utils';
export * from './interface';
export * from './pipelineUtils';
export * from './error';
export * from './settings';
export * as SystemModel from './model/system';
export * as TrainingModel from './model/training';
export * as OpcountModel from './model/opcount';
export * as OplogModel from './model/oplog';
export * as BlogModel from './model/blog';
export * as SettingModel from './model/setting';
export * as DiscussionModel from './model/discussion';
export * as DocumentModel from './model/document';
export * as BuiltinModel from './model/builtin';
export * as ContestModel from './model/contest';
export { default as TokenModel } from './model/token';
export { default as UserModel } from './model/user';
export { default as ProblemModel } from './model/problem';
export { default as RecordModel } from './model/record';
export { default as SolutionModel } from './model/solution';
export { default as MessageModel } from './model/message';
export { default as OauthModel } from './model/oauth';
export { default as BlackListModel } from './model/blacklist';
export { default as DomainModel } from './model/domain';
export { default as StorageModel } from './model/storage';
export { default as TaskModel } from './model/task';
export * from './model/builtin';
export * as JudgeHandler from './handler/judge';
export { postJudge } from './handler/judge';
export { Service, Context } from './context';
export { buildContent } from './lib/content';
export * as validator from './lib/validator';
export { default as rank } from './lib/rank';
export { default as paginate } from './lib/paginate';
export * from './service/decorators';
export { Handler, ConnectionHandler } from './service/server';
export { UiContextBase } from './service/layers/base';
export { EventMap } from './service/bus';
export {
db, Schema, yaml, fs, AdmZip, superagent,
};
+3 -3
View File
@@ -1,8 +1,8 @@
import { PassThrough } from 'stream';
import { cloneDeep } from 'lodash';
import * as system from 'hydrooj/src/model/system';
import token from 'hydrooj/src/model/token';
import type { HydroRequest, HydroResponse } from 'hydrooj/src/service/server';
import * as system from '../../model/system';
import token from '../../model/token';
import type { HydroRequest, HydroResponse } from '../server';
export interface UiContextBase {
cdn_prefix: string;
@@ -1,7 +1,7 @@
import BlackListModel from 'hydrooj/src/model/blacklist';
import DomainModel from 'hydrooj/src/model/domain';
import * as system from 'hydrooj/src/model/system';
import token from 'hydrooj/src/model/token';
import BlackListModel from '../../model/blacklist';
import DomainModel from '../../model/domain';
import * as system from '../../model/system';
import token from '../../model/token';
export default async (ctx, next) => {
const forceDomain = /^\/d\/([^/]+)\//.exec(ctx.request.path);
@@ -1,8 +1,8 @@
import { ObjectID } from 'mongodb';
import avatar from 'hydrooj/src/lib/avatar';
import { PERM } from 'hydrooj/src/model/builtin';
import * as system from 'hydrooj/src/model/system';
import { User } from 'hydrooj/src/model/user';
import avatar from '../../lib/avatar';
import { PERM } from '../../model/builtin';
import * as system from '../../model/system';
import { User } from '../../model/user';
import type { KoaContext } from '../server';
const serializer = (showDisplayName = false) => (k: string, v: any) => {
@@ -1,7 +1,7 @@
import { errorMessage } from '@hydrooj/utils/lib/utils';
import { SystemError, UserFacingError } from 'hydrooj/src/error';
import { PERM } from 'hydrooj/src/model/builtin';
import { User } from 'hydrooj/src/model/user';
import { SystemError, UserFacingError } from '../../error';
import { PERM } from '../../model/builtin';
import { User } from '../../model/user';
import type { KoaContext } from '../server';
const serializer = (showDisplayName = false) => (k: string, v: any) => {
+4 -4
View File
@@ -1,7 +1,7 @@
import { CsrfTokenError, NotFoundError } from 'hydrooj/src/error';
import avatar from 'hydrooj/src/lib/avatar';
import { PERM } from 'hydrooj/src/model/builtin';
import UserModel from 'hydrooj/src/model/user';
import { CsrfTokenError, NotFoundError } from '../../error';
import avatar from '../../lib/avatar';
import { PERM } from '../../model/builtin';
import UserModel from '../../model/user';
import type { KoaContext } from '../server';
export default async (ctx: KoaContext, next) => {
@@ -1,15 +1,10 @@
/* eslint-disable no-await-in-loop */
import os from 'os';
import path from 'path';
import AdmZip from 'adm-zip';
import fs from 'fs-extra';
import yaml from 'js-yaml';
import type { ContentNode, PluginContext, ProblemConfigFile } from 'hydrooj';
import { ValidationError } from 'hydrooj/src/error';
import { buildContent } from 'hydrooj/src/lib/content';
import { PERM } from 'hydrooj/src/model/builtin';
import problem from 'hydrooj/src/model/problem';
import { Handler } from 'hydrooj/src/service/server';
import {
AdmZip, buildContent, ContentNode, Context, fs, Handler, PERM,
ProblemConfigFile, ProblemModel, ValidationError, yaml,
} from 'hydrooj';
fs.ensureDirSync('/tmp/hydro/import-qduoj');
@@ -79,9 +74,9 @@ class ImportQduojHandler extends Handler {
});
}
if (+pdoc.display_id) pdoc.display_id = `P${pdoc.display_id}`;
const n = await problem.get(domainId, pdoc.display_id);
const n = await ProblemModel.get(domainId, pdoc.display_id);
if (n) pdoc.display_id = null;
const pid = await problem.add(domainId, pdoc.display_id, pdoc.title, buildContent(content, 'html'), this.user._id, pdoc.tags);
const pid = await ProblemModel.add(domainId, pdoc.display_id, pdoc.title, buildContent(content, 'html'), this.user._id, pdoc.tags);
const config: ProblemConfigFile = {
time: `${pdoc.time_limit}ms`,
memory: `${pdoc.memory_limit}m`,
@@ -89,11 +84,11 @@ class ImportQduojHandler extends Handler {
};
for (const tc of pdoc.test_case_score) {
await Promise.all([
problem.addTestdata(
ProblemModel.addTestdata(
domainId, pid, tc.input_name,
path.join(tmp, folder, 'testcase', tc.input_name),
),
problem.addTestdata(
ProblemModel.addTestdata(
domainId, pid, tc.output_name,
path.join(tmp, folder, 'testcase', tc.output_name),
),
@@ -107,8 +102,8 @@ class ImportQduojHandler extends Handler {
});
}
await Promise.all([
problem.addTestdata(domainId, pid, 'config.yaml', Buffer.from(yaml.dump(config))),
problem.edit(domainId, pid, { html: true }),
ProblemModel.addTestdata(domainId, pid, 'config.yaml', Buffer.from(yaml.dump(config))),
ProblemModel.edit(domainId, pid, { html: true }),
]);
}
} finally {
@@ -130,7 +125,7 @@ class ImportQduojHandler extends Handler {
}
}
export async function apply(ctx: PluginContext) {
export async function apply(ctx: Context) {
ctx.Route('problem_import_qduoj', '/problem/import/qduoj', ImportQduojHandler, PERM.PERM_CREATE_PROBLEM);
ctx.inject('ProblemAdd', 'problem_import_qduoj', { icon: 'copy', text: 'From QDUOJ Export' });
}
+1 -3
View File
@@ -8,8 +8,6 @@
"license": "AGPL-3.0-or-later",
"preferUnplugged": true,
"dependencies": {
"adm-zip": "0.5.5",
"decode-html": "^2.0.0",
"fs-extra": "^10.1.0"
"decode-html": "^2.0.0"
}
}
+10 -11
View File
@@ -1,8 +1,7 @@
import superagent from 'superagent';
import { PluginContext } from 'hydrooj';
import { ForbiddenError, UserFacingError, ValidationError } from 'hydrooj/src/error';
import * as system from 'hydrooj/src/model/system';
import token from 'hydrooj/src/model/token';
import {
Context, ForbiddenError, superagent, SystemModel,
TokenModel, UserFacingError, ValidationError,
} from 'hydrooj';
declare module 'hydrooj' {
interface SystemKeys {
@@ -14,21 +13,21 @@ declare module 'hydrooj' {
async function get() {
const [appid, [state]] = await Promise.all([
system.get('login-with-github.id'),
token.add(token.TYPE_OAUTH, 600, { redirect: this.request.referer }),
SystemModel.get('login-with-github.id'),
TokenModel.add(TokenModel.TYPE_OAUTH, 600, { redirect: this.request.referer }),
]);
this.response.redirect = `https://github.com/login/oauth/authorize?client_id=${appid}&state=${state}&scope=read:user,user:email`;
}
async function callback({ state, code }) {
const [[appid, secret, endpoint, url], s] = await Promise.all([
system.getMany([
SystemModel.getMany([
'login-with-github.id',
'login-with-github.secret',
'login-with-github.endpoint',
'server.url',
]),
token.get(state, token.TYPE_OAUTH),
TokenModel.get(state, TokenModel.TYPE_OAUTH),
]);
if (!s) throw new ValidationError('token');
const res = await superagent.post(`${endpoint || 'https://github.com'}/login/oauth/access_token`)
@@ -66,12 +65,12 @@ async function callback({ state, code }) {
ret.email = emailInfo.body.find((e) => e.primary && e.verified).email;
}
}
await token.del(s._id, token.TYPE_OAUTH);
await TokenModel.del(s._id, TokenModel.TYPE_OAUTH);
if (!ret.email) throw new ForbiddenError("You don't have a verified email.");
return ret;
}
export function apply(ctx: PluginContext) {
export function apply(ctx: Context) {
ctx.provideModule('oauth', 'github', {
text: 'Login with Github',
callback,
-6
View File
@@ -6,13 +6,7 @@
"author": "undefined <i@undefined.moe>",
"license": "AGPL-3.0-or-later",
"preferUnplugged": true,
"devDependencies": {
"@types/superagent": "^4.1.15"
},
"peerDependencies": {
"hydrooj": "*"
},
"dependencies": {
"superagent": "^8.0.0"
}
}
@@ -1,5 +1,6 @@
import * as superagent from 'superagent';
import { PluginContext } from 'hydrooj';
import {
Context, superagent, SystemModel, TokenModel, UserFacingError,
} from 'hydrooj';
declare module 'hydrooj' {
interface SystemKeys {
@@ -9,11 +10,10 @@ declare module 'hydrooj' {
}
async function get() {
const { system, token } = global.Hydro.model;
const [appid, url, [state]] = await Promise.all([
system.get('login-with-google.id'),
system.get('server.url'),
token.add(token.TYPE_OAUTH, 600, { redirect: this.request.referer }),
SystemModel.get('login-with-google.id'),
SystemModel.get('server.url'),
TokenModel.add(TokenModel.TYPE_OAUTH, 600, { redirect: this.request.referer }),
]);
// eslint-disable-next-line max-len
this.response.redirect = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${appid}&response_type=code&redirect_uri=${url}oauth/google/callback&scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile&state=${state}`;
@@ -45,17 +45,12 @@ function decodeJWT(idToken: string) {
async function callback({
state, code, error,
}) {
const { system, token } = global.Hydro.model;
const { UserFacingError } = global.Hydro.error;
if (error) throw new UserFacingError(error);
const [
[appid, secret, url],
s,
] = await Promise.all([
system.getMany([
const [[appid, secret, url], s] = await Promise.all([
SystemModel.getMany([
'login-with-google.id', 'login-with-google.secret', 'server.url',
]),
token.get(state, token.TYPE_OAUTH),
TokenModel.get(state, TokenModel.TYPE_OAUTH),
]);
const res = await superagent.post('https://oauth2.googleapis.com/token')
.send({
@@ -66,7 +61,7 @@ async function callback({
redirect_uri: `${url}oauth/google/callback`,
});
const payload = decodeJWT(res.body.id_token).payload;
await token.del(state, token.TYPE_OAUTH);
await TokenModel.del(state, TokenModel.TYPE_OAUTH);
this.response.redirect = s.redirect;
return {
// TODO use openid
@@ -77,7 +72,7 @@ async function callback({
};
}
export function apply(ctx: PluginContext) {
export function apply(ctx: Context) {
ctx.provideModule('oauth', 'google', {
text: 'Login with Google',
callback,
-6
View File
@@ -6,13 +6,7 @@
"author": "undefined <i@undefined.moe>",
"license": "AGPL-3.0-or-later",
"preferUnplugged": true,
"devDependencies": {
"@types/superagent": "^4.1.15"
},
"peerDependencies": {
"hydrooj": "*"
},
"dependencies": {
"superagent": "^8.0.0"
}
}
+2 -3
View File
@@ -1,12 +1,11 @@
import { md5, sha1 } from '@hydrooj/utils/lib/utils';
import { PluginContext } from 'hydrooj';
import { Context, md5, sha1 } from 'hydrooj';
import { apply as hustoj } from './scripts/hustoj';
import { apply as syzoj } from './scripts/syzoj';
import { apply as vijos } from './scripts/vijos';
const RE_MD5 = /^[\da-f]{32}$/;
export function apply(ctx: PluginContext) {
export function apply(ctx: Context) {
hustoj(ctx);
vijos(ctx);
syzoj(ctx);
-2
View File
@@ -11,8 +11,6 @@
"@types/turndown": "^5.0.1"
},
"dependencies": {
"@hydrooj/utils": "workspace:*",
"js-yaml": "^4.1.0",
"mariadb": "^3.0.1",
"mongodb": "^3.7.3",
"mysql": "^2.18.1",
+22 -34
View File
@@ -1,23 +1,11 @@
/* eslint-disable no-tabs */
/* eslint-disable no-await-in-loop */
import fs from 'fs-extra';
import { ObjectID } from 'mongodb';
import mysql from 'mysql';
import TurndownService from 'turndown';
import { STATUS } from '@hydrooj/utils/lib/status';
import { noop, Time } from '@hydrooj/utils/lib/utils';
import { Schema } from 'hydrooj';
import { NotFoundError } from 'hydrooj/src/error';
import { postJudge } from 'hydrooj/src/handler/judge';
import { RecordDoc } from 'hydrooj/src/interface';
import { buildContent } from 'hydrooj/src/lib/content';
import * as contest from 'hydrooj/src/model/contest';
import domain from 'hydrooj/src/model/domain';
import problem from 'hydrooj/src/model/problem';
import record from 'hydrooj/src/model/record';
import SolutionModel from 'hydrooj/src/model/solution';
import * as system from 'hydrooj/src/model/system';
import user from 'hydrooj/src/model/user';
import {
buildContent, ContestModel, DomainModel, fs, noop, NotFoundError, ObjectID, postJudge, ProblemModel,
RecordDoc, RecordModel, Schema, SolutionModel, STATUS, SystemModel, Time, UserModel,
} from 'hydrooj';
const turndown = new TurndownService({
codeBlockStyle: 'fenced',
@@ -81,7 +69,7 @@ export async function run({
else res([val, fields]);
});
});
const target = await domain.get(domainId);
const target = await DomainModel.get(domainId);
if (!target) throw new NotFoundError(domainId);
report({ message: 'Connected to database' });
/*
@@ -101,22 +89,22 @@ export async function run({
*/
const uidMap: Record<string, number> = {};
const [udocs] = await query('SELECT * FROM `users`');
const precheck = await user.getMulti({ unameLower: { $in: udocs.map((u) => u.user_id.toLowerCase()) } }).toArray();
const precheck = await UserModel.getMulti({ unameLower: { $in: udocs.map((u) => u.user_id.toLowerCase()) } }).toArray();
if (precheck.length) throw new Error(`Conflict username: ${precheck.map((u) => u.unameLower).join(', ')}`);
for (const udoc of udocs) {
if (randomMail) delete udoc.email;
let current = await user.getByEmail(domainId, udoc.email || `${udoc.user_id}@hustoj.local`);
if (!current) current = await user.getByUname(domainId, udoc.user_id);
let current = await UserModel.getByEmail(domainId, udoc.email || `${udoc.user_id}@hustoj.local`);
if (!current) current = await UserModel.getByUname(domainId, udoc.user_id);
if (current) {
report({ message: `duplicate user with email ${udoc.email}: ${current.uname},${udoc.user_id}` });
uidMap[udoc.user_id] = current._id;
} else {
const uid = await user.create(
const uid = await UserModel.create(
udoc.email || `${udoc.user_id}@hustoj.local`, udoc.user_id, '',
null, udoc.ip, udoc.defunct === 'Y' ? 0 : system.get('default.priv'),
null, udoc.ip, udoc.defunct === 'Y' ? 0 : SystemModel.get('default.priv'),
);
uidMap[udoc.user_id] = uid;
await user.setById(uid, {
await UserModel.setById(uid, {
loginat: udoc.accesstime,
regat: udoc.reg_time,
hash: udoc.password,
@@ -124,7 +112,7 @@ export async function run({
school: udoc.school || '',
hashType: 'hust',
});
await domain.setUserInDomain(domainId, uid, {
await DomainModel.setUserInDomain(domainId, uid, {
displayName: udoc.nick || '',
school: udoc.school || '',
nSubmit: udoc.submit,
@@ -134,7 +122,7 @@ export async function run({
}
const [admins] = await query("SELECT * FROM `privilege` WHERE `rightstr` = 'administrator'");
for (const admin of admins) await domain.setUserRole(domainId, uidMap[admin.user_id], 'root');
for (const admin of admins) await DomainModel.setUserRole(domainId, uidMap[admin.user_id], 'root');
const adminUids = admins.map((admin) => uidMap[admin.user_id]);
report({ message: 'user finished' });
@@ -167,11 +155,11 @@ export async function run({
const [pdocs] = await query(`SELECT * FROM \`problem\` LIMIT ${pageId * step}, ${step}`);
for (const pdoc of pdocs) {
if (rerun) {
const opdoc = await problem.get(domainId, `P${pdoc.problem_id}`);
const opdoc = await ProblemModel.get(domainId, `P${pdoc.problem_id}`);
if (opdoc) pidMap[pdoc.problem_id] = opdoc.docId;
}
if (!pidMap[pdoc.problem_id]) {
const pid = await problem.add(
const pid = await ProblemModel.add(
domainId, `P${pdoc.problem_id}`,
pdoc.title, buildContent({
description: pdoc.description,
@@ -188,7 +176,7 @@ export async function run({
const [cdoc] = await query(`SELECT * FROM \`privilege\` WHERE \`rightstr\` = 'p${pdoc.problem_id}'`);
const maintainer = [];
for (let i = 1; i < cdoc.length; i++) maintainer.push(uidMap[cdoc[i].user_id]);
await problem.edit(domainId, pidMap[pdoc.problem_id], {
await ProblemModel.edit(domainId, pidMap[pdoc.problem_id], {
nAccept: 0,
nSubmit: pdoc.submit,
config: `time: ${pdoc.time_limit}s\nmemory: ${pdoc.memory_limit}m`,
@@ -221,7 +209,7 @@ export async function run({
for (const tdoc of tdocs) {
const [pdocs] = await query(`SELECT * FROM \`contest_problem\` WHERE \`contest_id\` = ${tdoc.contest_id}`);
const pids = pdocs.map((i) => pidMap[i.problem_id]).filter((i) => i);
const tid = await contest.add(
const tid = await ContestModel.add(
domainId, tdoc.title, tdoc.description || 'Description',
adminUids[0], contestType, tdoc.start_time, tdoc.end_time, pids, true,
{ _code: password },
@@ -282,9 +270,9 @@ export async function run({
if (source[0]?.source) data.code = source[0].source;
if (rdoc.contest_id) {
data.contest = new ObjectID(tidMap[rdoc.contest_id]);
await contest.attend(domainId, data.contest, uidMap[rdoc.user_id]).catch(noop);
await ContestModel.attend(domainId, data.contest, uidMap[rdoc.user_id]).catch(noop);
}
await record.coll.insertOne(data);
await RecordModel.coll.insertOne(data);
await postJudge(data).catch((err) => report({ message: err.message }));
}
}
@@ -298,15 +286,15 @@ export async function run({
for (const file of files) {
if (!file.isDirectory()) continue;
const datas = await fs.readdir(`${dataDir}/${file.name}`, { withFileTypes: true });
const pdoc = await problem.get(domainId, `P${file.name}`, undefined, true);
const pdoc = await ProblemModel.get(domainId, `P${file.name}`, undefined, true);
if (!pdoc) continue;
report({ message: `Syncing testdata for ${file.name}` });
for (const data of datas) {
if (data.isDirectory()) continue;
const filename = nameMap[data.name] || data;
await problem.addTestdata(domainId, pdoc.docId, filename, `${dataDir}/${file.name}/${data.name}`);
await ProblemModel.addTestdata(domainId, pdoc.docId, filename, `${dataDir}/${file.name}/${data.name}`);
}
await problem.addTestdata(domainId, pdoc.docId, 'config.yaml', Buffer.from(pdoc.config as string));
await ProblemModel.addTestdata(domainId, pdoc.docId, 'config.yaml', Buffer.from(pdoc.config as string));
}
return true;
}
+41 -54
View File
@@ -1,23 +1,10 @@
/* eslint-disable no-await-in-loop */
import fs from 'fs-extra';
import yaml from 'js-yaml';
import mariadb from 'mariadb';
import { ObjectID } from 'mongodb';
import { STATUS } from '@hydrooj/utils/lib/status';
import { noop, Time } from '@hydrooj/utils/lib/utils';
import { Schema } from 'hydrooj';
import { NotFoundError } from 'hydrooj/src/error';
import { postJudge } from 'hydrooj/src/handler/judge';
import { DiscussionDoc, DiscussionReplyDoc, RecordDoc } from 'hydrooj/src/interface';
import { buildContent } from 'hydrooj/src/lib/content';
import { PERM } from 'hydrooj/src/model/builtin';
import * as contest from 'hydrooj/src/model/contest';
import * as document from 'hydrooj/src/model/document';
import domain from 'hydrooj/src/model/domain';
import problem from 'hydrooj/src/model/problem';
import record from 'hydrooj/src/model/record';
import * as system from 'hydrooj/src/model/system';
import user from 'hydrooj/src/model/user';
import {
buildContent, ContestModel, DiscussionDoc, DiscussionReplyDoc, DocumentModel, DomainModel,
fs, noop, NotFoundError, ObjectID, PERM, postJudge, ProblemModel, RecordDoc, RecordModel,
Schema, STATUS, SystemModel, Time, UserModel, yaml,
} from 'hydrooj';
const contentTypeMap = {
noi: 'oi',
@@ -99,7 +86,7 @@ export async function run({
const query = (q: string) => new Promise<any[]>((res, rej) => {
src.query(q).then((r) => res(r)).catch((e) => rej(e));
});
const target = await domain.get(domainId);
const target = await DomainModel.get(domainId);
if (!target) throw new NotFoundError(domainId);
report({ message: 'Connected to database' });
/*
@@ -124,24 +111,24 @@ export async function run({
const superAdmin = [];
const udocs = await query('SELECT * FROM `user`');
report({ message: udocs.map((u) => u.username.toLowerCase()) });
const precheck = await user.getMulti({ unameLower: { $in: udocs.map((u) => u.username.toLowerCase()) } }).toArray();
const precheck = await UserModel.getMulti({ unameLower: { $in: udocs.map((u) => u.username.toLowerCase()) } }).toArray();
if (precheck.length) throw new Error(`Conflict username: ${precheck.map((u) => u.unameLower).join(', ')}`);
for (const udoc of udocs) {
if (randomMail) delete udoc.email;
let current = await user.getByEmail(domainId, udoc.email || `${udoc.username}@syzoj.local`);
if (!current) current = await user.getByUname(domainId, udoc.username);
let current = await UserModel.getByEmail(domainId, udoc.email || `${udoc.username}@syzoj.local`);
if (!current) current = await UserModel.getByUname(domainId, udoc.username);
if (current) {
report({ message: `duplicate user with email ${udoc.email}: ${current.uname},${udoc.username}` });
uidMap[udoc.id] = current._id;
} else {
const uid = await user.create(
const uid = await UserModel.create(
udoc.email || `${udoc.username}@syzoj.local`, udoc.username, '',
null, udoc.ip, system.get('default.priv'),
null, udoc.ip, SystemModel.get('default.priv'),
);
if (udoc.is_admin) await user.setSuperAdmin(uid);
if (udoc.is_admin) await UserModel.setSuperAdmin(uid);
superAdmin.push(uid);
uidMap[udoc.id] = uid;
await user.setById(uid, {
await UserModel.setById(uid, {
regat: new Date(udoc.register_time * 1000),
hash: udoc.password,
salt: udoc.password,
@@ -149,7 +136,7 @@ export async function run({
bio: udoc.information || '',
gender: sexMap[udoc.sex] || 3,
});
await domain.setUserInDomain(domainId, uid, {
await DomainModel.setUserInDomain(domainId, uid, {
displayName: udoc.nickname || '',
nSubmit: udoc.submit_num,
nAccept: udoc.ac_num,
@@ -158,19 +145,19 @@ export async function run({
}
// I think manage_problem_tag is a useless role
await domain.addRole(domainId, 'manage_problem',
await DomainModel.addRole(domainId, 'manage_problem',
PERM.PERM_DEFAULT | PERM.PERM_CREATE_PROBLEM | PERM.PERM_EDIT_PROBLEM | PERM.PERM_VIEW_PROBLEM_HIDDEN | PERM.PERM_READ_PROBLEM_DATA
| PERM.PERM_EDIT_PROBLEM_SOLUTION | PERM.PERM_DELETE_PROBLEM_SOLUTION | PERM.PERM_DELETE_PROBLEM_SOLUTION_REPLY);
await domain.addRole(domainId, 'manage_user', PERM.PERM_DEFAULT | PERM.PERM_EDIT_DOMAIN);
await DomainModel.addRole(domainId, 'manage_user', PERM.PERM_DEFAULT | PERM.PERM_EDIT_DOMAIN);
const privileges = await query('SELECT user_id,group_concat(privilege) as privilege FROM `user_privilege` group by user_id');
for (const privilege of privileges) {
if (!superAdmin.includes(privilege.user_id)) {
if (privilege.privilege.split(',').includes('manage_problem') && privilege.privilege.split(',').includes('manage_user')) {
await domain.setUserRole(domainId, uidMap[privilege.user_id], 'root');
await DomainModel.setUserRole(domainId, uidMap[privilege.user_id], 'root');
} else if (privilege.privilege.split(',').includes('manage_problem')) {
await domain.setUserRole(domainId, uidMap[privilege.user_id], 'manage_problem');
await DomainModel.setUserRole(domainId, uidMap[privilege.user_id], 'manage_problem');
} else if (privilege.privilege.split(',').includes('manage_user')) {
await domain.setUserRole(domainId, uidMap[privilege.user_id], 'manage_user');
await DomainModel.setUserRole(domainId, uidMap[privilege.user_id], 'manage_user');
}
}
}
@@ -215,7 +202,7 @@ export async function run({
const pdocs = await query(`SELECT * FROM \`problem\` LIMIT ${pageId * step}, ${step}`);
for (const pdoc of pdocs) {
if (rerun) {
const opdoc = await problem.get(domainId, `P${pdoc.id}`);
const opdoc = await ProblemModel.get(domainId, `P${pdoc.id}`);
if (opdoc) pidMap[pdoc.id] = opdoc.docId;
}
if (!pidMap[pdoc.id]) {
@@ -232,13 +219,13 @@ export async function run({
else problemAdditionalFile[`P${pdoc.id}`].push({ fromPid: pid, filename });
content = content.replace(origialPath, `file://${filename}`);
}
const pid = await problem.add(domainId, `P${pdoc.id}`, pdoc.title, content, uidMap[pdoc.user_id] || 1);
const pid = await ProblemModel.add(domainId, `P${pdoc.id}`, pdoc.title, content, uidMap[pdoc.user_id] || 1);
pidMap[pdoc.id] = pid;
}
const tags = await query(`SELECT * FROM \`problem_tag_map\` WHERE \`problem_id\` = ${pdoc.id}`);
const tagList = [];
for (const tag of tags) tagList.push(tagMap[tag.tag_id]);
await problem.edit(domainId, pidMap[pdoc.id], {
await ProblemModel.edit(domainId, pidMap[pdoc.id], {
nAccept: pdoc.ac_num || 0,
nSubmit: pdoc.submit_num || 0,
hidden: pdoc.is_public !== 1,
@@ -250,7 +237,7 @@ export async function run({
const additionalFile = await query(`SELECT * FROM \`file\` WHERE \`id\` = ${pdoc.additional_file_id}`);
if (additionalFile.length) {
const [afdoc] = additionalFile;
await problem.addAdditionalFile(domainId, pdoc.docId,
await ProblemModel.addAdditionalFile(domainId, pdoc.docId,
`additional_file_${pdoc.additional_file_id}.zip`, `${dataDir}/additional_file/${afdoc.md5}`);
}
}
@@ -282,7 +269,7 @@ export async function run({
const pdocs = tdoc.problems.split('|').map((i) => i.trim());
const pids = pdocs.map((i) => pidMap[i]).filter((i) => i);
const admin = uidMap[tdoc.holder_id] || uidMap[tdoc.admins.split('|')[0]];
const tid = await contest.add(
const tid = await ContestModel.add(
domainId, tdoc.title, `${tdoc.subtitle ? `#### ${tdoc.subtitle}\n` : ''}${tdoc.information || 'No Description'}`,
admin, contentTypeMap[tdoc.type], new Date(tdoc.start_time * 1000), new Date(tdoc.end_time * 1000),
pids, ratedTids.includes(tdoc.id), { maintainer: tdoc.admins.split('|').map((i) => uidMap[i]) },
@@ -359,9 +346,9 @@ export async function run({
}
if (rdoc.type) {
data.contest = new ObjectID(tidMap[rdoc.type_info]);
await contest.attend(domainId, data.contest, uidMap[rdoc.user_id]).catch(noop);
await ContestModel.attend(domainId, data.contest, uidMap[rdoc.user_id]).catch(noop);
}
await record.coll.insertOne(data);
await RecordModel.coll.insertOne(data);
await postJudge(data).catch((err) => report({ message: err.message }));
}
}
@@ -385,7 +372,7 @@ export async function run({
for (const ddoc of ddocs) {
const data: Partial<DiscussionDoc> = {
_id: Time.getObjectID(new Date(ddoc.public_time * 1000), false),
docType: document.TYPE_DISCUSSION,
docType: DocumentModel.TYPE_DISCUSSION,
docId: Time.getObjectID(new Date(ddoc.public_time * 1000), false),
owner: uidMap[ddoc.user_id] || 0,
title: ddoc.title,
@@ -397,11 +384,11 @@ export async function run({
lock: ddoc.allow_comment === 0,
pin: ddoc.is_notice === 1,
highlight: ddoc.is_notice === 1,
parentType: ddoc.problem_id ? document.TYPE_PROBLEM : document.TYPE_DISCUSSION_NODE,
parentType: ddoc.problem_id ? DocumentModel.TYPE_PROBLEM : DocumentModel.TYPE_DISCUSSION_NODE,
parentId: pidMap[ddoc.problem_id] || 'Hydro',
ip: '127.0.0.1',
};
await document.coll.insertOne(data);
await DocumentModel.coll.insertOne(data);
didMap[ddoc.id] = data._id;
}
@@ -418,14 +405,14 @@ export async function run({
_id: Time.getObjectID(new Date(drdoc.public_time * 1000), false),
domainId,
docId: Time.getObjectID(new Date(drdoc.public_time * 1000), false),
docType: document.TYPE_DISCUSSION_REPLY,
docType: DocumentModel.TYPE_DISCUSSION_REPLY,
content: drdoc.content,
owner: uidMap[drdoc.user_id],
parentType: document.TYPE_DISCUSSION,
parentType: DocumentModel.TYPE_DISCUSSION,
parentId: didMap[drdoc.article_id],
ip: '127.0.0.1',
};
await document.coll.insertOne(data);
await DocumentModel.coll.insertOne(data);
}
report({ message: 'article finished' });
@@ -437,21 +424,21 @@ export async function run({
for (const file of files) {
if (!file.isDirectory()) continue;
const datas = await fs.readdir(`${dataDir}/testdata/${file.name}`, { withFileTypes: true });
const pdoc = await problem.get(domainId, `P${file.name}`, undefined, true);
const pdoc = await ProblemModel.get(domainId, `P${file.name}`, undefined, true);
if (!pdoc) continue;
report({ message: `Syncing testdata for ${file.name}` });
for (const data of datas) {
if (data.isDirectory()) continue;
await problem.addTestdata(domainId, pdoc.docId, data.name, `${dataDir}/testdata/${file.name}/${data.name}`);
await ProblemModel.addTestdata(domainId, pdoc.docId, data.name, `${dataDir}/testdata/${file.name}/${data.name}`);
if (data.name.startsWith('spj_')) {
report({ message: `Syncing spj for ${file.name}` });
await problem.addTestdata(domainId, pdoc.docId,
await ProblemModel.addTestdata(domainId, pdoc.docId,
`spj.${langMap[data.name.split('spj_')[1].split('.')[0]]}`, `${dataDir}/testdata/${file.name}/${data.name}`);
pdoc.config += `\nchecker_type: syzoj\nchecker: spj.${langMap[data.name.split('spj_')[1].split('.')[0]]}`;
}
}
if (!(datas.find((i) => i.name === 'data.yml'))) {
await problem.addTestdata(domainId, pdoc.docId, 'config.yaml', Buffer.from(configMap[`P${file.name}`]));
await ProblemModel.addTestdata(domainId, pdoc.docId, 'config.yaml', Buffer.from(configMap[`P${file.name}`]));
} else {
report({ message: `Transfering data.yml for ${file.name}` });
const config = yaml.load(configMap[`P${file.name}`]) as any;
@@ -459,7 +446,7 @@ export async function run({
if (syzojConfig.specialJudge) {
report({ message: `Syncing spj config for ${file.name}` });
config.checker_type = 'syzoj';
await problem.addTestdata(domainId, pdoc.docId,
await ProblemModel.addTestdata(domainId, pdoc.docId,
`spj.${langMap[syzojConfig.specialJudge.language]}`, `${dataDir}/testdata/${file.name}/${syzojConfig.specialJudge.fileName}`);
config.checker = `spj.${langMap[syzojConfig.specialJudge.language]}`;
}
@@ -476,7 +463,7 @@ export async function run({
}
if (syzojConfig.extraSourceFiles?.length === 1) {
for (const { name: sourceName, dest } of syzojConfig.extraSourceFiles[0].files) {
await problem.addTestdata(domainId, pdoc.docId, dest,
await ProblemModel.addTestdata(domainId, pdoc.docId, dest,
`${dataDir}/testdata/${file.name}/${sourceName}`);
}
config.user_extra_files = syzojConfig.extraSourceFiles[0].files.map((x) => x.dest);
@@ -490,17 +477,17 @@ export async function run({
if (syzojConfig.interactor) {
report({ message: `Syncing interactor config for ${file.name}` });
config.type = 'interactive';
await problem.addTestdata(domainId, pdoc.docId,
await ProblemModel.addTestdata(domainId, pdoc.docId,
`spj.${langMap[syzojConfig.interactor.language]}`, `${dataDir}/testdata/${file.name}/${syzojConfig.interactor.fileName}`);
config.interactor = `spj.${langMap[syzojConfig.interactor.language]}`;
}
await problem.addTestdata(domainId, pdoc.docId, 'config.yaml', Buffer.from(yaml.dump(config)));
await ProblemModel.addTestdata(domainId, pdoc.docId, 'config.yaml', Buffer.from(yaml.dump(config)));
}
if (problemAdditionalFile[`P${file.name}`]) {
report({ message: `Syncing additional_file for ${file.name}` });
for (const data of problemAdditionalFile[`P${file.name}`]) {
if (!fs.existsSync(`${dataDir}/testdata/${data.fromPid}/${decodeURIComponent(data.filename)}`)) continue;
await problem.addAdditionalFile(domainId, pdoc.docId, data.filename,
await ProblemModel.addAdditionalFile(domainId, pdoc.docId, data.filename,
`${dataDir}/testdata/${data.fromPid}/${decodeURIComponent(data.filename)}`);
}
}
+3 -4
View File
@@ -2,12 +2,11 @@
/* eslint-disable no-await-in-loop */
import mongodb, { Cursor, Db } from 'mongodb';
import {
DiscussionTailReplyDoc, MessageDoc,
DiscussionModel, DiscussionTailReplyDoc, MessageDoc,
RecordDoc, Schema, TestCase, TrainingNode,
} from 'hydrooj';
const dst = global.Hydro.service.db;
const { discussion } = global.Hydro.model;
const map = {};
const pid = (id) => {
@@ -300,11 +299,11 @@ async function discussionNode(src: Db, report: Function) {
const nodes = item[1];
for (const node of nodes || []) {
if (node.pic) {
t.push(discussion.addNode(
t.push(DiscussionModel.addNode(
doc.domain_id, node.name, category, { pic: node.pic },
));
} else {
t.push(discussion.addNode(doc.domain_id, node.name, category, {}));
t.push(DiscussionModel.addNode(doc.domain_id, node.name, category, {}));
}
}
}
+3 -4
View File
@@ -1,9 +1,8 @@
import * as system from 'hydrooj/src/model/system';
import { UiContextBase } from 'hydrooj/src/service/layers/base';
import { SystemModel, UiContextBase } from 'hydrooj';
declare module 'hydrooj/src/service/layers/base' {
declare module 'hydrooj' {
interface UiContextBase {
onlyofficeApi?: string;
}
}
UiContextBase.onlyofficeApi = system.get('onlyoffice.api');
UiContextBase.onlyofficeApi = SystemModel.get('onlyoffice.api');
+7 -9
View File
@@ -1,15 +1,13 @@
import { AggregatorRegistry, metric } from 'prom-client';
import * as system from 'hydrooj/src/model/system';
import * as bus from 'hydrooj/src/service/bus';
import { Handler } from 'hydrooj/src/service/server';
import { Context, Handler, SystemModel } from 'hydrooj';
import { registry } from './metrics';
declare module 'hydrooj/src/service/bus' {
declare module 'hydrooj' {
interface EventMap {
metrics: (id: string, metrics: any) => void;
}
}
declare module 'hydrooj/src/interface' {
declare module 'hydrooj' {
interface SystemKeys {
'prom-client.name': string;
'prom-client.password': string;
@@ -30,7 +28,7 @@ class MetricsHandler extends Handler {
this.response.addHeader('WWW-Authenticate', 'Basic');
return;
}
const [name, password] = system.getMany(['prom-client.name', 'prom-client.password']);
const [name, password] = SystemModel.getMany(['prom-client.name', 'prom-client.password']);
const key = this.request.headers.authorization?.split('Basic ')?.[1];
if (!key || key !== Buffer.from(`${name}:${password}`).toString('base64')) {
this.response.status = 403;
@@ -42,10 +40,10 @@ class MetricsHandler extends Handler {
}
}
export function apply(ctx) {
export function apply(ctx: Context) {
ctx.on('metrics', (id, metrics) => { instances[id] = metrics; });
ctx.setInterval(async () => {
bus.broadcast('metrics', process.env.NODE_APP_INSTANCE!, await registry.getMetricsAsJSON());
}, 5000 * (+system.get('prom-client.collect_rate') || 1));
ctx.broadcast('metrics', process.env.NODE_APP_INSTANCE!, await registry.getMetricsAsJSON());
}, 5000 * (+SystemModel.get('prom-client.collect_rate') || 1));
ctx.Route('metrics', '/metrics', MetricsHandler);
}
+1 -1
View File
@@ -1,8 +1,8 @@
import {
collectDefaultMetrics, Counter, Gauge, Metric, Registry,
} from 'prom-client';
import { db } from 'hydrooj';
import * as bus from 'hydrooj/src/service/bus';
import * as db from 'hydrooj/src/service/db';
export const registry = new Registry();
registry.setDefaultLabels({ instanceid: process.env.NODE_APP_INSTANCE });
-27
View File
@@ -1,27 +0,0 @@
import superagent from 'superagent';
import { ForbiddenError, ValidationError } from 'hydrooj/src/error';
import { PRIV } from 'hydrooj/src/model/builtin';
import * as system from 'hydrooj/src/model/system';
import * as bus from 'hydrooj/src/service/bus';
bus.on('handler/before/UserRegister', async (thisArg) => {
if (!system.get('recaptcha.key')) return;
if (thisArg.request.method !== 'post') {
thisArg.UiContext.recaptchaKey = system.get('recaptcha.key');
return;
}
if (thisArg.user.hasPriv(PRIV.PRIV_UNLIMITED_ACCESS)) return;
if (!thisArg.args.captcha) throw new ValidationError('captcha');
const response = await superagent.post('https://recaptcha.net/recaptcha/api/siteverify')
.field('secret', system.get('recaptcha.secret'))
.field('response', thisArg.args.captcha)
.field('remoteip', thisArg.request.ip);
if (!response.body.success) throw new ForbiddenError('captcha fail');
});
bus.on('handler/after/UserRegister', async (thisArg) => {
thisArg.response.body.captcha = `\
<script src="https://recaptcha.net/recaptcha/api.js?render=${system.get('recaptcha.key')}"></script>
<input type="text" name="captcha" id="_captcha" style="display:none">
<input type="submit" id="_submit" style="display:none">`;
});
+27
View File
@@ -0,0 +1,27 @@
import {
Context, ForbiddenError, PRIV, superagent, SystemModel, ValidationError,
} from 'hydrooj';
export function apply(ctx: Context) {
ctx.on('handler/before/UserRegister', async (thisArg) => {
if (!SystemModel.get('recaptcha.key')) return;
if (thisArg.request.method !== 'post') {
thisArg.UiContext.recaptchaKey = SystemModel.get('recaptcha.key');
return;
}
if (thisArg.user.hasPriv(PRIV.PRIV_UNLIMITED_ACCESS)) return;
if (!thisArg.args.captcha) throw new ValidationError('captcha');
const response = await superagent.post('https://recaptcha.net/recaptcha/api/siteverify')
.field('secret', SystemModel.get('recaptcha.secret'))
.field('response', thisArg.args.captcha)
.field('remoteip', thisArg.request.ip);
if (!response.body.success) throw new ForbiddenError('captcha fail');
});
ctx.on('handler/after/UserRegister', async (thisArg) => {
thisArg.response.body.captcha = `\
<script src="https://recaptcha.net/recaptcha/api.js?render=${SystemModel.get('recaptcha.key')}"></script>
<input type="text" name="captcha" id="_captcha" style="display:none">
<input type="submit" id="_submit" style="display:none">`;
});
}
+1 -4
View File
@@ -6,8 +6,5 @@
"repository": "https://github.com/hydro-dev/Hydro.git",
"author": "undefined",
"license": "AGPL-3.0-or-later",
"preferUnplugged": true,
"dependencies": {
"superagent": "^8.0.0"
}
"preferUnplugged": true
}
+23 -22
View File
@@ -1,16 +1,18 @@
import { Schema } from 'hydrooj';
import { Logger } from 'hydrooj/src/logger';
import DomainModel from 'hydrooj/src/model/domain';
import * as system from 'hydrooj/src/model/system';
import { iterateAllProblem, iterateAllProblemInDomain } from 'hydrooj/src/pipelineUtils';
import sonic from './service';
import {
Context, DomainModel, iterateAllProblem, iterateAllProblemInDomain,
Logger, Schema, SystemModel,
} from 'hydrooj';
import * as SonicService from './service';
const logger = new Logger('sonic');
export function apply(ctx: Context) {
ctx.plugin(SonicService);
global.Hydro.lib.problemSearch = async (domainId, query, opts) => {
const limit = opts?.limit || system.get('pagination.problem');
const ids = await sonic.query('problem', `${domainId}@title`, query, { limit });
if (limit - ids.length > 0) ids.push(...await sonic.query('problem', `${domainId}@content`, query, { limit: limit - ids.length }));
const limit = opts?.limit || SystemModel.get('pagination.problem');
const ids = await ctx.sonic.query('problem', `${domainId}@title`, query, { limit });
if (limit - ids.length > 0) ids.push(...await ctx.sonic.query('problem', `${domainId}@content`, query, { limit: limit - ids.length }));
return {
countRelation: ids.length >= limit ? 'gte' : 'eq',
total: ids.length,
@@ -19,8 +21,8 @@ global.Hydro.lib.problemSearch = async (domainId, query, opts) => {
};
async function run({ domainId }, report) {
if (domainId) await sonic.flushb('problem', domainId);
else await sonic.flushc('problem');
if (domainId) await ctx.sonic.flushb('problem', domainId);
else await ctx.sonic.flushc('problem');
let i = 0;
const cb = async (pdoc) => {
i++;
@@ -29,11 +31,11 @@ async function run({ domainId }, report) {
const tasks = [];
for (const did of [pdoc.domainId, ...union.map((j) => j._id)]) {
tasks.push(
pdoc.title && sonic.push(
pdoc.title && ctx.sonic.push(
'problem', `${did}@title`, `${pdoc.domainId}/${pdoc.docId}`,
`${pdoc.pid || ''} ${pdoc.title} ${pdoc.tag.join(' ')}`,
),
pdoc.content.toString() && sonic.push('problem', `${did}@content`, `${pdoc.domainId}/${pdoc.docId}`, pdoc.content.toString()),
pdoc.content.toString() && ctx.sonic.push('problem', `${did}@content`, `${pdoc.domainId}/${pdoc.docId}`, pdoc.content.toString()),
);
}
await Promise.all(tasks).catch((e) => console.log(`${pdoc.domainId}/${pdoc.docId}`, e));
@@ -43,14 +45,13 @@ async function run({ domainId }, report) {
return true;
}
export function apply(ctx) {
ctx.on('problem/add', async (doc, docId) => {
const union = await DomainModel.searchUnion({ union: doc.domainId, problem: true });
const tasks = [];
for (const domainId of [doc.domainId, ...union.map((i) => i._id)]) {
tasks.push(
sonic.push('problem', `${domainId}@title`, `${doc.domainId}/${docId}`, `${doc.pid || ''} ${doc.title} ${doc.tag?.join(' ')}`),
sonic.push('problem', `${domainId}@content`, `${doc.domainId}/${docId}`, doc.content.toString()),
ctx.sonic.push('problem', `${domainId}@title`, `${doc.domainId}/${docId}`, `${doc.pid || ''} ${doc.title} ${doc.tag?.join(' ')}`),
ctx.sonic.push('problem', `${domainId}@content`, `${doc.domainId}/${docId}`, doc.content.toString()),
);
}
Promise.all(tasks).catch((e) => logger.error(e));
@@ -62,10 +63,10 @@ export function apply(ctx) {
const id = `${pdoc.domainId}/${pdoc.docId}`;
for (const domainId of [pdoc.domainId, ...union.map((i) => i._id)]) {
tasks.push(
sonic.flusho('problem', `${domainId}@title`, id)
.then(() => sonic.push('problem', `${domainId}@title`, id, `${pdoc.pid || ''} ${pdoc.title} ${pdoc.tag?.join(' ')}`)),
sonic.flusho('problem', `${domainId}@content`, id)
.then(() => sonic.push('problem', `${domainId}@content`, id, pdoc.content.toString())),
ctx.sonic.flusho('problem', `${domainId}@title`, id)
.then(() => ctx.sonic.push('problem', `${domainId}@title`, id, `${pdoc.pid || ''} ${pdoc.title} ${pdoc.tag?.join(' ')}`)),
ctx.sonic.flusho('problem', `${domainId}@content`, id)
.then(() => ctx.sonic.push('problem', `${domainId}@content`, id, pdoc.content.toString())),
);
}
Promise.all(tasks).catch((e) => logger.error(e));
@@ -77,8 +78,8 @@ export function apply(ctx) {
const id = `${domainId}/${docId}`;
for (const domain of [domainId, ...union.map((i) => i._id)]) {
tasks.push(
sonic.flusho('problem', `${domain}@title`, id),
sonic.flusho('problem', `${domain}@content`, id),
ctx.sonic.flusho('problem', `${domain}@title`, id),
ctx.sonic.flusho('problem', `${domain}@content`, id),
);
}
await Promise.all(tasks);
-58
View File
@@ -1,58 +0,0 @@
import { Logger } from 'hydrooj/src/logger';
import DomainModel from 'hydrooj/src/model/domain';
import * as system from 'hydrooj/src/model/system';
import * as bus from 'hydrooj/src/service/bus';
import sonic from './service';
const logger = new Logger('sonic');
bus.on('problem/add', async (doc, docId) => {
const union = await DomainModel.searchUnion({ union: doc.domainId, problem: true });
const tasks = [];
for (const domainId of [doc.domainId, ...union.map((i) => i._id)]) {
tasks.push(
sonic.push('problem', `${domainId}@title`, `${doc.domainId}/${docId}`, `${doc.pid || ''} ${doc.title} ${doc.tag?.join(' ')}`),
sonic.push('problem', `${domainId}@content`, `${doc.domainId}/${docId}`, doc.content.toString()),
);
}
Promise.all(tasks).catch((e) => logger.error(e));
});
bus.on('problem/edit', async (pdoc) => {
const union = await DomainModel.searchUnion({ union: pdoc.domainId, problem: true });
const tasks = [];
const id = `${pdoc.domainId}/${pdoc.docId}`;
for (const domainId of [pdoc.domainId, ...union.map((i) => i._id)]) {
tasks.push(
sonic.flusho('problem', `${domainId}@title`, id)
.then(() => sonic.push('problem', `${domainId}@title`, id, `${pdoc.pid || ''} ${pdoc.title} ${pdoc.tag?.join(' ')}`)),
sonic.flusho('problem', `${domainId}@content`, id)
.then(() => sonic.push('problem', `${domainId}@content`, id, pdoc.content.toString())),
);
}
Promise.all(tasks).catch((e) => logger.error(e));
});
bus.on('problem/del', async (domainId, docId) => {
const union = await DomainModel.searchUnion({ union: domainId, problem: true });
const tasks = [];
const id = `${domainId}/${docId}`;
for (const domain of [domainId, ...union.map((i) => i._id)]) {
tasks.push(
sonic.flusho('problem', `${domain}@title`, id),
sonic.flusho('problem', `${domain}@content`, id),
);
}
await Promise.all(tasks);
});
global.Hydro.lib.problemSearch = async (domainId, query, opts) => {
const limit = opts?.limit || system.get('pagination.problem');
const ids = await sonic.query('problem', `${domainId}@title`, query, { limit });
if (limit - ids.length > 0) ids.push(...await sonic.query('problem', `${domainId}@content`, query, { limit: limit - ids.length }));
return {
countRelation: ids.length >= limit ? 'gte' : 'eq',
total: ids.length,
hits: ids,
};
};
-37
View File
@@ -1,37 +0,0 @@
import { Schema } from 'hydrooj';
import DomainModel from 'hydrooj/src/model/domain';
import { iterateAllProblem, iterateAllProblemInDomain } from 'hydrooj/src/pipelineUtils';
import sonic from './service';
async function run({ domainId }, report) {
if (domainId) await sonic.flushb('problem', domainId);
else await sonic.flushc('problem');
let i = 0;
const cb = async (pdoc) => {
i++;
if (!(i % 1000)) report({ message: `${i} problems indexed` });
const union = await DomainModel.searchUnion({ union: pdoc.domainId, problem: true });
const tasks = [];
for (const did of [pdoc.domainId, ...union.map((j) => j._id)]) {
tasks.push(
pdoc.title && sonic.push(
'problem', `${did}@title`, `${pdoc.domainId}/${pdoc.docId}`,
`${pdoc.pid || ''} ${pdoc.title} ${pdoc.tag.join(' ')}`,
),
pdoc.content.toString() && sonic.push('problem', `${did}@content`, `${pdoc.domainId}/${pdoc.docId}`, pdoc.content.toString()),
);
}
await Promise.all(tasks).catch((e) => console.log(`${pdoc.domainId}/${pdoc.docId}`, e));
};
if (domainId) await iterateAllProblemInDomain(domainId, ['title', 'content'], cb);
else await iterateAllProblem(['title', 'content', 'tag'], cb);
return true;
}
export const apply = (ctx) => ctx.addScript(
'ensureSonicSearch', 'Sonic problem search re-index',
Schema.object({
domainId: Schema.string(),
}),
run,
);
+13 -18
View File
@@ -2,28 +2,29 @@ import {
Ingest, PushOptions, QueryOptions,
Search, SuggestOptions,
} from 'sonic-channel';
import { BaseService } from 'hydrooj';
import { Logger } from 'hydrooj/src/logger';
import * as system from 'hydrooj/src/model/system';
import {
Context, Logger, Service, SystemModel,
} from 'hydrooj';
const logger = new Logger('sonic');
declare module 'hydrooj/src/interface' {
declare module 'hydrooj' {
interface SystemKeys {
'sonic.host': string;
'sonic.port': number;
'sonic.auth': string;
}
interface Context {
sonic: SonicService;
}
}
const logger = new Logger('sonic');
function getHandler(type: string, that: any) {
return {
connected() {
that.started = true;
logger.info(`Sonic Channel succeeded to connect to host (${type}).`);
},
disconnected() {
that.started = false;
logger.error(`Sonic Channel is now disconnected (${type}).`);
},
timeout() {
@@ -39,14 +40,13 @@ function getHandler(type: string, that: any) {
};
}
class SonicService implements BaseService {
class SonicService extends Service {
public search: Search;
public ingest: Ingest;
public started = false;
public error = '';
async start() {
const [host, port, auth] = system.getMany(['sonic.host', 'sonic.port', 'sonic.auth']);
const [host, port, auth] = SystemModel.getMany(['sonic.host', 'sonic.port', 'sonic.auth']);
const cfg = {
host: host || '::1',
port: port || 1491,
@@ -108,11 +108,6 @@ class SonicService implements BaseService {
}
}
const service = new SonicService();
global.Hydro.service.sonic = service;
export = service;
declare module 'hydrooj/src/interface' {
interface Service {
sonic: typeof service
}
export function apply() {
Context.service('sonic', SonicService);
}
+23 -33
View File
@@ -2,26 +2,18 @@
/* eslint-disable camelcase */
import crypto from 'crypto';
import esbuild from 'esbuild';
import fs from 'fs-extra';
import { Logger } from 'hydrooj/src/logger';
import { PERM, PRIV } from 'hydrooj/src/model/builtin';
import * as contest from 'hydrooj/src/model/contest';
import problem from 'hydrooj/src/model/problem';
import * as setting from 'hydrooj/src/model/setting';
import * as system from 'hydrooj/src/model/system';
import user from 'hydrooj/src/model/user';
import { UiContextBase } from 'hydrooj/src/service/layers/base';
import { Handler } from 'hydrooj/src/service/server';
import { SystemSettings } from 'hydrooj/src/settings';
import {
ContestModel, fs, Handler, Logger, ObjectID, PERM, PRIV, ProblemModel, Schema,
SettingModel, SystemModel, SystemSettings, UiContextBase,
UserModel,
} from 'hydrooj';
import { debounce } from 'lodash';
import { ObjectID } from 'mongodb';
import { tmpdir } from 'os';
import { join, resolve } from 'path';
import Schema from 'schemastery';
import convert from 'schemastery-jsonschema';
import markdown from './backendlib/markdown';
declare module 'hydrooj/src/interface' {
declare module 'hydrooj' {
interface UI {
esbuildPlugins?: esbuild.Plugin[]
}
@@ -29,8 +21,6 @@ declare module 'hydrooj/src/interface' {
'ui-default.nav_logo_dark': string;
'ui-default.nav_logo_dark_2x': string;
}
}
declare module 'hydrooj/src/service/layers/base' {
interface UiContextBase {
nav_logo_dark: string;
nav_logo_dark_2x: string;
@@ -70,7 +60,7 @@ export async function buildUI() {
if (build.errors.length) console.error(build.errors);
if (build.warnings.length) console.warn(build.warnings);
const pages = build.outputFiles.map((i) => i.text);
const payload = [`window.LANGS=${JSON.stringify(setting.langs)};`, ...pages];
const payload = [`window.LANGS=${JSON.stringify(SettingModel.langs)};`, ...pages];
const c = crypto.createHash('sha1');
c.update(JSON.stringify(payload));
@@ -80,7 +70,7 @@ export async function buildUI() {
logger.success('UI addons built in %d ms', Date.now() - start);
}
function updateLogo() {
[UiContextBase.nav_logo_dark, UiContextBase.nav_logo_dark_2x] = system.getMany([
[UiContextBase.nav_logo_dark, UiContextBase.nav_logo_dark_2x] = SystemModel.getMany([
'ui-default.nav_logo_dark', 'ui-default.nav_logo_dark_2x',
]);
}
@@ -89,7 +79,7 @@ class WikiHelpHandler extends Handler {
noCheckPermView = true;
async get({ domainId }) {
const LANGS = setting.langs;
const LANGS = SettingModel.langs;
const languages = {};
for (const key in LANGS) {
if (LANGS[key].domain && !LANGS[key].domain.includes(domainId)) continue;
@@ -105,9 +95,9 @@ class WikiAboutHandler extends Handler {
noCheckPermView = true;
async get() {
let raw = system.get('ui-default.about') || '';
let raw = SystemModel.get('ui-default.about') || '';
// TODO template engine
raw = raw.replace(/{{ name }}/g, this.domain.ui?.name || system.get('server.name')).trim();
raw = raw.replace(/{{ name }}/g, this.domain.ui?.name || SystemModel.get('server.name')).trim();
const lines = raw.split('\n');
const sections = [];
for (const line of lines) {
@@ -130,7 +120,7 @@ class SetThemeHandler extends Handler {
async get({ theme }) {
this.checkPriv(PRIV.PRIV_USER_PROFILE);
await user.setById(this.user._id, { theme });
await UserModel.setById(this.user._id, { theme });
this.back();
}
}
@@ -165,7 +155,7 @@ class UiConstantsHandler extends ResourceHandler {
class LanguageHandler extends ResourceHandler {
async all({ lang }) {
if (!global.Hydro.locales[lang]) lang = system.get('server.language');
if (!global.Hydro.locales[lang]) lang = SystemModel.get('server.language');
this.response.body = `window.LOCALES=${JSON.stringify(global.Hydro.locales[lang])};`;
this.response.type = 'application/javascript';
}
@@ -181,34 +171,34 @@ class SystemConfigSchemaHandler extends Handler {
class RichMediaHandler extends Handler {
async renderUser(domainId, payload) {
let d = payload.domainId || domainId;
const cur = payload.domainId ? await user.getById(payload.domainId, this.user._id) : this.user;
const cur = payload.domainId ? await UserModel.getById(payload.domainId, this.user._id) : this.user;
if (!cur.hasPerm(PERM.PERM_VIEW)) d = domainId;
const udoc = Number.isNaN(+payload.id) ? await user.getByUname(d, payload.id) : await user.getById(d, +payload.id);
const udoc = Number.isNaN(+payload.id) ? await UserModel.getByUname(d, payload.id) : await UserModel.getById(d, +payload.id);
return await this.renderHTML('partials/user.html', { udoc });
}
async renderProblem(domainId, payload) {
const cur = payload.domainId ? await user.getById(payload.domainId, this.user._id) : this.user;
const cur = payload.domainId ? await UserModel.getById(payload.domainId, this.user._id) : this.user;
let pdoc = cur.hasPerm(PERM.PERM_VIEW | PERM.PERM_VIEW_PROBLEM)
? await problem.get(payload.domainId || domainId, payload.id) || problem.default
: problem.default;
if (pdoc.hidden && !cur.own(pdoc) && !cur.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) pdoc = problem.default;
? await ProblemModel.get(payload.domainId || domainId, payload.id) || ProblemModel.default
: ProblemModel.default;
if (pdoc.hidden && !cur.own(pdoc) && !cur.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) pdoc = ProblemModel.default;
return await this.renderHTML('partials/problem.html', { pdoc });
}
async renderContest(domainId, payload) {
const cur = payload.domainId ? await user.getById(payload.domainId, this.user._id) : this.user;
const cur = payload.domainId ? await UserModel.getById(payload.domainId, this.user._id) : this.user;
const tdoc = cur.hasPerm(PERM.PERM_VIEW | PERM.PERM_VIEW_CONTEST)
? await contest.get(payload.domainId || domainId, new ObjectID(payload.id))
? await ContestModel.get(payload.domainId || domainId, new ObjectID(payload.id))
: null;
if (tdoc) return await this.renderHTML('partials/contest.html', { tdoc });
return '';
}
async renderHomework(domainId, payload) {
const cur = payload.domainId ? await user.getById(payload.domainId, this.user._id) : this.user;
const cur = payload.domainId ? await UserModel.getById(payload.domainId, this.user._id) : this.user;
const tdoc = cur.hasPerm(PERM.PERM_VIEW | PERM.PERM_VIEW_HOMEWORK)
? await contest.get(payload.domainId || domainId, new ObjectID(payload.id))
? await ContestModel.get(payload.domainId || domainId, new ObjectID(payload.id))
: null;
if (tdoc) return await this.renderHTML('partials/homework.html', { tdoc });
return '';
+1 -1
View File
@@ -13,7 +13,7 @@ export interface RemoteAccount {
problemLists?: string[];
enableOn?: string[];
}
declare module 'hydrooj/src/interface' {
declare module 'hydrooj' {
interface Collections {
vjudge: RemoteAccount;
}
+10 -13
View File
@@ -1,14 +1,9 @@
/* eslint-disable no-await-in-loop */
import os from 'os';
import { sleep } from '@hydrooj/utils/lib/utils';
import * as Judge from 'hydrooj/src/handler/judge';
import { Logger } from 'hydrooj/src/logger';
import { STATUS } from 'hydrooj/src/model/builtin';
import DomainModel from 'hydrooj/src/model/domain';
import ProblemModel from 'hydrooj/src/model/problem';
import TaskModel from 'hydrooj/src/model/task';
import * as bus from 'hydrooj/src/service/bus';
import db from 'hydrooj/src/service/db';
import {
Context, db, DomainModel, JudgeHandler, Logger,
ProblemModel, sleep, STATUS, TaskModel,
} from 'hydrooj';
import { BasicProvider, IBasicProvider, RemoteAccount } from './interface';
import { getDifficulty } from './providers/codeforces';
import providers from './providers/index';
@@ -40,8 +35,8 @@ class Service {
}
async judge(task) {
const next = (payload) => Judge.next({ ...payload, rid: task.rid });
const end = (payload) => Judge.end({ ...payload, rid: task.rid });
const next = (payload) => JudgeHandler.next({ ...payload, rid: task.rid });
const end = (payload) => JudgeHandler.end({ ...payload, rid: task.rid });
await next({ status: STATUS.STATUS_FETCHED });
try {
const rid = await this.api.submitProblem(task.target, task.lang, task.code, task, next, end);
@@ -137,7 +132,7 @@ async function loadAccounts() {
}
}
declare module 'hydrooj/src/interface' {
declare module 'hydrooj' {
interface Model {
vjudge: VJudgeModel;
}
@@ -155,4 +150,6 @@ class VJudgeModel {
global.Hydro.model.vjudge = VJudgeModel;
bus.on('app/started', loadAccounts);
export function apply(ctx: Context) {
ctx.on('ready', loadAccounts);
}
+4 -6
View File
@@ -9,11 +9,9 @@ import PortalPlugin from 'puppeteer-extra-plugin-portal';
import StealthPlugin from 'puppeteer-extra-plugin-stealth';
import superagent from 'superagent';
import proxy from 'superagent-proxy';
import { STATUS } from '@hydrooj/utils/lib/status';
import { sleep } from '@hydrooj/utils/lib/utils';
import { buildContent } from 'hydrooj/src/lib/content';
import { Logger } from 'hydrooj/src/logger';
import * as setting from 'hydrooj/src/model/setting';
import {
buildContent, Logger, SettingModel, sleep, STATUS,
} from 'hydrooj';
import { IBasicProvider, RemoteAccount } from '../interface';
import { VERDICT } from '../verdict';
@@ -386,7 +384,7 @@ export default class CodeforcesProvider implements IBasicProvider {
async submitProblem(id: string, lang: string, code: string, info, next, end) {
const programTypeId = lang.includes('codeforces.') ? lang.split('codeforces.')[1] : '54';
const comment = setting.langs[lang].comment;
const comment = SettingModel.langs[lang].comment;
if (comment) {
const msg = `Hydro submission #${info.rid}@${new Date().getTime()}`;
if (typeof comment === 'string') code = `${comment} ${msg}\n${code}`;
+1 -3
View File
@@ -3,9 +3,7 @@ import { PassThrough } from 'stream';
import { JSDOM } from 'jsdom';
import * as superagent from 'superagent';
import proxy from 'superagent-proxy';
import { STATUS } from '@hydrooj/utils/lib/status';
import { sleep } from '@hydrooj/utils/lib/utils';
import { Logger } from 'hydrooj/src/logger';
import { Logger, sleep, STATUS } from 'hydrooj';
import { IBasicProvider, RemoteAccount } from '../interface';
proxy(superagent as any);
+1 -1
View File
@@ -5,7 +5,7 @@ import poj from './poj';
import spoj from './spoj';
import uoj from './uoj';
declare module 'hydrooj/src/interface' {
declare module 'hydrooj' {
interface HydroGlobal {
vjudge: typeof vjudge;
}
+4 -5
View File
@@ -3,10 +3,9 @@ import { JSDOM } from 'jsdom';
import { flattenDeep } from 'lodash';
import superagent from 'superagent';
import proxy from 'superagent-proxy';
import { STATUS } from '@hydrooj/utils/lib/status';
import { sleep } from '@hydrooj/utils/lib/utils';
import { Logger } from 'hydrooj/src/logger';
import * as setting from 'hydrooj/src/model/setting';
import {
Logger, SettingModel, sleep, STATUS,
} from 'hydrooj';
import { IBasicProvider, RemoteAccount } from '../interface';
proxy(superagent);
@@ -104,7 +103,7 @@ export default class LuoguProvider implements IBasicProvider {
async submitProblem(id: string, lang: string, code: string, info, next, end) {
let enableO2 = 0;
const comment = setting.langs[lang]?.comment;
const comment = SettingModel.langs[lang]?.comment;
if (code.length < 10) {
end({ status: STATUS.STATUS_COMPILE_ERROR, message: 'Code too short' });
return null;
+3 -6
View File
@@ -3,12 +3,9 @@ import { PassThrough } from 'stream';
import { JSDOM } from 'jsdom';
import * as superagent from 'superagent';
import proxy from 'superagent-proxy';
import { STATUS } from '@hydrooj/utils/lib/status';
import {
htmlEncode, parseMemoryMB, parseTimeMS, sleep,
} from '@hydrooj/utils/lib/utils';
import { Logger } from 'hydrooj/src/logger';
import * as setting from 'hydrooj/src/model/setting';
htmlEncode, Logger, parseMemoryMB, parseTimeMS, SettingModel, sleep, STATUS,
} from 'hydrooj';
import { IBasicProvider, RemoteAccount } from '../interface';
import { VERDICT } from '../verdict';
@@ -221,7 +218,7 @@ export default class POJProvider implements IBasicProvider {
async submitProblem(id: string, lang: string, code: string, info) {
await this.ensureLogin();
const language = lang.includes('poj.') ? lang.split('poj.')[1] : '0';
const comment = setting.langs[lang].comment;
const comment = SettingModel.langs[lang].comment;
if (comment) {
const msg = `Hydro submission #${info.rid}@${new Date().getTime()}`;
if (typeof comment === 'string') code = `${comment} ${msg}\n${code}`;
+4 -5
View File
@@ -3,10 +3,9 @@ import { PassThrough } from 'stream';
import { JSDOM } from 'jsdom';
import superagent from 'superagent';
import proxy from 'superagent-proxy';
import { STATUS } from '@hydrooj/utils/lib/status';
import { parseMemoryMB, sleep } from '@hydrooj/utils/lib/utils';
import { Logger } from 'hydrooj/src/logger';
import * as setting from 'hydrooj/src/model/setting';
import {
Logger, parseMemoryMB, SettingModel, sleep, STATUS,
} from 'hydrooj';
import { IBasicProvider, RemoteAccount } from '../interface';
proxy(superagent);
@@ -118,7 +117,7 @@ langs: ${JSON.stringify(langs)}`),
}
async submitProblem(problemcode: string, lang: string, code: string, info, next, end) {
const comment = setting.langs[lang]?.comment;
const comment = SettingModel.langs[lang]?.comment;
if (comment) {
const msg = `Hydro submission #${info.rid}@${new Date().getTime()}`;
if (typeof comment === 'string') code = `${comment} ${msg}\n${code}`;
+4 -5
View File
@@ -3,10 +3,9 @@ import { PassThrough } from 'stream';
import { JSDOM } from 'jsdom';
import * as superagent from 'superagent';
import proxy from 'superagent-proxy';
import { STATUS } from '@hydrooj/utils/lib/status';
import { parseMemoryMB, parseTimeMS, sleep } from '@hydrooj/utils/lib/utils';
import { Logger } from 'hydrooj/src/logger';
import * as setting from 'hydrooj/src/model/setting';
import {
Logger, parseMemoryMB, parseTimeMS, SettingModel, sleep, STATUS,
} from 'hydrooj';
import { IBasicProvider, RemoteAccount } from '../interface';
import { VERDICT } from '../verdict';
@@ -165,7 +164,7 @@ export default class UOJProvider implements IBasicProvider {
async submitProblem(id: string, lang: string, code: string, info) {
let programTypeId = lang.includes('uoj.') ? lang.split('uoj.')[1] : 'C++11';
if (programTypeId === 'Python27') programTypeId = 'Python2.7';
const comment = setting.langs[lang].comment;
const comment = SettingModel.langs[lang].comment;
if (comment) {
const msg = `Hydro submission #${info.rid}@${new Date().getTime()}`;
if (typeof comment === 'string') code = `${comment} ${msg}\n${code}`;
-25
View File
@@ -1,25 +0,0 @@
{
"entryPoints": [
"packages/hydrooj/src/model/blacklist.ts",
"packages/hydrooj/src/model/builtin.ts",
"packages/hydrooj/src/model/contest.ts",
"packages/hydrooj/src/model/discussion.ts",
"packages/hydrooj/src/model/document.ts",
"packages/hydrooj/src/model/domain.ts",
"packages/hydrooj/src/model/message.ts",
"packages/hydrooj/src/model/oauth.ts",
"packages/hydrooj/src/model/opcount.ts",
"packages/hydrooj/src/model/oplog.ts",
"packages/hydrooj/src/model/problem.ts",
"packages/hydrooj/src/model/record.ts",
"packages/hydrooj/src/model/setting.ts",
"packages/hydrooj/src/model/solution.ts",
"packages/hydrooj/src/model/system.ts",
"packages/hydrooj/src/model/task.ts",
"packages/hydrooj/src/model/token.ts",
"packages/hydrooj/src/model/training.ts",
"packages/hydrooj/src/model/user.ts"
],
"tsconfig": "packages/hydrooj/tsconfig.json",
"out": "docs"
}