mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core: !EXPERIMENTAL! 支持跨域引用题目
This commit is contained in:
1 parent
33d7c70ef6
commit
d897391e9a
25 files changed
+434
-392
No files matched your search
@@ -8,7 +8,7 @@ import {
|
||||
ContestNotAttendedError, ContestScoreboardHiddenError,
|
||||
} from '../error';
|
||||
import {
|
||||
ContestRule, ContestRules, ProblemDict,
|
||||
ProblemId, ContestRule, ContestRules, ProblemDict,
|
||||
ScoreboardNode, ScoreboardRow, Tdoc,
|
||||
Udict,
|
||||
} from '../interface';
|
||||
@@ -178,14 +178,20 @@ const oi: ContestRule = {
|
||||
const psdict = {};
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
if (isDone(tdoc)) {
|
||||
const psdocs = await problem.getMultiStatus(tdoc.domainId, {
|
||||
$or: rankedTsdocs.map(([, i]) => ({
|
||||
uid: i.uid,
|
||||
docId: { $in: tdoc.pids },
|
||||
})),
|
||||
}).toArray();
|
||||
for (const psdoc of psdocs) {
|
||||
psdict[`${psdoc.uid}/${psdoc.docId}`] = psdoc;
|
||||
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, { uid: { $in: uids } }).toArray();
|
||||
}),
|
||||
);
|
||||
for (const tpsdoc of psdocs) {
|
||||
for (const psdoc of tpsdoc) {
|
||||
psdict[`${psdoc.uid}/${psdoc.docId}`] = psdoc;
|
||||
}
|
||||
}
|
||||
}
|
||||
const rows = [columns];
|
||||
@@ -397,7 +403,7 @@ function _getStatusJournal(tsdoc) {
|
||||
|
||||
export function add(
|
||||
domainId: string, title: string, content: string, owner: number,
|
||||
rule: string, beginAt = new Date(), endAt = new Date(), pids = [],
|
||||
rule: string, beginAt = new Date(), endAt = new Date(), pids: ProblemId[] = [],
|
||||
rated = false, data: Partial<Tdoc> = {}, type: Type = document.TYPE_CONTEST,
|
||||
) {
|
||||
if (!this.RULES[rule]) throw new ValidationError('rule');
|
||||
@@ -444,12 +450,12 @@ export async function get(domainId: string, tid: ObjectID, type: Type | -1 = doc
|
||||
return tdoc;
|
||||
}
|
||||
|
||||
export async function getRelated(domainId: string, pid: number, type: Type = document.TYPE_CONTEST) {
|
||||
export async function getRelated(domainId: string, pid: ProblemId, type: Type = document.TYPE_CONTEST) {
|
||||
return await document.getMulti(domainId, type, { pids: pid }).toArray();
|
||||
}
|
||||
|
||||
export async function updateStatus(
|
||||
domainId: string, tid: ObjectID, uid: number, rid: ObjectID, pid: number,
|
||||
domainId: string, tid: ObjectID, uid: number, rid: ObjectID, pid: ProblemId,
|
||||
accept = false, score = 0, type: 30 | 60 = document.TYPE_CONTEST,
|
||||
) {
|
||||
const tdoc = await get(domainId, tid, type);
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import { ObjectID, FilterQuery } from 'mongodb';
|
||||
import { Dictionary, escapeRegExp, pick } from 'lodash';
|
||||
import {
|
||||
Dictionary, escapeRegExp, flatten, groupBy, pick,
|
||||
} from 'lodash';
|
||||
import { streamToBuffer } from '@hydrooj/utils/lib/utils';
|
||||
import type { Readable } from 'stream';
|
||||
import * as document from './document';
|
||||
import { STATUS } from './builtin';
|
||||
import domain from './domain';
|
||||
import storage from './storage';
|
||||
import { buildProjection } from '../utils';
|
||||
import type { ProblemStatusDoc, ProblemDict, Document } from '../interface';
|
||||
import type {
|
||||
ProblemStatusDoc, ProblemDict, Document, ProblemId,
|
||||
} from '../interface';
|
||||
import {
|
||||
ArrayKeys, MaybeArray, NumberKeys, Projection,
|
||||
} from '../typeutils';
|
||||
@@ -16,9 +21,6 @@ import { parseConfig } from '../lib/testdataConfig';
|
||||
|
||||
export interface ProblemDoc extends Document { }
|
||||
export type Field = keyof ProblemDoc;
|
||||
const fields: Field[] = [];
|
||||
export type Getter = (docId?: number, pid?: string) => Partial<ProblemDoc>;
|
||||
const getters: Getter[] = [];
|
||||
|
||||
export class ProblemModel {
|
||||
static PROJECTION_LIST: Field[] = [
|
||||
@@ -32,18 +34,47 @@ export class ProblemModel {
|
||||
'content', 'html', 'data', 'config', 'additional_file',
|
||||
];
|
||||
|
||||
static extend(getter: Getter) {
|
||||
getters.push(getter);
|
||||
fields.push(...Object.keys(getter(0, '0')) as any);
|
||||
}
|
||||
static default = {
|
||||
_id: new ObjectID(),
|
||||
domainId: 'system',
|
||||
docType: document.TYPE_PROBLEM,
|
||||
docId: 0,
|
||||
pid: null,
|
||||
owner: 1,
|
||||
title: '*',
|
||||
content: '',
|
||||
html: false,
|
||||
nSubmit: 0,
|
||||
nAccept: 0,
|
||||
tag: [],
|
||||
data: [],
|
||||
additional_file: [],
|
||||
stats: {},
|
||||
hidden: true,
|
||||
config: '',
|
||||
difficulty: 0,
|
||||
};
|
||||
|
||||
static create(docId?: number, pid?: string) {
|
||||
const result = {} as ProblemDoc;
|
||||
for (const getter of getters) {
|
||||
Object.assign(result, getter(docId, pid));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
static deleted = {
|
||||
_id: new ObjectID(),
|
||||
domainId: 'system',
|
||||
docType: document.TYPE_PROBLEM,
|
||||
docId: -1,
|
||||
pid: null,
|
||||
owner: 1,
|
||||
title: '*',
|
||||
content: 'Deleted Problem',
|
||||
html: false,
|
||||
nSubmit: 0,
|
||||
nAccept: 0,
|
||||
tag: [],
|
||||
data: [],
|
||||
additional_file: [],
|
||||
stats: {},
|
||||
hidden: true,
|
||||
config: '',
|
||||
difficulty: 0,
|
||||
};
|
||||
|
||||
static async add(
|
||||
domainId: string, pid: string = null, title: string, content: string, owner: number,
|
||||
@@ -80,6 +111,12 @@ 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];
|
||||
@@ -121,7 +158,12 @@ export class ProblemModel {
|
||||
return document.deleteSub(domainId, document.TYPE_PROBLEM, pid, key, values);
|
||||
}
|
||||
|
||||
static inc(domainId: string, _id: number, field: NumberKeys<ProblemDoc> | string, n: number): Promise<ProblemDoc> {
|
||||
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];
|
||||
}
|
||||
return document.inc(domainId, document.TYPE_PROBLEM, _id, field as any, n);
|
||||
}
|
||||
|
||||
@@ -191,16 +233,43 @@ export class ProblemModel {
|
||||
}
|
||||
|
||||
static async getList(
|
||||
domainId: string, pids: Array<number | string>,
|
||||
domainId: string, pids: ProblemId[],
|
||||
getHidden = false, doThrow = true, projection = ProblemModel.PROJECTION_PUBLIC,
|
||||
): Promise<ProblemDict> {
|
||||
pids = Array.from(new Set(pids)).map((i) => (+i ? +i : i));
|
||||
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 = {};
|
||||
const l = {};
|
||||
const q: any = { $or: [{ docId: { $in: pids } }, { pid: { $in: pids } }] };
|
||||
if (!getHidden) q.hidden = false;
|
||||
const pdocs = await document.getMulti(domainId, document.TYPE_PROBLEM, q)
|
||||
.project(buildProjection(projection)).toArray();
|
||||
const ddocs = await Promise.all(Object.keys(parsed).map((i) => domain.get(i)));
|
||||
const f = ddocs.filter((i) => !(
|
||||
i.share === '*'
|
||||
|| (`,${i.share.replace(/,/g, ',').split(',').map((i) => i.trim()).join(',')},`).includes(`,${domainId},`)
|
||||
));
|
||||
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 };
|
||||
}
|
||||
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 }] };
|
||||
if (!getHidden) q.hidden = false;
|
||||
console.log(task, parsed[task]);
|
||||
tasks.push(document.getMulti(task, document.TYPE_PROBLEM, q).project(buildProjection(projection)).toArray());
|
||||
}
|
||||
const pdocs = flatten(await Promise.all(tasks));
|
||||
for (const pdoc of pdocs) {
|
||||
try {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
@@ -208,15 +277,20 @@ export class ProblemModel {
|
||||
} catch (e) {
|
||||
pdoc.config = `Cannot parse: ${e.message}`;
|
||||
}
|
||||
r[pdoc.docId] = pdoc;
|
||||
l[pdoc.pid] = pdoc;
|
||||
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;
|
||||
}
|
||||
}
|
||||
// TODO enhance
|
||||
if (pdocs.length !== pids.length) {
|
||||
for (const pid of pids) {
|
||||
if (!(r[pid] || l[pid])) {
|
||||
if (doThrow) throw new ProblemNotFoundError(domainId, pid);
|
||||
else r[pid] = ProblemModel.create(undefined, pid.toString());
|
||||
else r[pid] = { ...ProblemModel.default, domainId, pid };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,26 +347,5 @@ bus.on('problem/delTestdata', async (domainId, docId, names) => {
|
||||
await ProblemModel.edit(domainId, docId, { config: '' });
|
||||
});
|
||||
|
||||
ProblemModel.extend((docId, pid) => ({
|
||||
_id: new ObjectID(),
|
||||
domainId: 'system',
|
||||
docType: document.TYPE_PROBLEM,
|
||||
docId: docId || -1,
|
||||
pid: pid || docId.toString(),
|
||||
owner: 1,
|
||||
title: '*',
|
||||
content: '',
|
||||
html: false,
|
||||
nSubmit: 0,
|
||||
nAccept: 0,
|
||||
tag: [],
|
||||
data: [],
|
||||
additional_file: [],
|
||||
stats: {},
|
||||
hidden: true,
|
||||
config: '',
|
||||
difficulty: 0,
|
||||
}));
|
||||
|
||||
global.Hydro.model.problem = ProblemModel;
|
||||
export default ProblemModel;
|
||||
@@ -8,7 +8,9 @@ import moment from 'moment';
|
||||
import { STATUS } from './builtin';
|
||||
import task from './task';
|
||||
import problem from './problem';
|
||||
import { RecordDoc, ContestInfo, ProblemConfigFile } from '../interface';
|
||||
import {
|
||||
RecordDoc, ContestInfo, ProblemConfigFile, ExternalProblemId,
|
||||
} from '../interface';
|
||||
import { ArgMethod, buildProjection, Time } from '../utils';
|
||||
import { MaybeArray } from '../typeutils';
|
||||
import * as bus from '../service/bus';
|
||||
@@ -29,9 +31,13 @@ class RecordModel {
|
||||
return base - ((pending + 0.5) * (sum(timeRecent.map((i) => i.time || 0)) / 10000));
|
||||
}
|
||||
|
||||
static async get(domainId: string, _id: ObjectID): Promise<RecordDoc | null> {
|
||||
static async get(_id: ObjectID): Promise<RecordDoc | null>
|
||||
static async get(domainId: string, _id: ObjectID): Promise<RecordDoc | null>
|
||||
static async get(arg0, arg1?) {
|
||||
const _id = arg1 || arg0;
|
||||
const domainId = arg1 ? arg0 : null;
|
||||
const res = await RecordModel.coll.findOne({ _id });
|
||||
if (res && res.domainId === domainId) return res;
|
||||
if (res.domainId === (domainId || res.domainId)) return res;
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -55,7 +61,7 @@ class RecordModel {
|
||||
const rdoc = await RecordModel.get(domainId, rid);
|
||||
let data = [];
|
||||
if (rdoc.pid) {
|
||||
const pdoc = await problem.get(domainId, rdoc.pid);
|
||||
const pdoc = await problem.get(rdoc.pdomain, rdoc.pid);
|
||||
data = pdoc.data;
|
||||
}
|
||||
delete rdoc._id;
|
||||
@@ -71,15 +77,21 @@ class RecordModel {
|
||||
}
|
||||
|
||||
static async add(
|
||||
domainId: string, pid: number, uid: number,
|
||||
domainId: string, pid: ExternalProblemId | number, uid: number,
|
||||
lang: string, code: string, addTask: boolean, contestOrConfig?: ContestInfo | string,
|
||||
) {
|
||||
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,
|
||||
@@ -189,16 +201,20 @@ class RecordModel {
|
||||
}
|
||||
|
||||
@ArgMethod
|
||||
static getUserInProblemMulti(
|
||||
domainId: string, uid: number, pid: number,
|
||||
getHidden: boolean = false,
|
||||
) {
|
||||
if (!getHidden) {
|
||||
static getUserInProblemMulti(domainId: string, uid: number, pid: ExternalProblemId | number, contestInfo?: ContestInfo) {
|
||||
let pdomain = domainId;
|
||||
if (typeof pid === 'string') {
|
||||
pdomain = pid.split(':')[0];
|
||||
pid = +pid.split(':')[1];
|
||||
}
|
||||
if (!contestInfo) {
|
||||
return RecordModel.coll.find({
|
||||
domainId, uid, pid, hidden: false,
|
||||
domainId, uid, pdomain, pid, contest: { $exists: false },
|
||||
});
|
||||
}
|
||||
return RecordModel.coll.find({ domainId, uid, pid });
|
||||
return RecordModel.coll.find({
|
||||
domainId, uid, pdomain, pid, contest: contestInfo,
|
||||
});
|
||||
}
|
||||
|
||||
@ArgMethod
|
||||
@@ -207,8 +223,12 @@ class RecordModel {
|
||||
}
|
||||
}
|
||||
|
||||
bus.on('problem/delete', (domainId, docId) => RecordModel.coll.deleteMany({ domainId, pid: docId }));
|
||||
bus.on('domain/delete', (domainId) => RecordModel.coll.deleteMany({ domainId }));
|
||||
// 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 } }),
|
||||
]));
|
||||
|
||||
export default RecordModel;
|
||||
global.Hydro.model.record = RecordModel;
|
||||
@@ -138,6 +138,7 @@ AccountSetting(
|
||||
DomainSetting(
|
||||
Setting('setting_domain', 'name', 'New domain', 'text', 'name'),
|
||||
Setting('setting_domain', 'avatar', '', 'text', 'avatar', 'Will be used as the domain icon.'),
|
||||
Setting('setting_domain', 'share', '', 'text', 'Share problem with domain (* for any)'),
|
||||
Setting('setting_domain', 'bulletin', '', 'markdown', 'Bulletin'),
|
||||
Setting('setting_storage', 'host', '', 'text', 'Custom host', null, FLAG_HIDDEN | FLAG_DISABLED),
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user