mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 17:36:08 +08:00
导出比赛代码功能
This commit is contained in:
1 parent
b54c22cb84
commit
9247b71b5f
11 files changed
+123
-69
No files matched your search
@@ -1,9 +1,14 @@
|
||||
const moment = require('moment-timezone');
|
||||
const { ObjectID } = require('bson');
|
||||
const AdmZip = require('adm-zip');
|
||||
const {
|
||||
ContestNotLiveError, ValidationError, ProblemNotFoundError,
|
||||
ContestNotAttendedError,
|
||||
} = require('../error');
|
||||
const { PERM_CREATE_CONTEST, PERM_EDIT_CONTEST } = require('../permission');
|
||||
const {
|
||||
PERM_CREATE_CONTEST, PERM_EDIT_CONTEST, PERM_READ_RECORD_CODE,
|
||||
PERM_VIEW_CONTEST,
|
||||
} = require('../permission');
|
||||
const paginate = require('../lib/paginate');
|
||||
const contest = require('../model/contest');
|
||||
const document = require('../model/document');
|
||||
@@ -259,6 +264,30 @@ class ContestProblemSubmitHandler extends ContestProblemHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class ContestCodeHandler extends ContestDetailHandler {
|
||||
async get({ domainId, tid }) {
|
||||
this.checkPerm(PERM_READ_RECORD_CODE);
|
||||
this.limitRate('homework_code', 3600, 60);
|
||||
const [tdoc, tsdocs] = await contest.getAndListStatus(domainId, tid);
|
||||
const rnames = {};
|
||||
for (const tsdoc of tsdocs) {
|
||||
for (const pdetail of tsdoc.detail || []) {
|
||||
rnames[pdetail.rid] = `U${tsdoc.uid}_P${pdetail.pid}_R${pdetail.rid}`;
|
||||
}
|
||||
}
|
||||
const zip = new AdmZip();
|
||||
const rdocs = await record.getMulti(domainId, {
|
||||
_id: {
|
||||
$in: Array.from(Object.keys(rnames)).map((id) => new ObjectID(id)),
|
||||
},
|
||||
}).toArray();
|
||||
for (const rdoc of rdocs) {
|
||||
zip.addFile(`${rnames[rdoc._id]}.${rdoc.lang}`, rdoc.code);
|
||||
}
|
||||
await this.binary(zip.toBuffer(), `${tdoc.title}.zip`);
|
||||
}
|
||||
}
|
||||
|
||||
class ContestCreateHandler extends ContestHandler {
|
||||
async get() {
|
||||
this.response.template = 'contest_edit.html';
|
||||
@@ -302,13 +331,14 @@ class ContestCreateHandler extends ContestHandler {
|
||||
}
|
||||
|
||||
async function apply() {
|
||||
Route('contest_main', '/contest', ContestListHandler);
|
||||
Route('contest_detail', '/contest/:tid', ContestDetailHandler);
|
||||
Route('contest_edit', '/contest/:tid/edit', ContestEditHandler);
|
||||
Route('contest_scoreboard', '/contest/:tid/scoreboard', ContestScoreboardHandler);
|
||||
Route('contest_scoreboard_download', '/contest/:tid/export/:ext', ContestScoreboardDownloadHandler);
|
||||
Route('contest_detail_problem', '/contest/:tid/p/:pid', ContestProblemHandler);
|
||||
Route('contest_detail_problem_submit', '/contest/:tid/p/:pid/submit', ContestProblemSubmitHandler);
|
||||
Route('contest_main', '/contest', ContestListHandler, PERM_VIEW_CONTEST);
|
||||
Route('contest_detail', '/contest/:tid', ContestDetailHandler, PERM_VIEW_CONTEST);
|
||||
Route('contest_edit', '/contest/:tid/edit', ContestEditHandler, PERM_VIEW_CONTEST);
|
||||
Route('contest_scoreboard', '/contest/:tid/scoreboard', ContestScoreboardHandler, PERM_VIEW_CONTEST);
|
||||
Route('contest_scoreboard_download', '/contest/:tid/export/:ext', ContestScoreboardDownloadHandler, PERM_VIEW_CONTEST);
|
||||
Route('contest_detail_problem', '/contest/:tid/p/:pid', ContestProblemHandler, PERM_VIEW_CONTEST);
|
||||
Route('contest_detail_problem_submit', '/contest/:tid/p/:pid/submit', ContestProblemSubmitHandler, PERM_VIEW_CONTEST);
|
||||
Route('contest_code', '/contest/:tid/code', ContestCodeHandler, PERM_VIEW_CONTEST);
|
||||
Route('contest_create', '/contest/create', ContestCreateHandler, PERM_CREATE_CONTEST);
|
||||
}
|
||||
|
||||
|
||||
+32
-31
@@ -1,5 +1,7 @@
|
||||
const yaml = require('js-yaml');
|
||||
const moment = require('moment-timezone');
|
||||
const { ObjectID } = require('bson');
|
||||
const AdmZip = require('adm-zip');
|
||||
const {
|
||||
ValidationError, HomeworkNotLiveError, ProblemNotFoundError,
|
||||
HomeworkNotAttendedError,
|
||||
@@ -7,7 +9,7 @@ const {
|
||||
const {
|
||||
PERM_VIEW_HOMEWORK, PERM_ATTEND_HOMEWORK, PERM_VIEW_PROBLEM,
|
||||
PERM_SUBMIT_PROBLEM, PERM_CREATE_HOMEWORK, PERM_EDIT_HOMEWORK,
|
||||
PERM_VIEW_HOMEWORK_SCOREBOARD,
|
||||
PERM_VIEW_HOMEWORK_SCOREBOARD, PERM_READ_RECORD_CODE,
|
||||
} = require('../permission');
|
||||
const { Route, Handler } = require('../service/server');
|
||||
const system = require('../model/system');
|
||||
@@ -221,7 +223,7 @@ class HomeworkCreateHandler extends HomeworkHandler {
|
||||
'homework', beginAt.toDate(), endAt.toDate(), pids, rated,
|
||||
{ penaltySince, penaltyRules }, document.TYPE_HOMEWORK);
|
||||
this.response.body = { tid };
|
||||
this.response.redirect = `/homework/${tid}`;
|
||||
this.response.redirect = this.url('homework_detail', { tid });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,7 +291,7 @@ class HomeworkEditHandler extends HomeworkHandler {
|
||||
await contest.recalcStatus(domainId, document.TYPE_HOMEWORK, tdoc.docId);
|
||||
}
|
||||
this.response.body = { tid };
|
||||
this.response.redirect = `/homework/${tid}`;
|
||||
this.response.redirect = this.url('homework_detail', { tid });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,6 +325,32 @@ class HomeworkScoreboardDownloadHandler extends HomeworkHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class HomeworkCodeHandler extends HomeworkHandler {
|
||||
async get({ domainId, tid }) {
|
||||
this.checkPerm(PERM_READ_RECORD_CODE);
|
||||
this.limitRate('homework_code', 3600, 60);
|
||||
const [tdoc, tsdocs] = await contest.getAndListStatus(
|
||||
domainId, tid, document.TYPE_HOMEWORK,
|
||||
);
|
||||
const rnames = {};
|
||||
for (const tsdoc of tsdocs) {
|
||||
for (const pdetail of tsdoc.detail || []) {
|
||||
rnames[pdetail.rid] = `U${tsdoc.uid}_P${pdetail.pid}_R${pdetail.rid}`;
|
||||
}
|
||||
}
|
||||
const zip = new AdmZip();
|
||||
const rdocs = await record.getMulti(domainId, {
|
||||
_id: {
|
||||
$in: Array.from(Object.keys(rnames)).map((id) => new ObjectID(id)),
|
||||
},
|
||||
}).toArray();
|
||||
for (const rdoc of rdocs) {
|
||||
zip.addFile(`${rnames[rdoc._id]}.${rdoc.lang}`, rdoc.code);
|
||||
}
|
||||
await this.binary(zip.toBuffer(), `${tdoc.title}.zip`);
|
||||
}
|
||||
}
|
||||
|
||||
async function apply() {
|
||||
contest.isHomeworkExtended = (tdoc) => {
|
||||
const now = new Date().getTime();
|
||||
@@ -471,35 +499,8 @@ async function apply() {
|
||||
Route('homework_scoreboard', '/homework/:tid/scoreboard/download/:ext', HomeworkScoreboardDownloadHandler, PERM_VIEW_HOMEWORK_SCOREBOARD);
|
||||
Route('homework_detail_problem', '/homework/:tid/p/:pid', HomeworkDetailProblemHandler, PERM_VIEW_HOMEWORK);
|
||||
Route('homework_detail_problem_submit', '/homework/:tid/p/:pid/submit', HomeworkDetailProblemSubmitHandler, PERM_SUBMIT_PROBLEM);
|
||||
Route('homework_code', '/homework/:tid/code', HomeworkCodeHandler, PERM_VIEW_HOMEWORK);
|
||||
Route('homework_edit', '/homework/:tid/edit', HomeworkEditHandler);
|
||||
}
|
||||
|
||||
global.Hydro.handler.homework = module.exports = apply;
|
||||
|
||||
/*
|
||||
@app.route('/homework/{tid:\w{24}}/code', 'homework_code')
|
||||
class HomeworkCodeHandler(base.OperationHandler):
|
||||
@base.limit_rate('homework_code', 3600, 60)
|
||||
@base.route_argument
|
||||
@base.require_perm(builtin.PERM_VIEW_HOMEWORK)
|
||||
@base.require_perm(builtin.PERM_READ_RECORD_CODE)
|
||||
@base.sanitize
|
||||
async def get(this, *, tid: objectid.ObjectId):
|
||||
tdoc, tsdocs = await contest.get_and_list_status(domainId, document.TYPE_HOMEWORK, tid)
|
||||
rnames = {}
|
||||
for tsdoc in tsdocs:
|
||||
for pdetail in tsdoc.get('detail', []):
|
||||
rnames[pdetail['rid']] = 'U{}_P{}_R{}'.format(tsdoc['uid'], pdetail['pid'], pdetail['rid'])
|
||||
output_buffer = io.BytesIO()
|
||||
zip_file = zipfile.ZipFile(output_buffer, 'a', zipfile.ZIP_DEFLATED)
|
||||
rdocs = record.get_multi(get_hidden=True, _id={'$in': list(rnames.keys())})
|
||||
async for rdoc in rdocs:
|
||||
zip_file.writestr(rnames[rdoc['_id']] + '.' + rdoc['lang'], rdoc['code'])
|
||||
# mark all files as created in Windows :p
|
||||
for zfile in zip_file.filelist:
|
||||
zfile.create_system = 0
|
||||
zip_file.close()
|
||||
|
||||
await this.binary(output_buffer.getvalue(), 'application/zip',
|
||||
file_name='{}.zip'.format(tdoc['title']))
|
||||
*/
|
||||
@@ -1,3 +1,4 @@
|
||||
const { ObjectID } = require('bson');
|
||||
const { PERM_READ_RECORD_CODE, PERM_REJUDGE, PERM_VIEW_PROBLEM_HIDDEN } = require('../permission');
|
||||
const { PermissionError } = require('../error');
|
||||
const problem = require('../model/problem');
|
||||
@@ -108,7 +109,7 @@ class RecordMainConnectionHandler extends RecordConnectionHandler {
|
||||
async message(msg) {
|
||||
if (msg.rids instanceof Array) {
|
||||
const rdocs = await record.getMulti(
|
||||
this.domainId, { _id: { $in: msg.rids } },
|
||||
this.domainId, { _id: { $in: msg.rids.map((id) => new ObjectID(id)) } },
|
||||
).toArray();
|
||||
for (const rdoc of rdocs) {
|
||||
this.onRecordChange({ value: rdoc });
|
||||
@@ -150,7 +151,7 @@ class RecordDetailConnectionHandler extends contest.ContestHandlerMixin(Connecti
|
||||
|
||||
async onRecordChange(data) {
|
||||
const rdoc = data.value;
|
||||
if (rdoc._id.toString() !== this.rid) return;
|
||||
if (rdoc._id.toString() !== this.rid.toString()) return;
|
||||
this.send({
|
||||
status_html: await this.renderHTML('record_detail_status.html', { rdoc }),
|
||||
summary_html: await this.renderHTML('record_detail_summary.html', { rdoc }),
|
||||
|
||||
+3
-2
@@ -24,7 +24,6 @@ async function fork(args) {
|
||||
|
||||
async function entry(config) {
|
||||
if (config.entry) {
|
||||
// TODO newProcess
|
||||
if (config.newProcess) {
|
||||
const process = await fork([`--entry=${config.entry}`]);
|
||||
await new Promise((resolve) => {
|
||||
@@ -43,7 +42,8 @@ async function stopWorker() {
|
||||
}
|
||||
|
||||
async function startWorker(cnt) {
|
||||
for (let i = 0; i < cnt; i++) await fork();
|
||||
await fork(['--firstWorker']);
|
||||
for (let i = 1; i < cnt; i++) await fork();
|
||||
}
|
||||
|
||||
async function executeCommand(input) {
|
||||
@@ -120,6 +120,7 @@ async function load() {
|
||||
await entry({ entry: argv.entry });
|
||||
console.log(`Worker ${process.pid} Started as ${argv.entry}`);
|
||||
} else {
|
||||
if (argv.firstWorker) cluster.isFirstWorker = true;
|
||||
console.log(`Worker ${process.pid} Starting`);
|
||||
await entry({ entry: 'worker' });
|
||||
console.log(`Worker ${process.pid} Started`);
|
||||
|
||||
@@ -369,6 +369,14 @@ function _getStatusJournal(tsdoc) {
|
||||
return tsdoc.journal.sort((a, b) => (a.rid.generationTime - b.rid.generationTime));
|
||||
}
|
||||
|
||||
async function getAndListStatus(domainId, tid, docType = document.TYPE_CONTEST) {
|
||||
// TODO(iceboy): projection, pagination.
|
||||
const tdoc = await get(domainId, tid, docType);
|
||||
const tsdocs = await document.getMultiStatus(domainId, docType, { docId: tid })
|
||||
.sort(RULES[tdoc.rule].statusSort).toArray();
|
||||
return [tdoc, tsdocs];
|
||||
}
|
||||
|
||||
async function recalcStatus(domainId, tid, type) {
|
||||
const [tdoc, tsdocs] = await Promise.all([
|
||||
document.get(domainId, type, tid),
|
||||
@@ -404,6 +412,7 @@ global.Hydro.model.contest = module.exports = {
|
||||
count,
|
||||
getMulti,
|
||||
setStatus,
|
||||
getAndListStatus,
|
||||
recalcStatus,
|
||||
isNew,
|
||||
isUpcoming,
|
||||
|
||||
+23
-11
@@ -281,10 +281,15 @@ class Handler {
|
||||
|
||||
async putResponse() {
|
||||
if (this.response.disposition) this.ctx.set('Content-Disposition', this.response.disposition);
|
||||
if (this.response.redirect && !this.preferJson) {
|
||||
this.ctx.response.type = 'application/octet-stream';
|
||||
this.ctx.response.status = 302;
|
||||
this.ctx.redirect(this.response.redirect);
|
||||
if (this.response.redirect) {
|
||||
if (!this.preferJson) {
|
||||
this.ctx.response.type = 'application/octet-stream';
|
||||
this.ctx.response.status = 302;
|
||||
this.ctx.redirect(this.response.redirect);
|
||||
} else {
|
||||
this.response.body = this.response.body || {};
|
||||
this.response.body.url = this.response.redirect;
|
||||
}
|
||||
} else {
|
||||
if (this.response.body != null) {
|
||||
this.ctx.response.body = this.response.body;
|
||||
@@ -439,19 +444,26 @@ class ConnectionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
function Connection(name, prefix, RouteConnHandler) {
|
||||
function Connection(name, prefix, RouteConnHandler, permission) {
|
||||
const sock = sockjs.createServer({ prefix });
|
||||
sock.on('connection', async (conn) => {
|
||||
const h = new RouteConnHandler(conn);
|
||||
try {
|
||||
const args = { domainId: 'system', ...h.request.params };
|
||||
|
||||
if (args.uid) args.uid = parseInt(validator.checkUid(args.uid));
|
||||
if (args.page) args.page = parseInt(args.page);
|
||||
if (args.rid) args.rid = new ObjectID(args.rid);
|
||||
if (args.tid) args.tid = new ObjectID(args.tid);
|
||||
|
||||
if (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 ${checking} check failed`);
|
||||
}
|
||||
if (h.__prepare) await h.__prepare(args);
|
||||
if (h._prepare) await h._prepare(args);
|
||||
if (h.prepare) await h.prepare(args);
|
||||
|
||||
@@ -5,7 +5,7 @@ const { nav } = global.Hydro.lib;
|
||||
|
||||
class PasteMainHandler extends Handler {
|
||||
async get() {
|
||||
this.response.redirect = this.url('paste_creaate');
|
||||
this.response.redirect = this.url('paste_create');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{% macro nav_item(target_page_name, target_page_args, target_page_prefix) %}
|
||||
{%- macro nav_item(target_page_name, target_page_args, target_page_prefix) -%}
|
||||
<li class="nav__list-item">
|
||||
<a href="{{ url(target_page_name, target_page_args) }}" class="nav__item{% if page_name.startsWith(target_page_prefix) %} nav--active{% endif %}">
|
||||
{{ _(target_page_name) }}
|
||||
</a>
|
||||
</li>
|
||||
{% endmacro %}
|
||||
{%- endmacro -%}
|
||||
|
||||
<nav class="nav slideout-menu" id="menu">
|
||||
<div class="row"><div class="columns clearfix">
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{% extends "layout/basic.html" %}
|
||||
{% block content %}
|
||||
<script>
|
||||
var Context = {
|
||||
'problemId': "{{ pdoc.pid }}",
|
||||
'postSubmitUrl': "{{ url('problem_submit', pid=pdoc.pid) }}",
|
||||
'getSubmissionsUrl': "{{ url('problem_submit', pid=pdoc.pid) }}",
|
||||
'getRecordDetailUrl': "{{ url('record_detail', rid='{rid}') }}",
|
||||
'code_lang': "{{ handler.user.codeLang }}",
|
||||
'code_template': "{{ handler.user.codeTemplate }}",
|
||||
};
|
||||
var Context = {{ {
|
||||
problemId: pdoc.pid,
|
||||
postSubmitUrl: url('problem_submit', pid=pdoc.pid),
|
||||
getSubmissionsUrl: url('problem_submit', pid=pdoc.pid),
|
||||
getRecordDetailUrl: url('record_detail', rid='{rid}'),
|
||||
codeLang: handler.user.codeLang,
|
||||
codeTemplate: handler.user.codeTemplate
|
||||
}|json|safe }};
|
||||
</script>
|
||||
<div class="row">
|
||||
<div class="medium-9 columns">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export default function reducer(state = {
|
||||
lang: Context.code_lang,
|
||||
code: Context.code_template,
|
||||
lang: Context.codeLang,
|
||||
code: Context.codeTemplate,
|
||||
}, action) {
|
||||
switch (action.type) {
|
||||
case 'SCRATCHPAD_EDITOR_UPDATE_CODE': {
|
||||
|
||||
@@ -26,11 +26,11 @@ const page = new NamedPage('manage_script', () => {
|
||||
if (action !== 'ok') return;
|
||||
const args = runScriptDialog.$dom.find('[name="args"]').val();
|
||||
try {
|
||||
await request.post('', {
|
||||
const res = await request.post('', {
|
||||
args,
|
||||
id,
|
||||
});
|
||||
window.location.reload();
|
||||
window.location.href = res.url;
|
||||
} catch (error) {
|
||||
Notification.error(error.message);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user