From 80a9c6a399cad36961716aaeb2b3f67618190564 Mon Sep 17 00:00:00 2001 From: Baoshuo Date: Sun, 25 May 2025 19:50:46 +0800 Subject: [PATCH 01/24] fix: prevent update root role in DomainPermissionHandler (#984) --- packages/hydrooj/src/handler/domain.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/hydrooj/src/handler/domain.ts b/packages/hydrooj/src/handler/domain.ts index 85013ca08..d6f6ec27d 100644 --- a/packages/hydrooj/src/handler/domain.ts +++ b/packages/hydrooj/src/handler/domain.ts @@ -214,6 +214,7 @@ class DomainPermissionHandler extends ManageHandler { async post({ domainId }) { const roles = {}; for (const role in this.request.body) { + if (role === 'root') continue; // root role is not editable const perms = this.request.body[role] instanceof Array ? this.request.body[role] : [this.request.body[role]]; From d9b578d56aa10deb7506ba327b1c8cb43f2a2173 Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 28 May 2025 14:32:30 +0800 Subject: [PATCH 02/24] minor patches & bump deps --- framework/eslint-config/package.json | 10 +++++----- framework/framework/package.json | 5 +++-- framework/framework/server.ts | 27 ++++++++++++++------------- package.json | 14 +++++++------- packages/center/package.json | 2 +- packages/components/package.json | 8 ++++---- packages/elastic/package.json | 2 +- packages/hydrojudge/package.json | 4 ++-- packages/hydrooj/package.json | 10 +++++----- packages/hydrooj/src/context.ts | 13 ++++++++++++- packages/hydrooj/src/lib/ui.ts | 3 ++- packages/hydrooj/src/model/user.ts | 1 + packages/import-qduoj/index.ts | 5 ++++- packages/onsite-toolkit/package.json | 2 +- packages/ui-default/package.json | 22 +++++++++++----------- packages/ui-default/service-worker.ts | 1 + packages/ui-next/package.json | 8 ++++---- packages/utils/lib/utils.ts | 2 +- packages/utils/package.json | 2 +- 19 files changed, 80 insertions(+), 61 deletions(-) diff --git a/framework/eslint-config/package.json b/framework/eslint-config/package.json index d31a11efa..6420181d5 100644 --- a/framework/eslint-config/package.json +++ b/framework/eslint-config/package.json @@ -4,12 +4,12 @@ "license": "MIT", "main": "base.mjs", "dependencies": { - "@eslint-react/eslint-plugin": "^1.49.0", + "@eslint-react/eslint-plugin": "^1.50.0", "@eslint/compat": "^1.2.9", "@eslint/eslintrc": "^3.3.1", - "@stylistic/eslint-plugin": "^4.2.0", - "@typescript-eslint/eslint-plugin": "^8.32.0", - "@typescript-eslint/parser": "^8.32.0", + "@stylistic/eslint-plugin": "^4.4.0", + "@typescript-eslint/eslint-plugin": "^8.33.0", + "@typescript-eslint/parser": "^8.33.0", "eslint-config-airbnb": "^19.0.4", "eslint-plugin-de-morgan": "^1.2.1", "eslint-plugin-github": "^6.0.0", @@ -18,7 +18,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "eslint-plugin-simple-import-sort": "^12.1.1", - "globals": "^16.1.0" + "globals": "^16.2.0" }, "peerDependencies": { "eslint": ">= 9" diff --git a/framework/framework/package.json b/framework/framework/package.json index c4eb6eee4..0c6e0af5b 100644 --- a/framework/framework/package.json +++ b/framework/framework/package.json @@ -10,7 +10,7 @@ "@hydrooj/utils": "workspace:^", "emoji-regex": "^10.4.0", "fs-extra": "^11.3.0", - "koa": "^2.16.1", + "koa": "^3.0.0", "koa-body": "^6.0.1", "koa-compress": "^5.1.1", "koa-router": "^13.0.1", @@ -19,6 +19,7 @@ "path-to-regexp": "^8.2.0", "sanitize-filename": "^1.6.3", "saslprep": "^1.0.3", + "schemastery": "^3.16.1", "shorty.js": "^1.0.1", "ws": "^8.18.2" }, @@ -27,7 +28,7 @@ "@types/koa": "^2.15.0", "@types/koa-compress": "^4.0.6", "@types/koa-router": "^7.4.8", - "@types/lodash": "^4.17.16", + "@types/lodash": "^4.17.17", "@types/saslprep": "^1.0.3", "@types/ws": "^8.18.1" } diff --git a/framework/framework/server.ts b/framework/framework/server.ts index b2ea25063..67e5b1469 100644 --- a/framework/framework/server.ts +++ b/framework/framework/server.ts @@ -8,6 +8,7 @@ import fs from 'fs-extra'; import Koa from 'koa'; import Body from 'koa-body'; import Compress from 'koa-compress'; +import Schema from 'schemastery'; import { Shorty } from 'shorty.js'; import { WebSocket, WebSocketServer } from 'ws'; import { @@ -324,19 +325,19 @@ function executeMiddlewareStack(context: any, middlewares: { name: string, func: 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 extends Service { + 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 = Object.create(null); private registrationCount = Counter(); private serverLayers = []; @@ -352,7 +353,7 @@ export class WebService extends Service Handler = Handler; ConnectionHandler = ConnectionHandler; - constructor(ctx: C, public config: WebServiceConfig) { + constructor(ctx: C, public config: ReturnType) { super(ctx, 'server'); ctx.mixin('server', ['Route', 'Connection', 'withHandlerClass']); this.server.keys = this.config.keys; diff --git a/package.json b/package.json index f11c8bb26..756529656 100644 --- a/package.json +++ b/package.json @@ -43,22 +43,22 @@ "cross-env": "^7.0.3", "cross-spawn": "^7.0.6", "esbuild": "0.25.2", - "eslint": "^9.26.0", - "eslint-import-resolver-typescript": "^4.3.4", + "eslint": "^9.27.0", + "eslint-import-resolver-typescript": "^4.4.1", "eslint-import-resolver-webpack": "^0.13.10", "fs-extra": "^11.3.0", - "globals": "^16.1.0", + "globals": "^16.2.0", "globby": "~14.0.2", "inspectpack": "^4.7.1", "mongodb-memory-server": "10.1.4", "nyc": "^17.1.0", "ora": "^8.2.0", - "oxlint": "^0.16.9", + "oxlint": "^0.16.12", "package-json": "^10.0.1", - "semver": "^7.7.1", + "semver": "^7.7.2", "simple-git": "^3.27.0", - "supertest": "^7.1.0", - "supports-hyperlinks": "^3.2.0", + "supertest": "^7.1.1", + "supports-hyperlinks": "^4.1.0", "typescript": "5.8.3", "wtfnode": "^0.10.0" }, diff --git a/packages/center/package.json b/packages/center/package.json index b3b50dd78..23e36e413 100644 --- a/packages/center/package.json +++ b/packages/center/package.json @@ -11,6 +11,6 @@ }, "dependencies": { "crypto-js": "^4.2.0", - "semver": "^7.7.1" + "semver": "^7.7.2" } } diff --git a/packages/components/package.json b/packages/components/package.json index c67179e20..ecf466f31 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -4,7 +4,7 @@ "main": "./frontend/index.ts", "dependencies": { "allotment": "^1.20.3", - "diff": "^7.0.0", + "diff": "^8.0.2", "js-yaml": "^4.1.0", "lodash": "^4.17.21", "react-dnd": "^16.0.1", @@ -13,10 +13,10 @@ "schemastery-react": "^0.1.2" }, "devDependencies": { - "@types/diff": "^7.0.2", + "@types/diff": "^8.0.0", "@types/js-yaml": "^4.0.9", - "@types/lodash": "^4.17.16", - "sass": "^1.87.0" + "@types/lodash": "^4.17.17", + "sass": "^1.89.0" }, "peerDependencies": { "react": "*", diff --git a/packages/elastic/package.json b/packages/elastic/package.json index b4dd97035..0853db243 100644 --- a/packages/elastic/package.json +++ b/packages/elastic/package.json @@ -6,7 +6,7 @@ "author": "undefined ", "license": "AGPL-3.0-or-later", "dependencies": { - "@elastic/elasticsearch": "^8.18.2" + "@elastic/elasticsearch": "^9.0.2" }, "engines": { "hydrooj": ">=5.0.0-alpha.1" diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index d96861850..208295cd9 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -8,11 +8,11 @@ "dependencies": { "@hydrooj/common": "workspace:^", "@hydrooj/utils": "workspace:^", - "@zip.js/zip.js": "^2.7.60", + "@zip.js/zip.js": "^2.7.62", "cac": "^6.7.14", "p-queue": "^8.1.0", "schemastery": "^3.16.1", - "semver": "^7.7.1", + "semver": "^7.7.2", "shell-quote": "^1.8.2", "superagent": "^10.2.1", "ws": "^8.18.2" diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 17d0a99f1..8da924777 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -23,10 +23,10 @@ "@hydrooj/utils": "workspace:^", "@simplewebauthn/server": "13.1.1", "@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", "cac": "^6.7.14", - "cordis": "4.0.0-beta.2", + "cordis": "4.0.0-beta.4", "csv-stringify": "^6.5.2", "emoji-regex": "^10.4.0", "emojis-list": "2.1.0", @@ -35,7 +35,7 @@ "koa-proxies": "^0.12.4", "koa-static-cache": "^5.1.4", "lodash": "^4.17.21", - "lru-cache": "^10.4.3", + "lru-cache": "^11.1.0", "mime-types": "^3.0.1", "moment-timezone": "^0.5.48", "mongodb": "^6.16.0", @@ -47,7 +47,7 @@ "sanitize-filename": "^1.6.3", "saslprep": "^1.0.3", "schemastery": "^3.16.1", - "semver": "^7.7.1", + "semver": "^7.7.2", "shorty.js": "^1.0.1", "superagent": "^10.2.1", "tar": "6.2.1", @@ -60,7 +60,7 @@ "@types/fs-extra": "^11.0.4", "@types/js-yaml": "^4.0.9", "@types/koa-static-cache": "^5.1.4", - "@types/lodash": "^4.17.16", + "@types/lodash": "^4.17.17", "@types/mime-types": "^2.1.4", "@types/mongodb-uri": "^0.9.4", "@types/nodemailer": "^6.4.17", diff --git a/packages/hydrooj/src/context.ts b/packages/hydrooj/src/context.ts index cdbfc6e1f..0433a3211 100644 --- a/packages/hydrooj/src/context.ts +++ b/packages/hydrooj/src/context.ts @@ -71,6 +71,17 @@ export class Context extends cordis.Context { super(); this.plugin(ApiMixin); 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 }, + }, + }); } } diff --git a/packages/hydrooj/src/lib/ui.ts b/packages/hydrooj/src/lib/ui.ts index 741822ce3..9d3e61fd6 100644 --- a/packages/hydrooj/src/lib/ui.ts +++ b/packages/hydrooj/src/lib/ui.ts @@ -54,7 +54,8 @@ inject('Nav', 'record_main', { query: (handler) => (handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) ? ({ 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', 'domain_dashboard', { prefix: 'domain' }, PERM.PERM_EDIT_DOMAIN); inject('Nav', 'manage_dashboard', { prefix: 'manage' }, PRIV.PRIV_EDIT_SYSTEM); diff --git a/packages/hydrooj/src/model/user.ts b/packages/hydrooj/src/model/user.ts index fb2a0f048..6049eb529 100644 --- a/packages/hydrooj/src/model/user.ts +++ b/packages/hydrooj/src/model/user.ts @@ -189,6 +189,7 @@ async function initAndCache(udoc: Udoc, dudoc, scope: bigint = PERM.PERM_ALL) { class UserModel { static coll = coll; + static collGroup = collGroup; static User = User; static cache = cache; static defaultUser: Udoc = { diff --git a/packages/import-qduoj/index.ts b/packages/import-qduoj/index.ts index 6ace605d9..2dbe3ffb8 100644 --- a/packages/import-qduoj/index.ts +++ b/packages/import-qduoj/index.ts @@ -26,7 +26,10 @@ const ProblemSchema = Schema.object({ })), hint: StringValue, 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(), memory_limit: Schema.union([Schema.number(), Schema.string()]).required(), spj: Schema.union([ diff --git a/packages/onsite-toolkit/package.json b/packages/onsite-toolkit/package.json index 5fc7bac74..b25a99ee7 100644 --- a/packages/onsite-toolkit/package.json +++ b/packages/onsite-toolkit/package.json @@ -2,7 +2,7 @@ "name": "@hydrooj/onsite-toolkit", "version": "0.1.0-beta.1", "dependencies": { - "@react-spring/web": "^9.7.5", + "@react-spring/web": "^10.0.1", "react-use": "^17.6.0" } } diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 3e4efe31a..fc2949ecc 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -21,8 +21,8 @@ "@fontsource/source-code-pro": "^5.2.5", "@fontsource/ubuntu-mono": "^5.2.5", "@hydrooj/utils": "workspace:^", - "@sentry/browser": "^9.17.0", - "@sentry/webpack-plugin": "^3.4.0", + "@sentry/browser": "^9.22.0", + "@sentry/webpack-plugin": "^3.5.0", "@simplewebauthn/browser": "13.1.0", "@svgr/webpack": "^8.1.0", "@types/html-to-text": "^9.0.4", @@ -34,8 +34,8 @@ "@types/nunjucks": "^3.2.6", "@types/pickadate": "^3.5.35", "@types/qrcode": "^1.5.5", - "@types/react": "^19.1.3", - "@types/react-dom": "^19.1.3", + "@types/react": "^19.1.6", + "@types/react-dom": "^19.1.5", "@types/redux-logger": "^3.0.13", "@types/uuid": "^10.0.0", "@types/webpack-env": "^1.18.8", @@ -49,12 +49,12 @@ "clean-webpack-plugin": "^4.0.0", "clipboard": "^2.0.11", "copy-webpack-plugin": "^13.0.0", - "cordis": "4.0.0-beta.2", + "cordis": "4.0.0-beta.4", "core-js": "^3.42.0", "core-js-compat": "^3.42.0", "css-loader": "^7.1.2", - "diff": "^7.0.0", - "diff-dom": "^5.1.4", + "diff": "^8.0.2", + "diff-dom": "^5.2.0", "echarts": "^5.6.0", "emojis-keywords": "2.0.0", "emojis-list": "2.1.0", @@ -66,7 +66,7 @@ "jquery.easing": "^1.4.1", "jquery.transit": "^0.9.12", "matchmedia-polyfill": "^0.3.2", - "md-editor-rt": "^5.5.1", + "md-editor-rt": "^5.6.0", "mini-css-extract-plugin": "^2.9.2", "moment": "^2.30.1", "monaco-editor": "0.52.2", @@ -98,7 +98,7 @@ "redux-promise-middleware": "^6.2.0", "redux-thunk": "^3.1.0", "rupture": "^0.7.1", - "sass": "^1.87.0", + "sass": "^1.89.0", "sass-loader": "^16.0.5", "schemastery": "^3.16.1", "schemastery-react": "^0.1.2", @@ -115,7 +115,7 @@ "timeago.js": "^4.0.2", "ts-loader": "^9.5.2", "web-streams-polyfill": "^4.1.0", - "webpack": "^5.99.8", + "webpack": "^5.99.9", "webpack-bundle-analyzer": "^4.10.2", "webpack-dev-server": "^5.2.1", "webpack-manifest-plugin": "^5.0.1", @@ -123,7 +123,7 @@ }, "dependencies": { "@hydrooj/common": "workspace:^", - "ansi_up": "^6.0.5", + "ansi_up": "^6.0.6", "cssfilter": "^0.0.11", "esbuild": "0.25.2", "fs-extra": "^11.3.0", diff --git a/packages/ui-default/service-worker.ts b/packages/ui-default/service-worker.ts index 9de66a695..37c112ff8 100644 --- a/packages/ui-default/service-worker.ts +++ b/packages/ui-default/service-worker.ts @@ -95,6 +95,7 @@ function shouldCachePath(path: string) { if (!path.split('?')[0].split('/').pop()) 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 (path.includes('?v=')) return false; return true; } function shouldCache(request: Request) { diff --git a/packages/ui-next/package.json b/packages/ui-next/package.json index d0aa000dd..7ae386721 100644 --- a/packages/ui-next/package.json +++ b/packages/ui-next/package.json @@ -8,14 +8,14 @@ "preview": "vite preview" }, "dependencies": { - "@vitejs/plugin-react": "^4.4.1", + "@vitejs/plugin-react": "^4.5.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { - "@types/react": "^19.1.3", - "@types/react-dom": "^19.1.3", - "@vitejs/plugin-react-swc": "^3.9.0", + "@types/react": "^19.1.6", + "@types/react-dom": "^19.1.5", + "@vitejs/plugin-react-swc": "^3.10.0", "vite": "^6.3.5" } } diff --git a/packages/utils/lib/utils.ts b/packages/utils/lib/utils.ts index d8b308c99..ff4ae3427 100644 --- a/packages/utils/lib/utils.ts +++ b/packages/utils/lib/utils.ts @@ -25,8 +25,8 @@ factory.addExporter(new Exporter.Console({ margin: 1, }, 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) { return factory.createLogger(name); diff --git a/packages/utils/package.json b/packages/utils/package.json index d6ff7c435..a583ee0e1 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -16,7 +16,7 @@ "mongodb": "^6.16.0", "reggol": "^2.1.0", "search-query-parser": "^1.6.0", - "systeminformation": "^5.25.11" + "systeminformation": "^5.27.1" }, "devDependencies": { "@types/fs-extra": "^11.0.4" From 3a9755556e1e7ad0257ba996ca18579f383dd26c Mon Sep 17 00:00:00 2001 From: undefined Date: Thu, 29 May 2025 14:38:46 +0800 Subject: [PATCH 03/24] ui: fix highlighter line-numbers --- packages/ui-default/components/highlighter/highlighter.page.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui-default/components/highlighter/highlighter.page.ts b/packages/ui-default/components/highlighter/highlighter.page.ts index 38f9841a2..4b051d64a 100644 --- a/packages/ui-default/components/highlighter/highlighter.page.ts +++ b/packages/ui-default/components/highlighter/highlighter.page.ts @@ -7,10 +7,10 @@ const highlighterPage = new AutoloadPage('highlighterPage', () => { function runHighlight($container) { $container.find('pre code').get().forEach((code) => { 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,-]+)/); if (m?.[2]) { $(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]}`); } }); From 76a822c17957d6e6307b9413980b4737b6cae5fe Mon Sep 17 00:00:00 2001 From: Baoshuo Date: Tue, 3 Jun 2025 14:01:05 +0800 Subject: [PATCH 04/24] core: fix User.serialize (#986) --- packages/hydrooj/src/model/user.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hydrooj/src/model/user.ts b/packages/hydrooj/src/model/user.ts index 6049eb529..a68dcad42 100644 --- a/packages/hydrooj/src/model/user.ts +++ b/packages/hydrooj/src/model/user.ts @@ -159,7 +159,7 @@ export class User { return user; } - serialize(_, h) { + serialize(h) { if (!this._isPrivate) { const fields = ['_id', 'uname', 'mail', 'perm', 'role', 'priv', 'regat', 'loginat', 'tfa', 'authn']; if (h?.user?.hasPerm(PERM.PERM_VIEW_DISPLAYNAME)) fields.push('displayName'); From 001085ea7125770740c1af74ae549abd0c74717e Mon Sep 17 00:00:00 2001 From: MiaoTony <41962043+miaotony@users.noreply.github.com> Date: Tue, 3 Jun 2025 14:01:47 +0800 Subject: [PATCH 05/24] docs: update links (#985) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 05fa87d39..3f5509fae 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ 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 的权限可以按比赛/作业分配给对应的用户,也可以将用户分组(班级),按组分配权限。 -有关权限节点,可以查看 [介绍](https://docs.hydro.ac/docs/) 下方截图。 +有关权限节点,可以查看 [介绍](https://docs.hydro.ac/docs/Hydro) 下方截图。 ### 规模化支持,上千用户无压力,伸缩组秒级自动扩展 From b75bdbe326d2ed6b9dc49f2b4b3ab54bd1327102 Mon Sep 17 00:00:00 2001 From: undefined Date: Tue, 3 Jun 2025 14:07:33 +0800 Subject: [PATCH 06/24] ui: fix comments_solution permission check --- .../ui-default/templates/components/comments_solution.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui-default/templates/components/comments_solution.html b/packages/ui-default/templates/components/comments_solution.html index aa0d4203d..593ce15c1 100644 --- a/packages/ui-default/templates/components/comments_solution.html +++ b/packages/ui-default/templates/components/comments_solution.html @@ -142,12 +142,12 @@ {% if handler.user.hasPerm(reply_post_perm) %} {% 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) %} {% 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) %} From cd24cba711b28b76ea8e42e565cc1715ae28a783 Mon Sep 17 00:00:00 2001 From: undefined Date: Tue, 3 Jun 2025 21:08:26 +0800 Subject: [PATCH 07/24] bump deps --- framework/eslint-config/package.json | 4 ++-- package.json | 4 ++-- packages/components/package.json | 2 +- packages/geoip/package.json | 2 +- packages/hydrojudge/package.json | 2 +- packages/hydrooj/package.json | 2 +- packages/ui-default/components/scratchpad/index.tsx | 5 +---- packages/ui-default/package.json | 10 +++++----- packages/ui-next/package.json | 4 ++-- 9 files changed, 16 insertions(+), 19 deletions(-) diff --git a/framework/eslint-config/package.json b/framework/eslint-config/package.json index 6420181d5..73adb824e 100644 --- a/framework/eslint-config/package.json +++ b/framework/eslint-config/package.json @@ -8,8 +8,8 @@ "@eslint/compat": "^1.2.9", "@eslint/eslintrc": "^3.3.1", "@stylistic/eslint-plugin": "^4.4.0", - "@typescript-eslint/eslint-plugin": "^8.33.0", - "@typescript-eslint/parser": "^8.33.0", + "@typescript-eslint/eslint-plugin": "^8.33.1", + "@typescript-eslint/parser": "^8.33.1", "eslint-config-airbnb": "^19.0.4", "eslint-plugin-de-morgan": "^1.2.1", "eslint-plugin-github": "^6.0.0", diff --git a/package.json b/package.json index 756529656..8b5949446 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,8 @@ "cross-env": "^7.0.3", "cross-spawn": "^7.0.6", "esbuild": "0.25.2", - "eslint": "^9.27.0", - "eslint-import-resolver-typescript": "^4.4.1", + "eslint": "^9.28.0", + "eslint-import-resolver-typescript": "^4.4.2", "eslint-import-resolver-webpack": "^0.13.10", "fs-extra": "^11.3.0", "globals": "^16.2.0", diff --git a/packages/components/package.json b/packages/components/package.json index ecf466f31..82e10b970 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -16,7 +16,7 @@ "@types/diff": "^8.0.0", "@types/js-yaml": "^4.0.9", "@types/lodash": "^4.17.17", - "sass": "^1.89.0" + "sass": "^1.89.1" }, "peerDependencies": { "react": "*", diff --git a/packages/geoip/package.json b/packages/geoip/package.json index 7a7365dac..43000ce8d 100644 --- a/packages/geoip/package.json +++ b/packages/geoip/package.json @@ -6,7 +6,7 @@ "author": "undefined ", "license": "AGPL-3.0-or-later", "dependencies": { - "maxmind": "^4.3.25" + "maxmind": "^4.3.27" }, "preferUnplugged": true } diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 208295cd9..91fa51345 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -13,7 +13,7 @@ "p-queue": "^8.1.0", "schemastery": "^3.16.1", "semver": "^7.7.2", - "shell-quote": "^1.8.2", + "shell-quote": "^1.8.3", "superagent": "^10.2.1", "ws": "^8.18.2" }, diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 8da924777..586a02b08 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -26,7 +26,7 @@ "@zip.js/zip.js": "^2.7.62", "adm-zip": "0.5.16", "cac": "^6.7.14", - "cordis": "4.0.0-beta.4", + "cordis": "4.0.0-beta.5", "csv-stringify": "^6.5.2", "emoji-regex": "^10.4.0", "emojis-list": "2.1.0", diff --git a/packages/ui-default/components/scratchpad/index.tsx b/packages/ui-default/components/scratchpad/index.tsx index 92e2583cf..d7020315a 100644 --- a/packages/ui-default/components/scratchpad/index.tsx +++ b/packages/ui-default/components/scratchpad/index.tsx @@ -64,10 +64,7 @@ let scratchpad: ScratchpadService; export default function ScratchpadContainer() { const store = useStore(); - if (!scratchpad) { - scratchpad = new ScratchpadService(store); - ctx.provide('scratchpad', scratchpad); - } + scratchpad ||= new ScratchpadService(store); const [, updateState] = React.useState(); const forceUpdate = React.useCallback(() => updateState({}), []); React.useEffect(() => { diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index fc2949ecc..6417c3141 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -17,11 +17,11 @@ "@fontsource/inconsolata": "^5.2.5", "@fontsource/jetbrains-mono": "^5.2.5", "@fontsource/pt-mono": "^5.2.5", - "@fontsource/roboto-mono": "^5.2.5", - "@fontsource/source-code-pro": "^5.2.5", + "@fontsource/roboto-mono": "^5.2.6", + "@fontsource/source-code-pro": "^5.2.6", "@fontsource/ubuntu-mono": "^5.2.5", "@hydrooj/utils": "workspace:^", - "@sentry/browser": "^9.22.0", + "@sentry/browser": "^9.24.0", "@sentry/webpack-plugin": "^3.5.0", "@simplewebauthn/browser": "13.1.0", "@svgr/webpack": "^8.1.0", @@ -49,7 +49,7 @@ "clean-webpack-plugin": "^4.0.0", "clipboard": "^2.0.11", "copy-webpack-plugin": "^13.0.0", - "cordis": "4.0.0-beta.4", + "cordis": "4.0.0-beta.5", "core-js": "^3.42.0", "core-js-compat": "^3.42.0", "css-loader": "^7.1.2", @@ -98,7 +98,7 @@ "redux-promise-middleware": "^6.2.0", "redux-thunk": "^3.1.0", "rupture": "^0.7.1", - "sass": "^1.89.0", + "sass": "^1.89.1", "sass-loader": "^16.0.5", "schemastery": "^3.16.1", "schemastery-react": "^0.1.2", diff --git a/packages/ui-next/package.json b/packages/ui-next/package.json index 7ae386721..7b3a68807 100644 --- a/packages/ui-next/package.json +++ b/packages/ui-next/package.json @@ -8,14 +8,14 @@ "preview": "vite preview" }, "dependencies": { - "@vitejs/plugin-react": "^4.5.0", + "@vitejs/plugin-react": "^4.5.1", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { "@types/react": "^19.1.6", "@types/react-dom": "^19.1.5", - "@vitejs/plugin-react-swc": "^3.10.0", + "@vitejs/plugin-react-swc": "^3.10.1", "vite": "^6.3.5" } } From 23e4dbf1bee08f1d053baf1bc247b41d206a7df0 Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 4 Jun 2025 13:01:25 +0800 Subject: [PATCH 08/24] bump beta.3 --- framework/eslint-config/package.json | 2 +- framework/framework/api.ts | 2 +- framework/framework/package.json | 2 +- packages/components/package.json | 2 +- packages/hydrojudge/package.json | 2 +- packages/hydrooj/package.json | 4 ++-- packages/import-qduoj/package.json | 2 +- packages/migrate/package.json | 2 +- packages/onsite-toolkit/package.json | 2 +- packages/ui-default/package.json | 12 ++++++------ packages/ui-next/package.json | 4 ++-- packages/utils/package.json | 4 ++-- 12 files changed, 20 insertions(+), 20 deletions(-) diff --git a/framework/eslint-config/package.json b/framework/eslint-config/package.json index 73adb824e..16031b66f 100644 --- a/framework/eslint-config/package.json +++ b/framework/eslint-config/package.json @@ -4,7 +4,7 @@ "license": "MIT", "main": "base.mjs", "dependencies": { - "@eslint-react/eslint-plugin": "^1.50.0", + "@eslint-react/eslint-plugin": "^1.51.0", "@eslint/compat": "^1.2.9", "@eslint/eslintrc": "^3.3.1", "@stylistic/eslint-plugin": "^4.4.0", diff --git a/framework/framework/api.ts b/framework/framework/api.ts index 049adf0c5..930a29b84 100644 --- a/framework/framework/api.ts +++ b/framework/framework/api.ts @@ -150,7 +150,7 @@ export class ApiService extends Service { await emitHook?.(`api/before/${callOrName}`, args); } 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; while (true) { const value = await it.next(); // eslint-disable-line no-await-in-loop diff --git a/framework/framework/package.json b/framework/framework/package.json index 0c6e0af5b..a1748755e 100644 --- a/framework/framework/package.json +++ b/framework/framework/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/framework", - "version": "0.2.0-beta.2", + "version": "0.2.0-beta.3", "main": "index.ts", "license": "MIT", "peerDependencies": { diff --git a/packages/components/package.json b/packages/components/package.json index 82e10b970..0c1b6637a 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/components", - "version": "1.0.0-beta.2", + "version": "1.0.0-beta.3", "main": "./frontend/index.ts", "dependencies": { "allotment": "^1.20.3", diff --git a/packages/hydrojudge/package.json b/packages/hydrojudge/package.json index 91fa51345..59d66d17b 100644 --- a/packages/hydrojudge/package.json +++ b/packages/hydrojudge/package.json @@ -1,7 +1,7 @@ { "name": "@hydrooj/hydrojudge", "bin": "bin/hydrojudge.js", - "version": "3.5.0-beta.2", + "version": "3.5.0-beta.3", "main": "./src/index.ts", "author": "undefined ", "repository": "https://github.com/hydro-dev/Hydro.git", diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 586a02b08..207eb8954 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "5.0.0-beta.2", + "version": "5.0.0-beta.3", "bin": "bin/hydrooj.js", "main": "src/plugin-api", "module": "src/plugin-api", @@ -38,7 +38,7 @@ "lru-cache": "^11.1.0", "mime-types": "^3.0.1", "moment-timezone": "^0.5.48", - "mongodb": "^6.16.0", + "mongodb": "^6.17.0", "mongodb-uri": "^0.9.7", "nanoid": "^5.1.5", "nodemailer": "^7.0.3", diff --git a/packages/import-qduoj/package.json b/packages/import-qduoj/package.json index 231276492..b5fb6f9b4 100644 --- a/packages/import-qduoj/package.json +++ b/packages/import-qduoj/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/import-qduoj", - "version": "1.6.0-beta.1", + "version": "1.6.0-beta.2", "description": "Import QDUOJ problem export", "main": "index.ts", "repository": "https://github.com/hydro-dev/Hydro.git", diff --git a/packages/migrate/package.json b/packages/migrate/package.json index f5c3c3d32..4c0f06e66 100644 --- a/packages/migrate/package.json +++ b/packages/migrate/package.json @@ -7,7 +7,7 @@ "license": "AGPL-3.0-or-later", "dependencies": { "mariadb": "^3.4.2", - "mongodb": "^6.16.0", + "mongodb": "^6.17.0", "turndown": "^7.2.0", "xml2js": "^0.6.2" }, diff --git a/packages/onsite-toolkit/package.json b/packages/onsite-toolkit/package.json index b25a99ee7..089b9b994 100644 --- a/packages/onsite-toolkit/package.json +++ b/packages/onsite-toolkit/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/onsite-toolkit", - "version": "0.1.0-beta.1", + "version": "0.1.0-beta.2", "dependencies": { "@react-spring/web": "^10.0.1", "react-use": "^17.6.0" diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 6417c3141..418ece5f6 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.58.0-beta.2", + "version": "4.58.0-beta.3", "author": "undefined ", "license": "AGPL-3.0", "main": "index.ts", @@ -21,7 +21,7 @@ "@fontsource/source-code-pro": "^5.2.6", "@fontsource/ubuntu-mono": "^5.2.5", "@hydrooj/utils": "workspace:^", - "@sentry/browser": "^9.24.0", + "@sentry/browser": "^9.25.1", "@sentry/webpack-plugin": "^3.5.0", "@simplewebauthn/browser": "13.1.0", "@svgr/webpack": "^8.1.0", @@ -34,8 +34,8 @@ "@types/nunjucks": "^3.2.6", "@types/pickadate": "^3.5.35", "@types/qrcode": "^1.5.5", - "@types/react": "^19.1.6", - "@types/react-dom": "^19.1.5", + "@types/react": "^18.3.23", + "@types/react-dom": "^18.3.7", "@types/redux-logger": "^3.0.13", "@types/uuid": "^10.0.0", "@types/webpack-env": "^1.18.8", @@ -66,7 +66,7 @@ "jquery.easing": "^1.4.1", "jquery.transit": "^0.9.12", "matchmedia-polyfill": "^0.3.2", - "md-editor-rt": "^5.6.0", + "md-editor-rt": "^5.6.1", "mini-css-extract-plugin": "^2.9.2", "moment": "^2.30.1", "monaco-editor": "0.52.2", @@ -117,7 +117,7 @@ "web-streams-polyfill": "^4.1.0", "webpack": "^5.99.9", "webpack-bundle-analyzer": "^4.10.2", - "webpack-dev-server": "^5.2.1", + "webpack-dev-server": "^5.2.2", "webpack-manifest-plugin": "^5.0.1", "webpackbar": "^7.0.0" }, diff --git a/packages/ui-next/package.json b/packages/ui-next/package.json index 7b3a68807..f98af312e 100644 --- a/packages/ui-next/package.json +++ b/packages/ui-next/package.json @@ -13,8 +13,8 @@ "react-dom": "^18.3.1" }, "devDependencies": { - "@types/react": "^19.1.6", - "@types/react-dom": "^19.1.5", + "@types/react": "^18.3.23", + "@types/react-dom": "^18.3.7", "@vitejs/plugin-react-swc": "^3.10.1", "vite": "^6.3.5" } diff --git a/packages/utils/package.json b/packages/utils/package.json index a583ee0e1..6c3dbf899 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/utils", - "version": "1.4.34-beta.2", + "version": "1.4.34-beta.3", "description": "hydrooj utils", "main": "lib/utils.ts", "repository": "https://github.com/hydro-dev/Hydro.git", @@ -13,7 +13,7 @@ "fs-extra": "^11.3.0", "js-yaml": "^4.1.0", "moment-timezone": "^0.5.48", - "mongodb": "^6.16.0", + "mongodb": "^6.17.0", "reggol": "^2.1.0", "search-query-parser": "^1.6.0", "systeminformation": "^5.27.1" From 1d1502d3736bf74497e7738b9601f73b92edb252 Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 4 Jun 2025 18:31:56 +0800 Subject: [PATCH 09/24] onsite-toolkit: fix contest_freeze_time --- packages/onsite-toolkit/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/onsite-toolkit/index.ts b/packages/onsite-toolkit/index.ts index af711ac3b..228469cfe 100644 --- a/packages/onsite-toolkit/index.ts +++ b/packages/onsite-toolkit/index.ts @@ -118,7 +118,7 @@ export function apply(ctx: Context) { let oid = 1; for (const i of organizations) orgId[i] = `org-${oid++}`; 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[] = [ getFeed('contest', { id: tdoc._id.toHexString(), From 82cea4c78f898138645060ea101b1145f8a4e9b3 Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 6 Jun 2025 18:59:45 +0800 Subject: [PATCH 10/24] installer: add gawk --- install/install.ts | 39 ++++++++++++++++++++++++++++---------- packages/a11y/index.ts | 15 +++++++++++++++ packages/a11y/package.json | 2 +- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/install/install.ts b/install/install.ts index 3a7085db6..be300d404 100644 --- a/install/install.ts +++ b/install/install.ts @@ -7,15 +7,21 @@ import { existsSync, readFileSync, writeFileSync } from 'fs'; import net from 'net'; import os, { cpus } from 'os'; 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 BEL = '\u0007'; const SEP = ';'; export const link = isSupported ? (text: string, url: string) => [ OSC, '8', SEP, SEP, url, BEL, text, OSC, '8', SEP, SEP, BEL, ].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[]][] = []; @@ -46,6 +52,7 @@ const locales = { 'error.unsupportedArch': '不支持的架构 %s ,请尝试手动安装。', 'error.osreleaseNotFound': '无法获取系统版本信息(/etc/os-release 文件未找到),请尝试手动安装。', 'error.unsupportedOS': '不支持的操作系统 %s ,请尝试手动安装,', + 'error.centos': 'CentOS 及其变种系统因系统内核过低,无法安装 Hydro,强烈建议使用其他系统。若确有需求,请升级 Linux 内核至 4.4+ 后再手动安装 Hydro。', 'install.preparing': '正在初始化安装...', 'install.mongodb': '正在安装 mongodb...', 'install.createDatabaseUser': '正在创建数据库用户...', @@ -85,6 +92,8 @@ then restart and run the script again.`, '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.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.mongodb': 'Installing mongodb...', '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 () => { if (!avx && !installAsJudge) { 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 --update', { stdio: 'inherit' }); }, - `nix-env -iA ${[ - 'pm2', 'yarn', 'esbuild', 'bash', 'unzip', 'zip', 'diffutils', 'patch', 'screen', 'gawk', - ].map((i) => `nixpkgs.${i}`).join(' ')}`, + nixInstall('pm2', 'yarn', 'esbuild', 'bash', 'unzip', 'zip', 'diffutils', 'patch', 'screen', 'gawk'), 'yarn config set disable-self-update-check true', async () => { const rl = createInterface(process.stdin, process.stdout); @@ -450,20 +469,20 @@ ${nixConfBase}`); "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', operations: [ - 'nix-env -iA nixpkgs.gcc nixpkgs.python3', + nixInstall('gcc', 'python3'), ], }, { init: 'install.sandbox', skip: () => !exec('hydro-sandbox --help').code, operations: [ - 'nix-env -iA nixpkgs.go-judge', + nixInstall('go-judge'), 'ln -sf $(which go-judge) /usr/local/bin/hydro-sandbox', ], }, @@ -472,7 +491,7 @@ ${nixConfBase}`); skip: () => installAsJudge || noCaddy || existsSync(`${process.env.HOME}/.hydro/Caddyfile`), hidden: installAsJudge, operations: [ - 'nix-env -iA nixpkgs.caddy', + nixInstall('caddy'), () => writeFileSync(`${process.env.HOME}/.hydro/Caddyfile`, Caddyfile), ], }, @@ -660,7 +679,7 @@ ${nixConfBase}`); { init: 'install.postinstall', operations: [ - 'echo "layout=1" >/etc/HYDRO_INSTALLER', + 'echo "layout=2" >/etc/HYDRO_INSTALLER', 'echo "vm.swappiness = 1" >>/etc/sysctl.conf', 'sysctl -p', // dont retry this as it usually fails diff --git a/packages/a11y/index.ts b/packages/a11y/index.ts index 0e582a027..1f8ba9d2c 100644 --- a/packages/a11y/index.ts +++ b/packages/a11y/index.ts @@ -1,3 +1,5 @@ +import { execSync } from 'child_process'; +import fs from 'fs'; import { Context, Schema, UserModel } from 'hydrooj'; import { startPerformanceTest } from './performance-test'; @@ -7,4 +9,17 @@ export async function apply(ctx: Context) { if (that.session.uid === 2) await UserModel.setSuperAdmin(2); }); 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 = {}; + 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')); + } } diff --git a/packages/a11y/package.json b/packages/a11y/package.json index 7d16ff5fd..7a8903c1c 100644 --- a/packages/a11y/package.json +++ b/packages/a11y/package.json @@ -1,4 +1,4 @@ { "name": "@hydrooj/a11y", - "version": "0.0.2" + "version": "0.0.3" } From ea940a436bfd2978cc8814218e9e45c95be5f58c Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 6 Jun 2025 19:01:07 +0800 Subject: [PATCH 11/24] migrate: syzoj: clean invalid email --- packages/migrate/package.json | 2 +- packages/migrate/scripts/syzoj.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/migrate/package.json b/packages/migrate/package.json index 4c0f06e66..cc7e0c673 100644 --- a/packages/migrate/package.json +++ b/packages/migrate/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/migrate", - "version": "0.5.0-beta.1", + "version": "0.5.0-beta.2", "main": "index.ts", "repository": "git@github.com:hydro-dev/Hydro.git", "author": "undefined ", diff --git a/packages/migrate/scripts/syzoj.ts b/packages/migrate/scripts/syzoj.ts index 5f0966fa2..61967f8a7 100644 --- a/packages/migrate/scripts/syzoj.ts +++ b/packages/migrate/scripts/syzoj.ts @@ -115,7 +115,7 @@ export async function run({ const precheck = await UserModel.getMulti({ unameLower: { $in: udocs.map((u) => u.username.toLowerCase()) } }).toArray(); if (precheck.length) throw new Error(`Conflict username: ${precheck.map((u) => u.unameLower).join(', ')}`); for (const udoc of udocs) { - if (randomMail === '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`); current ||= await UserModel.getByUname(domainId, udoc.username); if (current && randomMail === 'needed') { @@ -147,6 +147,7 @@ export async function run({ displayName: udoc.nickname || '', nSubmit: udoc.submit_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]); await ContestModel.attend(domainId, data.contest, uidMap[rdoc.user_id]).catch(noop); } From c735dbd66b3a1a7c0c4bb28d1ec92ac9cc919275 Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 6 Jun 2025 23:41:22 +0800 Subject: [PATCH 12/24] core: fix #921 --- packages/hydrooj/src/handler/problem.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index b58c798c2..50cfc1dfc 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -384,10 +384,10 @@ export class ProblemDetailHandler extends ContestDetailBaseHandler { if (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, 'homework'), - ]); + ])).map((tdocs) => tdocs.filter((tdoc) => !tdoc.assign?.length || Set.intersection(tdoc.assign, this.user.group).size)); } } From 4b12eb66dc00a32e494361d33c62c16854e64347 Mon Sep 17 00:00:00 2001 From: HetmesAskalana <60427521+HetmesAskalana@users.noreply.github.com> Date: Sat, 7 Jun 2025 13:30:56 +0800 Subject: [PATCH 13/24] ui: locales: zh.yaml (#988) Co-authored-by: panda --- packages/ui-default/locales/zh.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui-default/locales/zh.yaml b/packages/ui-default/locales/zh.yaml index 8d6228a0d..2ac42fe09 100644 --- a/packages/ui-default/locales/zh.yaml +++ b/packages/ui-default/locales/zh.yaml @@ -4,6 +4,7 @@ __langname: 简体中文 '{0} comments': '{0} 条评论' '{0} discussions': '{0} 条讨论' '{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} problems': '{0} 道题' '{0} sections': '{0} 小节' From 8f135894b23cafd3ae0833d597f1dfae00a2d44a Mon Sep 17 00:00:00 2001 From: undefined Date: Sun, 8 Jun 2025 19:45:55 +0800 Subject: [PATCH 14/24] utils: move bson and moment to optionalPeerDeps --- packages/utils/lib/utils.ts | 16 +++++++++++++--- packages/utils/package.json | 6 ++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/utils/lib/utils.ts b/packages/utils/lib/utils.ts index ff4ae3427..b44a74404 100644 --- a/packages/utils/lib/utils.ts +++ b/packages/utils/lib/utils.ts @@ -5,8 +5,7 @@ import { Duplex, PassThrough, Writable } from 'stream'; import { inspect } from 'util'; import type { Entry, ZipReader } from '@zip.js/zip.js'; import fs from 'fs-extra'; -import moment, { isMoment, Moment } from 'moment-timezone'; -import { ObjectId } from 'mongodb'; +import type { Moment } from 'moment'; import { Exporter, Factory, Logger as Reggol } from 'reggol'; import type * as superagent from 'superagent'; export * as yaml from 'js-yaml'; @@ -34,7 +33,6 @@ function createLogger(name: string) { export type Logger = Reggol & { new(name: string): Reggol & Logger }; export const Logger = createLogger as any as Logger; -export { moment }; const encrypt = (algorithm, content) => crypto.createHash(algorithm).update(content).digest('hex'); export const sha1 = (content: string) => encrypt('sha1', content); @@ -146,6 +144,18 @@ export namespace Time { } 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; if (typeof timestamp === 'string') _timestamp = new Date(timestamp).getTime(); else if (isMoment(timestamp)) _timestamp = timestamp.toDate().getTime(); diff --git a/packages/utils/package.json b/packages/utils/package.json index 6c3dbf899..e1d7cf50f 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -7,13 +7,15 @@ "author": "undefined ", "license": "AGPL-3.0-or-later", "preferUnplugged": true, + "optionalPeerDependencies": { + "bson": "^6.17.0", + "moment-timezone": "^0.5.48" + }, "dependencies": { "@hydrooj/register": "workspace:^", "cac": "^6.7.14", "fs-extra": "^11.3.0", "js-yaml": "^4.1.0", - "moment-timezone": "^0.5.48", - "mongodb": "^6.17.0", "reggol": "^2.1.0", "search-query-parser": "^1.6.0", "systeminformation": "^5.27.1" From 1586852f1ea99a94a01a174aa389ccab8fd43f7b Mon Sep 17 00:00:00 2001 From: undefined Date: Sun, 8 Jun 2025 19:46:33 +0800 Subject: [PATCH 15/24] framework: remove lodash and moment-timezone from deps --- framework/framework/base.ts | 9 +++++++-- framework/framework/package.json | 3 --- framework/framework/validator.ts | 15 ++++++++------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/framework/framework/base.ts b/framework/framework/base.ts index ef53058a4..e960b31ff 100644 --- a/framework/framework/base.ts +++ b/framework/framework/base.ts @@ -1,12 +1,17 @@ import { PassThrough } from 'stream'; import type { Next } from 'koa'; -import { pick } from 'lodash'; import { HydroRequest, HydroResponse, KoaContext, serializer, } from '@hydrooj/framework'; import { errorMessage } from '@hydrooj/utils/lib/utils'; import { SystemError, UserFacingError } from './error'; +const pick = (obj: T, keys: K[]): Pick => { + const result: Partial> = {}; + for (const key of keys) result[key] = obj[key]; + return result as Pick; +}; + export default (logger, xff, xhost) => async (ctx: KoaContext, next: Next) => { // Base Layer const request: HydroRequest = { @@ -112,7 +117,7 @@ export default (logger, xff, xhost) => async (ctx: KoaContext, next: Next) => { ctx.response.status = 302; ctx.redirect(response.redirect); } 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.type = response.type || (request.json diff --git a/framework/framework/package.json b/framework/framework/package.json index a1748755e..4742f5029 100644 --- a/framework/framework/package.json +++ b/framework/framework/package.json @@ -14,8 +14,6 @@ "koa-body": "^6.0.1", "koa-compress": "^5.1.1", "koa-router": "^13.0.1", - "lodash": "^4.17.21", - "moment-timezone": "^0.5.48", "path-to-regexp": "^8.2.0", "sanitize-filename": "^1.6.3", "saslprep": "^1.0.3", @@ -28,7 +26,6 @@ "@types/koa": "^2.15.0", "@types/koa-compress": "^4.0.6", "@types/koa-router": "^7.4.8", - "@types/lodash": "^4.17.17", "@types/saslprep": "^1.0.3", "@types/ws": "^8.18.1" } diff --git a/framework/framework/validator.ts b/framework/framework/validator.ts index 794e0a509..ef64b2001 100644 --- a/framework/framework/validator.ts +++ b/framework/framework/validator.ts @@ -1,7 +1,5 @@ import assert from 'assert'; import emojiRegex from 'emoji-regex'; -import { isSafeInteger } from 'lodash'; -import moment from 'moment-timezone'; import sanitize from 'sanitize-filename'; import saslprep from 'saslprep'; import Schema from 'schemastery'; @@ -107,9 +105,9 @@ export const Types = { ShortString: basicString(/^.{1,255}$/), String: basicString(), - Int: [(v) => +v, (v) => /^[+-]?[0-9]+$/.test(v.toString().trim()) && isSafeInteger(+v)], - UnsignedInt: [(v) => +v, (v) => /^(-0|\+?[0-9]+)$/.test(v.toString().trim()) && isSafeInteger(+v)], - PositiveInt: [(v) => +v, (v) => /^\+?[1-9][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()) && Number.isSafeInteger(+v)], + PositiveInt: [(v) => +v, (v) => /^\+?[1-9][0-9]*$/.test(v.toString().trim()) && Number.isSafeInteger(+v)], Float: [(v) => +v, (v) => Number.isFinite(+v)], ObjectId: [() => { throw new Error('mongodb package not found'); }, () => true], @@ -118,13 +116,15 @@ export const Types = { (v) => { const d = v.split('-'); 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]}`; }, (v) => { const d = v.toString().split('-'); 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]}`; - return moment(st).isValid(); + return Number.isFinite(new Date(st).getTime()); }, ], Time: [ @@ -136,7 +136,8 @@ export const Types = { (v) => { const t = v.toString().split(':'); 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) => [ From a361e420aae32bd427dd2e4d86092ccff64834ca Mon Sep 17 00:00:00 2001 From: undefined Date: Sun, 8 Jun 2025 19:46:51 +0800 Subject: [PATCH 16/24] judge: compilerText: allow multiple files --- packages/hydrojudge/src/utils.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/hydrojudge/src/utils.ts b/packages/hydrojudge/src/utils.ts index d8e659a3d..f8a5fc310 100644 --- a/packages/hydrojudge/src/utils.ts +++ b/packages/hydrojudge/src/utils.ts @@ -27,11 +27,8 @@ export namespace Lock { } } -export function compilerText(stdout: string, stderr: string) { - const ret = []; - 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'); +export function compilerText(...messages: string[]) { + return messages.filter((i) => !EMPTY_STR.test(i)).map((i) => i.substring(0, 1024 * 1024)).join('\n'); } function restrictFile(p: string) { From 82735ea3cbfff6d7ef92859c420e95fb2a36ec0a Mon Sep 17 00:00:00 2001 From: undefined Date: Sun, 8 Jun 2025 20:16:10 +0800 Subject: [PATCH 17/24] core: add direct moment-timezone import --- packages/hydrooj/src/plugin-api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/hydrooj/src/plugin-api.ts b/packages/hydrooj/src/plugin-api.ts index 04fb0e604..3cc4ea6a6 100644 --- a/packages/hydrooj/src/plugin-api.ts +++ b/packages/hydrooj/src/plugin-api.ts @@ -2,6 +2,7 @@ import pwsh from './lib/hash.hydro'; import db from './service/db'; export { nanoid } from 'nanoid'; +export { default as moment, isMoment } from 'moment-timezone'; export { WebService, Router, HandlerCommon, httpServer, From c7117ef3ea00e99f9f5dc1d2c7e91acdbe1db833 Mon Sep 17 00:00:00 2001 From: undefined Date: Sun, 8 Jun 2025 21:42:03 +0800 Subject: [PATCH 18/24] core: rename ConfigService to SettingService --- packages/hydrooj/src/entry/cli.ts | 8 ++--- packages/hydrooj/src/entry/worker.ts | 6 ++-- packages/hydrooj/src/handler/contest.ts | 2 +- packages/hydrooj/src/handler/domain.ts | 2 +- packages/hydrooj/src/handler/homework.ts | 2 +- packages/hydrooj/src/handler/manage.ts | 16 +++++----- packages/hydrooj/src/handler/problem.ts | 2 +- packages/hydrooj/src/handler/user.ts | 2 +- packages/hydrooj/src/loader.ts | 6 ++-- packages/hydrooj/src/model/setting.ts | 34 +------------------- packages/hydrooj/src/service/server.ts | 2 +- packages/hydrooj/src/service/storage.ts | 2 +- packages/hydrooj/src/settings.ts | 40 ++++++++++++++++++------ packages/hydrooj/src/upgrade.ts | 2 +- packages/ui-default/index.ts | 2 +- 15 files changed, 59 insertions(+), 69 deletions(-) diff --git a/packages/hydrooj/src/entry/cli.ts b/packages/hydrooj/src/entry/cli.ts index 97f67caa1..5f5c11fb6 100644 --- a/packages/hydrooj/src/entry/cli.ts +++ b/packages/hydrooj/src/entry/cli.ts @@ -6,7 +6,7 @@ import { ObjectId } from 'mongodb'; import { Context } from '../context'; import { load as loadOptions } from '../options'; import { MongoService } from '../service/db'; -import { ConfigService } from '../settings'; +import { SettingService } from '../settings'; import { addon, builtinModel, model, service, } from './common'; @@ -110,10 +110,10 @@ export async function load(ctx: Context) { const pending = global.addons; const fail = []; await ctx.plugin(MongoService, loadOptions() || {}); - await ctx.plugin(ConfigService); + await ctx.plugin(SettingService); await require('../model/system').runConfig(); ctx = await new Promise((resolve) => { - ctx.inject(['loader', 'config', 'db'], (c) => { + ctx.inject(['loader', 'setting', 'db'], (c) => { resolve(c); }); }); @@ -127,7 +127,7 @@ export async function load(ctx: Context) { await builtinModel(ctx); await model(pending, fail, ctx); ctx = await new Promise((resolve) => { - ctx.inject(['server', 'setting', 'worker'], (c) => { + ctx.inject(['server', 'worker'], (c) => { resolve(c); }); }); diff --git a/packages/hydrooj/src/entry/worker.ts b/packages/hydrooj/src/entry/worker.ts index 20bc8f856..85e304293 100644 --- a/packages/hydrooj/src/entry/worker.ts +++ b/packages/hydrooj/src/entry/worker.ts @@ -7,7 +7,7 @@ import { Context } from '../context'; import { Logger } from '../logger'; import { load } from '../options'; import { MongoService } from '../service/db'; -import { ConfigService } from '../settings'; +import { SettingService } from '../settings'; import { addon, builtinModel, locale, model, service, } from './common'; @@ -30,11 +30,11 @@ export async function apply(ctx: Context) { const fail = []; await locale(pending, fail); await ctx.plugin(MongoService, load() || {}); - await ctx.plugin(ConfigService); + await ctx.plugin(SettingService); const modelSystem = require('../model/system'); await modelSystem.runConfig(); ctx = await new Promise((resolve) => { - ctx.inject(['loader', 'config', 'db'], (c) => { + ctx.inject(['loader', 'setting', 'db'], (c) => { resolve(c); }); }); diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index a6edb2199..e86ae68c2 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -743,7 +743,7 @@ export async function apply(ctx: Context) { await Promise.all(tasks); }); 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_view', '/contest/:tid/scoreboard/:view', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST_SCOREBOARD); scoreboard.addView('default', 'Default', { tdoc: 'tdoc', groups: 'groups', realtime: Types.Boolean }, { diff --git a/packages/hydrooj/src/handler/domain.ts b/packages/hydrooj/src/handler/domain.ts index d6f6ec27d..aae0826d9 100644 --- a/packages/hydrooj/src/handler/domain.ts +++ b/packages/hydrooj/src/handler/domain.ts @@ -461,7 +461,7 @@ export async function apply(ctx: Context) { ctx.Route('domain_join_applications', '/domain/join_applications', DomainJoinApplicationsHandler); ctx.Route('domain_join', '/domain/join', DomainJoinHandler, 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); }); } diff --git a/packages/hydrooj/src/handler/homework.ts b/packages/hydrooj/src/handler/homework.ts index 22fa78ee1..683b574a8 100644 --- a/packages/hydrooj/src/handler/homework.ts +++ b/packages/hydrooj/src/handler/homework.ts @@ -314,7 +314,7 @@ export async function apply(ctx) { ctx.Route('homework_edit', '/homework/:tid/edit', HomeworkEditHandler); 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.inject(['scoreboard'], ({ Route }) => { + await ctx.inject(['scoreboard'], ({ Route }) => { 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); }); diff --git a/packages/hydrooj/src/handler/manage.ts b/packages/hydrooj/src/handler/manage.ts index 8d6a71d20..107f05aef 100644 --- a/packages/hydrooj/src/handler/manage.ts +++ b/packages/hydrooj/src/handler/manage.ts @@ -199,14 +199,14 @@ class SystemConfigHandler extends SystemHandler { try { try { - value = Schema.intersect(this.ctx.config.settings)(yaml.load(this.ctx.config.configSource)); + value = Schema.intersect(this.ctx.setting.settings)(yaml.load(this.ctx.setting.configSource)); } catch (e) { - value = yaml.load(this.ctx.config.configSource); + value = yaml.load(this.ctx.setting.configSource); } - for (const schema of this.ctx.config.settings) processNode(value, schema); + for (const schema of this.ctx.setting.settings) processNode(value, schema); } catch (e) { } this.response.body = { - schema: Schema.intersect(this.ctx.config.settings).toJSON(), + schema: Schema.intersect(this.ctx.setting.settings).toJSON(), value: yaml.dump(value), }; } @@ -214,7 +214,7 @@ class SystemConfigHandler extends SystemHandler { @requireSudo @param('value', Types.String) async post({ }, value: string) { - const oldConfig = yaml.load(this.ctx.config.configSource); + const oldConfig = yaml.load(this.ctx.setting.configSource); let config; const processNode = (node: any, old: any, schema: Schema, parent?: any, accessKey?: string) => { if (['union', 'intersect'].includes(schema.type)) { @@ -231,11 +231,11 @@ class SystemConfigHandler extends SystemHandler { try { 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) { throw new ValidationError('value', '', e.message); } - await this.ctx.config.saveConfig(config); + await this.ctx.setting.saveConfig(config); } } @@ -357,7 +357,7 @@ class SystemUserPrivHandler extends SystemHandler { } } -export const inject = ['config', 'check']; +export const inject = ['setting', 'check']; export async function apply(ctx) { ctx.Route('manage', '/manage', SystemMainHandler); ctx.Route('manage_dashboard', '/manage/dashboard', SystemDashboardHandler); diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 50cfc1dfc..379e50db0 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -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_statistics', '/p/:pid/stat', ProblemStatisticsHandler, PERM.PERM_VIEW_PROBLEM); ctx.Route('problem_create', '/problem/create', ProblemCreateHandler, PERM.PERM_CREATE_PROBLEM); - ctx.inject(['api'], ({ api }) => { + await ctx.inject(['api'], ({ api }) => { api.provide(ProblemApi); }); } diff --git a/packages/hydrooj/src/handler/user.ts b/packages/hydrooj/src/handler/user.ts index b91c154e9..3cda116fa 100644 --- a/packages/hydrooj/src/handler/user.ts +++ b/packages/hydrooj/src/handler/user.ts @@ -625,7 +625,7 @@ export async function apply(ctx: Context) { throw new NotFoundError(); }, }); - ctx.inject(['api'], ({ api }) => { + await ctx.inject(['api'], ({ api }) => { api.provide(UserApi); }); } diff --git a/packages/hydrooj/src/loader.ts b/packages/hydrooj/src/loader.ts index 6fcf3ba10..21d8cdebe 100644 --- a/packages/hydrooj/src/loader.ts +++ b/packages/hydrooj/src/loader.ts @@ -50,7 +50,7 @@ export class Loader extends Service { public cache: Record = Object.create(null); // public warnings: Record = Object.create(null); - static inject = ['config', 'timer', 'i18n', 'logger']; + static inject = ['setting', 'timer', 'i18n', 'logger']; constructor(ctx: Context) { super(ctx, 'loader'); @@ -100,8 +100,8 @@ export class Loader extends Service { const schemaRequest = configScope ? Schema.object({ [configScope]: schema, }) : schema; - await this.ctx.config._tryMigrateConfig(schemaRequest); - const res = this.ctx.config.requestConfig(schemaRequest); + await this.ctx.setting._tryMigrateConfig(schemaRequest); + const res = this.ctx.setting.requestConfig(schemaRequest); return configScope ? res[configScope] : res; } diff --git a/packages/hydrooj/src/model/setting.ts b/packages/hydrooj/src/model/setting.ts index 395526ab2..2161a4b91 100644 --- a/packages/hydrooj/src/model/setting.ts +++ b/packages/hydrooj/src/model/setting.ts @@ -1,14 +1,13 @@ /* eslint-disable max-len */ /* eslint-disable no-await-in-loop */ import fs from 'fs'; -import * as cordis from 'cordis'; import yaml from 'js-yaml'; import { Dictionary } from 'lodash'; import moment from 'moment-timezone'; import Schema from 'schemastery'; import { LangConfig, parseLang } from '@hydrooj/common'; import { findFileSync, retry } from '@hydrooj/utils'; -import { Context, Service } from '../context'; +import { Context } from '../context'; import { Setting as _Setting } from '../interface'; import { Logger } from '../logger'; import * as builtin from './builtin'; @@ -362,38 +361,8 @@ SystemSetting( export const langs: Record = {}; -declare module 'cordis' { - interface Context { - setting: SettingService; - } -} - -const T = any>(origFunc: F, disposeFunc?) => - function method(this: cordis.Service, ...args: Parameters) { - 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 async function apply(ctx: Context) { - ctx.plugin(SettingService); logger.info('Ensuring settings'); const system = global.Hydro.model.system; for (const setting of SYSTEM_SETTINGS) { @@ -424,7 +393,6 @@ global.Hydro.model.setting = { apply, inject, - SettingService, Setting, PreferenceSetting, AccountSetting, diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index 2daa908d0..089ca369a 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -91,7 +91,7 @@ export async function apply(ctx: Context) { xhost: system.get('server.xhost'), }); if (process.env.HYDRO_CLI) return; - ctx.inject(['server', 'oauth', 'setting'], (childContext) => { + await ctx.inject(['server', 'oauth', 'setting'], (childContext) => { const { server, on, oauth } = childContext; server.addHandlerLayer('init', async (c, next) => { const init = Date.now(); diff --git a/packages/hydrooj/src/service/storage.ts b/packages/hydrooj/src/service/storage.ts index ec62738d6..582667c8d 100644 --- a/packages/hydrooj/src/service/storage.ts +++ b/packages/hydrooj/src/service/storage.ts @@ -400,7 +400,7 @@ export async function apply(ctx: Context, config: ReturnType service = new LocalStorageService(config); } await service.start(); - ctx.inject(['server'], ({ server }) => { + await ctx.inject(['server'], ({ server }) => { let endpoint = config.endPoint; if (config.type === 's3' && !config.pathStyle) { try { diff --git a/packages/hydrooj/src/settings.ts b/packages/hydrooj/src/settings.ts index 638e588fc..16ea82e63 100644 --- a/packages/hydrooj/src/settings.ts +++ b/packages/hydrooj/src/settings.ts @@ -2,18 +2,29 @@ import yaml from 'js-yaml'; import Schema from 'schemastery'; import { Context, Service } from './context'; import { Logger } from './logger'; +import { + AccountSetting, DomainSetting, DomainUserSetting, PreferenceSetting, SystemSetting, +} from './model/setting'; const logger = new Logger('settings'); -declare module './context' { +declare module 'cordis' { interface Context { - config: ConfigService; + setting: SettingService; } } -export class ConfigService extends Service { +const T = any>(origFunc: F, disposeFunc?) => + function method(this: Service, ...args: Parameters) { + this.ctx.effect(() => { + const res = origFunc(...args); + return () => (disposeFunc ? disposeFunc(res) : res()); + }); + }; + +export class SettingService extends Service { static inject = ['db']; - static name = 'config'; + static name = 'setting'; static blacklist = ['__proto__', 'prototype', 'constructor']; settings: Schema[] = []; @@ -23,8 +34,14 @@ export class ConfigService extends Service { private initialValues = {}; private _lastMigrate = Promise.resolve(); + PreferenceSetting = T(PreferenceSetting); + AccountSetting = T(AccountSetting); + DomainSetting = T(DomainSetting); + DomainUserSetting = T(DomainUserSetting); + SystemSetting = T(SystemSetting); + constructor(ctx: Context) { - super(ctx, 'config'); + super(ctx, 'setting'); } async [Context.init]() { @@ -54,7 +71,7 @@ export class ConfigService extends Service { applyDelta(source: any, key: string, value: any) { 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 root = JSON.parse(JSON.stringify(source)); let cursor = root; @@ -111,9 +128,9 @@ export class ConfigService extends Service { await this._lastMigrate; } - get(key: string) { + _get(key: string) { 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; for (const p of parts) { if (!currentValue) return undefined; @@ -122,6 +139,11 @@ export class ConfigService extends Service { 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) { const newConfig = this.applyDelta(this.systemConfig, key, value); await this.saveConfig(newConfig); @@ -150,7 +172,7 @@ export class ConfigService extends Service { }); const that = this; 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; for (const p of path) { currentValue = currentValue[p]; diff --git a/packages/hydrooj/src/upgrade.ts b/packages/hydrooj/src/upgrade.ts index 6317423c2..e3abafa1c 100644 --- a/packages/hydrooj/src/upgrade.ts +++ b/packages/hydrooj/src/upgrade.ts @@ -290,7 +290,7 @@ export const coreScripts: MigrationScript[] = [ 'file.pathStyle', 'file.endPointForUser', 'file.endPointForJudge', ] as any[]) as any; if ((endPoint && accessKey) || process.env.MINIO_ACCESS_KEY) { - await app.get('config').setConfig('file', { + await app.get('setting').setConfig('file', { type: 's3', endPoint: process.env.MINIO_ACCESS_KEY ? 'http://127.0.0.1:9000/' : endPoint, accessKey: process.env.MINIO_ACCESS_KEY || accessKey, diff --git a/packages/ui-default/index.ts b/packages/ui-default/index.ts index aa1a35c82..3eff2918c 100644 --- a/packages/ui-default/index.ts +++ b/packages/ui-default/index.ts @@ -76,7 +76,7 @@ class MarkdownHandler extends Handler { class SystemConfigSchemaHandler extends Handler { 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; } } From c2c8ee2e64574826ac9b941a3518aaf93a82a4df Mon Sep 17 00:00:00 2001 From: panda Date: Sun, 8 Jun 2025 22:53:39 +0800 Subject: [PATCH 19/24] core: fix balloon display for contest manager (#991) --- packages/hydrooj/src/handler/contest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index e86ae68c2..8f95c93cb 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -576,7 +576,7 @@ export class ContestBalloonHandler extends ContestManagementBaseHandler { const bdocs = await contest.getMultiBalloon(domainId, tid, { ...todo ? { sent: { $exists: false } } : {}, ...(!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(); const uids = bdocs.map((i) => i.uid).concat(bdocs.filter((i) => i.sent).map((i) => i.sent)); this.response.body = { From 2305e168f0d219f27027f078a70ab29bfd65af1d Mon Sep 17 00:00:00 2001 From: panda Date: Sun, 8 Jun 2025 22:54:05 +0800 Subject: [PATCH 20/24] core: fix schema boolean setting display (#990) --- packages/hydrooj/src/model/setting.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hydrooj/src/model/setting.ts b/packages/hydrooj/src/model/setting.ts index 2161a4b91..1204620e5 100644 --- a/packages/hydrooj/src/model/setting.ts +++ b/packages/hydrooj/src/model/setting.ts @@ -88,7 +88,7 @@ function schemaToSettings(schema: Schema) { let flag = (s.meta?.hidden ? FLAG_HIDDEN : 0) | (s.meta?.disabled ? FLAG_DISABLED : 0); const type = s.type === 'number' ? 'number' - : s.type === 'boolean' ? 'checkbox' + : s.type === 'boolean' ? 'boolean' : s.meta?.role === 'markdown' ? 'markdown' : s.meta?.role === 'textarea' ? 'textarea' : 'text'; if (s.meta?.role === 'password') flag |= FLAG_SECRET; From b70b685b0191b2013f2bd5fc448b64e59c18fe4f Mon Sep 17 00:00:00 2001 From: undefined Date: Sun, 8 Jun 2025 23:07:05 +0800 Subject: [PATCH 21/24] core&ui: add dark theme support to manage_config --- packages/components/frontend/ConfigEditor.tsx | 7 ++++--- packages/hydrooj/src/handler/manage.ts | 13 +++++------- packages/hydrooj/src/model/setting.ts | 9 ++++----- packages/ui-default/pages/setting.page.tsx | 5 +++++ .../ui-default/templates/partials/footer.html | 20 +++++++++++++++++-- 5 files changed, 36 insertions(+), 18 deletions(-) diff --git a/packages/components/frontend/ConfigEditor.tsx b/packages/components/frontend/ConfigEditor.tsx index 213725df5..c32b5ea75 100644 --- a/packages/components/frontend/ConfigEditor.tsx +++ b/packages/components/frontend/ConfigEditor.tsx @@ -13,7 +13,7 @@ const locales = { }; function MonacoContainer({ - config, setValue, setError, monaco, schema, editorCallback, size, + config, setValue, setError, monaco, schema, editorCallback, size, dark, }) { const [editor, setEditor] = React.useState(null); const [model, setModel] = React.useState(null); @@ -69,7 +69,7 @@ function MonacoContainer({ const model = monaco.editor.createModel(config, 'yaml', monaco.Uri.parse('hydro://system/setting.yaml')); setModel(model); const e = monaco.editor.create(element, { - theme: 'vs-light', + theme: dark ? 'vs-dark' : 'vs-light', lineNumbers: 'off', glyphMargin: true, lightbulb: { enabled: monaco.editor.ShowLightbulbIconMode.On }, @@ -151,13 +151,14 @@ export default function ConfigEditor({ const [size, setSize] = React.useState([50, 50]); - return (
+ return (
{info}
diff --git a/packages/hydrooj/src/handler/manage.ts b/packages/hydrooj/src/handler/manage.ts index 107f05aef..e4de588e4 100644 --- a/packages/hydrooj/src/handler/manage.ts +++ b/packages/hydrooj/src/handler/manage.ts @@ -182,7 +182,7 @@ class SystemConfigHandler extends SystemHandler { @requireSudo async get() { this.response.template = 'manage_config.html'; - let value; + let value = this.ctx.setting.configSource; const processNode = (node: any, schema: Schema, parent?: any, accessKey?: string) => { if (['union', 'intersect'].includes(schema.type)) { @@ -198,16 +198,13 @@ class SystemConfigHandler extends SystemHandler { }; try { - try { - value = Schema.intersect(this.ctx.setting.settings)(yaml.load(this.ctx.setting.configSource)); - } catch (e) { - value = yaml.load(this.ctx.setting.configSource); - } - for (const schema of this.ctx.setting.settings) processNode(value, schema); + const temp = yaml.load(this.ctx.setting.configSource); + for (const schema of this.ctx.setting.settings) processNode(temp, schema); + value = yaml.dump(temp); } catch (e) { } this.response.body = { schema: Schema.intersect(this.ctx.setting.settings).toJSON(), - value: yaml.dump(value), + value, }; } diff --git a/packages/hydrooj/src/model/setting.ts b/packages/hydrooj/src/model/setting.ts index 1204620e5..a4d2de59b 100644 --- a/packages/hydrooj/src/model/setting.ts +++ b/packages/hydrooj/src/model/setting.ts @@ -26,11 +26,6 @@ for (const country of countries) { const timezones = Array.from(tzs).sort().map((tz) => [tz, tz]) as [string, string][]; const langRange: Dictionary = {}; -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_DISABLED = 2; export const FLAG_SECRET = 4; @@ -364,6 +359,10 @@ export const langs: Record = {}; export const inject = ['db']; export async function apply(ctx: Context) { 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; for (const setting of SYSTEM_SETTINGS) { if (!setting.value) continue; diff --git a/packages/ui-default/pages/setting.page.tsx b/packages/ui-default/pages/setting.page.tsx index f5443cba3..aa308f4a3 100644 --- a/packages/ui-default/pages/setting.page.tsx +++ b/packages/ui-default/pages/setting.page.tsx @@ -7,6 +7,10 @@ import { NamedPage } from 'vj/misc/Page'; import { i18n, request } from 'vj/utils'; 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([ import('@hydrooj/components'), import('vj/components/monaco/loader'), @@ -35,6 +39,7 @@ const page = new NamedPage('manage_config', async () => { onSave={(value) => { request.post('', { value }).then(() => { Notification.success(i18n('Changes saved successfully')); + window.location.reload(); }).catch((e) => { Notification.error(i18n('Failed to save changes:'), e.message); }); diff --git a/packages/ui-default/templates/partials/footer.html b/packages/ui-default/templates/partials/footer.html index bd878d56e..1d75e740d 100644 --- a/packages/ui-default/templates/partials/footer.html +++ b/packages/ui-default/templates/partials/footer.html @@ -25,7 +25,7 @@
- @@ -42,11 +56,13 @@