diff --git a/hydro/handler/contest.js b/hydro/handler/contest.js index ebb10c80f..3c6c516c0 100644 --- a/hydro/handler/contest.js +++ b/hydro/handler/contest.js @@ -1,8 +1,7 @@ const { ContestNotLiveError, ValidationError, ProblemNotFoundError, - ContestNotAttendedError, ContestScoreboardHiddenError } = require('../error'), - { PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD, PERM_CREATE_CONTEST, - PERM_EDIT_CONTEST } = require('../permission'), + ContestNotAttendedError } = require('../error'), + { PERM_CREATE_CONTEST, PERM_EDIT_CONTEST } = require('../permission'), { constants } = require('../options'), paginate = require('../lib/paginate'), contest = require('../model/contest'), @@ -11,52 +10,7 @@ const user = require('../model/user'), { Route, Handler } = require('../service/server'); - -class ContestHandler extends Handler { - canViewHiddenScoreboard() { - return this.user.hasPerm(PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD); - } - canShowRecord(tdoc, allowPermOverride = true) { - if (contest.RULES[tdoc.rule].showRecord(tdoc, new Date())) return true; - if (allowPermOverride && this.canViewHiddenScoreboard(tdoc)) return true; - return false; - } - canShowScoreboard(tdoc, allowPermOverride = true) { - if (contest.RULES[tdoc.rule].showScoreboard(tdoc, new Date())) return true; - if (allowPermOverride && this.canViewHiddenScoreboard(tdoc)) return true; - return false; - } - async getScoreboard(tid, isExport = false) { - 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, str => str ? str.toString().translate(this.user.language) : '', tdoc, ranked_tsdocs, udict, pdict); - return [tdoc, rows, udict]; - } - async verifyProblems(pids) { - console.log(pids); - let pdocs = await problem.getMulti({ - $or: [ - { _id: { $in: pids } }, { pid: { $in: pids } } - ] - }).sort({ _id: 1 }).toArray(); - if (pids.length != pdocs.length) - for (let pid of pids) { - let p = false; - for (let pdoc of pdocs) - if (pid == pdoc._id || pid == pdoc.pid) { - p = true; - break; - } - if (!p) throw new ProblemNotFoundError(pid); - } - return pids; - } -} +const ContestHandler = contest.ContestHandlerMixin(Handler); class ContestListHandler extends ContestHandler { async get({ rule = 0, page = 1 }) { this.response.template = 'contest_main.html'; @@ -91,7 +45,7 @@ class ContestDetailHandler extends ContestHandler { let psdict = {}, rdict = {}, attended; if (tsdoc) { attended = tsdoc.attend == 1; - for (let pdetail in tsdoc.detail || []) + for (let pdetail of tsdoc.journal || []) psdict[pdetail.pid] = pdetail; if (this.canShowRecord(this.tdoc)) { let q = []; @@ -161,7 +115,7 @@ class ContestEditHandler extends ContestDetailHandler { } endAt = new Date(beginAt + duration * 3600 * 1000); if (beginAt >= endAt) throw new ValidationError('duration'); - await this.verifyProblems(pids); + pids = await this.verifyProblems(pids); await contest.edit(this.tdoc._id, title, content, rule, beginAt, endAt, pids); if (this.tdoc.beginAt != beginAt || this.tdoc.endAt != endAt || Array.isDiff(this.tdoc.pids, pids) || this.tdoc.rule != rule) @@ -172,7 +126,7 @@ class ContestEditHandler extends ContestDetailHandler { } class ContestProblemHandler extends ContestDetailHandler { async prepare({ tid, pid }) { - [this.tdoc, this.pdoc] = await Promise.all([contest.get(tid), problem.get({ pid, uid: this.user._id })]); + [this.tdoc, this.pdoc] = await Promise.all([contest.get(tid), problem.get(pid, this.user._id)]); [this.tsdoc, this.udoc] = await Promise.all([ contest.getStatus(this.tdoc._id, this.user._id), user.getById(this.tdoc.owner) @@ -183,7 +137,9 @@ class ContestProblemHandler extends ContestDetailHandler { if (!this.attended) throw new ContestNotAttendedError(this.tdoc._id); if (!contest.is_ongoing(this.tdoc)) throw new ContestNotLiveError(this.tdoc._id); } - if (!this.tdoc.pids.includes(pid)) throw new ProblemNotFoundError(pid, this.tdoc._id); + console.log(this.tdoc.pids, this.pdoc._id); + if (!this.tdoc.pids.map(s => s.toString()).includes(this.pdoc._id.toString())) + throw new ProblemNotFoundError(pid, this.tdoc._id); } async get({ tid }) { let path = [ @@ -257,7 +213,7 @@ class ContestCreateHandler extends ContestHandler { } endAt = new Date(beginAt.getTime() + duration * 3600 * 1000); if (beginAt >= endAt) throw new ValidationError('duration'); - await this.verifyProblems(pids); + pids = await this.verifyProblems(pids); let tid = await contest.add(title, content, this.user._id, rule, beginAt, endAt, pids); this.response.body = { tid }; this.response.redirect = `/c/${tid}`; diff --git a/hydro/handler/problem.js b/hydro/handler/problem.js index bbeda8206..8628b7384 100644 --- a/hydro/handler/problem.js +++ b/hydro/handler/problem.js @@ -56,7 +56,7 @@ class ProblemDetailHandler extends ProblemHandler { this.response.template = 'problem_detail.html'; this.uid = this.user._id; this.pid = pid; - if (pid) this.pdoc = await problem.get(this.pid, this.uid); + if (pid) this.pdoc = await problem.get(pid, this.uid); if (this.pdoc.hidden && this.pdoc.owner != this.uid) this.checkPerm(PERM_VIEW_PROBLEM_HIDDEN); if (this.pdoc) this.udoc = await user.getById(this.pdoc.owner); this.response.body = { diff --git a/hydro/handler/record.js b/hydro/handler/record.js index 7e32d6112..c30d1f6fe 100644 --- a/hydro/handler/record.js +++ b/hydro/handler/record.js @@ -4,6 +4,7 @@ const PERM_REJUDGE, PERM_VIEW_PROBLEM_HIDDEN } = require('../permission'), problem = require('../model/problem'), record = require('../model/record'), + contest = require('../model/contest'), user = require('../model/user'), bus = require('../service/bus'), queue = require('../service/queue'), @@ -75,14 +76,16 @@ class RecordConnectionHandler extends ConnectionHandler { this.send({ html: await this.renderHTML('record_main_tr.html', { rdoc, udoc, pdoc }) }); } } -class RecordDetailConnectionHandler extends ConnectionHandler { +class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(ConnectionHandler) { async prepare({ rid }) { let rdoc = await record.get(rid); - if (rdoc.tid) - if (!await this.rdocContestVisible(rdoc)) { + if (rdoc.tid) { + let tdoc = await contest.get(rdoc.tid); + if (!this.canShowRecord(tdoc)) { this.close(); return; } + } this.rid = rid; bus.subscribe(['record_change'], this.onRecordChange); this.onRecordChange({ value: rdoc }); diff --git a/hydro/model/contest.js b/hydro/model/contest.js index 02b6eb4ad..87467dc2f 100644 --- a/hydro/model/contest.js +++ b/hydro/model/contest.js @@ -1,7 +1,11 @@ const + user = require('./user'), + problem = require('./problem'), + { ValidationError, ContestNotFoundError, ContestAlreadyAttendedError, + ContestNotAttendedError, ProblemNotFoundError, ContestScoreboardHiddenError } = require('../error'), + { PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD } = require('../permission'), validator = require('../lib/validator'), - { ValidationError, ContestNotFoundError, ContestAlreadyAttendedError } = require('../error'), - db = require('../service/db.js'), + db = require('../service/db'), coll = db.collection('contest'), coll_status = db.collection('contest.status'); @@ -65,13 +69,13 @@ async function get(tid) { if (!tdoc) throw new ContestNotFoundError(tid); return tdoc; } -async function get_random_id(query) { - let pdocs = coll.find(query); - let pcount = await pdocs.count(); - if (pcount) { - let pdoc = await pdocs.skip(Math.floor(Math.random() * pcount)).limit(1).toArray()[0]; - return pdoc.pid; - } else return null; +async function updateStatus(tid, uid, rid, pid, accept, score) { + let tdoc = await get(tid); + let tsdoc = await coll_status.findOneAndUpdate({ tid: tdoc._id, uid }, { + $push: { journal: { rid, pid, accept, score } }, + $inc: { rev: 1 } + }, { upsert: true }); + if (!tsdoc.value.attend) throw new ContestNotAttendedError(tid, uid); } async function getListStatus(uid, tids) { let r = {}; @@ -86,6 +90,9 @@ async function attend(tid, uid) { } await coll.findOneAndUpdate({ _id: tid }, { $inc: { attend: 1 } }); } +function getMultiStatus(query) { + return coll_status.find(query); +} function is_new(tdoc, days = 1) { let now = new Date().getTime(); let readyAt = tdoc.beginAt.getTime(); @@ -106,11 +113,47 @@ function is_ongoing(tdoc) { function is_done(tdoc) { return tdoc.endAt <= new Date(); } + +const ContestHandlerMixin = c => class extends c { + canViewHiddenScoreboard() { + return this.user.hasPerm(PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD); + } + canShowRecord(tdoc, allowPermOverride = true) { + if (RULES[tdoc.rule].showRecord(tdoc, new Date())) return true; + if (allowPermOverride && this.canViewHiddenScoreboard(tdoc)) return true; + return false; + } + canShowScoreboard(tdoc, allowPermOverride = true) { + if (RULES[tdoc.rule].showScoreboard(tdoc, new Date())) return true; + if (allowPermOverride && this.canViewHiddenScoreboard(tdoc)) return true; + return false; + } + async getScoreboard(tid, isExport = false) { + let tdoc = await get(tid); + if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid); + let tsdocs = await getMultiStatus(tid).sort(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 = RULES[tdoc.rule].rank(tsdocs); + let rows = 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) { + let r = []; + for (let pid of pids) { + let res = await problem.get(pid); + if (res) r.push(res._id); + else throw new ProblemNotFoundError(pid); + } + return r; + } +}; + module.exports = { - RULES, add, getListStatus, attend, edit, get, get_random_id, + RULES, ContestHandlerMixin, add, getListStatus, attend, edit, get, updateStatus, count: query => coll.find(query).count(), getMulti: query => coll.find(query), - getMultiStatus: query => coll_status.find(query), getStatus: (tid, uid) => coll_status.findOne({ tid, uid }), setStatus: (tid, uid, $set) => coll_status.findOneAndUpdate({ tid, uid }, { $set }), is_new, is_upcoming, is_not_started, is_ongoing, is_done, diff --git a/hydro/model/problem.js b/hydro/model/problem.js index a214f0d3c..784a40700 100644 --- a/hydro/model/problem.js +++ b/hydro/model/problem.js @@ -120,7 +120,7 @@ async function random(query) { } async function getList(pids) { let r = {}; - for (let pid of pids) r[pid] = await get({ pid }); + for (let pid of pids) r[pid] = await get(pid); return r; } diff --git a/templates/components/problem.html b/templates/components/problem.html index fa9b38a3b..4d8a8f460 100644 --- a/templates/components/problem.html +++ b/templates/components/problem.html @@ -3,8 +3,8 @@ {%- endif %} diff --git a/templates/contest_detail.html b/templates/contest_detail.html index c83e39847..851e59747 100644 --- a/templates/contest_detail.html +++ b/templates/contest_detail.html @@ -46,12 +46,12 @@ {% if handler.hasPerm(perm.PERM_LOGGEDIN) %} {% if psdict[pid] and psdict[pid].rid %} {% set rdoc = rdict[psdict[pid].rid] %} - {% if handler.can_show_record(tdoc) %} + {% if handler.canShowRecord(tdoc) %} {{ record.render_status_td(rdoc) }} {% else %}