core: mark model as plugin

This commit is contained in:
undefined committed 2022-09-30 23:34:54 +08:00
1 parent 7f96f602ce
commit 90a9fa95f4
21 files changed
+117 -89

No files matched your search

+1 -1
View File
@@ -2,6 +2,6 @@
require('@hydrooj/utils/lib/register'); require('@hydrooj/utils/lib/register');
const { default: hook } = require('require-resolve-hook'); const { default: hook } = require('require-resolve-hook');
const { bypass } = hook(/^(hydrooj|@hydrooj\/utils)/, (id) => bypass(() => require.resolve(id))); const { bypass } = hook(/^(hydrooj|@hydrooj\/utils|cordis|schemastery|lodash|js-yaml)/, (id) => bypass(() => require.resolve(id)));
require('./commands'); require('./commands');
+8
View File
@@ -56,6 +56,14 @@ export const lib = getLoader('lib', 'lib');
export const script = getLoader('script', 'script'); export const script = getLoader('script', 'script');
export const service = getLoader('service', 'service'); export const service = getLoader('service', 'service');
export async function builtinModel(ctx: Context) {
const modelDir = path.resolve(__dirname, '..', 'model');
const models = ['task', 'blacklist', 'discussion', 'document'];
for (const t of models) {
ctx.loader.reloadPlugin(ctx, path.resolve(modelDir, t), {}, `hydrooj/model/${t}`);
}
}
export async function locale(pending: string[], fail: string[]) { export async function locale(pending: string[], fail: string[]) {
await Promise.all(pending.map(async (i) => { await Promise.all(pending.map(async (i) => {
const p = locateFile(i, ['locale', 'locales']); const p = locateFile(i, ['locale', 'locales']);
+9 -5
View File
@@ -8,7 +8,7 @@ import { Context } from '../context';
import { Logger } from '../logger'; import { Logger } from '../logger';
import db from '../service/db'; import db from '../service/db';
import { import {
addon, handler, lib, locale, model, addon, builtinModel, handler, lib, locale, model,
script, service, setting, template, script, service, setting, template,
} from './common'; } from './common';
@@ -46,15 +46,17 @@ export async function apply(ctx: Context) {
await lib(pending, fail, ctx); await lib(pending, fail, ctx);
require('../service/monitor'); require('../service/monitor');
await service(pending, fail, ctx); await service(pending, fail, ctx);
require('../model/index'); await builtinModel(ctx);
await model(pending, fail, ctx);
await ctx.lifecycle.flush();
const modelSetting = require('../model/setting');
await setting(pending, fail, modelSetting);
const handlerDir = path.resolve(__dirname, '..', 'handler'); const handlerDir = path.resolve(__dirname, '..', 'handler');
const handlers = await fs.readdir(handlerDir); const handlers = await fs.readdir(handlerDir);
for (const h of handlers) { for (const h of handlers) {
ctx.loader.reloadPlugin(ctx, path.resolve(handlerDir, h), {}, `hydrooj/handler/${h.split('.')[0]}`); ctx.loader.reloadPlugin(ctx, path.resolve(handlerDir, h), {}, `hydrooj/handler/${h.split('.')[0]}`);
} }
await model(pending, fail, ctx);
const modelSetting = require('../model/setting');
await setting(pending, fail, modelSetting);
await handler(pending, fail, ctx); await handler(pending, fail, ctx);
await addon(pending, fail, ctx); await addon(pending, fail, ctx);
for (const i in global.Hydro.handler) await global.Hydro.handler[i](); for (const i in global.Hydro.handler) await global.Hydro.handler[i]();
@@ -62,7 +64,9 @@ export async function apply(ctx: Context) {
for (const h of await fs.readdir(scriptDir)) { for (const h of await fs.readdir(scriptDir)) {
ctx.loader.reloadPlugin(ctx, path.resolve(scriptDir, h), {}, `hydrooj/script/${h.split('.')[0]}`); ctx.loader.reloadPlugin(ctx, path.resolve(scriptDir, h), {}, `hydrooj/script/${h.split('.')[0]}`);
} }
await ctx.lifecycle.flush();
await script(pending, fail, ctx); await script(pending, fail, ctx);
await ctx.lifecycle.flush();
await ctx.parallel('app/started'); await ctx.parallel('app/started');
if (process.env.NODE_APP_INSTANCE === '0') { if (process.env.NODE_APP_INSTANCE === '0') {
const scripts = require('../upgrade').default; const scripts = require('../upgrade').default;
+2 -1
View File
@@ -1,6 +1,7 @@
import { load } from 'js-yaml'; import { load } from 'js-yaml';
import { Dictionary } from 'lodash'; import { Dictionary } from 'lodash';
import moment from 'moment-timezone'; import moment from 'moment-timezone';
import { Context } from '../context';
import { import {
DomainJoinAlreadyMemberError, DomainJoinForbiddenError, ForbiddenError, DomainJoinAlreadyMemberError, DomainJoinForbiddenError, ForbiddenError,
InvalidJoinInvitationCodeError, PermissionError, RoleAlreadyExistError, ValidationError, InvalidJoinInvitationCodeError, PermissionError, RoleAlreadyExistError, ValidationError,
@@ -334,7 +335,7 @@ class DomainSearchHandler extends Handler {
} }
} }
export async function apply(ctx) { export async function apply(ctx: Context) {
ctx.Route('ranking', '/ranking', DomainRankHandler, PERM.PERM_VIEW_RANKING); ctx.Route('ranking', '/ranking', DomainRankHandler, PERM.PERM_VIEW_RANKING);
ctx.Route('domain_dashboard', '/domain/dashboard', DomainDashboardHandler); ctx.Route('domain_dashboard', '/domain/dashboard', DomainDashboardHandler);
ctx.Route('domain_edit', '/domain/edit', DomainEditHandler); ctx.Route('domain_edit', '/domain/edit', DomainEditHandler);
+1 -1
View File
@@ -70,7 +70,7 @@ class StatusUpdateHandler extends Handler {
} }
} }
bus.on('app/started', () => coll.createIndex('updateAt', { expireAfterSeconds: 24 * 3600 })); bus.on('ready', () => coll.createIndex('updateAt', { expireAfterSeconds: 24 * 3600 }));
export async function apply(ctx) { export async function apply(ctx) {
ctx.Route('status', '/status', StatusHandler); ctx.Route('status', '/status', StatusHandler);
-7
View File
@@ -561,13 +561,6 @@ export interface Task {
[key: string]: any; [key: string]: any;
} }
export interface BaseService {
started: boolean;
error?: Error | string;
start: Function;
stop?: Function;
}
export interface FileNode { export interface FileNode {
/** File Path In MinIO */ /** File Path In MinIO */
_id: string; _id: string;
+9 -6
View File
@@ -1,9 +1,9 @@
import moment from 'moment-timezone'; import moment from 'moment-timezone';
import * as bus from '../service/bus'; import { Context } from '../context';
import db from '../service/db'; import db from '../service/db';
import { ArgMethod } from '../utils'; import { ArgMethod } from '../utils';
const coll = db.collection('blacklist'); let coll = db.collection('blacklist');
class BlackListModel { class BlackListModel {
@ArgMethod @ArgMethod
@@ -32,9 +32,12 @@ class BlackListModel {
} }
} }
bus.on('app/started', () => db.ensureIndexes( export function apply(ctx: Context) {
coll, coll = db.collection('blacklist');
{ key: { expireAt: -1 }, name: 'expire', expireAfterSeconds: 0 }, ctx.on('ready', () => db.ensureIndexes(
)); coll,
{ key: { expireAt: -1 }, name: 'expire', expireAfterSeconds: 0 },
));
}
export default BlackListModel; export default BlackListModel;
global.Hydro.model.blacklist = BlackListModel; global.Hydro.model.blacklist = BlackListModel;
+15 -11
View File
@@ -1,6 +1,7 @@
import { omit } from 'lodash'; import { omit } from 'lodash';
import moment from 'moment'; import moment from 'moment';
import { FilterQuery, ObjectID } from 'mongodb'; import { FilterQuery, ObjectID } from 'mongodb';
import { Context } from '../context';
import { DiscussionNodeNotFoundError, DocumentNotFoundError } from '../error'; import { DiscussionNodeNotFoundError, DocumentNotFoundError } from '../error';
import { DiscussionReplyDoc, DiscussionTailReplyDoc, Document } from '../interface'; import { DiscussionReplyDoc, DiscussionTailReplyDoc, Document } from '../interface';
import * as bus from '../service/bus'; import * as bus from '../service/bus';
@@ -289,24 +290,27 @@ async function updateSort() {
await document.coll.updateOne({ _id: data._id }, { $set: { sort, lastRCount: rCount } }); await document.coll.updateOne({ _id: data._id }, { $set: { sort, lastRCount: rCount } });
} }
} }
TaskModel.Worker.addHandler('discussion.sort', updateSort);
bus.on('app/ready', async () => { export async function apply(ctx: Context) {
if (!await TaskModel.count({ type: 'schedule', subType: 'discussion.sort' })) { ctx.using(['worker'], async ({ worker }) => {
await TaskModel.add({ worker.addHandler('discussion.sort', updateSort);
type: 'schedule', if (!await TaskModel.count({ type: 'schedule', subType: 'discussion.sort' })) {
subType: 'discussion.sort', await TaskModel.add({
executeAfter: moment().minute(0).second(0).millisecond(0).toDate(), type: 'schedule',
interval: [1, 'hour'], subType: 'discussion.sort',
}); executeAfter: moment().minute(0).second(0).millisecond(0).toDate(),
} interval: [1, 'hour'],
}); });
}
});
}
global.Hydro.model.DiscussionModel = { global.Hydro.model.DiscussionModel = {
typeDisplay, typeDisplay,
PROJECTION_LIST, PROJECTION_LIST,
PROJECTION_PUBLIC, PROJECTION_PUBLIC,
apply,
add, add,
get, get,
inc, inc,
+7 -6
View File
@@ -3,6 +3,7 @@ import assert from 'assert';
import { import {
Cursor, FilterQuery, ObjectID, OnlyFieldsOfType, UpdateQuery, Cursor, FilterQuery, ObjectID, OnlyFieldsOfType, UpdateQuery,
} from 'mongodb'; } from 'mongodb';
import { Context } from '../context';
import { import {
BlogDoc, Content, DiscussionDoc, BlogDoc, Content, DiscussionDoc,
DiscussionReplyDoc, ProblemDoc, ProblemStatusDoc, DiscussionReplyDoc, ProblemDoc, ProblemStatusDoc,
@@ -405,7 +406,11 @@ export async function revSetStatus<T extends keyof DocStatusType>(
return res.value; return res.value;
} }
bus.on('app/started', async () => { export async function apply(ctx: Context) {
ctx.on('domain/delete', (domainId) => Promise.all([
coll.deleteMany({ domainId }),
collStatus.deleteMany({ domainId }),
]));
await db.ensureIndexes( await db.ensureIndexes(
coll, coll,
{ key: { domainId: 1, docType: 1, docId: 1 }, name: 'basic', unique: true }, { key: { domainId: 1, docType: 1, docId: 1 }, name: 'basic', unique: true },
@@ -436,11 +441,7 @@ bus.on('app/started', async () => {
{ key: { domainId: 1, docType: 1, docId: 1, accept: -1, time: 1 }, name: 'contestRuleACM', sparse: true }, { key: { domainId: 1, docType: 1, docId: 1, accept: -1, time: 1 }, name: 'contestRuleACM', sparse: true },
{ key: { domainId: 1, docType: 1, uid: 1, enroll: 1, docId: 1 }, name: 'training', sparse: true }, { key: { domainId: 1, docType: 1, uid: 1, enroll: 1, docId: 1 }, name: 'training', sparse: true },
); );
}); }
bus.on('domain/delete', (domainId) => Promise.all([
coll.deleteMany({ domainId }),
collStatus.deleteMany({ domainId }),
]));
global.Hydro.model.document = { global.Hydro.model.document = {
coll, coll,
+6 -6
View File
@@ -274,16 +274,16 @@ class DomainModel {
} }
} }
bus.on('app/started', async () => { bus.on('ready', () => Promise.all([
await db.ensureIndexes( db.ensureIndexes(
coll, coll,
{ key: { lower: 1 }, name: 'lower', unique: true }, { key: { lower: 1 }, name: 'lower', unique: true },
); ),
await db.ensureIndexes( db.ensureIndexes(
collUser, collUser,
{ key: { domainId: 1, uid: 1 }, name: 'uid', unique: true }, { key: { domainId: 1, uid: 1 }, name: 'uid', unique: true },
{ key: { domainId: 1, rp: -1, uid: 1 }, name: 'rp', sparse: true }, { key: { domainId: 1, rp: -1, uid: 1 }, name: 'rp', sparse: true },
); ),
}); ]));
export default DomainModel; export default DomainModel;
global.Hydro.model.domain = DomainModel; global.Hydro.model.domain = DomainModel;
+7 -5
View File
@@ -77,10 +77,12 @@ class MessageModel {
} }
} }
bus.on('app/started', () => db.ensureIndexes( export async function apply() {
coll, return db.ensureIndexes(
{ key: { to: 1, _id: -1 }, name: 'to' }, coll,
{ key: { from: 1, _id: -1 }, name: 'from' }, { key: { to: 1, _id: -1 }, name: 'to' },
)); { key: { from: 1, _id: -1 }, name: 'from' },
);
}
export default MessageModel; export default MessageModel;
global.Hydro.model.message = MessageModel; global.Hydro.model.message = MessageModel;
+3 -3
View File
@@ -21,9 +21,9 @@ export async function inc(op: string, ident: string, periodSecs: number, maxOper
} }
} }
bus.on('app/started', () => db.ensureIndexes( export const apply = () => db.ensureIndexes(
coll, coll,
{ key: { expireAt: -1 }, name: 'expire', expireAfterSeconds: 0 }, { key: { expireAt: -1 }, name: 'expire', expireAfterSeconds: 0 },
{ key: { op: 1, ident: 1, expireAt: 1 }, name: 'unique', unique: true }, { key: { op: 1, ident: 1, expireAt: 1 }, name: 'unique', unique: true },
)); );
global.Hydro.model.opcount = { inc }; global.Hydro.model.opcount = { inc, apply };
+1 -2
View File
@@ -233,8 +233,7 @@ class RecordModel {
// Mark problem as deleted // Mark problem as deleted
bus.on('problem/delete', (domainId, docId) => RecordModel.coll.updateMany({ domainId, pid: docId }, { $set: { pid: -1 } })); bus.on('problem/delete', (domainId, docId) => RecordModel.coll.updateMany({ domainId, pid: docId }, { $set: { pid: -1 } }));
bus.on('domain/delete', (domainId) => RecordModel.coll.deleteMany({ domainId })); bus.on('domain/delete', (domainId) => RecordModel.coll.deleteMany({ domainId }));
bus.on('ready', () => db.ensureIndexes(
bus.on('app/started', () => db.ensureIndexes(
RecordModel.coll, RecordModel.coll,
{ key: { domainId: 1, contest: 1, _id: -1 }, name: 'basic' }, { key: { domainId: 1, contest: 1, _id: -1 }, name: 'basic' },
{ key: { domainId: 1, contest: 1, uid: 1, _id: -1 }, name: 'withUser' }, { key: { domainId: 1, contest: 1, uid: 1, _id: -1 }, name: 'withUser' },
+1 -1
View File
@@ -136,7 +136,7 @@ async function cleanFiles() {
} }
} }
TaskModel.Worker.addHandler('storage.prune', cleanFiles); TaskModel.Worker.addHandler('storage.prune', cleanFiles);
bus.on('app/started', async () => { bus.on('ready', async () => {
if (process.env.NODE_APP_INSTANCE !== '0') return; if (process.env.NODE_APP_INSTANCE !== '0') return;
await db.ensureIndexes( await db.ensureIndexes(
StorageModel.coll, StorageModel.coll,
+37 -24
View File
@@ -3,7 +3,8 @@ import moment from 'moment-timezone';
import { FilterQuery, ObjectID } from 'mongodb'; import { FilterQuery, ObjectID } from 'mongodb';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { sleep } from '@hydrooj/utils/lib/utils'; import { sleep } from '@hydrooj/utils/lib/utils';
import { BaseService, EventDoc, Task } from '../interface'; import { Context, Service } from '../context';
import { EventDoc, Task } from '../interface';
import { Logger } from '../logger'; import { Logger } from '../logger';
import * as bus from '../service/bus'; import * as bus from '../service/bus';
import db from '../service/db'; import db from '../service/db';
@@ -62,10 +63,8 @@ class Consumer {
} }
} }
class WorkerService implements BaseService { class WorkerService extends Service {
private handlers: Record<string, Function> = {}; private handlers: Record<string, Function> = {};
public started = true;
public start = () => { };
public consumer = new Consumer( public consumer = new Consumer(
{ type: 'schedule', subType: { $in: Object.keys(this.handlers) } }, { type: 'schedule', subType: { $in: Object.keys(this.handlers) } },
async (doc) => { async (doc) => {
@@ -89,10 +88,13 @@ class WorkerService implements BaseService {
public addHandler(type: string, handler: Function) { public addHandler(type: string, handler: Function) {
this.handlers[type] = handler; this.handlers[type] = handler;
this.consumer.filter = { type: 'schedule', subType: { $in: Object.keys(this.handlers) } }; this.consumer.filter = { type: 'schedule', subType: { $in: Object.keys(this.handlers) } };
this.caller?.on('dispose', () => {
delete this.handlers[type];
});
} }
} }
const Worker = new WorkerService(); const Worker = new WorkerService(app, 'worker', false);
class TaskModel { class TaskModel {
static async add(task: Partial<Task> & { type: string }) { static async add(task: Partial<Task> & { type: string }) {
@@ -141,17 +143,36 @@ class TaskModel {
} }
const id = process.env.exec_mode === 'cluster_mode' ? hostname() : nanoid(); const id = process.env.exec_mode === 'cluster_mode' ? hostname() : nanoid();
Worker.addHandler('task.daily', async () => {
await global.Hydro.model.record.coll.deleteMany({ contest: new ObjectID('000000000000000000000000') }); declare module '../context' {
await global.Hydro.script.rp?.run({}, new Logger('task/rp').debug); interface Context {
await global.Hydro.script.problemStat?.run({}, new Logger('task/problem').debug); worker: WorkerService;
if (global.Hydro.model.system.get('server.checkUpdate') && !(new Date().getDay() % 3)) {
await global.Hydro.script.checkUpdate?.run({}, new Logger('task/checkUpdate').debug);
} }
await bus.parallel('task/daily'); }
});
bus.on('domain/delete', (domainId) => coll.deleteMany({ domainId })); export async function apply(ctx: Context) {
bus.on('app/started', async () => { Context.service('worker', WorkerService);
ctx.worker = Worker;
Worker.addHandler('task.daily', async () => {
await global.Hydro.model.record.coll.deleteMany({ contest: new ObjectID('000000000000000000000000') });
await global.Hydro.script.rp?.run({}, new Logger('task/rp').debug);
await global.Hydro.script.problemStat?.run({}, new Logger('task/problem').debug);
if (global.Hydro.model.system.get('server.checkUpdate') && !(new Date().getDay() % 3)) {
await global.Hydro.script.checkUpdate?.run({}, new Logger('task/checkUpdate').debug);
}
await ctx.parallel('task/daily');
});
ctx.on('domain/delete', (domainId) => coll.deleteMany({ domainId }));
ctx.on('bus/broadcast', (event, payload) => {
collEvent.insertOne({
ack: [id],
event,
payload: JSON.stringify(payload),
expire: new Date(Date.now() + 10000),
});
});
if (process.env.NODE_APP_INSTANCE !== '0') return; if (process.env.NODE_APP_INSTANCE !== '0') return;
if (!await TaskModel.count({ type: 'schedule', subType: 'task.daily' })) { if (!await TaskModel.count({ type: 'schedule', subType: 'task.daily' })) {
await TaskModel.add({ await TaskModel.add({
@@ -187,15 +208,7 @@ bus.on('app/started', async () => {
await (res.value ? handleEvent(res.value) : sleep(500)); await (res.value ? handleEvent(res.value) : sleep(500));
} }
}); });
}); }
bus.on('bus/broadcast', (event, payload) => {
collEvent.insertOne({
ack: [id],
event,
payload: JSON.stringify(payload),
expire: new Date(Date.now() + 10000),
});
});
export default TaskModel; export default TaskModel;
global.Hydro.model.task = TaskModel; global.Hydro.model.task = TaskModel;
+1 -1
View File
@@ -95,7 +95,7 @@ class TokenModel {
} }
} }
bus.on('app/started', () => db.ensureIndexes( bus.on('ready', () => db.ensureIndexes(
TokenModel.coll, TokenModel.coll,
{ key: { uid: 1, tokenType: 1, updateAt: -1 }, name: 'basic', sparse: true }, { key: { uid: 1, tokenType: 1, updateAt: -1 }, name: 'basic', sparse: true },
{ key: { expireAt: -1 }, name: 'expire', expireAfterSeconds: 0 }, { key: { expireAt: -1 }, name: 'expire', expireAfterSeconds: 0 },
+1 -1
View File
@@ -403,7 +403,7 @@ class UserModel {
} }
} }
bus.on('app/started', () => Promise.all([ bus.on('ready', () => Promise.all([
db.ensureIndexes( db.ensureIndexes(
coll, coll,
{ key: { unameLower: 1 }, name: 'uname', unique: true }, { key: { unameLower: 1 }, name: 'uname', unique: true },
+2 -4
View File
@@ -2,7 +2,7 @@
import { import {
Collection, Db, IndexSpecification, MongoClient, Collection, Db, IndexSpecification, MongoClient,
} from 'mongodb'; } from 'mongodb';
import { BaseService, Collections } from '../interface'; import { Collections } from '../interface';
import { Logger } from '../logger'; import { Logger } from '../logger';
import options from '../options'; import options from '../options';
import * as bus from './bus'; import * as bus from './bus';
@@ -20,10 +20,9 @@ interface MongoConfig {
prefix?: string, prefix?: string,
} }
class MongoService implements BaseService { class MongoService {
public client: MongoClient; public client: MongoClient;
public db: Db; public db: Db;
public started = false;
private opts: MongoConfig; private opts: MongoConfig;
static buildUrl(opts: MongoConfig) { static buildUrl(opts: MongoConfig) {
@@ -46,7 +45,6 @@ class MongoService implements BaseService {
this.client = await MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: true }); this.client = await MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: true });
this.db = this.client.db(opts.name || 'hydro'); this.db = this.client.db(opts.name || 'hydro');
await bus.parallel('database/connect', this.db); await bus.parallel('database/connect', this.db);
this.started = true;
} }
public collection<K extends keyof Collections>(c: K): Collection<Collections[K]> { public collection<K extends keyof Collections>(c: K): Collection<Collections[K]> {
+1 -1
View File
@@ -91,7 +91,7 @@ export async function updateJudge(args) {
} }
if (process.env.NODE_APP_INSTANCE === '0') { if (process.env.NODE_APP_INSTANCE === '0') {
bus.on('app/started', async () => { bus.on('ready', async () => {
sysinfo.get().then((info) => { sysinfo.get().then((info) => {
coll.updateOne( coll.updateOne(
{ mid: info.mid, type: 'server' }, { mid: info.mid, type: 'server' },
+3 -1
View File
@@ -17,7 +17,9 @@ export function ArgMethod(target: any, funcName: string, obj: any) {
} }
export function unwrapExports(module: any) { export function unwrapExports(module: any) {
return module?.default || module; return (!module || typeof module !== 'object') ? module
: 'apply' in module && typeof module.apply === 'function' ? module
: 'default' in module ? module.default : module;
} }
export * from '@hydrooj/utils/lib/utils'; export * from '@hydrooj/utils/lib/utils';
+2 -2
View File
@@ -2,7 +2,7 @@
import os from 'os'; import os from 'os';
import { import {
Context, db, DomainModel, JudgeHandler, Logger, Context, db, DomainModel, JudgeHandler, Logger,
ProblemModel, sleep, STATUS, TaskModel, ProblemModel, sleep, STATUS, TaskModel, Time,
} from 'hydrooj'; } from 'hydrooj';
import { BasicProvider, IBasicProvider, RemoteAccount } from './interface'; import { BasicProvider, IBasicProvider, RemoteAccount } from './interface';
import { getDifficulty } from './providers/codeforces'; import { getDifficulty } from './providers/codeforces';
@@ -99,7 +99,7 @@ class Service {
async main() { async main() {
const res = await this.login(); const res = await this.login();
if (!res) return; if (!res) return;
setInterval(() => this.login(), 1 * 3600 * 1000); setInterval(() => this.login(), Time.hour);
TaskModel.consume({ type: 'remotejudge', subType: this.account.type }, this.judge.bind(this), false); TaskModel.consume({ type: 'remotejudge', subType: this.account.type }, this.judge.bind(this), false);
const ddocs = await DomainModel.getMulti({ mount: this.account.type }).toArray(); const ddocs = await DomainModel.getMulti({ mount: this.account.type }).toArray();
do { do {