比赛功能也太难写了

This commit is contained in:
masnn committed 2020-04-12 08:59:05 +08:00
1 parent f73859a42f
commit 32ad47e031
16 files changed
+195 -176

No files matched your search

+19 -11
View File
@@ -1,6 +1,6 @@
const
{ ContestNotLiveError, ValidationError, ProblemNotFoundError,
ContestNotAttendedError,ContestScoreboardHiddenError } = require('../error'),
ContestNotAttendedError, ContestScoreboardHiddenError } = require('../error'),
{ PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD, PERM_CREATE_CONTEST,
PERM_EDIT_CONTEST } = require('../permission'),
{ constants } = require('../options'),
@@ -22,7 +22,7 @@ class ContestHandler extends Handler {
return false;
}
canShowScoreboard(tdoc, allowPermOverride = true) {
if (contest.RULES[tdoc.rule].showScoreboardFunc(tdoc, new Date())) return true;
if (contest.RULES[tdoc.rule].showScoreboard(tdoc, new Date())) return true;
if (allowPermOverride && this.canViewHiddenScoreboard(tdoc)) return true;
return false;
}
@@ -37,12 +37,17 @@ class ContestHandler extends Handler {
return tdoc, rows, udict;
}
async verifyProblems(pids) {
let pdocs = await problem.getMulti({ _id: { $in: pids } }).sort({ doc_id: 1 }).toArray();
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) {
if (pid == pdoc._id || pid == pdoc.pid) {
p = true;
break;
}
@@ -105,7 +110,7 @@ class ContestDetailHandler extends ContestHandler {
};
}
async post_attend() {
if (contest.isDone(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);
this.back();
}
@@ -141,7 +146,7 @@ class ContestEditHandler extends ContestDetailHandler {
let dt = this.tdoc.beginAt;
this.response.body = {
rules, tdoc: this.tdoc, duration, path, pids: this.tdoc.pids.join(','),
date_text: `${dt.getFullYear()}-${dt.getMonth()}-${dt.getDate()}`,
date_text: `${dt.getFullYear()}-${dt.getMonth() + 1}-${dt.getDate()}`,
time_text: `${dt.getHours()}:${dt.getMinutes()}`,
page_name: 'contest_edit'
};
@@ -149,7 +154,7 @@ class ContestEditHandler extends ContestDetailHandler {
async post({ beginAtDate, beginAtTime, duration, title, content, rule, pids }) {
let beginAt, endAt;
try {
beginAt = Date.parse(`${beginAtDate}T${beginAtTime}+8:00`);
beginAt = new Date(Date.parse(`${beginAtDate} ${beginAtTime.replace('-', ':')}`));
} catch (e) {
throw new ValidationError('beginAtDate', 'beginAtTime');
}
@@ -237,19 +242,22 @@ class ContestCreateHandler extends ContestHandler {
let dt = new Date(ts);
this.response.body = {
rules, page_name: 'contest_create',
date_text: `${dt.getFullYear()}-${dt.getMonth()}-${dt.getDate()}`,
time_text: `${dt.getHours()}-${dt.getMinutes()}`,
date_text: `${dt.getFullYear()}-${dt.getMonth() + 1}-${dt.getDate()}`,
time_text: `${dt.getHours()}:${dt.getMinutes()}`,
pids: '1000, 1001'
};
}
async post({ title, content, rule, beginAtDate, beginAtTime, duration, pids }) {
let beginAt, endAt;
console.log(beginAtDate, beginAtTime);
try {
beginAt = Date.parse(`${beginAtDate}T${beginAtTime}+8:00`);
beginAt = new Date(Date.parse(`${beginAtDate} ${beginAtTime.replace('-', ':')}`));
console.log(beginAt, duration);
} catch (e) {
throw new ValidationError('beginAtDate', 'beginAtTime');
}
endAt = new Date(beginAt + duration * 3600 * 1000);
endAt = new Date(beginAt.getTime() + duration * 3600 * 1000);
console.log(endAt);
if (beginAt >= endAt) throw new ValidationError('duration');
await this.verifyProblems(pids);
let tid = await contest.add(title, content, this.user._id, rule, beginAt, endAt, pids);
+4 -7
View File
@@ -52,14 +52,11 @@ class ProblemRandomHandler extends ProblemHandler {
}
class ProblemDetailHandler extends ProblemHandler {
constructor(ctx) {
super(ctx);
this.uid = ctx.state.user._id;
this.pid = ctx.params.pid;
async _prepare({ pid }) {
this.response.template = 'problem_detail.html';
}
async _prepare() {
if (this.pid) this.pdoc = await problem.get(this);
this.uid = this.user._id;
this.pid = pid;
if (pid) this.pdoc = await problem.get(this);
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 = {
+2 -2
View File
@@ -2,13 +2,13 @@ const md5 = require('./md5');
exports.gravatar_url = function (email, size) {
return `//gravatar.loli.net/avatar/${md5((email || '').toString().trim().toLowerCase())}?d=mm&s=${size}`;
};
exports.datetime_span = function (dt, { relative = true } = {}) {
exports.datetime_span = function (dt, relative = true, format = '%Y-%m-%d %H:%M:%S') {
if (dt.generationTime) dt = new Date(dt.generationTime * 1000);
else if (typeof dt == 'number' || typeof dt == 'string') dt = new Date(dt);
return '<span class="time{0}" data-timestamp="{1}">{2}</span>'.format(
relative ? ' relative' : '',
dt.getTime() / 1000,
dt.toLocaleString()
dt.format(format)
);
};
exports.paginate = function* (page, num_pages) {
+77 -63
View File
@@ -12,82 +12,96 @@ const RULES = {
acm: require('../module/contest/acm')
};
async function add(title, content, owner, rule,
beginAt = new Date(), endAt = new Date(), pids = [], data = {}) {
validator.checkTitle(title);
validator.checkContent(content);
if (!this.RULES[rule]) throw new ValidationError('rule');
if (beginAt >= endAt) throw new ValidationError('beginAt', 'endAt');
Object.assign(data, { content, owner, title, rule, beginAt, endAt, pids, attend: 0 });
this.RULES[rule].check(data);
let res = await coll.insertOne(data);
return res.insertedId;
}
async function edit(tid, $set) {
if ($set.title) validator.checkTitle($set.title);
if ($set.content) validator.checkIntro($set.content);
if ($set.rule)
if (!this.RULES[$set.rule]) throw new ValidationError('rule');
if ($set.beginAt && $set.endAt)
if ($set.beginAt >= $set.endAt) throw new ValidationError('beginAt', 'endAt');
let tdoc = await coll.findOne({ tid });
if (!tdoc) throw new ContestNotFoundError(tid);
this.RULES[$set.rule || tdoc.rule].check(Object.assign(tdoc, $set));
await coll.findOneAndUpdate({ tid }, { $set });
return tdoc;
}
async function get(tid) {
let tdoc = await coll.findOne({ _id: 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 getListStatus(uid, tids) {
let r = {};
for (let tid of tids) r[tid] = await this.getStatus(tid, uid);
return r;
}
async function attend(tid, uid) {
try {
await coll_status.insertOne({ tid, uid });
} catch (e) {
throw new ContestAlreadyAttendedError(tid, uid);
}
await coll.findOneAndUpdate({ _id: tid }, { $inc: { attend: 1 } });
}
function is_new(tdoc, days = 1) {
let now = new Date().getTime();
let readyAt = tdoc.beginAt.getTime();
return (now < readyAt - days * 24 * 3600 * 1000);
}
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);
}
function is_not_started(tdoc) {
return new Date() < tdoc.beginAt;
}
function is_ongoing(tdoc) {
let now = new Date();
return (tdoc.beginAt <= now && now < tdoc.endAt);
}
function is_done(tdoc) {
return tdoc.endAt <= new Date();
}
module.exports = {
RULES,
async add(title, content, owner, rule,
beginAt = new Date(), endAt = new Date(), pids = [], data) {
validator.checkTitle(title);
validator.checkContent(content);
if (!this.RULES[rule]) throw new ValidationError('rule');
if (beginAt >= endAt) throw new ValidationError('beginAt', 'endAt');
Object.assign(data, { content, owner, title, rule, beginAt, endAt, pids, attend: 0 });
this.RULES[rule].check(data);
return await coll.insertOne(data);
},
RULES, add, getListStatus, attend, edit, get, get_random_id,
count: query => coll.find(query).count(),
async edit(tid, $set) {
if ($set.title) validator.checkTitle($set.title);
if ($set.content) validator.checkIntro($set.content);
if ($set.rule)
if (!this.RULES[$set.rule]) throw new ValidationError('rule');
if ($set.beginAt && $set.endAt)
if ($set.beginAt >= $set.endAt) throw new ValidationError('beginAt', 'endAt');
let tdoc = await coll.findOne({ tid });
if (!tdoc) throw new ContestNotFoundError(tid);
this.RULES[$set.rule || tdoc.rule].check(Object.assign(tdoc, $set));
await coll.findOneAndUpdate({ tid }, { $set });
return tdoc;
},
async get(tid) {
let tdoc = await coll.findOne({ tid });
if (!tdoc) throw new ContestNotFoundError(tid);
return tdoc;
},
getMulti: query => coll.find(query),
getMultiStatus: query => coll_status.find(query),
async 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;
},
getStatus: (tid, uid) => coll_status.findOne({ tid, uid }),
setStatus: (tid, uid, $set) => coll_status.findOneAndUpdate({ tid, uid }, { $set }),
async getListStatus(uid, tids) {
let r = {};
for (let tid of tids) r[tid] = await this.getStatus(tid, uid);
return r;
},
is_new(tdoc, days = 1) {
let now = new Date().getTime();
let readyAt = tdoc.beginAt.getTime();
return (now < readyAt - days * 24 * 3600 * 1000);
},
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);
},
is_not_started: tdoc => new Date() < tdoc.beginAt,
is_ongoing(tdoc) {
let now = new Date();
return (tdoc.beginAt <= now && now < tdoc.endAt);
},
is_done: tdoc => tdoc.endAt <= new Date(),
is_new, is_upcoming, is_not_started, is_ongoing, is_done,
status_text: tdoc =>
this.is_new(tdoc)
is_new(tdoc)
? 'New'
: this.is_upcoming(tdoc)
: is_upcoming(tdoc)
? 'Ready (☆▽☆)'
: this.is_ongoing(tdoc)
: is_ongoing(tdoc)
? 'Live...'
: 'Done',
get_status: tdoc =>
this.is_not_started(tdoc)
is_not_started(tdoc)
? 'not_started'
: this.is_ongoing(tdoc)
: is_ongoing(tdoc)
? 'ongoing'
: 'finished',
};
+7 -1
View File
@@ -85,6 +85,11 @@ async function random(query) {
return pdoc.pid;
} else return null;
}
async function getList(pids) {
let r = {};
for (let pid of pids) r[pid] = await get({pid});
return r;
}
module.exports = {
add,
@@ -94,5 +99,6 @@ module.exports = {
count,
random,
getById,
getMulti
getMulti,
getList
};
+3
View File
@@ -10,6 +10,9 @@ module.exports = {
}
return { score, detail };
},
showScoreboard(tdoc,now){
return now>tdoc.endAt;
},
scoreboard(is_export, _, tdoc, ranked_tsdocs, udict, dudict, pdict) {
let columns = [
{ type: 'rank', value: _('Rank') },
+27 -30
View File
@@ -13,8 +13,7 @@ const
blacklist = require('../model/blacklist'),
token = require('../model/token'),
opcount = require('../model/opcount'),
{ UserNotFoundError, BlacklistedError, PermissionError,
NotFoundError } = require('../error');
{ UserNotFoundError, BlacklistedError, PermissionError } = require('../error');
const options = require('../options');
let http = options.listen.https ? require('https') : require('http');
@@ -128,40 +127,33 @@ class Handler {
async ___cleanup() {
try {
await this.renderBody();
} catch (e) {
try {
let error;
if (this.response.body && this.response.body.error) error = this.response.body.error;
else error = e;
if (error instanceof NotFoundError) this.response.status = 404;
if (error.name == 'Template render error') throw error;
if (this.preferJson) this.response.body = { error };
else await this.render('error.html', { error });
} catch (error) {
if (this.preferJson) this.response.body = { error };
else await this.render('bsod.html', { error });
}
} catch (error) {
if (this.preferJson) this.response.body = { error };
else await this.render('bsod.html', { error });
}
await this.putResponse();
await this.saveCookie();
}
async renderBody() {
if (this.response.body || this.response.template) {
if (this.request.query.noTemplate || this.preferJson) return;
if (this.request.query.template || this.response.template) {
this.response.body = this.response.body || {};
await this.render(this.request.query.template || this.response.template, this.response.body);
if (!this.preferJson)
if (this.response.body || this.response.template) {
if (this.request.query.noTemplate || this.preferJson) return;
if (this.request.query.template || this.response.template) {
this.response.body = this.response.body || {};
await this.render(this.request.query.template || this.response.template, this.response.body);
}
}
}
}
async putResponse() {
if (this.response.redirect) {
this.response.type = 'application/octet-stream';
this.redirect(this.response.redirect);
if (this.response.redirect && !this.preferJson) {
this.ctx.response.type = 'application/octet-stream';
this.ctx.response.status = 302;
this.ctx.redirect(this.response.redirect);
} else {
if (this.response.body) this.ctx.body = this.response.body;
if (this.response.type) this.ctx.response.type = this.response.type;
if (this.response.status) this.ctx.response.status = this.response.status;
}
if (this.response.body) this.ctx.body = this.response.body;
if (this.response.type) this.ctx.response.type = this.response.type;
if (this.response.status) this.ctx.response.status = this.response.status;
}
async saveCookie() {
if (this.session.sid)
@@ -184,9 +176,11 @@ class Handler {
}
this.ctx.cookies.set('sid', this.session.sid, cookie);
}
async onerror(e) {
console.error(e.message, e.params);
console.error(e.stack);
async onerror(error) {
console.error(error.message, error.params);
console.error(error.stack);
this.response.template = 'error.html';
this.response.body = { error };
await this.___cleanup();
}
}
@@ -203,6 +197,8 @@ function Route(route, handler) {
if (args.page) args.page = parseInt(args.page);
if (args.rid) args.rid = new ObjectID(args.rid);
if (args.tid) args.tid = new ObjectID(args.tid);
if (args.duration) args.duration = parseFloat(args.duration);
if (args.pids) args.pids = args.pids.split(',').map(i => i.trim());
if (h.___prepare) await h.___prepare(args);
if (h.__prepare) await h.__prepare(args);
@@ -330,6 +326,7 @@ exports.Connection = Connection;
exports.start = function start() {
app.use(morgan(':method :url :status :res[content-length] - :response-time ms'));
app.use(router.routes()).use(router.allowedMethods());
Route('*', Handler);
server.listen(options.listen.port);
console.log('Server listening at: %s', options.listen.port);
};
+11
View File
@@ -27,3 +27,14 @@ Array.isDiff = function isDiff(a, b) {
if (a[i] != b[i]) return true;
return false;
};
Date.prototype.format = function (fmt = '%Y-%m-%d %H:%M:%S') {
return fmt
.replace('%Y', this.getFullYear())
.replace('%m', this.getMonth() + 1)
.replace('%D', this.getDate())
.replace('%d', this.getDate())
.replace('%H', this.getHours())
.replace('%M', this.getMinutes())
.replace('%S', this.getSeconds());
};