mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core: contest: simplify
This commit is contained in:
1 parent
534fc4bb3b
commit
8024122e8f
9 files changed
+32
-62
No files matched your search
@@ -92,10 +92,6 @@ export class ContestDetailBaseHandler extends Handler {
|
|||||||
|
|
||||||
@param('tid', Types.ObjectID, true)
|
@param('tid', Types.ObjectID, true)
|
||||||
async __prepare(domainId: string, tid: ObjectID) {
|
async __prepare(domainId: string, tid: ObjectID) {
|
||||||
if (this.session.tid && this.session.tid !== tid) {
|
|
||||||
// TODO: you are already in a contest
|
|
||||||
throw new ForbiddenError();
|
|
||||||
}
|
|
||||||
if (!tid) return; // ProblemDetailHandler also extends from ContestDetailBaseHandler
|
if (!tid) return; // ProblemDetailHandler also extends from ContestDetailBaseHandler
|
||||||
[this.tdoc, this.tsdoc] = await Promise.all([
|
[this.tdoc, this.tsdoc] = await Promise.all([
|
||||||
contest.get(domainId, tid),
|
contest.get(domainId, tid),
|
||||||
@@ -122,7 +118,7 @@ export class ContestDetailBaseHandler extends Handler {
|
|||||||
name: 'contest_main',
|
name: 'contest_main',
|
||||||
args: {},
|
args: {},
|
||||||
displayName: 'Back to contest list',
|
displayName: 'Back to contest list',
|
||||||
checker: () => !(this.tdoc.access & contest.AccessControl.LOCK_OTHER),
|
checker: () => true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'contest_detail',
|
name: 'contest_detail',
|
||||||
@@ -161,10 +157,6 @@ export class ContestDetailHandler extends Handler {
|
|||||||
contest.getStatus(domainId, tid, this.user._id),
|
contest.getStatus(domainId, tid, this.user._id),
|
||||||
]);
|
]);
|
||||||
if (contest.RULES[this.tdoc.rule].hidden) throw new ContestNotFoundError(domainId, tid);
|
if (contest.RULES[this.tdoc.rule].hidden) throw new ContestNotFoundError(domainId, tid);
|
||||||
if (this.session.tid && this.session.tid !== tid) {
|
|
||||||
// TODO: you are already in a contest
|
|
||||||
throw new ForbiddenError();
|
|
||||||
}
|
|
||||||
if (this.tdoc.assign?.length && !this.user.own(this.tdoc)) {
|
if (this.tdoc.assign?.length && !this.user.own(this.tdoc)) {
|
||||||
const groups = await user.listGroup(domainId, this.user._id);
|
const groups = await user.listGroup(domainId, this.user._id);
|
||||||
if (!Set.intersection(this.tdoc.assign, groups.map((i) => i.name)).size) {
|
if (!Set.intersection(this.tdoc.assign, groups.map((i) => i.name)).size) {
|
||||||
@@ -193,7 +185,7 @@ export class ContestDetailHandler extends Handler {
|
|||||||
name: 'contest_main',
|
name: 'contest_main',
|
||||||
args: {},
|
args: {},
|
||||||
displayName: 'Back to contest list',
|
displayName: 'Back to contest list',
|
||||||
checker: () => !(this.tdoc.access & contest.AccessControl.LOCK_OTHER),
|
checker: () => true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'contest_detail',
|
name: 'contest_detail',
|
||||||
@@ -225,9 +217,11 @@ export class ContestDetailHandler extends Handler {
|
|||||||
async postAttend(domainId: string, tid: ObjectID, code = '') {
|
async postAttend(domainId: string, tid: ObjectID, code = '') {
|
||||||
if (contest.isDone(this.tdoc)) throw new ContestNotLiveError(tid);
|
if (contest.isDone(this.tdoc)) throw new ContestNotLiveError(tid);
|
||||||
if (this.tdoc._code && code !== this.tdoc._code && !this.tdoc.uninvite) throw new InvalidTokenError('Contest Invitation', code);
|
if (this.tdoc._code && code !== this.tdoc._code && !this.tdoc.uninvite) throw new InvalidTokenError('Contest Invitation', code);
|
||||||
await contest.attend(domainId, tid, this.user._id);
|
await contest.attend(
|
||||||
if (this.tdoc.unrankUser.includes(this.user._id)
|
domainId, tid, this.user._id,
|
||||||
|| (this.tdoc._code && code !== this.tdoc._code && this.tdoc.uninvite)) contest.setStatus(domainId, tid, this.user._id, { unrank: true });
|
(this.tdoc._code && code !== this.tdoc._code && this.tdoc.uninvite)
|
||||||
|
? { unrank: true } : {},
|
||||||
|
);
|
||||||
this.back();
|
this.back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,14 +258,14 @@ export class TrainingFilesHandler extends Handler {
|
|||||||
@param('tid', Types.ObjectID)
|
@param('tid', Types.ObjectID)
|
||||||
@post('filename', Types.Name, true)
|
@post('filename', Types.Name, true)
|
||||||
async postUploadFile(domainId: string, tid: ObjectID, filename: string) {
|
async postUploadFile(domainId: string, tid: ObjectID, filename: string) {
|
||||||
if ((this.tdoc.files?.length || 0) >= system.get('limit.training_files')) {
|
if ((this.tdoc.files?.length || 0) >= system.get('limit.contest_files')) {
|
||||||
throw new FileLimitExceededError('count');
|
throw new FileLimitExceededError('count');
|
||||||
}
|
}
|
||||||
const file = this.request.files?.file;
|
const file = this.request.files?.file;
|
||||||
if (!file) throw new ValidationError('file');
|
if (!file) throw new ValidationError('file');
|
||||||
const f = statSync(file.filepath);
|
const f = statSync(file.filepath);
|
||||||
const size = Math.sum((this.tdoc.files || []).map((i) => i.size)) + f.size;
|
const size = Math.sum((this.tdoc.files || []).map((i) => i.size)) + f.size;
|
||||||
if (size >= system.get('limit.training_files_size')) {
|
if (size >= system.get('limit.contest_files_size')) {
|
||||||
throw new FileLimitExceededError('size');
|
throw new FileLimitExceededError('size');
|
||||||
}
|
}
|
||||||
if (!filename) filename = file.originalFilename || String.random(16);
|
if (!filename) filename = file.originalFilename || String.random(16);
|
||||||
|
|||||||
@@ -366,9 +366,7 @@ export interface Tdoc<docType = document['TYPE_CONTEST'] | document['TYPE_TRAINI
|
|||||||
// For contest
|
// For contest
|
||||||
lockAt?: Date;
|
lockAt?: Date;
|
||||||
unlocked?: boolean;
|
unlocked?: boolean;
|
||||||
access?: number;
|
|
||||||
autoHide?: boolean;
|
autoHide?: boolean;
|
||||||
unrankUser?: number[];
|
|
||||||
uninvite?: boolean;
|
uninvite?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Cursor } from 'mongodb';
|
import { Cursor } from 'mongodb';
|
||||||
|
|
||||||
type ranked = <T>(cursor: Cursor<T>, equ: ((a: T, b: T) => boolean)) => Promise<[number, T][]>;
|
type ranked = <T extends Record<string, any>>(cursor: Cursor<T>, equ: ((a: T, b: T) => boolean)) => Promise<[number, T][]>;
|
||||||
|
|
||||||
const ranked: ranked = async (cursor, equ) => {
|
const ranked: ranked = async (cursor, equ) => {
|
||||||
let last = null;
|
let last = null;
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ const acm = buildContestRule({
|
|||||||
async scoreboardRow(isExport, _, tdoc, pdict, udoc, rank, tsdoc, meta) {
|
async scoreboardRow(isExport, _, tdoc, pdict, udoc, rank, tsdoc, meta) {
|
||||||
const tsddict = tsdoc.detail || {};
|
const tsddict = tsdoc.detail || {};
|
||||||
const row: ScoreboardRow = [
|
const row: ScoreboardRow = [
|
||||||
{ type: 'rank', value: rank ? rank.toString() : '*', raw: rank },
|
{ type: 'rank', value: rank.toString() },
|
||||||
{ type: 'user', value: udoc.uname, raw: tsdoc.uid },
|
{ type: 'user', value: udoc.uname, raw: tsdoc.uid },
|
||||||
];
|
];
|
||||||
if (isExport) {
|
if (isExport) {
|
||||||
@@ -263,7 +263,7 @@ const oi = buildContestRule({
|
|||||||
async scoreboardRow(isExport, _, tdoc, pdict, udoc, rank, tsdoc, meta) {
|
async scoreboardRow(isExport, _, tdoc, pdict, udoc, rank, tsdoc, meta) {
|
||||||
const tsddict = tsdoc.detail || {};
|
const tsddict = tsdoc.detail || {};
|
||||||
const row: ScoreboardNode[] = [
|
const row: ScoreboardNode[] = [
|
||||||
{ type: 'rank', value: rank ? rank.toString() : '*', raw: rank },
|
{ type: 'rank', value: rank.toString() },
|
||||||
{ type: 'user', value: udoc.uname, raw: tsdoc.uid },
|
{ type: 'user', value: udoc.uname, raw: tsdoc.uid },
|
||||||
];
|
];
|
||||||
if (isExport) {
|
if (isExport) {
|
||||||
@@ -510,13 +510,6 @@ export const RULES: ContestRules = {
|
|||||||
acm, oi, homework, ioi,
|
acm, oi, homework, ioi,
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum AccessControl {
|
|
||||||
FREE,
|
|
||||||
LOCK_OTHER = 2,
|
|
||||||
UNIQUE_LOGIN = 4,
|
|
||||||
CLIENT_REQUIRED = 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getStatusJournal(tsdoc) {
|
function _getStatusJournal(tsdoc) {
|
||||||
return tsdoc.journal.sort((a, b) => (a.rid.generationTime - b.rid.generationTime));
|
return tsdoc.journal.sort((a, b) => (a.rid.generationTime - b.rid.generationTime));
|
||||||
}
|
}
|
||||||
@@ -600,9 +593,9 @@ export async function getListStatus(domainId: string, uid: number, tids: ObjectI
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function attend(domainId: string, tid: ObjectID, uid: number) {
|
export async function attend(domainId: string, tid: ObjectID, uid: number, payload: any = {}) {
|
||||||
try {
|
try {
|
||||||
await document.cappedIncStatus(domainId, document.TYPE_CONTEST, tid, uid, 'attend', 1, 0, 1);
|
await document.cappedIncStatus(domainId, document.TYPE_CONTEST, tid, uid, 'attend', 1, 0, 1, payload);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new ContestAlreadyAttendedError(tid, uid);
|
throw new ContestAlreadyAttendedError(tid, uid);
|
||||||
}
|
}
|
||||||
@@ -753,7 +746,6 @@ export const statusText = (tdoc: Tdoc, tsdoc?: any) => (
|
|||||||
|
|
||||||
global.Hydro.model.contest = {
|
global.Hydro.model.contest = {
|
||||||
RULES,
|
RULES,
|
||||||
AccessControl,
|
|
||||||
add,
|
add,
|
||||||
getListStatus,
|
getListStatus,
|
||||||
getMultiStatus,
|
getMultiStatus,
|
||||||
|
|||||||
@@ -350,12 +350,15 @@ export async function setStatusIfNotCondition<T extends keyof DocStatusType>(
|
|||||||
export async function cappedIncStatus<T extends keyof DocStatusType>(
|
export async function cappedIncStatus<T extends keyof DocStatusType>(
|
||||||
domainId: string, docType: T, docId: DocStatusType[T]['docId'], uid: number,
|
domainId: string, docType: T, docId: DocStatusType[T]['docId'], uid: number,
|
||||||
key: NumberKeys<DocStatusType[T]>, value: number, minValue = -1, maxValue = 1,
|
key: NumberKeys<DocStatusType[T]>, value: number, minValue = -1, maxValue = 1,
|
||||||
|
setPayload: Partial<DocStatusType[T]> = {},
|
||||||
): Promise<DocStatusType[T]> {
|
): Promise<DocStatusType[T]> {
|
||||||
assert(value !== 0);
|
assert(value !== 0);
|
||||||
const $not = value > 0 ? { $gte: maxValue } : { $lte: minValue };
|
const $not = value > 0 ? { $gte: maxValue } : { $lte: minValue };
|
||||||
|
const operation = { $inc: { [key]: value } } as UpdateQuery<DocStatusType[T]>;
|
||||||
|
if (Object.keys(setPayload).length) operation.$set = setPayload;
|
||||||
const res = await collStatus.findOneAndUpdate(
|
const res = await collStatus.findOneAndUpdate(
|
||||||
{ domainId, docType, docId, uid, [key]: { $not } },
|
{ domainId, docType, docId, uid, [key]: { $not } },
|
||||||
{ $inc: { [key]: value } },
|
operation,
|
||||||
{ upsert: true, returnDocument: 'after' },
|
{ upsert: true, returnDocument: 'after' },
|
||||||
);
|
);
|
||||||
return res.value;
|
return res.value;
|
||||||
|
|||||||
@@ -213,10 +213,8 @@ SystemSetting(
|
|||||||
Setting('setting_limits', 'limit.problem_files_max_size', 256 * 1024 * 1024, 'number', 'limit.problem_files_max_size', 'Max files size per problem'),
|
Setting('setting_limits', 'limit.problem_files_max_size', 256 * 1024 * 1024, 'number', 'limit.problem_files_max_size', 'Max files size per problem'),
|
||||||
Setting('setting_limits', 'limit.user_files', 100, 'number', 'limit.user_files', 'Max files for user'),
|
Setting('setting_limits', 'limit.user_files', 100, 'number', 'limit.user_files', 'Max files for user'),
|
||||||
Setting('setting_limits', 'limit.user_files_size', 128 * 1024 * 1024, 'number', 'limit.user_files_size', 'Max total file size for user'),
|
Setting('setting_limits', 'limit.user_files_size', 128 * 1024 * 1024, 'number', 'limit.user_files_size', 'Max total file size for user'),
|
||||||
Setting('setting_limits', 'limit.contest_files', 100, 'number', 'limit.contest_files', 'Max files for contest'),
|
Setting('setting_limits', 'limit.contest_files', 100, 'number', 'limit.contest_files', 'Max files for contest or training'),
|
||||||
Setting('setting_limits', 'limit.contest_files_size', 128 * 1024 * 1024, 'number', 'limit.contest_files_size', 'Max total file size for contest'),
|
Setting('setting_limits', 'limit.contest_files_size', 128 * 1024 * 1024, 'number', 'limit.contest_files_size', 'Max total file size for contest or training'),
|
||||||
Setting('setting_limits', 'limit.training_files', 100, 'number', 'limit.training_files', 'Max files for training'),
|
|
||||||
Setting('setting_limits', 'limit.training_files_size', 128 * 1024 * 1024, 'number', 'limit.training_files_size', 'Max total file size for training'),
|
|
||||||
Setting('setting_limits', 'limit.submission', 60, 'number', 'limit.submission', 'Max submission count per minute'),
|
Setting('setting_limits', 'limit.submission', 60, 'number', 'limit.submission', 'Max submission count per minute'),
|
||||||
Setting('setting_limits', 'limit.submission_user', 15, 'number', 'limit.submission_user', 'Max submission count per user per minute'),
|
Setting('setting_limits', 'limit.submission_user', 15, 'number', 'limit.submission_user', 'Max submission count per user per minute'),
|
||||||
Setting('setting_limits', 'limit.pretest', 60, 'number', 'limit.pretest', 'Max pretest count per minute'),
|
Setting('setting_limits', 'limit.pretest', 60, 'number', 'limit.pretest', 'Max pretest count per minute'),
|
||||||
|
|||||||
@@ -3,35 +3,23 @@ import { NamedPage } from 'vj/misc/Page';
|
|||||||
|
|
||||||
const page = new NamedPage('contest_scoreboard', () => {
|
const page = new NamedPage('contest_scoreboard', () => {
|
||||||
const { tdoc } = UiContext;
|
const { tdoc } = UiContext;
|
||||||
let starData = JSON.parse(localStorage.getItem('Hydro.starData')) || {};
|
const key = `scoreboard-star/${tdoc.domainId}/${tdoc.docId}`;
|
||||||
Object.keys(starData).forEach((key) => {
|
const read = () => JSON.parse(localStorage.getItem(key) || '[]');
|
||||||
if (starData[key].expire < Date.now()) delete starData[key];
|
const write = (data) => localStorage.setItem(key, JSON.stringify(data));
|
||||||
});
|
|
||||||
if (!starData[tdoc.docId]) {
|
read().forEach((uid) => $(`.star.user--${uid}`).addClass('activated'));
|
||||||
starData[tdoc.docId] = {
|
|
||||||
expire: tdoc.endAt + 1000 * 60 * 60 * 24 * 7,
|
|
||||||
starUids: [],
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
starData[tdoc.docId].starUids.forEach((uid) => {
|
|
||||||
console.log(uid);
|
|
||||||
$(`.star.user--${uid}`).addClass('activated');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
console.log(starData[tdoc.docId].starUids);
|
|
||||||
localStorage.setItem('Hydro.starData', JSON.stringify(starData));
|
|
||||||
$('.star').on('click', (e) => {
|
$('.star').on('click', (e) => {
|
||||||
starData = JSON.parse(localStorage.getItem('Hydro.starData'));
|
const star = read();
|
||||||
const $target = $(e.currentTarget);
|
const $target = $(e.currentTarget);
|
||||||
const uid = $target.data('uid');
|
const uid = $target.data('uid');
|
||||||
if (starData[tdoc.docId].starUids.includes(uid)) {
|
if (star.includes(uid)) {
|
||||||
$target.removeClass('activated');
|
$target.removeClass('activated');
|
||||||
starData[tdoc.docId].starUids.splice(starData[tdoc.docId].starUids.indexOf(uid), 1);
|
star.splice(star.indexOf(uid), 1);
|
||||||
} else {
|
} else {
|
||||||
$target.addClass('activated');
|
$target.addClass('activated');
|
||||||
starData[tdoc.docId].starUids.push(uid);
|
star.push(uid);
|
||||||
}
|
}
|
||||||
localStorage.setItem('Hydro.starData', JSON.stringify(starData));
|
write(star);
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.select.filter').on('change', (e) => {
|
$('.select.filter').on('change', (e) => {
|
||||||
@@ -40,9 +28,7 @@ const page = new NamedPage('contest_scoreboard', () => {
|
|||||||
$('.data-table tbody tr').show();
|
$('.data-table tbody tr').show();
|
||||||
} else if (val === 'star') {
|
} else if (val === 'star') {
|
||||||
$('.data-table tbody tr').hide();
|
$('.data-table tbody tr').hide();
|
||||||
starData[tdoc.docId].starUids.forEach((uid) => {
|
read().forEach((uid) => $(`.star.user--${uid}`).closest('tr').show());
|
||||||
$(`.star.user--${uid}`).closest('tr').show();
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
$('.data-table tbody tr').hide();
|
$('.data-table tbody tr').hide();
|
||||||
$('.rank--unrank').closest('tr').show();
|
$('.rank--unrank').closest('tr').show();
|
||||||
|
|||||||
@@ -35,7 +35,6 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h1 style="text-align:center" class="section__title">{{ tdoc.title }}</h1>
|
|
||||||
{% if model.contest.isLocked(tdoc) %}
|
{% if model.contest.isLocked(tdoc) %}
|
||||||
<div class="section__body no-padding">
|
<div class="section__body no-padding">
|
||||||
<blockquote class="note">
|
<blockquote class="note">
|
||||||
@@ -78,7 +77,7 @@
|
|||||||
{%- for column in row -%}
|
{%- for column in row -%}
|
||||||
<td class="col--{{ rows[0][loop.index0]['type'] }}"{% if column.style %} style="{{ column.style }}"{% endif %}>
|
<td class="col--{{ rows[0][loop.index0]['type'] }}"{% if column.style %} style="{{ column.style }}"{% endif %}>
|
||||||
{%- if column.type == 'rank' -%}
|
{%- if column.type == 'rank' -%}
|
||||||
<span class="{{ 'rank--normal' if column.raw else 'rank--unrank' }}">{{ column.value|string|nl2br|safe }}</span>
|
<span class="{{ 'rank--unrank' if column.value == '0' else 'rank--normal' }}">{{ '*' if column.value == '0' else column.value|string|nl2br|safe }}</span>
|
||||||
{%- elif column.type == 'user' -%}
|
{%- elif column.type == 'user' -%}
|
||||||
{%- set canView = canViewAll or handler.user._id == column.raw -%}
|
{%- set canView = canViewAll or handler.user._id == column.raw -%}
|
||||||
<button class="star user--{{ column.raw }}" data-uid="{{ column.raw }}">
|
<button class="star user--{{ column.raw }}" data-uid="{{ column.raw }}">
|
||||||
|
|||||||
Reference in new issue
Block a user