mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core&ui: remove deprecated apis
This commit is contained in:
26 files changed
+90
-249
No files matched your search
@@ -145,8 +145,10 @@ Hydro 用户群:1085853538
|
|||||||
- core: 移除 global.Hydro.service
|
- core: 移除 global.Hydro.service
|
||||||
- core: 移除 global.Hydro.ui.template
|
- core: 移除 global.Hydro.ui.template
|
||||||
- core: 移除 global.Hydro.lib
|
- core: 移除 global.Hydro.lib
|
||||||
- core: 移除 String.prototype.translate (使用 app.i18n.translate)
|
- core: 移除 String.prototype.translate (使用 ctx.i18n.translate)
|
||||||
- core: 升级至 Mongo Driver 6
|
- core: 升级至 Mongo Driver 6
|
||||||
|
- core: 移除 registerResolver, registerValue, registerUnion (使用 ctx.api.resolver/...)
|
||||||
|
- core: 移除 paginate, rank (使用 ctx.db.paginate, ctx.db.ranked)
|
||||||
|
|
||||||
## Hydro 4.19.0 / UI 4.57.0
|
## Hydro 4.19.0 / UI 4.57.0
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -20,7 +20,7 @@ const compilerOptionsBase = {
|
|||||||
module: 'preserve',
|
module: 'preserve',
|
||||||
esModuleInterop: true,
|
esModuleInterop: true,
|
||||||
moduleResolution: 'bundler',
|
moduleResolution: 'bundler',
|
||||||
jsx: 'react',
|
jsx: 'react-jsx',
|
||||||
sourceMap: false,
|
sourceMap: false,
|
||||||
composite: true,
|
composite: true,
|
||||||
strictBindCallApply: true,
|
strictBindCallApply: true,
|
||||||
@@ -70,6 +70,10 @@ for (const name of ['plugins', 'modules']) {
|
|||||||
}
|
}
|
||||||
// Write an empty file to make eslint happy
|
// Write an empty file to make eslint happy
|
||||||
fs.writeFileSync(path.resolve(process.cwd(), name, 'nop.ts'), 'export default {};\n');
|
fs.writeFileSync(path.resolve(process.cwd(), name, 'nop.ts'), 'export default {};\n');
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.resolve(process.cwd(), name, 'vue.d.ts'),
|
||||||
|
'declare module "*.vue" {\n const content: any;\n export default content;\n}\n',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const modules = [
|
const modules = [
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "base.mjs",
|
"main": "base.mjs",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-react/eslint-plugin": "^1.38.2",
|
"@eslint-react/eslint-plugin": "^1.38.3",
|
||||||
"@eslint/compat": "^1.2.7",
|
"@eslint/compat": "^1.2.7",
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@stylistic/eslint-plugin": "^4.2.0",
|
"@stylistic/eslint-plugin": "^4.2.0",
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ export interface WebServiceConfig {
|
|||||||
enableSSE?: boolean;
|
enableSSE?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebService<C extends CordisContext = CordisContext> extends Service<C> {
|
export class WebService<C extends CordisContext = CordisContext> extends Service<never, C> {
|
||||||
private registry: Record<string, any> = Object.create(null);
|
private registry: Record<string, any> = Object.create(null);
|
||||||
private registrationCount = Counter();
|
private registrationCount = Counter();
|
||||||
private serverLayers = [];
|
private serverLayers = [];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
_, Context, DiscussionNotFoundError, DocumentModel, Filter,
|
_, Context, DiscussionNotFoundError, DocumentModel, Filter,
|
||||||
Handler, NumberKeys, ObjectId, OplogModel, paginate,
|
Handler, NumberKeys, ObjectId, OplogModel,
|
||||||
param, PRIV, Types, UserModel,
|
param, PRIV, Types, UserModel,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ class BlogUserHandler extends BlogHandler {
|
|||||||
@param('uid', Types.Int)
|
@param('uid', Types.Int)
|
||||||
@param('page', Types.PositiveInt, true)
|
@param('page', Types.PositiveInt, true)
|
||||||
async get(domainId: string, uid: number, page = 1) {
|
async get(domainId: string, uid: number, page = 1) {
|
||||||
const [ddocs, dpcount] = await paginate(
|
const [ddocs, dpcount] = await this.ctx.db.paginate(
|
||||||
BlogModel.getMulti({ owner: uid }),
|
BlogModel.getMulti({ owner: uid }),
|
||||||
page,
|
page,
|
||||||
10,
|
10,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
@include form-styles();
|
|
||||||
background-color: $input-background-color;
|
background-color: $input-background-color;
|
||||||
outline: $input-outline;
|
outline: $input-outline;
|
||||||
transition: outline-color .2s, border-color .2s;
|
transition: outline-color .2s, border-color .2s;
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
declare module '*.svg?react' {
|
||||||
|
import { ComponentType } from 'react';
|
||||||
|
const content: ComponentType;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
declare module '*.vue' {
|
||||||
|
const content: any;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
declare module '*.css' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
declare module '*.styl' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
declare module '*.scss' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hydrooj/common": "workspace:^",
|
"@hydrooj/common": "workspace:^",
|
||||||
"@hydrooj/utils": "workspace:^",
|
"@hydrooj/utils": "workspace:^",
|
||||||
"adm-zip": "0.5.14",
|
"adm-zip": "0.5.16",
|
||||||
"cac": "^6.7.14",
|
"cac": "^6.7.14",
|
||||||
"p-queue": "^8.1.0",
|
"p-queue": "^8.1.0",
|
||||||
"schemastery": "^3.16.1",
|
"schemastery": "^3.16.1",
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export default class VJ4 implements Session {
|
|||||||
if (entries.length > 512) throw new FormatError('Too many files');
|
if (entries.length > 512) throw new FormatError('Too many files');
|
||||||
if (Math.sum(entries.map((i) => i.header.size)) > 256 * 1024 * 1024) throw new FormatError('File too large');
|
if (Math.sum(entries.map((i) => i.header.size)) > 256 * 1024 * 1024) throw new FormatError('File too large');
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
zip.extractAllToAsync(savePath, true, (e) => {
|
zip.extractAllToAsync(savePath, true, false, (e) => {
|
||||||
if (e) reject(e);
|
if (e) reject(e);
|
||||||
else resolve(null);
|
else resolve(null);
|
||||||
});
|
});
|
||||||
@@ -183,7 +183,7 @@ export default class VJ4 implements Session {
|
|||||||
const entries = zip.getEntries();
|
const entries = zip.getEntries();
|
||||||
if (entries.length > 512) throw new FormatError('Too many files');
|
if (entries.length > 512) throw new FormatError('Too many files');
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
zip.extractAllToAsync(savePath, true, (e) => {
|
zip.extractAllToAsync(savePath, true, false, (e) => {
|
||||||
if (e) reject(e);
|
if (e) reject(e);
|
||||||
else resolve(null);
|
else resolve(null);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
"@aws-sdk/lib-storage": "3.726.1",
|
"@aws-sdk/lib-storage": "3.726.1",
|
||||||
"@aws-sdk/s3-presigned-post": "3.726.1",
|
"@aws-sdk/s3-presigned-post": "3.726.1",
|
||||||
"@aws-sdk/s3-request-presigner": "3.726.1",
|
"@aws-sdk/s3-request-presigner": "3.726.1",
|
||||||
"@cordisjs/core": "4.0.0-alpha.7",
|
"@cordisjs/core": "4.0.0-alpha.8",
|
||||||
"@cordisjs/logger": "1.0.0-alpha.4",
|
"@cordisjs/logger": "1.0.0-alpha.5",
|
||||||
"@cordisjs/plugin-timer": "1.0.0-alpha.0",
|
"@cordisjs/plugin-timer": "1.0.0-alpha.0",
|
||||||
"@graphql-tools/schema": "^10.0.23",
|
"@graphql-tools/schema": "^10.0.23",
|
||||||
"@hydrooj/common": "workspace:^",
|
"@hydrooj/common": "workspace:^",
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"@simplewebauthn/server": "13.1.1",
|
"@simplewebauthn/server": "13.1.1",
|
||||||
"@undefined-moe/require-resolve-hook": "^1.1.0",
|
"@undefined-moe/require-resolve-hook": "^1.1.0",
|
||||||
"@yarnpkg/libzip": "^3.1.1",
|
"@yarnpkg/libzip": "^3.1.1",
|
||||||
"adm-zip": "0.5.14",
|
"adm-zip": "0.5.16",
|
||||||
"cac": "^6.7.14",
|
"cac": "^6.7.14",
|
||||||
"csv-stringify": "^6.5.2",
|
"csv-stringify": "^6.5.2",
|
||||||
"emoji-regex": "^10.4.0",
|
"emoji-regex": "^10.4.0",
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
"koa-static-cache": "^5.1.4",
|
"koa-static-cache": "^5.1.4",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lru-cache": "^10.4.3",
|
"lru-cache": "^10.4.3",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^3.0.1",
|
||||||
"moment-timezone": "^0.5.48",
|
"moment-timezone": "^0.5.48",
|
||||||
"mongodb": "^6.15.0",
|
"mongodb": "^6.15.0",
|
||||||
"mongodb-uri": "^0.9.7",
|
"mongodb-uri": "^0.9.7",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export interface Context extends cordis.Context {
|
|||||||
geoip?: GeoIP;
|
geoip?: GeoIP;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class Service extends cordis.Service<Context> {
|
export abstract class Service<T = never> extends cordis.Service<T, Context> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const T = <F extends (...args: any[]) => any>(origFunc: F, disposeFunc?) =>
|
const T = <F extends (...args: any[]) => any>(origFunc: F, disposeFunc?) =>
|
||||||
@@ -50,7 +50,7 @@ const T = <F extends (...args: any[]) => any>(origFunc: F, disposeFunc?) =>
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ApiMixin extends cordis.Service<Context> {
|
export class ApiMixin extends cordis.Service<never, Context> {
|
||||||
addScript = T(addScript);
|
addScript = T(addScript);
|
||||||
setImmediate = T(setImmediate, clearImmediate);
|
setImmediate = T(setImmediate, clearImmediate);
|
||||||
provideModule = T(provideModule);
|
provideModule = T(provideModule);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import sanitize from 'sanitize-filename';
|
|||||||
import { JudgeResultBody, ProblemConfigFile, TestCase } from '@hydrooj/common';
|
import { JudgeResultBody, ProblemConfigFile, TestCase } from '@hydrooj/common';
|
||||||
import { Context } from '../context';
|
import { Context } from '../context';
|
||||||
import {
|
import {
|
||||||
FileLimitExceededError, ForbiddenError, ProblemIsReferencedError, ValidationError,
|
BadRequestError, FileLimitExceededError, ForbiddenError, ProblemIsReferencedError, ValidationError,
|
||||||
} from '../error';
|
} from '../error';
|
||||||
import { RecordDoc, Task } from '../interface';
|
import { RecordDoc, Task } from '../interface';
|
||||||
import { Logger } from '../logger';
|
import { Logger } from '../logger';
|
||||||
@@ -102,40 +102,6 @@ export async function postJudge(rdoc: RecordDoc) {
|
|||||||
await bus.parallel('record/judge', rdoc, updated, pdoc);
|
await bus.parallel('record/judge', rdoc, updated, pdoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
bus.on('record/judge', async (rdoc, updated, pdoc) => {
|
|
||||||
if (!pdoc || rdoc.status !== STATUS.STATUS_HACK_SUCCESSFUL) return;
|
|
||||||
if (rdoc.contest) return;
|
|
||||||
try {
|
|
||||||
const config = yaml.load(pdoc.config as string) as ProblemConfigFile;
|
|
||||||
assert(config.subtasks instanceof Array);
|
|
||||||
const file = await storage.get(`submission/${rdoc.files.hack.split('#')[0]}`);
|
|
||||||
assert(file);
|
|
||||||
const hackSubtask = config.subtasks[config.subtasks.length - 1];
|
|
||||||
hackSubtask.cases ||= [];
|
|
||||||
const input = `hack-${rdoc._id}-${hackSubtask.cases.length + 1}.in`;
|
|
||||||
hackSubtask.cases.push({ input, output: '/dev/null' });
|
|
||||||
await Promise.all([
|
|
||||||
problem.addTestdata(rdoc.domainId, rdoc.pid, input, file),
|
|
||||||
problem.addTestdata(rdoc.domainId, rdoc.pid, 'config.yaml', Buffer.from(yaml.dump(config))),
|
|
||||||
]);
|
|
||||||
// trigger rejudge
|
|
||||||
const rdocs = await record.getMulti(rdoc.domainId, {
|
|
||||||
pid: rdoc.pid,
|
|
||||||
status: STATUS.STATUS_ACCEPTED,
|
|
||||||
contest: { $nin: [record.RECORD_GENERATE, record.RECORD_PRETEST] },
|
|
||||||
}).project({ _id: 1, contest: 1 }).toArray();
|
|
||||||
const priority = await record.submissionPriority(rdoc.uid, -5000 - rdocs.length * 5 - 50);
|
|
||||||
await record.judge(rdoc.domainId, rdocs.map((r) => r._id), priority, {}, { hackRejudge: input });
|
|
||||||
} catch (e) {
|
|
||||||
next({
|
|
||||||
rid: rdoc._id.toString(),
|
|
||||||
domainId: rdoc.domainId,
|
|
||||||
key: 'next',
|
|
||||||
message: { message: 'Unable to apply hack: {0}', params: [e.message] },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function end(body: Partial<JudgeResultBody>) {
|
export async function end(body: Partial<JudgeResultBody>) {
|
||||||
const { $set, $push } = processPayload(body);
|
const { $set, $push } = processPayload(body);
|
||||||
const $unset: any = { progress: '' };
|
const $unset: any = { progress: '' };
|
||||||
@@ -219,7 +185,6 @@ export class JudgeConnectionHandler extends ConnectionHandler {
|
|||||||
query: any = { type: { $in: ['judge', 'generate'] } };
|
query: any = { type: { $in: ['judge', 'generate'] } };
|
||||||
concurrency = 1;
|
concurrency = 1;
|
||||||
consumer: Consumer = null;
|
consumer: Consumer = null;
|
||||||
startTimeout: NodeJS.Timeout = null;
|
|
||||||
tasks: Record<string, {
|
tasks: Record<string, {
|
||||||
resolve: (_: any) => void,
|
resolve: (_: any) => void,
|
||||||
queue: PQueue,
|
queue: PQueue,
|
||||||
@@ -230,10 +195,6 @@ export class JudgeConnectionHandler extends ConnectionHandler {
|
|||||||
async prepare() {
|
async prepare() {
|
||||||
logger.info('Judge daemon connected from ', this.request.ip);
|
logger.info('Judge daemon connected from ', this.request.ip);
|
||||||
this.sendLanguageConfig();
|
this.sendLanguageConfig();
|
||||||
// TODO deprecated, just for compatibility
|
|
||||||
this.startTimeout = setTimeout(() => {
|
|
||||||
this.consumer ||= task.consume(this.query, this.newTask.bind(this), true, this.concurrency);
|
|
||||||
}, 15000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@subscribe('system/setting')
|
@subscribe('system/setting')
|
||||||
@@ -274,10 +235,6 @@ export class JudgeConnectionHandler extends ConnectionHandler {
|
|||||||
}
|
}
|
||||||
} else if (msg.key === 'status') {
|
} else if (msg.key === 'status') {
|
||||||
await updateJudge(msg.info);
|
await updateJudge(msg.info);
|
||||||
} else if (msg.key === 'prio' && typeof msg.prio === 'number') {
|
|
||||||
// TODO deprecated, use config instead
|
|
||||||
this.query.priority = { $gt: msg.prio };
|
|
||||||
this.consumer?.setQuery(this.query);
|
|
||||||
} else if (msg.key === 'config') {
|
} else if (msg.key === 'config') {
|
||||||
if (Number.isSafeInteger(msg.prio)) {
|
if (Number.isSafeInteger(msg.prio)) {
|
||||||
this.query.priority = { $gt: msg.prio };
|
this.query.priority = { $gt: msg.prio };
|
||||||
@@ -296,14 +253,13 @@ export class JudgeConnectionHandler extends ConnectionHandler {
|
|||||||
this.consumer?.setQuery(this.query);
|
this.consumer?.setQuery(this.query);
|
||||||
}
|
}
|
||||||
} else if (msg.key === 'start') {
|
} else if (msg.key === 'start') {
|
||||||
clearTimeout(this.startTimeout);
|
if (this.consumer) throw new BadRequestError('Judge daemon already started');
|
||||||
this.consumer ||= task.consume(this.query, this.newTask.bind(this), true, this.concurrency);
|
this.consumer = task.consume(this.query, this.newTask.bind(this), true, this.concurrency);
|
||||||
logger.info('Judge daemon started');
|
logger.info('Judge daemon started');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async cleanup() {
|
async cleanup() {
|
||||||
clearTimeout(this.startTimeout);
|
|
||||||
this.consumer?.destroy();
|
this.consumer?.destroy();
|
||||||
logger.info('Judge daemon disconnected from ', this.request.ip);
|
logger.info('Judge daemon disconnected from ', this.request.ip);
|
||||||
await Promise.all(Object.values(this.tasks).map(async ({ t }) => {
|
await Promise.all(Object.values(this.tasks).map(async ({ t }) => {
|
||||||
@@ -318,6 +274,39 @@ export async function apply(ctx: Context) {
|
|||||||
ctx.Route('judge_files_download', '/judge/files', JudgeFilesDownloadHandler, builtin.PRIV.PRIV_JUDGE);
|
ctx.Route('judge_files_download', '/judge/files', JudgeFilesDownloadHandler, builtin.PRIV.PRIV_JUDGE);
|
||||||
ctx.Route('judge_files_upload', '/judge/upload', JudgeFileUpdateHandler, builtin.PRIV.PRIV_JUDGE);
|
ctx.Route('judge_files_upload', '/judge/upload', JudgeFileUpdateHandler, builtin.PRIV.PRIV_JUDGE);
|
||||||
ctx.Connection('judge_conn', '/judge/conn', JudgeConnectionHandler, builtin.PRIV.PRIV_JUDGE);
|
ctx.Connection('judge_conn', '/judge/conn', JudgeConnectionHandler, builtin.PRIV.PRIV_JUDGE);
|
||||||
|
ctx.on('record/judge', async (rdoc, updated, pdoc) => {
|
||||||
|
if (!pdoc || rdoc.status !== STATUS.STATUS_HACK_SUCCESSFUL) return;
|
||||||
|
if (rdoc.contest) return;
|
||||||
|
try {
|
||||||
|
const config = yaml.load(pdoc.config as string) as ProblemConfigFile;
|
||||||
|
assert(Array.isArray(config.subtasks));
|
||||||
|
const file = await storage.get(`submission/${rdoc.files.hack.split('#')[0]}`);
|
||||||
|
assert(file);
|
||||||
|
const hackSubtask = config.subtasks[config.subtasks.length - 1];
|
||||||
|
hackSubtask.cases ||= [];
|
||||||
|
const input = `hack-${rdoc._id}-${hackSubtask.cases.length + 1}.in`;
|
||||||
|
hackSubtask.cases.push({ input, output: '/dev/null' });
|
||||||
|
await Promise.all([
|
||||||
|
problem.addTestdata(rdoc.domainId, rdoc.pid, input, file),
|
||||||
|
problem.addTestdata(rdoc.domainId, rdoc.pid, 'config.yaml', Buffer.from(yaml.dump(config))),
|
||||||
|
]);
|
||||||
|
// trigger rejudge
|
||||||
|
const rdocs = await record.getMulti(rdoc.domainId, {
|
||||||
|
pid: rdoc.pid,
|
||||||
|
status: STATUS.STATUS_ACCEPTED,
|
||||||
|
contest: { $nin: [record.RECORD_GENERATE, record.RECORD_PRETEST] },
|
||||||
|
}).project({ _id: 1, contest: 1 }).toArray();
|
||||||
|
const priority = await record.submissionPriority(rdoc.uid, -5000 - rdocs.length * 5 - 50);
|
||||||
|
await record.judge(rdoc.domainId, rdocs.map((r) => r._id), priority, {}, { hackRejudge: input });
|
||||||
|
} catch (e) {
|
||||||
|
next({
|
||||||
|
rid: rdoc._id.toString(),
|
||||||
|
domainId: rdoc.domainId,
|
||||||
|
key: 'next',
|
||||||
|
message: { message: 'Unable to apply hack: {0}', params: [e.message] },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
apply.next = next;
|
apply.next = next;
|
||||||
|
|||||||
@@ -42,14 +42,6 @@ export function inject(node: UIInjectableFields, name: string, args: Record<stri
|
|||||||
export function getNodes(name: UIInjectableFields) {
|
export function getNodes(name: UIInjectableFields) {
|
||||||
return nodes[name];
|
return nodes[name];
|
||||||
}
|
}
|
||||||
/** @deprecated */
|
|
||||||
export const Nav = (name, args, prefix, ...permPrivChecker) => {
|
|
||||||
inject('Nav', name, { ...args, prefix }, ...permPrivChecker);
|
|
||||||
};
|
|
||||||
/** @deprecated */
|
|
||||||
export const ProblemAdd = (name, args, icon = 'add', text = 'Create Problem') => {
|
|
||||||
inject('ProblemAdd', name, { ...args, icon, text });
|
|
||||||
};
|
|
||||||
|
|
||||||
inject('Nav', 'homepage', { prefix: 'homepage' });
|
inject('Nav', 'homepage', { prefix: 'homepage' });
|
||||||
inject('Nav', 'problem_main', { prefix: 'problem' }, PERM.PERM_VIEW_PROBLEM);
|
inject('Nav', 'problem_main', { prefix: 'problem' }, PERM.PERM_VIEW_PROBLEM);
|
||||||
@@ -84,4 +76,3 @@ inject('DomainManage', 'domain_group', { family: 'Access Control', icon: 'user'
|
|||||||
global.Hydro.ui.inject = inject;
|
global.Hydro.ui.inject = inject;
|
||||||
global.Hydro.ui.nodes = nodes as any;
|
global.Hydro.ui.nodes = nodes as any;
|
||||||
global.Hydro.ui.getNodes = getNodes;
|
global.Hydro.ui.getNodes = getNodes;
|
||||||
Object.assign(global.Hydro.ui, { ProblemAdd, Nav });
|
|
||||||
@@ -37,7 +37,6 @@ export const typeDisplay = {
|
|||||||
[document.TYPE_CONTEST]: 'contest',
|
[document.TYPE_CONTEST]: 'contest',
|
||||||
[document.TYPE_DISCUSSION_NODE]: 'node',
|
[document.TYPE_DISCUSSION_NODE]: 'node',
|
||||||
[document.TYPE_TRAINING]: 'training',
|
[document.TYPE_TRAINING]: 'training',
|
||||||
[document.TYPE_HOMEWORK]: 'homework',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const coll = db.collection('discussion.history');
|
export const coll = db.collection('discussion.history');
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ export const TYPE_DISCUSSION_REPLY = 22 as const;
|
|||||||
export const TYPE_CONTEST = 30 as const;
|
export const TYPE_CONTEST = 30 as const;
|
||||||
export const TYPE_CONTEST_CLARIFICATION = 31 as const;
|
export const TYPE_CONTEST_CLARIFICATION = 31 as const;
|
||||||
export const TYPE_TRAINING = 40 as const;
|
export const TYPE_TRAINING = 40 as const;
|
||||||
/** @deprecated use `TYPE_CONTEST` with rule `homework` instead. */
|
|
||||||
export const TYPE_HOMEWORK = 60 as const;
|
|
||||||
|
|
||||||
export interface DocType {
|
export interface DocType {
|
||||||
[TYPE_PROBLEM]: ProblemDoc;
|
[TYPE_PROBLEM]: ProblemDoc;
|
||||||
@@ -486,7 +484,6 @@ global.Hydro.model.document = {
|
|||||||
TYPE_DISCUSSION,
|
TYPE_DISCUSSION,
|
||||||
TYPE_DISCUSSION_NODE,
|
TYPE_DISCUSSION_NODE,
|
||||||
TYPE_DISCUSSION_REPLY,
|
TYPE_DISCUSSION_REPLY,
|
||||||
TYPE_HOMEWORK,
|
|
||||||
TYPE_PROBLEM,
|
TYPE_PROBLEM,
|
||||||
TYPE_PROBLEM_LIST,
|
TYPE_PROBLEM_LIST,
|
||||||
TYPE_PROBLEM_SOLUTION,
|
TYPE_PROBLEM_SOLUTION,
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ export { default as StorageModel } from './model/storage';
|
|||||||
export { default as TaskModel } from './model/task';
|
export { default as TaskModel } from './model/task';
|
||||||
export * from './model/builtin';
|
export * from './model/builtin';
|
||||||
export * as JudgeHandler from './handler/judge';
|
export * as JudgeHandler from './handler/judge';
|
||||||
export { registerResolver, registerValue, registerUnion } from './service/api';
|
|
||||||
export { postJudge } from './handler/judge';
|
export { postJudge } from './handler/judge';
|
||||||
export { Collections } from './service/db';
|
export { Collections } from './service/db';
|
||||||
export { Handler, ConnectionHandler, requireSudo } from './service/server';
|
export { Handler, ConnectionHandler, requireSudo } from './service/server';
|
||||||
@@ -46,10 +45,6 @@ export { default as rating } from './lib/rating';
|
|||||||
export { default as avatar } from './lib/avatar';
|
export { default as avatar } from './lib/avatar';
|
||||||
export { parseConfig as testdataConfig } from './lib/testdataConfig';
|
export { parseConfig as testdataConfig } from './lib/testdataConfig';
|
||||||
export { sendMail } from './lib/mail';
|
export { sendMail } from './lib/mail';
|
||||||
/** @deprecated use Handler.paginate instead */
|
|
||||||
export const paginate = db.paginate.bind(db);
|
|
||||||
/** @deprecated use db.ranked instead */
|
|
||||||
export const rank = db.ranked.bind(db);
|
|
||||||
export { UiContextBase } from './service/layers/base';
|
export { UiContextBase } from './service/layers/base';
|
||||||
export * from '@hydrooj/framework/decorators';
|
export * from '@hydrooj/framework/decorators';
|
||||||
export * from '@hydrooj/framework/validator';
|
export * from '@hydrooj/framework/validator';
|
||||||
@@ -57,5 +52,6 @@ export * as StorageService from './service/storage';
|
|||||||
export { EventMap } from './service/bus';
|
export { EventMap } from './service/bus';
|
||||||
export { db, pwsh };
|
export { db, pwsh };
|
||||||
|
|
||||||
// to load ctx.scoreboard
|
// to load services into to context
|
||||||
export { } from './handler/contest';
|
export { } from './handler/contest';
|
||||||
|
export { } from './service/api';
|
||||||
@@ -99,24 +99,6 @@ class ApiHandler extends Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated use ctx.api.value() instead */
|
|
||||||
export function registerValue(...args: any[]) {
|
|
||||||
// @ts-ignore
|
|
||||||
return app.api.value(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @deprecated use ctx.api.resolver() instead */
|
|
||||||
export function registerResolver(...args: any[]) {
|
|
||||||
// @ts-ignore
|
|
||||||
return app.api.resolver(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @deprecated use ctx.api.union() instead */
|
|
||||||
export function registerUnion(...args: any[]) {
|
|
||||||
// @ts-ignore
|
|
||||||
return app.api.resolver(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO support dispose
|
// TODO support dispose
|
||||||
class ApiService extends Service {
|
class ApiService extends Service {
|
||||||
constructor(ctx: Context) {
|
constructor(ctx: Context) {
|
||||||
@@ -212,7 +194,7 @@ class ApiService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const sideEffect = true;
|
export const sideEffect = true;
|
||||||
export const using = ['server'];
|
export const inject = ['server'];
|
||||||
|
|
||||||
export function apply(ctx: Context) {
|
export function apply(ctx: Context) {
|
||||||
ctx.plugin(ApiService);
|
ctx.plugin(ApiService);
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ import welcome from './welcome';
|
|||||||
|
|
||||||
const logger = new Logger('upgrade');
|
const logger = new Logger('upgrade');
|
||||||
const unsupportedUpgrade = async function _26_27() {
|
const unsupportedUpgrade = async function _26_27() {
|
||||||
throw new Error('This upgrade was no longer supported in hydrooj@4. \
|
throw new Error('This upgrade was no longer supported in hydrooj@5. \
|
||||||
Please use hydrooj@3 to perform these upgrades before upgrading to v4');
|
Please use hydrooj@4 to perform these upgrades before upgrading to v5');
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPrimary = { readPreference: 'primary' } as const;
|
const onPrimary = { readPreference: 'primary' } as const;
|
||||||
@@ -53,141 +53,7 @@ export const coreScripts: MigrationScript[] = [
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
// Init
|
// Init
|
||||||
...new Array(28).fill(unsupportedUpgrade), // oxlint-disable-line no-new-array
|
...new Array(47).fill(unsupportedUpgrade), // oxlint-disable-line no-new-array
|
||||||
async function _29_30() {
|
|
||||||
return await iterateAllDomain((ddoc) => RecordModel.coll.updateMany({ domainId: ddoc._id }, { $set: { pdomain: ddoc._id } }));
|
|
||||||
},
|
|
||||||
// Add send_message priv to user
|
|
||||||
async function _30_31() {
|
|
||||||
return await iterateAllUser((udoc) => user.setPriv(udoc._id, udoc.priv | PRIV.PRIV_SEND_MESSAGE));
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
// Write builtin users to database
|
|
||||||
async function _32_33() {
|
|
||||||
if (!await user.getById('system', 0)) {
|
|
||||||
await user.create('Guest@hydro.local', 'Guest', String.random(32), 0, '127.0.0.1', PRIV.PRIV_REGISTER_USER);
|
|
||||||
}
|
|
||||||
if (!await user.getById('system', 1)) {
|
|
||||||
await user.create('Hydro@hydro.local', 'Hydro', String.random(32), 1, '127.0.0.1', PRIV.PRIV_USER_PROFILE);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
async function _33_34() {
|
|
||||||
return await iterateAllProblem(['content'], async (pdoc) => {
|
|
||||||
if (typeof pdoc.content !== 'string') return;
|
|
||||||
await problem.edit(pdoc.domainId, pdoc.docId, { content: pdoc.content.replace(/%file%:\/\//g, 'file://') });
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async function _34_35() {
|
|
||||||
return await iterateAllDomain((ddoc) => domain.edit(ddoc._id, { lower: ddoc._id.toLowerCase() }));
|
|
||||||
},
|
|
||||||
async function _35_36() {
|
|
||||||
await RecordModel.coll.updateMany({}, { $unset: { effective: '' } });
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
async function _36_37() {
|
|
||||||
for await (const doc of document.collStatus.find()) {
|
|
||||||
await document.collStatus.deleteMany({
|
|
||||||
...pick(doc, ['docId', 'domainId', 'uid', 'docType']),
|
|
||||||
_id: { $gt: doc._id },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
async function _37_38() {
|
|
||||||
return await iterateAllProblem(['docId', 'domainId', 'config'], async (pdoc) => {
|
|
||||||
logger.info('%s/%s', pdoc.domainId, pdoc.docId);
|
|
||||||
if (typeof pdoc.config !== 'string') return;
|
|
||||||
if (!pdoc.config.includes('type: subjective')) return;
|
|
||||||
await problem.addTestdata(
|
|
||||||
pdoc.domainId, pdoc.docId, 'config.yaml',
|
|
||||||
Buffer.from(pdoc.config.replace('type: subjective', 'type: objective')),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async function _38_39() {
|
|
||||||
return await iterateAllDomain(async (ddoc) => {
|
|
||||||
ddoc.roles.root = '-1';
|
|
||||||
await domain.setRoles(ddoc._id, ddoc.roles);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async function _39_40() {
|
|
||||||
return await iterateAllDomain(async ({ _id }) => {
|
|
||||||
const ddocs = await discussion.getMulti(_id, { parentType: document.TYPE_PROBLEM }).toArray();
|
|
||||||
for (const ddoc of ddocs) {
|
|
||||||
const pdoc = await problem.get(_id, ddoc.parentId as any);
|
|
||||||
await document.set(_id, document.TYPE_DISCUSSION, ddoc.docId, { parentId: pdoc.docId });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async function _40_41() {
|
|
||||||
// Ignore drop index failure
|
|
||||||
await db.collection('storage').dropIndex('path_1').catch(() => { });
|
|
||||||
await db.collection('storage').updateMany(
|
|
||||||
{ autoDelete: { $gte: moment().add(5, 'days').toDate() } },
|
|
||||||
{ $unset: { autoDelete: '' } },
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
async function _41_42() {
|
|
||||||
return await iterateAllDomain(async (ddoc) => {
|
|
||||||
const cursor = discussion.getMulti(ddoc._id, { parentType: document.TYPE_CONTEST });
|
|
||||||
while (await cursor.hasNext()) {
|
|
||||||
const doc = await cursor.next();
|
|
||||||
const tdoc = await document.coll.findOne({ docType: doc.parentType, docId: doc.parentId });
|
|
||||||
if (!tdoc) await discussion.del(ddoc._id, doc.docId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
async function _43_44() {
|
|
||||||
const processer = (i) => {
|
|
||||||
i.status = i.accept ? STATUS.STATUS_ACCEPTED : STATUS.STATUS_WRONG_ANSWER;
|
|
||||||
return i;
|
|
||||||
};
|
|
||||||
return await iterateAllDomain(async (ddoc) => {
|
|
||||||
const tdocs = await contest.getMulti(ddoc._id, { rule: { $ne: 'acm' } }).toArray();
|
|
||||||
for (const tdoc of tdocs) {
|
|
||||||
const tsdocs = await contest.getMultiStatus(ddoc._id, { docId: tdoc.docId }).toArray();
|
|
||||||
for (const tsdoc of tsdocs) {
|
|
||||||
const $set: any = {};
|
|
||||||
if (tsdoc.journal?.length) $set.journal = tsdoc.journal.map(processer);
|
|
||||||
if (tsdoc.detail?.length) $set.detail = tsdoc.detail.map(processer);
|
|
||||||
if (Object.keys($set).length) {
|
|
||||||
await contest.setStatus(ddoc._id, tdoc.docId, tsdoc.uid, $set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
async function _44_45() {
|
|
||||||
return await iterateAllUser((udoc) => user.setById(udoc._id, { ip: [udoc.regip] }, { regip: '' }));
|
|
||||||
},
|
|
||||||
async function _45_46() {
|
|
||||||
return await iterateAllDomain(async (ddoc) => {
|
|
||||||
const ddocs = await discussion.getMulti(ddoc._id, {
|
|
||||||
docType: document.TYPE_DISCUSSION,
|
|
||||||
parentType: { $in: [document.TYPE_CONTEST, 60, document.TYPE_TRAINING] },
|
|
||||||
parentId: { $type: 'string' },
|
|
||||||
}).toArray();
|
|
||||||
for (const doc of ddocs) {
|
|
||||||
if (ObjectId.isValid(doc.parentId)) {
|
|
||||||
await document.set(ddoc._id, document.TYPE_DISCUSSION, doc.docId, { parentId: new ObjectId(doc.parentId) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
async function _47_48() {
|
|
||||||
await document.coll.updateMany({ docType: document.TYPE_HOMEWORK }, { $set: { docType: document.TYPE_CONTEST } });
|
|
||||||
await document.collStatus.updateMany({ docType: document.TYPE_HOMEWORK }, { $set: { docType: document.TYPE_CONTEST } });
|
|
||||||
await RecordModel.coll.deleteMany({ 'contest.tid': { $ne: null }, hidden: true });
|
|
||||||
await RecordModel.coll.updateMany({}, { $unset: { hidden: '' } });
|
|
||||||
await RecordModel.coll.updateMany({ 'contest.tid': { $exists: true } }, { $rename: { 'contest.tid': 'contest1' } });
|
|
||||||
await RecordModel.coll.updateMany({ contest1: { $exists: true } }, { $rename: { contest1: 'contest' } });
|
|
||||||
await RecordModel.coll.updateMany({ contest: null }, { $unset: { contest: '' } });
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
async function _48_49() {
|
async function _48_49() {
|
||||||
await RecordModel.coll.updateMany({ input: { $exists: true } }, { $set: { contest: new ObjectId('000000000000000000000000') } });
|
await RecordModel.coll.updateMany({ input: { $exists: true } }, { $set: { contest: new ObjectId('000000000000000000000000') } });
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class ImportHojHandler extends Handler {
|
|||||||
}
|
}
|
||||||
const tmp = path.resolve(tmpdir, String.random(32));
|
const tmp = path.resolve(tmpdir, String.random(32));
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
zip.extractAllToAsync(tmp, true, (err) => (err ? reject(err) : resolve(null)));
|
zip.extractAllToAsync(tmp, true, false, (err) => (err ? reject(err) : resolve(null)));
|
||||||
});
|
});
|
||||||
let cnt = 0;
|
let cnt = 0;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class ImportQduojHandler extends Handler {
|
|||||||
}
|
}
|
||||||
const tmp = path.resolve(tmpdir, String.random(32));
|
const tmp = path.resolve(tmpdir, String.random(32));
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
zip.extractAllToAsync(tmp, true, (err) => (err ? reject(err) : resolve(null)));
|
zip.extractAllToAsync(tmp, true, false, (err) => (err ? reject(err) : resolve(null)));
|
||||||
});
|
});
|
||||||
let cnt = 0;
|
let cnt = 0;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { CustomSelectAutoComplete as CustomSelectAutoCompleteFC } from '@hydrooj/components';
|
import { CustomSelectAutoComplete as CustomSelectAutoCompleteFC } from '@hydrooj/components';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
|
||||||
import AutoComplete, { AutoCompleteOptions } from '.';
|
import AutoComplete, { AutoCompleteOptions } from '.';
|
||||||
|
|
||||||
interface CustomSelectOptions {
|
interface CustomSelectOptions {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
|
||||||
import AutoComplete from '.';
|
import AutoComplete from '.';
|
||||||
import DomainSelectAutoCompleteFC from './components/DomainSelectAutoComplete';
|
import DomainSelectAutoCompleteFC from './components/DomainSelectAutoComplete';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
|
||||||
import AutoComplete, { AutoCompleteOptions } from '.';
|
import AutoComplete, { AutoCompleteOptions } from '.';
|
||||||
import FileSelectAutoCompleteFC from './components/FileSelectAutoComplete';
|
import FileSelectAutoCompleteFC from './components/FileSelectAutoComplete';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
|
||||||
import AutoComplete from '.';
|
import AutoComplete from '.';
|
||||||
import ProblemSelectAutoCompleteFC from './components/ProblemSelectAutoComplete';
|
import ProblemSelectAutoCompleteFC from './components/ProblemSelectAutoComplete';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
|
||||||
import AutoComplete, { AutoCompleteOptions } from '.';
|
import AutoComplete, { AutoCompleteOptions } from '.';
|
||||||
import UserSelectAutoCompleteFC from './components/UserSelectAutoComplete';
|
import UserSelectAutoCompleteFC from './components/UserSelectAutoComplete';
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@blueprintjs/core": "^5.17.6",
|
"@blueprintjs/core": "^5.17.6",
|
||||||
"@cordisjs/core": "4.0.0-alpha.7",
|
"@cordisjs/core": "4.0.0-alpha.8",
|
||||||
"@fontsource/dm-mono": "^5.2.5",
|
"@fontsource/dm-mono": "^5.2.5",
|
||||||
"@fontsource/fira-code": "^5.2.5",
|
"@fontsource/fira-code": "^5.2.5",
|
||||||
"@fontsource/inconsolata": "^5.2.5",
|
"@fontsource/inconsolata": "^5.2.5",
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
"@fontsource/ubuntu-mono": "^5.2.5",
|
"@fontsource/ubuntu-mono": "^5.2.5",
|
||||||
"@hydrooj/utils": "workspace:^",
|
"@hydrooj/utils": "workspace:^",
|
||||||
"@sentry/browser": "^9.10.0",
|
"@sentry/browser": "^9.10.0",
|
||||||
"@sentry/webpack-plugin": "^3.2.3",
|
"@sentry/webpack-plugin": "^3.2.4",
|
||||||
"@simplewebauthn/browser": "13.1.0",
|
"@simplewebauthn/browser": "13.1.0",
|
||||||
"@svgr/webpack": "^8.1.0",
|
"@svgr/webpack": "^8.1.0",
|
||||||
"@types/html-to-text": "^9.0.4",
|
"@types/html-to-text": "^9.0.4",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"@types/nunjucks": "^3.2.6",
|
"@types/nunjucks": "^3.2.6",
|
||||||
"@types/pickadate": "^3.5.35",
|
"@types/pickadate": "^3.5.35",
|
||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
"@types/react": "^18.3.19",
|
"@types/react": "^18.3.20",
|
||||||
"@types/react-dom": "^18.3.5",
|
"@types/react-dom": "^18.3.5",
|
||||||
"@types/redux-logger": "^3.0.13",
|
"@types/redux-logger": "^3.0.13",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
"copy-webpack-plugin": "^13.0.0",
|
"copy-webpack-plugin": "^13.0.0",
|
||||||
"core-js": "^3.41.0",
|
"core-js": "^3.41.0",
|
||||||
"core-js-compat": "^3.41.0",
|
"core-js-compat": "^3.41.0",
|
||||||
"css-loader": "^6.11.0",
|
"css-loader": "^7.1.2",
|
||||||
"diff": "^7.0.0",
|
"diff": "^7.0.0",
|
||||||
"diff-dom": "^5.1.4",
|
"diff-dom": "^5.1.4",
|
||||||
"echarts": "^5.6.0",
|
"echarts": "^5.6.0",
|
||||||
|
|||||||
Reference in new issue
Block a user