thanks @Molmin
This commit is contained in:
undefined committed 2024-08-10 22:24:54 +08:00
1 parent 609466016c
commit a1d392a409
4 files changed
+27 -34

No files matched your search

+5 -2
View File
@@ -40,16 +40,19 @@ global.Hydro.lib.problemSearch = async (domainId, q, opts) => {
}); });
let hits = res.hits.hits.map((i) => i._id); let hits = res.hits.hits.map((i) => i._id);
if (!opts.skip) { if (!opts.skip) {
const pdoc = await ProblemModel.get(domainId, +q || q, ProblemModel.PROJECTION_LIST); let pdoc = await ProblemModel.get(domainId, +q || q, ProblemModel.PROJECTION_LIST);
if (pdoc) { if (pdoc) {
hits = hits.filter((i) => i !== `${pdoc.domainId}/${pdoc.docId}`); hits = hits.filter((i) => i !== `${pdoc.domainId}/${pdoc.docId}`);
hits.unshift(`${pdoc.domainId}/${pdoc.docId}`); hits.unshift(`${pdoc.domainId}/${pdoc.docId}`);
} else if (/^P\d+$/.test(q)) {
pdoc = await ProblemModel.get(domainId, +q.substring(1), ProblemModel.PROJECTION_LIST);
if (pdoc) hits.unshift(`${pdoc.domainId}/${pdoc.docId}`);
} }
} }
return { return {
countRelation: typeof res.hits.total === 'number' ? 'eq' : res.hits.total.relation, countRelation: typeof res.hits.total === 'number' ? 'eq' : res.hits.total.relation,
total: typeof res.hits.total === 'number' ? res.hits.total : res.hits.total.value, total: typeof res.hits.total === 'number' ? res.hits.total : res.hits.total.value,
hits, hits: Array.from(new Set(hits)),
}; };
}; };
+16 -30
View File
@@ -1,7 +1,7 @@
import AdmZip from 'adm-zip'; import AdmZip from 'adm-zip';
import { readFile, statSync } from 'fs-extra'; import { readFile, statSync } from 'fs-extra';
import { import {
escapeRegExp, flattenDeep, intersection, pick, uniqBy, escapeRegExp, flattenDeep, intersection, pick,
} from 'lodash'; } from 'lodash';
import { Filter, ObjectId } from 'mongodb'; import { Filter, ObjectId } from 'mongodb';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
@@ -34,7 +34,6 @@ import user from '../model/user';
import { import {
Handler, param, post, query, route, Types, Handler, param, post, query, route, Types,
} from '../service/server'; } from '../service/server';
import { buildProjection } from '../utils';
import { ContestDetailBaseHandler } from './contest'; import { ContestDetailBaseHandler } from './contest';
export const parseCategory = (value: string) => value.replace(/,/g, ',').split(',').map((e) => e.trim()); export const parseCategory = (value: string) => value.replace(/,/g, ',').split(',').map((e) => e.trim());
@@ -53,16 +52,21 @@ function buildQuery(udoc: User) {
const defaultSearch = async (domainId: string, q: string, options?: ProblemSearchOptions) => { const defaultSearch = async (domainId: string, q: string, options?: ProblemSearchOptions) => {
const escaped = escapeRegExp(q.toLowerCase()); const escaped = escapeRegExp(q.toLowerCase());
const projection: (keyof ProblemDoc)[] = ['domainId', 'docId', 'pid'];
const $regex = new RegExp(q.length >= 2 ? escaped : `\\A${escaped}`, 'gmi'); const $regex = new RegExp(q.length >= 2 ? escaped : `\\A${escaped}`, 'gmi');
const filter = { $or: [{ pid: { $regex } }, { title: { $regex } }, { tag: q }] }; const filter = { $or: [{ pid: { $regex } }, { title: { $regex } }, { tag: q }] };
const pdocs = await problem.getMulti(domainId, filter, ['domainId', 'docId', 'pid']) const pdocs = await problem.getMulti(domainId, filter, projection)
.skip(options.skip || 0).limit(options.limit || system.get('pagination.problem')).toArray(); .skip(options.skip || 0).limit(options.limit || system.get('pagination.problem')).toArray();
if (!options.skip) { if (!options.skip) {
const pdoc = await problem.get(domainId, +q || q, ['domainId', 'docId', 'pid', 'title']); let pdoc = await problem.get(domainId, Number.isSafeInteger(+q) ? +q : q, projection);
if (pdoc) pdocs.unshift(pdoc); if (pdoc) pdocs.unshift(pdoc);
else if (/^P\d+$/.test(q)) {
pdoc = await problem.get(domainId, +q.substring(1), projection);
if (pdoc) pdocs.unshift(pdoc);
}
} }
return { return {
hits: pdocs.map((i) => `${i.domainId}/${i.docId}`), hits: Array.from(new Set(pdocs.map((i) => `${i.domainId}/${i.docId}`))),
total: Math.max(pdocs.length, await problem.count(domainId, filter)), total: Math.max(pdocs.length, await problem.count(domainId, filter)),
countRelation: 'eq', countRelation: 'eq',
}; };
@@ -95,7 +99,8 @@ export class ProblemMainHandler extends Handler {
@param('q', Types.Content, true) @param('q', Types.Content, true)
@param('limit', Types.PositiveInt, true) @param('limit', Types.PositiveInt, true)
@param('pjax', Types.Boolean) @param('pjax', Types.Boolean)
async get(domainId: string, page = 1, q = '', limit: number, pjax = false) { @param('quick', Types.Boolean)
async get(domainId: string, page = 1, q = '', limit: number, pjax = false, quick = false) {
this.response.template = 'problem_main.html'; this.response.template = 'problem_main.html';
if (!limit || limit > this.ctx.setting.get('pagination.problem') || page > 1) limit = this.ctx.setting.get('pagination.problem'); if (!limit || limit > this.ctx.setting.get('pagination.problem') || page > 1) limit = this.ctx.setting.get('pagination.problem');
this.queryContext.query = buildQuery(this.user); this.queryContext.query = buildQuery(this.user);
@@ -137,7 +142,11 @@ export class ProblemMainHandler extends Handler {
// eslint-disable-next-line prefer-const // eslint-disable-next-line prefer-const
let [pdocs, ppcount, pcount] = this.queryContext.fail let [pdocs, ppcount, pcount] = this.queryContext.fail
? [[], 0, 0] ? [[], 0, 0]
: await problem.list(domainId, query, sort.length ? 1 : page, limit, undefined, this.user._id); : await problem.list(
domainId, query, sort.length ? 1 : page, limit,
quick ? ['title', 'pid', 'domainId', 'docId'] : undefined,
this.user._id,
);
if (total) { if (total) {
pcount = total; pcount = total;
ppcount = Math.ceil(total / limit); ppcount = Math.ceil(total / limit);
@@ -995,28 +1004,6 @@ export class ProblemCreateHandler extends Handler {
} }
} }
export class ProblemPrefixListHandler extends Handler {
@param('prefix', Types.Name)
async get(domainId: string, prefix: string) {
const projection = ['domainId', 'docId', 'pid', 'title', 'hidden'] as const;
const [pdocs, pdoc, apdoc] = await Promise.all([
problem.getPrefixList(domainId, prefix),
problem.get(domainId, Number.isSafeInteger(+prefix) ? +prefix : prefix, projection),
/^P\d+$/.test(prefix) ? problem.get(domainId, +prefix.substring(1), projection) : Promise.resolve(null),
]);
if (apdoc) pdocs.unshift(apdoc);
if (pdoc) pdocs.unshift(pdoc);
if (pdocs.length < 20) {
const search = global.Hydro.lib.problemSearch || defaultSearch;
const result = await search(domainId, prefix, { limit: 20 - pdocs.length });
const docs = await problem.getMulti(domainId, { docId: { $in: result.hits.map((i) => +i.split('/')[1]) } })
.project<ProblemDoc>(buildProjection(projection)).toArray();
pdocs.push(...docs);
}
this.response.body = uniqBy(pdocs, 'docId');
}
}
export async function apply(ctx: Context) { export async function apply(ctx: Context) {
ctx.Route('problem_main', '/p', ProblemMainHandler, PERM.PERM_VIEW_PROBLEM); ctx.Route('problem_main', '/p', ProblemMainHandler, PERM.PERM_VIEW_PROBLEM);
ctx.Route('problem_random', '/problem/random', ProblemRandomHandler, PERM.PERM_VIEW_PROBLEM); ctx.Route('problem_random', '/problem/random', ProblemRandomHandler, PERM.PERM_VIEW_PROBLEM);
@@ -1033,7 +1020,6 @@ export async function apply(ctx: Context) {
ctx.Route('problem_solution_reply_raw', '/p/:pid/solution/:psid/:psrid/raw', ProblemSolutionRawHandler, PERM.PERM_VIEW_PROBLEM); ctx.Route('problem_solution_reply_raw', '/p/:pid/solution/:psid/:psrid/raw', ProblemSolutionRawHandler, PERM.PERM_VIEW_PROBLEM);
ctx.Route('problem_statistics', '/p/:pid/stat', ProblemStatisticsHandler, PERM.PERM_VIEW_PROBLEM); ctx.Route('problem_statistics', '/p/:pid/stat', ProblemStatisticsHandler, PERM.PERM_VIEW_PROBLEM);
ctx.Route('problem_create', '/problem/create', ProblemCreateHandler, PERM.PERM_CREATE_PROBLEM); ctx.Route('problem_create', '/problem/create', ProblemCreateHandler, PERM.PERM_CREATE_PROBLEM);
ctx.Route('problem_prefix_list', '/problem/list', ProblemPrefixListHandler, PERM.PERM_VIEW_PROBLEM);
ctx.inject(['api'], ({ api }) => { ctx.inject(['api'], ({ api }) => {
api.value('FileInfo', [ api.value('FileInfo', [
['_id', 'String!'], ['_id', 'String!'],
+2 -1
View File
@@ -162,7 +162,8 @@ export class ProblemModel {
if (Number.isSafeInteger(+pid)) pid = +pid; if (Number.isSafeInteger(+pid)) pid = +pid;
const res = typeof pid === 'number' const res = typeof pid === 'number'
? await document.get(domainId, document.TYPE_PROBLEM, pid, projection) ? await document.get(domainId, document.TYPE_PROBLEM, pid, projection)
: (await document.getMulti(domainId, document.TYPE_PROBLEM, { sort: sortable(pid), pid }).toArray())[0]; : (await document.getMulti(domainId, document.TYPE_PROBLEM, { sort: sortable(pid), pid })
.project(buildProjection(projection)).limit(1).toArray())[0];
if (!res) return null; if (!res) return null;
try { try {
if (!rawConfig) res.config = await parseConfig(res.config); if (!rawConfig) res.config = await parseConfig(res.config);
@@ -8,7 +8,10 @@ const ProblemSelectAutoComplete = forwardRef<AutoCompleteHandle<ProblemDoc>, Aut
<AutoComplete<ProblemDoc> <AutoComplete<ProblemDoc>
ref={ref as any} ref={ref as any}
cacheKey={`problem-${UiContext.domainId}`} cacheKey={`problem-${UiContext.domainId}`}
queryItems={(query) => request.get(`/d/${UiContext.domainId}/problem/list`, { prefix: query })} queryItems={async (query) => {
const { pdocs } = await request.get(`/d/${UiContext.domainId}/p`, { q: query, quick: true });
return pdocs;
}}
fetchItems={(ids) => api(gql` fetchItems={(ids) => api(gql`
problems(ids: ${ids.map((i) => +i)}) { problems(ids: ${ids.map((i) => +i)}) {
docId docId