remove domain system

This commit is contained in:
masnn committed 2020-04-03 20:56:08 +08:00
1 parent 7fa4a58abe
commit fc26af30ef
31 files changed
+709 -1242

No files matched your search

+1 -1
View File
@@ -31,6 +31,6 @@
],
"no-unused-vars": "warn",
"no-unreachable": "warn",
"require-atomic-updates": "warn"
"require-atomic-updates": "off"
}
}
+2 -1
View File
@@ -1,2 +1,3 @@
node_modules/
config.yaml
config.yaml
.uibuild/
+2 -1
View File
@@ -27,9 +27,10 @@ async function run() {
});
});
let server = require('./service/server');
require('./lib/i18n');
require('./handler/ui');
require('./handler/base');
require('./handler/home');
require('./handler/user');
require('./handler/problem');
require('./handler/record');
require('./handler/judge');
+10 -64
View File
@@ -1,5 +1,3 @@
const { PRIV_USER_PROFILE } = require('./privilege');
class UserFacingError extends Error {
constructor(type) {
super(type);
@@ -62,12 +60,6 @@ class VerifyPasswordError extends ForbiddenError {
super('VerifyPasswordError');
}
}
class PrivilegeError extends ForbiddenError {
constructor(priv) {
super('PrivilegeError');
this.params = [priv];
}
}
class OpcountExceededError extends ForbiddenError {
constructor(op, period_secs, max_operations) {
super('OpcountExceededError');
@@ -75,25 +67,6 @@ class OpcountExceededError extends ForbiddenError {
}
}
class DomainAlreadyExistError extends ForbiddenError {
constructor(domain) {
super('DomainAlreadyExistError');
this.params = [domain];
}
}
class DomainNotFoundError extends ForbiddenError {
constructor(domain) {
super('DomainNotFoundError');
this.params = [domain];
}
}
class UserAlreadyDomainMemberError extends ForbiddenError {
constructor(domainId, uid) {
super('UserAlreadyDomainMemberError');
this.params = [domainId, uid];
}
}
class PermissionError extends ForbiddenError {
constructor(perm) {
super('PermissionError');
@@ -108,35 +81,34 @@ class ValidationError extends ForbiddenError {
}
}
class ProblemNotFoundError extends NotFoundError {
constructor(domainId, pid) {
constructor(pid) {
super('ProblemNotFoundError');
this.params = [domainId, pid];
this.params = [pid];
}
}
class RecordNotFoundError extends NotFoundError {
constructor(domainId, rid) {
constructor(rid) {
super('RecordNotFoundError');
this.params = [domainId, rid];
this.params = [rid];
}
}
class TrainingNotFoundError extends NotFoundError {
constructor(domainId, tid) {
constructor(tid) {
super('TrainingNotFoundError');
this.params = [domainId, tid];
this.params = [tid];
}
}
class ContestNotFoundError extends NotFoundError {
constructor(domainId, cid) {
constructor(cid) {
super('ContestNotFoundError');
this.params = [domainId, cid];
this.params = [cid];
}
}
module.exports = {
BadRequestError, ForbiddenError, NotFoundError,
LoginError, UserAlreadyExistError, InvalidTokenError,
UserNotFoundError, VerifyPasswordError, PrivilegeError,
OpcountExceededError, DomainAlreadyExistError, DomainNotFoundError,
UserAlreadyDomainMemberError, PermissionError, NoProblemError,
UserNotFoundError, VerifyPasswordError,
OpcountExceededError, PermissionError, NoProblemError,
ValidationError, ProblemNotFoundError, TrainingNotFoundError,
ContestNotFoundError, RecordNotFoundError
};
@@ -176,11 +148,6 @@ class UserFacingError(Error):
return 'An error has occurred.'
class BuiltinDomainError(ForbiddenError):
@property
def message(self):
return 'Domain {0} is bulit-in and cannot be modified.'
class BlacklistedError(ForbiddenError):
@property
@@ -284,20 +251,6 @@ class MessageNotFoundError(NotFoundError):
return 'Message {0} not found.'
class DomainJoinForbiddenError(ForbiddenError):
@property
def message(self):
return 'You are not allowed to join the domain. The link is either invalid or expired.'
class DomainJoinAlreadyMemberError(ForbiddenError):
@property
def message(self):
return 'Failed to join the domain. You are already a member.'
class InvalidJoinInvitationCodeError(ForbiddenError):
@property
def message(self):
@@ -369,13 +322,6 @@ class UsageExceededError(ForbiddenError):
def message(self):
return 'Usage exceeded.'
class DomainRoleAlreadyExistError(ForbiddenError):
@property
def message(self):
return 'Role {1} already exists in domain {0}.'
class ModifyBuiltinRoleError(ForbiddenError):
@property
def message(self):
+2 -25
View File
@@ -1,20 +1,17 @@
const
{ MIDDLEWARE } = require('../service/server.js'),
user = require('../model/user'),
domain = require('../model/domain'),
blacklist = require('../model/blacklist'),
token = require('../model/token'),
options = require('../options'),
{
UserNotFoundError,
BlacklistedError,
PermissionError,
PrivilegeError
PermissionError
} = require('../error');
MIDDLEWARE(async (ctx, next) => {
try {
ctx.state.domainId = 'system';
let sid = ctx.cookies.get('sid');
let save = ctx.cookies.get('save');
let tokenType, expireSeconds;
@@ -33,11 +30,7 @@ MIDDLEWARE(async (ctx, next) => {
if (!ctx.session) ctx.session = { uid: 1 };
let bdoc = await blacklist.get(ctx.request.ip);
if (bdoc) throw new BlacklistedError(ctx.request.ip);
console.log(ctx.session.uid);
[ctx.state.user, ctx.state.domain] = await Promise.all([
user.getById(ctx.session.uid, ctx.state.domainId),
domain.get(ctx.state.domainId)
]);
ctx.state.user = await user.getById(ctx.session.uid);
if (!ctx.state.user) throw new UserNotFoundError(ctx.session.uid);
ctx.checkPerm = function (perm) {
for (let i in arguments) {
@@ -55,22 +48,6 @@ MIDDLEWARE(async (ctx, next) => {
}
}
};
ctx.checkPriv = function (priv) {
for (let i in arguments) {
if (arguments[i] instanceof Array) {
let p = false;
for (let j in arguments)
if (ctx.state.user.hasPriv(arguments[i][j])) {
p = true;
break;
}
if (!p) throw new PrivilegeError([arguments[i]]);
} else {
if (ctx.state.user.hasPriv(arguments[i])) continue;
else throw new PrivilegeError([[arguments[i]]]);
}
}
};
await next();
if (ctx.session.sid)
await token.update(ctx.session.sid, tokenType, expireSeconds, Object.assign({
+19 -418
View File
@@ -1,434 +1,35 @@
const
{ GET, SOCKET } = require('../service/server'),
{ assertQueue, consume } = require('../service/queue'),
{ requirePriv } = require('./tools'),
{ CONTEXT } = require('../service/server'),
queue = require('../service/queue'),
record = require('../model/record'),
token = require('../model/token'),
user = require('../model/user'),
{ PRIV_JUDGE } = require('../privilege');
{ requirePerm } = require('./tools'),
{ PERM_JUDGE } = require('../permission');
assertQueue('judge');
queue.assert('judge');
GET('/judge/noop', requirePriv(PRIV_JUDGE), async ctx => {
const { MIDDLEWARE, GET, POST } = CONTEXT();
MIDDLEWARE(requirePerm(PERM_JUDGE));
GET('/judge/noop', async ctx => {
ctx.body = {};
});
SOCKET('/judge/conn', async conn => {
let sid = await new Promise((resolve, reject) => {
setTimeout(reject, 5000);
conn.once('data', async (data) => {
console.log(data);
resolve(data);
});
});
let session = await token.get(sid, token.TYPE_SESSION);
let u = await user.getById(session.uid);
if (!u.hasPriv(PRIV_JUDGE)) conn.close(1000, 'PrivilegeError');
let h = 1;
let interval = setInterval(() => {
if (!h) h.close();
h = 0;
conn.write('h');
}, 30000);
conn.on('data', (data) => {
data = JSON.parse(data);
h++;
if (data.event == 'next') {
// TODO (masnn)
} else if (data.event == 'end') {
// TODO (masnn)
}
});
consume('judge', async rid => {
rid = rid.toString();
GET('/judge/fetch', async ctx => {
let rid = await queue.get('judge', false);
if (rid) {
let rdoc = await record.get(rid);
let data = {
event: 'judge',
rid, type: 0,
domainId: rdoc.domainId,
pid: rdoc.pid,
data: rdoc.data,
lang: rdoc.lang,
code: rdoc.code
};
conn.write(JSON.stringify(data));
});
conn.on('close', () => {
clearInterval(interval);
});
ctx.body = data;
}
});
POST('/judge/next', async ctx => {
console.log(ctx.request.body);
});
POST('/judge/end', async ctx => {
console.log(ctx.request.body);
});
/*
@app.route('/p/category/{category:[^/]*}/random', 'problem_category_random')
class ProblemCategoryRandomHandler(base.Handler):
@base.requirePerm(builtin.PERM_VIEW_PROBLEM)
@base.get_argument
@base.route_argument
@base.sanitize
async def get(self, *, category: str):
if not self.has_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN):
f = {'hidden': False}
else:
f = {}
query = ProblemCategoryHandler.build_query(category)
pid = await problem.get_random_id(self.domainId, **query, **f)
if pid:
self.json_or_redirect(self.reverse_url('problem_detail', pid=pid))
else:
self.json_or_redirect(self.referer_or_main)
@app.route('/p/{pid}/submit', 'problem_submit')
class ProblemSubmitHandler(base.Handler):
@base.requirePerm(builtin.PERM_SUBMIT_PROBLEM)
@base.route_argument
@base.sanitize
async def get(self, *, pid: document.convert_doc_id):
# TODO(twd2): check status, eg. test, hidden problem, ...
uid = self.user['_id'] if self.has_priv(builtin.PRIV_USER_PROFILE) else None
pdoc = await problem.get(self.domainId, pid, uid)
if pdoc.get('hidden', False):
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
udoc, dudoc = await asyncio.gather(user.get_by_uid(pdoc['owner_uid']),
domain.get_user(self.domainId, pdoc['owner_uid']))
if uid == None:
rdocs = []
else:
# TODO(iceboy): needs to be in sync with contest_detail_problem_submit
rdocs = await record \
.get_user_in_problem_multi(uid, self.domainId, pdoc['doc_id']) \
.sort([('_id', -1)]) \
.limit(10) \
.to_list()
if not self.prefer_json:
path_components = self.build_path(
(self.translate('problem_main'), self.reverse_url('problem_main')),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['doc_id'])),
(self.translate('problem_submit'), None))
self.render('problem_submit.html', pdoc=pdoc, udoc=udoc, rdocs=rdocs, dudoc=dudoc,
page_title=pdoc['title'], path_components=path_components)
else:
self.json({'rdocs': rdocs})
@base.requirePriv(builtin.PRIV_USER_PROFILE)
@base.requirePerm(builtin.PERM_SUBMIT_PROBLEM)
@base.route_argument
@base.post_argument
@base.require_csrf_token
@base.sanitize
@base.limitRate('add_record', 60, 100)
async def post(self, *, pid: document.convert_doc_id, lang: str, code: str):
# TODO(twd2): check status, eg. test, hidden problem, ...
pdoc = await problem.get(self.domainId, pid)
if pdoc.get('hidden', False):
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
rid = await record.add(self.domainId, pdoc['doc_id'], constant.record.TYPE_SUBMISSION,
self.user['_id'], lang, code)
self.json_or_redirect(self.reverse_url('record_detail', rid=rid))
@app.route('/p/{pid}/pretest', 'problem_pretest')
class ProblemPretestHandler(base.Handler):
@base.requirePerm(builtin.PERM_SUBMIT_PROBLEM)
@base.route_argument
@base.post_argument
@base.require_csrf_token
@base.sanitize
@base.limitRate('add_record', 60, 100)
async def post(self, *, pid: document.convert_doc_id, lang: str, code: str,
data_input: str, data_output: str):
pdoc = await problem.get(self.domainId, pid)
# don't need to check hidden status
# create zip file, TODO(twd2): check file size
post = await self.request.post()
content = list(zip(post.getall('data_input'), post.getall('data_output')))
output_buffer = io.BytesIO()
zip_file = zipfile.ZipFile(output_buffer, 'a', zipfile.ZIP_DEFLATED)
config_content = str(len(content)) + '\n'
for i, (data_input, data_output) in enumerate(content):
input_file = 'input{0}.txt'.format(i)
output_file = 'output{0}.txt'.format(i)
config_content += '{0}|{1}|1|10|262144\n'.format(input_file, output_file)
zip_file.writestr('Input/{0}'.format(input_file), data_input)
zip_file.writestr('Output/{0}'.format(output_file), data_output)
zip_file.writestr('Config.ini', config_content)
# mark all files as created in Windows :p
for zfile in zip_file.filelist:
zfile.create_system = 0
zip_file.close()
fid = await fs.add_data('application/zip', output_buffer.getvalue())
output_buffer.close()
rid = await record.add(self.domainId, pdoc['doc_id'], constant.record.TYPE_PRETEST,
self.user['_id'], lang, code, fid)
self.json_or_redirect(self.reverse_url('record_detail', rid=rid))
@app.connection_route('/p/{pid}/pretest-conn', 'problem_pretest-conn')
class ProblemPretestConnection(record_handler.RecordVisibilityMixin, base.Connection):
async def on_open(self):
await super(ProblemPretestConnection, self).on_open()
self.pid = document.convert_doc_id(self.request.match_info['pid'])
bus.subscribe(self.on_record_change, ['record_change'])
async def on_record_change(self, e):
rdoc = e['value']
if rdoc['uid'] != self.user['_id'] or \
rdoc['domainId'] != self.domainId or rdoc['pid'] != self.pid:
return
# check permission for visibility: contest
if rdoc['tid']:
show_status, tdoc = await self.rdoc_contest_visible(rdoc)
if not show_status:
return
self.send(rdoc=rdoc)
async def on_close(self):
bus.unsubscribe(self.on_record_change)
@app.route('/p/{pid}/data', 'problem_data')
class ProblemDataHandler(base.Handler):
@base.route_argument
@base.sanitize
async def get(self, *, pid: document.convert_doc_id):
# Judges will have PRIV_READ_PROBLEM_DATA,
# domain administrators will have PERM_READ_PROBLEM_DATA,
# problem owner will have PERM_READ_PROBLEM_DATA_SELF.
pdoc = await problem.get(self.domainId, pid)
if type(pdoc['data']) is dict:
return self.redirect(self.reverse_url('problem_data',
domainId=pdoc['data']['domain'],
pid=pdoc['data']['pid']))
if (not self.own(pdoc, builtin.PERM_READ_PROBLEM_DATA_SELF)
and not self.has_perm(builtin.PERM_READ_PROBLEM_DATA)):
self.check_priv(builtin.PRIV_READ_PROBLEM_DATA)
fdoc = await problem.get_data(pdoc)
if not fdoc:
raise error.ProblemDataNotFoundError(self.domainId, pid)
self.redirect(options.cdn_prefix.rstrip('/') + \
self.reverse_url('fs_get', domainId=builtin.domainId_SYSTEM,
secret=fdoc['metadata']['secret']))
@app.route('/p/copy', 'problem_copy')
class ProblemCopyHandler(base.Handler):
MAX_PROBLEMS_PER_REQUEST = 20
@base.requirePriv(builtin.PRIV_USER_PROFILE)
@base.requirePerm(builtin.PERM_CREATE_PROBLEM)
async def get(self):
self.render('problem_copy.html')
@base.requirePriv(builtin.PRIV_USER_PROFILE)
@base.requirePerm(builtin.PERM_CREATE_PROBLEM)
@base.post_argument
@base.require_csrf_token
@base.sanitize
@base.limitRate('copy_problems', 30, 10)
async def post(self, *, src_domainId: str, src_pids: str,
numeric_pid: bool=False, hidden: bool=False):
src_ddoc, src_dudoc = await asyncio.gather(domain.get(src_domainId),
domain.get_user(src_domainId, self.user['_id']))
if not src_dudoc:
src_dudoc = {}
if not self.dudoc_has_perm(ddoc=src_ddoc, dudoc=src_dudoc, udoc=self.user,
perm=builtin.PERM_VIEW_PROBLEM):
# TODO: This is the source domain's PermissionError.
raise error.PermissionError(builtin.PERM_VIEW_PROBLEM)
src_pids = misc.dedupe(map(document.convert_doc_id, src_pids.replace('\r\n', '\n').split('\n')))
if len(src_pids) > self.MAX_PROBLEMS_PER_REQUEST:
raise error.BatchCopyLimitExceededError(self.MAX_PROBLEMS_PER_REQUEST, len(src_pids))
pdocs = await problem.get_multi(domainId=src_domainId, doc_id={'$in': src_pids}) \
.sort('doc_id', 1) \
.to_list()
exist_pids = [pdoc['doc_id'] for pdoc in pdocs]
if len(src_pids) != len(exist_pids):
for pid in src_pids:
if pid not in exist_pids:
raise error.ProblemNotFoundError(src_domainId, pid)
for pdoc in pdocs:
if pdoc.get('hidden', False):
if not self.dudoc_has_perm(ddoc=src_ddoc, dudoc=src_dudoc, udoc=self.user,
perm=builtin.PERM_VIEW_PROBLEM_HIDDEN):
# TODO: This is the source domain's PermissionError.
raise error.PermissionError(builtin.PERM_VIEW_PROBLEM_HIDDEN)
for pdoc in pdocs:
pid = None
if numeric_pid:
pid = await domain.inc_pid_counter(self.domainId)
await problem.copy(pdoc, self.domainId, self.user['_id'], pid, hidden)
self.redirect(self.reverse_url('problem_main'))
@app.route('/p/{pid}/edit', 'problem_edit')
class ProblemEditHandler(base.Handler):
@base.requirePriv(builtin.PRIV_USER_PROFILE)
@base.route_argument
@base.sanitize
async def get(self, *, pid: document.convert_doc_id):
uid = self.user['_id'] if self.has_priv(builtin.PRIV_USER_PROFILE) else None
pdoc = await problem.get(self.domainId, pid, uid)
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
self.check_perm(builtin.PERM_EDIT_PROBLEM)
udoc, dudoc = await asyncio.gather(user.get_by_uid(pdoc['owner_uid']),
domain.get_user(self.domainId, pdoc['owner_uid']))
path_components = self.build_path(
(self.translate('problem_main'), self.reverse_url('problem_main')),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['doc_id'])),
(self.translate('problem_edit'), None))
self.render('problem_edit.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
page_title=pdoc['title'], path_components=path_components)
@base.requirePriv(builtin.PRIV_USER_PROFILE)
@base.route_argument
@base.post_argument
@base.require_csrf_token
@base.sanitize
async def post(self, *, pid: document.convert_doc_id, title: str, content: str):
pdoc = await problem.get(self.domainId, pid)
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
self.check_perm(builtin.PERM_EDIT_PROBLEM)
await problem.edit(self.domainId, pdoc['doc_id'], title=title, content=content)
self.json_or_redirect(self.reverse_url('problem_detail', pid=pid))
@app.route('/p/{pid}/settings', 'problem_settings')
class ProblemSettingsHandler(base.Handler):
@base.requirePriv(builtin.PRIV_USER_PROFILE)
@base.route_argument
@base.sanitize
async def get(self, *, pid: document.convert_doc_id):
uid = self.user['_id'] if self.has_priv(builtin.PRIV_USER_PROFILE) else None
pdoc = await problem.get(self.domainId, pid, uid)
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
self.check_perm(builtin.PERM_EDIT_PROBLEM)
udoc, dudoc = await asyncio.gather(user.get_by_uid(pdoc['owner_uid']),
domain.get_user(self.domainId, pdoc['owner_uid']))
path_components = self.build_path(
(self.translate('problem_main'), self.reverse_url('problem_main')),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['doc_id'])),
(self.translate('problem_settings'), None))
self.render('problem_settings.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
categories=problem.get_categories(),
page_title=pdoc['title'], path_components=path_components)
def split_tags(self, s):
s = s.replace(',', ',') # Chinese ', '
return list(filter(lambda _: _ != '', map(lambda _: _.strip(), s.split(','))))
@base.requirePriv(builtin.PRIV_USER_PROFILE)
@base.route_argument
@base.post_argument
@base.require_csrf_token
@base.sanitize
async def post(self, *, pid: document.convert_doc_id, hidden: bool=False,
category: str, tag: str,
difficulty_setting: int, difficulty_admin: str=''):
pdoc = await problem.get(self.domainId, pid)
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
self.check_perm(builtin.PERM_EDIT_PROBLEM)
category = self.split_tags(category)
tag = self.split_tags(tag)
for c in category:
if not (c in builtin.PROBLEM_CATEGORIES
or c in builtin.PROBLEM_SUB_CATEGORIES):
raise error.ValidationError('category')
if difficulty_setting not in problem.SETTING_DIFFICULTY_RANGE:
raise error.ValidationError('difficulty_setting')
if difficulty_admin:
try:
difficulty_admin = int(difficulty_admin)
except ValueError:
raise error.ValidationError('difficulty_admin')
else:
difficulty_admin = None
await problem.edit(self.domainId, pdoc['doc_id'], hidden=hidden,
category=category, tag=tag,
difficulty_setting=difficulty_setting, difficulty_admin=difficulty_admin)
await job.difficulty.update_problem(self.domainId, pdoc['doc_id'])
self.json_or_redirect(self.reverse_url('problem_detail', pid=pid))
@app.route('/p/{pid}/upload', 'problem_upload')
class ProblemUploadHandler(base.Handler):
def get_content_type(self, filename):
if os.path.splitext(filename)[1].lower() != '.zip':
raise error.FileTypeNotAllowedError(filename)
return 'application/zip'
@base.requirePriv(builtin.PRIV_USER_PROFILE)
@base.route_argument
@base.sanitize
async def get(self, *, pid: document.convert_doc_id):
pdoc = await problem.get(self.domainId, pid)
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
self.check_perm(builtin.PERM_EDIT_PROBLEM)
if (not self.own(pdoc, builtin.PERM_READ_PROBLEM_DATA_SELF)
and not self.has_perm(builtin.PERM_READ_PROBLEM_DATA)):
self.check_priv(builtin.PRIV_READ_PROBLEM_DATA)
md5 = await fs.get_md5(await problem.get_data(pdoc))
self.render('problem_upload.html', pdoc=pdoc, md5=md5)
@base.requirePriv(builtin.PRIV_USER_PROFILE)
@base.route_argument
@base.multipart_argument
@base.require_csrf_token
@base.sanitize
async def post(self, *, pid: document.convert_doc_id, file: objectid.ObjectId):
pdoc = await problem.get(self.domainId, pid)
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
self.check_perm(builtin.PERM_EDIT_PROBLEM)
if (not self.own(pdoc, builtin.PERM_READ_PROBLEM_DATA_SELF)
and not self.has_perm(builtin.PERM_READ_PROBLEM_DATA)):
self.check_priv(builtin.PRIV_READ_PROBLEM_DATA)
if pdoc.get('data') and type(pdoc['data']) is objectid.ObjectId:
await fs.unlink(pdoc['data'])
await problem.set_data(self.domainId, pid, file)
self.json_or_redirect(self.url)
@app.route('/p/{pid}/statistics', 'problem_statistics')
class ProblemStatisticsHandler(base.Handler):
@base.route_argument
@base.sanitize
async def get(self, *, pid: document.convert_doc_id):
# TODO(twd2)
uid = self.user['_id'] if self.has_priv(builtin.PRIV_USER_PROFILE) else None
pdoc = await problem.get(self.domainId, pid, uid)
if pdoc.get('hidden', False):
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
udoc, dudoc = await asyncio.gather(user.get_by_uid(pdoc['owner_uid']),
domain.get_user(self.domainId, pdoc['owner_uid']))
path_components = self.build_path(
(self.translate('problem_main'), self.reverse_url('problem_main')),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['doc_id'])),
(self.translate('problem_statistics'), None))
self.render('problem_statistics.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
page_title=pdoc['title'], path_components=path_components)
@app.route('/p/search', 'problem_search')
class ProblemSearchHandler(base.Handler):
@base.get_argument
@base.route_argument
@base.sanitize
async def get(self, *, q: str):
q = q.strip()
if not q:
self.json_or_redirect(self.referer_or_main)
return
try:
pdoc = await problem.get(self.domainId, document.convert_doc_id(q))
except error.ProblemNotFoundError:
pdoc = None
if pdoc:
self.redirect(self.reverse_url('problem_detail', pid=pdoc['doc_id']))
return
self.redirect('http://cn.bing.com/search?q={0}+site%3A{1}' \
.format(parse.quote(q), parse.quote(options.url_prefix)))
*/
+37 -49
View File
@@ -1,75 +1,63 @@
const
{ GET, POST } = require('../service/server'),
{ assertQueue, publish } = require('../service/queue'),
{ requirePerm, requirePriv } = require('./tools'),
validator = require('../lib/validator'),
problem = require('../model/problem'),
training = require('../model/training'),
contest = require('../model/contest'),
record = require('../model/record'),
domain = require('../model/domain'),
user = require('../model/user'),
{ GET, POST } = require('../service/server'),
queue = require('../service/queue'),
{ requirePerm } = require('./tools'),
{ NoProblemError } = require('../error'),
{ PRIV_USER_PROFILE } = require('../privilege'),
{ constants } = require('../options'),
{
PERM_VIEW_PROBLEM,
PERM_VIEW_PROBLEM_HIDDEN,
PERM_VIEW_TRAINING,
PERM_VIEW_CONTEST,
PERM_VIEW_HOMEWORK,
PERM_SUBMIT_PROBLEM,
PERM_CREATE_PROBLEM
} = require('../permission');
const PROBLEMS_PER_PAGE = 100;
assertQueue('judge');
queue.assert('judge');
GET('/p', requirePerm(PERM_VIEW_PROBLEM), async ctx => {
let q = { domainId: ctx.state.domainId },
let q = {},
page = ctx.query.page || 1;
if (!ctx.state.user.hasPerm(PERM_VIEW_PROBLEM_HIDDEN)) q.hidden = false;
let pdocs = await problem.getMany(q, { pid: 1 }, page, PROBLEMS_PER_PAGE);
ctx.body = { code: 0, page, pdocs, category: '' };
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: '' };
});
GET('/problem/random', requirePerm(PERM_VIEW_PROBLEM), async ctx => {
let q = { domainId: ctx.state.domainId };
let q = {};
if (!ctx.state.user.hasPerm(PERM_VIEW_PROBLEM_HIDDEN)) q.hidden = false;
let pid = await problem.random(q);
if (!pid) throw new NoProblemError();
ctx.body = { pid };
});
GET('/p/:pid', requirePerm(PERM_VIEW_PROBLEM), async ctx => {
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.body = { pdoc, udoc, title: pdoc.title };
});
POST('/p/:pid/submit', requirePerm(PERM_SUBMIT_PROBLEM), async ctx => {
let rid = await record.add({
creator: ctx.state.user._id,
lang: ctx.request.body.language,
code: ctx.request.body.code,
domainId: ctx.state.domainId,
pid: ctx.params.pid
});
await publish('judge', rid);
await queue.push('judge', rid);
ctx.state.user.nSubmit++;
ctx.body = { rid };
});
GET('/p/:pid', requirePerm(PERM_VIEW_PROBLEM), async ctx => {
let uid = null,
pid = Number(ctx.params.pid);
if (ctx.state.user.hasPriv(PRIV_USER_PROFILE)) uid = ctx.state.user._id;
let pdoc = await problem.get({ domainId: ctx.state.domainId, pid, uid });
if (pdoc.hidden) ctx.checkPerm(PERM_VIEW_PROBLEM_HIDDEN);
let udoc = await user.getById(pdoc.owner);
ctx.body = { pdoc, udoc, title: pdoc.title };
POST('/problem/create', requirePerm(PERM_CREATE_PROBLEM), async ctx => {
let { title, pid, content, hidden } = ctx.request.body;
validator.checkPid(pid);
pid = pid || await system.incPidCounter();
await problem.add({ title, content, owner: ctx.state.user._id, pid, hidden });
ctx.body = { pid };
});
POST('/problem/create',
requirePriv(PRIV_USER_PROFILE),
requirePerm(PERM_CREATE_PROBLEM),
async ctx => {
let { title, pid, content, hidden } = ctx.request.body;
validator.checkPid(pid);
pid = pid || await domain.incPidCounter(ctx.state.domainId);
await problem.add({ domainId: ctx.state.domainId, title, content, owner: ctx.state.user._id, pid, hidden });
ctx.body = { pid };
}
);
/*
@@ -110,14 +98,14 @@ class ProblemSubmitHandler(base.Handler):
else:
# TODO(iceboy): needs to be in sync with contest_detail_problem_submit
rdocs = await record \
.get_user_in_problem_multi(uid, self.domainId, pdoc['doc_id']) \
.get_user_in_problem_multi(uid, self.domainId, pdoc['_id']) \
.sort([('_id', -1)]) \
.limit(10) \
.to_list()
if not self.prefer_json:
path_components = self.build_path(
(self.translate('problem_main'), self.reverse_url('problem_main')),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['doc_id'])),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['_id'])),
(self.translate('problem_submit'), None))
self.render('problem_submit.html', pdoc=pdoc, udoc=udoc, rdocs=rdocs, dudoc=dudoc,
page_title=pdoc['title'], path_components=path_components)
@@ -136,7 +124,7 @@ class ProblemSubmitHandler(base.Handler):
pdoc = await problem.get(self.domainId, pid)
if pdoc.get('hidden', False):
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
rid = await record.add(self.domainId, pdoc['doc_id'], constant.record.TYPE_SUBMISSION,
rid = await record.add(self.domainId, pdoc['_id'], constant.record.TYPE_SUBMISSION,
self.user['_id'], lang, code)
self.json_or_redirect(self.reverse_url('record_detail', rid=rid))
@@ -172,7 +160,7 @@ class ProblemPretestHandler(base.Handler):
zip_file.close()
fid = await fs.add_data('application/zip', output_buffer.getvalue())
output_buffer.close()
rid = await record.add(self.domainId, pdoc['doc_id'], constant.record.TYPE_PRETEST,
rid = await record.add(self.domainId, pdoc['_id'], constant.record.TYPE_PRETEST,
self.user['_id'], lang, code, fid)
self.json_or_redirect(self.reverse_url('record_detail', rid=rid))
@@ -256,7 +244,7 @@ class ProblemCopyHandler(base.Handler):
.sort('doc_id', 1) \
.to_list()
exist_pids = [pdoc['doc_id'] for pdoc in pdocs]
exist_pids = [pdoc['_id'] for pdoc in pdocs]
if len(src_pids) != len(exist_pids):
for pid in src_pids:
if pid not in exist_pids:
@@ -292,7 +280,7 @@ class ProblemEditHandler(base.Handler):
domain.get_user(self.domainId, pdoc['owner_uid']))
path_components = self.build_path(
(self.translate('problem_main'), self.reverse_url('problem_main')),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['doc_id'])),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['_id'])),
(self.translate('problem_edit'), None))
self.render('problem_edit.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
page_title=pdoc['title'], path_components=path_components)
@@ -306,7 +294,7 @@ class ProblemEditHandler(base.Handler):
pdoc = await problem.get(self.domainId, pid)
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
self.check_perm(builtin.PERM_EDIT_PROBLEM)
await problem.edit(self.domainId, pdoc['doc_id'], title=title, content=content)
await problem.edit(self.domainId, pdoc['_id'], title=title, content=content)
self.json_or_redirect(self.reverse_url('problem_detail', pid=pid))
@@ -324,7 +312,7 @@ class ProblemSettingsHandler(base.Handler):
domain.get_user(self.domainId, pdoc['owner_uid']))
path_components = self.build_path(
(self.translate('problem_main'), self.reverse_url('problem_main')),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['doc_id'])),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['_id'])),
(self.translate('problem_settings'), None))
self.render('problem_settings.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
categories=problem.get_categories(),
@@ -360,10 +348,10 @@ class ProblemSettingsHandler(base.Handler):
raise error.ValidationError('difficulty_admin')
else:
difficulty_admin = None
await problem.edit(self.domainId, pdoc['doc_id'], hidden=hidden,
await problem.edit(self.domainId, pdoc['_id'], hidden=hidden,
category=category, tag=tag,
difficulty_setting=difficulty_setting, difficulty_admin=difficulty_admin)
await job.difficulty.update_problem(self.domainId, pdoc['doc_id'])
await job.difficulty.update_problem(self.domainId, pdoc['_id'])
self.json_or_redirect(self.reverse_url('problem_detail', pid=pid))
@@ -419,7 +407,7 @@ class ProblemStatisticsHandler(base.Handler):
domain.get_user(self.domainId, pdoc['owner_uid']))
path_components = self.build_path(
(self.translate('problem_main'), self.reverse_url('problem_main')),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['doc_id'])),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['_id'])),
(self.translate('problem_statistics'), None))
self.render('problem_statistics.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
page_title=pdoc['title'], path_components=path_components)
@@ -440,7 +428,7 @@ class ProblemSearchHandler(base.Handler):
except error.ProblemNotFoundError:
pdoc = None
if pdoc:
self.redirect(self.reverse_url('problem_detail', pid=pdoc['doc_id']))
self.redirect(self.reverse_url('problem_detail', pid=pdoc['_id']))
return
self.redirect('http://cn.bing.com/search?q={0}+site%3A{1}' \
.format(parse.quote(q), parse.quote(options.url_prefix)))
+14 -21
View File
@@ -1,19 +1,12 @@
const
bson = require('bson'),
{ constants } = require('../options'),
{ GET, POST } = require('../service/server'),
{ publish } = require('../service/queue'),
{ requirePerm, requirePriv } = require('./tools'),
validator = require('../lib/validator'),
problem = require('../model/problem'),
{ GET } = require('../service/server'),
record = require('../model/record'),
domain = require('../model/domain'),
user = require('../model/user'),
{ PRIV_USER_PROFILE } = require('../privilege'),
{ PERM_READ_RECORD_CODE, PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD } = require('../permission');
GET('/r', async ctx => {
let q = { domainId: ctx.state.domainId },
let q = {},
page = ctx.query.page || 1;
let rdocs = await record.getMany(q, { rid: 1 }, page, constants.RECORD_PER_PAGE);
ctx.body = { page, rdocs };
@@ -65,14 +58,14 @@ class ProblemSubmitHandler(base.Handler):
else:
# TODO(iceboy): needs to be in sync with contest_detail_problem_submit
rdocs = await record \
.get_user_in_problem_multi(uid, self.domainId, pdoc['doc_id']) \
.get_user_in_problem_multi(uid, self.domainId, pdoc['_id']) \
.sort([('_id', -1)]) \
.limit(10) \
.to_list()
if not self.prefer_json:
path_components = self.build_path(
(self.translate('problem_main'), self.reverse_url('problem_main')),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['doc_id'])),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['_id'])),
(self.translate('problem_submit'), None))
self.render('problem_submit.html', pdoc=pdoc, udoc=udoc, rdocs=rdocs, dudoc=dudoc,
page_title=pdoc['title'], path_components=path_components)
@@ -91,7 +84,7 @@ class ProblemSubmitHandler(base.Handler):
pdoc = await problem.get(self.domainId, pid)
if pdoc.get('hidden', False):
self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN)
rid = await record.add(self.domainId, pdoc['doc_id'], constant.record.TYPE_SUBMISSION,
rid = await record.add(self.domainId, pdoc['_id'], constant.record.TYPE_SUBMISSION,
self.user['_id'], lang, code)
self.json_or_redirect(self.reverse_url('record_detail', rid=rid))
@@ -127,7 +120,7 @@ class ProblemPretestHandler(base.Handler):
zip_file.close()
fid = await fs.add_data('application/zip', output_buffer.getvalue())
output_buffer.close()
rid = await record.add(self.domainId, pdoc['doc_id'], constant.record.TYPE_PRETEST,
rid = await record.add(self.domainId, pdoc['_id'], constant.record.TYPE_PRETEST,
self.user['_id'], lang, code, fid)
self.json_or_redirect(self.reverse_url('record_detail', rid=rid))
@@ -211,7 +204,7 @@ class ProblemCopyHandler(base.Handler):
.sort('doc_id', 1) \
.to_list()
exist_pids = [pdoc['doc_id'] for pdoc in pdocs]
exist_pids = [pdoc['_id'] for pdoc in pdocs]
if len(src_pids) != len(exist_pids):
for pid in src_pids:
if pid not in exist_pids:
@@ -247,7 +240,7 @@ class ProblemEditHandler(base.Handler):
domain.get_user(self.domainId, pdoc['owner_uid']))
path_components = self.build_path(
(self.translate('problem_main'), self.reverse_url('problem_main')),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['doc_id'])),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['_id'])),
(self.translate('problem_edit'), None))
self.render('problem_edit.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
page_title=pdoc['title'], path_components=path_components)
@@ -261,7 +254,7 @@ class ProblemEditHandler(base.Handler):
pdoc = await problem.get(self.domainId, pid)
if not self.own(pdoc, builtin.PERM_EDIT_PROBLEM_SELF):
self.check_perm(builtin.PERM_EDIT_PROBLEM)
await problem.edit(self.domainId, pdoc['doc_id'], title=title, content=content)
await problem.edit(self.domainId, pdoc['_id'], title=title, content=content)
self.json_or_redirect(self.reverse_url('problem_detail', pid=pid))
@@ -279,7 +272,7 @@ class ProblemSettingsHandler(base.Handler):
domain.get_user(self.domainId, pdoc['owner_uid']))
path_components = self.build_path(
(self.translate('problem_main'), self.reverse_url('problem_main')),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['doc_id'])),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['_id'])),
(self.translate('problem_settings'), None))
self.render('problem_settings.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
categories=problem.get_categories(),
@@ -315,10 +308,10 @@ class ProblemSettingsHandler(base.Handler):
raise error.ValidationError('difficulty_admin')
else:
difficulty_admin = None
await problem.edit(self.domainId, pdoc['doc_id'], hidden=hidden,
await problem.edit(self.domainId, pdoc['_id'], hidden=hidden,
category=category, tag=tag,
difficulty_setting=difficulty_setting, difficulty_admin=difficulty_admin)
await job.difficulty.update_problem(self.domainId, pdoc['doc_id'])
await job.difficulty.update_problem(self.domainId, pdoc['_id'])
self.json_or_redirect(self.reverse_url('problem_detail', pid=pid))
@@ -374,7 +367,7 @@ class ProblemStatisticsHandler(base.Handler):
domain.get_user(self.domainId, pdoc['owner_uid']))
path_components = self.build_path(
(self.translate('problem_main'), self.reverse_url('problem_main')),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['doc_id'])),
(pdoc['title'], self.reverse_url('problem_detail', pid=pdoc['_id'])),
(self.translate('problem_statistics'), None))
self.render('problem_statistics.html', pdoc=pdoc, udoc=udoc, dudoc=dudoc,
page_title=pdoc['title'], path_components=path_components)
@@ -395,7 +388,7 @@ class ProblemSearchHandler(base.Handler):
except error.ProblemNotFoundError:
pdoc = None
if pdoc:
self.redirect(self.reverse_url('problem_detail', pid=pdoc['doc_id']))
self.redirect(self.reverse_url('problem_detail', pid=pdoc['_id']))
return
self.redirect('http://cn.bing.com/search?q={0}+site%3A{1}' \
.format(parse.quote(q), parse.quote(options.url_prefix)))
+1 -20
View File
@@ -1,6 +1,6 @@
const
opcount = require('../model/opcount'),
{ PermissionError, PrivilegeError } = require('../error');
{ PermissionError } = require('../error');
module.exports = {
requirePerm(perm) {
@@ -22,25 +22,6 @@ module.exports = {
await next();
};
},
requirePriv(priv) {
return async (ctx, next) => {
for (let i in arguments) {
if (arguments[i] instanceof Array) {
let p = false;
for (let j in arguments)
if (ctx.state.user.hasPriv(arguments[i][j])) {
p = true;
break;
}
if (!p) throw new PrivilegeError([arguments[i]]);
} else {
if (ctx.state.user.hasPriv(arguments[i])) continue;
else throw new PrivilegeError([arguments[i]]);
}
}
await next();
};
},
limitRate(op, period_secs, max_operations) {
return async (ctx, next) => {
await opcount.inc(op, ctx.request.ip, period_secs, max_operations);
+65
View File
@@ -0,0 +1,65 @@
const
path = require('path'),
static = require('koa-static'),
nunjucks = require('nunjucks'),
options = require('../options'),
perm = require('../permission'),
builtin = require('../model/builtin'),
{ MIDDLEWARE } = require('../service/server'),
{ NotFoundError } = require('../error');
class Nunjucks extends nunjucks.Environment {
constructor() {
super(
new nunjucks.FileSystemLoader(path.resolve(options.template.path)),
{ autoescape: true, trimBlocks: true }
);
this.addFilter('json', function (data) {
return JSON.stringify(data);
}, false);
this.addFilter('assign', function (self, data) {
return Object.assign(self, data);
});
this.addGlobal('static_url', str => `/${str}`);
this.addGlobal('reverse_url', str => str);
this.addGlobal('perm', perm);
this.addGlobal('builtin', builtin);
}
}
const env = new Nunjucks();
MIDDLEWARE(static(path.resolve(process.cwd(), '.uibuild')));
MIDDLEWARE(async (ctx, next) => {
ctx.render_title = str => str;
ctx.ui_context = {};
ctx.user_context = {};
ctx.render = async (name, context) => {
ctx.user = ctx.state.user;
ctx.translate = str => {
return str.translate(ctx.state.user.language);
};
ctx.has_perm = perm => ctx.state.user.hasPerm(perm);
ctx.body = await new Promise((resolve, reject) => {
env.render(name, Object.assign(ctx.state, context, {
handler: ctx,
_: ctx.translate
}), (error, res) => {
if (error) reject(error);
else resolve(res);
});
});
ctx.response.type = 'text/html';
};
try {
try {
await next();
if (!ctx.body) throw new NotFoundError();
} catch (error) {
await ctx.render('error.html', { error });
}
Object.assign(ctx.body, JSON.parse(ctx.query.data || '{}'));
console.log(ctx.body);
if (ctx.query.template || ctx.templateName) await ctx.render(ctx.query.template || ctx.templateName, ctx.body);
} catch (error) {
await ctx.render('bsod.html', { error });
}
});
+14 -11
View File
@@ -1,13 +1,13 @@
const
{ GET, POST } = require('../service/server.js'),
{ GET, POST, CONTEXT } = require('../service/server.js'),
user = require('../model/user'),
token = require('../model/token'),
system = require('../model/system'),
mail = require('../lib/mail'),
validator = require('../lib/validator'),
options = require('../options'),
{ PRIV_REGISTER_USER, PRIV_USER_PROFILE } = require('../privilege'),
{ requirePriv, limitRate } = require('./tools'),
{ PERM_REGISTER_USER, PERM_LOGGEDIN } = require('../permission'),
{ requirePerm, limitRate } = require('./tools'),
{ UserAlreadyExistError, InvalidTokenError, VerifyPasswordError, UserNotFoundError, LoginError } = require('../error');
GET('/user', async ctx => {
@@ -27,17 +27,20 @@ POST('/login', async ctx => {
ctx.session.rememberme = rememberme;
ctx.body = { udoc };
});
POST('/logout', requirePriv(PRIV_USER_PROFILE), async ctx => {
POST('/logout', requirePerm(PERM_LOGGEDIN), async ctx => {
ctx.session = { uid: 1 };
ctx.body = {};
});
GET('/register/:code', requirePriv(PRIV_REGISTER_USER), async ctx => {
let { GET: _GET, POST: _POST, MIDDLEWARE: _MIDDLEWARE } = CONTEXT();
_MIDDLEWARE(requirePerm(PERM_REGISTER_USER));
_GET('/register/:code', async ctx => {
let code = ctx.request.body.code;
let { mail } = await token.get(code, token.TYPE_REGISTRATION);
if (!mail) throw new InvalidTokenError(token.TYPE_REGISTRATION, code);
ctx.body = { mail };
});
GET('/register/:code', requirePriv(PRIV_REGISTER_USER), async ctx => {
_GET('/register/:code', async ctx => {
let { code, password, verify_password, uname } = ctx.request.body;
let { mail } = await token.get(code, token.TYPE_REGISTRATION);
if (!mail) throw new InvalidTokenError(token.TYPE_REGISTRATION, code);
@@ -50,7 +53,7 @@ GET('/register/:code', requirePriv(PRIV_REGISTER_USER), async ctx => {
});
if (options.smtp.user) {
POST('/register', requirePriv(PRIV_REGISTER_USER), limitRate('send_mail', 3600, 30), async ctx => {
_POST('/register', limitRate('send_mail', 3600, 30), async ctx => {
let email = ctx.request.body.email;
validator.check_mail(email);
if (await user.get_by_mail(email)) throw new UserAlreadyExistError(email);
@@ -59,7 +62,7 @@ if (options.smtp.user) {
await mail.send_mail(email, 'Sign Up', 'user_register_mail', m);
ctx.body = {};
});
POST('/lostpass', requirePriv(PRIV_REGISTER_USER), limitRate('send_mail', 3600, 30), async ctx => {
POST('/lostpass', limitRate('send_mail', 3600, 30), async ctx => {
let email = ctx.request.body.mail;
validator.check_mail(email);
let udoc = await user.getByEmail(email);
@@ -73,14 +76,14 @@ if (options.smtp.user) {
await mail.send_mail(email, 'Lost Password', 'user_lostpass_mail', m);
ctx.body = {};
});
GET('/lostpass/:code', requirePriv(PRIV_REGISTER_USER), async ctx => {
GET('/lostpass/:code', async ctx => {
let code = ctx.params.code;
let tdoc = await token.get(code, token.TYPE_LOSTPASS);
if (!tdoc) throw new InvalidTokenError(token.TYPE_LOSTPASS, code);
let udoc = await user.getById(tdoc.uid);
ctx.body = { uname: udoc.uname };
});
POST('/lostpass/:code', requirePriv(PRIV_REGISTER_USER), async ctx => {
POST('/lostpass/:code', async ctx => {
let code = ctx.params.code;
let password = ctx.request.body.password;
let verify_password = ctx.request.body.verify_password;
@@ -92,7 +95,7 @@ if (options.smtp.user) {
ctx.redirect('/');
});
} else
POST('/register', requirePriv(PRIV_REGISTER_USER), async ctx => {
_POST('/register', async ctx => {
let email = ctx.request.body.email;
validator.check_mail(email);
if (await user.get_by_mail(email)) throw new UserAlreadyExistError(email);
+6 -9
View File
@@ -1,9 +1,8 @@
const
Mongo = require('mongodb'),
{ pwhash } = require('./utils'),
priv = require('./privilege'),
builtin = require('./model/builtin'),
options = require('./lib/options');
{ pwhash } = require('./utils'),
options = require('./options');
async function run() {
let mongourl = 'mongodb://';
@@ -12,8 +11,7 @@ async function run() {
let Database = await Mongo.MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: true });
let db = Database.db(options.db.name);
let coll_user = db.collection('user');
let coll_domain = db.collection('domain');
let coll_domain_user = db.collection('domain_user');
let coll_role = db.collection('role');
let coll_blacklist = db.collection('blacklist');
async function createRootUser() {
let salt = pwhash.salt();
@@ -29,15 +27,14 @@ async function run() {
regip: '127.0.0.1',
loginat: new Date(),
loginip: '127.0.0.1',
priv: priv.PRIV_ALL,
gravatar: 'root@hydro'
gravatar: 'root@hydro',
role: 'admin'
});
await coll_domain_user.insertOne({ domainId: 'system', uid: -1, role: 'admin', joinAt: new Date() });
}
await coll_user.createIndex('unameLower', { unique: true });
await coll_user.createIndex('emailLower', { sparse: true });
await coll_role.insertMany(builtin.BUILTIN_ROLES);
await coll_user.insertMany(builtin.BUILTIN_USERS);
await coll_domain.insertMany(builtin.BUILTIN_DOMAINS);
await coll_blacklist.createIndex('expireAt', { expireAfterSeconds: 0 });
await createRootUser();
console.log('Installed');
+1 -7
View File
@@ -6,17 +6,13 @@ export class User {
unameLower: string
salt: string
hash: string
priv: number
domainId: string = 'system'
displayName: string = ''
nAccept: number = 0
nSubmit: number = 0
perm: string = '0'
constructor(user, domainUser) { }
hasPriv(priv) { }
constructor(user) { }
hasPerm(perm) { }
checkPassword(password) { }
async joinDomain(role, joinAt) { }
}
export interface TestCase {
time: number,
@@ -26,7 +22,6 @@ export interface TestCase {
}
export interface Record {
_id: import('bson').ObjectID,
domainId: string,
pid: string | boolean,
creator: number,
lang: string,
@@ -43,7 +38,6 @@ export interface Record {
}
export interface Problem {
_id: number | string,
domainId: string,
title: string,
content: string,
timeLimit: number,
+37
View File
@@ -0,0 +1,37 @@
const
fs = require('fs'),
yaml = require('js-yaml');
let locales = {};
String.prototype.format = function (args) {
var result = this;
if (arguments.length > 0) {
if (arguments.length == 1 && typeof (args) == 'object') {
for (var key in args)
if (args[key] != undefined) {
let reg = new RegExp('({' + key + '})', 'g');
result = result.replace(reg, args[key]);
}
} else for (var i = 0; i < arguments.length; i++)
if (arguments[i] != undefined) {
let reg = new RegExp('({)' + i + '(})', 'g');
result = result.replace(reg, arguments[i]);
}
}
return result;
};
String.prototype.rawformat = function (object) {
let res = this.split('{@}');
return [res[0], object, res[1]];
};
String.prototype.translate = function (language = 'zh-CN') {
if (locales[language]) return this;
else return this;
};
module.exports = function load(file, language) {
if (!locales[language]) locales[language] = {};
let content = fs.readFileSync(file).toString();
Object.assign(locales[language], yaml.safeLoad(content));
};
-11
View File
@@ -34,19 +34,8 @@ module.exports = {
isPid, checkPid
};
/*
domainId_RE = re.compile(r'[_A-Za-z][_0-9A-Za-z]{3,255}')
ID_RE = re.compile(r'[^\\/\s\u3000]([^\\/\n\r]*[^\\/\s\u3000])?')
ROLE_RE = re.compile(r'[_0-9A-Za-z]{1,256}')
DOMAIN_INVITATION_CODE_RE = re.compile(r'[0-9A-Za-z]{1,64}')
def is_domainId(s):
return bool(domainId_RE.fullmatch(s))
def check_domainId(s):
if not is_domainId(s):
raise error.ValidationError('domainId')
def is_id(s):
+152 -22
View File
@@ -1,6 +1,4 @@
const
perm = require('../permission'),
priv = require('../privilege');
const perm = require('../permission');
const BUILTIN_USERS = [
{
_id: 0,
@@ -11,12 +9,12 @@ const BUILTIN_USERS = [
salt: '',
hash: '',
gender: 'other',
regat: new Date(0),
regat: new Date(),
regip: '127.0.0.1',
priv: priv.PRIV_NONE,
gravatar: '',
loginat: new Date(),
loginip: '127.0.0.1'
loginip: '127.0.0.1',
role: 'guest'
},
{
_id: 1,
@@ -30,23 +28,155 @@ const BUILTIN_USERS = [
regip: '127.0.0.1',
loginat: new Date(),
loginip: '127.0.0.1',
priv: priv.PRIV_DEFAULT,
gravatar: 'guest@hydro'
gravatar: 'guest@hydro',
role: 'guest'
}
];
const BUILTIN_DOMAINS = [{
_id: 'system',
owner: 0,
roles: {
guest: perm.PERM_BASIC,
default: perm.PERM_DEFAULT,
member: perm.PERM_DEFAULT,
admin: perm.PERM_ADMIN
},
gravatar: '',
name: 'System',
bulletin: ''
}];
const BUILTIN_ROLES = [
{ _id: 'guest', perm: perm.PERM_BASIC },
{ _id: 'default', perm: perm.PERM_DEFAULT },
{ _id: 'admin', perm: perm.PERM_ADMIN }
];
const CATEGORIES = {
'动态规划': [
'LCS',
'LIS',
'背包',
'单调性DP',
'环形DP',
'树形DP',
'状态压缩DP'
],
'搜索': [
'枚举',
'搜索与剪枝',
'启发式搜索',
'DLX',
'双向搜索',
'折半搜索',
'记忆化搜索',
'模拟退火'
],
'计算几何': [
'半平面交',
'凸包',
'几何图形的交与并',
'旋转卡壳',
'点定位',
'坐标变换',
'离散化与扫描',
'反演',
'Voronoi图',
'平面图的对偶图',
'三角剖分',
'梯形剖分',
'几何知识'
],
'贪心': [],
'树结构': [
'最近公共祖先',
'生成树',
'DFS序列',
'树上倍增',
'树的分治',
'树链剖分',
'Link-Cut-Tree'
],
'图结构': [
'平面图',
'二分图',
'二分图匹配',
'最短路',
'差分约束',
'拓扑排序',
'网络流',
'强连通分量',
'割点割边',
'欧拉回路',
'2-SAT'
],
'数论': [
'素数判定',
'欧几里得算法',
'不定方程',
'数位统计',
'解线性同余方程',
'baby-step-giant-step',
'Pell方程',
'大整数质因数分解',
'勾股方程',
'积性函数',
'Fibonacci数列'
],
'模拟': [],
'数据结构': [
'栈',
'队列',
'链表',
'单调队列',
'并查集',
'堆',
'平衡树',
'线段树',
'树状数组',
'树套树',
'四分树',
'划分树',
'归并树',
'k-d树',
'块状链表',
'Hashing',
'函数式编程'
],
'博弈论': [],
'字符串': [
'KMP',
'后缀数据结构',
'Trie树',
'AC自动机',
'Manacher',
'表达式处理',
'最小表示法'
],
'组合数学': [
'生成函数',
'容斥原理',
'康托展开',
'Catalan数列',
'Stirling数',
'差分',
'Polya定理'
],
'线性代数': [
'矩阵乘法',
'高斯消元',
'线性规划'
],
'高精度': [
'FFT'
],
'递推': [],
'概率论': [
'随机化'
],
'NPC': [],
'其他': [
'二分查找',
'三分查找',
'双指针扫描',
'分治',
'分块',
'RMQ',
'快速幂',
'数学',
'排序',
'构造'
]
};
const FOOTER_EXTRA_HTMLS = [];
const VIEW_LANGS = [
{ code: 'zh-CN', name: 'zh-CN' }
];
module.exports = {
BUILTIN_USERS, BUILTIN_DOMAINS
BUILTIN_USERS, BUILTIN_ROLES, CATEGORIES, VIEW_LANGS, FOOTER_EXTRA_HTMLS
};
+14 -15
View File
@@ -1,5 +1,4 @@
const
domain = require('./domain'),
validator = require('../lib/validator'),
{ ValidationError, ContestNotFoundError } = require('../error'),
RULE_HOMEWORK = require('../module/contest/homework'),
@@ -18,37 +17,37 @@ const RULES = {
module.exports = {
RULES,
async add(domainId, title, content, owner, rule,
async add(title, content, owner, rule,
beginAt = new Date(), endAt = new Date(), pids = [], data) {
validator.checkTitle(title);
validator.checkContent(content);
if (!this.RULES[rule]) throw new ValidationError('rule');
if (beginAt >= endAt) throw new ValidationError('beginAt', 'endAt');
Object.assign(data, { domainId, content, owner, title, rule, beginAt, endAt, pids, attend: 0 });
Object.assign(data, { content, owner, title, rule, beginAt, endAt, pids, attend: 0 });
this.RULES[rule].check(data);
return await coll.insertOne(data);
},
count: query => coll.find(query).count(),
async edit(domainId, cid, $set) {
async edit(cid, $set) {
if ($set.title) validator.checkTitle($set.title);
if ($set.content) validator.checkIntro($set.content);
if ($set.rule)
if (!this.RULES[$set.rule]) throw new ValidationError('rule');
if ($set.beginAt && $set.endAt)
if ($set.beginAt >= $set.endAt) throw new ValidationError('beginAt', 'endAt');
let cdoc = await coll.findOne({ domainId, cid });
if (!cdoc) throw new ContestNotFoundError(domainId, cid);
let cdoc = await coll.findOne({ cid });
if (!cdoc) throw new ContestNotFoundError(cid);
this.RULES[$set.rule || cdoc.rule].check(Object.assign(cdoc, $set));
await coll.findOneAndUpdate({ domainId, cid }, { $set });
await coll.findOneAndUpdate({ cid }, { $set });
return cdoc;
},
async get(domainId, cid) {
let tdoc = await coll.findOne({ domainId, cid });
if (!tdoc) throw new ContestNotFoundError(domainId, cid);
async get( cid) {
let tdoc = await coll.findOne({ cid });
if (!tdoc) throw new ContestNotFoundError(cid);
return tdoc;
},
get_multi: (domainId, query, fields) => coll.find(Object.assign({ domainId }, query), { fields }),
get_multi_status: (query, fields) => coll.find(query, { fields }),
get_multi: (query, fields) => coll.find(query, { fields }),
get_multi_status: (query, fields) => coll_status.find(query, { fields }),
async get_random_id(query) {
let pdocs = coll.find(query);
let pcount = await pdocs.count();
@@ -57,8 +56,8 @@ module.exports = {
return pdoc.pid;
} else return null;
},
get_status: (domainId, cid, uid, fields) => coll_status.findOne({ domainId, cid, uid }, { fields }),
set_status: (domainId, cid, uid, $set) => coll_status.findOneAndUpdate({ domainId, cid, uid }, { $set })
get_status: (cid, uid, fields) => coll_status.findOne({ cid, uid }, { fields }),
set_status: (cid, uid, $set) => coll_status.findOneAndUpdate({ cid, uid }, { $set })
};
/*
@@ -321,7 +320,7 @@ class ContestCommonOperationMixin(object):
fields={'doc_id': 1}) \
.sort('doc_id', 1) \
.to_list()
exist_pids = [pdoc['doc_id'] for pdoc in pdocs]
exist_pids = [pdoc['_id'] for pdoc in pdocs]
if len(pids) != len(exist_pids):
for pid in pids:
if pid not in exist_pids:
-315
View File
@@ -1,315 +0,0 @@
const
validator = require('../utils').validator,
user = require('./user'),
{ DomainAlreadyExistError, DomainNotFoundError } = require('../error'),
db = require('../service/db.js'),
coll = db.collection('domain');
module.exports = {
/**
* @param {string} domainId
* @param {int} owner
* @param {roles} roles
* @param {str} name
* @param {str} gravatar
* @param {str} bulletin
*/
async add(domainId, owner, roles, name, gravatar, bulletin = '') {
validator.checkDomainId(domainId);
validator.checkName(name);
validator.checkBulletin(bulletin);
try {
let result = await coll.insertOne({
_id: domainId, pending: true, owner, roles, name, gravatar, bulletin
});
domainId = result.insertedId;
} catch (e) {
throw new DomainAlreadyExistError(domainId);
}
await (await user.getById(owner, domainId)).joinDomain('root');
await coll.updateOne({ _id: domainId }, { $unset: { pending: '' } });
return domainId;
},
/**
* @param {string} domainId
* @param {object} fields
*/
async get(domainId, fields) {
let ddoc = await coll.findOne({ _id: domainId }, fields);
if (!ddoc) throw new DomainNotFoundError(domainId);
return ddoc;
},
/**
* Increments the problem ID counter.
* @param {string} domainId
* @returns {number} Integer value before increment.
*/
async incPidCounter(domainId) {
await coll.updateOne({ _id: domainId, pidCounter: { $exists: false } },
{ $set: { pidCounter: 1000 } });
let doc = await coll.findOneAndUpdate({ _id: domainId }, { $inc: { pidCounter: 1 } });
return doc.value.pidCounter;
},
async incUser(domainId, uid, $inc) {
let res = await coll.findOneAndUpdate({ domainId, uid }, { $inc }, { upsert: true });
return res.value;
}
};
/*
async def add_continue(domainId: str, ensure_owner_uid: int=None):
ddoc = await get(domainId)
if 'pending' not in ddoc:
raise error.DomainNotFoundError(domainId)
owner_uid = ddoc['owner_uid']
if ensure_owner_uid != None and owner_uid != ensure_owner_uid:
raise error.DomainNotFoundError(domainId)
try:
await add_user_role(domainId, owner_uid, builtin.ROLE_ROOT)
except error.UserAlreadyDomainMemberError:
pass
coll = db.coll('domain')
await coll.update_one({'_id': domainId},
{'$unset': {'pending': ''}})
def get_multi(*, fields=None, **kwargs):
coll = db.coll('domain')
return coll.find(kwargs, fields)
@argmethod.wrap
async def get_list(*, fields=None, limit: int=None, **kwargs):
coll = db.coll('domain')
return await coll.find(kwargs, fields).limit(limit).to_list(None)
def get_pending(**kwargs):
return get_multi(pending=True, **kwargs)
@argmethod.wrap
async def edit(domainId: str, **kwargs):
for domain in builtin.DOMAINS:
if domain['_id'] == domainId:
raise error.BuiltinDomainError(domainId)
coll = db.coll('domain')
if 'owner_uid' in kwargs:
del kwargs['owner_uid']
if 'name' in kwargs:
validator.check_name(kwargs['name'])
# TODO(twd2): check kwargs
return await coll.find_one_and_update(filter={'_id': domainId},
update={'$set': {**kwargs}},
return_document=ReturnDocument.AFTER)
async def unset(domainId, fields):
# TODO(twd2): check fields
coll = db.coll('domain')
return await coll.find_one_and_update(filter={'_id': domainId},
update={'$unset': dict((f, '') for f in set(fields))},
return_document=ReturnDocument.AFTER)
@argmethod.wrap
async def set_role(domainId: str, role: str, perm: int):
return await set_roles(domainId, {role: perm})
@argmethod.wrap
async def set_roles(domainId: str, roles):
roles = {str(role): int(perm) for role, perm in roles.items()}
update = {}
for role in roles:
validator.check_role(role)
if role in builtin.BUILTIN_ROLE_DESCRIPTORS:
if not builtin.BUILTIN_ROLE_DESCRIPTORS[role].modifiable:
raise error.ModifyBuiltinRoleError(domainId, role)
update['roles.{0}'.format(role)] = roles[role]
for domain in builtin.DOMAINS:
if domain['_id'] == domainId:
raise error.BuiltinDomainError(domainId)
coll = db.coll('domain')
return await coll.find_one_and_update(filter={'_id': domainId},
update={'$set': update},
return_document=ReturnDocument.AFTER)
@argmethod.wrap
async def delete_role(domainId: str, role: str):
return await delete_roles(domainId, [role])
async def delete_roles(domainId: str, roles):
roles = list(set(roles))
for role in roles:
validator.check_role(role)
if role in builtin.BUILTIN_ROLE_DESCRIPTORS:
raise error.ModifyBuiltinRoleError(domainId, role)
for domain in builtin.DOMAINS:
if domain['_id'] == domainId:
raise error.BuiltinDomainError(domainId)
user_coll = db.coll('domain.user')
await user_coll.update_many({'domainId': domainId, 'role': {'$in': list(roles)}},
{'$unset': {'role': ''}})
coll = db.coll('domain')
return await coll.find_one_and_update(filter={'_id': domainId},
update={'$unset': dict(('roles.{0}'.format(role), '')
for role in roles)},
return_document=ReturnDocument.AFTER)
@argmethod.wrap
async def transfer(domainId: str, old_owner_uid: int, new_owner_uid: int):
for domain in builtin.DOMAINS:
if domain['_id'] == domainId:
raise error.BuiltinDomainError(domainId)
coll = db.coll('domain')
return await coll.find_one_and_update(filter={'_id': domainId, 'owner_uid': old_owner_uid},
update={'$set': {'owner_uid': new_owner_uid}},
return_document=ReturnDocument.AFTER)
async def set_user(domainId, uid, **kwargs):
coll = db.coll('domain.user')
return await coll.find_one_and_update(filter={'domainId': domainId, 'uid': uid},
update={'$set': kwargs},
upsert=True,
return_document=ReturnDocument.AFTER)
async def unset_user(domainId, uid, fields):
coll = db.coll('domain.user')
return await coll.find_one_and_update(filter={'domainId': domainId, 'uid': uid},
update={'$unset': dict((f, '') for f in set(fields))},
upsert=True,
return_document=ReturnDocument.AFTER)
async def set_users(domainId, uids, **kwargs):
coll = db.coll('domain.user')
await coll.update_many({'domainId': domainId, 'uid': {'$in': list(set(uids))}},
{'$set': kwargs},
upsert=False)
async def unset_users(domainId, uids, fields):
coll = db.coll('domain.user')
await coll.update_many({'domainId': domainId, 'uid': {'$in': list(set(uids))}},
{'$unset': dict((f, '') for f in set(fields))},
upsert=True)
@argmethod.wrap
async def set_user_role(domainId: str, uid: int, role: str):
validator.check_role(role)
# use set_users to utilize "upsert=False"
return await set_users(domainId, [uid], role=role)
@argmethod.wrap
async def unset_user_role(domainId: str, uid: int):
return await unset_user(domainId, uid, ['role'])
async def set_users_role(domainId: str, uids, role: str):
validator.check_role(role)
await set_users(domainId, uids, role=role)
async def unset_users_role(domainId: str, uids):
await unset_users(domainId, uids, ['role'])
async def inc_user_usage(domainId: str, uid: int, usage_field: str, usage: int, quota: int):
coll = db.coll('domain.user')
try:
return await coll.find_one_and_update(filter={'domainId': domainId, 'uid': uid,
usage_field: {'$not': {'$gte': quota - usage}}},
update={'$inc': {usage_field: usage}},
upsert=True,
return_document=ReturnDocument.AFTER)
except errors.DuplicateKeyError:
raise error.UsageExceededError(domainId, uid, usage_field, usage, quota)
def get_multi_user(*, fields=None, **kwargs):
coll = db.coll('domain.user')
return coll.find(kwargs, fields)
async def get_dict_user_by_uid(domainId, uids, *, fields=None):
result = dict()
async for dudoc in get_multi_user(
domainId=domainId, uid={'$in': list(set(uids))}, fields=fields):
result[dudoc['uid']] = dudoc
return result
async def get_dict_user_by_domainId(uid, *, fields=None):
result = dict()
async for dudoc in get_multi_user(uid=uid, fields=fields):
result[dudoc['domainId']] = dudoc
return result
def get_all_roles(ddoc):
builtin_roles = {role: rd.default_permission for role, rd in builtin.BUILTIN_ROLE_DESCRIPTORS.items()}
domain_roles = ddoc['roles']
return {**builtin_roles, **domain_roles}
def get_join_settings(ddoc, now):
if 'join' not in ddoc:
return None
join_settings = ddoc['join']
if not join_settings:
return None
if join_settings['method'] == constant.domain.JOIN_METHOD_NONE:
return None
if join_settings['role'] not in ddoc['roles']:
return None
if join_settings['expire'] != None and join_settings['expire'] < now:
return None
return join_settings
@argmethod.wrap
async def get_prefix_search(prefix: str, fields={}, limit: int=50):
prefix_lower = prefix.lower()
builtin_ddocs = []
for domain in builtin.DOMAINS:
if domain['_id'].lower().startswith(prefix_lower) or \
domain['name'].lower().startswith(prefix_lower):
builtin_ddocs.append(domain)
regex = r'\A\Q{0}\E'.format(prefix.replace(r'\E', r'\E\\E\Q'))
coll = db.coll('domain')
ddocs = await coll.find({'$or': [{'_id': {'$regex': regex}},
{'name': {'$regex': regex}}]},
projection=fields) \
.limit(limit) \
.to_list()
return builtin_ddocs + ddocs
@argmethod.wrap
async def ensure_indexes():
coll = db.coll('domain')
await coll.create_index('owner_uid')
await coll.create_index('name')
user_coll = db.coll('domain.user')
await user_coll.create_index('uid')
await user_coll.create_index([('domainId', 1),
('uid', 1)], unique=True)
await user_coll.create_index([('domainId', 1),
('role', 1)], sparse=True)
await user_coll.create_index([('domainId', 1),
('rp', -1)])
await user_coll.create_index([('domainId', 1),
('rank', 1)])
if __name__ == '__main__':
argmethod.invoke_by_args()
*/
+8 -11
View File
@@ -6,7 +6,6 @@ const
coll_status = db.collection('problem.status');
/**
* @param {string} domainId
* @param {string} title
* @param {string} content
* @param {number} owner
@@ -17,7 +16,6 @@ const
* @param {boolean} hidden
*/
async function add({
domainId,
title,
content,
owner,
@@ -30,7 +28,6 @@ async function add({
validator.checkTitle(title);
validator.checkContent(content);
await coll.insertOne({
domainId,
content,
owner,
pid,
@@ -44,24 +41,24 @@ async function add({
});
return pid;
}
async function get({ domainId, pid, uid }) {
async function get({ pid, uid }) {
pid = parseInt(pid) || pid;
let pdoc = await coll.findOne({ domainId, pid });
if (!pdoc) throw new ProblemNotFoundError(domainId, pid);
let pdoc = await coll.findOne({ pid });
if (!pdoc) throw new ProblemNotFoundError(pid);
pdoc.psdoc = uid ?
await coll_status.findOne({ domainId, pid, uid }) :
await coll_status.findOne({ pid, uid }) :
null;
return pdoc;
}
async function getMany(query, sort, page, limit) {
return await coll.find(query).sort(sort).skip((page - 1) * limit).limit(limit).toArray();
}
async function edit(domainId, pid, $set) {
async function edit(pid, $set) {
if ($set.title) validator.checkTitle($set.title);
if ($set.content) validator.checkContent($set.content);
await coll.findOneAndUpdate({ domainId, pid }, { $set });
let pdoc = await coll.findOne({ domainId, pid });
if (!pdoc) throw new ProblemNotFoundError(domainId, pid);
await coll.findOneAndUpdate({ pid }, { $set });
let pdoc = await coll.findOne({ pid });
if (!pdoc) throw new ProblemNotFoundError(pid);
return pdoc;
}
async function count(query) {
+4 -4
View File
@@ -36,10 +36,10 @@ async function get(rid) {
async function getMany(query, sort, page, limit) {
return await coll.find(query).sort(sort).skip((page - 1) * limit).limit(limit).toArray();
}
async function update(domainId, rid, $set) {
await coll.findOneAndUpdate({ domainId, _id: rid }, { $set });
let rdoc = await coll.findOne({ domainId, _id: rid });
if (!rdoc) throw new RecordNotFoundError(domainId, rid);
async function update(rid, $set) {
await coll.findOneAndUpdate({ _id: rid }, { $set });
let rdoc = await coll.findOne({ _id: rid });
if (!rdoc) throw new RecordNotFoundError(rid);
return rdoc;
}
async function count(query) {
+10 -2
View File
@@ -12,10 +12,17 @@ async function update(_id, update, config) {
}
/**
* Increments the user counter.
* @returns {Number} Integer value after increment.
* @returns {number} Integer value after increment.
*/
async function incUserCounter() {
return this.update('userCounter', { $inc: { value: 1 } }, { upsert: true });
return await this.update('userCounter', { $inc: { value: 1 } }, { upsert: true });
}
/**
* Increments the problem ID counter.
* @returns {number} Integer value before increment.
*/
async function incPidCounter() {
return await this.update('userCounter', { $inc: { value: 1 } }, { upsert: true });
}
async function acquireLock(name) {
let value = Math.floor(Math.random() * 0xFFFFFFFF);
@@ -35,6 +42,7 @@ async function releaseLockAnyway(name) {
module.exports = {
get,
update,
incPidCounter,
incUserCounter,
acquireLock,
releaseLock,
+14 -23
View File
@@ -1,5 +1,4 @@
const
domain = require('./domain'),
validator = require('../lib/validator'),
{ ValidationError, TrainingNotFoundError } = require('../error'),
db = require('../service/db.js'),
@@ -11,7 +10,7 @@ module.exports = {
SETTING_DIFFICULTY_ADMIN: 1,
SETTING_DIFFICULTY_AVERAGE: 2,
async add(domainId, title, content, owner_uid, dag = [], desc = '') {
async add(title, content, owner_uid, dag = [], desc = '') {
validator.check_title(title);
validator.check_intro(content);
validator.check_description(desc);
@@ -20,12 +19,12 @@ module.exports = {
if (nid >= node._id)
throw new ValidationError('dag');
return await coll.insertOne({
domainId, content, owner_uid,
content, owner_uid,
dag, title, desc, enroll: 0
});
},
count: (domainId, query) => coll.find(Object.assign({ domainId }, query)).count(),
async edit(domainId, tid, $set) {
count: (query) => coll.find(query).count(),
async edit(tid, $set) {
if ($set.title) validator.check_title($set.title);
if ($set.content) validator.check_intro($set.content);
if ($set.desc) validator.check_description($set.desc);
@@ -33,28 +32,20 @@ module.exports = {
for (let node of $set.dag)
for (let nid of node.require_nids)
if (nid >= node._id) throw new ValidationError('dag');
await coll.findOneAndUpdate({ domainId, tid }, { $set });
let tdoc = await coll.findOne({ domainId, tid });
if (!tdoc) throw new TrainingNotFoundError(domainId, tid);
await coll.findOneAndUpdate({ tid }, { $set });
let tdoc = await coll.findOne({ tid });
if (!tdoc) throw new TrainingNotFoundError(tid);
return tdoc;
},
async get(domainId, tid) {
let tdoc = await coll.findOne({ domainId, tid });
if (!tdoc) throw new TrainingNotFoundError(domainId, tid);
async get(tid) {
let tdoc = await coll.findOne({ tid });
if (!tdoc) throw new TrainingNotFoundError(tid);
return tdoc;
},
get_multi: (domainId, query) => coll.find(Object.assign({ domainId }, query)),
get_multi_status: (query) => coll.find(query),
async get_random_id(query) {
let pdocs = coll.find(query);
let pcount = await pdocs.count();
if (pcount) {
let pdoc = await pdocs.skip(Math.floor(Math.random() * pcount)).limit(1).toArray()[0];
return pdoc.pid;
} else return null;
},
get_status: (domainId, tid, uid) => coll_status.findOne({ domainId, tid, uid }),
set_status: (domainId, tid, uid, $set) => coll_status.findOneAndUpdate({ domainId, tid, uid }, { $set })
get_multi: (query) => coll.find( query),
get_multi_status: (query) => coll_status.find(query),
get_status: (tid, uid) => coll_status.findOne({ tid, uid }),
set_status: (tid, uid, $set) => coll_status.findOneAndUpdate({ tid, uid }, { $set })
};
+17 -45
View File
@@ -1,28 +1,19 @@
const
{ UserNotFoundError, UserAlreadyExistError, DomainNotFoundError,
UserAlreadyDomainMemberError } = require('../error'),
{ UserNotFoundError, UserAlreadyExistError } = require('../error'),
system = require('./system'),
domain = require('./domain'),
{ pwhash, validator } = require('../utils'),
db = require('../service/db.js'),
coll = db.collection('user'),
coll_domain = db.collection('domain_user');
coll_role = db.collection('role');
class USER {
constructor(user, domainUser = {}) {
if (!user) throw new UserNotFoundError();
constructor(user) {
this._id = user._id;
this.email = user.email;
this.uname = user.uname;
this.priv = user.priv;
this.salt = user.salt;
this.hash = user.hash;
this.perm = domainUser.perm || '0';
this.displayName = domainUser.displayName || null;
this.domainId = domainUser.domainId || 'system';
}
hasPriv(priv) {
return this.priv & priv;
this.perm = user.perm;
}
hasPerm(perm) {
console.log('PERM', perm, this.perm);
@@ -31,43 +22,28 @@ class USER {
checkPassword(password) {
return pwhash.check(password, this.salt, this.hash);
}
/**
* @param {string} role
* @param {date} joinAt
*/
async joinDomain(role, joinAt) {
validator.checkRole(role);
try {
await coll_domain.updateOne(
{ domainId: this.domainId, uid: this._id, role: { $exists: false } },
{ $set: { role, joinAt: joinAt || new Date() } },
{ upsert: true }
);
} catch (e) {
throw new UserAlreadyDomainMemberError(this.domainId, this._id);
}
return true;
}
}
async function getById(_id, domainId) {
let dudoc, udoc = await coll.findOne({ _id });
if (domainId) {
let ddoc = await domain.get(domainId);
if (!ddoc) throw new DomainNotFoundError(domainId);
dudoc = (await coll_domain.findOne({ uid: _id, domainId })) || {};
console.log(dudoc.role, ddoc.roles, { uid: _id, domainId });
dudoc.perm = ddoc.roles[dudoc.role || 'default'];
}
return new USER(udoc, dudoc);
async function getById(_id) {
let udoc = await coll.findOne({ _id });
if (!udoc) throw new UserNotFoundError(_id);
let role = await coll_role.findOne({ _id: udoc.role || 'default' });
udoc.perm = role.perm;
return new USER(udoc);
}
async function getByUname(uname) {
let unameLower = uname.trim().toLowerCase();
let udoc = await coll.findOne({ unameLower });
if (!udoc) throw new UserNotFoundError(uname);
let role = await coll_role.findOne({ _id: udoc.role || 'default' });
udoc.perm = role.perm;
return new USER(udoc);
}
async function getByEmail(email) {
let emailLower = email.trim().toLowerCase();
let udoc = await coll.findOne({ emailLower });
if (!udoc) throw new UserNotFoundError(email);
let role = await coll_role.findOne({ _id: udoc.role || 'default' });
udoc.perm = role.perm;
return new USER(udoc);
}
async function setPassword(uid, password) {
@@ -84,10 +60,6 @@ async function setEmail(uid, email) {
function setById(uid, args) {
coll.findOneAndUpdate({ _id: uid }, { $set: args });
}
function setPriv(uid, priv) {
setById(uid, { priv });
}
async function changePassword(uid, currentPassword, newPassword) {
validator.checkPassword(newPassword);
let udoc = await getById(uid);
@@ -133,5 +105,5 @@ module.exports = {
changePassword, create, getByEmail,
getById, getByUname, getMany,
setById, setEmail,
setPassword, setPriv
setPassword
};
+3 -1
View File
@@ -12,7 +12,9 @@ let options = {
username: '',
password: ''
},
mq: '',
template: {
path: path.join(__dirname, '..', 'templates')
},
listen: {
host: '127.0.0.1',
port: 8888,
+7 -3
View File
@@ -1,6 +1,8 @@
let permissions = {
PERM_NONE: '0',
PERM_ALL: '-',
PERM_LOGGEDIN: '~',
PERM_REGISTER_USER: '+',
PERM_VIEW: '1',
PERM_EDIT_PERM: '2',
PERM_MOD_BADGE: '3',
@@ -38,7 +40,7 @@ let permissions = {
PERM_EDIT_CONTEST: 'Z',
PERM_VIEW_TRAINING: 'a',
PERM_CREATE_TRAINING: 'b',
PERM_EDIT_TRAINING: 'c'
PERM_EDIT_TRAINING: 'c',
};
permissions.PERM_BASIC =
permissions.PERM_VIEW +
@@ -46,7 +48,8 @@ permissions.PERM_BASIC =
permissions.PERM_VIEW_PROBLEM_SOLUTION +
permissions.PERM_VIEW_DISCUSSION +
permissions.PERM_VIEW_CONTEST +
permissions.PERM_VIEW_CONTEST_SCOREBOARD;
permissions.PERM_VIEW_CONTEST_SCOREBOARD +
permissions.PERM_REGISTER_USER;
permissions.PERM_DEFAULT =
permissions.PERM_VIEW +
permissions.PERM_VIEW_PROBLEM +
@@ -62,6 +65,7 @@ permissions.PERM_DEFAULT =
permissions.PERM_VIEW_CONTEST_SCOREBOARD +
permissions.PERM_ATTEND_CONTEST +
permissions.PERM_VIEW_TRAINING +
permissions.PERM_CREATE_TRAINING;
permissions.PERM_CREATE_TRAINING +
permissions.PERM_LOGGEDIN;
permissions.PERM_ADMIN = permissions.PERM_ALL;
module.exports = permissions;
-32
View File
@@ -1,32 +0,0 @@
let privileges = {
PRIV_NONE: 0,
PRIV_ALL: -1,
PRIV_SET_PRIV: 1 << 0,
PRIV_SET_PERM: 1 << 1,
PRIV_USER_PROFILE: 1 << 2,
PRIV_REGISTER_USER: 1 << 3,
PRIV_READ_PROBLEM_DATA: 1 << 4,
PRIV_READ_PRETEST_DATA: 1 << 5,
PRIV_READ_PRETEST_DATA_SELF: 1 << 6,
PRIV_READ_RECORD_CODE: 1 << 7,
PRIV_VIEW_HIDDEN_RECORD: 1 << 8,
PRIV_WRITE_RECORD: 1 << 9,
PRIV_CREATE_DOMAIN: 1 << 10,
PRIV_VIEW_ALL_DOMAIN: 1 << 11,
PRIV_MANAGE_ALL_DOMAIN: 1 << 12,
PRIV_REJUDGE: 1 << 13,
PRIV_VIEW_USER_SECRET: 1 << 14,
PRIV_VIEW_JUDGE_STATISTICS: 1 << 15,
PRIV_CREATE_FILE: 1 << 16,
PRIV_UNLIMITED_QUOTA: 1 << 17,
PRIV_DELETE_FILE: 1 << 18,
PRIV_DELETE_FILE_SELF: 1 << 19
};
privileges.PRIV_DEFAULT =
privileges.PRIV_USER_PROFILE | privileges.PRIV_CREATE_DOMAIN
| privileges.PRIV_CREATE_FILE | privileges.PRIV_DELETE_FILE_SELF;
privileges.PRIV_JUDGE =
privileges.PRIV_USER_PROFILE | privileges.PRIV_VIEW_ALL_DOMAIN
| privileges.PRIV_READ_PROBLEM_DATA | privileges.PRIV_READ_PRETEST_DATA
| privileges.PRIV_READ_RECORD_CODE | privileges.PRIV_WRITE_RECORD;
module.exports = privileges;
+24 -49
View File
@@ -1,53 +1,28 @@
const options = require('../options');
if (options.mq) {
const amqplib = require('amqplib');
/**
* @type {import('amqplib').Connection}
*/
let conn = null;
let ch = null;
module.exports = {
async connect() {
conn = await amqplib.connect(options.mq.url);
ch = conn.createChannel();
},
async assertQueue(queue) {
return await ch.assertQueue(queue);
},
async publish(queue, data) {
return await ch.sendToQueue(queue, Buffer.from(data));
},
async consume(queue, callback) {
ch.consume(queue, function (msg) {
if (msg !== null) {
callback(msg.content.toString(), () => { ch.ack(msg); });
ch.ack(msg);
}
});
}
};
} else {
let q = {};
module.exports = {
async connect() { },
async assertQueue(queue) {
q[queue] = q[queue] || [];
},
async publish(queue, data) {
q[queue].push(data);
},
async consume(queue, callback) {
while (1) {
if (q[queue].length) {
let data = q[queue][0];
q[queue].splice(0, 1);
callback(data, () => { });
}
let q = {};
module.exports = {
async connect() { },
async assert(queue) {
q[queue] = q[queue] || [];
},
async push(queue, data) {
q[queue].push(data);
},
async get(queue, wait = true) {
if (wait) {
while (!q[queue].length)
await new Promise(resolve => {
setTimeout(resolve, 500);
setTimeout(resolve, 100);
});
let data = q[queue][0];
q[queue].splice(0, 1);
return data;
} else {
if (!q[queue].length) return null;
else {
let data = q[queue][0];
q[queue].splice(0, 1);
return data;
}
}
};
}
}
};
+48 -7
View File
@@ -1,12 +1,50 @@
const
Koa = require('koa'),
morgan = require('koa-morgan'),
Body = require('koa-body'),
Router = require('koa-router'),
SockJS = require('sockjs');
const options = require('../options');
let http = options.listen.https ? require('https') : require('http');
let app = new (require('koa'))();
let app = new Koa();
let server = http.createServer(app.callback());
app.keys = options.session.keys;
app.use(require('koa-morgan')(':method :url :status :res[content-length] - :response-time ms'));
app.use(require('koa-body')());
let router = (new require('koa-router'))();
const sockjs = require('sockjs');
app.use(morgan(':method :url :status :res[content-length] - :response-time ms'));
app.use(Body());
let router = new Router();
let contexts = [];
function CONTEXT() {
this.router = new Router();
/**
* @param {import('koa').Middleware} middleware
*/
function MIDDLEWARE(middleware) {
router.use(middleware);
}
/**
* @param {string} route
* @param {...import('koa').Middleware} handler
*/
function GET(route, ...handler) {
router.get(route, ...handler);
}
/**
* @param {string} route
* @param {...import('koa').Middleware} handler
*/
function POST(route, ...handler) {
router.post(route, ...handler);
}
this.MIDDLEWARE = MIDDLEWARE;
this.GET = GET;
this.POST = POST;
contexts.push(this);
return this;
}
/**
* @param {import('koa').Middleware} middleware
*/
@@ -36,16 +74,19 @@ function POST(route, ...handler) {
* @param {SockJSHandler} handler
*/
function SOCKET(prefix, handler) {
const sock = sockjs.createServer({ prefix });
const sock = SockJS.createServer({ prefix });
sock.on('connection', handler);
sock.installHandlers(server);
}
exports.CONTEXT = CONTEXT;
exports.MIDDLEWARE = MIDDLEWARE;
exports.GET = GET;
exports.POST = POST;
exports.SOCKET = SOCKET;
exports.start = function start() {
app.use(router.routes()).use(router.allowedMethods());
for (let c of contexts)
app.use(c.router.routes()).use(c.router.allowedMethods());
app.listen(options.listen.port);
console.log('Server listening at: %s', options.listen.port);
};
};
+1 -1
View File
@@ -1,6 +1,6 @@
const
Mongo = require('mongodb'),
options = require('./lib/options');
options = require('./options');
async function run() {
let mongourl = 'mongodb://';
+2 -1
View File
@@ -6,7 +6,6 @@
"author": "masnn",
"license": "MIT",
"dependencies": {
"amqplib": "^0.5.5",
"axios": "^0.19.0",
"bson": "^4.0.2",
"js-yaml": "^3.13.1",
@@ -14,9 +13,11 @@
"koa-body": "^4.1.1",
"koa-morgan": "^1.0.1",
"koa-router": "^7.4.0",
"koa-static": "^5.0.0",
"lodash": "^4.17.15",
"mongodb": "^3.3.4",
"nodemailer": "^6.3.1",
"nunjucks": "^3.2.1",
"sockjs": "^0.3.20"
},
"devDependencies": {
+194 -73
View File
@@ -36,6 +36,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.14.tgz#1c1d6e3c75dba466e0326948d56e8bd72a1903d2"
integrity sha512-u/SJDyXwuihpwjXy7hOOghagLEV1KdAST6syfnOk6QZAMzZuWZqXy5aYYZbh8Jdpd4escVFP0MvftHNDb9pruA==
a-sync-waterfall@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz#75b6b6aa72598b497a125e7a2770f14f4c8a1fa7"
integrity sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==
accepts@^1.3.5:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@@ -64,18 +69,6 @@ ajv@^6.10.0, ajv@^6.10.2:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
amqplib@^0.5.5:
version "0.5.5"
resolved "https://registry.yarnpkg.com/amqplib/-/amqplib-0.5.5.tgz#698f0cb577e0591954a90572fcb3b8998a76fd40"
integrity sha512-sWx1hbfHbyKMw6bXOK2k6+lHL8TESWxjAx5hG8fBtT7wcxoXNIsFxZMnFyBjxt3yL14vn7WqBDe5U6BGOadtLg==
dependencies:
bitsyntax "~0.1.0"
bluebird "^3.5.2"
buffer-more-ints "~1.0.0"
readable-stream "1.x >=1.1.9"
safe-buffer "~5.1.2"
url-parse "~1.4.3"
ansi-escapes@^4.2.1:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.0.tgz#a4ce2b33d6b214b7950d8595c212f12ac9cc569d"
@@ -100,11 +93,19 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
any-promise@^1.1.0:
any-promise@^1.0.0, any-promise@^1.1.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
integrity sha1-q8av7tzqUugJzcA3au0845Y10X8=
anymatch@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@@ -112,6 +113,11 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
asap@^2.0.3:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
astral-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
@@ -142,19 +148,10 @@ basic-auth@~2.0.0:
dependencies:
safe-buffer "5.1.2"
bitsyntax@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/bitsyntax/-/bitsyntax-0.1.0.tgz#b0c59acef03505de5a2ed62a2f763c56ae1d6205"
integrity sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==
dependencies:
buffer-more-ints "~1.0.0"
debug "~2.6.9"
safe-buffer "~5.1.2"
bluebird@^3.5.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
binary-extensions@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
brace-expansion@^1.1.7:
version "1.1.11"
@@ -164,6 +161,13 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
bson@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.3.tgz#aa82cb91f9a453aaa060d6209d0675114a8154d3"
@@ -177,11 +181,6 @@ bson@^4.0.2:
buffer "^5.1.0"
long "^4.0.0"
buffer-more-ints@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz#ef4f8e2dddbad429ed3828a9c55d44f05c611422"
integrity sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==
buffer@^5.1.0:
version "5.4.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115"
@@ -222,6 +221,21 @@ chardet@^0.7.0:
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
chokidar@^3.3.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450"
integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==
dependencies:
anymatch "~3.1.1"
braces "~3.0.2"
glob-parent "~5.1.0"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.3.0"
optionalDependencies:
fsevents "~2.1.2"
cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
@@ -261,6 +275,11 @@ color-name@1.1.3:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
commander@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e"
integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -286,11 +305,6 @@ cookies@~0.8.0:
depd "~2.0.0"
keygrip "~1.1.0"
core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -302,7 +316,7 @@ cross-spawn@^6.0.5:
shebang-command "^1.2.0"
which "^1.2.9"
debug@2.6.9, debug@~2.6.9:
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -548,6 +562,13 @@ file-entry-cache@^5.0.1:
dependencies:
flat-cache "^2.0.1"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
flat-cache@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0"
@@ -584,6 +605,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
fsevents@~2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805"
integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
@@ -596,6 +622,13 @@ glob-parent@^5.0.0:
dependencies:
is-glob "^4.0.1"
glob-parent@~5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229"
integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==
dependencies:
is-glob "^4.0.1"
glob@^7.1.3:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
@@ -637,6 +670,16 @@ http-errors@1.7.3, http-errors@^1.3.1, http-errors@^1.6.3, http-errors@~1.7.2:
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
http-errors@~1.6.2:
version "1.6.3"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=
dependencies:
depd "~1.1.2"
inherits "2.0.3"
setprototypeof "1.1.0"
statuses ">= 1.4.0 < 2"
"http-parser-js@>=0.4.0 <0.4.11":
version "0.4.10"
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4"
@@ -685,11 +728,16 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
inherits@2, inherits@2.0.4, inherits@~2.0.1:
inherits@2, inherits@2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
inherits@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
inquirer@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.0.tgz#9e2b032dde77da1db5db804758b8fea3a970519a"
@@ -709,6 +757,13 @@ inquirer@^7.0.0:
strip-ansi "^5.1.0"
through "^2.3.6"
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-buffer@^2.0.2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623"
@@ -734,13 +789,18 @@ is-generator-function@^1.0.7:
resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522"
integrity sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==
is-glob@^4.0.0, is-glob@^4.0.1:
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-promise@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa"
@@ -834,6 +894,24 @@ koa-router@^7.4.0:
path-to-regexp "^1.1.1"
urijs "^1.19.0"
koa-send@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/koa-send/-/koa-send-5.0.0.tgz#5e8441e07ef55737734d7ced25b842e50646e7eb"
integrity sha512-90ZotV7t0p3uN9sRwW2D484rAaKIsD8tAVtypw/aBU+ryfV+fR2xrcAwhI8Wl6WRkojLUs/cB9SBSCuIb+IanQ==
dependencies:
debug "^3.1.0"
http-errors "^1.6.3"
mz "^2.7.0"
resolve-path "^1.4.0"
koa-static@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/koa-static/-/koa-static-5.0.0.tgz#5e92fc96b537ad5219f425319c95b64772776943"
integrity sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==
dependencies:
debug "^3.1.0"
koa-send "^5.0.0"
koa@^2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/koa/-/koa-2.11.0.tgz#fe5a51c46f566d27632dd5dc8fd5d7dd44f935a4"
@@ -970,6 +1048,15 @@ mute-stream@0.0.8:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
mz@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32"
integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
dependencies:
any-promise "^1.0.0"
object-assign "^4.0.1"
thenify-all "^1.0.0"
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
@@ -990,6 +1077,27 @@ nodemailer@^6.3.1:
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.3.1.tgz#2784beebac6b9f014c424c54dbdcc5c4d1221346"
integrity sha512-j0BsSyaMlyadEDEypK/F+xlne2K5m6wzPYMXS/yxKI0s7jmT1kBx6GEKRVbZmyYfKOsjkeC/TiMVDJBI/w5gMQ==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
nunjucks@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/nunjucks/-/nunjucks-3.2.1.tgz#f229539281e92c6ad25d8c578c9bdb41655caf83"
integrity sha512-LYlVuC1ZNSalQQkLNNPvcgPt2M9FTY9bs39mTCuFXtqh7jWbYzhDlmz2M6onPiXEhdZo+b9anRhc+uBGuJZ2bQ==
dependencies:
a-sync-waterfall "^1.0.0"
asap "^2.0.3"
commander "^3.0.2"
optionalDependencies:
chokidar "^3.3.0"
object-assign@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
on-finished@^2.3.0, on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
@@ -1050,7 +1158,7 @@ parseurl@^1.3.2:
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
path-is-absolute@^1.0.0:
path-is-absolute@1.0.1, path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
@@ -1067,6 +1175,11 @@ path-to-regexp@^1.1.1:
dependencies:
isarray "0.0.1"
picomatch@^2.0.4, picomatch@^2.0.7:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@@ -1087,11 +1200,6 @@ qs@^6.4.0:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.1.tgz#20082c65cb78223635ab1a9eaca8875a29bf8ec9"
integrity sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==
querystringify@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e"
integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==
raw-body@^2.2.0:
version "2.4.1"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c"
@@ -1102,15 +1210,12 @@ raw-body@^2.2.0:
iconv-lite "0.4.24"
unpipe "1.0.0"
"readable-stream@1.x >=1.1.9":
version "1.1.14"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
readdirp@~3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.3.0.tgz#984458d13a1e42e2e9f5841b129e162f369aff17"
integrity sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.1"
isarray "0.0.1"
string_decoder "~0.10.x"
picomatch "^2.0.7"
regexpp@^2.0.1:
version "2.0.1"
@@ -1125,11 +1230,6 @@ require_optional@^1.0.1:
resolve-from "^2.0.0"
semver "^5.1.0"
requires-port@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
resolve-from@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57"
@@ -1140,6 +1240,14 @@ resolve-from@^4.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve-path@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/resolve-path/-/resolve-path-1.4.0.tgz#c4bda9f5efb2fce65247873ab36bb4d834fe16f7"
integrity sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=
dependencies:
http-errors "~1.6.2"
path-is-absolute "1.0.1"
restore-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
@@ -1169,7 +1277,7 @@ rxjs@^6.4.0:
dependencies:
tslib "^1.9.0"
safe-buffer@5.1.2, safe-buffer@~5.1.2:
safe-buffer@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
@@ -1201,6 +1309,11 @@ semver@^6.1.2:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
setprototypeof@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
setprototypeof@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
@@ -1253,7 +1366,7 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
"statuses@>= 1.5.0 < 2", statuses@^1.5.0:
"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
@@ -1276,11 +1389,6 @@ string-width@^4.1.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"
string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
strip-ansi@^5.1.0, strip-ansi@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
@@ -1322,6 +1430,20 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
thenify-all@^1.0.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726"
integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=
dependencies:
thenify ">= 3.1.0 < 4"
"thenify@>= 3.1.0 < 4":
version "3.3.0"
resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839"
integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=
dependencies:
any-promise "^1.0.0"
through@^2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
@@ -1334,6 +1456,13 @@ tmp@^0.0.33:
dependencies:
os-tmpdir "~1.0.2"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
@@ -1386,14 +1515,6 @@ urijs@^1.19.0:
resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.2.tgz#f9be09f00c4c5134b7cb3cf475c1dd394526265a"
integrity sha512-s/UIq9ap4JPZ7H1EB5ULo/aOUbWqfDi7FKzMC2Nz+0Si8GiT1rIEaprt8hy3Vy2Ex2aJPpOQv4P4DuOZ+K1c6w==
url-parse@~1.4.3:
version "1.4.7"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==
dependencies:
querystringify "^2.1.1"
requires-port "^1.0.0"
uuid@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"