mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-19 17:38:19 +08:00
core: database structure update
This commit is contained in:
1 parent
dca8e28361
commit
4daf8bac80
24 files changed
+182
-263
No files matched your search
@@ -16,8 +16,6 @@ import * as document from './document';
|
||||
import problem from './problem';
|
||||
import user from './user';
|
||||
|
||||
type Type = 30 | 60;
|
||||
|
||||
interface AcmJournal {
|
||||
rid: ObjectID;
|
||||
pid: ProblemId;
|
||||
@@ -439,7 +437,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[] = [],
|
||||
rated = false, data: Partial<Tdoc> = {}, type: Type = document.TYPE_CONTEST,
|
||||
rated = false, data: Partial<Tdoc> = {},
|
||||
) {
|
||||
if (!this.RULES[rule]) throw new ValidationError('rule');
|
||||
if (beginAt >= endAt) throw new ValidationError('beginAt', 'endAt');
|
||||
@@ -448,101 +446,79 @@ export async function add(
|
||||
});
|
||||
this.RULES[rule].check(data);
|
||||
await bus.serial('contest/before-add', data);
|
||||
const res = await document.add(domainId, content, owner, type, null, null, null, {
|
||||
const res = await document.add(domainId, content, owner, document.TYPE_CONTEST, null, null, null, {
|
||||
...data, title, rule, beginAt, endAt, pids, attend: 0, rated,
|
||||
});
|
||||
await bus.serial('contest/add', data, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function edit(
|
||||
domainId: string, tid: ObjectID,
|
||||
$set: any, type: Type = document.TYPE_CONTEST,
|
||||
) {
|
||||
if ($set.rule) {
|
||||
if (!this.RULES[$set.rule]) throw new ValidationError('rule');
|
||||
}
|
||||
const tdoc = await document.get(domainId, type, tid);
|
||||
export async function edit(domainId: string, tid: ObjectID, $set: any) {
|
||||
if ($set.rule && !this.RULES[$set.rule]) throw new ValidationError('rule');
|
||||
const tdoc = await document.get(domainId, document.TYPE_CONTEST, tid);
|
||||
if (!tdoc) throw new ContestNotFoundError(domainId, tid);
|
||||
this.RULES[$set.rule || tdoc.rule].check(Object.assign(tdoc, $set));
|
||||
return await document.set(domainId, type, tid, $set);
|
||||
return await document.set(domainId, document.TYPE_CONTEST, tid, $set);
|
||||
}
|
||||
|
||||
export async function del(domainId: string, tid: ObjectID, type: Type = document.TYPE_CONTEST) {
|
||||
export async function del(domainId: string, tid: ObjectID) {
|
||||
await Promise.all([
|
||||
document.deleteOne(domainId, type, tid),
|
||||
document.deleteMultiStatus(domainId, type, { docId: tid }),
|
||||
document.deleteMulti(domainId, document.TYPE_DISCUSSION, { parentType: type, parentId: tid }),
|
||||
document.deleteOne(domainId, document.TYPE_CONTEST, tid),
|
||||
document.deleteMultiStatus(domainId, document.TYPE_CONTEST, { docId: tid }),
|
||||
document.deleteMulti(domainId, document.TYPE_DISCUSSION, { parentType: document.TYPE_CONTEST, parentId: tid }),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function get(domainId: string, tid: ObjectID, type: -1): Promise<Tdoc<30 | 60>>;
|
||||
export async function get<T extends Type>(domainId: string, tid: ObjectID, type: T): Promise<Tdoc<T>>;
|
||||
export async function get(domainId: string, tid: ObjectID): Promise<Tdoc<30>>;
|
||||
export async function get(domainId: string, tid: ObjectID, type: Type | -1 = document.TYPE_CONTEST) {
|
||||
let tdoc: Tdoc;
|
||||
if (type === -1) {
|
||||
tdoc = await document.get(domainId, document.TYPE_CONTEST, tid);
|
||||
if (!tdoc) tdoc = await document.get(domainId, document.TYPE_HOMEWORK, tid);
|
||||
} else tdoc = await document.get(domainId, type, tid);
|
||||
export async function get(domainId: string, tid: ObjectID): Promise<Tdoc<30>> {
|
||||
const tdoc = await document.get(domainId, document.TYPE_CONTEST, tid);
|
||||
if (!tdoc) throw new ContestNotFoundError(tid);
|
||||
return tdoc;
|
||||
}
|
||||
|
||||
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 getRelated(domainId: string, pid: ProblemId) {
|
||||
return await document.getMulti(domainId, document.TYPE_CONTEST, { pids: pid }).toArray();
|
||||
}
|
||||
|
||||
export function getStatus(
|
||||
domainId: string, tid: ObjectID, uid: number,
|
||||
type: 30 | 60 | -1 = document.TYPE_CONTEST,
|
||||
) {
|
||||
if (type === -1) type = { $in: [30, 60] } as any;
|
||||
return document.getStatus(domainId, type, tid, uid);
|
||||
export function getStatus(domainId: string, tid: ObjectID, uid: number) {
|
||||
return document.getStatus(domainId, document.TYPE_CONTEST, tid, uid);
|
||||
}
|
||||
|
||||
export async function updateStatus(
|
||||
domainId: string, tid: ObjectID, uid: number, rid: ObjectID, pid: ProblemId,
|
||||
status = STATUS.STATUS_WRONG_ANSWER, score = 0, type: 30 | 60 = document.TYPE_CONTEST,
|
||||
status = STATUS.STATUS_WRONG_ANSWER, score = 0,
|
||||
) {
|
||||
const [tdoc, otsdoc] = await Promise.all([
|
||||
get(domainId, tid, type),
|
||||
getStatus(domainId, tid, uid, type),
|
||||
get(domainId, tid),
|
||||
getStatus(domainId, tid, uid),
|
||||
]);
|
||||
if (!otsdoc.attend) throw new ContestNotAttendedError(tid, uid);
|
||||
const tsdoc = await document.revPushStatus(domainId, type, tid, uid, 'journal', {
|
||||
const tsdoc = await document.revPushStatus(domainId, document.TYPE_CONTEST, tid, uid, 'journal', {
|
||||
rid, pid, status, score,
|
||||
}, 'rid');
|
||||
const journal = _getStatusJournal(tsdoc);
|
||||
const stats = RULES[tdoc.rule].stat(tdoc, journal);
|
||||
return await document.revSetStatus(domainId, type, tid, uid, tsdoc.rev, { journal, ...stats });
|
||||
return await document.revSetStatus(domainId, document.TYPE_CONTEST, tid, uid, tsdoc.rev, { journal, ...stats });
|
||||
}
|
||||
|
||||
export async function getListStatus(
|
||||
domainId: string, uid: number, tids: ObjectID[],
|
||||
type: 30 | 60 = document.TYPE_CONTEST,
|
||||
) {
|
||||
export async function getListStatus(domainId: string, uid: number, tids: ObjectID[]) {
|
||||
const r = {};
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
for (const tid of tids) r[tid.toHexString()] = await getStatus(domainId, tid, uid, type);
|
||||
for (const tid of tids) r[tid.toHexString()] = await getStatus(domainId, tid, uid);
|
||||
return r;
|
||||
}
|
||||
|
||||
export async function attend(
|
||||
domainId: string, tid: ObjectID, uid: number,
|
||||
type: Type = document.TYPE_CONTEST,
|
||||
) {
|
||||
export async function attend(domainId: string, tid: ObjectID, uid: number) {
|
||||
try {
|
||||
await document.cappedIncStatus(domainId, type, tid, uid, 'attend', 1, 0, 1);
|
||||
await document.cappedIncStatus(domainId, document.TYPE_CONTEST, tid, uid, 'attend', 1, 0, 1);
|
||||
} catch (e) {
|
||||
throw new ContestAlreadyAttendedError(tid, uid);
|
||||
}
|
||||
await document.inc(domainId, type, tid, 'attend', 1);
|
||||
await document.inc(domainId, document.TYPE_CONTEST, tid, 'attend', 1);
|
||||
return {};
|
||||
}
|
||||
|
||||
export function getMultiStatus(domainId: string, query: any, docType: 30 | 60 = document.TYPE_CONTEST) {
|
||||
return document.getMultiStatus(domainId, docType, query);
|
||||
export function getMultiStatus(domainId: string, query: any) {
|
||||
return document.getMultiStatus(domainId, document.TYPE_CONTEST, query);
|
||||
}
|
||||
|
||||
export function isNew(tdoc: Tdoc, days = 1) {
|
||||
@@ -579,30 +555,28 @@ export function setStatus(domainId: string, tid: ObjectID, uid: number, $set: an
|
||||
return document.setStatus(domainId, document.TYPE_CONTEST, tid, uid, $set);
|
||||
}
|
||||
|
||||
export function count(domainId: string, query: any, type: Type = document.TYPE_CONTEST) {
|
||||
return document.count(domainId, type, query);
|
||||
export function count(domainId: string, query: any) {
|
||||
return document.count(domainId, document.TYPE_CONTEST, query);
|
||||
}
|
||||
|
||||
export function getMulti<K extends Type & keyof document.DocType>(
|
||||
domainId: string, query: FilterQuery<document.DocType[K]> = {}, type?: K,
|
||||
export function getMulti(
|
||||
domainId: string, query: FilterQuery<document.DocType['30']> = {},
|
||||
) {
|
||||
return document.getMulti(domainId, type || document.TYPE_CONTEST, query).sort({ beginAt: -1 });
|
||||
return document.getMulti(domainId, document.TYPE_CONTEST, query).sort({ beginAt: -1 });
|
||||
}
|
||||
|
||||
export async function getAndListStatus(
|
||||
domainId: string, tid: ObjectID, docType: Type | -1 = document.TYPE_CONTEST,
|
||||
): Promise<[Tdoc, any[]]> {
|
||||
export async function getAndListStatus(domainId: string, tid: ObjectID): Promise<[Tdoc, any[]]> {
|
||||
// TODO(iceboy): projection, pagination.
|
||||
const tdoc = await get(domainId, tid, docType as any);
|
||||
const tsdocs = await document.getMultiStatus(domainId, docType, { docId: tid })
|
||||
const tdoc = await get(domainId, tid);
|
||||
const tsdocs = await document.getMultiStatus(domainId, document.TYPE_CONTEST, { docId: tid })
|
||||
.sort(RULES[tdoc.rule].statusSort).toArray();
|
||||
return [tdoc, tsdocs];
|
||||
}
|
||||
|
||||
export async function recalcStatus(domainId: string, tid: ObjectID, type: Type = document.TYPE_CONTEST) {
|
||||
export async function recalcStatus(domainId: string, tid: ObjectID) {
|
||||
const [tdoc, tsdocs] = await Promise.all([
|
||||
document.get(domainId, type, tid),
|
||||
document.getMultiStatus(domainId, type, { docId: tid }).toArray(),
|
||||
document.get(domainId, document.TYPE_CONTEST, tid),
|
||||
document.getMultiStatus(domainId, document.TYPE_CONTEST, { docId: tid }).toArray(),
|
||||
]);
|
||||
const tasks = [];
|
||||
for (const tsdoc of tsdocs || []) {
|
||||
@@ -611,7 +585,7 @@ export async function recalcStatus(domainId: string, tid: ObjectID, type: Type =
|
||||
const stats = RULES[tdoc.rule].stat(tdoc, journal);
|
||||
tasks.push(
|
||||
document.revSetStatus(
|
||||
domainId, type, tid,
|
||||
domainId, document.TYPE_CONTEST, tid,
|
||||
tsdoc.uid, tsdoc.rev, { journal, ...stats },
|
||||
),
|
||||
);
|
||||
@@ -624,19 +598,19 @@ export function canViewHiddenScoreboard() {
|
||||
return this.user.hasPerm(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
|
||||
}
|
||||
|
||||
export function canShowRecord(tdoc: Tdoc<30 | 60>, allowPermOverride = true) {
|
||||
export function canShowRecord(tdoc: Tdoc<30>, allowPermOverride = true) {
|
||||
if (RULES[tdoc.rule].showRecord(tdoc, new Date())) return true;
|
||||
if (allowPermOverride && canViewHiddenScoreboard.call(this)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function canShowSelfRecord(tdoc: Tdoc<30 | 60>, allowPermOverride = true) {
|
||||
export function canShowSelfRecord(tdoc: Tdoc<30>, allowPermOverride = true) {
|
||||
if (RULES[tdoc.rule].showSelfRecord(tdoc, new Date())) return true;
|
||||
if (allowPermOverride && canViewHiddenScoreboard.call(this)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
export function canShowScoreboard(tdoc: Tdoc<30 | 60>, allowPermOverride = true) {
|
||||
export function canShowScoreboard(tdoc: Tdoc<30>, allowPermOverride = true) {
|
||||
if (RULES[tdoc.rule].showScoreboard(tdoc, new Date())) return true;
|
||||
if (allowPermOverride && canViewHiddenScoreboard.call(this)) return true;
|
||||
return false;
|
||||
@@ -644,11 +618,11 @@ export function canShowScoreboard(tdoc: Tdoc<30 | 60>, allowPermOverride = true)
|
||||
|
||||
export async function getScoreboard(
|
||||
domainId: string, tid: ObjectID,
|
||||
isExport = false, page: number, docType: 30 | 60 = document.TYPE_CONTEST,
|
||||
isExport = false, page: number,
|
||||
): Promise<[Tdoc<30 | 60>, ScoreboardRow[], Udict, ProblemDict, number]> {
|
||||
const tdoc = await get(domainId, tid, docType);
|
||||
const tdoc = await get(domainId, tid);
|
||||
if (!canShowScoreboard.call(this, tdoc)) throw new ContestScoreboardHiddenError(tid);
|
||||
const tsdocsCursor = getMultiStatus(domainId, { docId: tid }, docType).sort(RULES[tdoc.rule].statusSort);
|
||||
const tsdocsCursor = getMultiStatus(domainId, { docId: tid }).sort(RULES[tdoc.rule].statusSort);
|
||||
const pdict = await problem.getList(domainId, tdoc.pids, true);
|
||||
const [rows, udict, nPages] = await RULES[tdoc.rule].scoreboard(
|
||||
isExport, this.translate.bind(this),
|
||||
|
||||
@@ -252,7 +252,7 @@ export async function getVnode(domainId: string, type: number, id: string, uid?:
|
||||
if ([document.TYPE_CONTEST, document.TYPE_TRAINING, document.TYPE_HOMEWORK].includes(type as any)) {
|
||||
const model = type === document.TYPE_TRAINING ? training : contest;
|
||||
const _id = new ObjectID(id);
|
||||
const tdoc = await model.get(domainId, _id, type as any);
|
||||
const tdoc = await model.get(domainId, _id);
|
||||
if (!tdoc) throw new DiscussionNodeNotFoundError(id);
|
||||
if (uid) {
|
||||
const tsdoc = await model.getStatus(domainId, _id, uid);
|
||||
|
||||
@@ -35,7 +35,6 @@ export interface DocType {
|
||||
[TYPE_DISCUSSION_REPLY]: DiscussionReplyDoc,
|
||||
[TYPE_CONTEST]: Tdoc,
|
||||
[TYPE_TRAINING]: TrainingDoc,
|
||||
[TYPE_HOMEWORK]: Tdoc,
|
||||
}
|
||||
|
||||
export interface DocStatusType {
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
} from 'mongodb';
|
||||
import { ProblemNotFoundError } from '../error';
|
||||
import {
|
||||
ContestInfo, ExternalProblemId, FileInfo,
|
||||
ProblemConfigFile, RecordDoc,
|
||||
ExternalProblemId, FileInfo, ProblemConfigFile,
|
||||
RecordDoc,
|
||||
} from '../interface';
|
||||
import * as bus from '../service/bus';
|
||||
import db from '../service/db';
|
||||
@@ -23,9 +23,8 @@ class RecordModel {
|
||||
static coll: Collection<RecordDoc> = db.collection('record');
|
||||
static PROJECTION_LIST: (keyof RecordDoc)[] = [
|
||||
'_id', 'score', 'time', 'memory', 'lang',
|
||||
'uid', 'pid', 'rejudged', 'hidden', 'progress',
|
||||
'uid', 'pid', 'rejudged', 'progress', 'pdomain',
|
||||
'contest', 'judger', 'judgeAt', 'status', 'domainId',
|
||||
'pdomain',
|
||||
];
|
||||
|
||||
static async submissionPriority(uid: number, base: number = 0) {
|
||||
@@ -98,7 +97,7 @@ class RecordModel {
|
||||
|
||||
static async add(
|
||||
domainId: string, pid: ExternalProblemId | number, uid: number,
|
||||
lang: string, code: string, addTask: boolean, contestOrConfig?: ContestInfo | string,
|
||||
lang: string, code: string, addTask: boolean, contestOrConfig?: ObjectID | string,
|
||||
) {
|
||||
let pdomain = domainId;
|
||||
if (typeof pid === 'string') {
|
||||
@@ -117,7 +116,6 @@ class RecordModel {
|
||||
score: 0,
|
||||
time: 0,
|
||||
memory: 0,
|
||||
hidden: false,
|
||||
judgeTexts: [],
|
||||
compilerTexts: [],
|
||||
testCases: [],
|
||||
@@ -129,7 +127,7 @@ class RecordModel {
|
||||
if (typeof contestOrConfig === 'string') {
|
||||
// is Run
|
||||
data.input = contestOrConfig;
|
||||
data.hidden = true;
|
||||
data.contest = new ObjectID('000000000000000000000000');
|
||||
} else {
|
||||
data.contest = contestOrConfig;
|
||||
isContest = true;
|
||||
@@ -207,16 +205,14 @@ class RecordModel {
|
||||
}
|
||||
|
||||
static async getList(
|
||||
domainId: string, rids: ObjectID[], showHidden: boolean, fields?: (keyof RecordDoc)[],
|
||||
domainId: string, rids: ObjectID[], fields?: (keyof RecordDoc)[],
|
||||
): Promise<Record<string, Partial<RecordDoc>>> {
|
||||
const r: Record<string, RecordDoc> = {};
|
||||
rids = Array.from(new Set(rids));
|
||||
let cursor = RecordModel.coll.find({ domainId, _id: { $in: rids } });
|
||||
if (fields) cursor = cursor.project(buildProjection(fields));
|
||||
const rdocs = await cursor.toArray();
|
||||
for (const rdoc of rdocs) {
|
||||
if (!rdoc.hidden || showHidden) r[rdoc._id.toHexString()] = rdoc;
|
||||
}
|
||||
for (const rdoc of rdocs) r[rdoc._id.toHexString()] = rdoc;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
@@ -230,9 +226,9 @@ bus.on('domain/delete', (domainId) => Promise.all([
|
||||
|
||||
bus.once('app/started', () => db.ensureIndexes(
|
||||
RecordModel.coll,
|
||||
{ key: { domainId: 1, 'contest.tid': 1, hidden: 1, _id: -1 }, name: 'basic' },
|
||||
{ key: { domainId: 1, 'contest.tid': 1, hidden: 1, uid: 1, _id: -1 }, name: 'withUser' },
|
||||
{ key: { domainId: 1, 'contest.tid': 1, hidden: 1, pid: 1, _id: -1 }, name: 'withProblem' },
|
||||
{ key: { domainId: 1, contest: 1, _id: -1 }, name: 'basic' },
|
||||
{ key: { domainId: 1, contest: 1, uid: 1, _id: -1 }, name: 'withUser' },
|
||||
{ key: { domainId: 1, contest: 1, pid: 1, _id: -1 }, name: 'withProblem' },
|
||||
));
|
||||
|
||||
export default RecordModel;
|
||||
|
||||
@@ -103,6 +103,11 @@ async function cleanFiles() {
|
||||
}
|
||||
TaskModel.Worker.addHandler('storage.prune', cleanFiles);
|
||||
bus.once('app/started', async () => {
|
||||
if (process.env.NODE_APP_INSTANCE !== '0') return;
|
||||
await db.ensureIndexes(
|
||||
StorageModel.coll,
|
||||
{ key: { path: 1, autoDelete: 1 }, sparse: true, name: 'autoDelete' },
|
||||
);
|
||||
if (!await TaskModel.count({ type: 'schedule', subType: 'storage.prune' })) {
|
||||
await TaskModel.add({
|
||||
type: 'schedule',
|
||||
|
||||
@@ -137,6 +137,7 @@ class TaskModel {
|
||||
|
||||
const id = hostname();
|
||||
Worker.addHandler('task.daily', async () => {
|
||||
await global.Hydro.model.record.coll.deleteMany({ contest: new ObjectID('000000000000000000000000') });
|
||||
await global.Hydro.script.rp?.run({}, new Logger('task/rp').debug);
|
||||
await global.Hydro.script.problemStat?.run({}, new Logger('task/problem').debug);
|
||||
});
|
||||
|
||||
@@ -286,7 +286,7 @@ class UserModel {
|
||||
@ArgMethod
|
||||
static async ensureVuser(uname: string) {
|
||||
const [[min], current] = await Promise.all([
|
||||
collV.find({}).sort({ _id: -1 }).limit(1).toArray(),
|
||||
collV.find({}).sort({ _id: 1 }).limit(1).toArray(),
|
||||
collV.findOne({ unameLower: uname.toLowerCase() }),
|
||||
]);
|
||||
if (current) return current._id;
|
||||
|
||||
Reference in new issue
Block a user