mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
testdata.convert
This commit is contained in:
1 parent
26f780ddee
commit
0d7d6ea12a
15 files changed
+199
-127
No files matched your search
@@ -5,7 +5,7 @@ const fs = require('fs');
|
|||||||
const builtinLib = [
|
const builtinLib = [
|
||||||
'axios', 'download', 'i18n', 'mail', 'markdown',
|
'axios', 'download', 'i18n', 'mail', 'markdown',
|
||||||
'md5', 'misc', 'paginate', 'hash.hydro', 'rank',
|
'md5', 'misc', 'paginate', 'hash.hydro', 'rank',
|
||||||
'template', 'validator', 'nav', 'sysinfo',
|
'template', 'validator', 'nav', 'sysinfo', 'testdata.convert.ini',
|
||||||
];
|
];
|
||||||
|
|
||||||
const builtinModel = [
|
const builtinModel = [
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class HomeSecurityHandler extends Handler {
|
|||||||
for (const session of sessions) {
|
for (const session of sessions) {
|
||||||
if (tokenDigest === md5(session._id)) {
|
if (tokenDigest === md5(session._id)) {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await token.delete(session._id, token.TYPE_SESSION);
|
await token.del(session._id, token.TYPE_SESSION);
|
||||||
return this.back();
|
return this.back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ class HomeSecurityHandler extends Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async postDeleteAllTokens() {
|
async postDeleteAllTokens() {
|
||||||
await token.deleteByUid(this.user._id);
|
await token.delByUid(this.user._id);
|
||||||
this.response.redirect = '/login';
|
this.response.redirect = '/login';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,7 +200,7 @@ class UserChangemailWithCodeHandler extends Handler {
|
|||||||
if (udoc) throw new UserAlreadyExistError(tdoc.mail);
|
if (udoc) throw new UserAlreadyExistError(tdoc.mail);
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
user.setEmail(this.user._id, tdoc.mail),
|
user.setEmail(this.user._id, tdoc.mail),
|
||||||
token.delete(code, token.TYPE_CHANGEMAIL),
|
token.del(code, token.TYPE_CHANGEMAIL),
|
||||||
]);
|
]);
|
||||||
this.response.redirect = '/home/security';
|
this.response.redirect = '/home/security';
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-31
@@ -311,6 +311,18 @@ class HomeworkScoreboardHandler extends HomeworkHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HomeworkScoreboardDownloadHandler extends HomeworkHandler {
|
||||||
|
async get({ domainId, tid, ext }) {
|
||||||
|
const getContent = {
|
||||||
|
csv: async (rows) => `\uFEFF${rows.map((c) => (c.map((i) => i.value).join(','))).join('\n')}`,
|
||||||
|
html: (rows) => this.renderHTML('contest_scoreboard_download_html.html', { rows }),
|
||||||
|
};
|
||||||
|
if (!getContent[ext]) throw new ValidationError('ext');
|
||||||
|
const [, rows] = await this.getScoreboard(domainId, tid, true, document.TYPE_CONTEST);
|
||||||
|
this.binary(await getContent[ext](rows), `${this.tdoc.title}.${ext}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function apply() {
|
async function apply() {
|
||||||
contest.isHomeworkExtended = (tdoc) => {
|
contest.isHomeworkExtended = (tdoc) => {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
@@ -456,15 +468,15 @@ async function apply() {
|
|||||||
Route('homework_create', '/homework/create', HomeworkCreateHandler, PERM_CREATE_HOMEWORK);
|
Route('homework_create', '/homework/create', HomeworkCreateHandler, PERM_CREATE_HOMEWORK);
|
||||||
Route('homework_detail', '/homework/:tid', HomeworkDetailHandler, PERM_VIEW_HOMEWORK);
|
Route('homework_detail', '/homework/:tid', HomeworkDetailHandler, PERM_VIEW_HOMEWORK);
|
||||||
Route('homework_scoreboard', '/homework/:tid/scoreboard', HomeworkScoreboardHandler, PERM_VIEW_HOMEWORK_SCOREBOARD);
|
Route('homework_scoreboard', '/homework/:tid/scoreboard', HomeworkScoreboardHandler, PERM_VIEW_HOMEWORK_SCOREBOARD);
|
||||||
Route('homework_problem', '/homework/:tid/p/:pid', HomeworkDetailProblemHandler, PERM_VIEW_HOMEWORK);
|
Route('homework_scoreboard', '/homework/:tid/scoreboard/download/:ext', HomeworkScoreboardDownloadHandler, PERM_VIEW_HOMEWORK_SCOREBOARD);
|
||||||
Route('homework_problem_submit', '/homework/:tid/p/:pid/submit', HomeworkDetailProblemSubmitHandler, PERM_SUBMIT_PROBLEM);
|
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_edit', '/homework/:tid/edit', HomeworkEditHandler);
|
Route('homework_edit', '/homework/:tid/edit', HomeworkEditHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Hydro.handler.homework = module.exports = apply;
|
global.Hydro.handler.homework = module.exports = apply;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
@app.route('/homework/{tid:\w{24}}/code', 'homework_code')
|
@app.route('/homework/{tid:\w{24}}/code', 'homework_code')
|
||||||
class HomeworkCodeHandler(base.OperationHandler):
|
class HomeworkCodeHandler(base.OperationHandler):
|
||||||
@base.limit_rate('homework_code', 3600, 60)
|
@base.limit_rate('homework_code', 3600, 60)
|
||||||
@@ -490,32 +502,4 @@ class HomeworkCodeHandler(base.OperationHandler):
|
|||||||
|
|
||||||
await this.binary(output_buffer.getvalue(), 'application/zip',
|
await this.binary(output_buffer.getvalue(), 'application/zip',
|
||||||
file_name='{}.zip'.format(tdoc['title']))
|
file_name='{}.zip'.format(tdoc['title']))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/homework/{tid}/scoreboard/download/{ext}', 'homework_scoreboard_download')
|
|
||||||
class HomeworkScoreboardDownloadHandler(contest.ContestMixin, base.Handler):
|
|
||||||
def _export_status_as_csv(this, rows):
|
|
||||||
# \r\n for notepad compatibility
|
|
||||||
csv_content = '\r\n'.join([','.join([str(c['value']) for c in row]) for row in rows])
|
|
||||||
data = '\uFEFF' + csv_content
|
|
||||||
return data.encode()
|
|
||||||
|
|
||||||
def _export_status_as_html(this, rows):
|
|
||||||
return this.render_html('contest_scoreboard_download_html.html', rows=rows).encode()
|
|
||||||
|
|
||||||
@base.route_argument
|
|
||||||
@base.require_perm(builtin.PERM_VIEW_HOMEWORK)
|
|
||||||
@base.require_perm(builtin.PERM_VIEW_HOMEWORK_SCOREBOARD)
|
|
||||||
@base.sanitize
|
|
||||||
async def get(this, *, tid: objectid.ObjectId, ext: str):
|
|
||||||
get_status_content = {
|
|
||||||
'csv': this._export_status_as_csv,
|
|
||||||
'html': this._export_status_as_html,
|
|
||||||
}
|
|
||||||
if ext not in get_status_content:
|
|
||||||
raise error.ValidationError('ext')
|
|
||||||
tdoc, rows, udict = await this.get_scoreboard(document.TYPE_HOMEWORK, tid, True)
|
|
||||||
data = get_status_content[ext](rows)
|
|
||||||
file_name = tdoc['title']
|
|
||||||
await this.binary(data, file_name='{}.{}'.format(file_name, ext))
|
|
||||||
*/
|
*/
|
||||||
@@ -9,10 +9,10 @@ const { Route, Handler } = require('../service/server');
|
|||||||
const coll = db.collection('status');
|
const coll = db.collection('status');
|
||||||
|
|
||||||
class FileDownloadHandler extends Handler {
|
class FileDownloadHandler extends Handler {
|
||||||
async get({ id, secret, name }) {
|
async get({ docId, secret, name }) {
|
||||||
if (name) name = Buffer.from(name, 'base64').toString();
|
if (name) name = Buffer.from(name, 'base64').toString();
|
||||||
this.response.attachment(name || id);
|
this.response.attachment(name || docId);
|
||||||
this.response.body = await file.get(id, secret);
|
this.response.body = await file.get(docId, secret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,8 +65,8 @@ class MarkdownHandler extends Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function apply() {
|
async function apply() {
|
||||||
Route('file_download', '/fs/:id/:secret', FileDownloadHandler);
|
Route('file_download', '/fs/:docId/:secret', FileDownloadHandler);
|
||||||
Route('file_download_with_name', '/fs/:id/:name/:secret', FileDownloadHandler);
|
Route('file_download_with_name', '/fs/:docId/:name/:secret', FileDownloadHandler);
|
||||||
Route('status', '/status', StatusHandler);
|
Route('status', '/status', StatusHandler);
|
||||||
Route('status_update', '/status/update', StatusUpdateHandler);
|
Route('status_update', '/status/update', StatusUpdateHandler);
|
||||||
Route('switch_language', '/language/:lang', SwitchLanguageHandler);
|
Route('switch_language', '/language/:lang', SwitchLanguageHandler);
|
||||||
|
|||||||
@@ -247,8 +247,15 @@ class ProblemSettingsHandler extends ProblemManageHandler {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
async post() {
|
async postConfig({ domainId, pid, yaml }) {
|
||||||
// TODO(masnn)
|
await problem.edit(domainId, pid, { config: yaml });
|
||||||
|
this.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
async postSetting({
|
||||||
|
domainId, pid, hidden = false, category, tag,
|
||||||
|
}) {
|
||||||
|
await problem.edit(domainId, pid, { hidden, category, tag });
|
||||||
this.back();
|
this.back();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,7 +310,7 @@ class ProblemDataDownloadHandler extends ProblemDetailHandler {
|
|||||||
if (this.user._id !== this.pdoc.owner) this.checkPerm([PERM_READ_PROBLEM_DATA, PERM_JUDGE]);
|
if (this.user._id !== this.pdoc.owner) this.checkPerm([PERM_READ_PROBLEM_DATA, PERM_JUDGE]);
|
||||||
if (!this.pdoc.data) throw new ProblemDataNotFoundError(pid);
|
if (!this.pdoc.data) throw new ProblemDataNotFoundError(pid);
|
||||||
else if (typeof this.pdoc.data === 'string') [, this.response.redirect] = this.pdoc.data.split('from:');
|
else if (typeof this.pdoc.data === 'string') [, this.response.redirect] = this.pdoc.data.split('from:');
|
||||||
this.response.redirect = file.url(this.pdoc.data, this.pdoc.title);
|
this.response.redirect = await file.url(this.pdoc.data, `${this.pdoc.title}.zip`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class UserRegisterWithCodeHandler extends Handler {
|
|||||||
await user.create({
|
await user.create({
|
||||||
uid, uname, password, mail, regip: this.request.ip,
|
uid, uname, password, mail, regip: this.request.ip,
|
||||||
});
|
});
|
||||||
await token.delete(code, token.TYPE_REGISTRATION);
|
await token.del(code, token.TYPE_REGISTRATION);
|
||||||
this.session.uid = uid;
|
this.session.uid = uid;
|
||||||
this.response.redirect = '/';
|
this.response.redirect = '/';
|
||||||
}
|
}
|
||||||
@@ -129,7 +129,7 @@ class UserLostPassWithCodeHandler extends Handler {
|
|||||||
if (!tdoc) throw new InvalidTokenError(token.TYPE_LOSTPASS, code);
|
if (!tdoc) throw new InvalidTokenError(token.TYPE_LOSTPASS, code);
|
||||||
if (password !== verifyPassword) throw new VerifyPasswordError();
|
if (password !== verifyPassword) throw new VerifyPasswordError();
|
||||||
await user.setPassword(tdoc.uid, password);
|
await user.setPassword(tdoc.uid, password);
|
||||||
await token.delete(code, token.TYPE_LOSTPASS);
|
await token.del(code, token.TYPE_LOSTPASS);
|
||||||
this.response.redirect = '/';
|
this.response.redirect = '/';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
const yaml = require('js-yaml');
|
||||||
|
|
||||||
|
function convert(ini) {
|
||||||
|
const f = ini.split('\n');
|
||||||
|
const count = f[0];
|
||||||
|
const res = { cases: [] };
|
||||||
|
for (let i = 1; i <= count; i++) {
|
||||||
|
const [input, output, time, score, memory] = f[i].split('|');
|
||||||
|
const cur = { input, output, score };
|
||||||
|
if (parseInt(time) !== 1) cur.time = `${time}s`;
|
||||||
|
if (!Number.isNaN(parseInt(memory))) cur.memory = parseInt(memory) / 1024;
|
||||||
|
res.cases.push(cur);
|
||||||
|
}
|
||||||
|
return yaml.safeDump(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
global.Hydro.lib['testdata.convert.ini'] = module.exports = convert;
|
||||||
+16
-7
@@ -90,7 +90,7 @@ function getMultiStatus(domainId, query) {
|
|||||||
* @returns {Pdoc}
|
* @returns {Pdoc}
|
||||||
*/
|
*/
|
||||||
function edit(domainId, _id, $set) {
|
function edit(domainId, _id, $set) {
|
||||||
return document.set(domainId, document.TYPE_CONTEST, _id, $set);
|
return document.set(domainId, document.TYPE_PROBLEM, _id, $set);
|
||||||
}
|
}
|
||||||
|
|
||||||
function inc(domainId, _id, field, n) {
|
function inc(domainId, _id, field, n) {
|
||||||
@@ -111,13 +111,21 @@ async function random(domainId, query) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getList(domainId, pids, doThrow = true) {
|
async function getList(domainId, pids, doThrow = true) {
|
||||||
|
pids = new Set(pids);
|
||||||
const r = {};
|
const r = {};
|
||||||
if (doThrow) {
|
const pdocs = await document.getMulti(
|
||||||
// eslint-disable-next-line no-await-in-loop
|
domainId, document.TYPE_PROBLEM,
|
||||||
for (const pid of pids) r[pid] = await get(domainId, pid);
|
{ $or: [{ docId: { $in: pids } }, { pid: { $in: pids } }] },
|
||||||
} else {
|
).toArray();
|
||||||
// eslint-disable-next-line no-await-in-loop
|
for (const pdoc of pdocs) {
|
||||||
for (const pid of pids) r[pid] = await get(domainId, pid).catch((e) => e);
|
r[pdoc.docId] = r[pdoc.pid] = pdoc;
|
||||||
|
}
|
||||||
|
if (pdocs.length !== pids.size) {
|
||||||
|
if (doThrow) {
|
||||||
|
for (const pid of pids) {
|
||||||
|
if (!r[pid]) throw new ProblemNotFoundError(domainId, pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@@ -141,6 +149,7 @@ async function updateStatus(domainId, pid, uid, rid, status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function setTestdata(domainId, _id, readStream) {
|
async function setTestdata(domainId, _id, readStream) {
|
||||||
|
// TODO read config in zipfile
|
||||||
const pdoc = await get(domainId, _id);
|
const pdoc = await get(domainId, _id);
|
||||||
const id = await file.add(readStream, 'data.zip');
|
const id = await file.add(readStream, 'data.zip');
|
||||||
if (pdoc.data && typeof pdoc.data === 'object') file.dec(this.pdoc.data);
|
if (pdoc.data && typeof pdoc.data === 'object') file.dec(this.pdoc.data);
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ async function judge(domainId, rid) {
|
|||||||
type: 'judge',
|
type: 'judge',
|
||||||
rid,
|
rid,
|
||||||
domainId,
|
domainId,
|
||||||
|
config: pdoc.config || '',
|
||||||
pid: rdoc.pid,
|
pid: rdoc.pid,
|
||||||
data: pdoc.data,
|
data: pdoc.data,
|
||||||
lang: rdoc.lang,
|
lang: rdoc.lang,
|
||||||
|
|||||||
+89
-67
@@ -3,85 +3,107 @@ const { ValidationError } = require('../error');
|
|||||||
|
|
||||||
const coll = db.collection('token');
|
const coll = db.collection('token');
|
||||||
|
|
||||||
function index() {
|
function ensureIndexes() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
coll.createIndex([{ uid: 1 }, { tokenType: 1 }, { updateAt: -1 }], { sparse: true }),
|
coll.createIndex([{ uid: 1 }, { tokenType: 1 }, { updateAt: -1 }], { sparse: true }),
|
||||||
coll.createIndex('expireAt', { expireAfterSeconds: 0 }),
|
coll.createIndex('expireAt', { expireAfterSeconds: 0 }),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a token.
|
||||||
|
* @param {number} tokenType type of the token.
|
||||||
|
* @param {number} expireSeconds expire time, in seconds.
|
||||||
|
* @param {object} data extra data.
|
||||||
|
* @returns {Promise<Array>} token ID, token data
|
||||||
|
*/
|
||||||
|
async function add(tokenType, expireSeconds, data) {
|
||||||
|
const now = new Date();
|
||||||
|
const str = String.random(32);
|
||||||
|
const res = await coll.insertOne({
|
||||||
|
...data,
|
||||||
|
_id: str,
|
||||||
|
tokenType,
|
||||||
|
createAt: now,
|
||||||
|
updateAt: now,
|
||||||
|
expireAt: new Date(now.getTime() + expireSeconds * 1000),
|
||||||
|
});
|
||||||
|
return [str, res.ops];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a token.
|
||||||
|
* @param {string} tokenId token ID.
|
||||||
|
* @param {number} tokenType type of the token.
|
||||||
|
* @returns {Promise<object>} The token document, or null.
|
||||||
|
*/
|
||||||
|
async function get(tokenId, tokenType) {
|
||||||
|
const res = await coll.findOne({ _id: tokenId, tokenType });
|
||||||
|
if (!res) throw new ValidationError('token');
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a token.
|
||||||
|
* @param {string} tokenId token ID.
|
||||||
|
* @param {number} tokenType type of the token.
|
||||||
|
* @param {number} expireSeconds expire time, in seconds.
|
||||||
|
* @param {object} data extra data.
|
||||||
|
* @returns {Promise<object>} The token document, or null.
|
||||||
|
*/
|
||||||
|
async function update(tokenId, tokenType, expireSeconds, data) {
|
||||||
|
const now = new Date();
|
||||||
|
const res = await coll.findOneAndUpdate({ _id: tokenId, tokenType }, {
|
||||||
|
$set: {
|
||||||
|
...data,
|
||||||
|
updateAt: now,
|
||||||
|
expireAt: new Date(now.getTime() + expireSeconds * 1000),
|
||||||
|
tokenType,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return res.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a token.
|
||||||
|
* @param {string} tokenId token ID.
|
||||||
|
* @param {int} tokenType type of the token.
|
||||||
|
* @returns {Promise<boolean>} true if deleted, or false.
|
||||||
|
*/
|
||||||
|
async function del(tokenId, tokenType) {
|
||||||
|
const result = await coll.deleteOne({ _id: tokenId, tokenType });
|
||||||
|
return !!result.deletedCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a token(or create one)
|
||||||
|
* @param {number} tokenType type of the token.
|
||||||
|
* @param {number} expireSeconds expire time, in seconds.
|
||||||
|
* @param {object} data extra data.
|
||||||
|
* @returns {Array} token ID, token data
|
||||||
|
*/
|
||||||
|
async function createOrUpdate(tokenType, expireSeconds, data) {
|
||||||
|
const d = await coll.findOne({ tokenType, ...data });
|
||||||
|
if (!d) {
|
||||||
|
const res = await add(tokenType, expireSeconds, data);
|
||||||
|
return res[0];
|
||||||
|
}
|
||||||
|
await update(d._id, tokenType, expireSeconds, data);
|
||||||
|
return d._id;
|
||||||
|
}
|
||||||
|
|
||||||
global.Hydro.model.token = module.exports = {
|
global.Hydro.model.token = module.exports = {
|
||||||
TYPE_SESSION: 0,
|
TYPE_SESSION: 0,
|
||||||
TYPE_CSRF_TOKEN: 1,
|
TYPE_CSRF_TOKEN: 1,
|
||||||
TYPE_REGISTER: 2,
|
TYPE_REGISTER: 2,
|
||||||
TYPE_CHANGEMAIL: 3,
|
TYPE_CHANGEMAIL: 3,
|
||||||
|
|
||||||
index,
|
ensureIndexes,
|
||||||
|
add,
|
||||||
/**
|
createOrUpdate,
|
||||||
* Add a token.
|
get,
|
||||||
* @param {number} tokenType type of the token.
|
update,
|
||||||
* @param {number} expireSeconds expire time, in seconds.
|
del,
|
||||||
* @param {object} data extra data.
|
|
||||||
* @returns {Array} token ID, token data
|
|
||||||
*/
|
|
||||||
async add(tokenType, expireSeconds, data) {
|
|
||||||
const now = new Date();
|
|
||||||
const str = String.random(32);
|
|
||||||
const res = await coll.insertOne({
|
|
||||||
...data,
|
|
||||||
_id: str,
|
|
||||||
tokenType,
|
|
||||||
createAt: now,
|
|
||||||
updateAt: now,
|
|
||||||
expireAt: new Date(now.getTime() + expireSeconds * 1000),
|
|
||||||
});
|
|
||||||
return [str, res.ops];
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a token.
|
|
||||||
* @param {string} tokenId token ID.
|
|
||||||
* @param {number} tokenType type of the token.
|
|
||||||
* @returns {object} The token document, or null.
|
|
||||||
*/
|
|
||||||
async get(tokenId, tokenType) {
|
|
||||||
const res = await coll.findOne({ _id: tokenId, tokenType });
|
|
||||||
if (!res) throw new ValidationError('token');
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a token.
|
|
||||||
* @param {string} tokenId token ID.
|
|
||||||
* @param {number} tokenType type of the token.
|
|
||||||
* @param {number} expireSeconds expire time, in seconds.
|
|
||||||
* @param {object} data extra data.
|
|
||||||
* @returns {object} The token document, or null.
|
|
||||||
*/
|
|
||||||
async update(tokenId, tokenType, expireSeconds, data) {
|
|
||||||
const now = new Date();
|
|
||||||
const res = await coll.findOneAndUpdate({ _id: tokenId, tokenType }, {
|
|
||||||
$set: {
|
|
||||||
...data,
|
|
||||||
updateAt: now,
|
|
||||||
expireAt: new Date(now.getTime() + expireSeconds * 1000),
|
|
||||||
tokenType,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return res.value;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a token.
|
|
||||||
* @param {string} tokenId token ID.
|
|
||||||
* @param {int} tokenType type of the token.
|
|
||||||
* @returns {boolean} true if deleted, or false.
|
|
||||||
*/
|
|
||||||
async delete(tokenId, tokenType) {
|
|
||||||
const result = await coll.deleteOne({ _id: tokenId, tokenType });
|
|
||||||
return !!result.deletedCount;
|
|
||||||
},
|
|
||||||
deleteByUid(uid) {
|
deleteByUid(uid) {
|
||||||
return coll.deleteMany({ uid });
|
return coll.deleteMany({ uid });
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -224,7 +224,7 @@ function deleteRoles(domainId, roles) {
|
|||||||
function ban(uid) {
|
function ban(uid) {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
coll.updateOne({ _id: uid }, { $set: { ban: true } }),
|
coll.updateOne({ _id: uid }, { $set: { ban: true } }),
|
||||||
token.deleteByUid(uid),
|
token.delByUid(uid),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ const validate = {
|
|||||||
if (page <= 0) throw new ValidationError('page');
|
if (page <= 0) throw new ValidationError('page');
|
||||||
return page;
|
return page;
|
||||||
},
|
},
|
||||||
|
category: (category) => category.split(',').map((i) => i.trim()),
|
||||||
|
tag: (tag) => tag.split(',').map((i) => i.trim()),
|
||||||
|
hidden: (hidden) => !!hidden,
|
||||||
duration: (duration) => {
|
duration: (duration) => {
|
||||||
if (!Number.isNaN(parseFloat(duration))) duration = parseFloat(duration);
|
if (!Number.isNaN(parseFloat(duration))) duration = parseFloat(duration);
|
||||||
if (duration <= 0) throw new ValidationError('duration');
|
if (duration <= 0) throw new ValidationError('duration');
|
||||||
@@ -76,6 +79,10 @@ const validate = {
|
|||||||
assert(typeof penaltyRules === 'object', new ValidationError('penalty_rules', 'invalid format'));
|
assert(typeof penaltyRules === 'object', new ValidationError('penalty_rules', 'invalid format'));
|
||||||
return penaltyRules;
|
return penaltyRules;
|
||||||
},
|
},
|
||||||
|
yaml: (input) => {
|
||||||
|
yaml.safeLoad(input);
|
||||||
|
return input;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function prepare() {
|
async function prepare() {
|
||||||
@@ -222,7 +229,7 @@ class Handler {
|
|||||||
if (bdoc) throw new BlacklistedError(this.request.ip);
|
if (bdoc) throw new BlacklistedError(this.request.ip);
|
||||||
this.user = await user.getById(domainId, this.session.uid);
|
this.user = await user.getById(domainId, this.session.uid);
|
||||||
if (!this.user) throw new UserNotFoundError(this.session.uid);
|
if (!this.user) throw new UserNotFoundError(this.session.uid);
|
||||||
[this.csrfToken] = await token.add(token.TYPE_CSRF_TOKEN, 600, {
|
this.csrfToken = await token.createOrUpdate(token.TYPE_CSRF_TOKEN, 600, {
|
||||||
path: this.request.path,
|
path: this.request.path,
|
||||||
uid: this.session.uid,
|
uid: this.session.uid,
|
||||||
});
|
});
|
||||||
@@ -400,7 +407,7 @@ class ConnectionHandler {
|
|||||||
async ___prepare({ domainId }) {
|
async ___prepare({ domainId }) {
|
||||||
try {
|
try {
|
||||||
this.session = await token.get(this.request.params.token, token.TYPE_CSRF_TOKEN);
|
this.session = await token.get(this.request.params.token, token.TYPE_CSRF_TOKEN);
|
||||||
await token.delete(this.request.params.token, token.TYPE_CSRF_TOKEN);
|
await token.del(this.request.params.token, token.TYPE_CSRF_TOKEN);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.session = { uid: 1 };
|
this.session = { uid: 1 };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
{% macro render_problem_title(pdoc, tdoc=none, prefix='/c', show_tags=true, show_invisible_flag=true, rp=none, invalid=false) %}
|
{% macro render_problem_title(pdoc, tdoc=none, show_tags=true, show_invisible_flag=true, rp=none, invalid=false) %}
|
||||||
{%- if not invalid %}
|
{%- if not invalid %}
|
||||||
<a
|
<a
|
||||||
{%- if tdoc %}
|
{%- if tdoc and tdoc.docType == model.document.TYPE_CONTEST %}
|
||||||
{# TODO(masnn) use url() #}
|
href="{{ url('contest_detail_problem', tid=tdoc.docId, pid=pdoc.docId) }}"
|
||||||
href="{{ prefix }}/{{ tdoc.docId }}/p/{{ pdoc.docId }}"
|
{%- elif tdoc and tdoc.docType == model.document.TYPE_HOMEWORK %}
|
||||||
|
href="{{ url('homework_detail_problem', tid=tdoc.docId, pid=pdoc.docId) }}"
|
||||||
{%- else %}
|
{%- else %}
|
||||||
href="{{ url('problem_detail', pid=pdoc.pid) }}"
|
href="{{ url('problem_detail', pid=pdoc.pid) }}"
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<td class="col--problem col--problem-name">
|
<td class="col--problem col--problem-name">
|
||||||
#{{ loop.index }}:
|
#{{ loop.index }}:
|
||||||
{{ problem.render_problem_title(pdict[pid], tdoc=tdoc, prefix='/homework', show_invisible_flag=false, show_tags=false) }}
|
{{ problem.render_problem_title(pdict[pid], tdoc=tdoc, show_invisible_flag=false, show_tags=false) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -14,6 +14,29 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="section">
|
||||||
|
<div class="section__header">
|
||||||
|
<h1 class="section__title">{{ _('Testdata Config') }}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="section__body">
|
||||||
|
<form method="post">
|
||||||
|
{{ form.form_textarea({
|
||||||
|
columns:null,
|
||||||
|
label:'Config',
|
||||||
|
name:'yaml',
|
||||||
|
value:pdoc['config']|default(''),
|
||||||
|
yaml:true
|
||||||
|
}) }}
|
||||||
|
<div class="row"><div class="columns">
|
||||||
|
<input type="hidden" name="operation" value="config">
|
||||||
|
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||||
|
<button type="submit" class="rounded primary button">
|
||||||
|
{{ _('Update') }}
|
||||||
|
</button>
|
||||||
|
</div></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="section__header">
|
<div class="section__header">
|
||||||
<h1 class="section__title">{{ _('Settings') }}</h1>
|
<h1 class="section__title">{{ _('Settings') }}</h1>
|
||||||
@@ -41,6 +64,7 @@
|
|||||||
value:pdoc['tag']|join(', ')
|
value:pdoc['tag']|join(', ')
|
||||||
}) }}
|
}) }}
|
||||||
<div class="row"><div class="columns">
|
<div class="row"><div class="columns">
|
||||||
|
<input type="hidden" name="operation" value="settings">
|
||||||
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
<input type="hidden" name="csrfToken" value="{{ handler.csrfToken }}">
|
||||||
<button type="submit" class="rounded primary button">
|
<button type="submit" class="rounded primary button">
|
||||||
{{ _('Update') }}
|
{{ _('Update') }}
|
||||||
|
|||||||
Reference in new issue
Block a user