core: change domain.union to namespaces

This commit is contained in:
undefined committed 2024-10-07 23:15:06 +08:00
1 parent 4b7a080de7
commit 89188d1598
7 files changed
+46 -90

No files matched your search

+1 -1
View File
@@ -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),
+2 -4
View File
@@ -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 } }],
},
},
});
+8 -11
View File
@@ -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<string, number> = {};
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) {
+8 -26
View File
@@ -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<ProblemDoc>,
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) {
+1 -3
View File
@@ -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<keyof typeof RpTypes, ND> = {};
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);
+24 -42
View File
@@ -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(
@@ -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 %}
<a href="{{ url('problem_detail', _linkArgs) }}"{% if small %} data-tooltip="{{ pdoc.title }}"{% endif %}>
{%- endif -%}
{%- if pdoc.domainId !== handler.args.domainId -%}<b>{{ pdoc.domainId }}#</b>{%- endif -%}
{%- if pdoc.pid and pdoc.pid.includes('-') -%}<b>{{ pdoc.pid.split('-')[0] }}#</b>{%- endif -%}
{%- if tdoc and alphabetic -%}
<b>{{ String.fromCharCode(65 + tdoc.pids.indexOf(pdoc.docId)) if tdoc.pids.includes(pdoc.docId) else '#' }}</b>
{%- else -%}
<b>{{ pdoc.pid or ('P'+pdoc.docId if pdoc.domainId === handler.args.domainId else pdoc.docId) }}</b>
<b>{{ pdoc.pid.split('-')[1] if pdoc.pid else ('P'+pdoc.docId) }}</b>
{%- endif -%}
{%- if not small -%}&nbsp;&nbsp;{{ pdoc.title }}{%- endif -%}
{%- if not invalid -%}