markdown语法增强,错误修复

This commit is contained in:
undefined committed 2020-06-16 12:10:55 +08:00
1 parent 4b3c10b4eb
commit a29b801e72
22 files changed
+311 -200

No files matched your search

+12 -10
View File
@@ -147,25 +147,27 @@ class ContestEditHandler extends ContestDetailHandler {
} }
async post({ async post({
domainId, beginAtDate, beginAtTime, duration, title, content, rule, pids, domainId, beginAtDate, beginAtTime, duration, title, content, rule, pids, rated = false,
}) { }) {
let beginAt; let beginAt;
try { try {
beginAt = moment.tz(`${beginAtDate} ${beginAtTime}`, this.uset.timeZone); beginAt = moment.tz(`${beginAtDate} ${beginAtTime}`, this.user.timeZone);
} catch (e) { } catch (e) {
throw new ValidationError('beginAtDate', 'beginAtTime'); throw new ValidationError('beginAtDate', 'beginAtTime');
} }
const endAt = beginAt.add(duration, 'hours').toDate(); const endAt = beginAt.clone().add(duration, 'hours').toDate();
if (beginAt.isSameOrAfter(endAt)) throw new ValidationError('duration'); if (beginAt.isSameOrAfter(endAt)) throw new ValidationError('duration');
beginAt = beginAt.toDate(); beginAt = beginAt.toDate();
pids = await this.verifyProblems(domainId, pids); pids = await this.verifyProblems(domainId, pids);
await contest.edit(domainId, this.tdoc.docId, title, content, rule, beginAt, endAt, pids); await contest.edit(domainId, this.tdoc.docId, {
title, content, rule, beginAt, endAt, pids, rated,
});
if (this.tdoc.beginAt !== beginAt || this.tdoc.endAt !== endAt if (this.tdoc.beginAt !== beginAt || this.tdoc.endAt !== endAt
|| Array.isDiff(this.tdoc.pids, pids) || this.tdoc.rule !== rule) { || Array.isDiff(this.tdoc.pids, pids) || this.tdoc.rule !== rule) {
await contest.recalcStatus(this.tdoc.docId); await contest.recalcStatus(this.tdoc.docId);
} }
if (this.preferJson) this.response.body = { tid: this.tdoc.docId }; this.response.body = { tid: this.tdoc.docId };
else this.response.redirect = `/c/${this.tdoc.docId}`; this.response.redirect = this.url('contest_detail', { tid: this.tdoc.docId });
} }
} }
@@ -251,10 +253,10 @@ class ContestProblemSubmitHandler extends ContestProblemHandler {
await contest.updateStatus(domainId, this.tdoc.docId, this.user._id, rid, this.pdoc.docId); await contest.updateStatus(domainId, this.tdoc.docId, this.user._id, rid, this.pdoc.docId);
if (!this.canShowRecord(this.tdoc)) { if (!this.canShowRecord(this.tdoc)) {
this.response.body = { tid }; this.response.body = { tid };
this.response.redirect = `/c/${tid}`; this.response.redirect = this.url('contest_detail', { tid });
} else { } else {
this.response.body = { rid }; this.response.body = { rid };
this.response.redirect = `/r/${rid}`; this.response.redirect = this.url('record_detail', { rid });
} }
} }
} }
@@ -292,14 +294,14 @@ class ContestCreateHandler extends ContestHandler {
} catch (e) { } catch (e) {
throw new ValidationError('beginAtDate', 'beginAtTime'); throw new ValidationError('beginAtDate', 'beginAtTime');
} }
const endAt = beginAt.add(duration, 'hours'); const endAt = beginAt.clone().add(duration, 'hours');
pids = await this.verifyProblems(domainId, pids); pids = await this.verifyProblems(domainId, pids);
const tid = await contest.add( const tid = await contest.add(
domainId, title, content, domainId, title, content,
this.user._id, rule, beginAt, endAt, pids, rated, this.user._id, rule, beginAt, endAt, pids, rated,
); );
this.response.body = { tid }; this.response.body = { tid };
this.response.redirect = `/c/${tid}`; this.response.redirect = this.url('contest_detail', { tid });
} }
} }
+3 -3
View File
@@ -176,7 +176,7 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler {
class HomeworkCreateHandler extends HomeworkHandler { class HomeworkCreateHandler extends HomeworkHandler {
async get() { async get() {
const beginAt = moment().add(1, 'day'); const beginAt = moment().add(1, 'day');
const penaltySince = beginAt.add(7, 'days'); const penaltySince = beginAt.clone().add(7, 'days');
const path = [ const path = [
['Hydro', 'homepage'], ['Hydro', 'homepage'],
['homework_main', 'homework_main'], ['homework_main', 'homework_main'],
@@ -212,7 +212,7 @@ class HomeworkCreateHandler extends HomeworkHandler {
} catch (e) { } catch (e) {
throw new ValidationError('endAtDate', 'endAtTime'); throw new ValidationError('endAtDate', 'endAtTime');
} }
const endAt = penaltySince.add(extensionDays, 'days'); const endAt = penaltySince.clone().add(extensionDays, 'days');
if (beginAt.isSameOrAfter(penaltySince)) throw new ValidationError('endAtDate', 'endAtTime'); if (beginAt.isSameOrAfter(penaltySince)) throw new ValidationError('endAtDate', 'endAtTime');
if (penaltySince.isAfter(endAt)) throw new ValidationError('extensionDays'); if (penaltySince.isAfter(endAt)) throw new ValidationError('extensionDays');
penaltySince = penaltySince.toDate(); penaltySince = penaltySince.toDate();
@@ -273,7 +273,7 @@ class HomeworkEditHandler extends HomeworkHandler {
} catch (e) { } catch (e) {
throw new ValidationError('endAtDate', 'endAtTime'); throw new ValidationError('endAtDate', 'endAtTime');
} }
let endAt = penaltySince.add(extensionDays, 'days'); let endAt = penaltySince.clone().add(extensionDays, 'days');
if (beginAt.isSameOrAfter(penaltySince)) throw new ValidationError('endAtDate', 'endAtTime'); if (beginAt.isSameOrAfter(penaltySince)) throw new ValidationError('endAtDate', 'endAtTime');
await this.verifyProblems(domainId, pids); await this.verifyProblems(domainId, pids);
beginAt = beginAt.toDate(); beginAt = beginAt.toDate();
+15 -10
View File
@@ -38,14 +38,17 @@ async function next(body) {
const $set = {}; const $set = {};
const $push = {}; const $push = {};
if (body.case) { if (body.case) {
body.case.memory = body.case.memory_kb; const c = {};
body.case.time = body.case.time_ms; c.memory = body.case.memory_kb || body.case.memory;
rdoc.testCases.push(body.case); c.time = body.case.time_ms || body.case.time;
$push.testCases = body.case; c.judgeText = body.case.judge_text || body.case.judgeText || body.case.message;
c.status = body.case.status;
rdoc.testCases.push(c);
$push.testCases = c;
} }
if (body.judge_text) { if (body.judge_text || body.message) {
rdoc.judgeTexts.push(body.judge_text); rdoc.judgeTexts.push(body.judge_text || body.message);
$push.judgeTexts = body.judge_text; $push.judgeTexts = body.judge_text || body.message;
} }
if (body.compiler_text) { if (body.compiler_text) {
rdoc.compilerTexts.push(body.compiler_text); rdoc.compilerTexts.push(body.compiler_text);
@@ -53,8 +56,9 @@ async function next(body) {
} }
if (body.status) $set.status = body.status; if (body.status) $set.status = body.status;
if (body.score) $set.score = body.score; if (body.score) $set.score = body.score;
if (body.time_ms) $set.time = body.time_ms; if (body.time_ms) $set.time = body.time_ms || body.time;
if (body.memory_kb) $set.memory = body.memory_kb; if (body.memory_kb) $set.memory = body.memory_kb || body.memory;
if (body.progress) $set.progress = body.progress;
rdoc = await record.update(body.domainId, body.rid, $set, $push); rdoc = await record.update(body.domainId, body.rid, $set, $push);
bus.publish('record_change', rdoc); bus.publish('record_change', rdoc);
} }
@@ -63,6 +67,7 @@ async function end(body) {
let rdoc = await record.get(body.domainId, body.rid); let rdoc = await record.get(body.domainId, body.rid);
const $set = {}; const $set = {};
const $push = {}; const $push = {};
const $unset = { progress: '' };
if (body.judge_text) { if (body.judge_text) {
rdoc.judgeTexts.push(body.judge_text); rdoc.judgeTexts.push(body.judge_text);
$push.judgeTexts = body.judge_text; $push.judgeTexts = body.judge_text;
@@ -77,7 +82,7 @@ async function end(body) {
if (body.memory_kb) $set.memory = body.memory_kb; if (body.memory_kb) $set.memory = body.memory_kb;
$set.judgeAt = new Date(); $set.judgeAt = new Date();
$set.judger = body.judger; $set.judger = body.judger;
rdoc = await record.update(body.domainId, body.rid, $set, $push); rdoc = await record.update(body.domainId, body.rid, $set, $push, $unset);
await _postJudge(rdoc); await _postJudge(rdoc);
rdoc = await record.update(body.domainId, body.rid, $set, $push); rdoc = await record.update(body.domainId, body.rid, $set, $push);
} }
+44 -3
View File
@@ -1,5 +1,8 @@
const setting = require('../model/setting'); const setting = require('../model/setting');
const system = require('../model/system'); const system = require('../model/system');
const { STATUS } = require('../model/builtin');
const record = require('../model/record');
const judge = require('./judge');
const { Route, Handler } = require('../service/server'); const { Route, Handler } = require('../service/server');
const hpm = require('../lib/hpm'); const hpm = require('../lib/hpm');
const loader = require('../loader'); const loader = require('../loader');
@@ -56,11 +59,49 @@ class SystemScriptHandler extends SystemHandler {
this.response.body.path.push(['manage_script', null]); this.response.body.path.push(['manage_script', null]);
} }
async post({ id, args = '{}' }) { async post({ domainId, id, args = '{}' }) {
args = JSON.parse(args); args = JSON.parse(args);
// TODO Do not use console.log // TODO Do not use console.log
await global.Hydro.script[id].run(args, console.log); const rid = await record.add(domainId, {
this.back(); pid: id,
uid: this.user._id,
lang: null,
code: null,
status: STATUS.STATUS_JUDGING,
}, false);
async function report(data) {
judge.next({ domainId, rid, ...data });
}
setTimeout(() => {
const start = new Date().getTime();
global.Hydro.script[id].run(args, report)
.then((ret) => {
const time = new Date().getTime() - start;
judge.end({
domainId,
rid,
status: STATUS.STATUS_ACCEPTED,
judge_text: ret,
judger: 1,
time_ms: time,
memory_kb: 0,
});
})
.catch((err) => {
const time = new Date().getTime() - start;
judge.end({
domainId,
rid,
status: STATUS.STATUS_SYSTEM_ERROR,
judge_text: `${err}\n${err.stack}`,
judger: 1,
time_ms: time,
memory_kb: 0,
});
});
}, 500);
this.response.body = { rid };
this.response.redirect = this.url('record_main', { rid });
} }
} }
+14 -9
View File
@@ -5,6 +5,7 @@ const record = require('../model/record');
const contest = require('../model/contest'); const contest = require('../model/contest');
const system = require('../model/system'); const system = require('../model/system');
const user = require('../model/user'); const user = require('../model/user');
const paginate = require('../lib/paginate');
const bus = require('../service/bus'); const bus = require('../service/bus');
const { const {
Route, Handler, Connection, ConnectionHandler, Route, Handler, Connection, ConnectionHandler,
@@ -25,7 +26,11 @@ class RecordListHandler extends RecordHandler {
]; ];
} }
if (pid) q.pid = pid; if (pid) q.pid = pid;
const rdocs = await record.getMany(domainId, q, { _id: -1 }, page, await system.get('RECORD_PER_PAGE')); const [rdocs] = await paginate(
record.getMulti(domainId, q).sort('_id', -1),
page,
await system.get('RECORD_PER_PAGE'),
);
const [udict, pdict] = await Promise.all([ const [udict, pdict] = await Promise.all([
user.getList(domainId, rdocs.map((rdoc) => rdoc.uid)), user.getList(domainId, rdocs.map((rdoc) => rdoc.uid)),
problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid), false), problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid), false),
@@ -59,7 +64,7 @@ class RecordDetailHandler extends RecordHandler {
rdoc.code = null; rdoc.code = null;
} }
const [pdoc, udoc] = await Promise.all([ const [pdoc, udoc] = await Promise.all([
problem.get(domainId, rdoc.pid), problem.get(domainId, rdoc.pid, null, false),
user.getById(domainId, rdoc.uid), user.getById(domainId, rdoc.uid),
]); ]);
this.response.body = { this.response.body = {
@@ -101,13 +106,13 @@ class RecordMainConnectionHandler extends RecordConnectionHandler {
} }
async message(msg) { async message(msg) {
const rdocs = await record.getMany( if (msg.rids instanceof Array) {
this.domainId, const rdocs = await record.getMulti(
{ _id: { $in: msg.rids } }, this.domainId, { _id: { $in: msg.rids } },
{ _id: -1 }, 1, 100, ).toArray();
); for (const rdoc of rdocs) {
for (const rdoc of rdocs) { this.onRecordChange({ value: rdoc });
this.onRecordChange({ value: rdoc }); }
} }
} }
+56 -1
View File
@@ -1,6 +1,29 @@
const MarkdownIt = require('markdown-it'); const MarkdownIt = require('markdown-it');
const katex = require('markdown-it-katex');
const md = MarkdownIt();
const Prism = require('prismjs'); const Prism = require('prismjs');
// For math: $a\times b\eq 10$
const katex = require('markdown-it-katex');
// Specific image size: ![image](image.png =100x100)
const Imsize = require('markdown-it-imsize');
/* Footnote support.
Here is a footnote reference,[^1] and another.[^longnote]
[^1]: Here is the footnote.
[^longnote]: Here's one with multiple blocks.
Subsequent paragraphs are indented to show that they
belong to the previous footnote.
*/
const Footnote = require('markdown-it-footnote');
// ==Highlight==
const Mark = require('markdown-it-mark');
// :::warn This page requires javascript. :::
// :::record-pass Accepted :::
const Container = require('markdown-it-container');
require('prismjs/components/index'); require('prismjs/components/index');
class Markdown extends MarkdownIt { class Markdown extends MarkdownIt {
@@ -19,6 +42,38 @@ class Markdown extends MarkdownIt {
}); });
this.linkify.tlds('.py', false); this.linkify.tlds('.py', false);
this.use(katex); this.use(katex);
this.use(Imsize);
this.use(Footnote);
this.use(Mark);
const RE_CONTAINER = /^(note|warn|record-pending|record-progress|record-fail|record-pass|record-ignored)\s+(.*)$/;
const CONTAINER_MAP = {
note: ['<blockquote class="note">', '</blockquote>'],
warn: ['<blockquote class="warn">', '</blockquote>'],
'record-pending': ['<span class="record-status--text pending">', '</span>'],
'record-progress': ['<span class="record-status--text progress">', '</span>'],
'record-fail': ['<span class="record-status--text fail">', '</span>'],
'record-pass': ['<span class="record-status--text pass">', '</span>'],
'record-ignored': ['<span class="record-status--text ignored">', '</span>'],
};
this.use(Container, 'blockquote', {
validate(params) {
return params.trim().match(RE_CONTAINER);
},
render(tokens, idx) {
const m = tokens[idx].info.trim().match(RE_CONTAINER);
if (!m) return '';
if (tokens[idx].nesting === 1) {
if (CONTAINER_MAP[m[1]]) {
return `${CONTAINER_MAP[m[1]][0] + md.utils.escapeHtml(m[2])}\n`;
}
return `[${m[1]}] ${md.utils.escapeHtml(m[2])}\n`;
}
if (CONTAINER_MAP[m[1]]) {
return `${CONTAINER_MAP[m[1]][1]}\n`;
}
return `[/${m[1]}]`;
},
});
} }
} }
+6
View File
@@ -1,5 +1,11 @@
const { ValidationError } = require('../error'); const { ValidationError } = require('../error');
/**
* @param {import('mongodb').Cursor} cursor
* @param {number} page
* @param {number} pageSize
* @returns {Promise<Array[]>} pageDocs numPages count
*/
async function paginate(cursor, page, pageSize) { async function paginate(cursor, page, pageSize) {
if (page <= 0) throw new ValidationError('page'); if (page <= 0) throw new ValidationError('page');
const [count, pageDocs] = await Promise.all([ const [count, pageDocs] = await Promise.all([
+2 -1
View File
@@ -348,7 +348,8 @@ const ContestHandlerMixin = (c) => class extends c {
} }
async verifyProblems(domainId, pids) { // eslint-disable-line class-methods-use-this async verifyProblems(domainId, pids) { // eslint-disable-line class-methods-use-this
return await problem.getList(domainId, pids, true); await problem.getList(domainId, pids, true);
return pids;
} }
}; };
+5 -1
View File
@@ -25,4 +25,8 @@ async function inc(op, ident, periodSecs, maxOperations) {
} }
} }
global.Hydro.model.opcount = module.exports = { inc }; function ensureIndexes() {
return coll.createIndex('expireAt', { expireAfterSeconds: 0 });
}
global.Hydro.model.opcount = module.exports = { inc, ensureIndexes };
+9 -6
View File
@@ -44,13 +44,16 @@ async function add(domainId, title, content, owner, {
* @param {string} domainId * @param {string} domainId
* @param {string|number} pid * @param {string|number} pid
* @param {number} uid * @param {number} uid
* @returns {Pdoc} * @returns {Promise<Pdoc>}
*/ */
async function get(domainId, pid, uid = null) { async function get(domainId, pid, uid = null, doThrow = true) {
const pdoc = Number.isInteger(pid) const pdoc = Number.isInteger(pid)
? await document.get(domainId, document.TYPE_PROBLEM, pid) ? await document.get(domainId, document.TYPE_PROBLEM, pid)
: (await document.getMulti(domainId, document.TYPE_PROBLEM, { pid }).toArray())[0]; : (await document.getMulti(domainId, document.TYPE_PROBLEM, { pid }).toArray())[0];
if (!pdoc) throw new ProblemNotFoundError(domainId, pid); if (!pdoc) {
if (doThrow) throw new ProblemNotFoundError(domainId, pid);
return null;
}
if (uid) { if (uid) {
pdoc.psdoc = await document.getStatus(domainId, document.TYPE_PROBLEM, pdoc.docId, uid); pdoc.psdoc = await document.getStatus(domainId, document.TYPE_PROBLEM, pdoc.docId, uid);
} }
@@ -63,7 +66,7 @@ async function get(domainId, pid, uid = null) {
* @param {object} sort * @param {object} sort
* @param {number} page * @param {number} page
* @param {number} limit * @param {number} limit
* @returns {Pdoc[]} * @returns {Promise<Pdoc[]>}
*/ */
function getMany(domainId, query, sort, page, limit) { function getMany(domainId, query, sort, page, limit) {
return document.getMulti(domainId, query).sort(sort).skip((page - 1) * limit).limit(limit) return document.getMulti(domainId, query).sort(sort).skip((page - 1) * limit).limit(limit)
@@ -116,7 +119,7 @@ async function random(domainId, query) {
} }
async function getList(domainId, pids, doThrow = true) { async function getList(domainId, pids, doThrow = true) {
pids = new Set(pids); pids = Array.from(new Set(pids));
const r = {}; const r = {};
const pdocs = await document.getMulti( const pdocs = await document.getMulti(
domainId, document.TYPE_PROBLEM, domainId, document.TYPE_PROBLEM,
@@ -125,7 +128,7 @@ async function getList(domainId, pids, doThrow = true) {
for (const pdoc of pdocs) { for (const pdoc of pdocs) {
r[pdoc.docId] = r[pdoc.pid] = pdoc; r[pdoc.docId] = r[pdoc.pid] = pdoc;
} }
if (pdocs.length !== pids.size) { if (pdocs.length !== pids.length) {
if (doThrow) { if (doThrow) {
for (const pid of pids) { for (const pid of pids) {
if (!r[pid]) throw new ProblemNotFoundError(domainId, pid); if (!r[pid]) throw new ProblemNotFoundError(domainId, pid);
+18 -16
View File
@@ -12,7 +12,7 @@ const coll = db.collection('record');
* @param {string} domainId * @param {string} domainId
* @param {import('../interface').Record} data * @param {import('../interface').Record} data
*/ */
async function add(domainId, data) { async function add(domainId, data, addTask = true) {
_.defaults(data, { _.defaults(data, {
status: STATUS_WAITING, status: STATUS_WAITING,
domainId, domainId,
@@ -27,18 +27,20 @@ async function add(domainId, data) {
judgeAt: null, judgeAt: null,
}); });
const [pdoc, res] = await Promise.all([ const [pdoc, res] = await Promise.all([
problem.get(domainId, data.pid), problem.get(domainId, data.pid, null, false),
coll.insertOne(data), coll.insertOne(data),
]); ]);
await task.add({ if (addTask) {
type: 'judge', await task.add({
rid: res.insertedId, type: 'judge',
domainId, rid: res.insertedId,
pid: data.pid, domainId,
data: pdoc.data, pid: data.pid,
lang: data.lang, data: (pdoc || {}).data,
code: data.code, lang: data.lang,
}); code: data.code,
});
}
return res.insertedId; return res.insertedId;
} }
@@ -54,16 +56,16 @@ async function get(domainId, rid) {
return rdoc; return rdoc;
} }
function getMany(domainId, query, sort, page, limit) { function getMulti(domainId, query) {
return coll.find({ ...query, domainId }).sort(sort).skip((page - 1) * limit).limit(limit) return coll.find({ ...query, domainId });
.toArray();
} }
async function update(domainId, rid, $set, $push) { async function update(domainId, rid, $set, $push, $unset) {
const _id = new ObjectID(rid); const _id = new ObjectID(rid);
const upd = {}; const upd = {};
if ($set && Object.keys($set).length) upd.$set = $set; if ($set && Object.keys($set).length) upd.$set = $set;
if ($push && Object.keys($push).length) upd.$push = $push; if ($push && Object.keys($push).length) upd.$push = $push;
if ($unset && Object.keys($unset).length) upd.$unset = $unset;
await coll.findOneAndUpdate({ domainId, _id }, upd); await coll.findOneAndUpdate({ domainId, _id }, upd);
const rdoc = await coll.findOne({ _id }); const rdoc = await coll.findOne({ _id });
if (!rdoc) throw new RecordNotFoundError(rid); if (!rdoc) throw new RecordNotFoundError(rid);
@@ -136,7 +138,7 @@ async function rejudge(domainId, rid) {
global.Hydro.model.record = module.exports = { global.Hydro.model.record = module.exports = {
add, add,
get, get,
getMany, getMulti,
update, update,
count, count,
reset, reset,
+28 -5
View File
@@ -3,21 +3,34 @@ const domain = require('../model/domain');
const contest = require('../model/contest'); const contest = require('../model/contest');
const user = require('../model/user'); const user = require('../model/user');
const rating = require('../lib/rating'); const rating = require('../lib/rating');
const { STATUS } = require('../model/builtin');
async function run({ domainId, isSub = false }, report) { async function run({ domainId, isSub = false }, report) {
if (!domainId) { if (!domainId) {
const domains = await domain.getMulti().toArray(); const domains = await domain.getMulti().toArray();
await report({ total: domains.length }); await report({ message: `Found ${domains.length} domains` });
for (const i in domains) { for (const i in domains) {
const start = new Date().getTime();
await run({ domainId: domains[i]._id, isSub: true }, report); await run({ domainId: domains[i]._id, isSub: true }, report);
await report({ current: parseInt(i) + 1 }); await report({
case: {
status: STATUS.STATUS_ACCEPTED,
judgeText: `Domain ${i} finished`,
time_ms: new Date().getTime() - start,
memory_kb: 0,
score: 0,
},
progress: Math.floor(((parseInt(i) + 1) / domains.length) * 100),
});
} }
return true; return true;
} }
await user.setMultiInDomain(domainId, {}, { rating: 1500 });
const contests = await contest.getMulti(domainId, { rated: true }).sort('endAt', -1).toArray(); const contests = await contest.getMulti(domainId, { rated: true }).sort('endAt', -1).toArray();
await report({ total: contests.length }); await report({ message: `Found ${contests.length} contests in ${domainId}` });
const udict = {}; const udict = {};
for (const i in contests) { for (const i in contests) {
const start = new Date().getTime();
const tdoc = contests[i]; const tdoc = contests[i];
const tsdocs = await contest.getMultiStatus(domainId, { docId: tdoc.docId }) const tsdocs = await contest.getMultiStatus(domainId, { docId: tdoc.docId })
.sort(contest.RULES[tdoc.rule].statusSort).toArray(); .sort(contest.RULES[tdoc.rule].statusSort).toArray();
@@ -30,8 +43,18 @@ async function run({ domainId, isSub = false }, report) {
for (const udoc of rated) { for (const udoc of rated) {
udict[udoc.uid] = udoc.new; udict[udoc.uid] = udoc.new;
} }
if (isSub) await report({ subProgress: (i + 1) / contests.length }); if (!isSub) {
else await report({ current: parseInt(i) + 1 }); await report({ progress: Math.floor(((parseInt(i) + 1) / contests.length) * 100) });
}
await report({
case: {
status: STATUS.STATUS_ACCEPTED,
judgeText: `Contest ${tdoc.title} finished`,
time_ms: new Date().getTime() - start,
memory_kb: 0,
score: 0,
},
});
} }
const tasks = []; const tasks = [];
for (const uid in udict) { for (const uid in udict) {
+36 -15
View File
@@ -31,6 +31,18 @@ const _validateObjectId = (id, key) => {
if (ObjectID.isValid(id)) return new ObjectID(id); if (ObjectID.isValid(id)) return new ObjectID(id);
throw new ValidationError(key); throw new ValidationError(key);
}; };
const _bool = (val) => !!val;
const _splitAndTrim = (val) => val.split(',').map((i) => i.trim());
const _date = (val) => {
const d = val.split('-');
assert(d.length === 3);
return `${d[0]}-${d[1].length === 1 ? '0' : ''}${d[1]}-${d[2].length === 1 ? '0' : ''}${d[2]}`;
};
const _time = (val) => {
const t = val.split(':');
assert(t.length === 2);
return `${(t[0].length === 1 ? '0' : '') + t[0]}:${t[1].length === 1 ? '0' : ''}${t[1]}`;
};
const validate = { const validate = {
tid: _validateObjectId, tid: _validateObjectId,
@@ -42,6 +54,12 @@ const validate = {
psrid: _validateObjectId, psrid: _validateObjectId,
docId: _validateObjectId, docId: _validateObjectId,
mongoId: _validateObjectId, mongoId: _validateObjectId,
hidden: _bool,
rated: _bool,
category: _splitAndTrim,
tag: _splitAndTrim,
beginAtDate: _date,
beginAtTime: _time,
pid: (pid) => (Number.isSafeInteger(parseInt(pid)) ? parseInt(pid) : pid), pid: (pid) => (Number.isSafeInteger(parseInt(pid)) ? parseInt(pid) : pid),
content: validator.checkContent, content: validator.checkContent,
title: validator.checkTitle, title: validator.checkTitle,
@@ -54,9 +72,6 @@ const validate = {
if (page <= 0) throw new ValidationError('page'); if (page <= 0) throw new ValidationError('page');
return page; return page;
}, },
category: (category) => category.split(',').map((i) => i.trim()),
tag: (tag) => tag.split(',').map((i) => i.trim()),
hidden: (hidden) => !!hidden,
duration: (duration) => { duration: (duration) => {
if (!Number.isNaN(parseFloat(duration))) duration = parseFloat(duration); if (!Number.isNaN(parseFloat(duration))) duration = parseFloat(duration);
if (duration <= 0) throw new ValidationError('duration'); if (duration <= 0) throw new ValidationError('duration');
@@ -192,18 +207,22 @@ class Handler {
} }
url(name, kwargs = {}) { // eslint-disable-line class-methods-use-this url(name, kwargs = {}) { // eslint-disable-line class-methods-use-this
delete kwargs.__keywords; let res = '#';
if (this.args.domainId !== 'system') { try {
name += '_with_domainId'; delete kwargs.__keywords;
kwargs.domainId = kwargs.domainId || this.args.domainId; if (this.args.domainId !== 'system') {
name += '_with_domainId';
kwargs.domainId = kwargs.domainId || this.args.domainId;
}
const { anchor, query } = kwargs;
delete kwargs.anchor;
delete kwargs.query;
if (query) res = router.url(name, kwargs, { query });
else res = router.url(name, kwargs);
if (anchor) return `${res}#${anchor}`;
} catch (e) {
console.error(e.message);
} }
let res;
const { anchor, query } = kwargs;
delete kwargs.anchor;
delete kwargs.query;
if (query) res = router.url(name, kwargs, { query });
else res = router.url(name, kwargs);
if (anchor) return `${res}#${anchor}`;
return res; return res;
} }
@@ -331,15 +350,17 @@ async function handle(ctx, HandlerClass, permission) {
if (h.___prepare) await h.___prepare(args); if (h.___prepare) await h.___prepare(args);
if (permission) h.checkPerm(permission); if (permission) h.checkPerm(permission);
let checking = '';
try { try {
for (const key in validate) { for (const key in validate) {
checking = key;
if (args[key]) { if (args[key]) {
args[key] = validate[key](args[key], key); args[key] = validate[key](args[key], key);
} }
} }
} catch (e) { } catch (e) {
if (e instanceof ValidationError) throw e; if (e instanceof ValidationError) throw e;
throw new ValidationError('Argument check failed'); throw new ValidationError(`Argument ${checking} check failed`);
} }
h.args = args; h.args = args;
if (h.__prepare) await h.__prepare(args); if (h.__prepare) await h.__prepare(args);
+11 -4
View File
@@ -1,3 +1,5 @@
const { hasIn } = require("lodash");
const dict = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'; const dict = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
String.random = function random(digit) { String.random = function random(digit) {
@@ -19,14 +21,19 @@ Array.isDiff = function isDiff(a, b) {
* @returns {string} Formatted date string * @returns {string} Formatted date string
*/ */
Date.prototype.format = function formatDate(fmt = '%Y-%m-%d %H:%M:%S') { Date.prototype.format = function formatDate(fmt = '%Y-%m-%d %H:%M:%S') {
let h = this.getHours();
if (h < 10) h = `0${h}`;
let m = this.getMinutes();
if (m < 10) m = `0${m}`;
let s = this.getSeconds();
if (s < 10) s = `0${s}`;
return fmt return fmt
.replace('%Y', this.getFullYear()) .replace('%Y', this.getFullYear())
.replace('%m', this.getMonth() + 1) .replace('%m', this.getMonth() + 1)
.replace('%D', this.getDate())
.replace('%d', this.getDate()) .replace('%d', this.getDate())
.replace('%H', this.getHours()) .replace('%H', h)
.replace('%M', this.getMinutes()) .replace('%M', m)
.replace('%S', this.getSeconds()); .replace('%S', s);
}; };
/** /**
-33
View File
@@ -1,33 +0,0 @@
const { Route, Handler } = global.Hydro.service.server;
const { PERM_MANAGE } = global.Hydro.permission;
const { message } = global.Hydro.model;
class MigrateVijosHandler extends Handler {
async prepare() {
this.checkPerm(PERM_MANAGE);
}
async get() {
const path = [
['Hydro', 'homepage'],
['migrate_vijos', null],
];
this.response.body = { path };
this.response.template = 'migrate_vijos.html';
}
async post({
host, port, name, username, password,
}) {
global.Hydro.script.migrateVijos({
host, port, name, username, password,
}, (data) => message.send(1, 1, data));
this.response.redirect = '/manage/log';
}
}
async function apply() {
Route('migrate_vijos', '/migrate/vijos', MigrateVijosHandler);
}
global.Hydro.handler.migrateVijos = module.exports = apply;
-1
View File
@@ -1,4 +1,3 @@
const { md5, sha1 } = global.Hydro.lib; const { md5, sha1 } = global.Hydro.lib;
global.Hydro.lib['hash.vj2'] = module.exports = { global.Hydro.lib['hash.vj2'] = module.exports = {
+10 -6
View File
@@ -3,6 +3,9 @@ const { mongodb } = global.nodeModules;
const dst = global.Hydro.service.db; const dst = global.Hydro.service.db;
const { file, problem, discussion } = global.Hydro.model; const { file, problem, discussion } = global.Hydro.model;
// TODO read config file
// TODO output enhancement
const map = {}; const map = {};
const pid = (id) => { const pid = (id) => {
@@ -114,7 +117,6 @@ const tasks = {
domain_id: 'domainId', domain_id: 'domainId',
num_accept: 'nAccept', num_accept: 'nAccept',
num_submit: 'nSubmit', num_submit: 'nSubmit',
rp: 'rp',
status: 'status', status: 'status',
accept: 'accept', accept: 'accept',
vote: 'vote', vote: 'vote',
@@ -259,6 +261,8 @@ async function domainUser(src, report) {
num_problems: 'nProblems', num_problems: 'nProblems',
domain_id: 'domainId', domain_id: 'domainId',
level: 'level', level: 'level',
rp: null,
rank: null,
}; };
const d = {}; const d = {};
for (const key in doc) { for (const key in doc) {
@@ -269,7 +273,7 @@ async function domainUser(src, report) {
} else if (mapper[key] === null) { } else if (mapper[key] === null) {
// Ignore this key // Ignore this key
} else { } else {
console.log('Unknown key:', key); await report({ message: `Unknown key ${key} in collection domain.user` });
} }
} }
const docWithoutId = {}; const docWithoutId = {};
@@ -282,7 +286,7 @@ async function domainUser(src, report) {
{ _id: d._id }, { _id: d._id },
{ {
$set: { $set: {
...docWithoutId, docType: 0, docId: 1, role: docWithoutId.role || 'default', ...docWithoutId, docType: 0, docId: 0, role: docWithoutId.role || 'default',
}, },
}, },
{ upsert: true }, { upsert: true },
@@ -349,7 +353,7 @@ async function task(name, src, report) {
} else if (mapper[key] === null) { } else if (mapper[key] === null) {
// Ignore this key // Ignore this key
} else { } else {
console.log('Unknown key:', key); await report({ message: `Unknown key ${key} in collection ${name}` });
} }
} }
} }
@@ -392,7 +396,7 @@ async function task(name, src, report) {
} else res.push(dst.collection(name).insertOne(d)); } else res.push(dst.collection(name).insertOne(d));
} }
} }
await Promise.all(res).catch((e) => console.log(e)); await Promise.all(res).catch((e) => report({ message: `${e}\n${e.stack}` }));
const progress = Math.round(100 * ((i + 1) / (total + 1))); const progress = Math.round(100 * ((i + 1) / (total + 1)));
if (progress > lastProgress) { if (progress > lastProgress) {
await report({ progress }); await report({ progress });
@@ -403,7 +407,7 @@ async function task(name, src, report) {
async function migrateVijos({ async function migrateVijos({
host, port, name, username, password, host, port, name, username, password,
}, report = () => { }) { }, report) {
let mongourl = 'mongodb://'; let mongourl = 'mongodb://';
if (username) mongourl += `${username}:${password}@`; if (username) mongourl += `${username}:${password}@`;
mongourl += `${host}:${port}/${name}`; mongourl += `${host}:${port}/${name}`;
@@ -1,58 +0,0 @@
{% extends "layout/basic.html" %}
{% block content %}
<div class="row">
<div class="medium-9 columns">
<div class="section">
<div class="section__body">
<form method="post">
<div class="row">
{{ form.form_text({
columns:8,
label:'Source Database Host',
name:'host',
value:'127.0.0.1',
autofocus:true,
row:false
}) }}
{{ form.form_text({
columns:4,
label:'Source Database Port',
name:'post',
value:'27017',
row:false
}) }}
</div><div class="row">
{{ form.form_text({
columns:4,
label:'Source Database Name',
name:'name',
value:'vijos',
row:false
}) }}
{{ form.form_text({
columns:4,
label:'Source Database Username',
name:'username',
placeholder:'Leave empty if none.',
row:false
}) }}
{{ form.form_text({
columns:4,
label:'Source Database Password',
name:'password',
placeholder:'Leave empty if none.',
row:false
}) }}
</div>
<div class="row"><div class="columns">
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
<button type="submit" class="rounded primary button">
{{ _('Migrate') }}
</button>
</div></div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
+4
View File
@@ -17,7 +17,11 @@
"koa-static-cache": "^5.1.3", "koa-static-cache": "^5.1.3",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"markdown-it": "^10.0.0", "markdown-it": "^10.0.0",
"markdown-it-container": "^3.0.0",
"markdown-it-footnote": "^3.0.2",
"markdown-it-imsize": "^2.0.1",
"markdown-it-katex": "^2.0.3", "markdown-it-katex": "^2.0.3",
"markdown-it-mark": "^3.0.0",
"moment-timezone": "^0.5.31", "moment-timezone": "^0.5.31",
"mongodb": "^3.5.7", "mongodb": "^3.5.7",
"nodemailer": "^6.3.1", "nodemailer": "^6.3.1",
+1 -1
View File
@@ -28,7 +28,7 @@
label:'Rated', label:'Rated',
name:'rated', name:'rated',
placeholder:_('rated'), placeholder:_('rated'),
value:true, value:tdoc.rated|default(true),
row:false row:false
}) }} }) }}
</div><div class="row"> </div><div class="row">
+17 -17
View File
@@ -24,21 +24,21 @@ Hydro 评测机使用进程虚拟内存与物理内存总和计算内存空间
# Judge Status # Judge Status
- <span class="record-status--text pending">Waiting</span> 评测:评测请求正在等待被评测机抓取 - :::record-pending Waiting::: 评测:评测请求正在等待被评测机抓取
- <span class="record-status--text progress">Fetched</span> 评测:评测请求已被评测机抓取,正在准备开始评测 - :::record-progress Fetched::: 评测:评测请求已被评测机抓取,正在准备开始评测
- <span class="record-status--text progress">Compiling</span> 评测:正在编译中 - :::record-progress Compiling::: 评测:正在编译中
- <span class="record-status--text progress">Judging</span> 评测:编译成功,正在评测中 - :::record-progress Judging::: 评测:编译成功,正在评测中
- <span class="record-status--text pass">Accepted</span> 通过:程序输出完全正确 - :::record-pass Accepted::: 通过:程序输出完全正确
- <span class="record-status--text fail">Wrong Answer</span> 不通过:程序输出与标准答案不一致(不包括行末空格以及文件末空行) - :::record-fail Wrong Answer::: 不通过:程序输出与标准答案不一致(不包括行末空格以及文件末空行)
- <span class="record-status--text fail">Time Exceeded</span> 不通过:程序运行时间超过了题目限制 - :::record-fail Time Exceeded::: 不通过:程序运行时间超过了题目限制
- <span class="record-status--text fail">Memory Exceeded</span> 不通过:程序运行内存空间超过了题目限制 - :::record-fail Memory Exceeded::: 不通过:程序运行内存空间超过了题目限制
- <span class="record-status--text fail">Runtime Error</span> 不通过:程序运行时错误(如数组越界、被零除、运算溢出、栈溢出、无效指针等) - :::record-fail Runtime Error::: 不通过:程序运行时错误(如数组越界、被零除、运算溢出、栈溢出、无效指针等)
- <span class="record-status--text fail">Compile Error</span> 不通过:编译失败 - :::record-fail Compile Error::: 不通过:编译失败
- <span class="record-status--text fail">System Error</span> 错误:系统错误(如果您遇到此问题,请及时在讨论区进行反馈) - :::record-fail System Error::: 错误:系统错误(如果您遇到此问题,请及时在讨论区进行反馈)
- <span class="record-status--text ignored">Canceled</span> 其他:评测被取消 - :::record-ignored Canceled::: 其他:评测被取消
- <span class="record-status--text fail">Unknown Error</span> 其他:未知错误 - :::record-fail Unknown Error::: 其他:未知错误
<blockquote class="note">有“成绩取消”字样则说明管理员手动标记此记录为取消,可能违反了服务条款,比如代码被发现与其他用户的代码十分相似。</blockquote> :::note 有“成绩取消”字样则说明管理员手动标记此记录为取消,可能违反了服务条款,比如代码被发现与其他用户的代码十分相似。:::
# Compile Error # Compile Error
@@ -49,7 +49,7 @@ Hydro 评测机使用进程虚拟内存与物理内存总和计算内存空间
- 对于 C/C++:见下 - 对于 C/C++:见下
- 一般性的编译错误 - 一般性的编译错误
<blockquote class="warn"> :::warn
对 C/C++ 选手的特别提醒: 对 C/C++ 选手的特别提醒:
<ul> <ul>
<li>1. __int64 在 GNU C++ 中应写成 long long 类型</li> <li>1. __int64 在 GNU C++ 中应写成 long long 类型</li>
@@ -58,7 +58,7 @@ Hydro 评测机使用进程虚拟内存与物理内存总和计算内存空间
<li>4. itoa 不是一个通用 ANSI 函数(标准 C/C++ 中无此函数)</li> <li>4. itoa 不是一个通用 ANSI 函数(标准 C/C++ 中无此函数)</li>
<li>5. printf 中使用 %lf 格式是不正确的</li> <li>5. printf 中使用 %lf 格式是不正确的</li>
</ul> </ul>
</blockquote> :::
# Training # Training
@@ -82,7 +82,7 @@ ACM/ICPC 赛制排名规则为:通过题目数多的排在前面,通过题
Hydro RP 分为固定 RP 与浮动 RP ,固定 RP 可由活动、比赛等的奖励获得,浮动 RP 由一般性通过题目获得。 Hydro RP 分为固定 RP 与浮动 RP ,固定 RP 可由活动、比赛等的奖励获得,浮动 RP 由一般性通过题目获得。
浮动 RP 是一个动态的数值,每个用户在每道题上获得的 RP 都不一样,并且未来也不固定(即浮动之意)。 浮动 RP 是一个动态的数值,每个用户在每道题上获得的 RP 都不一样,并且未来也不固定(即浮动之意)。
<blockquote class="note">每道题的浮动 RP 由该题通过总人数与该用户通过此题的名次计算得来。</blockquote> :::note 每道题的浮动 RP 由该题通过总人数与该用户通过此题的名次计算得来。:::
# Dataset Format # Dataset Format
+20
View File
@@ -2433,6 +2433,21 @@ map-visit@^1.0.0:
dependencies: dependencies:
object-visit "^1.0.0" object-visit "^1.0.0"
markdown-it-container@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/markdown-it-container/-/markdown-it-container-3.0.0.tgz#1d19b06040a020f9a827577bb7dbf67aa5de9a5b"
integrity sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw==
markdown-it-footnote@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/markdown-it-footnote/-/markdown-it-footnote-3.0.2.tgz#1575ee7a093648d4e096aa33386b058d92ac8bc1"
integrity sha512-JVW6fCmZWjvMdDQSbOT3nnOQtd9iAXmw7hTSh26+v42BnvXeVyGMDBm5b/EZocMed2MbCAHiTX632vY0FyGB8A==
markdown-it-imsize@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/markdown-it-imsize/-/markdown-it-imsize-2.0.1.tgz#cca0427905d05338a247cb9ca9d968c5cddd5170"
integrity sha1-zKBCeQXQUziiR8ucqdloxc3dUXA=
markdown-it-katex@^2.0.3: markdown-it-katex@^2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/markdown-it-katex/-/markdown-it-katex-2.0.3.tgz#d7b86a1aea0b9d6496fab4e7919a18fdef589c39" resolved "https://registry.yarnpkg.com/markdown-it-katex/-/markdown-it-katex-2.0.3.tgz#d7b86a1aea0b9d6496fab4e7919a18fdef589c39"
@@ -2440,6 +2455,11 @@ markdown-it-katex@^2.0.3:
dependencies: dependencies:
katex "^0.6.0" katex "^0.6.0"
markdown-it-mark@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/markdown-it-mark/-/markdown-it-mark-3.0.0.tgz#27c3e39ef3cc310b2dde5375082c9fa912983cda"
integrity sha512-HqMWeKfMMOu4zBO0emmxsoMWmbf2cPKZY1wP6FsTbKmicFfp5y4L3KXAsNeO1rM6NTJVOrNlLKMPjWzriBGspw==
markdown-it@^10.0.0: markdown-it@^10.0.0:
version "10.0.0" version "10.0.0"
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc" resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc"