From b3024bebb0bb1572fac513de79617d8b919f8f93 Mon Sep 17 00:00:00 2001 From: masnn Date: Sun, 5 Apr 2020 09:02:35 +0800 Subject: [PATCH] judge, problem_settings, problem_upload --- hydro/development.js | 2 +- hydro/error.js | 21 ++- hydro/handler/base.js | 1 - hydro/handler/judge.js | 49 ++++++- hydro/handler/problem.js | 80 ++++++++++- hydro/handler/record.js | 19 ++- hydro/handler/ui.js | 47 +++++-- hydro/lib/md5.js | 133 ++++++++++++++++++ hydro/model/builtin.js | 59 +++++++- hydro/model/record.js | 11 +- hydro/model/user.js | 1 - hydro/module/contest/acm.js | 2 +- hydro/module/contest/homework.js | 2 +- hydro/permission.js | 1 + hydro/service/db.js | 1 + hydro/service/gridfs.js | 5 + hydro/service/server.js | 7 +- hydro/status.js | 32 ----- package.json | 3 +- templates/components/comments_discussion.html | 4 +- templates/components/comments_solution.html | 4 +- templates/components/form.html | 74 +++++----- templates/components/record.html | 6 +- templates/contest_detail.html | 4 +- templates/discussion_detail.html | 2 +- templates/home_domain.html | 2 +- templates/home_file.html | 2 +- templates/homework_detail.html | 4 +- templates/partials/contest_sidebar.html | 2 +- templates/partials/discussion_list.html | 2 +- templates/partials/homework_sidebar.html | 2 +- templates/partials/problem_lucky.html | 14 +- .../partials/problem_sidebar_normal.html | 6 +- templates/problem_detail.html | 2 +- templates/problem_edit.html | 16 +-- templates/problem_main.html | 33 +++-- templates/problem_settings.html | 47 ++++--- templates/problem_submit.html | 7 +- templates/problem_submit_tr.html | 2 +- templates/problem_upload.html | 23 ++- templates/record_detail.html | 14 +- templates/record_detail_status.html | 18 +-- templates/record_main_tr.html | 2 +- templates/training_detail.html | 2 +- yarn.lock | 78 ++++++++-- 45 files changed, 598 insertions(+), 250 deletions(-) create mode 100644 hydro/lib/md5.js create mode 100644 hydro/service/gridfs.js delete mode 100644 hydro/status.js diff --git a/hydro/development.js b/hydro/development.js index c27e6c99e..ed0fd0a60 100644 --- a/hydro/development.js +++ b/hydro/development.js @@ -31,8 +31,8 @@ async function run() { resolve(); }); }); + require('./service/gridfs'); let server = require('./service/server'); - require('./handler/ui'); require('./handler/base'); require('./handler/home'); diff --git a/hydro/error.js b/hydro/error.js index de039ee0e..3447ac18f 100644 --- a/hydro/error.js +++ b/hydro/error.js @@ -104,10 +104,17 @@ class ContestNotFoundError extends NotFoundError { this.params = [cid]; } } + +class ProblemDataNotFoundError extends NotFoundError { + constructor(pid) { + super('Data of problem {0} not found.'); + this.params = [pid]; + } +} module.exports = { BadRequestError, ForbiddenError, NotFoundError, LoginError, UserAlreadyExistError, InvalidTokenError, - UserNotFoundError, VerifyPasswordError, + UserNotFoundError, VerifyPasswordError, ProblemDataNotFoundError, OpcountExceededError, PermissionError, NoProblemError, ValidationError, ProblemNotFoundError, TrainingNotFoundError, ContestNotFoundError, RecordNotFoundError @@ -179,18 +186,6 @@ class DocumentNotFoundError(NotFoundError): return 'Document {2} not found.' -class ProblemDataNotFoundError(NotFoundError): - @property - def message(self): - return 'Data of problem {1} not found.' - - -class RecordDataNotFoundError(NotFoundError): - @property - def message(self): - return 'Data of record {0} not found.' - - class CsrfTokenError(ForbiddenError): pass diff --git a/hydro/handler/base.js b/hydro/handler/base.js index 47a9192ea..58b50762d 100644 --- a/hydro/handler/base.js +++ b/hydro/handler/base.js @@ -59,7 +59,6 @@ MIDDLEWARE(async (ctx, next) => { update_ip: ctx.request.ip, update_ua: ctx.request.headers['user-agent'] || '' }, ctx.session)); - console.log(ctx.session.sid, ctx.session.uid); let cookie = { secure: options.session.secure, httponly: true }; if (save) { cookie.expires = ctx.session.expireAt, cookie.maxAge = expireSeconds; diff --git a/hydro/handler/judge.js b/hydro/handler/judge.js index 8c172b44e..75b1d4bb0 100644 --- a/hydro/handler/judge.js +++ b/hydro/handler/judge.js @@ -14,7 +14,7 @@ GET('/judge/fetch', requirePerm(PERM_JUDGE), async ctx => { let rid = await queue.get('judge', false); if (rid) { let rdoc = await record.get(rid); - let data = { + let task = { event: 'judge', rid, type: 0, pid: rdoc.pid, @@ -22,12 +22,57 @@ GET('/judge/fetch', requirePerm(PERM_JUDGE), async ctx => { lang: rdoc.lang, code: rdoc.code }; - ctx.body = data; + ctx.body = { task }; } + else ctx.body = {}; }); POST('/judge/next', requirePerm(PERM_JUDGE), async ctx => { console.log(ctx.request.body); + let body = ctx.request.body; + let rdoc = await record.get(body.rid); + let $set = {}; + if (body.case) { + rdoc.testCases.push(body.case); + $set.testCases = rdoc.testCases; + } + if (body.judge_text) { + rdoc.judgeTexts.push(body.judge_text); + $set.judgeTexts = rdoc.judgeTexts; + } + if (body.compiler_text) { + rdoc.compilerTexts.push(body.compilerTexts); + $set.compilerTexts = rdoc.compilerTexts; + } + 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; + await record.update(body.rid, $set); + ctx.body = {}; }); POST('/judge/end', requirePerm(PERM_JUDGE), async ctx => { console.log(ctx.request.body); + let body = ctx.request.body; + let rdoc = await record.get(body.rid); + let $set = {}; + if (body.case) { + rdoc.testCases.push(body.case); + $set.testCases = rdoc.testCases; + } + if (body.judge_text) { + rdoc.judgeTexts.push(body.judge_text); + $set.judgeTexts = rdoc.judgeTexts; + } + if (body.compiler_text) { + rdoc.compilerTexts.push(body.compilerTexts); + $set.compilerTexts = rdoc.compilerTexts; + } + 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; + $set.judgeAt = new Date(); + $set.judger = ctx.state.user._id; + await record.update(body.rid, $set); + ctx.body = {}; }); diff --git a/hydro/handler/problem.js b/hydro/handler/problem.js index 744593420..3df5bbfa4 100644 --- a/hydro/handler/problem.js +++ b/hydro/handler/problem.js @@ -1,29 +1,37 @@ const + fs = require('fs'), + bson = require('bson'), validator = require('../lib/validator'), problem = require('../model/problem'), record = require('../model/record'), user = require('../model/user'), + system = require('../model/system'), { GET, POST } = require('../service/server'), + gridfs = require('../service/gridfs'), queue = require('../service/queue'), { requirePerm } = require('./tools'), - { NoProblemError } = require('../error'), + { NoProblemError, ProblemDataNotFoundError, BadRequestError } = require('../error'), { constants } = require('../options'), { PERM_VIEW_PROBLEM, PERM_VIEW_PROBLEM_HIDDEN, PERM_SUBMIT_PROBLEM, - PERM_CREATE_PROBLEM + PERM_CREATE_PROBLEM, + PERM_READ_PROBLEM_DATA, + PERM_EDIT_PROBLEM, + PERM_JUDGE } = require('../permission'); queue.assert('judge'); GET('/p', requirePerm(PERM_VIEW_PROBLEM), async ctx => { + ctx.templateName = 'problem_main.html'; let q = {}, page = ctx.query.page || 1; + if (ctx.query.category) q.category = ctx.query.category; if (!ctx.state.user.hasPerm(PERM_VIEW_PROBLEM_HIDDEN)) q.hidden = false; let pdocs = await problem.getMany(q, { pid: 1 }, page, constants.PROBLEM_PER_PAGE); - ctx.templateName = 'problem_main.html'; - ctx.body = { page_name: 'problem_list', page, pdocs, category: '' }; + ctx.body = { page, pdocs, category: '' }; }); GET('/problem/random', requirePerm(PERM_VIEW_PROBLEM), async ctx => { let q = {}; @@ -33,33 +41,89 @@ GET('/problem/random', requirePerm(PERM_VIEW_PROBLEM), async ctx => { ctx.body = { pid }; }); GET('/p/:pid', requirePerm(PERM_VIEW_PROBLEM), async ctx => { + ctx.templateName = 'problem_detail.html'; let uid = ctx.state.user._id, pid = ctx.params.pid; let pdoc = await problem.get({ pid, uid }); if (pdoc.hidden) ctx.checkPerm(PERM_VIEW_PROBLEM_HIDDEN); let udoc = await user.getById(pdoc.owner); - ctx.templateName = 'problem_detail.html'; ctx.body = { pdoc, udoc, title: pdoc.title }; }); GET('/p/:pid/submit', requirePerm(PERM_SUBMIT_PROBLEM), async ctx => { + ctx.templateName = 'problem_submit.html'; let uid = ctx.state.user._id, pid = ctx.params.pid; let pdoc = await problem.get({ pid, uid }); if (pdoc.hidden) ctx.checkPerm(PERM_VIEW_PROBLEM_HIDDEN); let udoc = await user.getById(pdoc.owner); - ctx.templateName = 'problem_submit.html'; ctx.body = { pdoc, udoc, title: pdoc.title }; }); POST('/p/:pid/submit', requirePerm(PERM_SUBMIT_PROBLEM), async ctx => { let rid = await record.add({ uid: ctx.state.user._id, - lang: ctx.request.body.language, + lang: ctx.request.body.lang, code: ctx.request.body.code, pid: ctx.params.pid }); await queue.push('judge', rid); ctx.state.user.nSubmit++; ctx.body = { rid }; + ctx.setRedirect = `/r/${rid}`; +}); +GET('/p/:pid/settings', async ctx => { + ctx.templateName = 'problem_settings.html'; + let pdoc = await problem.get({ pid: ctx.params.pid, uid: ctx.state.user._id }); + if (pdoc.hidden) ctx.checkPerm(PERM_VIEW_PROBLEM_HIDDEN); + ctx.body = { pdoc }; +}); +POST('/p/:pid/settings', async ctx => { + ctx.templateName = 'problem_settings.html'; + // TODO(masnn) + let pdoc = await problem.get({ pid: ctx.params.pid, uid: ctx.state.user._id }); + ctx.body = { pdoc }; +}); +GET('/p/:pid/upload', async ctx => { + ctx.templateName = 'problem_upload.html'; + let pdoc = await problem.get({ pid: ctx.params.pid, uid: ctx.state.user._id }); + if (pdoc.owner != ctx.state.user._id) ctx.checkPerm(PERM_EDIT_PROBLEM); + let md5; + if (typeof pdoc.data == 'object') { + let files = await gridfs.find({ _id: pdoc.data }).toArray(); + md5 = files[0].md5; + } + ctx.body = { pdoc, md5 }; +}); +POST('/p/:pid/upload', async ctx => { + ctx.templateName = 'problem_upload.html'; + if (!ctx.request.files.file) throw new BadRequestError(); + let pdoc = await problem.get({ pid: ctx.params.pid, uid: ctx.state.user._id }); + if (pdoc.owner != ctx.state.user._id) ctx.checkPerm(PERM_EDIT_PROBLEM); + const r = fs.createReadStream(ctx.request.files.file.path); + let f = gridfs.openUploadStream('data.zip'); + await new Promise((resolve, reject) => { + r.pipe(f); + f.once('finish', resolve); + f.once('error', reject); + }); + let md5; + if (typeof pdoc.data == 'object') { + let files = await gridfs.find({ _id: pdoc.data }).toArray(); + md5 = files[0].md5; + } + pdoc = await problem.edit(ctx.params.pid, { data: f.id }); + ctx.body = { pdoc, md5 }; +}); +GET('/p/:pid/data', async ctx => { + let pdoc = await problem.get({ pid: ctx.params.pid, uid: ctx.state.user._id }); + if (!ctx.state.user._id == pdoc.owner) ctx.checkPerm([PERM_READ_PROBLEM_DATA, PERM_JUDGE]); + if (!pdoc.data) throw new ProblemDataNotFoundError(ctx.params.pid); + else if (typeof pdoc.data == 'string') ctx.setRedirect = pdoc.data.split('from:')[1]; + ctx.attachment(`${pdoc.title}.zip`); + ctx.body = gridfs.openDownloadStream(pdoc.data); +}); +GET('/problem/create', requirePerm(PERM_CREATE_PROBLEM), async ctx => { + ctx.templateName = 'problem_edit.html'; + ctx.body = { page_name: 'problem_create' }; }); POST('/problem/create', requirePerm(PERM_CREATE_PROBLEM), async ctx => { let { title, pid, content, hidden } = ctx.request.body; @@ -67,8 +131,10 @@ POST('/problem/create', requirePerm(PERM_CREATE_PROBLEM), async ctx => { pid = pid || await system.incPidCounter(); await problem.add({ title, content, owner: ctx.state.user._id, pid, hidden }); ctx.body = { pid }; + ctx.setRedirect = `/p/${pid}/settings`; }); + /* @app.route('/p/category/{category:[^/]*}/random', 'problem_category_random') diff --git a/hydro/handler/record.js b/hydro/handler/record.js index e88b8c9e3..84cff6582 100644 --- a/hydro/handler/record.js +++ b/hydro/handler/record.js @@ -1,13 +1,16 @@ const bson = require('bson'), { constants } = require('../options'), - { GET } = require('../service/server'), + { GET, POST } = require('../service/server'), + { requirePerm } = require('../handler/tools'), + queue = require('../service/queue'), record = require('../model/record'), user = require('../model/user'), problem = require('../model/problem'), - { PERM_READ_RECORD_CODE, PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD } = require('../permission'); + { PERM_READ_RECORD_CODE, PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD, PERM_REJUDGE } = require('../permission'); GET('/r', async ctx => { + ctx.templateName = 'record_main.html'; let q = {}, page = ctx.query.page || 1; let rdocs = await record.getMany(q, { rid: 1 }, page, constants.RECORD_PER_PAGE); @@ -16,16 +19,24 @@ GET('/r', async ctx => { udict[rdoc.uid] = await user.getById(rdoc.uid); pdict[rdoc.pid] = await problem.get({ pid: rdoc.pid, uid: ctx.state.user._id }); } - ctx.templateName = 'record_main.html'; ctx.body = { page, rdocs, pdict, udict }; }); GET('/r/:rid', async ctx => { + ctx.templateName = 'record_detail.html'; let uid = ctx.state.user._id, rid = new bson.ObjectID(ctx.params.rid); let rdoc = await record.get(rid); if (rdoc.hidden) ctx.checkPerm(PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD); if (rdoc.uid != uid && !ctx.state.user.hasPerm(PERM_READ_RECORD_CODE)) rdoc.code = null; + ctx.body = { rdoc, show_status: true }; +}); +POST('/r/:rid/rejudge', requirePerm(PERM_REJUDGE), async ctx => { ctx.templateName = 'record_detail.html'; - ctx.body = { rdoc }; + let uid = ctx.state.user._id, rid = new bson.ObjectID(ctx.params.rid); + let rdoc = await record.get(rid); + if (rdoc.hidden) ctx.checkPerm(PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD); + if (rdoc.uid != uid && !ctx.state.user.hasPerm(PERM_READ_RECORD_CODE)) rdoc.code = null; + if (rdoc) await queue.push('judge', rid); + ctx.body = { rdoc, show_status: true }; }); /* diff --git a/hydro/handler/ui.js b/hydro/handler/ui.js index b61e204bb..aa466d22d 100644 --- a/hydro/handler/ui.js +++ b/hydro/handler/ui.js @@ -1,6 +1,5 @@ const path = require('path'), - md5 = require('blueimp-md5'), static = require('koa-static'), nunjucks = require('nunjucks'), hljs = require('hljs'), @@ -9,14 +8,9 @@ const options = require('../options'), perm = require('../permission'), builtin = require('../model/builtin'), + md5 = require('../lib/md5'), { MIDDLEWARE } = require('../service/server'), - { NotFoundError } = require('../error'), - MD5_REGEX = /^[0-9a-f]{32}$/; - -function getHash(email) { - email = (typeof email === 'string') ? email.trim().toLowerCase() : 'unspecified'; - return email.match(MD5_REGEX) ? email : md5(email); -} + { NotFoundError } = require('../error'); class Markdown extends MarkdownIt { constructor() { @@ -35,6 +29,14 @@ class Markdown extends MarkdownIt { } } const md = new Markdown(); +function datetime_span(dt, { relative = true } = {}) { + dt = new Date(dt); + return '{2}'.format( + relative ? ' relative' : '', + dt.getTime(), + dt.toLocaleString() + ); +} class Nunjucks extends nunjucks.Environment { constructor() { super( @@ -51,12 +53,26 @@ class Nunjucks extends nunjucks.Environment { return md.render(self); }); this.addFilter('gravatar_url', function (email, size) { - return `//gravatar.loli.net/avatar/${getHash(email)}?d=mm&s=${size}`; + return `//gravatar.loli.net/avatar/${md5(email.toString().trim().toLowerCase())}?d=mm&s=${size}`; }); + this.addFilter('format_size', function (size, base = 1) { + size *= base; + let unit = 1024; + let unit_names = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; + for (let unit_name of unit_names) { + if (size < unit) return '{0} {1}'.format(Math.round(size), unit_name); + size /= unit; + } + return '{0} {1}'.format(Math.round(size * unit), unit_names[unit_names.length - 1]); + }); + this.addGlobal('typeof', o => typeof o); + this.addGlobal('console', console); this.addGlobal('static_url', str => `/${str}`); this.addGlobal('reverse_url', str => str); + this.addGlobal('datetime_span', datetime_span); this.addGlobal('perm', perm); this.addGlobal('builtin', builtin); + this.addGlobal('status', builtin.STATUS); } } const env = new Nunjucks(); @@ -67,6 +83,7 @@ MIDDLEWARE(async (ctx, next) => { cdn_prefix: '/', url_prefix: '/' }; + ctx.preferJson = ctx.request.headers['accept'].includes('application/json'); ctx.render = async (name, context) => { ctx.user = ctx.state.user; ctx.translate = str => { @@ -77,7 +94,8 @@ MIDDLEWARE(async (ctx, next) => { ctx.body = await new Promise((resolve, reject) => { env.render(name, Object.assign(ctx.state, context, { handler: ctx, - _: ctx.translate + _: ctx.translate, + user: ctx.state.user }), (error, res) => { if (error) reject(error); else resolve(res); @@ -89,6 +107,7 @@ MIDDLEWARE(async (ctx, next) => { try { await next(); if (ctx.body || ctx.templateName) { + if (ctx.preferJson) return; if (ctx.query.template || ctx.templateName) { ctx.body = ctx.body || {}; Object.assign(ctx.body, JSON.parse(ctx.query.data || '{}')); @@ -99,11 +118,13 @@ MIDDLEWARE(async (ctx, next) => { } } else throw new NotFoundError(); } catch (error) { - console.log(error); + if (error.toString().startsWith('NotFoundError')) console.log(error); if (error.toString().startsWith('Template render error')) throw error; - await ctx.render('error.html', { error }); + if (ctx.preferJson) ctx.body = { error }; + else await ctx.render('error.html', { error }); } } catch (error) { - await ctx.render('bsod.html', { error }); + if (ctx.preferJson) ctx.body = { error }; + else await ctx.render('bsod.html', { error }); } }); \ No newline at end of file diff --git a/hydro/lib/md5.js b/hydro/lib/md5.js new file mode 100644 index 000000000..7951965a7 --- /dev/null +++ b/hydro/lib/md5.js @@ -0,0 +1,133 @@ + +'use strict'; +function safeAdd(x, y) { + var lsw = (x & 0xffff) + (y & 0xffff); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xffff); +} +const bitRotateLeft = (num, cnt) => (num << cnt) | (num >>> (32 - cnt)); +const md5cmn = (q, a, b, x, s, t) => safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b); +const md5ff = (a, b, c, d, x, s, t) => md5cmn((b & c) | (~b & d), a, b, x, s, t); +const md5gg = (a, b, c, d, x, s, t) => md5cmn((b & d) | (c & ~d), a, b, x, s, t); +const md5hh = (a, b, c, d, x, s, t) => md5cmn(b ^ c ^ d, a, b, x, s, t); +const md5ii = (a, b, c, d, x, s, t) => md5cmn(c ^ (b | ~d), a, b, x, s, t); +function binlMD5(x, len) { + x[len >> 5] |= 0x80 << len % 32; + x[(((len + 64) >>> 9) << 4) + 14] = len; + var i; + var olda; + var oldb; + var oldc; + var oldd; + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + for (i = 0; i < x.length; i += 16) { + olda = a; + oldb = b; + oldc = c; + oldd = d; + a = md5ff(a, b, c, d, x[i], 7, -680876936); + d = md5ff(d, a, b, c, x[i + 1], 12, -389564586); + c = md5ff(c, d, a, b, x[i + 2], 17, 606105819); + b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330); + a = md5ff(a, b, c, d, x[i + 4], 7, -176418897); + d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426); + c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341); + b = md5ff(b, c, d, a, x[i + 7], 22, -45705983); + a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416); + d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417); + c = md5ff(c, d, a, b, x[i + 10], 17, -42063); + b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162); + a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682); + d = md5ff(d, a, b, c, x[i + 13], 12, -40341101); + c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290); + b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329); + a = md5gg(a, b, c, d, x[i + 1], 5, -165796510); + d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632); + c = md5gg(c, d, a, b, x[i + 11], 14, 643717713); + b = md5gg(b, c, d, a, x[i], 20, -373897302); + a = md5gg(a, b, c, d, x[i + 5], 5, -701558691); + d = md5gg(d, a, b, c, x[i + 10], 9, 38016083); + c = md5gg(c, d, a, b, x[i + 15], 14, -660478335); + b = md5gg(b, c, d, a, x[i + 4], 20, -405537848); + a = md5gg(a, b, c, d, x[i + 9], 5, 568446438); + d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690); + c = md5gg(c, d, a, b, x[i + 3], 14, -187363961); + b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501); + a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467); + d = md5gg(d, a, b, c, x[i + 2], 9, -51403784); + c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473); + b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734); + a = md5hh(a, b, c, d, x[i + 5], 4, -378558); + d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463); + c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562); + b = md5hh(b, c, d, a, x[i + 14], 23, -35309556); + a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060); + d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353); + c = md5hh(c, d, a, b, x[i + 7], 16, -155497632); + b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640); + a = md5hh(a, b, c, d, x[i + 13], 4, 681279174); + d = md5hh(d, a, b, c, x[i], 11, -358537222); + c = md5hh(c, d, a, b, x[i + 3], 16, -722521979); + b = md5hh(b, c, d, a, x[i + 6], 23, 76029189); + a = md5hh(a, b, c, d, x[i + 9], 4, -640364487); + d = md5hh(d, a, b, c, x[i + 12], 11, -421815835); + c = md5hh(c, d, a, b, x[i + 15], 16, 530742520); + b = md5hh(b, c, d, a, x[i + 2], 23, -995338651); + a = md5ii(a, b, c, d, x[i], 6, -198630844); + d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415); + c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905); + b = md5ii(b, c, d, a, x[i + 5], 21, -57434055); + a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571); + d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606); + c = md5ii(c, d, a, b, x[i + 10], 15, -1051523); + b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799); + a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359); + d = md5ii(d, a, b, c, x[i + 15], 10, -30611744); + c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380); + b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649); + a = md5ii(a, b, c, d, x[i + 4], 6, -145523070); + d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379); + c = md5ii(c, d, a, b, x[i + 2], 15, 718787259); + b = md5ii(b, c, d, a, x[i + 9], 21, -343485551); + a = safeAdd(a, olda); + b = safeAdd(b, oldb); + c = safeAdd(c, oldc); + d = safeAdd(d, oldd); + } + return [a, b, c, d]; +} +function binl2rstr(input) { + var i; + var output = ''; + var length32 = input.length * 32; + for (i = 0; i < length32; i += 8) + output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff); + return output; +} +function rstr2binl(input) { + var i; + var output = []; + output[(input.length >> 2) - 1] = undefined; + for (i = 0; i < output.length; i += 1) + output[i] = 0; + var length8 = input.length * 8; + for (i = 0; i < length8; i += 8) + output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32; + return output; +} +const rstrMD5 = s => binl2rstr(binlMD5(rstr2binl(s), s.length * 8)); +function rstr2hex(input) { + var hexTab = '0123456789abcdef'; + var output = ''; + var x; + var i; + for (i = 0; i < input.length; i += 1) { + x = input.charCodeAt(i); + output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f); + } + return output; +} +module.exports = s => rstr2hex(rstrMD5(unescape(encodeURIComponent(s)))); diff --git a/hydro/model/builtin.js b/hydro/model/builtin.js index d6f6b8828..c88e7a686 100644 --- a/hydro/model/builtin.js +++ b/hydro/model/builtin.js @@ -177,9 +177,9 @@ const FOOTER_EXTRA_HTMLS = []; const VIEW_LANGS = [ { code: 'zh-CN', name: 'zh-CN' } ]; -const LANGS = [ - { id: 'cc', name: 'cpp' } -]; +const LANGS = { + cc: 'cpp' +}; const LANG_TEXTS = { c: 'C', cc: 'C++', @@ -210,7 +210,58 @@ const LANG_HIGHLIGHT_ID = { go: 'go', rb: 'ruby', }; +const STATUS = { + STATUS_WAITING: 0, + STATUS_ACCEPTED: 1, + STATUS_WRONG_ANSWER: 2, + STATUS_TIME_LIMIT_EXCEEDED: 3, + STATUS_MEMORY_LIMIT_EXCEEDED: 4, + STATUS_OUTPUT_LIMIT_EXCEEDED: 5, + STATUS_RUNTIME_ERROR: 6, + STATUS_COMPILE_ERROR: 7, + STATUS_SYSTEM_ERROR: 8, + STATUS_CANCELED: 9, + STATUS_ETC: 10, + STATUS_JUDGING: 20, + STATUS_COMPILING: 21, + STATUS_FETCHED: 22, + STATUS_IGNORED: 30 +}; +const STATUS_TEXTS = { + [STATUS.STATUS_WAITING]: 'Waiting', + [STATUS.STATUS_ACCEPTED]: 'Accepted', + [STATUS.STATUS_WRONG_ANSWER]: 'Wrong Answer', + [STATUS.STATUS_TIME_LIMIT_EXCEEDED]: 'Time Exceeded', + [STATUS.STATUS_MEMORY_LIMIT_EXCEEDED]: 'Memory Exceeded', + [STATUS.STATUS_OUTPUT_LIMIT_EXCEEDED]: 'Output Exceeded', + [STATUS.STATUS_RUNTIME_ERROR]: 'Runtime Error', + [STATUS.STATUS_COMPILE_ERROR]: 'Compile Error', + [STATUS.STATUS_SYSTEM_ERROR]: 'System Error', + [STATUS.STATUS_CANCELED]: 'Cancelled', + [STATUS.STATUS_ETC]: 'Unknown Error', + [STATUS.STATUS_JUDGING]: 'Running', + [STATUS.STATUS_COMPILING]: 'Compiling', + [STATUS.STATUS_FETCHED]: 'Fetched', + [STATUS.STATUS_IGNORED]: 'Ignored', +}; +const STATUS_CODES = { + 0: 'pending', + 1: 'pass', + 2: 'fail', + 3: 'fail', + 4: 'fail', + 5: 'fail', + 6: 'fail', + 7: 'fail', + 8: 'fail', + 9: 'ignored', + 10: 'fail', + 20: 'progress', + 21: 'progress', + 22: 'progress', + 30: 'ignored' +}; module.exports = { BUILTIN_USERS, BUILTIN_ROLES, CATEGORIES, VIEW_LANGS, FOOTER_EXTRA_HTMLS, LANGS, - LANG_TEXTS, LANG_HIGHLIGHT_ID + LANG_TEXTS, LANG_HIGHLIGHT_ID, STATUS, STATUS_TEXTS, STATUS_CODES }; \ No newline at end of file diff --git a/hydro/model/record.js b/hydro/model/record.js index 4840d37b0..f058f5ab0 100644 --- a/hydro/model/record.js +++ b/hydro/model/record.js @@ -1,7 +1,8 @@ const _ = require('lodash'), + { ObjectID } = require('bson'), { RecordNotFoundError } = require('../error'), - { STATUS_WAITING } = require('../status'), + { STATUS_WAITING } = require('../model/builtin').STATUS, db = require('../service/db.js'), coll = db.collection('record'); @@ -29,7 +30,8 @@ async function add(data) { * @returns {import('../interface').Record} */ async function get(rid) { - let rdoc = await coll.findOne({ _id: rid }); + let _id = new ObjectID(rid); + let rdoc = await coll.findOne({ _id }); if (!rdoc) throw new RecordNotFoundError(rid); return rdoc; } @@ -37,8 +39,9 @@ async function getMany(query, sort, page, limit) { return await coll.find(query).sort(sort).skip((page - 1) * limit).limit(limit).toArray(); } async function update(rid, $set) { - await coll.findOneAndUpdate({ _id: rid }, { $set }); - let rdoc = await coll.findOne({ _id: rid }); + let _id = new ObjectID(rid); + await coll.findOneAndUpdate({ _id }, { $set }); + let rdoc = await coll.findOne({ _id }); if (!rdoc) throw new RecordNotFoundError(rid); return rdoc; } diff --git a/hydro/model/user.js b/hydro/model/user.js index ca1df64d5..d58299fce 100644 --- a/hydro/model/user.js +++ b/hydro/model/user.js @@ -16,7 +16,6 @@ class USER { this.perm = user.perm; } hasPerm(perm) { - console.log('PERM', perm, this.perm); return this.perm == '-' || (this.perm || '').includes(perm); } checkPassword(password) { diff --git a/hydro/module/contest/acm.js b/hydro/module/contest/acm.js index f7f6897fc..6bcc6bba8 100644 --- a/hydro/module/contest/acm.js +++ b/hydro/module/contest/acm.js @@ -9,7 +9,7 @@ module.exports = { naccept[j.pid]++; } function _time(jdoc) { - let real = jdoc.rid.generation_time - Math.floor(tdoc.begin_at / 1000); + let real = jdoc.rid.generationTime - Math.floor(tdoc.begin_at / 1000); let penalty = 20 * 60 * naccept[jdoc.pid]; return real + penalty; } diff --git a/hydro/module/contest/homework.js b/hydro/module/contest/homework.js index 6059ee672..dbb2c4dbf 100644 --- a/hydro/module/contest/homework.js +++ b/hydro/module/contest/homework.js @@ -12,7 +12,7 @@ module.exports = { effective[j.pid] = j; const _time = jdoc => jdoc.rid.generationTime - Math.floor(tdoc.begin_at / 1000); function _penalty_score(jdoc) { - let exceed_seconds = jdoc.rid.generation_time - Math.floor(tdoc.penalty_since / 1000); + let exceed_seconds = jdoc.rid.generationTime - Math.floor(tdoc.penalty_since / 1000); if (exceed_seconds < 0) return jdoc.score; else return 0.5 * jdoc.score; } diff --git a/hydro/permission.js b/hydro/permission.js index 6e04a7553..eeb1abb09 100644 --- a/hydro/permission.js +++ b/hydro/permission.js @@ -41,6 +41,7 @@ let permissions = { PERM_VIEW_TRAINING: 'a', PERM_CREATE_TRAINING: 'b', PERM_EDIT_TRAINING: 'c', + PERM_JUDGE: 'd' }; permissions.PERM_BASIC = permissions.PERM_VIEW + diff --git a/hydro/service/db.js b/hydro/service/db.js index db031ea37..fccfe0a53 100644 --- a/hydro/service/db.js +++ b/hydro/service/db.js @@ -10,6 +10,7 @@ let db = null; Mongo.MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: true }) .then(Database => { db = Database.db(options.db.name); + db.Db = Database; global.bus.emit('connected'); }); diff --git a/hydro/service/gridfs.js b/hydro/service/gridfs.js new file mode 100644 index 000000000..43fe4e712 --- /dev/null +++ b/hydro/service/gridfs.js @@ -0,0 +1,5 @@ +const + { GridFSBucket } = require('mongodb'), + db = require('./db'); +if (!db.s) db.s = {}; +module.exports = new GridFSBucket(db); \ No newline at end of file diff --git a/hydro/service/server.js b/hydro/service/server.js index 0b59fafc9..b8abb39c3 100644 --- a/hydro/service/server.js +++ b/hydro/service/server.js @@ -12,7 +12,12 @@ let app = new Koa(); let server = http.createServer(app.callback()); app.keys = options.session.keys; app.use(morgan(':method :url :status :res[content-length] - :response-time ms')); -app.use(Body()); +app.use(Body({ + multipart: true, + formidable: { + maxFileSize: 256 * 1024 * 1024 + } +})); let router = new Router(); /** diff --git a/hydro/status.js b/hydro/status.js deleted file mode 100644 index 5e75212ea..000000000 --- a/hydro/status.js +++ /dev/null @@ -1,32 +0,0 @@ -exports.STATUS_WAITING = 0; -exports.STATUS_ACCEPTED = 1; -exports.STATUS_WRONG_ANSWER = 2; -exports.STATUS_TIME_LIMIT_EXCEEDED = 3; -exports.STATUS_MEMORY_LIMIT_EXCEEDED = 4; -exports.STATUS_OUTPUT_LIMIT_EXCEEDED = 5; -exports.STATUS_RUNTIME_ERROR = 6; -exports.STATUS_COMPILE_ERROR = 7; -exports.STATUS_SYSTEM_ERROR = 8; -exports.STATUS_CANCELED = 9; -exports.STATUS_ETC = 10; -exports.STATUS_JUDGING = 20; -exports.STATUS_COMPILING = 21; -exports.STATUS_FETCHED = 22; -exports.STATUS_IGNORED = 30; -exports.STATUS_TEXTS = { - [exports.STATUS_WAITING]: 'Waiting', - [exports.STATUS_ACCEPTED]: 'Accepted', - [exports.STATUS_WRONG_ANSWER]: 'Wrong Answer', - [exports.STATUS_TIME_LIMIT_EXCEEDED]: 'Time Exceeded', - [exports.STATUS_MEMORY_LIMIT_EXCEEDED]: 'Memory Exceeded', - [exports.STATUS_OUTPUT_LIMIT_EXCEEDED]: 'Output Exceeded', - [exports.STATUS_RUNTIME_ERROR]: 'Runtime Error', - [exports.STATUS_COMPILE_ERROR]: 'Compile Error', - [exports.STATUS_SYSTEM_ERROR]: 'System Error', - [exports.STATUS_CANCELED]: 'Cancelled', - [exports.STATUS_ETC]: 'Unknown Error', - [exports.STATUS_JUDGING]: 'Running', - [exports.STATUS_COMPILING]: 'Compiling', - [exports.STATUS_FETCHED]: 'Fetched', - [exports.STATUS_IGNORED]: 'Ignored', -}; \ No newline at end of file diff --git a/package.json b/package.json index 4734db4ba..deac8a35e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ "license": "MIT", "dependencies": { "axios": "^0.19.0", - "blueimp-md5": "^2.13.0", "bson": "^4.0.2", "hljs": "^6.2.3", "js-yaml": "^3.13.1", @@ -19,7 +18,7 @@ "lodash": "^4.17.15", "markdown-it": "^10.0.0", "markdown-it-katex": "^2.0.3", - "mongodb": "^3.3.4", + "mongodb": "^3.5.5", "nodemailer": "^6.3.1", "nunjucks": "^3.2.1", "sockjs": "^0.3.20" diff --git a/templates/components/comments_discussion.html b/templates/components/comments_discussion.html index 88f873527..9e151a74f 100644 --- a/templates/components/comments_discussion.html +++ b/templates/components/comments_discussion.html @@ -10,7 +10,7 @@
{{ user.render_inline(udoc, dudict[udoc['_id']], avatar=false) }} - @ {{ datetime_span(rdoc['_id'].generation_time) }} + @ {{ datetime_span(rdoc['_id'].generationTime) }}
{{ comment_base.reply_operations(doc, rdoc) }}
@@ -37,7 +37,7 @@
{{ user.render_inline(udoc, dudict[udoc['_id']], avatar=false) }} - @ {{ datetime_span(doc['_id'].generation_time) }} + @ {{ datetime_span(doc['_id'].generationTime) }}
{{ comment_base.comment_operations(doc) }}
diff --git a/templates/components/comments_solution.html b/templates/components/comments_solution.html index 9ad99982e..bd0db45d4 100644 --- a/templates/components/comments_solution.html +++ b/templates/components/comments_solution.html @@ -7,7 +7,7 @@
{{ user.render_inline(udoc, badge=false) }} - @ {{ datetime_span(rdoc['_id'].generation_time) }} + @ {{ datetime_span(rdoc['_id'].generationTime) }}
{{ comment_base.reply_operations(doc, rdoc) }}
@@ -65,7 +65,7 @@
{{ user.render_inline(udoc, dudict[udoc['_id']]) }} - @ {{ datetime_span(doc['_id'].generation_time) }} + @ {{ datetime_span(doc['_id'].generationTime) }}
{{ comment_base.comment_operations(doc) }}
diff --git a/templates/components/form.html b/templates/components/form.html index 468e05c07..8a208b1ae 100644 --- a/templates/components/form.html +++ b/templates/components/form.html @@ -71,19 +71,19 @@ name="{{ args.name }}" {{ form_attr('select', args) }} > - {% for item in args.options %} - {% endfor %} {% endmacro %} -{% macro form_select(columns=5, args) %} -{{ form_begin({columns:columns}|assign(args)) }} -{{ select({columns:columns}|assign(args)) }} -{{ form_end({columns:columns}|assign(args)) }} +{% macro form_select(args) %} +{{ form_begin({columns:5}|assign(args)) }} +{{ select(args) }} +{{ form_end(args) }} {% endmacro %} {% macro radio(args) %} @@ -91,24 +91,24 @@ name="form_item_{{ args.name }}" {{ container_attr('radiobox-container', args) }} > -{% for item in options %} +{% for k, v in options %} {% endfor %} {% endmacro %} -{% macro form_radio(columns=5, args) %} -{{ form_begin({columns:columns, label_wrap:false}|assign(args)) }} -{{ radio({columns:columns}|assign(args)) }} -{{ form_end({columns:columns, label_warp:false}|assign(args)) }} +{% macro form_radio(args) %} +{{ form_begin({columns:5, label_wrap:false}|assign(args)) }} +{{ radio(args) }} +{{ form_end({label_warp:false}|assign(args)) }} {% endmacro %} {% macro image_radio(args) %} @@ -116,27 +116,27 @@ name="form_item_{{ args.name }}" {{ container_attr('radiobox-container with-image', args) }} > -{% for item in options %} +{% for k, v in options %}