mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
Merge branch 'master' into events
This commit is contained in:
57 files changed
+381
-229
No files matched your search
@@ -32,7 +32,7 @@ Hydro 设计了一套模块化的插件系统,可以方便地扩展系统功
|
|||||||
使用插件系统,可以在修改功能后,仍然保证系统的可升级性。
|
使用插件系统,可以在修改功能后,仍然保证系统的可升级性。
|
||||||
Hydro 的所有历史版本均可平滑升级到最新版本。
|
Hydro 的所有历史版本均可平滑升级到最新版本。
|
||||||
|
|
||||||
插件使用和开发指南,请前往文档 [插件](https://docs.hydro.ac/plugins/) 和 [开发](https://docs.hydro.ac/dev/typescript/) 章节。
|
插件使用和开发指南,请前往文档 [插件](https://docs.hydro.ac/docs/Hydro/plugins) 和 [开发](https://docs.hydro.ac/docs/Hydro/dev/typescript) 章节。
|
||||||
|
|
||||||
### 跨平台兼容,数据一键备份/导入
|
### 跨平台兼容,数据一键备份/导入
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ Hydro 提供了单系统多空间支持,可以方便地为不同的班级/年
|
|||||||
### 粒度精细的权限系统,灵活调节
|
### 粒度精细的权限系统,灵活调节
|
||||||
|
|
||||||
Hydro 的权限可以按比赛/作业分配给对应的用户,也可以将用户分组(班级),按组分配权限。
|
Hydro 的权限可以按比赛/作业分配给对应的用户,也可以将用户分组(班级),按组分配权限。
|
||||||
有关权限节点,可以查看 [介绍](https://docs.hydro.ac/docs/) 下方截图。
|
有关权限节点,可以查看 [介绍](https://docs.hydro.ac/docs/Hydro) 下方截图。
|
||||||
|
|
||||||
### 规模化支持,上千用户无压力,伸缩组秒级自动扩展
|
### 规模化支持,上千用户无压力,伸缩组秒级自动扩展
|
||||||
|
|
||||||
@@ -111,7 +111,9 @@ Hydro 用户群:1085853538
|
|||||||
<details>
|
<details>
|
||||||
<summary><h2>更新日志(点击展开)</h2></summary>
|
<summary><h2>更新日志(点击展开)</h2></summary>
|
||||||
|
|
||||||
## Hydro 5.0.0-beta.0 / UI 4.58.0-beta.0
|
## Hydro 5.0.0-beta.3 / UI 4.58.0-beta.3
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
- judge: 添加 checker 编译缓存
|
- judge: 添加 checker 编译缓存
|
||||||
- ui: 优化题目详情页面 OGP 信息
|
- ui: 优化题目详情页面 OGP 信息
|
||||||
@@ -127,21 +129,32 @@ Hydro 用户群:1085853538
|
|||||||
- core: 分离 HMR 和 Watcher 组件
|
- core: 分离 HMR 和 Watcher 组件
|
||||||
- core: i18n: 添加 interface 选项
|
- core: i18n: 添加 interface 选项
|
||||||
- judge: 添加 kattis checker 支持
|
- judge: 添加 kattis checker 支持
|
||||||
- migrate: 修复 hustoj 自动运行
|
|
||||||
- core: Settings: 支持使用 Schemastery
|
- core: Settings: 支持使用 Schemastery
|
||||||
- ui: 更新系统设置页面样式
|
- ui: 更新系统设置页面样式
|
||||||
- import-qduoj: 修复 spj=null
|
|
||||||
- core: 修复文件复制
|
|
||||||
- core: 支持存储并显示提交记录重测历史
|
- core: 支持存储并显示提交记录重测历史
|
||||||
- core: 新的加域逻辑
|
- core: 新的加域逻辑
|
||||||
- ui: UserSelect.Multi: 支持批量粘贴用户名
|
- ui: UserSelect.Multi: 支持批量粘贴用户名
|
||||||
- ui: 修复在线 IDE 右键菜单
|
|
||||||
- ui: 修复未登录用户查看题目文件页
|
|
||||||
- core: oauth: 支持绑定/解绑三方平台账户 (#971)
|
- core: oauth: 支持绑定/解绑三方平台账户 (#971)
|
||||||
- ui: 修复暗色模式表格边框 (#968)
|
|
||||||
- core: 优化 icpc 题目包导入 (#966)
|
- core: 优化 icpc 题目包导入 (#966)
|
||||||
- judge: 弃用 diff (#965)
|
- judge: 弃用 diff (#965)
|
||||||
- core: 支持设置加域时自动加入小组
|
- core: 支持设置加域时自动加入小组
|
||||||
|
- core: 添加 `--disable-worker` 启动选项
|
||||||
|
- ui: 支持使用自定义字体
|
||||||
|
- framework: 添加 Subscription API
|
||||||
|
- core: 添加 fixStorage 脚本
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- migrate: 修复 hustoj 自动运行
|
||||||
|
- import-qduoj: 修复 spj=null
|
||||||
|
- core: 修复文件复制
|
||||||
|
- ui: 修复在线 IDE 右键菜单
|
||||||
|
- ui: 修复未登录用户查看题目文件页
|
||||||
|
- ui: 修复暗色模式表格边框 (#968)
|
||||||
|
- ui: 修复题解回复编辑权限
|
||||||
|
- ui: 修复高亮行号显示
|
||||||
|
- core: 修复 `User.serialize()` 参数
|
||||||
|
- ui: 修复更换邮箱功能
|
||||||
|
|
||||||
### Breaking API Changes
|
### Breaking API Changes
|
||||||
|
|
||||||
|
|||||||
@@ -4,21 +4,21 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "base.mjs",
|
"main": "base.mjs",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-react/eslint-plugin": "^1.49.0",
|
"@eslint-react/eslint-plugin": "^1.51.2",
|
||||||
"@eslint/compat": "^1.2.9",
|
"@eslint/compat": "^1.2.9",
|
||||||
"@eslint/eslintrc": "^3.3.1",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@stylistic/eslint-plugin": "^4.2.0",
|
"@stylistic/eslint-plugin": "^4.4.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.32.0",
|
"@typescript-eslint/eslint-plugin": "^8.33.1",
|
||||||
"@typescript-eslint/parser": "^8.32.0",
|
"@typescript-eslint/parser": "^8.33.1",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-plugin-de-morgan": "^1.2.1",
|
"eslint-plugin-de-morgan": "^1.3.0",
|
||||||
"eslint-plugin-github": "^6.0.0",
|
"eslint-plugin-github": "^6.0.0",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
"globals": "^16.1.0"
|
"globals": "^16.2.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"eslint": ">= 9"
|
"eslint": ">= 9"
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export class ApiService extends Service {
|
|||||||
await emitHook?.(`api/before/${callOrName}`, args);
|
await emitHook?.(`api/before/${callOrName}`, args);
|
||||||
}
|
}
|
||||||
let result = await func(context, args as any, sendPayload);
|
let result = await func(context, args as any, sendPayload);
|
||||||
if (result && 'next' in result) {
|
if (result && typeof result === 'object' && 'next' in result) {
|
||||||
const it = result as AsyncGenerator<any, any, never>;
|
const it = result as AsyncGenerator<any, any, never>;
|
||||||
while (true) {
|
while (true) {
|
||||||
const value = await it.next(); // eslint-disable-line no-await-in-loop
|
const value = await it.next(); // eslint-disable-line no-await-in-loop
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import { PassThrough } from 'stream';
|
import { PassThrough } from 'stream';
|
||||||
import type { Next } from 'koa';
|
import type { Next } from 'koa';
|
||||||
import { pick } from 'lodash';
|
|
||||||
import {
|
import {
|
||||||
HydroRequest, HydroResponse, KoaContext, serializer,
|
HydroRequest, HydroResponse, KoaContext, serializer,
|
||||||
} from '@hydrooj/framework';
|
} from '@hydrooj/framework';
|
||||||
import { errorMessage } from '@hydrooj/utils/lib/utils';
|
import { errorMessage } from '@hydrooj/utils/lib/utils';
|
||||||
import { SystemError, UserFacingError } from './error';
|
import { SystemError, UserFacingError } from './error';
|
||||||
|
|
||||||
|
const pick = <T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> => {
|
||||||
|
const result: Partial<Pick<T, K>> = {};
|
||||||
|
for (const key of keys) result[key] = obj[key];
|
||||||
|
return result as Pick<T, K>;
|
||||||
|
};
|
||||||
|
|
||||||
export default (logger, xff, xhost) => async (ctx: KoaContext, next: Next) => {
|
export default (logger, xff, xhost) => async (ctx: KoaContext, next: Next) => {
|
||||||
// Base Layer
|
// Base Layer
|
||||||
const request: HydroRequest = {
|
const request: HydroRequest = {
|
||||||
@@ -112,7 +117,7 @@ export default (logger, xff, xhost) => async (ctx: KoaContext, next: Next) => {
|
|||||||
ctx.response.status = 302;
|
ctx.response.status = 302;
|
||||||
ctx.redirect(response.redirect);
|
ctx.redirect(response.redirect);
|
||||||
} else if (response.body) {
|
} else if (response.body) {
|
||||||
ctx.body = response.body instanceof Blob ? Buffer.from(await response.body.arrayBuffer()) : response.body;
|
ctx.body = response.body;
|
||||||
ctx.response.status = response.status || 200;
|
ctx.response.status = response.status || 200;
|
||||||
ctx.response.type = response.type
|
ctx.response.type = response.type
|
||||||
|| (request.json
|
|| (request.json
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/framework",
|
"name": "@hydrooj/framework",
|
||||||
"version": "0.2.0-beta.2",
|
"version": "0.2.0-beta.3",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -10,15 +10,14 @@
|
|||||||
"@hydrooj/utils": "workspace:^",
|
"@hydrooj/utils": "workspace:^",
|
||||||
"emoji-regex": "^10.4.0",
|
"emoji-regex": "^10.4.0",
|
||||||
"fs-extra": "^11.3.0",
|
"fs-extra": "^11.3.0",
|
||||||
"koa": "^2.16.1",
|
"koa": "^3.0.0",
|
||||||
"koa-body": "^6.0.1",
|
"koa-body": "^6.0.1",
|
||||||
"koa-compress": "^5.1.1",
|
"koa-compress": "^5.1.1",
|
||||||
"koa-router": "^13.0.1",
|
"koa-router": "^13.0.1",
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"moment-timezone": "^0.5.48",
|
|
||||||
"path-to-regexp": "^8.2.0",
|
"path-to-regexp": "^8.2.0",
|
||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"saslprep": "^1.0.3",
|
"saslprep": "^1.0.3",
|
||||||
|
"schemastery": "^3.16.1",
|
||||||
"shorty.js": "^1.0.1",
|
"shorty.js": "^1.0.1",
|
||||||
"ws": "^8.18.2"
|
"ws": "^8.18.2"
|
||||||
},
|
},
|
||||||
@@ -27,7 +26,6 @@
|
|||||||
"@types/koa": "^2.15.0",
|
"@types/koa": "^2.15.0",
|
||||||
"@types/koa-compress": "^4.0.6",
|
"@types/koa-compress": "^4.0.6",
|
||||||
"@types/koa-router": "^7.4.8",
|
"@types/koa-router": "^7.4.8",
|
||||||
"@types/lodash": "^4.17.16",
|
|
||||||
"@types/saslprep": "^1.0.3",
|
"@types/saslprep": "^1.0.3",
|
||||||
"@types/ws": "^8.18.1"
|
"@types/ws": "^8.18.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import fs from 'fs-extra';
|
|||||||
import Koa from 'koa';
|
import Koa from 'koa';
|
||||||
import Body from 'koa-body';
|
import Body from 'koa-body';
|
||||||
import Compress from 'koa-compress';
|
import Compress from 'koa-compress';
|
||||||
|
import Schema from 'schemastery';
|
||||||
import { Shorty } from 'shorty.js';
|
import { Shorty } from 'shorty.js';
|
||||||
import { WebSocket, WebSocketServer } from 'ws';
|
import { WebSocket, WebSocketServer } from 'ws';
|
||||||
import {
|
import {
|
||||||
@@ -324,19 +325,19 @@ function executeMiddlewareStack(context: any, middlewares: { name: string, func:
|
|||||||
return dispatch(0);
|
return dispatch(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebServiceConfig {
|
|
||||||
keys: string[];
|
|
||||||
proxy: boolean;
|
|
||||||
cors?: string;
|
|
||||||
upload?: string;
|
|
||||||
port: number;
|
|
||||||
host?: string;
|
|
||||||
xff?: string;
|
|
||||||
xhost?: string;
|
|
||||||
enableSSE?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WebService<C extends CordisContext = CordisContext> extends Service<never, C> {
|
export class WebService<C extends CordisContext = CordisContext> extends Service<never, C> {
|
||||||
|
static Config = Schema.object({
|
||||||
|
keys: Schema.array(Schema.string()),
|
||||||
|
proxy: Schema.boolean(),
|
||||||
|
cors: Schema.string(),
|
||||||
|
upload: Schema.string(),
|
||||||
|
port: Schema.number(),
|
||||||
|
host: Schema.string(),
|
||||||
|
xff: Schema.string(),
|
||||||
|
xhost: Schema.string(),
|
||||||
|
enableSSE: Schema.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
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 = [];
|
||||||
@@ -352,7 +353,7 @@ export class WebService<C extends CordisContext = CordisContext> extends Service
|
|||||||
Handler = Handler;
|
Handler = Handler;
|
||||||
ConnectionHandler = ConnectionHandler;
|
ConnectionHandler = ConnectionHandler;
|
||||||
|
|
||||||
constructor(ctx: C, public config: WebServiceConfig) {
|
constructor(ctx: C, public config: ReturnType<typeof WebService.Config>) {
|
||||||
super(ctx, 'server');
|
super(ctx, 'server');
|
||||||
ctx.mixin('server', ['Route', 'Connection', 'withHandlerClass']);
|
ctx.mixin('server', ['Route', 'Connection', 'withHandlerClass']);
|
||||||
this.server.keys = this.config.keys;
|
this.server.keys = this.config.keys;
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import emojiRegex from 'emoji-regex';
|
import emojiRegex from 'emoji-regex';
|
||||||
import { isSafeInteger } from 'lodash';
|
|
||||||
import moment from 'moment-timezone';
|
|
||||||
import sanitize from 'sanitize-filename';
|
import sanitize from 'sanitize-filename';
|
||||||
import saslprep from 'saslprep';
|
import saslprep from 'saslprep';
|
||||||
import Schema from 'schemastery';
|
import Schema from 'schemastery';
|
||||||
@@ -107,9 +105,9 @@ export const Types = {
|
|||||||
ShortString: basicString(/^.{1,255}$/),
|
ShortString: basicString(/^.{1,255}$/),
|
||||||
String: basicString(),
|
String: basicString(),
|
||||||
|
|
||||||
Int: [(v) => +v, (v) => /^[+-]?[0-9]+$/.test(v.toString().trim()) && isSafeInteger(+v)],
|
Int: [(v) => +v, (v) => /^[+-]?[0-9]+$/.test(v.toString().trim()) && Number.isSafeInteger(+v)],
|
||||||
UnsignedInt: [(v) => +v, (v) => /^(-0|\+?[0-9]+)$/.test(v.toString().trim()) && isSafeInteger(+v)],
|
UnsignedInt: [(v) => +v, (v) => /^(-0|\+?[0-9]+)$/.test(v.toString().trim()) && Number.isSafeInteger(+v)],
|
||||||
PositiveInt: [(v) => +v, (v) => /^\+?[1-9][0-9]*$/.test(v.toString().trim()) && isSafeInteger(+v)],
|
PositiveInt: [(v) => +v, (v) => /^\+?[1-9][0-9]*$/.test(v.toString().trim()) && Number.isSafeInteger(+v)],
|
||||||
Float: [(v) => +v, (v) => Number.isFinite(+v)],
|
Float: [(v) => +v, (v) => Number.isFinite(+v)],
|
||||||
|
|
||||||
ObjectId: [() => { throw new Error('mongodb package not found'); }, () => true],
|
ObjectId: [() => { throw new Error('mongodb package not found'); }, () => true],
|
||||||
@@ -118,13 +116,15 @@ export const Types = {
|
|||||||
(v) => {
|
(v) => {
|
||||||
const d = v.split('-');
|
const d = v.split('-');
|
||||||
assert(d.length === 3);
|
assert(d.length === 3);
|
||||||
|
assert(d[0].length === 4);
|
||||||
return `${d[0]}-${d[1].length === 1 ? '0' : ''}${d[1]}-${d[2].length === 1 ? '0' : ''}${d[2]}`;
|
return `${d[0]}-${d[1].length === 1 ? '0' : ''}${d[1]}-${d[2].length === 1 ? '0' : ''}${d[2]}`;
|
||||||
},
|
},
|
||||||
(v) => {
|
(v) => {
|
||||||
const d = v.toString().split('-');
|
const d = v.toString().split('-');
|
||||||
if (d.length !== 3) return false;
|
if (d.length !== 3) return false;
|
||||||
|
if (d[0].length !== 4) return false;
|
||||||
const st = `${d[0]}-${d[1].length === 1 ? '0' : ''}${d[1]}-${d[2].length === 1 ? '0' : ''}${d[2]}`;
|
const st = `${d[0]}-${d[1].length === 1 ? '0' : ''}${d[1]}-${d[2].length === 1 ? '0' : ''}${d[2]}`;
|
||||||
return moment(st).isValid();
|
return Number.isFinite(new Date(st).getTime());
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
Time: [
|
Time: [
|
||||||
@@ -136,7 +136,8 @@ export const Types = {
|
|||||||
(v) => {
|
(v) => {
|
||||||
const t = v.toString().split(':');
|
const t = v.toString().split(':');
|
||||||
if (t.length !== 2) return false;
|
if (t.length !== 2) return false;
|
||||||
return moment(`2020-01-01 ${(t[0].length === 1 ? '0' : '') + t[0]}:${t[1].length === 1 ? '0' : ''}${t[1]}`).isValid();
|
const d = new Date(`2020-01-01 ${(t[0].length === 1 ? '0' : '') + t[0]}:${t[1].length === 1 ? '0' : ''}${t[1]}`);
|
||||||
|
return Number.isFinite(d.getTime());
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
Range: (range) => [
|
Range: (range) => [
|
||||||
|
|||||||
+29
-10
@@ -7,15 +7,21 @@ import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|||||||
import net from 'net';
|
import net from 'net';
|
||||||
import os, { cpus } from 'os';
|
import os, { cpus } from 'os';
|
||||||
import { createInterface } from 'readline/promises';
|
import { createInterface } from 'readline/promises';
|
||||||
import { supportsHyperlink } from 'supports-hyperlinks';
|
import supportsHyperlink from 'supports-hyperlinks';
|
||||||
|
|
||||||
const isSupported = supportsHyperlink(process.stdout);
|
const isSupported = supportsHyperlink.stdout;
|
||||||
const OSC = '\u001B]';
|
const OSC = '\u001B]';
|
||||||
const BEL = '\u0007';
|
const BEL = '\u0007';
|
||||||
const SEP = ';';
|
const SEP = ';';
|
||||||
export const link = isSupported ? (text: string, url: string) => [
|
export const link = isSupported ? (text: string, url: string) => [
|
||||||
OSC, '8', SEP, SEP, url, BEL, text, OSC, '8', SEP, SEP, BEL,
|
OSC, '8', SEP, SEP, url, BEL, text, OSC, '8', SEP, SEP, BEL,
|
||||||
].join('') : (text: string, url: string) => `${text} < ${url} > `;
|
].join('') : (text: string, url: string) => `${text} < ${url} > `;
|
||||||
|
const freemem = os.freemem();
|
||||||
|
const smallMemory = (freemem < 1024 * 1024 * 1024);
|
||||||
|
|
||||||
|
const nixInstall = (...packages: string[]) => (smallMemory
|
||||||
|
? packages.map((t) => `nix-env -iA ${t.includes('.') ? t : `nixpkgs.${t}`}`).join(' && ')
|
||||||
|
: `nix-env -iA ${packages.map((t) => (t.includes('.') ? t : `nixpkgs.${t}`)).join(' ')}`);
|
||||||
|
|
||||||
const warnings: [string, ...any[]][] = [];
|
const warnings: [string, ...any[]][] = [];
|
||||||
|
|
||||||
@@ -46,6 +52,7 @@ const locales = {
|
|||||||
'error.unsupportedArch': '不支持的架构 %s ,请尝试手动安装。',
|
'error.unsupportedArch': '不支持的架构 %s ,请尝试手动安装。',
|
||||||
'error.osreleaseNotFound': '无法获取系统版本信息(/etc/os-release 文件未找到),请尝试手动安装。',
|
'error.osreleaseNotFound': '无法获取系统版本信息(/etc/os-release 文件未找到),请尝试手动安装。',
|
||||||
'error.unsupportedOS': '不支持的操作系统 %s ,请尝试手动安装,',
|
'error.unsupportedOS': '不支持的操作系统 %s ,请尝试手动安装,',
|
||||||
|
'error.centos': 'CentOS 及其变种系统因系统内核过低,无法安装 Hydro,强烈建议使用其他系统。若确有需求,请升级 Linux 内核至 4.4+ 后再手动安装 Hydro。',
|
||||||
'install.preparing': '正在初始化安装...',
|
'install.preparing': '正在初始化安装...',
|
||||||
'install.mongodb': '正在安装 mongodb...',
|
'install.mongodb': '正在安装 mongodb...',
|
||||||
'install.createDatabaseUser': '正在创建数据库用户...',
|
'install.createDatabaseUser': '正在创建数据库用户...',
|
||||||
@@ -85,6 +92,8 @@ then restart and run the script again.`,
|
|||||||
'error.unsupportedArch': 'Unsupported architecture %s, please try to install manually.',
|
'error.unsupportedArch': 'Unsupported architecture %s, please try to install manually.',
|
||||||
'error.osreleaseNotFound': 'Unable to get system version information (/etc/os-release file not found), please try to install manually.',
|
'error.osreleaseNotFound': 'Unable to get system version information (/etc/os-release file not found), please try to install manually.',
|
||||||
'error.unsupportedOS': 'Unsupported operating system %s, please try to install manually.',
|
'error.unsupportedOS': 'Unsupported operating system %s, please try to install manually.',
|
||||||
|
'error.centos': `CentOS and its derivatives are not supported due to low system kernel versions.
|
||||||
|
It is strongly recommended to use other systems. If you really need it, please upgrade the Linux kernel to 4.4+ and manually install Hydro.`,
|
||||||
'install.preparing': 'Initializing installation...',
|
'install.preparing': 'Initializing installation...',
|
||||||
'install.mongodb': 'Installing mongodb...',
|
'install.mongodb': 'Installing mongodb...',
|
||||||
'install.createDatabaseUser': 'Creating database user...',
|
'install.createDatabaseUser': 'Creating database user...',
|
||||||
@@ -347,6 +356,18 @@ const Steps = () => [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async () => {
|
||||||
|
if (process.env.IGNORE_CENTOS) return;
|
||||||
|
const res = exec('yum -h');
|
||||||
|
if (res.code) return;
|
||||||
|
if (!process.argv.includes('--unsupported-centos')) {
|
||||||
|
log.warn('error.centos');
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
log.warn('warn.centos');
|
||||||
|
warnings.push(['warn.centos']);
|
||||||
|
}
|
||||||
|
},
|
||||||
async () => {
|
async () => {
|
||||||
if (!avx && !installAsJudge) {
|
if (!avx && !installAsJudge) {
|
||||||
log.warn('note.avx');
|
log.warn('note.avx');
|
||||||
@@ -397,9 +418,7 @@ ${nixConfBase}`);
|
|||||||
exec('nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs', { stdio: 'inherit' });
|
exec('nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs', { stdio: 'inherit' });
|
||||||
exec('nix-channel --update', { stdio: 'inherit' });
|
exec('nix-channel --update', { stdio: 'inherit' });
|
||||||
},
|
},
|
||||||
`nix-env -iA ${[
|
nixInstall('pm2', 'yarn', 'esbuild', 'bash', 'unzip', 'zip', 'diffutils', 'patch', 'screen', 'gawk'),
|
||||||
'pm2', 'yarn', 'esbuild', 'bash', 'unzip', 'zip', 'diffutils', 'patch', 'screen', 'gawk',
|
|
||||||
].map((i) => `nixpkgs.${i}`).join(' ')}`,
|
|
||||||
'yarn config set disable-self-update-check true',
|
'yarn config set disable-self-update-check true',
|
||||||
async () => {
|
async () => {
|
||||||
const rl = createInterface(process.stdin, process.stdout);
|
const rl = createInterface(process.stdin, process.stdout);
|
||||||
@@ -450,20 +469,20 @@ ${nixConfBase}`);
|
|||||||
"openssl-1.1.1z"
|
"openssl-1.1.1z"
|
||||||
];
|
];
|
||||||
}`),
|
}`),
|
||||||
`nix-env -iA hydro.mongodb${avx ? 7 : 4}${CN ? '-cn' : ''} nixpkgs.mongosh nixpkgs.mongodb-tools`,
|
nixInstall(`hydro.mongodb${avx ? 7 : 4}${CN ? '-cn' : ''}`, 'mongosh', 'mongodb-tools'),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
init: 'install.compiler',
|
init: 'install.compiler',
|
||||||
operations: [
|
operations: [
|
||||||
'nix-env -iA nixpkgs.gcc nixpkgs.python3',
|
nixInstall('gcc', 'python3'),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
init: 'install.sandbox',
|
init: 'install.sandbox',
|
||||||
skip: () => !exec('hydro-sandbox --help').code,
|
skip: () => !exec('hydro-sandbox --help').code,
|
||||||
operations: [
|
operations: [
|
||||||
'nix-env -iA nixpkgs.go-judge',
|
nixInstall('go-judge'),
|
||||||
'ln -sf $(which go-judge) /usr/local/bin/hydro-sandbox',
|
'ln -sf $(which go-judge) /usr/local/bin/hydro-sandbox',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -472,7 +491,7 @@ ${nixConfBase}`);
|
|||||||
skip: () => installAsJudge || noCaddy || existsSync(`${process.env.HOME}/.hydro/Caddyfile`),
|
skip: () => installAsJudge || noCaddy || existsSync(`${process.env.HOME}/.hydro/Caddyfile`),
|
||||||
hidden: installAsJudge,
|
hidden: installAsJudge,
|
||||||
operations: [
|
operations: [
|
||||||
'nix-env -iA nixpkgs.caddy',
|
nixInstall('caddy'),
|
||||||
() => writeFileSync(`${process.env.HOME}/.hydro/Caddyfile`, Caddyfile),
|
() => writeFileSync(`${process.env.HOME}/.hydro/Caddyfile`, Caddyfile),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -660,7 +679,7 @@ ${nixConfBase}`);
|
|||||||
{
|
{
|
||||||
init: 'install.postinstall',
|
init: 'install.postinstall',
|
||||||
operations: [
|
operations: [
|
||||||
'echo "layout=1" >/etc/HYDRO_INSTALLER',
|
'echo "layout=2" >/etc/HYDRO_INSTALLER',
|
||||||
'echo "vm.swappiness = 1" >>/etc/sysctl.conf',
|
'echo "vm.swappiness = 1" >>/etc/sysctl.conf',
|
||||||
'sysctl -p',
|
'sysctl -p',
|
||||||
// dont retry this as it usually fails
|
// dont retry this as it usually fails
|
||||||
|
|||||||
+8
-8
@@ -43,22 +43,22 @@
|
|||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cross-spawn": "^7.0.6",
|
"cross-spawn": "^7.0.6",
|
||||||
"esbuild": "0.25.2",
|
"esbuild": "0.25.2",
|
||||||
"eslint": "^9.26.0",
|
"eslint": "^9.28.0",
|
||||||
"eslint-import-resolver-typescript": "^4.3.4",
|
"eslint-import-resolver-typescript": "^4.4.3",
|
||||||
"eslint-import-resolver-webpack": "^0.13.10",
|
"eslint-import-resolver-webpack": "^0.13.10",
|
||||||
"fs-extra": "^11.3.0",
|
"fs-extra": "^11.3.0",
|
||||||
"globals": "^16.1.0",
|
"globals": "^16.2.0",
|
||||||
"globby": "~14.0.2",
|
"globby": "~14.0.2",
|
||||||
"inspectpack": "^4.7.1",
|
"inspectpack": "^4.7.1",
|
||||||
"mongodb-memory-server": "10.1.4",
|
"mongodb-memory-server": "10.1.4",
|
||||||
"nyc": "^17.1.0",
|
"nyc": "^17.1.0",
|
||||||
"ora": "^8.2.0",
|
"ora": "^8.2.0",
|
||||||
"oxlint": "^0.16.9",
|
"oxlint": "^0.16.12",
|
||||||
"package-json": "^10.0.1",
|
"package-json": "^10.0.1",
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.2",
|
||||||
"simple-git": "^3.27.0",
|
"simple-git": "^3.28.0",
|
||||||
"supertest": "^7.1.0",
|
"supertest": "^7.1.1",
|
||||||
"supports-hyperlinks": "^3.2.0",
|
"supports-hyperlinks": "^4.1.0",
|
||||||
"typescript": "5.8.3",
|
"typescript": "5.8.3",
|
||||||
"wtfnode": "^0.10.0"
|
"wtfnode": "^0.10.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { execSync } from 'child_process';
|
||||||
|
import fs from 'fs';
|
||||||
import { Context, Schema, UserModel } from 'hydrooj';
|
import { Context, Schema, UserModel } from 'hydrooj';
|
||||||
import { startPerformanceTest } from './performance-test';
|
import { startPerformanceTest } from './performance-test';
|
||||||
|
|
||||||
@@ -7,4 +9,17 @@ export async function apply(ctx: Context) {
|
|||||||
if (that.session.uid === 2) await UserModel.setSuperAdmin(2);
|
if (that.session.uid === 2) await UserModel.setSuperAdmin(2);
|
||||||
});
|
});
|
||||||
ctx.addScript('performance-test', 'test', Schema.object({ enable5: Schema.boolean().default(false) }), startPerformanceTest);
|
ctx.addScript('performance-test', 'test', Schema.object({ enable5: Schema.boolean().default(false) }), startPerformanceTest);
|
||||||
|
const installerFileExists = fs.existsSync('/etc/HYDRO_INSTALLER');
|
||||||
|
if (!installerFileExists) return;
|
||||||
|
const content = fs.readFileSync('/etc/HYDRO_INSTALLER', 'utf-8');
|
||||||
|
const val: Record<string, string> = {};
|
||||||
|
for (const line of content.split('\n')) {
|
||||||
|
const [key, value] = line.split('=').map((i) => i?.trim());
|
||||||
|
if (key && value) val[key] = value;
|
||||||
|
}
|
||||||
|
if (val.layout === '1') {
|
||||||
|
execSync('nix-env -iA nixpkgs.gawk');
|
||||||
|
execSync('pm2 restart hydro-sandbox');
|
||||||
|
fs.writeFileSync('/etc/HYDRO_INSTALLER', content.replace('layout=1', 'layout=2'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/a11y",
|
"name": "@hydrooj/a11y",
|
||||||
"version": "0.0.2"
|
"version": "0.0.3"
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"semver": "^7.7.1"
|
"semver": "^7.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ const locales = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function MonacoContainer({
|
function MonacoContainer({
|
||||||
config, setValue, setError, monaco, schema, editorCallback, size,
|
config, setValue, setError, monaco, schema, editorCallback, size, dark,
|
||||||
}) {
|
}) {
|
||||||
const [editor, setEditor] = React.useState<any>(null);
|
const [editor, setEditor] = React.useState<any>(null);
|
||||||
const [model, setModel] = React.useState<any>(null);
|
const [model, setModel] = React.useState<any>(null);
|
||||||
@@ -69,7 +69,7 @@ function MonacoContainer({
|
|||||||
const model = monaco.editor.createModel(config, 'yaml', monaco.Uri.parse('hydro://system/setting.yaml'));
|
const model = monaco.editor.createModel(config, 'yaml', monaco.Uri.parse('hydro://system/setting.yaml'));
|
||||||
setModel(model);
|
setModel(model);
|
||||||
const e = monaco.editor.create(element, {
|
const e = monaco.editor.create(element, {
|
||||||
theme: 'vs-light',
|
theme: dark ? 'vs-dark' : 'vs-light',
|
||||||
lineNumbers: 'off',
|
lineNumbers: 'off',
|
||||||
glyphMargin: true,
|
glyphMargin: true,
|
||||||
lightbulb: { enabled: monaco.editor.ShowLightbulbIconMode.On },
|
lightbulb: { enabled: monaco.editor.ShowLightbulbIconMode.On },
|
||||||
@@ -149,15 +149,27 @@ export default function ConfigEditor({
|
|||||||
setValue(yaml.load(v));
|
setValue(yaml.load(v));
|
||||||
}, [stringConfig]);
|
}, [stringConfig]);
|
||||||
|
|
||||||
|
// FIXME: Otherwise first form change will be ignored
|
||||||
|
React.useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
updateFromMonaco(`${stringConfig}\n\ndummy: 1`);
|
||||||
|
setTimeout(() => {
|
||||||
|
setStringConfig(stringConfig);
|
||||||
|
setValue(yaml.load(stringConfig));
|
||||||
|
}, 300);
|
||||||
|
}, 300);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const [size, setSize] = React.useState([50, 50]);
|
const [size, setSize] = React.useState([50, 50]);
|
||||||
|
|
||||||
return (<div className="fullscreen-content" style={{ backgroundColor: 'white', zIndex: 10 }}>
|
return (<div className="fullscreen-content" style={{ zIndex: 10 }}>
|
||||||
<Allotment onChange={setSize}>
|
<Allotment onChange={setSize}>
|
||||||
<Allotment.Pane>
|
<Allotment.Pane>
|
||||||
<MonacoContainer
|
<MonacoContainer
|
||||||
editorCallback={registerAction} schema={schema} monaco={monaco}
|
editorCallback={registerAction} schema={schema} monaco={monaco}
|
||||||
config={stringConfig} setValue={updateFromMonaco} setError={setInfo}
|
config={stringConfig} setValue={updateFromMonaco} setError={setInfo}
|
||||||
size={size[0]}
|
size={size[0]}
|
||||||
|
dark={document.documentElement.className.includes('theme--dark')}
|
||||||
/>
|
/>
|
||||||
<pre className="help-text">{info}</pre>
|
<pre className="help-text">{info}</pre>
|
||||||
<button onClick={() => onSave(stringConfig)} className="rounded primary button">{i18n('Save All Changes')}</button>
|
<button onClick={() => onSave(stringConfig)} className="rounded primary button">{i18n('Save All Changes')}</button>
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/components",
|
"name": "@hydrooj/components",
|
||||||
"version": "1.0.0-beta.2",
|
"version": "1.0.0-beta.3",
|
||||||
"main": "./frontend/index.ts",
|
"main": "./frontend/index.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"allotment": "^1.20.3",
|
"allotment": "^1.20.3",
|
||||||
"diff": "^7.0.0",
|
"diff": "^8.0.2",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
"schemastery-react": "^0.1.2"
|
"schemastery-react": "^0.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/diff": "^7.0.2",
|
"@types/diff": "^8.0.0",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/lodash": "^4.17.16",
|
"@types/lodash": "^4.17.17",
|
||||||
"sass": "^1.87.0"
|
"sass": "^1.89.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*",
|
"react": "*",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@elastic/elasticsearch": "^8.18.2"
|
"@elastic/elasticsearch": "^9.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"hydrooj": ">=5.0.0-alpha.1"
|
"hydrooj": ">=5.0.0-alpha.1"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"maxmind": "^4.3.25"
|
"maxmind": "^4.3.27"
|
||||||
},
|
},
|
||||||
"preferUnplugged": true
|
"preferUnplugged": true
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/hydrojudge",
|
"name": "@hydrooj/hydrojudge",
|
||||||
"bin": "bin/hydrojudge.js",
|
"bin": "bin/hydrojudge.js",
|
||||||
"version": "3.5.0-beta.2",
|
"version": "3.5.0-beta.3",
|
||||||
"main": "./src/index.ts",
|
"main": "./src/index.ts",
|
||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"repository": "https://github.com/hydro-dev/Hydro.git",
|
"repository": "https://github.com/hydro-dev/Hydro.git",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hydrooj/common": "workspace:^",
|
"@hydrooj/common": "workspace:^",
|
||||||
"@hydrooj/utils": "workspace:^",
|
"@hydrooj/utils": "workspace:^",
|
||||||
"@zip.js/zip.js": "^2.7.60",
|
"@zip.js/zip.js": "^2.7.62",
|
||||||
"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",
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.2",
|
||||||
"shell-quote": "^1.8.2",
|
"shell-quote": "^1.8.3",
|
||||||
"superagent": "^10.2.1",
|
"superagent": "^10.2.1",
|
||||||
"ws": "^8.18.2"
|
"ws": "^8.18.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ const getDefaultChecker = (strict: boolean) => async (config) => {
|
|||||||
...config.copyIn,
|
...config.copyIn,
|
||||||
'compare.sh': { content: compareSh },
|
'compare.sh': { content: compareSh },
|
||||||
},
|
},
|
||||||
|
processLimit: 32,
|
||||||
});
|
});
|
||||||
let status: number;
|
let status: number;
|
||||||
let message: any = '';
|
let message: any = '';
|
||||||
|
|||||||
@@ -27,11 +27,8 @@ export namespace Lock {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function compilerText(stdout: string, stderr: string) {
|
export function compilerText(...messages: string[]) {
|
||||||
const ret = [];
|
return messages.filter((i) => !EMPTY_STR.test(i)).map((i) => i.substring(0, 1024 * 1024)).join('\n');
|
||||||
if (!EMPTY_STR.test(stdout)) ret.push(stdout.substring(0, 1024 * 1024));
|
|
||||||
if (!EMPTY_STR.test(stderr)) ret.push(stderr.substring(0, 1024 * 1024));
|
|
||||||
return ret.join('\n');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function restrictFile(p: string) {
|
function restrictFile(p: string) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hydrooj",
|
"name": "hydrooj",
|
||||||
"version": "5.0.0-beta.2",
|
"version": "5.0.0-beta.3",
|
||||||
"bin": "bin/hydrooj.js",
|
"bin": "bin/hydrooj.js",
|
||||||
"main": "src/plugin-api",
|
"main": "src/plugin-api",
|
||||||
"module": "src/plugin-api",
|
"module": "src/plugin-api",
|
||||||
@@ -23,10 +23,10 @@
|
|||||||
"@hydrooj/utils": "workspace:^",
|
"@hydrooj/utils": "workspace:^",
|
||||||
"@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",
|
||||||
"@zip.js/zip.js": "^2.7.60",
|
"@zip.js/zip.js": "^2.7.62",
|
||||||
"adm-zip": "0.5.16",
|
"adm-zip": "0.5.16",
|
||||||
"cac": "^6.7.14",
|
"cac": "^6.7.14",
|
||||||
"cordis": "4.0.0-beta.2",
|
"cordis": "4.0.0-beta.5",
|
||||||
"csv-stringify": "^6.5.2",
|
"csv-stringify": "^6.5.2",
|
||||||
"emoji-regex": "^10.4.0",
|
"emoji-regex": "^10.4.0",
|
||||||
"emojis-list": "2.1.0",
|
"emojis-list": "2.1.0",
|
||||||
@@ -35,10 +35,10 @@
|
|||||||
"koa-proxies": "^0.12.4",
|
"koa-proxies": "^0.12.4",
|
||||||
"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": "^11.1.0",
|
||||||
"mime-types": "^3.0.1",
|
"mime-types": "^3.0.1",
|
||||||
"moment-timezone": "^0.5.48",
|
"moment-timezone": "^0.5.48",
|
||||||
"mongodb": "^6.16.0",
|
"mongodb": "^6.17.0",
|
||||||
"mongodb-uri": "^0.9.7",
|
"mongodb-uri": "^0.9.7",
|
||||||
"nanoid": "^5.1.5",
|
"nanoid": "^5.1.5",
|
||||||
"nodemailer": "^7.0.3",
|
"nodemailer": "^7.0.3",
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"saslprep": "^1.0.3",
|
"saslprep": "^1.0.3",
|
||||||
"schemastery": "^3.16.1",
|
"schemastery": "^3.16.1",
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.2",
|
||||||
"shorty.js": "^1.0.1",
|
"shorty.js": "^1.0.1",
|
||||||
"superagent": "^10.2.1",
|
"superagent": "^10.2.1",
|
||||||
"tar": "6.2.1",
|
"tar": "6.2.1",
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/koa-static-cache": "^5.1.4",
|
"@types/koa-static-cache": "^5.1.4",
|
||||||
"@types/lodash": "^4.17.16",
|
"@types/lodash": "^4.17.17",
|
||||||
"@types/mime-types": "^2.1.4",
|
"@types/mime-types": "^2.1.4",
|
||||||
"@types/mongodb-uri": "^0.9.4",
|
"@types/mongodb-uri": "^0.9.4",
|
||||||
"@types/nodemailer": "^6.4.17",
|
"@types/nodemailer": "^6.4.17",
|
||||||
|
|||||||
@@ -71,6 +71,17 @@ export class Context extends cordis.Context {
|
|||||||
super();
|
super();
|
||||||
this.plugin(ApiMixin);
|
this.plugin(ApiMixin);
|
||||||
this.plugin(TimerService);
|
this.plugin(TimerService);
|
||||||
this.plugin(LoggerService);
|
this.plugin(LoggerService, {
|
||||||
|
console: {
|
||||||
|
showDiff: false,
|
||||||
|
showTime: 'dd hh:mm:ss',
|
||||||
|
label: {
|
||||||
|
align: 'right',
|
||||||
|
width: 9,
|
||||||
|
margin: 1,
|
||||||
|
},
|
||||||
|
levels: { default: process.env.DEV ? 3 : 2 },
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import { ObjectId } from 'mongodb';
|
|||||||
import { Context } from '../context';
|
import { Context } from '../context';
|
||||||
import { load as loadOptions } from '../options';
|
import { load as loadOptions } from '../options';
|
||||||
import { MongoService } from '../service/db';
|
import { MongoService } from '../service/db';
|
||||||
import { ConfigService } from '../settings';
|
import { SettingService } from '../settings';
|
||||||
import {
|
import {
|
||||||
addon, builtinModel, model, service,
|
addon, builtinModel, model, service,
|
||||||
} from './common';
|
} from './common';
|
||||||
@@ -110,10 +110,10 @@ export async function load(ctx: Context) {
|
|||||||
const pending = global.addons;
|
const pending = global.addons;
|
||||||
const fail = [];
|
const fail = [];
|
||||||
await ctx.plugin(MongoService, loadOptions() || {});
|
await ctx.plugin(MongoService, loadOptions() || {});
|
||||||
await ctx.plugin(ConfigService);
|
await ctx.plugin(SettingService);
|
||||||
await require('../model/system').runConfig();
|
await require('../model/system').runConfig();
|
||||||
ctx = await new Promise((resolve) => {
|
ctx = await new Promise((resolve) => {
|
||||||
ctx.inject(['loader', 'config', 'db'], (c) => {
|
ctx.inject(['loader', 'setting', 'db'], (c) => {
|
||||||
resolve(c);
|
resolve(c);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -127,7 +127,7 @@ export async function load(ctx: Context) {
|
|||||||
await builtinModel(ctx);
|
await builtinModel(ctx);
|
||||||
await model(pending, fail, ctx);
|
await model(pending, fail, ctx);
|
||||||
ctx = await new Promise((resolve) => {
|
ctx = await new Promise((resolve) => {
|
||||||
ctx.inject(['server', 'setting', 'worker'], (c) => {
|
ctx.inject(['server', 'worker'], (c) => {
|
||||||
resolve(c);
|
resolve(c);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Context } from '../context';
|
|||||||
import { Logger } from '../logger';
|
import { Logger } from '../logger';
|
||||||
import { load } from '../options';
|
import { load } from '../options';
|
||||||
import { MongoService } from '../service/db';
|
import { MongoService } from '../service/db';
|
||||||
import { ConfigService } from '../settings';
|
import { SettingService } from '../settings';
|
||||||
import {
|
import {
|
||||||
addon, builtinModel, locale, model, service,
|
addon, builtinModel, locale, model, service,
|
||||||
} from './common';
|
} from './common';
|
||||||
@@ -30,11 +30,11 @@ export async function apply(ctx: Context) {
|
|||||||
const fail = [];
|
const fail = [];
|
||||||
await locale(pending, fail);
|
await locale(pending, fail);
|
||||||
await ctx.plugin(MongoService, load() || {});
|
await ctx.plugin(MongoService, load() || {});
|
||||||
await ctx.plugin(ConfigService);
|
await ctx.plugin(SettingService);
|
||||||
const modelSystem = require('../model/system');
|
const modelSystem = require('../model/system');
|
||||||
await modelSystem.runConfig();
|
await modelSystem.runConfig();
|
||||||
ctx = await new Promise((resolve) => {
|
ctx = await new Promise((resolve) => {
|
||||||
ctx.inject(['loader', 'config', 'db'], (c) => {
|
ctx.inject(['loader', 'setting', 'db'], (c) => {
|
||||||
resolve(c);
|
resolve(c);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -576,7 +576,7 @@ export class ContestBalloonHandler extends ContestManagementBaseHandler {
|
|||||||
const bdocs = await contest.getMultiBalloon(domainId, tid, {
|
const bdocs = await contest.getMultiBalloon(domainId, tid, {
|
||||||
...todo ? { sent: { $exists: false } } : {},
|
...todo ? { sent: { $exists: false } } : {},
|
||||||
...(!this.tdoc.lockAt || this.user.hasPerm(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD))
|
...(!this.tdoc.lockAt || this.user.hasPerm(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD))
|
||||||
? {} : { _id: { $lt: this.tdoc.lockAt } },
|
? {} : { _id: { $lt: Time.getObjectID(this.tdoc.lockAt) } },
|
||||||
}).sort({ _id: -1 }).toArray();
|
}).sort({ _id: -1 }).toArray();
|
||||||
const uids = bdocs.map((i) => i.uid).concat(bdocs.filter((i) => i.sent).map((i) => i.sent));
|
const uids = bdocs.map((i) => i.uid).concat(bdocs.filter((i) => i.sent).map((i) => i.sent));
|
||||||
this.response.body = {
|
this.response.body = {
|
||||||
@@ -743,7 +743,7 @@ export async function apply(ctx: Context) {
|
|||||||
await Promise.all(tasks);
|
await Promise.all(tasks);
|
||||||
});
|
});
|
||||||
ctx.plugin(ScoreboardService);
|
ctx.plugin(ScoreboardService);
|
||||||
ctx.inject(['scoreboard'], ({ Route, scoreboard }) => {
|
await ctx.inject(['scoreboard'], ({ Route, scoreboard }) => {
|
||||||
Route('contest_scoreboard', '/contest/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST_SCOREBOARD);
|
Route('contest_scoreboard', '/contest/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST_SCOREBOARD);
|
||||||
Route('contest_scoreboard_view', '/contest/:tid/scoreboard/:view', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST_SCOREBOARD);
|
Route('contest_scoreboard_view', '/contest/:tid/scoreboard/:view', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST_SCOREBOARD);
|
||||||
scoreboard.addView('default', 'Default', { tdoc: 'tdoc', groups: 'groups', realtime: Types.Boolean }, {
|
scoreboard.addView('default', 'Default', { tdoc: 'tdoc', groups: 'groups', realtime: Types.Boolean }, {
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ class DomainPermissionHandler extends ManageHandler {
|
|||||||
async post({ domainId }) {
|
async post({ domainId }) {
|
||||||
const roles = {};
|
const roles = {};
|
||||||
for (const role in this.request.body) {
|
for (const role in this.request.body) {
|
||||||
|
if (role === 'root') continue; // root role is not editable
|
||||||
const perms = this.request.body[role] instanceof Array
|
const perms = this.request.body[role] instanceof Array
|
||||||
? this.request.body[role]
|
? this.request.body[role]
|
||||||
: [this.request.body[role]];
|
: [this.request.body[role]];
|
||||||
@@ -460,7 +461,7 @@ export async function apply(ctx: Context) {
|
|||||||
ctx.Route('domain_join_applications', '/domain/join_applications', DomainJoinApplicationsHandler);
|
ctx.Route('domain_join_applications', '/domain/join_applications', DomainJoinApplicationsHandler);
|
||||||
ctx.Route('domain_join', '/domain/join', DomainJoinHandler, PRIV.PRIV_USER_PROFILE);
|
ctx.Route('domain_join', '/domain/join', DomainJoinHandler, PRIV.PRIV_USER_PROFILE);
|
||||||
ctx.Route('domain_search', '/domain/search', DomainSearchHandler, PRIV.PRIV_USER_PROFILE);
|
ctx.Route('domain_search', '/domain/search', DomainSearchHandler, PRIV.PRIV_USER_PROFILE);
|
||||||
ctx.inject(['api'], ({ api }) => {
|
await ctx.inject(['api'], ({ api }) => {
|
||||||
api.provide(DomainApi);
|
api.provide(DomainApi);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -314,7 +314,7 @@ export async function apply(ctx) {
|
|||||||
ctx.Route('homework_edit', '/homework/:tid/edit', HomeworkEditHandler);
|
ctx.Route('homework_edit', '/homework/:tid/edit', HomeworkEditHandler);
|
||||||
ctx.Route('homework_files', '/homework/:tid/file', HomeworkFilesHandler, PERM.PERM_VIEW_HOMEWORK);
|
ctx.Route('homework_files', '/homework/:tid/file', HomeworkFilesHandler, PERM.PERM_VIEW_HOMEWORK);
|
||||||
ctx.Route('homework_file_download', '/homework/:tid/file/:filename', ContestFileDownloadHandler, PERM.PERM_VIEW_HOMEWORK);
|
ctx.Route('homework_file_download', '/homework/:tid/file/:filename', ContestFileDownloadHandler, PERM.PERM_VIEW_HOMEWORK);
|
||||||
ctx.inject(['scoreboard'], ({ Route }) => {
|
await ctx.inject(['scoreboard'], ({ Route }) => {
|
||||||
Route('homework_scoreboard', '/homework/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_HOMEWORK_SCOREBOARD);
|
Route('homework_scoreboard', '/homework/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_HOMEWORK_SCOREBOARD);
|
||||||
Route('homework_scoreboard_view', '/homework/:tid/scoreboard/:view', ContestScoreboardHandler, PERM.PERM_VIEW_HOMEWORK_SCOREBOARD);
|
Route('homework_scoreboard_view', '/homework/:tid/scoreboard/:view', ContestScoreboardHandler, PERM.PERM_VIEW_HOMEWORK_SCOREBOARD);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ class SystemConfigHandler extends SystemHandler {
|
|||||||
@requireSudo
|
@requireSudo
|
||||||
async get() {
|
async get() {
|
||||||
this.response.template = 'manage_config.html';
|
this.response.template = 'manage_config.html';
|
||||||
let value;
|
let value = this.ctx.setting.configSource;
|
||||||
|
|
||||||
const processNode = (node: any, schema: Schema<any, any>, parent?: any, accessKey?: string) => {
|
const processNode = (node: any, schema: Schema<any, any>, parent?: any, accessKey?: string) => {
|
||||||
if (['union', 'intersect'].includes(schema.type)) {
|
if (['union', 'intersect'].includes(schema.type)) {
|
||||||
@@ -198,23 +198,20 @@ class SystemConfigHandler extends SystemHandler {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
try {
|
const temp = yaml.load(this.ctx.setting.configSource);
|
||||||
value = Schema.intersect(this.ctx.config.settings)(yaml.load(this.ctx.config.configSource));
|
for (const schema of this.ctx.setting.settings) processNode(temp, schema);
|
||||||
} catch (e) {
|
value = yaml.dump(temp);
|
||||||
value = yaml.load(this.ctx.config.configSource);
|
|
||||||
}
|
|
||||||
for (const schema of this.ctx.config.settings) processNode(value, schema);
|
|
||||||
} catch (e) { }
|
} catch (e) { }
|
||||||
this.response.body = {
|
this.response.body = {
|
||||||
schema: Schema.intersect(this.ctx.config.settings).toJSON(),
|
schema: Schema.intersect(this.ctx.setting.settings).toJSON(),
|
||||||
value: yaml.dump(value),
|
value,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@requireSudo
|
@requireSudo
|
||||||
@param('value', Types.String)
|
@param('value', Types.String)
|
||||||
async post({ }, value: string) {
|
async post({ }, value: string) {
|
||||||
const oldConfig = yaml.load(this.ctx.config.configSource);
|
const oldConfig = yaml.load(this.ctx.setting.configSource);
|
||||||
let config;
|
let config;
|
||||||
const processNode = (node: any, old: any, schema: Schema<any, any>, parent?: any, accessKey?: string) => {
|
const processNode = (node: any, old: any, schema: Schema<any, any>, parent?: any, accessKey?: string) => {
|
||||||
if (['union', 'intersect'].includes(schema.type)) {
|
if (['union', 'intersect'].includes(schema.type)) {
|
||||||
@@ -231,11 +228,11 @@ class SystemConfigHandler extends SystemHandler {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
config = yaml.load(value);
|
config = yaml.load(value);
|
||||||
for (const schema of this.ctx.config.settings) processNode(config, oldConfig, schema, null, '');
|
for (const schema of this.ctx.setting.settings) processNode(config, oldConfig, schema, null, '');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new ValidationError('value', '', e.message);
|
throw new ValidationError('value', '', e.message);
|
||||||
}
|
}
|
||||||
await this.ctx.config.saveConfig(config);
|
await this.ctx.setting.saveConfig(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,7 +354,7 @@ class SystemUserPrivHandler extends SystemHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const inject = ['config', 'check'];
|
export const inject = ['setting', 'check'];
|
||||||
export async function apply(ctx) {
|
export async function apply(ctx) {
|
||||||
ctx.Route('manage', '/manage', SystemMainHandler);
|
ctx.Route('manage', '/manage', SystemMainHandler);
|
||||||
ctx.Route('manage_dashboard', '/manage/dashboard', SystemDashboardHandler);
|
ctx.Route('manage_dashboard', '/manage/dashboard', SystemDashboardHandler);
|
||||||
|
|||||||
@@ -384,10 +384,10 @@ export class ProblemDetailHandler extends ContestDetailBaseHandler {
|
|||||||
if (this.psdoc?.rid) {
|
if (this.psdoc?.rid) {
|
||||||
this.response.body.rdoc = await record.get(this.args.domainId, this.psdoc.rid);
|
this.response.body.rdoc = await record.get(this.args.domainId, this.psdoc.rid);
|
||||||
}
|
}
|
||||||
[this.response.body.ctdocs, this.response.body.htdocs] = await Promise.all([
|
[this.response.body.ctdocs, this.response.body.htdocs] = (await Promise.all([
|
||||||
contest.getRelated(this.args.domainId, this.pdoc.docId),
|
contest.getRelated(this.args.domainId, this.pdoc.docId),
|
||||||
contest.getRelated(this.args.domainId, this.pdoc.docId, 'homework'),
|
contest.getRelated(this.args.domainId, this.pdoc.docId, 'homework'),
|
||||||
]);
|
])).map((tdocs) => tdocs.filter((tdoc) => !tdoc.assign?.length || Set.intersection(tdoc.assign, this.user.group).size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1070,7 +1070,7 @@ export async function apply(ctx: Context) {
|
|||||||
ctx.Route('problem_solution_reply_raw', '/p/:pid/solution/:psid/:psrid/raw', ProblemSolutionRawHandler, PERM.PERM_VIEW_PROBLEM);
|
ctx.Route('problem_solution_reply_raw', '/p/:pid/solution/:psid/:psrid/raw', ProblemSolutionRawHandler, PERM.PERM_VIEW_PROBLEM);
|
||||||
ctx.Route('problem_statistics', '/p/:pid/stat', ProblemStatisticsHandler, PERM.PERM_VIEW_PROBLEM);
|
ctx.Route('problem_statistics', '/p/:pid/stat', ProblemStatisticsHandler, PERM.PERM_VIEW_PROBLEM);
|
||||||
ctx.Route('problem_create', '/problem/create', ProblemCreateHandler, PERM.PERM_CREATE_PROBLEM);
|
ctx.Route('problem_create', '/problem/create', ProblemCreateHandler, PERM.PERM_CREATE_PROBLEM);
|
||||||
ctx.inject(['api'], ({ api }) => {
|
await ctx.inject(['api'], ({ api }) => {
|
||||||
api.provide(ProblemApi);
|
api.provide(ProblemApi);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -625,7 +625,7 @@ export async function apply(ctx: Context) {
|
|||||||
throw new NotFoundError();
|
throw new NotFoundError();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
ctx.inject(['api'], ({ api }) => {
|
await ctx.inject(['api'], ({ api }) => {
|
||||||
api.provide(UserApi);
|
api.provide(UserApi);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,8 @@ inject('Nav', 'record_main', {
|
|||||||
query: (handler) => (handler.user.hasPriv(PRIV.PRIV_USER_PROFILE)
|
query: (handler) => (handler.user.hasPriv(PRIV.PRIV_USER_PROFILE)
|
||||||
? ({ uidOrName: handler.user._id })
|
? ({ uidOrName: handler.user._id })
|
||||||
: ({})),
|
: ({})),
|
||||||
});
|
}, (handler) => (handler.user.hasPriv(PRIV.PRIV_USER_PROFILE)
|
||||||
|
? true : handler.user.hasPerm(PERM.PERM_VIEW_RECORD)));
|
||||||
inject('Nav', 'ranking', { prefix: 'ranking' }, PERM.PERM_VIEW_RANKING);
|
inject('Nav', 'ranking', { prefix: 'ranking' }, PERM.PERM_VIEW_RANKING);
|
||||||
inject('Nav', 'domain_dashboard', { prefix: 'domain' }, PERM.PERM_EDIT_DOMAIN);
|
inject('Nav', 'domain_dashboard', { prefix: 'domain' }, PERM.PERM_EDIT_DOMAIN);
|
||||||
inject('Nav', 'manage_dashboard', { prefix: 'manage' }, PRIV.PRIV_EDIT_SYSTEM);
|
inject('Nav', 'manage_dashboard', { prefix: 'manage' }, PRIV.PRIV_EDIT_SYSTEM);
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export class Loader extends Service {
|
|||||||
public cache: Record<string, string> = Object.create(null);
|
public cache: Record<string, string> = Object.create(null);
|
||||||
// public warnings: Record<string, string> = Object.create(null);
|
// public warnings: Record<string, string> = Object.create(null);
|
||||||
|
|
||||||
static inject = ['config', 'timer', 'i18n', 'logger'];
|
static inject = ['setting', 'timer', 'i18n', 'logger'];
|
||||||
|
|
||||||
constructor(ctx: Context) {
|
constructor(ctx: Context) {
|
||||||
super(ctx, 'loader');
|
super(ctx, 'loader');
|
||||||
@@ -100,8 +100,8 @@ export class Loader extends Service {
|
|||||||
const schemaRequest = configScope ? Schema.object({
|
const schemaRequest = configScope ? Schema.object({
|
||||||
[configScope]: schema,
|
[configScope]: schema,
|
||||||
}) : schema;
|
}) : schema;
|
||||||
await this.ctx.config._tryMigrateConfig(schemaRequest);
|
await this.ctx.setting._tryMigrateConfig(schemaRequest);
|
||||||
const res = this.ctx.config.requestConfig(schemaRequest);
|
const res = this.ctx.setting.requestConfig(schemaRequest);
|
||||||
return configScope ? res[configScope] : res;
|
return configScope ? res[configScope] : res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import * as cordis from 'cordis';
|
|
||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import { Dictionary } from 'lodash';
|
import { Dictionary } from 'lodash';
|
||||||
import moment from 'moment-timezone';
|
import moment from 'moment-timezone';
|
||||||
import Schema from 'schemastery';
|
import Schema from 'schemastery';
|
||||||
import { LangConfig, parseLang } from '@hydrooj/common';
|
import { LangConfig, parseLang } from '@hydrooj/common';
|
||||||
import { findFileSync, retry } from '@hydrooj/utils';
|
import { findFileSync, retry } from '@hydrooj/utils';
|
||||||
import { Context, Service } from '../context';
|
import { Context } from '../context';
|
||||||
import { Setting as _Setting } from '../interface';
|
import { Setting as _Setting } from '../interface';
|
||||||
import { Logger } from '../logger';
|
import { Logger } from '../logger';
|
||||||
import * as builtin from './builtin';
|
import * as builtin from './builtin';
|
||||||
@@ -27,11 +26,6 @@ for (const country of countries) {
|
|||||||
const timezones = Array.from(tzs).sort().map((tz) => [tz, tz]) as [string, string][];
|
const timezones = Array.from(tzs).sort().map((tz) => [tz, tz]) as [string, string][];
|
||||||
const langRange: Dictionary<string> = {};
|
const langRange: Dictionary<string> = {};
|
||||||
|
|
||||||
for (const lang in global.Hydro.locales) {
|
|
||||||
if (!global.Hydro.locales[lang].__interface) continue;
|
|
||||||
langRange[lang] = global.Hydro.locales[lang].__langname;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const FLAG_HIDDEN = 1;
|
export const FLAG_HIDDEN = 1;
|
||||||
export const FLAG_DISABLED = 2;
|
export const FLAG_DISABLED = 2;
|
||||||
export const FLAG_SECRET = 4;
|
export const FLAG_SECRET = 4;
|
||||||
@@ -89,7 +83,7 @@ function schemaToSettings(schema: Schema<any>) {
|
|||||||
let flag = (s.meta?.hidden ? FLAG_HIDDEN : 0)
|
let flag = (s.meta?.hidden ? FLAG_HIDDEN : 0)
|
||||||
| (s.meta?.disabled ? FLAG_DISABLED : 0);
|
| (s.meta?.disabled ? FLAG_DISABLED : 0);
|
||||||
const type = s.type === 'number' ? 'number'
|
const type = s.type === 'number' ? 'number'
|
||||||
: s.type === 'boolean' ? 'checkbox'
|
: s.type === 'boolean' ? 'boolean'
|
||||||
: s.meta?.role === 'markdown' ? 'markdown'
|
: s.meta?.role === 'markdown' ? 'markdown'
|
||||||
: s.meta?.role === 'textarea' ? 'textarea' : 'text';
|
: s.meta?.role === 'textarea' ? 'textarea' : 'text';
|
||||||
if (s.meta?.role === 'password') flag |= FLAG_SECRET;
|
if (s.meta?.role === 'password') flag |= FLAG_SECRET;
|
||||||
@@ -362,39 +356,13 @@ SystemSetting(
|
|||||||
|
|
||||||
export const langs: Record<string, LangConfig> = {};
|
export const langs: Record<string, LangConfig> = {};
|
||||||
|
|
||||||
declare module 'cordis' {
|
|
||||||
interface Context {
|
|
||||||
setting: SettingService;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const T = <F extends (...args: any[]) => any>(origFunc: F, disposeFunc?) =>
|
|
||||||
function method(this: cordis.Service, ...args: Parameters<F>) {
|
|
||||||
this.ctx.effect(() => {
|
|
||||||
const res = origFunc(...args);
|
|
||||||
return () => (disposeFunc ? disposeFunc(res) : res());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export class SettingService extends Service {
|
|
||||||
PreferenceSetting = T(PreferenceSetting);
|
|
||||||
AccountSetting = T(AccountSetting);
|
|
||||||
DomainSetting = T(DomainSetting);
|
|
||||||
DomainUserSetting = T(DomainUserSetting);
|
|
||||||
SystemSetting = T(SystemSetting);
|
|
||||||
constructor(ctx: Context) {
|
|
||||||
super(ctx, 'setting');
|
|
||||||
}
|
|
||||||
|
|
||||||
get(key: string) {
|
|
||||||
return (this.ctx ? this.ctx.domain?.config?.[key.replace(/\./g, '$')] : null) ?? global.Hydro.model.system.get(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const inject = ['db'];
|
export const inject = ['db'];
|
||||||
export async function apply(ctx: Context) {
|
export async function apply(ctx: Context) {
|
||||||
ctx.plugin(SettingService);
|
|
||||||
logger.info('Ensuring settings');
|
logger.info('Ensuring settings');
|
||||||
|
for (const lang in global.Hydro.locales) {
|
||||||
|
if (!global.Hydro.locales[lang].__interface) continue;
|
||||||
|
langRange[lang] = global.Hydro.locales[lang].__langname;
|
||||||
|
}
|
||||||
const system = global.Hydro.model.system;
|
const system = global.Hydro.model.system;
|
||||||
for (const setting of SYSTEM_SETTINGS) {
|
for (const setting of SYSTEM_SETTINGS) {
|
||||||
if (!setting.value) continue;
|
if (!setting.value) continue;
|
||||||
@@ -424,7 +392,6 @@ global.Hydro.model.setting = {
|
|||||||
apply,
|
apply,
|
||||||
inject,
|
inject,
|
||||||
|
|
||||||
SettingService,
|
|
||||||
Setting,
|
Setting,
|
||||||
PreferenceSetting,
|
PreferenceSetting,
|
||||||
AccountSetting,
|
AccountSetting,
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ export class User {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(_, h) {
|
serialize(h) {
|
||||||
if (!this._isPrivate) {
|
if (!this._isPrivate) {
|
||||||
const fields = ['_id', 'uname', 'mail', 'perm', 'role', 'priv', 'regat', 'loginat', 'tfa', 'authn'];
|
const fields = ['_id', 'uname', 'mail', 'perm', 'role', 'priv', 'regat', 'loginat', 'tfa', 'authn'];
|
||||||
if (h?.user?.hasPerm(PERM.PERM_VIEW_DISPLAYNAME)) fields.push('displayName');
|
if (h?.user?.hasPerm(PERM.PERM_VIEW_DISPLAYNAME)) fields.push('displayName');
|
||||||
@@ -189,6 +189,7 @@ async function initAndCache(udoc: Udoc, dudoc, scope: bigint = PERM.PERM_ALL) {
|
|||||||
|
|
||||||
class UserModel {
|
class UserModel {
|
||||||
static coll = coll;
|
static coll = coll;
|
||||||
|
static collGroup = collGroup;
|
||||||
static User = User;
|
static User = User;
|
||||||
static cache = cache;
|
static cache = cache;
|
||||||
static defaultUser: Udoc = {
|
static defaultUser: Udoc = {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import pwsh from './lib/hash.hydro';
|
|||||||
import db from './service/db';
|
import db from './service/db';
|
||||||
|
|
||||||
export { nanoid } from 'nanoid';
|
export { nanoid } from 'nanoid';
|
||||||
|
export { default as moment, isMoment } from 'moment-timezone';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
WebService, Router, HandlerCommon, httpServer,
|
WebService, Router, HandlerCommon, httpServer,
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { resolve } from 'path';
|
||||||
|
import {
|
||||||
|
existsSync, mkdir, readdir, rename, rmdir,
|
||||||
|
} from 'fs-extra';
|
||||||
|
import Schema from 'schemastery';
|
||||||
|
import { Context } from '../context';
|
||||||
|
|
||||||
|
export const apply = (ctx: Context) => ctx.addScript(
|
||||||
|
'fixStorage', 'Rename all files to lowercase',
|
||||||
|
Schema.object({}),
|
||||||
|
async () => {
|
||||||
|
const config = await ctx.get('storage').status();
|
||||||
|
console.log(config);
|
||||||
|
if (config.type !== 'Local') throw new Error('Only local storage is supported');
|
||||||
|
const dir = (config as any).dir;
|
||||||
|
if (!dir) throw new Error('Storage dir not found');
|
||||||
|
const files = await readdir(dir);
|
||||||
|
for (const file of files) {
|
||||||
|
let inner = await readdir(resolve(dir, file));
|
||||||
|
if (file.toLowerCase() !== file && !existsSync(resolve(dir, file.toLowerCase()))) {
|
||||||
|
await mkdir(resolve(dir, file.toLowerCase()));
|
||||||
|
console.log('+: ', resolve(dir, file.toLowerCase()));
|
||||||
|
}
|
||||||
|
for (const f of inner) {
|
||||||
|
if (resolve(dir, file, f) === resolve(dir, file.toLowerCase(), f.toLowerCase())) continue;
|
||||||
|
await rename(resolve(dir, file, f), resolve(dir, file.toLowerCase(), f.toLowerCase()));
|
||||||
|
console.log('R: ', resolve(dir, file, f), '->', resolve(dir, file.toLowerCase(), f.toLowerCase()));
|
||||||
|
}
|
||||||
|
inner = await readdir(resolve(dir, file));
|
||||||
|
if (!inner.length) {
|
||||||
|
await rmdir(resolve(dir, file));
|
||||||
|
console.log('-: ', resolve(dir, file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const coll = ctx.get('db').collection('storage');
|
||||||
|
while (true) {
|
||||||
|
let hasItem = false;
|
||||||
|
for await (const t of coll.find({ $or: [{ _id: /[A-Z]/ }, { link: /[A-Z]/ }] })) {
|
||||||
|
hasItem = true;
|
||||||
|
await coll.insertOne({
|
||||||
|
...t,
|
||||||
|
_id: t._id.toLowerCase(),
|
||||||
|
...(t.link ? { link: t.link.toLowerCase() } : {}),
|
||||||
|
});
|
||||||
|
await coll.deleteOne({ _id: t._id });
|
||||||
|
console.log('Q: ', t._id, '->', t._id.toLowerCase());
|
||||||
|
}
|
||||||
|
if (!hasItem) break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -91,7 +91,7 @@ export async function apply(ctx: Context) {
|
|||||||
xhost: system.get('server.xhost'),
|
xhost: system.get('server.xhost'),
|
||||||
});
|
});
|
||||||
if (process.env.HYDRO_CLI) return;
|
if (process.env.HYDRO_CLI) return;
|
||||||
ctx.inject(['server', 'oauth', 'setting'], (childContext) => {
|
await ctx.inject(['server', 'oauth', 'setting'], (childContext) => {
|
||||||
const { server, on, oauth } = childContext;
|
const { server, on, oauth } = childContext;
|
||||||
server.addHandlerLayer('init', async (c, next) => {
|
server.addHandlerLayer('init', async (c, next) => {
|
||||||
const init = Date.now();
|
const init = Date.now();
|
||||||
|
|||||||
@@ -387,6 +387,7 @@ class LocalStorageService {
|
|||||||
status: !this.error,
|
status: !this.error,
|
||||||
error: this.error,
|
error: this.error,
|
||||||
bucket: 'Hydro',
|
bucket: 'Hydro',
|
||||||
|
dir: this.config.path,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,7 +401,7 @@ export async function apply(ctx: Context, config: ReturnType<typeof FileSetting>
|
|||||||
service = new LocalStorageService(config);
|
service = new LocalStorageService(config);
|
||||||
}
|
}
|
||||||
await service.start();
|
await service.start();
|
||||||
ctx.inject(['server'], ({ server }) => {
|
await ctx.inject(['server'], ({ server }) => {
|
||||||
let endpoint = config.endPoint;
|
let endpoint = config.endPoint;
|
||||||
if (config.type === 's3' && !config.pathStyle) {
|
if (config.type === 's3' && !config.pathStyle) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -2,18 +2,29 @@ import yaml from 'js-yaml';
|
|||||||
import Schema from 'schemastery';
|
import Schema from 'schemastery';
|
||||||
import { Context, Service } from './context';
|
import { Context, Service } from './context';
|
||||||
import { Logger } from './logger';
|
import { Logger } from './logger';
|
||||||
|
import {
|
||||||
|
AccountSetting, DomainSetting, DomainUserSetting, PreferenceSetting, SystemSetting,
|
||||||
|
} from './model/setting';
|
||||||
|
|
||||||
const logger = new Logger('settings');
|
const logger = new Logger('settings');
|
||||||
|
|
||||||
declare module './context' {
|
declare module 'cordis' {
|
||||||
interface Context {
|
interface Context {
|
||||||
config: ConfigService;
|
setting: SettingService;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ConfigService extends Service {
|
const T = <F extends (...args: any[]) => any>(origFunc: F, disposeFunc?) =>
|
||||||
|
function method(this: Service, ...args: Parameters<F>) {
|
||||||
|
this.ctx.effect(() => {
|
||||||
|
const res = origFunc(...args);
|
||||||
|
return () => (disposeFunc ? disposeFunc(res) : res());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export class SettingService extends Service {
|
||||||
static inject = ['db'];
|
static inject = ['db'];
|
||||||
static name = 'config';
|
static name = 'setting';
|
||||||
static blacklist = ['__proto__', 'prototype', 'constructor'];
|
static blacklist = ['__proto__', 'prototype', 'constructor'];
|
||||||
|
|
||||||
settings: Schema[] = [];
|
settings: Schema[] = [];
|
||||||
@@ -23,8 +34,14 @@ export class ConfigService extends Service {
|
|||||||
private initialValues = {};
|
private initialValues = {};
|
||||||
private _lastMigrate = Promise.resolve();
|
private _lastMigrate = Promise.resolve();
|
||||||
|
|
||||||
|
PreferenceSetting = T(PreferenceSetting);
|
||||||
|
AccountSetting = T(AccountSetting);
|
||||||
|
DomainSetting = T(DomainSetting);
|
||||||
|
DomainUserSetting = T(DomainUserSetting);
|
||||||
|
SystemSetting = T(SystemSetting);
|
||||||
|
|
||||||
constructor(ctx: Context) {
|
constructor(ctx: Context) {
|
||||||
super(ctx, 'config');
|
super(ctx, 'setting');
|
||||||
}
|
}
|
||||||
|
|
||||||
async [Context.init]() {
|
async [Context.init]() {
|
||||||
@@ -54,7 +71,7 @@ export class ConfigService extends Service {
|
|||||||
|
|
||||||
applyDelta(source: any, key: string, value: any) {
|
applyDelta(source: any, key: string, value: any) {
|
||||||
const path = key.split('.');
|
const path = key.split('.');
|
||||||
if (path.some((i) => ConfigService.blacklist.includes(i))) return false;
|
if (path.some((i) => SettingService.blacklist.includes(i))) return false;
|
||||||
const t = path.pop();
|
const t = path.pop();
|
||||||
const root = JSON.parse(JSON.stringify(source));
|
const root = JSON.parse(JSON.stringify(source));
|
||||||
let cursor = root;
|
let cursor = root;
|
||||||
@@ -111,9 +128,9 @@ export class ConfigService extends Service {
|
|||||||
await this._lastMigrate;
|
await this._lastMigrate;
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key: string) {
|
_get(key: string) {
|
||||||
const parts = key.split('.');
|
const parts = key.split('.');
|
||||||
if (parts.some((p) => ConfigService.blacklist.includes(p.toString()))) throw new Error('Invalid path');
|
if (parts.some((p) => SettingService.blacklist.includes(p.toString()))) throw new Error('Invalid path');
|
||||||
let currentValue = this.applied;
|
let currentValue = this.applied;
|
||||||
for (const p of parts) {
|
for (const p of parts) {
|
||||||
if (!currentValue) return undefined;
|
if (!currentValue) return undefined;
|
||||||
@@ -122,6 +139,11 @@ export class ConfigService extends Service {
|
|||||||
return currentValue;
|
return currentValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get(key: string) {
|
||||||
|
return (this.ctx ? this.ctx.domain?.config?.[key.replace(/\./g, '$')] : null)
|
||||||
|
?? (this._get(key) || global.Hydro?.model?.system?.get?.(key));
|
||||||
|
}
|
||||||
|
|
||||||
async setConfig(key: string, value: any) {
|
async setConfig(key: string, value: any) {
|
||||||
const newConfig = this.applyDelta(this.systemConfig, key, value);
|
const newConfig = this.applyDelta(this.systemConfig, key, value);
|
||||||
await this.saveConfig(newConfig);
|
await this.saveConfig(newConfig);
|
||||||
@@ -150,7 +172,7 @@ export class ConfigService extends Service {
|
|||||||
});
|
});
|
||||||
const that = this;
|
const that = this;
|
||||||
const getAccess = (path: (string | symbol)[]) => {
|
const getAccess = (path: (string | symbol)[]) => {
|
||||||
if (path.some((p) => ConfigService.blacklist.includes(p.toString()))) throw new Error('Invalid path');
|
if (path.some((p) => SettingService.blacklist.includes(p.toString()))) throw new Error('Invalid path');
|
||||||
let currentValue = curValue;
|
let currentValue = curValue;
|
||||||
for (const p of path) {
|
for (const p of path) {
|
||||||
currentValue = currentValue[p];
|
currentValue = currentValue[p];
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ export const coreScripts: MigrationScript[] = [
|
|||||||
'file.pathStyle', 'file.endPointForUser', 'file.endPointForJudge',
|
'file.pathStyle', 'file.endPointForUser', 'file.endPointForJudge',
|
||||||
] as any[]) as any;
|
] as any[]) as any;
|
||||||
if ((endPoint && accessKey) || process.env.MINIO_ACCESS_KEY) {
|
if ((endPoint && accessKey) || process.env.MINIO_ACCESS_KEY) {
|
||||||
await app.get('config').setConfig('file', {
|
await app.get('setting').setConfig('file', {
|
||||||
type: 's3',
|
type: 's3',
|
||||||
endPoint: process.env.MINIO_ACCESS_KEY ? 'http://127.0.0.1:9000/' : endPoint,
|
endPoint: process.env.MINIO_ACCESS_KEY ? 'http://127.0.0.1:9000/' : endPoint,
|
||||||
accessKey: process.env.MINIO_ACCESS_KEY || accessKey,
|
accessKey: process.env.MINIO_ACCESS_KEY || accessKey,
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ const ProblemSchema = Schema.object({
|
|||||||
})),
|
})),
|
||||||
hint: StringValue,
|
hint: StringValue,
|
||||||
source: Schema.union([StringValue, Schema.string()]),
|
source: Schema.union([StringValue, Schema.string()]),
|
||||||
display_id: Schema.string(),
|
display_id: Schema.transform(
|
||||||
|
Schema.union([Schema.number(), Schema.string()]),
|
||||||
|
(value) => (typeof value === 'number' ? `P${value.toString()}` : value),
|
||||||
|
),
|
||||||
time_limit: Schema.union([Schema.number(), Schema.string()]).required(),
|
time_limit: Schema.union([Schema.number(), Schema.string()]).required(),
|
||||||
memory_limit: Schema.union([Schema.number(), Schema.string()]).required(),
|
memory_limit: Schema.union([Schema.number(), Schema.string()]).required(),
|
||||||
spj: Schema.union([
|
spj: Schema.union([
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/import-qduoj",
|
"name": "@hydrooj/import-qduoj",
|
||||||
"version": "1.6.0-beta.1",
|
"version": "1.6.0-beta.2",
|
||||||
"description": "Import QDUOJ problem export",
|
"description": "Import QDUOJ problem export",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"repository": "https://github.com/hydro-dev/Hydro.git",
|
"repository": "https://github.com/hydro-dev/Hydro.git",
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/migrate",
|
"name": "@hydrooj/migrate",
|
||||||
"version": "0.5.0-beta.1",
|
"version": "0.5.0-beta.2",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
"repository": "git@github.com:hydro-dev/Hydro.git",
|
"repository": "git@github.com:hydro-dev/Hydro.git",
|
||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mariadb": "^3.4.2",
|
"mariadb": "^3.4.2",
|
||||||
"mongodb": "^6.16.0",
|
"mongodb": "^6.17.0",
|
||||||
"turndown": "^7.2.0",
|
"turndown": "^7.2.0",
|
||||||
"xml2js": "^0.6.2"
|
"xml2js": "^0.6.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export async function run({
|
|||||||
const precheck = await UserModel.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(', ')}`);
|
if (precheck.length) throw new Error(`Conflict username: ${precheck.map((u) => u.unameLower).join(', ')}`);
|
||||||
for (const udoc of udocs) {
|
for (const udoc of udocs) {
|
||||||
if (randomMail === 'always') delete udoc.email;
|
if (randomMail === 'always' || !udoc.email.includes('@')) delete udoc.email;
|
||||||
let current = await UserModel.getByEmail(domainId, udoc.email || `${udoc.username}@syzoj.local`);
|
let current = await UserModel.getByEmail(domainId, udoc.email || `${udoc.username}@syzoj.local`);
|
||||||
current ||= await UserModel.getByUname(domainId, udoc.username);
|
current ||= await UserModel.getByUname(domainId, udoc.username);
|
||||||
if (current && randomMail === 'needed') {
|
if (current && randomMail === 'needed') {
|
||||||
@@ -147,6 +147,7 @@ export async function run({
|
|||||||
displayName: udoc.nickname || '',
|
displayName: udoc.nickname || '',
|
||||||
nSubmit: udoc.submit_num,
|
nSubmit: udoc.submit_num,
|
||||||
nAccept: udoc.ac_num,
|
nAccept: udoc.ac_num,
|
||||||
|
join: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,7 +360,7 @@ export async function run({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rdoc.type) {
|
if (rdoc.type && rdoc.type_info && tidMap[rdoc.type_info]) {
|
||||||
data.contest = new ObjectId(tidMap[rdoc.type_info]);
|
data.contest = new ObjectId(tidMap[rdoc.type_info]);
|
||||||
await ContestModel.attend(domainId, data.contest, uidMap[rdoc.user_id]).catch(noop);
|
await ContestModel.attend(domainId, data.contest, uidMap[rdoc.user_id]).catch(noop);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ export function apply(ctx: Context) {
|
|||||||
let oid = 1;
|
let oid = 1;
|
||||||
for (const i of organizations) orgId[i] = `org-${oid++}`;
|
for (const i of organizations) orgId[i] = `org-${oid++}`;
|
||||||
const duration = moment(tdoc.endAt).diff(tdoc.beginAt, 'seconds');
|
const duration = moment(tdoc.endAt).diff(tdoc.beginAt, 'seconds');
|
||||||
const lockDuration = tdoc.lockAt ? moment(tdoc.lockAt).diff(tdoc.beginAt, 'seconds') : null;
|
const lockDuration = tdoc.lockAt ? moment(tdoc.endAt).diff(tdoc.lockAt, 'seconds') : null;
|
||||||
const eventfeed: Record<string, any>[] = [
|
const eventfeed: Record<string, any>[] = [
|
||||||
getFeed('contest', {
|
getFeed('contest', {
|
||||||
id: tdoc._id.toHexString(),
|
id: tdoc._id.toHexString(),
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/onsite-toolkit",
|
"name": "@hydrooj/onsite-toolkit",
|
||||||
"version": "0.1.0-beta.1",
|
"version": "0.1.0-beta.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-spring/web": "^9.7.5",
|
"@react-spring/web": "^10.0.1",
|
||||||
"react-use": "^17.6.0"
|
"react-use": "^17.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,10 +7,10 @@ const highlighterPage = new AutoloadPage('highlighterPage', () => {
|
|||||||
function runHighlight($container) {
|
function runHighlight($container) {
|
||||||
$container.find('pre code').get().forEach((code) => {
|
$container.find('pre code').get().forEach((code) => {
|
||||||
const language = ($(code).attr('class') || '').trim();
|
const language = ($(code).attr('class') || '').trim();
|
||||||
|
if (language.includes('line-numbers')) $(code).parent().addClass('line-numbers');
|
||||||
const m = language.match(/language-([a-z0-9]+)(\|[\d,-]+)/);
|
const m = language.match(/language-([a-z0-9]+)(\|[\d,-]+)/);
|
||||||
if (m?.[2]) {
|
if (m?.[2]) {
|
||||||
$(code).parent().attr('data-line', m[2].substring(1));
|
$(code).parent().attr('data-line', m[2].substring(1));
|
||||||
if (language.includes('line-numbers')) $(code).parent().addClass('line-numbers');
|
|
||||||
$(code).attr('class', `language-${m[1]}`);
|
$(code).attr('class', `language-${m[1]}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -64,10 +64,7 @@ let scratchpad: ScratchpadService;
|
|||||||
|
|
||||||
export default function ScratchpadContainer() {
|
export default function ScratchpadContainer() {
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
if (!scratchpad) {
|
scratchpad ||= new ScratchpadService(store);
|
||||||
scratchpad = new ScratchpadService(store);
|
|
||||||
ctx.provide('scratchpad', scratchpad);
|
|
||||||
}
|
|
||||||
const [, updateState] = React.useState<any>();
|
const [, updateState] = React.useState<any>();
|
||||||
const forceUpdate = React.useCallback(() => updateState({}), []);
|
const forceUpdate = React.useCallback(() => updateState({}), []);
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class MarkdownHandler extends Handler {
|
|||||||
|
|
||||||
class SystemConfigSchemaHandler extends Handler {
|
class SystemConfigSchemaHandler extends Handler {
|
||||||
async get() {
|
async get() {
|
||||||
const schema = convert(Schema.intersect(this.ctx.config.settings) as any, true);
|
const schema = convert(Schema.intersect(this.ctx.setting.settings) as any, true);
|
||||||
this.response.body = schema;
|
this.response.body = schema;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ __langname: 简体中文
|
|||||||
'{0} comments': '{0} 条评论'
|
'{0} comments': '{0} 条评论'
|
||||||
'{0} discussions': '{0} 条讨论'
|
'{0} discussions': '{0} 条讨论'
|
||||||
'{0} is a moderator of this domain.': '{}是这个域的管理者之一。'
|
'{0} is a moderator of this domain.': '{}是这个域的管理者之一。'
|
||||||
|
'{0} is a superuser.': '{0}是超级管理员。'
|
||||||
'{0} limit exceeded (limit: {2} operations in {1} seconds).': '{0} 超过频率限制。限制:{1} 秒内最多 {2} 次操作。'
|
'{0} limit exceeded (limit: {2} operations in {1} seconds).': '{0} 超过频率限制。限制:{1} 秒内最多 {2} 次操作。'
|
||||||
'{0} problems': '{0} 道题'
|
'{0} problems': '{0} 道题'
|
||||||
'{0} sections': '{0} 小节'
|
'{0} sections': '{0} 小节'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/ui-default",
|
"name": "@hydrooj/ui-default",
|
||||||
"version": "4.58.0-beta.2",
|
"version": "4.58.0-beta.3",
|
||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "index.ts",
|
"main": "index.ts",
|
||||||
@@ -12,17 +12,17 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@blueprintjs/core": "^5.19.0",
|
"@blueprintjs/core": "^5.19.0",
|
||||||
"@fontsource/dm-mono": "^5.2.5",
|
"@fontsource/dm-mono": "^5.2.6",
|
||||||
"@fontsource/fira-code": "^5.2.6",
|
"@fontsource/fira-code": "^5.2.6",
|
||||||
"@fontsource/inconsolata": "^5.2.5",
|
"@fontsource/inconsolata": "^5.2.6",
|
||||||
"@fontsource/jetbrains-mono": "^5.2.5",
|
"@fontsource/jetbrains-mono": "^5.2.6",
|
||||||
"@fontsource/pt-mono": "^5.2.5",
|
"@fontsource/pt-mono": "^5.2.6",
|
||||||
"@fontsource/roboto-mono": "^5.2.5",
|
"@fontsource/roboto-mono": "^5.2.6",
|
||||||
"@fontsource/source-code-pro": "^5.2.5",
|
"@fontsource/source-code-pro": "^5.2.6",
|
||||||
"@fontsource/ubuntu-mono": "^5.2.5",
|
"@fontsource/ubuntu-mono": "^5.2.6",
|
||||||
"@hydrooj/utils": "workspace:^",
|
"@hydrooj/utils": "workspace:^",
|
||||||
"@sentry/browser": "^9.17.0",
|
"@sentry/browser": "^9.27.0",
|
||||||
"@sentry/webpack-plugin": "^3.4.0",
|
"@sentry/webpack-plugin": "^3.5.0",
|
||||||
"@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",
|
||||||
@@ -34,8 +34,8 @@
|
|||||||
"@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": "^19.1.3",
|
"@types/react": "^18.3.23",
|
||||||
"@types/react-dom": "^19.1.3",
|
"@types/react-dom": "^18.3.7",
|
||||||
"@types/redux-logger": "^3.0.13",
|
"@types/redux-logger": "^3.0.13",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@types/webpack-env": "^1.18.8",
|
"@types/webpack-env": "^1.18.8",
|
||||||
@@ -49,12 +49,12 @@
|
|||||||
"clean-webpack-plugin": "^4.0.0",
|
"clean-webpack-plugin": "^4.0.0",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"copy-webpack-plugin": "^13.0.0",
|
"copy-webpack-plugin": "^13.0.0",
|
||||||
"cordis": "4.0.0-beta.2",
|
"cordis": "4.0.0-beta.5",
|
||||||
"core-js": "^3.42.0",
|
"core-js": "^3.43.0",
|
||||||
"core-js-compat": "^3.42.0",
|
"core-js-compat": "^3.43.0",
|
||||||
"css-loader": "^7.1.2",
|
"css-loader": "^7.1.2",
|
||||||
"diff": "^7.0.0",
|
"diff": "^8.0.2",
|
||||||
"diff-dom": "^5.1.4",
|
"diff-dom": "^5.2.0",
|
||||||
"echarts": "^5.6.0",
|
"echarts": "^5.6.0",
|
||||||
"emojis-keywords": "2.0.0",
|
"emojis-keywords": "2.0.0",
|
||||||
"emojis-list": "2.1.0",
|
"emojis-list": "2.1.0",
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
"jquery.easing": "^1.4.1",
|
"jquery.easing": "^1.4.1",
|
||||||
"jquery.transit": "^0.9.12",
|
"jquery.transit": "^0.9.12",
|
||||||
"matchmedia-polyfill": "^0.3.2",
|
"matchmedia-polyfill": "^0.3.2",
|
||||||
"md-editor-rt": "^5.5.1",
|
"md-editor-rt": "^5.6.1",
|
||||||
"mini-css-extract-plugin": "^2.9.2",
|
"mini-css-extract-plugin": "^2.9.2",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"monaco-editor": "0.52.2",
|
"monaco-editor": "0.52.2",
|
||||||
@@ -98,10 +98,9 @@
|
|||||||
"redux-promise-middleware": "^6.2.0",
|
"redux-promise-middleware": "^6.2.0",
|
||||||
"redux-thunk": "^3.1.0",
|
"redux-thunk": "^3.1.0",
|
||||||
"rupture": "^0.7.1",
|
"rupture": "^0.7.1",
|
||||||
"sass": "^1.87.0",
|
"sass": "^1.89.1",
|
||||||
"sass-loader": "^16.0.5",
|
"sass-loader": "^16.0.5",
|
||||||
"schemastery": "^3.16.1",
|
"schemastery": "^3.16.1",
|
||||||
"schemastery-react": "^0.1.2",
|
|
||||||
"shorty.js": "^1.0.1",
|
"shorty.js": "^1.0.1",
|
||||||
"slideout": "^1.0.1",
|
"slideout": "^1.0.1",
|
||||||
"sticky-kit": "^1.1.3",
|
"sticky-kit": "^1.1.3",
|
||||||
@@ -115,15 +114,15 @@
|
|||||||
"timeago.js": "^4.0.2",
|
"timeago.js": "^4.0.2",
|
||||||
"ts-loader": "^9.5.2",
|
"ts-loader": "^9.5.2",
|
||||||
"web-streams-polyfill": "^4.1.0",
|
"web-streams-polyfill": "^4.1.0",
|
||||||
"webpack": "^5.99.8",
|
"webpack": "^5.99.9",
|
||||||
"webpack-bundle-analyzer": "^4.10.2",
|
"webpack-bundle-analyzer": "^4.10.2",
|
||||||
"webpack-dev-server": "^5.2.1",
|
"webpack-dev-server": "^5.2.2",
|
||||||
"webpack-manifest-plugin": "^5.0.1",
|
"webpack-manifest-plugin": "^5.0.1",
|
||||||
"webpackbar": "^7.0.0"
|
"webpackbar": "^7.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hydrooj/common": "workspace:^",
|
"@hydrooj/common": "workspace:^",
|
||||||
"ansi_up": "^6.0.5",
|
"ansi_up": "^6.0.6",
|
||||||
"cssfilter": "^0.0.11",
|
"cssfilter": "^0.0.11",
|
||||||
"esbuild": "0.25.2",
|
"esbuild": "0.25.2",
|
||||||
"fs-extra": "^11.3.0",
|
"fs-extra": "^11.3.0",
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import { NamedPage } from 'vj/misc/Page';
|
|||||||
import { i18n, request } from 'vj/utils';
|
import { i18n, request } from 'vj/utils';
|
||||||
|
|
||||||
const page = new NamedPage('manage_config', async () => {
|
const page = new NamedPage('manage_config', async () => {
|
||||||
|
if (document.documentElement.className.includes('theme--dark')) {
|
||||||
|
document.documentElement.className += ' dark';
|
||||||
|
}
|
||||||
|
|
||||||
const [{ ConfigEditor, ComponentsProvider }, { load }] = await Promise.all([
|
const [{ ConfigEditor, ComponentsProvider }, { load }] = await Promise.all([
|
||||||
import('@hydrooj/components'),
|
import('@hydrooj/components'),
|
||||||
import('vj/components/monaco/loader'),
|
import('vj/components/monaco/loader'),
|
||||||
@@ -35,6 +39,7 @@ const page = new NamedPage('manage_config', async () => {
|
|||||||
onSave={(value) => {
|
onSave={(value) => {
|
||||||
request.post('', { value }).then(() => {
|
request.post('', { value }).then(() => {
|
||||||
Notification.success(i18n('Changes saved successfully'));
|
Notification.success(i18n('Changes saved successfully'));
|
||||||
|
window.location.reload();
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
Notification.error(i18n('Failed to save changes:'), e.message);
|
Notification.error(i18n('Failed to save changes:'), e.message);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ function shouldCachePath(path: string) {
|
|||||||
if (!path.split('?')[0].split('/').pop()) return false;
|
if (!path.split('?')[0].split('/').pop()) return false;
|
||||||
if (!path.split('?')[0].split('/').pop().includes('.')) return false;
|
if (!path.split('?')[0].split('/').pop().includes('.')) return false;
|
||||||
if (process.env.NODE_ENV !== 'production' && (path.includes('.hot-update.') || path.includes('?version='))) return false;
|
if (process.env.NODE_ENV !== 'production' && (path.includes('.hot-update.') || path.includes('?version='))) return false;
|
||||||
|
if (path.includes('?v=')) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
function shouldCache(request: Request) {
|
function shouldCache(request: Request) {
|
||||||
|
|||||||
@@ -142,12 +142,12 @@
|
|||||||
{% if handler.user.hasPerm(reply_post_perm) %}
|
{% if handler.user.hasPerm(reply_post_perm) %}
|
||||||
<a href="javascript:;" data-tooltip="{{ _('Reply') }}" data-op="reply" data-type="reply"><span class="icon icon-reply"></span></a>
|
<a href="javascript:;" data-tooltip="{{ _('Reply') }}" data-op="reply" data-type="reply"><span class="icon icon-reply"></span></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if (handler.user.hasPerm(reply_edit_self_perm) and handler.user.own(doc)) or handler.user.hasPerm(reply_edit_perm) %}
|
{% if (handler.user.hasPerm(reply_edit_self_perm) and handler.user.own(rdoc)) or handler.user.hasPerm(reply_edit_perm) %}
|
||||||
<a href="javascript:;" data-tooltip="{{ _('Edit') }}" data-op="edit" data-type="reply"
|
<a href="javascript:;" data-tooltip="{{ _('Edit') }}" data-op="edit" data-type="reply"
|
||||||
data-form="{{ {operation: reply_edit_op, psid: doc._id, psrid: rdoc._id}|json }}"
|
data-form="{{ {operation: reply_edit_op, psid: doc._id, psrid: rdoc._id}|json }}"
|
||||||
><span class="icon icon-edit"></span></a>
|
><span class="icon icon-edit"></span></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if (handler.user.hasPerm(reply_delete_self_perm) and handler.user.own(doc)) or handler.user.hasPerm(reply_delete_perm) %}
|
{% if (handler.user.hasPerm(reply_delete_self_perm) and handler.user.own(rdoc)) or handler.user.hasPerm(reply_delete_perm) %}
|
||||||
<a href="javascript:;" data-tooltip="{{ _('Delete') }}" data-op="delete" data-type="reply"
|
<a href="javascript:;" data-tooltip="{{ _('Delete') }}" data-op="delete" data-type="reply"
|
||||||
data-form="{{ {operation: reply_delete_op, psid: doc._id, psrid: rdoc._id}|json }}"
|
data-form="{{ {operation: reply_delete_op, psid: doc._id, psrid: rdoc._id}|json }}"
|
||||||
><span class="icon icon-delete"></span></a>
|
><span class="icon icon-delete"></span></a>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<li class="footer__category__item"><a href="https://github.com/hydro-dev/Hydro" target="_blank">{{ _('Open Source') }}</a></li>
|
<li class="footer__category__item"><a href="https://github.com/hydro-dev/Hydro" target="_blank">{{ _('Open Source') }}</a></li>
|
||||||
</ul></div>
|
</ul></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="medium-3 large-2 columns footer__category expandable end">
|
<div class="medium-3 large-2 columns footer__category expandable">
|
||||||
<h1>
|
<h1>
|
||||||
{{ _('Support') }}
|
{{ _('Support') }}
|
||||||
<span class="expand-icon">
|
<span class="expand-icon">
|
||||||
@@ -34,7 +34,21 @@
|
|||||||
</h1>
|
</h1>
|
||||||
<div class="footer__category__expander"><ul class="footer__category__list">
|
<div class="footer__category__expander"><ul class="footer__category__list">
|
||||||
<li class="footer__category__item"><a href="{{ url('wiki_help') }}">{{ _('Help') }}</a></li>
|
<li class="footer__category__item"><a href="{{ url('wiki_help') }}">{{ _('Help') }}</a></li>
|
||||||
<li class="footer__category__item"><a href="{{ url('wiki_about', anchor='contact') }}">{{ _('QQ Group') }}</a></li>
|
<li class="footer__category__item"><a href="{{ url('wiki_about', anchor='contact') }}">{{ _('Contact Us') }}</a></li>
|
||||||
|
</ul></div>
|
||||||
|
</div>
|
||||||
|
<div class="medium-3 large-2 columns footer__category expandable end">
|
||||||
|
<h1>
|
||||||
|
{{ _('About') }}
|
||||||
|
<span class="expand-icon">
|
||||||
|
<span class="icon icon-expand_more"></span>
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<div class="footer__category__expander"><ul class="footer__category__list">
|
||||||
|
<li class="footer__category__item"><a href="{{ url('wiki_about') }}">{{ _('About') }}</a></li>
|
||||||
|
<li class="footer__category__item"><a href="{{ url('wiki_about', anchor='privacy') }}">{{ _('Privacy') }}</a></li>
|
||||||
|
<li class="footer__category__item"><a href="{{ url('wiki_about', anchor='tos') }}">{{ _('Terms of Service') }}</a></li>
|
||||||
|
<li class="footer__category__item"><a href="{{ url('wiki_about', anchor='contact') }}">{{ _('Copyright Complaint') }}</a></li>
|
||||||
</ul></div>
|
</ul></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,11 +56,13 @@
|
|||||||
<div class="footer__extra-link clearfix">
|
<div class="footer__extra-link clearfix">
|
||||||
<div class="footer__extra-left">
|
<div class="footer__extra-left">
|
||||||
<ol class="clearfix">
|
<ol class="clearfix">
|
||||||
|
{% if not show_topics|default(true) %}
|
||||||
<li class="footer__extra-link-item"><a href="{{ url('wiki_about') }}">{{ _('About') }}</a></li>
|
<li class="footer__extra-link-item"><a href="{{ url('wiki_about') }}">{{ _('About') }}</a></li>
|
||||||
<li class="footer__extra-link-item"><a href="{{ url('wiki_about', anchor='contact') }}">{{ _('Contact Us') }}</a></li>
|
<li class="footer__extra-link-item"><a href="{{ url('wiki_about', anchor='contact') }}">{{ _('Contact Us') }}</a></li>
|
||||||
<li class="footer__extra-link-item"><a href="{{ url('wiki_about', anchor='privacy') }}">{{ _('Privacy') }}</a></li>
|
<li class="footer__extra-link-item"><a href="{{ url('wiki_about', anchor='privacy') }}">{{ _('Privacy') }}</a></li>
|
||||||
<li class="footer__extra-link-item"><a href="{{ url('wiki_about', anchor='tos') }}">{{ _('Terms of Service') }}</a></li>
|
<li class="footer__extra-link-item"><a href="{{ url('wiki_about', anchor='tos') }}">{{ _('Terms of Service') }}</a></li>
|
||||||
<li class="footer__extra-link-item"><a href="{{ url('wiki_about', anchor='contact') }}">{{ _('Copyright Complaint') }}</a></li>
|
<li class="footer__extra-link-item"><a href="{{ url('wiki_about', anchor='contact') }}">{{ _('Copyright Complaint') }}</a></li>
|
||||||
|
{% endif %}
|
||||||
<li class="footer__extra-link-item nojs--hide" data-dropdown-target="#menu-footer-lang">
|
<li class="footer__extra-link-item nojs--hide" data-dropdown-target="#menu-footer-lang">
|
||||||
<span><span class="icon icon-global"></span> Language <span class="icon icon-expand_less"></span></span>
|
<span><span class="icon icon-global"></span> Language <span class="icon icon-expand_less"></span></span>
|
||||||
<ol class="dropdown-target menu" id="menu-footer-lang">
|
<ol class="dropdown-target menu" id="menu-footer-lang">
|
||||||
|
|||||||
@@ -8,14 +8,14 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vitejs/plugin-react": "^4.4.1",
|
"@vitejs/plugin-react": "^4.5.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1"
|
"react-dom": "^18.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^19.1.3",
|
"@types/react": "^18.3.23",
|
||||||
"@types/react-dom": "^19.1.3",
|
"@types/react-dom": "^18.3.7",
|
||||||
"@vitejs/plugin-react-swc": "^3.9.0",
|
"@vitejs/plugin-react-swc": "^3.10.1",
|
||||||
"vite": "^6.3.5"
|
"vite": "^6.3.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,8 +5,7 @@ import { Duplex, PassThrough, Writable } from 'stream';
|
|||||||
import { inspect } from 'util';
|
import { inspect } from 'util';
|
||||||
import type { Entry, ZipReader } from '@zip.js/zip.js';
|
import type { Entry, ZipReader } from '@zip.js/zip.js';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import moment, { isMoment, Moment } from 'moment-timezone';
|
import type { Moment } from 'moment';
|
||||||
import { ObjectId } from 'mongodb';
|
|
||||||
import { Exporter, Factory, Logger as Reggol } from 'reggol';
|
import { Exporter, Factory, Logger as Reggol } from 'reggol';
|
||||||
import type * as superagent from 'superagent';
|
import type * as superagent from 'superagent';
|
||||||
export * as yaml from 'js-yaml';
|
export * as yaml from 'js-yaml';
|
||||||
@@ -25,8 +24,8 @@ factory.addExporter(new Exporter.Console({
|
|||||||
margin: 1,
|
margin: 1,
|
||||||
},
|
},
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
|
levels: { default: process.env.DEV ? 3 : 2 },
|
||||||
}));
|
}));
|
||||||
factory.exporters.get(1).levels = { default: process.env.DEV ? 3 : 2 };
|
|
||||||
|
|
||||||
function createLogger(name: string) {
|
function createLogger(name: string) {
|
||||||
return factory.createLogger(name);
|
return factory.createLogger(name);
|
||||||
@@ -34,7 +33,6 @@ function createLogger(name: string) {
|
|||||||
|
|
||||||
export type Logger = Reggol & { new(name: string): Reggol & Logger };
|
export type Logger = Reggol & { new(name: string): Reggol & Logger };
|
||||||
export const Logger = createLogger as any as Logger;
|
export const Logger = createLogger as any as Logger;
|
||||||
export { moment };
|
|
||||||
|
|
||||||
const encrypt = (algorithm, content) => crypto.createHash(algorithm).update(content).digest('hex');
|
const encrypt = (algorithm, content) => crypto.createHash(algorithm).update(content).digest('hex');
|
||||||
export const sha1 = (content: string) => encrypt('sha1', content);
|
export const sha1 = (content: string) => encrypt('sha1', content);
|
||||||
@@ -146,6 +144,18 @@ export namespace Time {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getObjectID(timestamp: string | Date | Moment, allZero = true) {
|
export function getObjectID(timestamp: string | Date | Moment, allZero = true) {
|
||||||
|
let isMoment: (x: any) => x is Moment;
|
||||||
|
let ObjectId: typeof import('bson').ObjectId; // eslint-disable-line
|
||||||
|
try {
|
||||||
|
({ ObjectId } = require('bson'));
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('No bson module found');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
({ isMoment } = require('moment'));
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('No moment module found');
|
||||||
|
}
|
||||||
let _timestamp: number;
|
let _timestamp: number;
|
||||||
if (typeof timestamp === 'string') _timestamp = new Date(timestamp).getTime();
|
if (typeof timestamp === 'string') _timestamp = new Date(timestamp).getTime();
|
||||||
else if (isMoment(timestamp)) _timestamp = timestamp.toDate().getTime();
|
else if (isMoment(timestamp)) _timestamp = timestamp.toDate().getTime();
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/utils",
|
"name": "@hydrooj/utils",
|
||||||
"version": "1.4.34-beta.2",
|
"version": "1.4.34-beta.3",
|
||||||
"description": "hydrooj utils",
|
"description": "hydrooj utils",
|
||||||
"main": "lib/utils.ts",
|
"main": "lib/utils.ts",
|
||||||
"repository": "https://github.com/hydro-dev/Hydro.git",
|
"repository": "https://github.com/hydro-dev/Hydro.git",
|
||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"preferUnplugged": true,
|
"preferUnplugged": true,
|
||||||
|
"optionalPeerDependencies": {
|
||||||
|
"bson": "^6.17.0",
|
||||||
|
"moment-timezone": "^0.5.48"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hydrooj/register": "workspace:^",
|
"@hydrooj/register": "workspace:^",
|
||||||
"cac": "^6.7.14",
|
"cac": "^6.7.14",
|
||||||
"fs-extra": "^11.3.0",
|
"fs-extra": "^11.3.0",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"moment-timezone": "^0.5.48",
|
|
||||||
"mongodb": "^6.16.0",
|
|
||||||
"reggol": "^2.1.0",
|
"reggol": "^2.1.0",
|
||||||
"search-query-parser": "^1.6.0",
|
"search-query-parser": "^1.6.0",
|
||||||
"systeminformation": "^5.25.11"
|
"systeminformation": "^5.27.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/fs-extra": "^11.0.4"
|
"@types/fs-extra": "^11.0.4"
|
||||||
|
|||||||
Reference in new issue
Block a user