From 89188d1598c24ce5443998c42cbbbdd87cba9825 Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 7 Oct 2024 23:15:06 +0800 Subject: [PATCH] core: change domain.union to namespaces --- framework/framework/validator.ts | 2 +- packages/elastic/index.ts | 6 +- packages/hydrooj/src/handler/user.ts | 19 +++--- packages/hydrooj/src/model/problem.ts | 34 +++------- packages/hydrooj/src/script/rating.ts | 4 +- packages/sonic/index.ts | 66 +++++++------------ .../templates/components/problem.html | 5 +- 7 files changed, 46 insertions(+), 90 deletions(-) diff --git a/framework/framework/validator.ts b/framework/framework/validator.ts index 2dcf7149d..d66f5f23a 100644 --- a/framework/framework/validator.ts +++ b/framework/framework/validator.ts @@ -82,7 +82,7 @@ export const Types: Types = { UidOrName: saslprepString(/^(.{3,31}|[\u4e00-\u9fa5]{2}|-?[0-9]+)$/), Username: saslprepString(/^(.{3,31}|[\u4e00-\u9fa5]{2})$/), Password: basicString(/^.{6,255}$/), - ProblemId: saslprepString(/^[a-zA-Z0-9]+$/i, () => true, (s) => (Number.isSafeInteger(+s) ? +s : s)), + ProblemId: saslprepString(/^([a-zA-Z0-9]{1,10}-)?[a-zA-Z0-9]+$/i, () => true, (s) => (Number.isSafeInteger(+s) ? +s : s)), Email: saslprepString(/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/i), DomainId: saslprepString(/^[a-zA-Z][a-zA-Z0-9_]{3,31}$/), Role: saslprepString(/^[_0-9A-Za-z\u4e00-\u9fa5]{1,31}$/i), diff --git a/packages/elastic/index.ts b/packages/elastic/index.ts index d33f89b55..60d51d6ec 100644 --- a/packages/elastic/index.ts +++ b/packages/elastic/index.ts @@ -1,6 +1,6 @@ import { Client } from '@elastic/elasticsearch'; import { - _, Context, DomainModel, iterateAllProblem, iterateAllProblemInDomain, + _, Context, iterateAllProblem, iterateAllProblemInDomain, ProblemDoc, ProblemModel, Schema, SystemModel, } from 'hydrooj'; @@ -19,8 +19,6 @@ global.Hydro.lib.problemSearch = async (domainId, q, opts) => { const allowedSize = SystemModel.get('elasic-search.indexSize') || 10000; const size = opts?.limit || SystemModel.get('pagination.problem'); const from = Math.min(allowedSize - size, opts?.skip || 0); - const union = await DomainModel.get(domainId); - const domainIds = [domainId, ...(union?.union || [])]; const res = await client.search({ index: 'problem', size, @@ -34,7 +32,7 @@ global.Hydro.lib.problemSearch = async (domainId, q, opts) => { post_filter: { bool: { minimum_should_match: 1, - should: domainIds.map((i) => ({ match: { domainId: i } })), + should: [{ match: { domainId } }], }, }, }); diff --git a/packages/hydrooj/src/handler/user.ts b/packages/hydrooj/src/handler/user.ts index 6eb0dbcf3..305b0b0c0 100644 --- a/packages/hydrooj/src/handler/user.ts +++ b/packages/hydrooj/src/handler/user.ts @@ -347,25 +347,22 @@ class UserDetailHandler extends Handler { async get(domainId: string, uid: number) { if (uid === 0) throw new UserNotFoundError(0); const isSelfProfile = this.user._id === uid; - const [udoc, sdoc, union] = await Promise.all([ + const [udoc, sdoc] = await Promise.all([ user.getById(domainId, uid), token.getMostRecentSessionByUid(uid, ['createAt', 'updateAt']), - domain.get(domainId), ]); if (!udoc) throw new UserNotFoundError(uid); const pdocs: ProblemDoc[] = []; const acInfo: Record = {}; const canViewHidden = this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN) || this.user._id; if (this.user.hasPerm(PERM.PERM_VIEW_PROBLEM)) { - await Promise.all([domainId, ...(union?.union || [])].map(async (did) => { - const psdocs = await problem.getMultiStatus(did, { uid, status: STATUS.STATUS_ACCEPTED }).toArray(); - pdocs.push(...Object.values( - await problem.getList( - did, psdocs.map((i) => i.docId), canViewHidden, - false, problem.PROJECTION_LIST, true, - ), - )); - })); + const psdocs = await problem.getMultiStatus(domainId, { uid, status: STATUS.STATUS_ACCEPTED }).toArray(); + pdocs.push(...Object.values( + await problem.getList( + domainId, psdocs.map((i) => i.docId), canViewHidden, + false, problem.PROJECTION_LIST, true, + ), + )); } for (const pdoc of pdocs) { for (const tag of pdoc.tag) { diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index b805a4cad..2f7dc4a1c 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -16,25 +16,25 @@ import type { } from '../interface'; import { parseConfig } from '../lib/testdataConfig'; import * as bus from '../service/bus'; +import db from '../service/db'; import { ArrayKeys, MaybeArray, NumberKeys, Projection, } from '../typeutils'; import { buildProjection } from '../utils'; import { PERM, STATUS } from './builtin'; import * as document from './document'; -import DomainModel from './domain'; import RecordModel from './record'; import SolutionModel from './solution'; import storage from './storage'; import * as SystemModel from './system'; -import user from './user'; export interface ProblemDoc extends Document { } export type Field = keyof ProblemDoc; const logger = new Logger('problem'); function sortable(source: string) { - return source.replace(/(\d+)/g, (str) => (str.length >= 6 ? str : ('0'.repeat(6 - str.length) + str))); + const [namespace, pid] = source.includes('-') ? source.split('-') : ['', source]; + return (namespace ? namespace.padEnd(6, ' ') : '') + pid.replace(/(\d+)/g, (str) => (str.length >= 6 ? str : ('0'.repeat(6 - str.length) + str))); } function findOverrideContent(dir: string, base: string) { @@ -191,30 +191,12 @@ export class ProblemModel { static async list( domainId: string, query: Filter, page: number, pageSize: number, - projection = ProblemModel.PROJECTION_LIST, uid?: number, + projection = ProblemModel.PROJECTION_LIST, ): Promise<[ProblemDoc[], number, number]> { - const union = await DomainModel.get(domainId); - const domainIds = [domainId, ...(union.union || [])]; - let count = 0; - const pdocs = []; - for (const id of domainIds) { - // TODO enhance performance - if (typeof uid === 'number') { - // eslint-disable-next-line no-await-in-loop - const udoc = await user.getById(id, uid); - if (!udoc.hasPerm(PERM.PERM_VIEW_PROBLEM)) continue; - } - // eslint-disable-next-line no-await-in-loop - const ccount = await document.count(id, document.TYPE_PROBLEM, query); - if (pdocs.length < pageSize && (page - 1) * pageSize - count <= ccount) { - // eslint-disable-next-line no-await-in-loop - pdocs.push(...await document.getMulti(id, document.TYPE_PROBLEM, query, projection) - .sort({ sort: 1, docId: 1 }) - .skip(Math.max((page - 1) * pageSize - count, 0)).limit(pageSize - pdocs.length).toArray()); - } - count += ccount; - } - return [pdocs, Math.ceil(count / pageSize), count]; + return await db.paginate( + document.getMulti(domainId, document.TYPE_PROBLEM, query, projection).sort({ sort: 1, docId: 1 }), + page, pageSize, + ); } static getStatus(domainId: string, docId: number, uid: number) { diff --git a/packages/hydrooj/src/script/rating.ts b/packages/hydrooj/src/script/rating.ts index 60bd95c0d..2ec3fa0af 100644 --- a/packages/hydrooj/src/script/rating.ts +++ b/packages/hydrooj/src/script/rating.ts @@ -136,14 +136,12 @@ export async function calcLevel(domainId: string, report: Function) { } async function runInDomain(domainId: string, report: Function) { - const info = await domain.get(domainId); - const domainIds = [domainId, ...(info.union || [])]; const results: Record = {}; const udict = Counter(); await db.collection('domain.user').updateMany({ domainId }, { $set: { rpInfo: {} } }); for (const type in RpTypes) { results[type] = new Proxy({}, { get: (self, key) => self[key] || RpTypes[type].base }); - await RpTypes[type].run(domainIds, results[type], report); + await RpTypes[type].run([domainId], results[type], report); const bulk = db.collection('domain.user').initializeUnorderedBulkOp(); for (const uid in results[type]) { const udoc = await UserModel.getById(domainId, +uid); diff --git a/packages/sonic/index.ts b/packages/sonic/index.ts index 2fd9014a4..74c4e4414 100644 --- a/packages/sonic/index.ts +++ b/packages/sonic/index.ts @@ -1,11 +1,12 @@ import { - Context, DomainModel, iterateAllProblem, iterateAllProblemInDomain, + Context, iterateAllProblem, iterateAllProblemInDomain, Logger, ProblemModel, Schema, SystemModel, } from 'hydrooj'; import { SonicService } from './service'; const logger = new Logger('sonic'); +export const inject = { 'sonic': { required: false } }; export function apply(ctx: Context) { ctx.plugin(SonicService); @@ -37,18 +38,14 @@ export function apply(ctx: Context) { const cb = async (pdoc) => { i++; if (!(i % 1000)) report({ message: `${i} problems indexed` }); - const union = await DomainModel.getMulti({ union: pdoc.domainId }).toArray(); - const tasks = []; - for (const did of [pdoc.domainId, ...union.map((j) => j._id)]) { - tasks.push( - pdoc.title && ctx.sonic.push( - 'problem', `${did}@title`, `${pdoc.domainId}/${pdoc.docId}`, - `${pdoc.pid || ''} ${pdoc.title} ${pdoc.tag.join(' ')}`, - ), - pdoc.content.toString() && ctx.sonic.push('problem', `${did}@content`, `${pdoc.domainId}/${pdoc.docId}`, pdoc.content.toString()), - ); - } - await Promise.all(tasks).catch((e) => console.log(`${pdoc.domainId}/${pdoc.docId}`, e)); + await Promise.all([ + pdoc.title && ctx.sonic.push( + 'problem', `${pdoc.domainid}@title`, `${pdoc.domainId}/${pdoc.docId}`, + `${pdoc.pid || ''} ${pdoc.title} ${pdoc.tag.join(' ')}`, + ), + pdoc.content.toString() + && ctx.sonic.push('problem', `${pdoc.domainId}@content`, `${pdoc.domainId}/${pdoc.docId}`, pdoc.content.toString()), + ]).catch((e) => console.log(`${pdoc.domainId}/${pdoc.docId}`, e)); }; if (domainId) await iterateAllProblemInDomain(domainId, ['title', 'content'], cb); else await iterateAllProblem(['title', 'content', 'tag'], cb); @@ -56,43 +53,28 @@ export function apply(ctx: Context) { } ctx.on('problem/add', async (doc, docId) => { - const union = await DomainModel.getMulti({ union: doc.domainId }).toArray(); - const tasks = []; - for (const domainId of [doc.domainId, ...union.map((i) => i._id)]) { - tasks.push( - ctx.sonic.push('problem', `${domainId}@title`, `${doc.domainId}/${docId}`, `${doc.pid || ''} ${doc.title} ${doc.tag?.join(' ')}`), - ctx.sonic.push('problem', `${domainId}@content`, `${doc.domainId}/${docId}`, doc.content.toString()), - ); - } - Promise.all(tasks).catch((e) => logger.error(e)); + Promise.all([ + ctx.sonic.push('problem', `${doc.domainId}@title`, `${doc.domainId}/${docId}`, `${doc.pid || ''} ${doc.title} ${doc.tag?.join(' ')}`), + ctx.sonic.push('problem', `${doc.domainId}@content`, `${doc.domainId}/${docId}`, doc.content.toString()), + ]).catch((e) => logger.error(e)); }); ctx.on('problem/edit', async (pdoc) => { - const union = await DomainModel.getMulti({ union: pdoc.domainId }).toArray(); - const tasks = []; const id = `${pdoc.domainId}/${pdoc.docId}`; - for (const domainId of [pdoc.domainId, ...union.map((i) => i._id)]) { - tasks.push( - ctx.sonic.flusho('problem', `${domainId}@title`, id) - .then(() => ctx.sonic.push('problem', `${domainId}@title`, id, `${pdoc.pid || ''} ${pdoc.title} ${pdoc.tag?.join(' ')}`)), - ctx.sonic.flusho('problem', `${domainId}@content`, id) - .then(() => ctx.sonic.push('problem', `${domainId}@content`, id, pdoc.content.toString())), - ); - } - Promise.all(tasks).catch((e) => logger.error(e)); + Promise.all([ + ctx.sonic.flusho('problem', `${pdoc.domainId}@title`, id) + .then(() => ctx.sonic.push('problem', `${pdoc.domainId}@title`, id, `${pdoc.pid || ''} ${pdoc.title} ${pdoc.tag?.join(' ')}`)), + ctx.sonic.flusho('problem', `${pdoc.domainId}@content`, id) + .then(() => ctx.sonic.push('problem', `${pdoc.domainId}@content`, id, pdoc.content.toString())), + ]).catch((e) => logger.error(e)); }); ctx.on('problem/del', async (domainId, docId) => { - const union = await DomainModel.getMulti({ union: domainId }).toArray(); - const tasks = []; const id = `${domainId}/${docId}`; - for (const domain of [domainId, ...union.map((i) => i._id)]) { - tasks.push( - ctx.sonic.flusho('problem', `${domain}@title`, id), - ctx.sonic.flusho('problem', `${domain}@content`, id), - ); - } - await Promise.all(tasks); + await Promise.all([ + ctx.sonic.flusho('problem', `${domainId}@title`, id), + ctx.sonic.flusho('problem', `${domainId}@content`, id), + ]); }); ctx.addScript( diff --git a/packages/ui-default/templates/components/problem.html b/packages/ui-default/templates/components/problem.html index 3cb6034ae..245711184 100644 --- a/packages/ui-default/templates/components/problem.html +++ b/packages/ui-default/templates/components/problem.html @@ -2,14 +2,13 @@ {%- if not invalid -%} {% set _linkArgs = { pid:pdoc.pid|default(pdoc.docId) } %} {% if tdoc %}{{ set(_linkArgs, 'query', {tid:tdoc.docId}) }}{% endif %} - {% if pdoc.domainId !== handler.args.domainId %}{{ set(_linkArgs, 'domainId', pdoc.domainId) }}{% endif %} {%- endif -%} -{%- if pdoc.domainId !== handler.args.domainId -%}{{ pdoc.domainId }}#{%- endif -%} +{%- if pdoc.pid and pdoc.pid.includes('-') -%}{{ pdoc.pid.split('-')[0] }}#{%- endif -%} {%- if tdoc and alphabetic -%} {{ String.fromCharCode(65 + tdoc.pids.indexOf(pdoc.docId)) if tdoc.pids.includes(pdoc.docId) else '#' }} {%- else -%} - {{ pdoc.pid or ('P'+pdoc.docId if pdoc.domainId === handler.args.domainId else pdoc.docId) }} + {{ pdoc.pid.split('-')[1] if pdoc.pid else ('P'+pdoc.docId) }} {%- endif -%} {%- if not small -%}  {{ pdoc.title }}{%- endif -%} {%- if not invalid -%}