mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
remove domain system
This commit is contained in:
1 parent
7fa4a58abe
commit
fc26af30ef
31 files changed
+709
-1242
No files matched your search
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
@@ -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
@@ -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
@@ -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
@@ -1,6 +1,6 @@
|
||||
const
|
||||
Mongo = require('mongodb'),
|
||||
options = require('./lib/options');
|
||||
options = require('./options');
|
||||
|
||||
async function run() {
|
||||
let mongourl = 'mongodb://';
|
||||
|
||||
Reference in new issue
Block a user