mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core: use rdoc.source & pdoc.reference
This commit is contained in:
1 parent
465a80d617
commit
4aa689b508
24 files changed
+250
-250
No files matched your search
@@ -5,8 +5,8 @@ import {
|
||||
} from '../error';
|
||||
import {
|
||||
ContestRule, ContestRules, ProblemDict,
|
||||
ProblemId, ScoreboardNode, ScoreboardRow,
|
||||
Tdoc, Udict,
|
||||
ScoreboardNode, ScoreboardRow, Tdoc,
|
||||
Udict,
|
||||
} from '../interface';
|
||||
import * as misc from '../lib/misc';
|
||||
import ranked from '../lib/rank';
|
||||
@@ -18,7 +18,7 @@ import user from './user';
|
||||
|
||||
interface AcmJournal {
|
||||
rid: ObjectID;
|
||||
pid: ProblemId;
|
||||
pid: number;
|
||||
score: number;
|
||||
status: number;
|
||||
time: number;
|
||||
@@ -37,7 +37,7 @@ const acm: ContestRule = {
|
||||
showSelfRecord: () => true,
|
||||
showRecord: (tdoc, now) => now > tdoc.endAt,
|
||||
stat: (tdoc, journal: AcmJournal[]) => {
|
||||
const naccept: Record<ProblemId, number> = {};
|
||||
const naccept: Record<number, number> = {};
|
||||
const effective: Record<number, AcmJournal> = {};
|
||||
const detail: AcmDetail[] = [];
|
||||
let accept = 0;
|
||||
@@ -222,14 +222,7 @@ const oi: ContestRule = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
if (isDone(tdoc)) {
|
||||
const psdocs = await Promise.all(
|
||||
tdoc.pids.map((pid) => {
|
||||
let pdomain = tdoc.domainId;
|
||||
if (typeof pid === 'string') {
|
||||
pdomain = pid.split(':')[0];
|
||||
pid = +pid.split(':')[1];
|
||||
}
|
||||
return problem.getMultiStatus(pdomain, { docId: pid, uid: { $in: uids } }).toArray();
|
||||
}),
|
||||
tdoc.pids.map((pid) => problem.getMultiStatus(tdoc.domainId, { docId: pid, uid: { $in: uids } }).toArray()),
|
||||
);
|
||||
for (const tpsdoc of psdocs) {
|
||||
for (const psdoc of tpsdoc) {
|
||||
@@ -250,9 +243,7 @@ const oi: ContestRule = {
|
||||
{ type: 'string', value: tsdoc.score || 0 },
|
||||
);
|
||||
for (const pid of tdoc.pids) {
|
||||
const index = pid.toString().includes(':')
|
||||
? `${tsdoc.uid}/${pid.toString().replace(':', '/')}`
|
||||
: `${tsdoc.uid}/${tdoc.domainId}/${pid}`;
|
||||
const index = `${tsdoc.uid}/${tdoc.domainId}/${pid}`;
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
const node: ScoreboardNode = (!isExport && isDone(tdoc) && tsddict[pid]?.rid?.toHexString() !== psdict[index]?.rid?.toHexString())
|
||||
? {
|
||||
@@ -450,7 +441,7 @@ function _getStatusJournal(tsdoc) {
|
||||
|
||||
export async function add(
|
||||
domainId: string, title: string, content: string, owner: number,
|
||||
rule: string, beginAt = new Date(), endAt = new Date(), pids: ProblemId[] = [],
|
||||
rule: string, beginAt = new Date(), endAt = new Date(), pids: number[] = [],
|
||||
rated = false, data: Partial<Tdoc> = {},
|
||||
) {
|
||||
if (!this.RULES[rule]) throw new ValidationError('rule');
|
||||
@@ -489,7 +480,7 @@ export async function get(domainId: string, tid: ObjectID): Promise<Tdoc<30>> {
|
||||
return tdoc;
|
||||
}
|
||||
|
||||
export async function getRelated(domainId: string, pid: ProblemId) {
|
||||
export async function getRelated(domainId: string, pid: number) {
|
||||
return await document.getMulti(domainId, document.TYPE_CONTEST, { pids: pid }).toArray();
|
||||
}
|
||||
|
||||
@@ -498,7 +489,7 @@ export function getStatus(domainId: string, tid: ObjectID, uid: number) {
|
||||
}
|
||||
|
||||
export async function updateStatus(
|
||||
domainId: string, tid: ObjectID, uid: number, rid: ObjectID, pid: ProblemId,
|
||||
domainId: string, tid: ObjectID, uid: number, rid: ObjectID, pid: number,
|
||||
status = STATUS.STATUS_WRONG_ANSWER, score = 0,
|
||||
) {
|
||||
const [tdoc, otsdoc] = await Promise.all([
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import {
|
||||
escapeRegExp, flatten, groupBy, pick,
|
||||
escapeRegExp, pick,
|
||||
} from 'lodash';
|
||||
import { FilterQuery, ObjectID } from 'mongodb';
|
||||
import type { Readable } from 'stream';
|
||||
import { streamToBuffer } from '@hydrooj/utils/lib/utils';
|
||||
import { ProblemNotFoundError, ValidationError } from '../error';
|
||||
import type {
|
||||
Document, DomainDoc, ProblemDict,
|
||||
ProblemId, ProblemStatusDoc,
|
||||
Document, ProblemDict, ProblemStatusDoc,
|
||||
} from '../interface';
|
||||
import { parseConfig } from '../lib/testdataConfig';
|
||||
import * as bus from '../service/bus';
|
||||
@@ -17,7 +16,6 @@ import {
|
||||
import { buildProjection } from '../utils';
|
||||
import { STATUS } from './builtin';
|
||||
import * as document from './document';
|
||||
import domain from './domain';
|
||||
import storage from './storage';
|
||||
|
||||
export interface ProblemDoc extends Document { }
|
||||
@@ -33,6 +31,7 @@ export class ProblemModel {
|
||||
static PROJECTION_PUBLIC: Field[] = [
|
||||
...ProblemModel.PROJECTION_LIST,
|
||||
'content', 'html', 'data', 'config', 'additional_file',
|
||||
'reference',
|
||||
];
|
||||
|
||||
static default = {
|
||||
@@ -112,12 +111,6 @@ export class ProblemModel {
|
||||
if (typeof pid !== 'number') {
|
||||
if (Number.isSafeInteger(parseInt(pid, 10))) pid = parseInt(pid, 10);
|
||||
}
|
||||
if (typeof pid === 'string') {
|
||||
if (pid.includes(':')) {
|
||||
domainId = pid.split(':')[0];
|
||||
pid = +pid.split(':')[1];
|
||||
}
|
||||
}
|
||||
const res = typeof pid === 'number'
|
||||
? await document.get(domainId, document.TYPE_PROBLEM, pid, projection)
|
||||
: (await document.getMulti(domainId, document.TYPE_PROBLEM, { pid }).toArray())[0];
|
||||
@@ -151,6 +144,19 @@ export class ProblemModel {
|
||||
return result;
|
||||
}
|
||||
|
||||
static async copy(domainId: string, _id: number, target: string, pid?: string) {
|
||||
const original = await ProblemModel.get(domainId, _id);
|
||||
if (!original) throw new ProblemNotFoundError(domainId, _id);
|
||||
if (pid && await ProblemModel.get(target, pid)) pid = '';
|
||||
if (!pid && original.pid && !await ProblemModel.get(target, original.pid)) pid = original.pid;
|
||||
const docId = await ProblemModel.add(
|
||||
target, pid, original.title, original.content,
|
||||
original.owner, original.tag, original.hidden,
|
||||
);
|
||||
await ProblemModel.edit(target, docId, { reference: { domainId, pid: _id } });
|
||||
return docId;
|
||||
}
|
||||
|
||||
static push<T extends ArrayKeys<ProblemDoc>>(domainId: string, _id: number, key: ArrayKeys<ProblemDoc>, value: ProblemDoc[T][0]) {
|
||||
return document.push(domainId, document.TYPE_PROBLEM, _id, key, value);
|
||||
}
|
||||
@@ -159,12 +165,7 @@ export class ProblemModel {
|
||||
return document.deleteSub(domainId, document.TYPE_PROBLEM, pid, key, values);
|
||||
}
|
||||
|
||||
static inc(domainId: string, _id: ProblemId, field: NumberKeys<ProblemDoc> | string, n: number): Promise<ProblemDoc> {
|
||||
if (typeof _id === 'string') {
|
||||
if (!_id.includes(':')) throw new Error(`model.problem.inc: invalid _id <${_id}>`);
|
||||
domainId = _id.split(':')[0];
|
||||
_id = +_id.split(':')[1];
|
||||
}
|
||||
static inc(domainId: string, _id: number, field: NumberKeys<ProblemDoc> | string, n: number): Promise<ProblemDoc> {
|
||||
return document.inc(domainId, document.TYPE_PROBLEM, _id, field as any, n);
|
||||
}
|
||||
|
||||
@@ -235,42 +236,14 @@ export class ProblemModel {
|
||||
}
|
||||
|
||||
static async getList(
|
||||
domainId: string, pids: ProblemId[],
|
||||
domainId: string, pids: number[],
|
||||
getHidden: number | boolean = false, doThrow = true, projection = ProblemModel.PROJECTION_PUBLIC,
|
||||
): Promise<ProblemDict> {
|
||||
const parsed = groupBy(
|
||||
Array.from(new Set(pids)).map((i) => ({
|
||||
domainId: (typeof i === 'string' && i.includes(':')) ? i.split(':')[0] : domainId,
|
||||
pid: (typeof i === 'string' && i.includes(':')) ? +i.split(':')[1] : i,
|
||||
})),
|
||||
'domainId',
|
||||
);
|
||||
const r: Record<ProblemId, ProblemDoc> = {};
|
||||
const r: Record<number, ProblemDoc> = {};
|
||||
const l: Record<string, ProblemDoc> = {};
|
||||
const ddocs = await Promise.all(Object.keys(parsed).map((i) => domain.get(i)));
|
||||
const f = ddocs.filter((i) => !(
|
||||
!i || i._id === domainId
|
||||
|| i.share === '*'
|
||||
|| (`,${(i.share || '').replace(/,/g, ',').split(',').map((q) => q.trim()).join(',')},`).includes(`,${domainId},`)
|
||||
)) as DomainDoc[];
|
||||
if (f.length) {
|
||||
if (doThrow) throw new ProblemNotFoundError(f[0]._id, parsed[f[0]._id][0].pid);
|
||||
else {
|
||||
for (const sf of f) {
|
||||
for (const pinfo of parsed[sf._id]) {
|
||||
r[pinfo.pid] = { ...ProblemModel.default, domainId: sf._id, pid: pinfo.pid.toString() };
|
||||
}
|
||||
delete parsed[sf._id];
|
||||
}
|
||||
}
|
||||
}
|
||||
const tasks = [];
|
||||
for (const task in parsed) {
|
||||
const range = { $in: parsed[task].map((i) => i.pid) };
|
||||
const q: any = { $or: [{ docId: range }, { pid: range }] };
|
||||
tasks.push(document.getMulti(task, document.TYPE_PROBLEM, q).project(buildProjection(projection)).toArray());
|
||||
}
|
||||
let pdocs = flatten(await Promise.all(tasks));
|
||||
const q: any = { $or: [{ docId: { $in: pids } }, { pid: { $in: pids } }] };
|
||||
let pdocs = await document.getMulti(domainId, document.TYPE_PROBLEM, q)
|
||||
.project(buildProjection(projection)).toArray();
|
||||
if (getHidden !== true) pdocs = pdocs.filter((i) => !i.hidden || i.owner === getHidden);
|
||||
for (const pdoc of pdocs) {
|
||||
try {
|
||||
@@ -279,13 +252,8 @@ export class ProblemModel {
|
||||
} catch (e) {
|
||||
pdoc.config = `Cannot parse: ${e.message}`;
|
||||
}
|
||||
if (pdoc.domainId === domainId) {
|
||||
r[pdoc.docId] = pdoc;
|
||||
if (pdoc.pid) l[pdoc.pid] = pdoc;
|
||||
} else {
|
||||
r[`${pdoc.domainId}:${pdoc.docId}`] = pdoc;
|
||||
if (pdoc.pid) l[`${pdoc.domainId}:${pdoc.pid}`] = pdoc;
|
||||
}
|
||||
r[pdoc.docId] = pdoc;
|
||||
if (pdoc.pid) l[pdoc.pid] = pdoc;
|
||||
}
|
||||
// TODO enhance
|
||||
if (pdocs.length !== pids.length) {
|
||||
|
||||
@@ -8,8 +8,7 @@ import {
|
||||
} from 'mongodb';
|
||||
import { ProblemNotFoundError } from '../error';
|
||||
import {
|
||||
ExternalProblemId, FileInfo, ProblemConfigFile,
|
||||
RecordDoc,
|
||||
FileInfo, ProblemConfigFile, RecordDoc,
|
||||
} from '../interface';
|
||||
import * as bus from '../service/bus';
|
||||
import db from '../service/db';
|
||||
@@ -23,8 +22,8 @@ class RecordModel {
|
||||
static coll: Collection<RecordDoc> = db.collection('record');
|
||||
static PROJECTION_LIST: (keyof RecordDoc)[] = [
|
||||
'_id', 'score', 'time', 'memory', 'lang',
|
||||
'uid', 'pid', 'rejudged', 'progress', 'pdomain',
|
||||
'contest', 'judger', 'judgeAt', 'status', 'domainId',
|
||||
'uid', 'pid', 'rejudged', 'progress', 'domainId',
|
||||
'contest', 'judger', 'judgeAt', 'status', 'source',
|
||||
];
|
||||
|
||||
static async submissionPriority(uid: number, base: number = 0) {
|
||||
@@ -65,9 +64,15 @@ class RecordModel {
|
||||
const rdoc = await RecordModel.get(domainId, rid);
|
||||
if (!rdoc) return null;
|
||||
let data: FileInfo[] = [];
|
||||
let source = `${domainId}/${rdoc.pid}`;
|
||||
if (rdoc.pid) {
|
||||
const pdoc = await problem.get(rdoc.pdomain, rdoc.pid);
|
||||
if (!pdoc) throw new ProblemNotFoundError(rdoc.pdomain, rdoc.pid);
|
||||
let pdoc = await problem.get(rdoc.domainId, rdoc.pid);
|
||||
if (!pdoc) throw new ProblemNotFoundError(rdoc.domainId, rdoc.pid);
|
||||
if (pdoc.reference) {
|
||||
pdoc = await problem.get(pdoc.reference.domainId, pdoc.reference.pid);
|
||||
if (!pdoc) throw new ProblemNotFoundError(rdoc.domainId, rdoc.pid);
|
||||
}
|
||||
source = `${pdoc.domainId}/${pdoc.docId}`;
|
||||
data = pdoc.data;
|
||||
if (typeof pdoc.config === 'string') throw new Error(pdoc.config);
|
||||
if (pdoc.config.type === 'remote_judge') {
|
||||
@@ -92,25 +97,20 @@ class RecordModel {
|
||||
domainId,
|
||||
config,
|
||||
data,
|
||||
source,
|
||||
});
|
||||
}
|
||||
|
||||
static async add(
|
||||
domainId: string, pid: ExternalProblemId | number, uid: number,
|
||||
domainId: string, pid: number, uid: number,
|
||||
lang: string, code: string, addTask: boolean, tidOrInput?: ObjectID | string, isContest?: boolean,
|
||||
) {
|
||||
let pdomain = domainId;
|
||||
if (typeof pid === 'string') {
|
||||
pdomain = pid.split(':')[0];
|
||||
pid = +pid.split(':')[1];
|
||||
}
|
||||
const data: RecordDoc = {
|
||||
status: STATUS.STATUS_WAITING,
|
||||
_id: new ObjectID(),
|
||||
uid,
|
||||
code,
|
||||
lang,
|
||||
pdomain,
|
||||
pid,
|
||||
domainId,
|
||||
score: 0,
|
||||
@@ -214,11 +214,8 @@ class RecordModel {
|
||||
}
|
||||
|
||||
// Mark problem as deleted
|
||||
bus.on('problem/delete', (domainId, docId) => RecordModel.coll.updateMany({ pdomain: domainId, pid: docId }, { $set: { pid: -1 } }));
|
||||
bus.on('domain/delete', (domainId) => Promise.all([
|
||||
RecordModel.coll.deleteMany({ domainId }),
|
||||
RecordModel.coll.updateMany({ pdomain: domainId }, { $set: { docId: -1 } }),
|
||||
]));
|
||||
bus.on('problem/delete', (domainId, docId) => RecordModel.coll.updateMany({ domainId, pid: docId }, { $set: { pid: -1 } }));
|
||||
bus.on('domain/delete', (domainId) => RecordModel.coll.deleteMany({ domainId }));
|
||||
|
||||
bus.once('app/started', () => db.ensureIndexes(
|
||||
RecordModel.coll,
|
||||
|
||||
Reference in new issue
Block a user