From a29b801e72408386698fda56234376868c68ae0a Mon Sep 17 00:00:00 2001 From: undefined Date: Tue, 16 Jun 2020 12:10:55 +0800 Subject: [PATCH] =?UTF-8?q?markdown=E8=AF=AD=E6=B3=95=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=EF=BC=8C=E9=94=99=E8=AF=AF=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hydro/handler/contest.js | 22 +++---- hydro/handler/homework.js | 6 +- hydro/handler/judge.js | 25 ++++---- hydro/handler/manage.js | 47 ++++++++++++++- hydro/handler/record.js | 23 +++++--- hydro/lib/markdown.js | 57 +++++++++++++++++- hydro/lib/paginate.js | 6 ++ hydro/model/contest.js | 3 +- hydro/model/opcount.js | 6 +- hydro/model/problem.js | 15 +++-- hydro/model/record.js | 34 ++++++----- hydro/script/recalcRating.js | 33 +++++++++-- hydro/service/server.js | 51 +++++++++++----- hydro/utils.js | 15 +++-- module/migrate-vijos/handler.js | 33 ----------- module/migrate-vijos/lib.js | 1 - module/migrate-vijos/script.js | 16 +++-- .../migrate-vijos/template/migrate_vijos.html | 58 ------------------- package.json | 4 ++ templates/contest_edit.html | 2 +- wiki/help.md | 34 +++++------ yarn.lock | 20 +++++++ 22 files changed, 311 insertions(+), 200 deletions(-) delete mode 100644 module/migrate-vijos/handler.js delete mode 100644 module/migrate-vijos/template/migrate_vijos.html diff --git a/hydro/handler/contest.js b/hydro/handler/contest.js index 6d13a0ec4..bd8cc1dd2 100644 --- a/hydro/handler/contest.js +++ b/hydro/handler/contest.js @@ -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 }); } } diff --git a/hydro/handler/homework.js b/hydro/handler/homework.js index b1f26cd96..b698c6b47 100644 --- a/hydro/handler/homework.js +++ b/hydro/handler/homework.js @@ -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(); diff --git a/hydro/handler/judge.js b/hydro/handler/judge.js index 800ed36ff..178b76794 100644 --- a/hydro/handler/judge.js +++ b/hydro/handler/judge.js @@ -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); } diff --git a/hydro/handler/manage.js b/hydro/handler/manage.js index e7b4b750f..e62905aa0 100644 --- a/hydro/handler/manage.js +++ b/hydro/handler/manage.js @@ -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 }); } } diff --git a/hydro/handler/record.js b/hydro/handler/record.js index cdbf03d60..ae8c18ec0 100644 --- a/hydro/handler/record.js +++ b/hydro/handler/record.js @@ -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 }); + } } } diff --git a/hydro/lib/markdown.js b/hydro/lib/markdown.js index 28dbd3244..9f39ae791 100644 --- a/hydro/lib/markdown.js +++ b/hydro/lib/markdown.js @@ -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: ![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'); 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: ['
', '
'], + warn: ['
', '
'], + 'record-pending': ['', ''], + 'record-progress': ['', ''], + 'record-fail': ['', ''], + 'record-pass': ['', ''], + 'record-ignored': ['', ''], + }; + 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]}]`; + }, + }); } } diff --git a/hydro/lib/paginate.js b/hydro/lib/paginate.js index be39a4bca..d970ced74 100644 --- a/hydro/lib/paginate.js +++ b/hydro/lib/paginate.js @@ -1,5 +1,11 @@ const { ValidationError } = require('../error'); +/** + * @param {import('mongodb').Cursor} cursor + * @param {number} page + * @param {number} pageSize + * @returns {Promise} pageDocs numPages count + */ async function paginate(cursor, page, pageSize) { if (page <= 0) throw new ValidationError('page'); const [count, pageDocs] = await Promise.all([ diff --git a/hydro/model/contest.js b/hydro/model/contest.js index bab43292e..01924f21b 100644 --- a/hydro/model/contest.js +++ b/hydro/model/contest.js @@ -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; } }; diff --git a/hydro/model/opcount.js b/hydro/model/opcount.js index f10546528..796d6b135 100644 --- a/hydro/model/opcount.js +++ b/hydro/model/opcount.js @@ -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 }; diff --git a/hydro/model/problem.js b/hydro/model/problem.js index dd6138837..6a58c4e81 100644 --- a/hydro/model/problem.js +++ b/hydro/model/problem.js @@ -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} */ -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} */ 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); diff --git a/hydro/model/record.js b/hydro/model/record.js index 276f17ff6..85483b8c4 100644 --- a/hydro/model/record.js +++ b/hydro/model/record.js @@ -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, diff --git a/hydro/script/recalcRating.js b/hydro/script/recalcRating.js index f7019ec5a..60fd52e94 100644 --- a/hydro/script/recalcRating.js +++ b/hydro/script/recalcRating.js @@ -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) { diff --git a/hydro/service/server.js b/hydro/service/server.js index 8ef00e9b4..7073bc1cf 100644 --- a/hydro/service/server.js +++ b/hydro/service/server.js @@ -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); diff --git a/hydro/utils.js b/hydro/utils.js index c526df49d..4206b9c48 100644 --- a/hydro/utils.js +++ b/hydro/utils.js @@ -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); }; /** diff --git a/module/migrate-vijos/handler.js b/module/migrate-vijos/handler.js deleted file mode 100644 index 75a3818d8..000000000 --- a/module/migrate-vijos/handler.js +++ /dev/null @@ -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; diff --git a/module/migrate-vijos/lib.js b/module/migrate-vijos/lib.js index 39d455c76..cc56062e3 100644 --- a/module/migrate-vijos/lib.js +++ b/module/migrate-vijos/lib.js @@ -1,4 +1,3 @@ - const { md5, sha1 } = global.Hydro.lib; global.Hydro.lib['hash.vj2'] = module.exports = { diff --git a/module/migrate-vijos/script.js b/module/migrate-vijos/script.js index 0a7bcf338..a65d0d240 100644 --- a/module/migrate-vijos/script.js +++ b/module/migrate-vijos/script.js @@ -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}`; diff --git a/module/migrate-vijos/template/migrate_vijos.html b/module/migrate-vijos/template/migrate_vijos.html deleted file mode 100644 index 5f7d4bebd..000000000 --- a/module/migrate-vijos/template/migrate_vijos.html +++ /dev/null @@ -1,58 +0,0 @@ -{% extends "layout/basic.html" %} -{% block content %} -
-
-
-
-
-
- {{ 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 - }) }} -
- {{ 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 - }) }} -
-
- - -
-
-
-
-
-
-{% endblock %} diff --git a/package.json b/package.json index 0522d57f0..8b25b1dd8 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/templates/contest_edit.html b/templates/contest_edit.html index 657066721..75b1ce94f 100644 --- a/templates/contest_edit.html +++ b/templates/contest_edit.html @@ -28,7 +28,7 @@ label:'Rated', name:'rated', placeholder:_('rated'), - value:true, + value:tdoc.rated|default(true), row:false }) }}
diff --git a/wiki/help.md b/wiki/help.md index 1ae2cdd9d..5c365eb82 100644 --- a/wiki/help.md +++ b/wiki/help.md @@ -24,21 +24,21 @@ Hydro 评测机使用进程虚拟内存与物理内存总和计算内存空间 # Judge Status -- Waiting 评测:评测请求正在等待被评测机抓取 -- Fetched 评测:评测请求已被评测机抓取,正在准备开始评测 -- Compiling 评测:正在编译中 -- Judging 评测:编译成功,正在评测中 -- Accepted 通过:程序输出完全正确 -- Wrong Answer 不通过:程序输出与标准答案不一致(不包括行末空格以及文件末空行) -- Time Exceeded 不通过:程序运行时间超过了题目限制 -- Memory Exceeded 不通过:程序运行内存空间超过了题目限制 -- Runtime Error 不通过:程序运行时错误(如数组越界、被零除、运算溢出、栈溢出、无效指针等) -- Compile Error 不通过:编译失败 -- System Error 错误:系统错误(如果您遇到此问题,请及时在讨论区进行反馈) -- Canceled 其他:评测被取消 -- Unknown Error 其他:未知错误 +- :::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::: 其他:未知错误 -
有“成绩取消”字样则说明管理员手动标记此记录为取消,可能违反了服务条款,比如代码被发现与其他用户的代码十分相似。
+:::note 有“成绩取消”字样则说明管理员手动标记此记录为取消,可能违反了服务条款,比如代码被发现与其他用户的代码十分相似。::: # Compile Error @@ -49,7 +49,7 @@ Hydro 评测机使用进程虚拟内存与物理内存总和计算内存空间 - 对于 C/C++:见下 - 一般性的编译错误 -
+:::warn 对 C/C++ 选手的特别提醒:
  • 1. __int64 在 GNU C++ 中应写成 long long 类型
  • @@ -58,7 +58,7 @@ Hydro 评测机使用进程虚拟内存与物理内存总和计算内存空间
  • 4. itoa 不是一个通用 ANSI 函数(标准 C/C++ 中无此函数)
  • 5. printf 中使用 %lf 格式是不正确的
-
+::: # Training @@ -82,7 +82,7 @@ ACM/ICPC 赛制排名规则为:通过题目数多的排在前面,通过题 Hydro RP 分为固定 RP 与浮动 RP ,固定 RP 可由活动、比赛等的奖励获得,浮动 RP 由一般性通过题目获得。 浮动 RP 是一个动态的数值,每个用户在每道题上获得的 RP 都不一样,并且未来也不固定(即浮动之意)。 -
每道题的浮动 RP 由该题通过总人数与该用户通过此题的名次计算得来。
+:::note 每道题的浮动 RP 由该题通过总人数与该用户通过此题的名次计算得来。::: # Dataset Format diff --git a/yarn.lock b/yarn.lock index 56c052c14..54e7964bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"