修复参加比赛的错误

This commit is contained in:
masnn committed 2020-04-12 12:29:49 +08:00
1 parent 32ad47e031
commit f40e4e2c74
10 files changed
+62 -38

No files matched your search

+7 -7
View File
@@ -92,6 +92,12 @@ class ContestNotAttendedError extends ForbiddenError {
this.params = [tid]; this.params = [tid];
} }
} }
class ContestAlreadyAttendedError extends ForbiddenError {
constructor(tid, uid) {
super('You\'ve already attended this contest.');
this.params = [tid, uid];
}
}
class ContestNotLiveError extends ForbiddenError { class ContestNotLiveError extends ForbiddenError {
constructor(tid) { constructor(tid) {
super('This contest is not live.'); super('This contest is not live.');
@@ -148,7 +154,7 @@ module.exports = {
ValidationError, ProblemNotFoundError, TrainingNotFoundError, ValidationError, ProblemNotFoundError, TrainingNotFoundError,
ContestNotFoundError, RecordNotFoundError, SolutionNotFoundError, ContestNotFoundError, RecordNotFoundError, SolutionNotFoundError,
AlreadyVotedError, ContestNotAttendedError, ContestNotLiveError, AlreadyVotedError, ContestNotAttendedError, ContestNotLiveError,
ContestScoreboardHiddenError ContestScoreboardHiddenError, ContestAlreadyAttendedError
}; };
/* /*
@@ -278,12 +284,6 @@ class InvalidJoinInvitationCodeError(ForbiddenError):
return 'The invitation code you provided is invalid.' return 'The invitation code you provided is invalid.'
class ContestAlreadyAttendedError(ForbiddenError):
@property
def message(self):
return "You've already attended this contest."
class HomeworkScoreboardHiddenError(ForbiddenError): class HomeworkScoreboardHiddenError(ForbiddenError):
@property @property
def message(self): def message(self):
+11 -10
View File
@@ -14,10 +14,10 @@ const
class ContestHandler extends Handler { class ContestHandler extends Handler {
canViewHiddenScoreboard() { canViewHiddenScoreboard() {
return this.hasPerm(PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD); return this.user.hasPerm(PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
} }
canShowRecord(tdoc, allowPermOverride = true) { canShowRecord(tdoc, allowPermOverride = true) {
if (contest.RULES[tdoc.rule].showRecordFunc(tdoc, new Date())) return true; if (contest.RULES[tdoc.rule].showRecord(tdoc, new Date())) return true;
if (allowPermOverride && this.canViewHiddenScoreboard(tdoc)) return true; if (allowPermOverride && this.canViewHiddenScoreboard(tdoc)) return true;
return false; return false;
} }
@@ -27,14 +27,15 @@ class ContestHandler extends Handler {
return false; return false;
} }
async getScoreboard(tid, isExport = false) { async getScoreboard(tid, isExport = false) {
let [tdoc, tsdocs] = await contest.getAndListStatus(tid); let tdoc = await contest.get(tid);
if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid); if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid);
let tsdocs = await contest.getMultiStatus(tid).sort(contest.RULES[tdoc.rule].statusSort).toArray();
let uids = []; let uids = [];
for (let tsdoc of tsdocs) uids.push(tsdoc.uid); for (let tsdoc of tsdocs) uids.push(tsdoc.uid);
let [udict, pdict] = await Promise.all([user.getList(uids), problem.getList(tdoc['pids'])]); let [udict, pdict] = await Promise.all([user.getList(uids), problem.getList(tdoc['pids'])]);
let ranked_tsdocs = contest.RULES[tdoc.rule].rank(tsdocs); let ranked_tsdocs = contest.RULES[tdoc.rule].rank(tsdocs);
let rows = contest.RULES[tdoc.rule].scoreboard(isExport, this.translate, tdoc, ranked_tsdocs, udict, pdict); let rows = contest.RULES[tdoc.rule].scoreboard(isExport, str => str ? str.toString().translate(this.user.language) : '', tdoc, ranked_tsdocs, udict, pdict);
return tdoc, rows, udict; return [tdoc, rows, udict];
} }
async verifyProblems(pids) { async verifyProblems(pids) {
console.log(pids); console.log(pids);
@@ -61,17 +62,17 @@ class ContestListHandler extends ContestHandler {
this.response.template = 'contest_main.html'; this.response.template = 'contest_main.html';
let tdocs, qs, tpcount; let tdocs, qs, tpcount;
if (!rule) { if (!rule) {
tdocs = contest.getMulti(); tdocs = contest.getMulti().sort({ beginAt: -1 });
qs = ''; qs = '';
} else { } else {
if (!contest.CONTEST_RULES.includes(rule)) throw new ValidationError('rule'); if (!contest.CONTEST_RULES.includes(rule)) throw new ValidationError('rule');
tdocs = contest.getMulti({ rule }); tdocs = contest.getMulti({ rule }).sort({ beginAt: -1 });
qs = 'rule={0}'.format(rule); qs = 'rule={0}'.format(rule);
} }
[tdocs, tpcount] = await paginate(tdocs, page, constants.CONTEST_PER_PAGE); [tdocs, tpcount] = await paginate(tdocs, page, constants.CONTEST_PER_PAGE);
let tids = []; let tids = [];
for (let tdoc of tdocs) tids.push(tdoc._id); for (let tdoc of tdocs) tids.push(tdoc._id);
let tsdict = await contest.getListStatus(this.uid, tids); let tsdict = await contest.getListStatus(this.user._id, tids);
this.response.body = { this.response.body = {
page, tpcount, qs, rule, tdocs, tsdict page, tpcount, qs, rule, tdocs, tsdict
}; };
@@ -84,7 +85,7 @@ class ContestDetailHandler extends ContestHandler {
async get({ page = 1 }) { async get({ page = 1 }) {
this.response.template = 'contest_detail.html'; this.response.template = 'contest_detail.html';
let [tsdoc, pdict] = await Promise.all([ let [tsdoc, pdict] = await Promise.all([
contest.getStatus(this.tdoc._id, this.uid), contest.getStatus(this.tdoc._id, this.user._id),
problem.getList(this.tdoc.pids) problem.getList(this.tdoc.pids)
]); ]);
let psdict = {}, rdict = {}, attended; let psdict = {}, rdict = {}, attended;
@@ -111,7 +112,7 @@ class ContestDetailHandler extends ContestHandler {
} }
async post_attend() { async post_attend() {
if (contest.is_done(this.tdoc)) throw new ContestNotLiveError(this.tdoc._id); if (contest.is_done(this.tdoc)) throw new ContestNotLiveError(this.tdoc._id);
await contest.attend(this.tdoc._id, this.uid); await contest.attend(this.tdoc._id, this.user._id);
this.back(); this.back();
} }
} }
+3
View File
@@ -11,6 +11,9 @@ exports.datetime_span = function (dt, relative = true, format = '%Y-%m-%d %H:%M:
dt.format(format) dt.format(format)
); );
}; };
exports.nl2br = function (self) {
return self.replace(/\n/gm, '<br>');
};
exports.paginate = function* (page, num_pages) { exports.paginate = function* (page, num_pages) {
let radius = 2, first, last; let radius = 2, first, last;
if (page > 1) { if (page > 1) {
+11
View File
@@ -0,0 +1,11 @@
module.exports = function* ranked(diter, equ = (a, b) => a == b) {
let last_doc = null;
let r = 0, count = 0;
for (let doc of diter) {
count++;
if (count == 1 || !equ(last_doc, doc))
r = count;
last_doc = doc;
yield [r, doc];
}
};
+4 -4
View File
@@ -1,6 +1,6 @@
const const
validator = require('../lib/validator'), validator = require('../lib/validator'),
{ ValidationError, ContestNotFoundError } = require('../error'), { ValidationError, ContestNotFoundError, ContestAlreadyAttendedError } = require('../error'),
db = require('../service/db.js'), db = require('../service/db.js'),
coll = db.collection('contest'), coll = db.collection('contest'),
coll_status = db.collection('contest.status'); coll_status = db.collection('contest.status');
@@ -56,7 +56,7 @@ async function getListStatus(uid, tids) {
} }
async function attend(tid, uid) { async function attend(tid, uid) {
try { try {
await coll_status.insertOne({ tid, uid }); await coll_status.insertOne({ tid, uid, attend: 1 });
} catch (e) { } catch (e) {
throw new ContestAlreadyAttendedError(tid, uid); throw new ContestAlreadyAttendedError(tid, uid);
} }
@@ -70,10 +70,10 @@ function is_new(tdoc, days = 1) {
function is_upcoming(tdoc, days = 1) { function is_upcoming(tdoc, days = 1) {
let now = new Date().getTime(); let now = new Date().getTime();
let readyAt = tdoc.beginAt.getTime(); let readyAt = tdoc.beginAt.getTime();
return (now > readyAt + days * 24 * 3600 * 1000 && now < tdoc.beginAt); return (now > readyAt - days * 24 * 3600 * 1000 && now < tdoc.beginAt);
} }
function is_not_started(tdoc) { function is_not_started(tdoc) {
return new Date() < tdoc.beginAt; return (new Date()) < tdoc.beginAt;
} }
function is_ongoing(tdoc) { function is_ongoing(tdoc) {
let now = new Date(); let now = new Date();
+2
View File
@@ -1,6 +1,8 @@
module.exports = { module.exports = {
TEXT: 'ACM/ICPC', TEXT: 'ACM/ICPC',
check: () => { }, check: () => { },
showScoreboard: () => true,
showRecord: (tdoc, now) => now > tdoc.endAt,
stat: (tdoc, journal) => { stat: (tdoc, journal) => {
let naccept = {}, effective = {}, detail = [], accept = 0, time = 0; let naccept = {}, effective = {}, detail = [], accept = 0, time = 0;
for (let j in journal) { for (let j in journal) {
+10 -4
View File
@@ -1,3 +1,5 @@
const ranked = require('../../lib/rank');
module.exports = { module.exports = {
TEXT: 'OI', TEXT: 'OI',
check: () => { }, check: () => { },
@@ -10,10 +12,13 @@ module.exports = {
} }
return { score, detail }; return { score, detail };
}, },
showScoreboard(tdoc,now){ showScoreboard(tdoc, now) {
return now>tdoc.endAt; return now > tdoc.endAt;
}, },
scoreboard(is_export, _, tdoc, ranked_tsdocs, udict, dudict, pdict) { showRecord(tdoc, now) {
return now > tdoc.endAt;
},
scoreboard(is_export, _, tdoc, ranked_tsdocs, udict, pdict) {
let columns = [ let columns = [
{ type: 'rank', value: _('Rank') }, { type: 'rank', value: _('Rank') },
{ type: 'user', value: _('User') }, { type: 'user', value: _('User') },
@@ -49,5 +54,6 @@ module.exports = {
rows.push(row); rows.push(row);
} }
return rows; return rows;
} },
rank: tdocs => ranked(tdocs, (a, b) => a.score == b.score)
}; };
+1 -1
View File
@@ -252,7 +252,7 @@ class ConnectionHandler {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
template.render(name, Object.assign(context, { template.render(name, Object.assign(context, {
handler: this, handler: this,
_: this.translate, _: str => str ? str.toString().translate(this.user.language) : '',
user: this.user user: this.user
}), (error, res) => { }), (error, res) => {
console.timeEnd(name); console.timeEnd(name);
+11 -10
View File
@@ -1,3 +1,4 @@
{% set page_name = "contest_scoreboard" %}
{% extends "layout/basic.html" %} {% extends "layout/basic.html" %}
{% block content %} {% block content %}
<div class="row"><div class="medium-12 columns"> <div class="row"><div class="medium-12 columns">
@@ -21,25 +22,25 @@
<tr> <tr>
{%- for column in rows[0] -%} {%- for column in rows[0] -%}
<th class="col--{{ column['type'] }}"> <th class="col--{{ column['type'] }}">
{% if column['type'] == 'problem_detail' %} {% if column.type == 'problem_detail' %}
<a href="{{ reverse_url('contest_detail_problem' if tdoc['doc_type'] == vj4.model.document.TYPE_CONTEST else 'homework_detail_problem', tid=tdoc['doc_id'], pid=column['raw']['doc_id']) }}" data-tooltip="{{ column['raw']['title'] }}">{{ column['value'] }}</a> <a href="{{ reverse_url('contest_detail_problem' , tid=tdoc._id, pid=column.raw._id) }}" data-tooltip="{{ column['raw']['title'] }}">{{ column['value'] }}</a>
{% else %} {% else %}
{{ column['value'] }} {{ column.value }}
{% endif %} {% endif %}
</th> </th>
{%- endfor -%} {%- endfor -%}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{%- for row in rows[1:] -%} {%- for i, row in rows -%}{% if i>0 %}
<tr> <tr>
{%- for column in row -%} {%- for column in row -%}
<td class="col--{{ rows[0][loop.index0]['type'] }}"> <td class="col--{{ rows[0][loop.index0]['type'] }}">
{% if column['type'] == 'user' %} {% if column.type == 'user' %}
{{ user.render_inline(column['raw'], badge=false) }} {{ user.render_inline(column.raw, badge=false) }}
{% elif column['type'] == 'record' %} {% elif column.type == 'record' %}
{% if column['raw'] %} {% if column.raw %}
<a href="{{ reverse_url('record_detail', rid=column['raw']) }}">{{ column['value']|nl2br }}</a> <a href="/r/{{ column.raw }}">{{ column.value|nl2br }}</a>
{% else %} {% else %}
{{ column['value']|nl2br }} {{ column['value']|nl2br }}
{% endif %} {% endif %}
@@ -49,7 +50,7 @@
</td> </td>
{%- endfor -%} {%- endfor -%}
</tr> </tr>
{%- endfor -%} {% endif %}{%- endfor -%}
</tbody> </tbody>
</table> </table>
</div> </div>
+2 -2
View File
@@ -43,11 +43,11 @@
{% endif %} {% endif %}
</li> </li>
{% endif %} {% endif %}
{% if handler.can_show_scoreboard(tdoc, False) %} {% if handler.canShowScoreboard(tdoc, False) %}
<li class="menu__item"><a class="menu__link" href="{{ reverse_url('homework_scoreboard', tid=tdoc['doc_id']) }}"> <li class="menu__item"><a class="menu__link" href="{{ reverse_url('homework_scoreboard', tid=tdoc['doc_id']) }}">
<span class="icon icon-statistics"></span> {{ _('Scoreboard') }} <span class="icon icon-statistics"></span> {{ _('Scoreboard') }}
</a></li> </a></li>
{% elif handler.can_view_hidden_scoreboard(tdoc) %} {% elif handler.canViewHiddenScoreboard(tdoc) %}
<li class="menu__item"><a class="menu__link" href="{{ reverse_url('homework_scoreboard', tid=tdoc['doc_id']) }}"> <li class="menu__item"><a class="menu__link" href="{{ reverse_url('homework_scoreboard', tid=tdoc['doc_id']) }}">
<span class="icon icon-statistics"></span> {{ _('Scoreboard (Hidden)') }} <span class="icon icon-statistics"></span> {{ _('Scoreboard (Hidden)') }}
</a></li> </a></li>