mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 17:36:08 +08:00
markdown语法增强,错误修复
This commit is contained in:
1 parent
4b3c10b4eb
commit
a29b801e72
22 files changed
+311
-200
No files matched your search
+12
-10
@@ -147,25 +147,27 @@ class ContestEditHandler extends ContestDetailHandler {
|
||||
}
|
||||
|
||||
async post({
|
||||
domainId, beginAtDate, beginAtTime, duration, title, content, rule, pids,
|
||||
domainId, beginAtDate, beginAtTime, duration, title, content, rule, pids, rated = false,
|
||||
}) {
|
||||
let beginAt;
|
||||
try {
|
||||
beginAt = moment.tz(`${beginAtDate} ${beginAtTime}`, this.uset.timeZone);
|
||||
beginAt = moment.tz(`${beginAtDate} ${beginAtTime}`, this.user.timeZone);
|
||||
} catch (e) {
|
||||
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');
|
||||
beginAt = beginAt.toDate();
|
||||
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
|
||||
|| Array.isDiff(this.tdoc.pids, pids) || this.tdoc.rule !== rule) {
|
||||
await contest.recalcStatus(this.tdoc.docId);
|
||||
}
|
||||
if (this.preferJson) this.response.body = { tid: this.tdoc.docId };
|
||||
else this.response.redirect = `/c/${this.tdoc.docId}`;
|
||||
this.response.body = { tid: 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);
|
||||
if (!this.canShowRecord(this.tdoc)) {
|
||||
this.response.body = { tid };
|
||||
this.response.redirect = `/c/${tid}`;
|
||||
this.response.redirect = this.url('contest_detail', { tid });
|
||||
} else {
|
||||
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) {
|
||||
throw new ValidationError('beginAtDate', 'beginAtTime');
|
||||
}
|
||||
const endAt = beginAt.add(duration, 'hours');
|
||||
const endAt = beginAt.clone().add(duration, 'hours');
|
||||
pids = await this.verifyProblems(domainId, pids);
|
||||
const tid = await contest.add(
|
||||
domainId, title, content,
|
||||
this.user._id, rule, beginAt, endAt, pids, rated,
|
||||
);
|
||||
this.response.body = { tid };
|
||||
this.response.redirect = `/c/${tid}`;
|
||||
this.response.redirect = this.url('contest_detail', { tid });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler {
|
||||
class HomeworkCreateHandler extends HomeworkHandler {
|
||||
async get() {
|
||||
const beginAt = moment().add(1, 'day');
|
||||
const penaltySince = beginAt.add(7, 'days');
|
||||
const penaltySince = beginAt.clone().add(7, 'days');
|
||||
const path = [
|
||||
['Hydro', 'homepage'],
|
||||
['homework_main', 'homework_main'],
|
||||
@@ -212,7 +212,7 @@ class HomeworkCreateHandler extends HomeworkHandler {
|
||||
} catch (e) {
|
||||
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 (penaltySince.isAfter(endAt)) throw new ValidationError('extensionDays');
|
||||
penaltySince = penaltySince.toDate();
|
||||
@@ -273,7 +273,7 @@ class HomeworkEditHandler extends HomeworkHandler {
|
||||
} catch (e) {
|
||||
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');
|
||||
await this.verifyProblems(domainId, pids);
|
||||
beginAt = beginAt.toDate();
|
||||
|
||||
+15
-10
@@ -38,14 +38,17 @@ async function next(body) {
|
||||
const $set = {};
|
||||
const $push = {};
|
||||
if (body.case) {
|
||||
body.case.memory = body.case.memory_kb;
|
||||
body.case.time = body.case.time_ms;
|
||||
rdoc.testCases.push(body.case);
|
||||
$push.testCases = body.case;
|
||||
const c = {};
|
||||
c.memory = body.case.memory_kb || body.case.memory;
|
||||
c.time = body.case.time_ms || body.case.time;
|
||||
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) {
|
||||
rdoc.judgeTexts.push(body.judge_text);
|
||||
$push.judgeTexts = body.judge_text;
|
||||
if (body.judge_text || body.message) {
|
||||
rdoc.judgeTexts.push(body.judge_text || body.message);
|
||||
$push.judgeTexts = body.judge_text || body.message;
|
||||
}
|
||||
if (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.score) $set.score = body.score;
|
||||
if (body.time_ms) $set.time = body.time_ms;
|
||||
if (body.memory_kb) $set.memory = body.memory_kb;
|
||||
if (body.time_ms) $set.time = body.time_ms || body.time;
|
||||
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);
|
||||
bus.publish('record_change', rdoc);
|
||||
}
|
||||
@@ -63,6 +67,7 @@ async function end(body) {
|
||||
let rdoc = await record.get(body.domainId, body.rid);
|
||||
const $set = {};
|
||||
const $push = {};
|
||||
const $unset = { progress: '' };
|
||||
if (body.judge_text) {
|
||||
rdoc.judgeTexts.push(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;
|
||||
$set.judgeAt = new Date();
|
||||
$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);
|
||||
rdoc = await record.update(body.domainId, body.rid, $set, $push);
|
||||
}
|
||||
|
||||
+44
-3
@@ -1,5 +1,8 @@
|
||||
const setting = require('../model/setting');
|
||||
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 hpm = require('../lib/hpm');
|
||||
const loader = require('../loader');
|
||||
@@ -56,11 +59,49 @@ class SystemScriptHandler extends SystemHandler {
|
||||
this.response.body.path.push(['manage_script', null]);
|
||||
}
|
||||
|
||||
async post({ id, args = '{}' }) {
|
||||
async post({ domainId, id, args = '{}' }) {
|
||||
args = JSON.parse(args);
|
||||
// TODO Do not use console.log
|
||||
await global.Hydro.script[id].run(args, console.log);
|
||||
this.back();
|
||||
const rid = await record.add(domainId, {
|
||||
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
@@ -5,6 +5,7 @@ const record = require('../model/record');
|
||||
const contest = require('../model/contest');
|
||||
const system = require('../model/system');
|
||||
const user = require('../model/user');
|
||||
const paginate = require('../lib/paginate');
|
||||
const bus = require('../service/bus');
|
||||
const {
|
||||
Route, Handler, Connection, ConnectionHandler,
|
||||
@@ -25,7 +26,11 @@ class RecordListHandler extends RecordHandler {
|
||||
];
|
||||
}
|
||||
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([
|
||||
user.getList(domainId, rdocs.map((rdoc) => rdoc.uid)),
|
||||
problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid), false),
|
||||
@@ -59,7 +64,7 @@ class RecordDetailHandler extends RecordHandler {
|
||||
rdoc.code = null;
|
||||
}
|
||||
const [pdoc, udoc] = await Promise.all([
|
||||
problem.get(domainId, rdoc.pid),
|
||||
problem.get(domainId, rdoc.pid, null, false),
|
||||
user.getById(domainId, rdoc.uid),
|
||||
]);
|
||||
this.response.body = {
|
||||
@@ -101,13 +106,13 @@ class RecordMainConnectionHandler extends RecordConnectionHandler {
|
||||
}
|
||||
|
||||
async message(msg) {
|
||||
const rdocs = await record.getMany(
|
||||
this.domainId,
|
||||
{ _id: { $in: msg.rids } },
|
||||
{ _id: -1 }, 1, 100,
|
||||
);
|
||||
for (const rdoc of rdocs) {
|
||||
this.onRecordChange({ value: rdoc });
|
||||
if (msg.rids instanceof Array) {
|
||||
const rdocs = await record.getMulti(
|
||||
this.domainId, { _id: { $in: msg.rids } },
|
||||
).toArray();
|
||||
for (const rdoc of rdocs) {
|
||||
this.onRecordChange({ value: rdoc });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+56
-1
@@ -1,6 +1,29 @@
|
||||
const MarkdownIt = require('markdown-it');
|
||||
const katex = require('markdown-it-katex');
|
||||
|
||||
const md = MarkdownIt();
|
||||
const Prism = require('prismjs');
|
||||
|
||||
// For math: $a\times b\eq 10$
|
||||
const katex = require('markdown-it-katex');
|
||||
// Specific image size: 
|
||||
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');
|
||||
|
||||
class Markdown extends MarkdownIt {
|
||||
@@ -19,6 +42,38 @@ class Markdown extends MarkdownIt {
|
||||
});
|
||||
this.linkify.tlds('.py', false);
|
||||
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]}]`;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
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) {
|
||||
if (page <= 0) throw new ValidationError('page');
|
||||
const [count, pageDocs] = await Promise.all([
|
||||
|
||||
@@ -348,7 +348,8 @@ const ContestHandlerMixin = (c) => class extends c {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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 };
|
||||
@@ -44,13 +44,16 @@ async function add(domainId, title, content, owner, {
|
||||
* @param {string} domainId
|
||||
* @param {string|number} pid
|
||||
* @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)
|
||||
? await document.get(domainId, document.TYPE_PROBLEM, pid)
|
||||
: (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) {
|
||||
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 {number} page
|
||||
* @param {number} limit
|
||||
* @returns {Pdoc[]}
|
||||
* @returns {Promise<Pdoc[]>}
|
||||
*/
|
||||
function getMany(domainId, query, sort, page, 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) {
|
||||
pids = new Set(pids);
|
||||
pids = Array.from(new Set(pids));
|
||||
const r = {};
|
||||
const pdocs = await document.getMulti(
|
||||
domainId, document.TYPE_PROBLEM,
|
||||
@@ -125,7 +128,7 @@ async function getList(domainId, pids, doThrow = true) {
|
||||
for (const pdoc of pdocs) {
|
||||
r[pdoc.docId] = r[pdoc.pid] = pdoc;
|
||||
}
|
||||
if (pdocs.length !== pids.size) {
|
||||
if (pdocs.length !== pids.length) {
|
||||
if (doThrow) {
|
||||
for (const pid of pids) {
|
||||
if (!r[pid]) throw new ProblemNotFoundError(domainId, pid);
|
||||
|
||||
+18
-16
@@ -12,7 +12,7 @@ const coll = db.collection('record');
|
||||
* @param {string} domainId
|
||||
* @param {import('../interface').Record} data
|
||||
*/
|
||||
async function add(domainId, data) {
|
||||
async function add(domainId, data, addTask = true) {
|
||||
_.defaults(data, {
|
||||
status: STATUS_WAITING,
|
||||
domainId,
|
||||
@@ -27,18 +27,20 @@ async function add(domainId, data) {
|
||||
judgeAt: null,
|
||||
});
|
||||
const [pdoc, res] = await Promise.all([
|
||||
problem.get(domainId, data.pid),
|
||||
problem.get(domainId, data.pid, null, false),
|
||||
coll.insertOne(data),
|
||||
]);
|
||||
await task.add({
|
||||
type: 'judge',
|
||||
rid: res.insertedId,
|
||||
domainId,
|
||||
pid: data.pid,
|
||||
data: pdoc.data,
|
||||
lang: data.lang,
|
||||
code: data.code,
|
||||
});
|
||||
if (addTask) {
|
||||
await task.add({
|
||||
type: 'judge',
|
||||
rid: res.insertedId,
|
||||
domainId,
|
||||
pid: data.pid,
|
||||
data: (pdoc || {}).data,
|
||||
lang: data.lang,
|
||||
code: data.code,
|
||||
});
|
||||
}
|
||||
return res.insertedId;
|
||||
}
|
||||
|
||||
@@ -54,16 +56,16 @@ async function get(domainId, rid) {
|
||||
return rdoc;
|
||||
}
|
||||
|
||||
function getMany(domainId, query, sort, page, limit) {
|
||||
return coll.find({ ...query, domainId }).sort(sort).skip((page - 1) * limit).limit(limit)
|
||||
.toArray();
|
||||
function getMulti(domainId, query) {
|
||||
return coll.find({ ...query, domainId });
|
||||
}
|
||||
|
||||
async function update(domainId, rid, $set, $push) {
|
||||
async function update(domainId, rid, $set, $push, $unset) {
|
||||
const _id = new ObjectID(rid);
|
||||
const upd = {};
|
||||
if ($set && Object.keys($set).length) upd.$set = $set;
|
||||
if ($push && Object.keys($push).length) upd.$push = $push;
|
||||
if ($unset && Object.keys($unset).length) upd.$unset = $unset;
|
||||
await coll.findOneAndUpdate({ domainId, _id }, upd);
|
||||
const rdoc = await coll.findOne({ _id });
|
||||
if (!rdoc) throw new RecordNotFoundError(rid);
|
||||
@@ -136,7 +138,7 @@ async function rejudge(domainId, rid) {
|
||||
global.Hydro.model.record = module.exports = {
|
||||
add,
|
||||
get,
|
||||
getMany,
|
||||
getMulti,
|
||||
update,
|
||||
count,
|
||||
reset,
|
||||
|
||||
@@ -3,21 +3,34 @@ const domain = require('../model/domain');
|
||||
const contest = require('../model/contest');
|
||||
const user = require('../model/user');
|
||||
const rating = require('../lib/rating');
|
||||
const { STATUS } = require('../model/builtin');
|
||||
|
||||
async function run({ domainId, isSub = false }, report) {
|
||||
if (!domainId) {
|
||||
const domains = await domain.getMulti().toArray();
|
||||
await report({ total: domains.length });
|
||||
await report({ message: `Found ${domains.length} domains` });
|
||||
for (const i in domains) {
|
||||
const start = new Date().getTime();
|
||||
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;
|
||||
}
|
||||
await user.setMultiInDomain(domainId, {}, { rating: 1500 });
|
||||
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 = {};
|
||||
for (const i in contests) {
|
||||
const start = new Date().getTime();
|
||||
const tdoc = contests[i];
|
||||
const tsdocs = await contest.getMultiStatus(domainId, { docId: tdoc.docId })
|
||||
.sort(contest.RULES[tdoc.rule].statusSort).toArray();
|
||||
@@ -30,8 +43,18 @@ async function run({ domainId, isSub = false }, report) {
|
||||
for (const udoc of rated) {
|
||||
udict[udoc.uid] = udoc.new;
|
||||
}
|
||||
if (isSub) await report({ subProgress: (i + 1) / contests.length });
|
||||
else await report({ current: parseInt(i) + 1 });
|
||||
if (!isSub) {
|
||||
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 = [];
|
||||
for (const uid in udict) {
|
||||
|
||||
+36
-15
@@ -31,6 +31,18 @@ const _validateObjectId = (id, key) => {
|
||||
if (ObjectID.isValid(id)) return new ObjectID(id);
|
||||
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 = {
|
||||
tid: _validateObjectId,
|
||||
@@ -42,6 +54,12 @@ const validate = {
|
||||
psrid: _validateObjectId,
|
||||
docId: _validateObjectId,
|
||||
mongoId: _validateObjectId,
|
||||
hidden: _bool,
|
||||
rated: _bool,
|
||||
category: _splitAndTrim,
|
||||
tag: _splitAndTrim,
|
||||
beginAtDate: _date,
|
||||
beginAtTime: _time,
|
||||
pid: (pid) => (Number.isSafeInteger(parseInt(pid)) ? parseInt(pid) : pid),
|
||||
content: validator.checkContent,
|
||||
title: validator.checkTitle,
|
||||
@@ -54,9 +72,6 @@ const validate = {
|
||||
if (page <= 0) throw new ValidationError('page');
|
||||
return page;
|
||||
},
|
||||
category: (category) => category.split(',').map((i) => i.trim()),
|
||||
tag: (tag) => tag.split(',').map((i) => i.trim()),
|
||||
hidden: (hidden) => !!hidden,
|
||||
duration: (duration) => {
|
||||
if (!Number.isNaN(parseFloat(duration))) duration = parseFloat(duration);
|
||||
if (duration <= 0) throw new ValidationError('duration');
|
||||
@@ -192,18 +207,22 @@ class Handler {
|
||||
}
|
||||
|
||||
url(name, kwargs = {}) { // eslint-disable-line class-methods-use-this
|
||||
delete kwargs.__keywords;
|
||||
if (this.args.domainId !== 'system') {
|
||||
name += '_with_domainId';
|
||||
kwargs.domainId = kwargs.domainId || this.args.domainId;
|
||||
let res = '#';
|
||||
try {
|
||||
delete kwargs.__keywords;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -331,15 +350,17 @@ async function handle(ctx, HandlerClass, permission) {
|
||||
if (h.___prepare) await h.___prepare(args);
|
||||
if (permission) h.checkPerm(permission);
|
||||
|
||||
let checking = '';
|
||||
try {
|
||||
for (const key in validate) {
|
||||
checking = key;
|
||||
if (args[key]) {
|
||||
args[key] = validate[key](args[key], key);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) throw e;
|
||||
throw new ValidationError('Argument check failed');
|
||||
throw new ValidationError(`Argument ${checking} check failed`);
|
||||
}
|
||||
h.args = args;
|
||||
if (h.__prepare) await h.__prepare(args);
|
||||
|
||||
+11
-4
@@ -1,3 +1,5 @@
|
||||
const { hasIn } = require("lodash");
|
||||
|
||||
const dict = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
|
||||
|
||||
String.random = function random(digit) {
|
||||
@@ -19,14 +21,19 @@ Array.isDiff = function isDiff(a, b) {
|
||||
* @returns {string} Formatted date string
|
||||
*/
|
||||
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
|
||||
.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());
|
||||
.replace('%H', h)
|
||||
.replace('%M', m)
|
||||
.replace('%S', s);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,4 +1,3 @@
|
||||
|
||||
const { md5, sha1 } = global.Hydro.lib;
|
||||
|
||||
global.Hydro.lib['hash.vj2'] = module.exports = {
|
||||
|
||||
@@ -3,6 +3,9 @@ const { mongodb } = global.nodeModules;
|
||||
const dst = global.Hydro.service.db;
|
||||
const { file, problem, discussion } = global.Hydro.model;
|
||||
|
||||
// TODO read config file
|
||||
// TODO output enhancement
|
||||
|
||||
const map = {};
|
||||
|
||||
const pid = (id) => {
|
||||
@@ -114,7 +117,6 @@ const tasks = {
|
||||
domain_id: 'domainId',
|
||||
num_accept: 'nAccept',
|
||||
num_submit: 'nSubmit',
|
||||
rp: 'rp',
|
||||
status: 'status',
|
||||
accept: 'accept',
|
||||
vote: 'vote',
|
||||
@@ -259,6 +261,8 @@ async function domainUser(src, report) {
|
||||
num_problems: 'nProblems',
|
||||
domain_id: 'domainId',
|
||||
level: 'level',
|
||||
rp: null,
|
||||
rank: null,
|
||||
};
|
||||
const d = {};
|
||||
for (const key in doc) {
|
||||
@@ -269,7 +273,7 @@ async function domainUser(src, report) {
|
||||
} else if (mapper[key] === null) {
|
||||
// Ignore this key
|
||||
} else {
|
||||
console.log('Unknown key:', key);
|
||||
await report({ message: `Unknown key ${key} in collection domain.user` });
|
||||
}
|
||||
}
|
||||
const docWithoutId = {};
|
||||
@@ -282,7 +286,7 @@ async function domainUser(src, report) {
|
||||
{ _id: d._id },
|
||||
{
|
||||
$set: {
|
||||
...docWithoutId, docType: 0, docId: 1, role: docWithoutId.role || 'default',
|
||||
...docWithoutId, docType: 0, docId: 0, role: docWithoutId.role || 'default',
|
||||
},
|
||||
},
|
||||
{ upsert: true },
|
||||
@@ -349,7 +353,7 @@ async function task(name, src, report) {
|
||||
} else if (mapper[key] === null) {
|
||||
// Ignore this key
|
||||
} 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));
|
||||
}
|
||||
}
|
||||
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)));
|
||||
if (progress > lastProgress) {
|
||||
await report({ progress });
|
||||
@@ -403,7 +407,7 @@ async function task(name, src, report) {
|
||||
|
||||
async function migrateVijos({
|
||||
host, port, name, username, password,
|
||||
}, report = () => { }) {
|
||||
}, report) {
|
||||
let mongourl = 'mongodb://';
|
||||
if (username) mongourl += `${username}:${password}@`;
|
||||
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 %}
|
||||
@@ -17,7 +17,11 @@
|
||||
"koa-static-cache": "^5.1.3",
|
||||
"lodash": "^4.17.15",
|
||||
"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-mark": "^3.0.0",
|
||||
"moment-timezone": "^0.5.31",
|
||||
"mongodb": "^3.5.7",
|
||||
"nodemailer": "^6.3.1",
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
label:'Rated',
|
||||
name:'rated',
|
||||
placeholder:_('rated'),
|
||||
value:true,
|
||||
value:tdoc.rated|default(true),
|
||||
row:false
|
||||
}) }}
|
||||
</div><div class="row">
|
||||
|
||||
+17
-17
@@ -24,21 +24,21 @@ Hydro 评测机使用进程虚拟内存与物理内存总和计算内存空间
|
||||
|
||||
# Judge Status
|
||||
|
||||
- <span class="record-status--text pending">Waiting</span> 评测:评测请求正在等待被评测机抓取
|
||||
- <span class="record-status--text progress">Fetched</span> 评测:评测请求已被评测机抓取,正在准备开始评测
|
||||
- <span class="record-status--text progress">Compiling</span> 评测:正在编译中
|
||||
- <span class="record-status--text progress">Judging</span> 评测:编译成功,正在评测中
|
||||
- <span class="record-status--text pass">Accepted</span> 通过:程序输出完全正确
|
||||
- <span class="record-status--text fail">Wrong Answer</span> 不通过:程序输出与标准答案不一致(不包括行末空格以及文件末空行)
|
||||
- <span class="record-status--text fail">Time Exceeded</span> 不通过:程序运行时间超过了题目限制
|
||||
- <span class="record-status--text fail">Memory Exceeded</span> 不通过:程序运行内存空间超过了题目限制
|
||||
- <span class="record-status--text fail">Runtime Error</span> 不通过:程序运行时错误(如数组越界、被零除、运算溢出、栈溢出、无效指针等)
|
||||
- <span class="record-status--text fail">Compile Error</span> 不通过:编译失败
|
||||
- <span class="record-status--text fail">System Error</span> 错误:系统错误(如果您遇到此问题,请及时在讨论区进行反馈)
|
||||
- <span class="record-status--text ignored">Canceled</span> 其他:评测被取消
|
||||
- <span class="record-status--text fail">Unknown Error</span> 其他:未知错误
|
||||
- :::record-pending Waiting::: 评测:评测请求正在等待被评测机抓取
|
||||
- :::record-progress Fetched::: 评测:评测请求已被评测机抓取,正在准备开始评测
|
||||
- :::record-progress Compiling::: 评测:正在编译中
|
||||
- :::record-progress Judging::: 评测:编译成功,正在评测中
|
||||
- :::record-pass Accepted::: 通过:程序输出完全正确
|
||||
- :::record-fail Wrong Answer::: 不通过:程序输出与标准答案不一致(不包括行末空格以及文件末空行)
|
||||
- :::record-fail Time Exceeded::: 不通过:程序运行时间超过了题目限制
|
||||
- :::record-fail Memory Exceeded::: 不通过:程序运行内存空间超过了题目限制
|
||||
- :::record-fail Runtime Error::: 不通过:程序运行时错误(如数组越界、被零除、运算溢出、栈溢出、无效指针等)
|
||||
- :::record-fail Compile Error::: 不通过:编译失败
|
||||
- :::record-fail System Error::: 错误:系统错误(如果您遇到此问题,请及时在讨论区进行反馈)
|
||||
- :::record-ignored Canceled::: 其他:评测被取消
|
||||
- :::record-fail Unknown Error::: 其他:未知错误
|
||||
|
||||
<blockquote class="note">有“成绩取消”字样则说明管理员手动标记此记录为取消,可能违反了服务条款,比如代码被发现与其他用户的代码十分相似。</blockquote>
|
||||
:::note 有“成绩取消”字样则说明管理员手动标记此记录为取消,可能违反了服务条款,比如代码被发现与其他用户的代码十分相似。:::
|
||||
|
||||
# Compile Error
|
||||
|
||||
@@ -49,7 +49,7 @@ Hydro 评测机使用进程虚拟内存与物理内存总和计算内存空间
|
||||
- 对于 C/C++:见下
|
||||
- 一般性的编译错误
|
||||
|
||||
<blockquote class="warn">
|
||||
:::warn
|
||||
对 C/C++ 选手的特别提醒:
|
||||
<ul>
|
||||
<li>1. __int64 在 GNU C++ 中应写成 long long 类型</li>
|
||||
@@ -58,7 +58,7 @@ Hydro 评测机使用进程虚拟内存与物理内存总和计算内存空间
|
||||
<li>4. itoa 不是一个通用 ANSI 函数(标准 C/C++ 中无此函数)</li>
|
||||
<li>5. printf 中使用 %lf 格式是不正确的</li>
|
||||
</ul>
|
||||
</blockquote>
|
||||
:::
|
||||
|
||||
# Training
|
||||
|
||||
@@ -82,7 +82,7 @@ ACM/ICPC 赛制排名规则为:通过题目数多的排在前面,通过题
|
||||
Hydro RP 分为固定 RP 与浮动 RP ,固定 RP 可由活动、比赛等的奖励获得,浮动 RP 由一般性通过题目获得。
|
||||
浮动 RP 是一个动态的数值,每个用户在每道题上获得的 RP 都不一样,并且未来也不固定(即浮动之意)。
|
||||
|
||||
<blockquote class="note">每道题的浮动 RP 由该题通过总人数与该用户通过此题的名次计算得来。</blockquote>
|
||||
:::note 每道题的浮动 RP 由该题通过总人数与该用户通过此题的名次计算得来。:::
|
||||
|
||||
# Dataset Format
|
||||
|
||||
|
||||
@@ -2433,6 +2433,21 @@ map-visit@^1.0.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "2.0.3"
|
||||
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:
|
||||
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:
|
||||
version "10.0.0"
|
||||
resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc"
|
||||
|
||||
Reference in new issue
Block a user