From 7968a10d4930a9f37f059e6da4cd42873121d2a3 Mon Sep 17 00:00:00 2001 From: panda Date: Wed, 11 Jun 2025 02:44:01 +0800 Subject: [PATCH 1/8] core: fix domain group delete (#994) --- packages/hydrooj/src/handler/domain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/hydrooj/src/handler/domain.ts b/packages/hydrooj/src/handler/domain.ts index aae0826d9..811592e59 100644 --- a/packages/hydrooj/src/handler/domain.ts +++ b/packages/hydrooj/src/handler/domain.ts @@ -434,7 +434,7 @@ export const DomainApi = { }), async (ctx, args) => { if (!ctx.user.hasPerm(PERM.PERM_EDIT_DOMAIN)) throw new PermissionError(PERM.PERM_EDIT_DOMAIN); - if (args.uids) { + if (args.uids?.length) { const res = await user.updateGroup(ctx.domain._id, args.name, args.uids); return res.upsertedCount > 0; } From 1a1cff6e8e2586f4a127fd5a31ed061f16964f1b Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 11 Jun 2025 11:44:50 +0800 Subject: [PATCH 2/8] core: problem/before-edit: add unset param --- packages/hydrooj/src/model/document.ts | 6 +++--- packages/hydrooj/src/model/problem.ts | 5 +++-- packages/hydrooj/src/service/bus.ts | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/hydrooj/src/model/document.ts b/packages/hydrooj/src/model/document.ts index d076b0a26..12b5f8329 100644 --- a/packages/hydrooj/src/model/document.ts +++ b/packages/hydrooj/src/model/document.ts @@ -104,9 +104,9 @@ export async function set( ): Promise { await bus.parallel('document/set', domainId, docType, docId, $set, $unset); const update: UpdateFilter = {}; - if ($set) update.$set = $set; - if ($unset) update.$unset = $unset; - if ($push) update.$push = $push; + if ($set && Object.keys($set).length) update.$set = $set; + if ($unset && Object.keys($unset).length) update.$unset = $unset; + if ($push && Object.keys($push).length) update.$push = $push; return await coll.findOneAndUpdate( { domainId, docType, docId }, update, diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index ce91df227..8f73353b2 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -231,14 +231,15 @@ export class ProblemModel { static async edit(domainId: string, _id: number, $set: Partial): Promise { const delpid = $set.pid === ''; const ddoc = await DomainModel.get(domainId); + const $unset = delpid ? { pid: '' } : {}; if (delpid) { delete $set.pid; $set.sort = sortable(`P${_id}`, ddoc.namespaces); } else if ($set.pid) { $set.sort = sortable($set.pid, ddoc.namespaces); } - await bus.parallel('problem/before-edit', $set); - const result = await document.set(domainId, document.TYPE_PROBLEM, _id, $set, delpid ? { pid: '' } : undefined); + await bus.parallel('problem/before-edit', $set, $unset); + const result = await document.set(domainId, document.TYPE_PROBLEM, _id, $set, $unset); await bus.emit('problem/edit', result); return result; } diff --git a/packages/hydrooj/src/service/bus.ts b/packages/hydrooj/src/service/bus.ts index bee4aff45..5e3f21647 100644 --- a/packages/hydrooj/src/service/bus.ts +++ b/packages/hydrooj/src/service/bus.ts @@ -62,7 +62,7 @@ export interface EventMap { 'problem/before-add': (domainId: string, content: string, owner: number, docId: number, doc: Partial) => VoidReturn 'problem/add': (doc: Partial, docId: number) => VoidReturn - 'problem/before-edit': (doc: Partial) => VoidReturn + 'problem/before-edit': (doc: Partial, $unset: OnlyFieldsOfType) => VoidReturn 'problem/edit': (doc: ProblemDoc) => VoidReturn 'problem/before-del': (domainId: string, docId: number) => VoidReturn 'problem/del': (domainId: string, docId: number) => VoidReturn From c1cdc703fe144cc23b606134482450265adc783e Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 11 Jun 2025 11:54:19 +0800 Subject: [PATCH 3/8] ui: fix config schema --- packages/ui-default/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui-default/index.ts b/packages/ui-default/index.ts index 3eff2918c..163e151bb 100644 --- a/packages/ui-default/index.ts +++ b/packages/ui-default/index.ts @@ -183,6 +183,7 @@ export function apply(ctx: Context) { about: Schema.string().role('markdown').default(defaultAbout), }), })); + ctx.Route('config_schema', '/manage/config/schema.json', SystemConfigSchemaHandler, PRIV.PRIV_EDIT_SYSTEM); }); if (process.env.HYDRO_CLI) return; ctx.Route('wiki_help', '/wiki/help', WikiHelpHandler); @@ -190,7 +191,6 @@ export function apply(ctx: Context) { ctx.Route('set_theme', '/set_theme/:theme', SetThemeHandler); ctx.Route('set_legacy', '/legacy', LegacyModeHandler); ctx.Route('markdown', '/markdown', MarkdownHandler); - ctx.Route('config_schema', '/manage/config/schema.json', SystemConfigSchemaHandler, PRIV.PRIV_EDIT_SYSTEM); ctx.Route('media', '/media', RichMediaHandler); ctx.on('handler/after/DiscussionRaw', async (that) => { if (that.args.render && that.response.type === 'text/markdown') { From 6213d0a90a2555acff9f244e7ebb517f940e20ac Mon Sep 17 00:00:00 2001 From: undefined Date: Wed, 11 Jun 2025 11:56:14 +0800 Subject: [PATCH 4/8] ui: ConfigEditor: add dynamic setting --- packages/components/frontend/ConfigEditor.tsx | 4 ++-- packages/ui-default/pages/setting.page.tsx | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/components/frontend/ConfigEditor.tsx b/packages/components/frontend/ConfigEditor.tsx index f39c0b732..5df323611 100644 --- a/packages/components/frontend/ConfigEditor.tsx +++ b/packages/components/frontend/ConfigEditor.tsx @@ -117,7 +117,7 @@ function MonacoContainer({ } export default function ConfigEditor({ - schema, config, monaco, Markdown, onSave, registerAction, sidebar, + schema, config, monaco, Markdown, onSave, registerAction, sidebar, dynamic, }) { const getValue = () => { try { @@ -178,7 +178,7 @@ export default function ConfigEditor({
-
+
{sidebar && {sidebar}} diff --git a/packages/ui-default/pages/setting.page.tsx b/packages/ui-default/pages/setting.page.tsx index aa308f4a3..a9c9d4a1b 100644 --- a/packages/ui-default/pages/setting.page.tsx +++ b/packages/ui-default/pages/setting.page.tsx @@ -45,6 +45,7 @@ const page = new NamedPage('manage_config', async () => { }); }} sidebar={} + dynamic={UiContext.dynamic || {}} /> + + + +
+
+
+ {' '} + +
+
+
+ + + ); +} + const page = new NamedPage(['problem_main'], () => { const $body = $('body'); $body.addClass('display-mode'); @@ -344,6 +435,12 @@ const page = new NamedPage(['problem_main'], () => { $(document).on('vjContentNew', (e) => processElement(e.target)); processElement(document); + ReactDOM.createRoot(document.getElementById('problem_selection')) + .render( { selectedPids = pids; }} + onClear={(handler) => { clearSelectionHandler = handler; }} + />); + addSpeculationRules({ prerender: [{ 'where': { diff --git a/packages/ui-default/templates/problem_main.html b/packages/ui-default/templates/problem_main.html index 05cbb8d34..9548cbdae 100644 --- a/packages/ui-default/templates/problem_main.html +++ b/packages/ui-default/templates/problem_main.html @@ -33,6 +33,7 @@ {{ _('Leave Edit Mode') }} +
{% if handler.user.hasPerm(perm.PERM_EDIT_PROBLEM) %} diff --git a/packages/ui-default/utils/base.ts b/packages/ui-default/utils/base.ts index 78f74146d..9ee4667d6 100644 --- a/packages/ui-default/utils/base.ts +++ b/packages/ui-default/utils/base.ts @@ -6,7 +6,7 @@ import DOMServer from 'react-dom/server'; export function substitute(str: string, obj: any) { return str.replace(/\{([^{}]+)\}/g, (match, key) => { - if (obj[key] !== undefined) return obj[key].toString(); + if (obj[key] !== undefined && obj[key] !== null) return obj[key].toString(); return `{${key}}`; }); } From 6b691a264139db728d8f2cf2921474c90bb39a4d Mon Sep 17 00:00:00 2001 From: undefined Date: Thu, 12 Jun 2025 16:43:26 +0800 Subject: [PATCH 8/8] core: fix problem.updateStatus ac override --- packages/hydrooj/src/model/document.ts | 10 +++++----- packages/hydrooj/src/model/problem.ts | 6 +++--- packages/hydrooj/src/model/record.ts | 9 +++++++++ packages/hydrooj/src/model/setting.ts | 1 + 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/hydrooj/src/model/document.ts b/packages/hydrooj/src/model/document.ts index 12b5f8329..647984ade 100644 --- a/packages/hydrooj/src/model/document.ts +++ b/packages/hydrooj/src/model/document.ts @@ -317,14 +317,14 @@ export async function setMultiStatus( ); } -export async function setStatusIfNotCondition( +export async function setStatusIfCondition( domainId: string, docType: T, docId: DocStatusType[T]['docId'], uid: number, filter: Filter, args: Partial = {}, returnDocument: 'before' | 'after' = 'after', ): Promise { try { return await collStatus.findOneAndUpdate( - { domainId, docType, docId, uid, $nor: [filter] }, + { domainId, docType, docId, uid, ...filter }, { $set: args }, { upsert: true, returnDocument }, ); @@ -338,8 +338,8 @@ export async function setIfNotStatus, returnDocument: 'before' | 'after' = 'after', ): Promise { - return await setStatusIfNotCondition( - domainId, docType, docId, uid, { [key]: ifNot } as any, + return await setStatusIfCondition( + domainId, docType, docId, uid, { [key]: { $ne: ifNot } } as any, { [key]: value, ...args }, returnDocument, ); } @@ -474,7 +474,7 @@ global.Hydro.model.document = { revSetStatus, set, setIfNotStatus, - setStatusIfNotCondition, + setStatusIfCondition, setStatus, setMultiStatus, setSub, diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index 8f73353b2..e90c1aabd 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -420,10 +420,10 @@ export class ProblemModel { domainId: string, pid: number, uid: number, rid: ObjectId, status: number, score: number, ) { - const filter: Filter = { rid: { $ne: rid }, status: STATUS.STATUS_ACCEPTED }; - const res = await document.setStatusIfNotCondition( + const condition = status === STATUS.STATUS_ACCEPTED ? {} : { status: { $ne: STATUS.STATUS_ACCEPTED } }; + const res = await document.setStatusIfCondition( domainId, document.TYPE_PROBLEM, pid, uid, - filter, { rid, status, score }, + condition, { rid, status, score }, ); return !!res; } diff --git a/packages/hydrooj/src/model/record.ts b/packages/hydrooj/src/model/record.ts index a1114a8fb..da19759e8 100644 --- a/packages/hydrooj/src/model/record.ts +++ b/packages/hydrooj/src/model/record.ts @@ -15,6 +15,7 @@ import { ArgMethod, buildProjection, Time } from '../utils'; import { STATUS } from './builtin'; import DomainModel from './domain'; import problem from './problem'; +import * as SystemModel from './system'; import task from './task'; export default class RecordModel { @@ -291,6 +292,14 @@ export async function apply(ctx: Context) { ctx.on('domain/delete', (domainId) => RecordModel.coll.deleteMany({ domainId })); ctx.on('record/judge', async (rdoc, updated) => { if (rdoc.status === STATUS.STATUS_ACCEPTED && updated) { + if (SystemModel.get('record.statMode') === 'unique') { + await RecordModel.collStat.deleteMany({ + _id: { $ne: rdoc._id }, + uid: rdoc.uid, + pid: rdoc.pid, + domainId: rdoc.domainId, + }); + } await RecordModel.collStat.updateOne({ _id: rdoc._id, }, { diff --git a/packages/hydrooj/src/model/setting.ts b/packages/hydrooj/src/model/setting.ts index a4d2de59b..1c869829d 100644 --- a/packages/hydrooj/src/model/setting.ts +++ b/packages/hydrooj/src/model/setting.ts @@ -334,6 +334,7 @@ SystemSetting( Setting('setting_basic', 'discussion.nodes', builtin.DEFAULT_NODES, 'yaml', 'discussion.nodes', 'Discussion Nodes'), Setting('setting_basic', 'problem.categories', builtin.CATEGORIES, 'yaml', 'problem.categories', 'Problem Categories'), Setting('setting_basic', 'training.enrolled-users', true, 'boolean', 'training.enrolled-users', 'Show enrolled users for training'), + Setting('setting_basic', 'record.statMode', 'unique', 'text', 'record.statMode', 'Record stat mode'), Setting('setting_basic', 'pagination.problem', 100, 'number', 'pagination.problem', 'Problems per page'), Setting('setting_basic', 'pagination.contest', 20, 'number', 'pagination.contest', 'Contests per page'), Setting('setting_basic', 'pagination.discussion', 50, 'number', 'pagination.discussion', 'Discussions per page'),