diff --git a/hydro/error.js b/hydro/error.js
index d6dbc07a1..123743b79 100644
--- a/hydro/error.js
+++ b/hydro/error.js
@@ -92,6 +92,12 @@ class ContestNotAttendedError extends ForbiddenError {
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 {
constructor(tid) {
super('This contest is not live.');
@@ -148,7 +154,7 @@ module.exports = {
ValidationError, ProblemNotFoundError, TrainingNotFoundError,
ContestNotFoundError, RecordNotFoundError, SolutionNotFoundError,
AlreadyVotedError, ContestNotAttendedError, ContestNotLiveError,
- ContestScoreboardHiddenError
+ ContestScoreboardHiddenError, ContestAlreadyAttendedError
};
/*
@@ -278,12 +284,6 @@ class InvalidJoinInvitationCodeError(ForbiddenError):
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):
@property
def message(self):
diff --git a/hydro/handler/contest.js b/hydro/handler/contest.js
index 5e65c5369..d26d4bf6c 100644
--- a/hydro/handler/contest.js
+++ b/hydro/handler/contest.js
@@ -14,10 +14,10 @@ const
class ContestHandler extends Handler {
canViewHiddenScoreboard() {
- return this.hasPerm(PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
+ return this.user.hasPerm(PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD);
}
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;
return false;
}
@@ -27,14 +27,15 @@ class ContestHandler extends Handler {
return 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);
+ let tsdocs = await contest.getMultiStatus(tid).sort(contest.RULES[tdoc.rule].statusSort).toArray();
let uids = [];
for (let tsdoc of tsdocs) uids.push(tsdoc.uid);
let [udict, pdict] = await Promise.all([user.getList(uids), problem.getList(tdoc['pids'])]);
let ranked_tsdocs = contest.RULES[tdoc.rule].rank(tsdocs);
- let rows = contest.RULES[tdoc.rule].scoreboard(isExport, this.translate, tdoc, ranked_tsdocs, udict, pdict);
- return tdoc, rows, udict;
+ 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];
}
async verifyProblems(pids) {
console.log(pids);
@@ -61,17 +62,17 @@ class ContestListHandler extends ContestHandler {
this.response.template = 'contest_main.html';
let tdocs, qs, tpcount;
if (!rule) {
- tdocs = contest.getMulti();
+ tdocs = contest.getMulti().sort({ beginAt: -1 });
qs = '';
} else {
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);
}
[tdocs, tpcount] = await paginate(tdocs, page, constants.CONTEST_PER_PAGE);
let tids = [];
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 = {
page, tpcount, qs, rule, tdocs, tsdict
};
@@ -84,7 +85,7 @@ class ContestDetailHandler extends ContestHandler {
async get({ page = 1 }) {
this.response.template = 'contest_detail.html';
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)
]);
let psdict = {}, rdict = {}, attended;
@@ -111,7 +112,7 @@ class ContestDetailHandler extends ContestHandler {
}
async post_attend() {
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();
}
}
diff --git a/hydro/lib/misc.js b/hydro/lib/misc.js
index d07a4bddc..f27e671ce 100644
--- a/hydro/lib/misc.js
+++ b/hydro/lib/misc.js
@@ -11,6 +11,9 @@ exports.datetime_span = function (dt, relative = true, format = '%Y-%m-%d %H:%M:
dt.format(format)
);
};
+exports.nl2br = function (self) {
+ return self.replace(/\n/gm, '
');
+};
exports.paginate = function* (page, num_pages) {
let radius = 2, first, last;
if (page > 1) {
diff --git a/hydro/lib/rank.js b/hydro/lib/rank.js
new file mode 100644
index 000000000..b415599dd
--- /dev/null
+++ b/hydro/lib/rank.js
@@ -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];
+ }
+};
diff --git a/hydro/model/contest.js b/hydro/model/contest.js
index 4a2a7e5d9..4ea3b9279 100644
--- a/hydro/model/contest.js
+++ b/hydro/model/contest.js
@@ -1,6 +1,6 @@
const
validator = require('../lib/validator'),
- { ValidationError, ContestNotFoundError } = require('../error'),
+ { ValidationError, ContestNotFoundError, ContestAlreadyAttendedError } = require('../error'),
db = require('../service/db.js'),
coll = db.collection('contest'),
coll_status = db.collection('contest.status');
@@ -56,7 +56,7 @@ async function getListStatus(uid, tids) {
}
async function attend(tid, uid) {
try {
- await coll_status.insertOne({ tid, uid });
+ await coll_status.insertOne({ tid, uid, attend: 1 });
} catch (e) {
throw new ContestAlreadyAttendedError(tid, uid);
}
@@ -70,10 +70,10 @@ function is_new(tdoc, days = 1) {
function is_upcoming(tdoc, days = 1) {
let now = new Date().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) {
- return new Date() < tdoc.beginAt;
+ return (new Date()) < tdoc.beginAt;
}
function is_ongoing(tdoc) {
let now = new Date();
diff --git a/hydro/module/contest/acm.js b/hydro/module/contest/acm.js
index 612a01017..1085221a8 100644
--- a/hydro/module/contest/acm.js
+++ b/hydro/module/contest/acm.js
@@ -1,6 +1,8 @@
module.exports = {
TEXT: 'ACM/ICPC',
check: () => { },
+ showScoreboard: () => true,
+ showRecord: (tdoc, now) => now > tdoc.endAt,
stat: (tdoc, journal) => {
let naccept = {}, effective = {}, detail = [], accept = 0, time = 0;
for (let j in journal) {
diff --git a/hydro/module/contest/oi.js b/hydro/module/contest/oi.js
index 6ee27cd7c..e2ad6d266 100644
--- a/hydro/module/contest/oi.js
+++ b/hydro/module/contest/oi.js
@@ -1,3 +1,5 @@
+const ranked = require('../../lib/rank');
+
module.exports = {
TEXT: 'OI',
check: () => { },
@@ -10,10 +12,13 @@ module.exports = {
}
return { score, detail };
},
- showScoreboard(tdoc,now){
- return now>tdoc.endAt;
+ showScoreboard(tdoc, now) {
+ 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 = [
{ type: 'rank', value: _('Rank') },
{ type: 'user', value: _('User') },
@@ -49,5 +54,6 @@ module.exports = {
rows.push(row);
}
return rows;
- }
+ },
+ rank: tdocs => ranked(tdocs, (a, b) => a.score == b.score)
};
\ No newline at end of file
diff --git a/hydro/service/server.js b/hydro/service/server.js
index 5e866f835..f622c5781 100644
--- a/hydro/service/server.js
+++ b/hydro/service/server.js
@@ -252,7 +252,7 @@ class ConnectionHandler {
return new Promise((resolve, reject) => {
template.render(name, Object.assign(context, {
handler: this,
- _: this.translate,
+ _: str => str ? str.toString().translate(this.user.language) : '',
user: this.user
}), (error, res) => {
console.timeEnd(name);
diff --git a/templates/contest_scoreboard.html b/templates/contest_scoreboard.html
index 202d460c2..6460e88f9 100644
--- a/templates/contest_scoreboard.html
+++ b/templates/contest_scoreboard.html
@@ -1,3 +1,4 @@
+{% set page_name = "contest_scoreboard" %}
{% extends "layout/basic.html" %}
{% block content %}