mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-19 17:38:19 +08:00
core: add contest invitation code
This commit is contained in:
1 parent
f91f201f1e
commit
4e564f1ca8
7 files changed
+51
-14
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hydrooj",
|
"name": "hydrooj",
|
||||||
"version": "2.29.23",
|
"version": "2.29.24",
|
||||||
"bin": "bin/hydrooj.js",
|
"bin": "bin/hydrooj.js",
|
||||||
"main": "dist/loader.js",
|
"main": "dist/loader.js",
|
||||||
"typings": "dist/loader.d.ts",
|
"typings": "dist/loader.d.ts",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import AdmZip from 'adm-zip';
|
|||||||
import { Time } from '@hydrooj/utils/lib/utils';
|
import { Time } from '@hydrooj/utils/lib/utils';
|
||||||
import {
|
import {
|
||||||
ContestNotLiveError, ValidationError, ProblemNotFoundError,
|
ContestNotLiveError, ValidationError, ProblemNotFoundError,
|
||||||
ContestNotAttendedError, PermissionError, BadRequestError, ContestNotFoundError,
|
ContestNotAttendedError, PermissionError, BadRequestError, ContestNotFoundError, InvalidTokenError,
|
||||||
} from '../error';
|
} from '../error';
|
||||||
import { ProblemDoc, Tdoc, User } from '../interface';
|
import { ProblemDoc, Tdoc, User } from '../interface';
|
||||||
import paginate from '../lib/paginate';
|
import paginate from '../lib/paginate';
|
||||||
@@ -86,9 +86,11 @@ export class ContestDetailHandler extends Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@param('tid', Types.ObjectID)
|
@param('tid', Types.ObjectID)
|
||||||
async postAttend(domainId: string, tid: ObjectID) {
|
@param('code', Types.String, true)
|
||||||
|
async postAttend(domainId: string, tid: ObjectID, code = '') {
|
||||||
const tdoc = await contest.get(domainId, tid);
|
const tdoc = await contest.get(domainId, tid);
|
||||||
if (contest.isDone(tdoc)) throw new ContestNotLiveError(tid);
|
if (contest.isDone(tdoc)) throw new ContestNotLiveError(tid);
|
||||||
|
if (tdoc._code && code !== tdoc._code) throw new InvalidTokenError(code);
|
||||||
await contest.attend(domainId, tid, this.user._id);
|
await contest.attend(domainId, tid, this.user._id);
|
||||||
this.back();
|
this.back();
|
||||||
}
|
}
|
||||||
@@ -215,9 +217,10 @@ export class ContestEditHandler extends Handler {
|
|||||||
@param('rule', Types.Range(contest.RULES))
|
@param('rule', Types.Range(contest.RULES))
|
||||||
@param('pids', Types.Content)
|
@param('pids', Types.Content)
|
||||||
@param('rated', Types.Boolean)
|
@param('rated', Types.Boolean)
|
||||||
|
@param('code', Types.String, true)
|
||||||
async post(
|
async post(
|
||||||
domainId: string, tid: ObjectID, beginAtDate: string, beginAtTime: string, duration: number,
|
domainId: string, tid: ObjectID, beginAtDate: string, beginAtTime: string, duration: number,
|
||||||
title: string, content: string, rule: string, _pids: string, rated = false,
|
title: string, content: string, rule: string, _pids: string, rated = false, _code = '',
|
||||||
) {
|
) {
|
||||||
const pids = _pids.replace(/,/g, ',').split(',').map((i) => {
|
const pids = _pids.replace(/,/g, ',').split(',').map((i) => {
|
||||||
i = i.trim();
|
i = i.trim();
|
||||||
@@ -241,6 +244,7 @@ export class ContestEditHandler extends Handler {
|
|||||||
} else {
|
} else {
|
||||||
tid = await contest.add(domainId, title, content, this.user._id, rule, beginAt, endAt, pids, rated);
|
tid = await contest.add(domainId, title, content, this.user._id, rule, beginAt, endAt, pids, rated);
|
||||||
}
|
}
|
||||||
|
await contest.edit(domainId, tid, { _code });
|
||||||
this.response.body = { tid };
|
this.response.body = { tid };
|
||||||
this.response.redirect = this.url('contest_detail', { tid });
|
this.response.redirect = this.url('contest_detail', { tid });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -321,6 +321,7 @@ export interface Tdoc<docType = document['TYPE_CONTEST'] | document['TYPE_HOMEWO
|
|||||||
rule: string,
|
rule: string,
|
||||||
pids: ProblemId[],
|
pids: ProblemId[],
|
||||||
rated?: boolean,
|
rated?: boolean,
|
||||||
|
_code?: string,
|
||||||
|
|
||||||
// For homework
|
// For homework
|
||||||
penaltySince?: Date,
|
penaltySince?: Date,
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { AutoloadPage } from 'vj/misc/Page';
|
||||||
|
import delay from 'vj/utils/delay';
|
||||||
|
import i18n from 'vj/utils/i18n';
|
||||||
|
import request from 'vj/utils/request';
|
||||||
|
import Notification from 'vj/components/notification';
|
||||||
|
|
||||||
|
const contestPage = new AutoloadPage('contestPage', () => {
|
||||||
|
$('[data-contest-code]').on('click', (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
// eslint-disable-next-line no-alert
|
||||||
|
const code = prompt(i18n('Invitation code:'));
|
||||||
|
request.post('', {
|
||||||
|
operation: 'attend',
|
||||||
|
code,
|
||||||
|
}).then(() => {
|
||||||
|
Notification.success(i18n('Successfully attended'));
|
||||||
|
delay(1000).then(() => window.location.reload());
|
||||||
|
}).catch((e) => {
|
||||||
|
Notification.error(e.message || e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default contestPage;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/ui-default",
|
"name": "@hydrooj/ui-default",
|
||||||
"version": "4.12.19",
|
"version": "4.12.20",
|
||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "hydro.js",
|
"main": "hydro.js",
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<form method="post">
|
<form method="post">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{{ form.form_select({
|
{{ form.form_select({
|
||||||
columns:4,
|
columns:3,
|
||||||
label:'Rule',
|
label:'Rule',
|
||||||
name:'rule',
|
name:'rule',
|
||||||
options:rules,
|
options:rules,
|
||||||
@@ -23,8 +23,16 @@
|
|||||||
autofocus:true,
|
autofocus:true,
|
||||||
row:false
|
row:false
|
||||||
}) }}
|
}) }}
|
||||||
|
{{ form.form_text({
|
||||||
|
columns:3,
|
||||||
|
label:'Invitation code',
|
||||||
|
name:'code',
|
||||||
|
placeholder:_('(leave blank if none)'),
|
||||||
|
value:tdoc.code|default(''),
|
||||||
|
row:false
|
||||||
|
}) }}
|
||||||
{{ form.form_checkbox({
|
{{ form.form_checkbox({
|
||||||
columns:4,
|
columns:2,
|
||||||
label:'Rated',
|
label:'Rated',
|
||||||
name:'rated',
|
name:'rated',
|
||||||
placeholder:_('Is rated'),
|
placeholder:_('Is rated'),
|
||||||
|
|||||||
@@ -41,13 +41,13 @@
|
|||||||
{% if not attended and not model.contest.isDone(tdoc) %}
|
{% if not attended and not model.contest.isDone(tdoc) %}
|
||||||
<li class="menu__item">
|
<li class="menu__item">
|
||||||
{% if handler.user.hasPerm(perm.PERM_ATTEND_CONTEST) and handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
{% if handler.user.hasPerm(perm.PERM_ATTEND_CONTEST) and handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
||||||
<form action="{{ url('contest_detail', tid=tdoc.docId) }}" method="POST">
|
<form action="{{ url('contest_detail', tid=tdoc.docId) }}" method="POST">
|
||||||
<input type="hidden" name="operation" value="attend">
|
<input type="hidden" name="operation" value="attend">
|
||||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||||
<button class="menu__link" type="submit">
|
<button class="menu__link" {% if tdoc._code %}data-contest-code{% endif %} type="submit">
|
||||||
<span class="icon icon-add"></span> {{ _('Attend Contest') }}
|
<span class="icon icon-add"></span> {{ _('Attend Contest') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{% elif not handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
{% elif not handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
||||||
<a class="menu__link" href="javascript:showSignInDialog()">
|
<a class="menu__link" href="javascript:showSignInDialog()">
|
||||||
<span class="icon icon-add"></span> {{ _('Login to Attend Contest') }}
|
<span class="icon icon-add"></span> {{ _('Login to Attend Contest') }}
|
||||||
|
|||||||
Reference in new issue
Block a user