module.add(user,problem,record)

This commit is contained in:
masnn committed 2020-04-01 18:13:04 +08:00
commit d5b1f623f8
43 files changed
+5965

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
const
db = require('../service/db.js'),
coll = db.collection('blacklist');
module.exports = {
add(ip) {
let expireAt = new Date(new Date().getTime() + 365 * 24 * 60 * 60 * 1000);
return coll.findOneAndUpdate({ _id: ip }, { $set: { expireAt } }, { upsert: true });
},
get: ip => coll.findOne({ _id: ip }),
delete: ip => coll.deleteOne({ _id: ip })
};
+52
View File
@@ -0,0 +1,52 @@
const
perm = require('../permission'),
priv = require('../privilege');
const BUILTIN_USERS = [
{
_id: 0,
uname: 'Hydro',
unameLower: 'hydro',
email: '',
emailLower: '',
salt: '',
hash: '',
gender: 'other',
regat: new Date(0),
regip: '127.0.0.1',
priv: priv.PRIV_NONE,
gravatar: '',
loginat: new Date(),
loginip: '127.0.0.1'
},
{
_id: 1,
email: 'guest@hydro',
emailLower: 'guest@hydro',
uname: 'Guest',
unameLower: 'guest',
hash: '',
salt: '',
regat: new Date(),
regip: '127.0.0.1',
loginat: new Date(),
loginip: '127.0.0.1',
priv: priv.PRIV_DEFAULT,
gravatar: 'guest@hydro'
}
];
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: ''
}];
module.exports = {
BUILTIN_USERS, BUILTIN_DOMAINS
};
+338
View File
@@ -0,0 +1,338 @@
const
domain = require('./domain'),
validator = require('../lib/validator'),
{ ValidationError, ContestNotFoundError } = require('../error'),
RULE_HOMEWORK = require('../module/contest/homework'),
RULE_OI = require('../module/contest/oi'),
RULE_ACM = require('../module/contest/acm'),
db = require('../service/db.js'),
coll = db.collection('contest'),
coll_status = db.collection('contest.status');
const RULES = {
homework: RULE_HOMEWORK,
oi: RULE_OI,
acm: RULE_ACM
};
module.exports = {
RULES,
async add(domainId, 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 });
this.RULES[rule].check(data);
return await coll.insertOne(data);
},
count: query => coll.find(query).count(),
async edit(domainId, 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);
this.RULES[$set.rule || cdoc.rule].check(Object.assign(cdoc, $set));
await coll.findOneAndUpdate({ domainId, cid }, { $set });
return cdoc;
},
async get(domainId, cid) {
let tdoc = await coll.findOne({ domainId, cid });
if (!tdoc) throw new ContestNotFoundError(domainId, cid);
return tdoc;
},
get_multi: (domainId, query, fields) => coll.find(Object.assign({ domainId }, query), { fields }),
get_multi_status: (query, fields) => coll.find(query, { fields }),
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, cid, uid, fields) => coll_status.findOne({ domainId, cid, uid }, { fields }),
set_status: (domainId, cid, uid, $set) => coll_status.findOneAndUpdate({ domainId, cid, uid }, { $set })
};
/*
journal_key_func = lambda j: j['rid']
Rule = collections.namedtuple('Rule', ['show_record_func',
'show_scoreboard_func',
'stat_func',
'status_sort',
'rank_func',
'scoreboard_func'])
def _oi_equ_func(a, b):
return a.get('score', 0) == b.get('score', 0)
RULES = {
constant.contest.RULE_OI: Rule(lambda tdoc, now: now > tdoc['end_at'],
lambda tdoc, now: now > tdoc['end_at'],
_oi_stat,
[('score', -1)],
functools.partial(rank.ranked, equ_func=_oi_equ_func),
_oi_scoreboard),
constant.contest.RULE_ACM: Rule(lambda tdoc, now: now >= tdoc['begin_at'],
lambda tdoc, now: now >= tdoc['begin_at'],
_acm_stat,
[('accept', -1), ('time', 1)],
functools.partial(enumerate, start=1),
_acm_scoreboard),
constant.contest.RULE_ASSIGNMENT: Rule(lambda tdoc, now: now >= tdoc['begin_at'],
lambda tdoc, now: False, # TODO: show scoreboard according to assignment preference
_assignment_stat,
[('penalty_score', -1), ('time', 1)],
functools.partial(enumerate, start=1),
_assignment_scoreboard),
}
def get_multi(domainId: str, doc_type: int, fields=None, **kwargs):
# TODO(twd2): projection.
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
return document.get_multi(domainId=domainId,
doc_type=doc_type,
fields=fields,
**kwargs) \
.sort([('doc_id', -1)])
@argmethod.wrap
async def attend(domainId: str, doc_type: int, cid: objeccid.Objeccid, uid: int):
# TODO(iceboy): check time.
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
try:
await document.capped_inc_status(domainId, doc_type, cid,
uid, 'attend', 1, 0, 1)
except errors.DuplicateKeyError:
if doc_type == document.TYPE_CONTEST:
raise error.ContestAlreadyAttendedError(domainId, cid, uid) from None
elif doc_type == document.TYPE_HOMEWORK:
raise error.HomeworkAlreadyAttendedError(domainId, cid, uid) from None
return await document.inc(domainId, doc_type, cid, 'attend', 1)
@argmethod.wrap
async def get_status(domainId: str, doc_type: int, cid: objeccid.Objeccid, uid: int, fields=None):
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
return await document.get_status(domainId, doc_type, doc_id=cid,
uid=uid, fields=fields)
def get_multi_status(doc_type: int, *, fields=None, **kwargs):
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
return document.get_multi_status(doc_type=doc_type,
fields=fields, **kwargs)
async def get_dict_status(domainId, uid, doc_type, cids, *, fields=None):
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
result = dict()
async for tsdoc in get_multi_status(domainId=domainId,
uid=uid,
doc_type=doc_type,
doc_id={'$in': list(set(cids))},
fields=fields):
result[tsdoc['doc_id']] = tsdoc
return result
@argmethod.wrap
async def get_and_list_status(domainId: str, doc_type: int, cid: objeccid.Objeccid, fields=None):
# TODO(iceboy): projection, pagination.
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
tdoc = await get(domainId, doc_type, cid)
tsdocs = await document.get_multi_status(domainId=domainId,
doc_type=doc_type,
doc_id=tdoc['doc_id'],
fields=fields) \
.sort(RULES[tdoc['rule']].status_sort) \
.to_list()
return tdoc, tsdocs
def _get_status_journal(tsdoc):
# Sort and uniquify journal of the contest status document, by rid.
return [list(g)[-1] for _, g in itertools.groupby(sorted(tsdoc['journal'], key=journal_key_func),
key=journal_key_func)]
@argmethod.wrap
async def update_status(domainId: str, doc_type: int, cid: objeccid.Objeccid, uid: int,
rid: objeccid.Objeccid, pid: document.convert_doc_id,
accept: bool, score: int):
"""This method returns None when the modification has been superseded by a parallel operation."""
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
tdoc = await document.get(domainId, doc_type, cid)
tsdoc = await document.rev_push_status(
domainId, tdoc['doc_type'], tdoc['doc_id'], uid,
'journal', {'rid': rid, 'pid': pid, 'accept': accept, 'score': score})
if 'attend' not in tsdoc or not tsdoc['attend']:
if tdoc['doc_type'] == document.TYPE_CONTEST:
raise error.ContestNotAttendedError(domainId, cid, uid)
elif tdoc['doc_type'] == document.TYPE_HOMEWORK:
raise error.HomeworkNotAttendedError(domainId, cid, uid)
else:
raise error.InvalidArgumentError('doc_type')
journal = _get_status_journal(tsdoc)
stats = RULES[tdoc['rule']].stat_func(tdoc, journal)
tsdoc = await document.rev_set_status(domainId, tdoc['doc_type'], cid, uid, tsdoc['rev'],
journal=journal, **stats)
return tsdoc
@argmethod.wrap
async def recalc_status(domainId: str, doc_type: int, cid: objeccid.Objeccid):
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
tdoc = await document.get(domainId, doc_type, cid)
async with document.get_multi_status(domainId=domainId,
doc_type=doc_type,
doc_id=tdoc['doc_id']) as tsdocs:
async for tsdoc in tsdocs:
if 'journal' not in tsdoc or not tsdoc['journal']:
continue
journal = _get_status_journal(tsdoc)
stats = RULES[tdoc['rule']].stat_func(tdoc, journal)
await document.rev_set_status(domainId, doc_type, cid, tsdoc['uid'], tsdoc['rev'],
return_doc=False, journal=journal, **stats)
def _parse_pids(pids_str):
pids = misc.dedupe(map(document.convert_doc_id, pids_str.split(',')))
return pids
def _format_pids(pids_list):
return ','.join([str(pid) for pid in pids_list])
class ContestStatusMixin(object):
@property
@functools.lru_cache()
def now(self):
# TODO(iceboy): This does not work on multi-machine environment.
return datetime.datetime.utcnow()
def is_new(self, tdoc):
ready_at = tdoc['begin_at'] - datetime.timedelta(days=1)
return self.now < ready_at
def is_upcoming(self, tdoc):
ready_at = tdoc['begin_at'] - datetime.timedelta(days=1)
return ready_at <= self.now < tdoc['begin_at']
def is_not_started(self, tdoc):
return self.now < tdoc['begin_at']
def is_ongoing(self, tdoc):
return tdoc['begin_at'] <= self.now < tdoc['end_at']
def is_done(self, tdoc):
return tdoc['end_at'] <= self.now
def is_homework_extended(self, tdoc):
return tdoc['penalty_since'] <= self.now < tdoc['end_at']
def status_text(self, tdoc):
if self.is_new(tdoc):
return 'New'
elif self.is_upcoming(tdoc):
return 'Ready (☆▽☆)'
elif self.is_ongoing(tdoc):
return 'Live...'
else:
return 'Done'
def get_status(self, tdoc):
if self.is_not_started(tdoc):
return 'not_started'
elif self.is_ongoing(tdoc):
return 'ongoing'
else:
return 'finished'
class ContestVisibilityMixin(object):
def can_view_hidden_scoreboard(self, tdoc):
if self.domainId != tdoc['domainId']:
return False
if tdoc['doc_type'] == document.TYPE_CONTEST:
return self.has_perm(builtin.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD)
elif tdoc['doc_type'] == document.TYPE_HOMEWORK:
return self.has_perm(builtin.PERM_VIEW_HOMEWORK_HIDDEN_SCOREBOARD)
else:
return False
def can_show_record(self, tdoc, allow_perm_override=True):
if RULES[tdoc['rule']].show_record_func(tdoc, datetime.datetime.utcnow()):
return True
if allow_perm_override and self.can_view_hidden_scoreboard(tdoc):
return True
return False
def can_show_scoreboard(self, tdoc, allow_perm_override=True):
if RULES[tdoc['rule']].show_scoreboard_func(tdoc, datetime.datetime.utcnow()):
return True
if allow_perm_override and self.can_view_hidden_scoreboard(tdoc):
return True
return False
class ContestCommonOperationMixin(object):
async def get_scoreboard(self, doc_type: int, cid: objeccid.Objeccid, is_export: bool=False):
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
tdoc, tsdocs = await get_and_list_status(self.domainId, doc_type, cid)
if not self.can_show_scoreboard(tdoc):
if doc_type == document.TYPE_CONTEST:
raise error.ContestScoreboardHiddenError(self.domainId, cid)
elif doc_type == document.TYPE_HOMEWORK:
raise error.HomeworkScoreboardHiddenError(self.domainId, cid)
udict, dudict, pdict = await asyncio.gather(
user.get_dict([tsdoc['uid'] for tsdoc in tsdocs]),
domain.get_dict_user_by_uid(self.domainId, [tsdoc['uid'] for tsdoc in tsdocs]),
problem.get_dict(self.domainId, tdoc['pids']))
ranked_tsdocs = RULES[tdoc['rule']].rank_func(tsdocs)
rows = RULES[tdoc['rule']].scoreboard_func(is_export, self.translate, tdoc,
ranked_tsdocs, udict, dudict, pdict)
return tdoc, rows, udict
async def verify_problems(self, pids):
pdocs = await problem.get_multi(domainId=self.domainId, doc_id={'$in': pids},
fields={'doc_id': 1}) \
.sort('doc_id', 1) \
.to_list()
exist_pids = [pdoc['doc_id'] for pdoc in pdocs]
if len(pids) != len(exist_pids):
for pid in pids:
if pid not in exist_pids:
raise error.ProblemNotFoundError(self.domainId, pid)
return pids
async def hide_problems(self, pids):
await asyncio.gather(*[problem.set_hidden(self.domainId, pid, True) for pid in pids])
class ContestMixin(ContestStatusMixin, ContestVisibilityMixin, ContestCommonOperationMixin):
pass
*/
+315
View File
@@ -0,0 +1,315 @@
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()
*/
+35
View File
@@ -0,0 +1,35 @@
const
{ OpcountExceededError } = require('../error'),
db = require('../service/db.js'),
coll = db.collection('opcount');
module.exports = {
/**
* @param {string} op
* @param {string} ident
* @param {number} period_secs
* @param {number} max_operations
*/
async inc(op, ident, period_secs, max_operations) {
let cur_time = new Date().getTime();
let begin_at = new Date(cur_time - cur_time % (period_secs * 1000));
let expire_at = new Date(begin_at.getTime() + period_secs * 1000);
try {
await coll.find_one_and_update({
ident, begin_at, expire_at,
op: { $not: { $gte: max_operations } }
}, { $inc: { op: 1 } }, { upsert: true });
} catch (e) {
throw new OpcountExceededError(op, period_secs, max_operations);
}
}
};
/*
@argmethod.wrap
async def ensure_indexes():
coll = db.coll('opcount')
await coll.create_index([('ident', 1),
('begin_at', 1),
('expire_at', 1)], unique=True)
await coll.create_index('expire_at', expireAfterSeconds=0)
*/
+87
View File
@@ -0,0 +1,87 @@
const
validator = require('../lib/validator'),
{ ProblemNotFoundError } = require('../error'),
db = require('../service/db.js'),
coll = db.collection('problem'),
coll_status = db.collection('problem.status');
/**
* @param {string} domainId
* @param {string} title
* @param {string} content
* @param {number} owner
* @param {number} pid
* @param {import('bson').ObjectID} data
* @param {string[]} category
* @param {string[]} tag
* @param {boolean} hidden
*/
async function add({
domainId,
title,
content,
owner,
pid = null,
data = null,
category = [],
tag = [],
hidden = false
}) {
validator.checkTitle(title);
validator.checkContent(content);
await coll.insertOne({
domainId,
content,
owner,
pid,
title,
data,
category,
tag,
hidden,
nSubmit: 0,
nAccept: 0
});
return pid;
}
async function get({ domainId, pid, uid }) {
pid = parseInt(pid) || pid;
let pdoc = await coll.findOne({ domainId, pid });
if (!pdoc) throw new ProblemNotFoundError(domainId, pid);
pdoc.psdoc = uid ?
await coll_status.findOne({ domainId, 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) {
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);
return pdoc;
}
async function count(query) {
return await coll.find(query).count();
}
async function random(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;
}
module.exports = {
add,
get,
getMany,
edit,
count,
random
};
+55
View File
@@ -0,0 +1,55 @@
const
_ = require('lodash'),
{ RecordNotFoundError } = require('../error'),
{ STATUS_WAITING } = require('../status'),
db = require('../service/db.js'),
coll = db.collection('record');
/**
* @param {import('../interface').Record} data
*/
async function add(data) {
_.defaults(data, {
status: STATUS_WAITING,
score: 0,
time: 0,
memory: 0,
rejudged: false,
judgeTexts: [],
compilerTexts: [],
testCases: [],
judger: null,
judgeAt: null
});
let res = await coll.insertOne(data);
return res.insertedId;
}
/**
* @param {string} rid
* @returns {import('../interface').Record}
*/
async function get(rid) {
let rdoc = await coll.findOne({ _id: rid });
if (!rdoc) throw new RecordNotFoundError(rid);
return rdoc;
}
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);
return rdoc;
}
async function count(query) {
return await coll.find(query).count();
}
module.exports = {
add,
get,
getMany,
update,
count
};
+110
View File
@@ -0,0 +1,110 @@
const
db = require('../service/db.js'),
coll = db.collection('system');
async function get(_id) {
let doc = await coll.findOne({ _id });
if (doc) return doc.value;
}
async function update(_id, update, config) {
await coll.findOneAndUpdate({ _id }, update, config);
return this.get(_id);
}
/**
* Increments the user counter.
* @returns {Number} Integer value after increment.
*/
async function incUserCounter() {
return this.update('userCounter', { $inc: { value: 1 } }, { upsert: true });
}
async function acquireLock(name) {
let value = Math.floor(Math.random() * 0xFFFFFFFF);
try {
await coll.updateOne({ _id: 'lock_' + name, value: 0 }, { $set: { value } }, { upsert: true });
} catch (e) { return null; }
return value;
}
async function releaseLock(name, value) {
let result = await coll.updateOne({ _id: 'lock_' + name, value }, { $set: { value: 0 } });
return !!result.matchedCount;
}
async function releaseLockAnyway(name) {
await coll.updateOne({ _id: 'lock_' + name }, { $set: { value: 0 } });
return true;
}
module.exports = {
get,
update,
incUserCounter,
acquireLock,
releaseLock,
releaseLockAnyway
};
/*
EXPECTED_DB_VERSION = 1
async def acquire_upgrade_lock():
lock = await acquire_lock('upgrade')
if not lock:
raise error.UpgradeLockAcquireError()
return lock
async def release_upgrade_lock(lock: int):
success = await release_lock('upgrade', lock)
if not success:
raise error.UpgradeLockReleaseError()
return True
@argmethod.wrap
async def release_upgrade_lock_anyway():
return await release_lock_anyway('upgrade')
@argmethod.wrap
async def get_db_version():
coll = db.coll('system')
doc = await coll.find_one({'_id': 'db_version'})
if doc is None:
return 0
else:
return doc['value']
async def set_db_version(version: int):
coll = db.coll('system')
result = await coll.update_one(filter={'_id': 'db_version'},
update={'$set': {'value': version}},
upsert=True)
return result.modified_count
async def ensure_db_version(allowed_version=None):
if allowed_version is None:
allowed_version = EXPECTED_DB_VERSION
current_version = await get_db_version()
if current_version != allowed_version:
raise error.DatabaseVersionMismatchError(current_version, allowed_version)
@argmethod.wrap
async def setup():
"""
Set up for fresh install
"""
coll = db.coll('system')
fdoc = await coll.find_one({'_id': 'user_counter'})
if fdoc:
# skip if not fresh install
return
await set_db_version(EXPECTED_DB_VERSION)
if __name__ == '__main__':
argmethod.invoke_by_args()
*/
+72
View File
@@ -0,0 +1,72 @@
const
db = require('../service/db.js'),
coll = db.collection('token');
module.exports = {
TYPE_SESSION: 0,
TYPE_REGISTER: 2,
/**
* Add a token.
* @param {number} tokenType type of the token.
* @param {number} expireSeconds expire time, in seconds.
* @param {object} data extra data.
* @returns {string} token ID
*/
async add(tokenType, expireSeconds, data) {
let now = new Date();
let str = String.random(32);
let res = await coll.insertOne(Object.assign({}, data, {
_id: str,
tokenType,
createAt: now,
updateAt: now,
expireAt: new Date(now.getTime() + expireSeconds * 1000)
}));
return [str, res.ops];
},
/**
* Get a token.
* @param {string} tokenId token ID.
* @param {number} tokenType type of the token.
* @returns {object} The token document, or null.
*/
get(tokenId, tokenType) {
return coll.findOne({ _id: tokenId, tokenType });
},
/**
* Update a token.
* @param {string} tokenId token ID.
* @param {number} tokenType type of the token.
* @param {number} expireSeconds expire time, in seconds.
* @param {object} data extra data.
* @returns {object} The token document, or null.
*/
async update(tokenId, tokenType, expireSeconds, data) {
let now = new Date();
let res = await coll.findOneAndUpdate({ _id: tokenId, tokenType }, {
$set: Object.assign({
updateAt: now,
expireAt: new Date(now.getTime() + expireSeconds * 1000)
}, data, { tokenType })
});
return res.value;
},
/**
* Delete a token.
* @param {string} tokenId token ID.
* @param {int} tokenType type of the token.
* @returns {boolean} true if deleted, or false.
*/
async delete(tokenId, tokenType) {
let result = await coll.delete_one({ _id: tokenId, tokenType });
return !!result.deletedCount;
},
init: () => Promise.all([
coll.createIndex([{ uid: 1 }, { tokenType: 1 }, { updateAt: -1 }], { sparse: true }),
coll.createIndex('expireAt', { expireAfterSeconds: 0 })
])
};
+78
View File
@@ -0,0 +1,78 @@
const
domain = require('./domain'),
validator = require('../lib/validator'),
{ ValidationError, TrainingNotFoundError } = require('../error'),
db = require('../service/db.js'),
coll = db.collection('traning'),
coll_status = db.collection('training.status');
module.exports = {
SETTING_DIFFICULTY_ALGORITHM: 0,
SETTING_DIFFICULTY_ADMIN: 1,
SETTING_DIFFICULTY_AVERAGE: 2,
async add(domainId, title, content, owner_uid, dag = [], desc = '') {
validator.check_title(title);
validator.check_intro(content);
validator.check_description(desc);
for (let node of dag)
for (let nid of node['require_nids'])
if (nid >= node._id)
throw new ValidationError('dag');
return await coll.insertOne({
domainId, content, owner_uid,
dag, title, desc, enroll: 0
});
},
count: (domainId, query) => coll.find(Object.assign({ domainId }, query)).count(),
async edit(domainId, 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);
if ($set.dag)
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);
return tdoc;
},
async get(domainId, tid) {
let tdoc = await coll.findOne({ domainId, tid });
if (!tdoc) throw new TrainingNotFoundError(domainId, 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 })
};
/*
async def get_dict_status(domainId, uid, tids, *, fields=None):
result = dict()
async for tsdoc in get_multi_status(domainId=domainId,
uid=uid,
doc_id={'$in': list(set(tids))},
fields=fields):
result[tsdoc['doc_id']] = tsdoc
return result
async def get_dict(domainId, tids, *, fields=None):
result = dict()
async for tdoc in get_multi(domainId=domainId,
doc_id={'$in': list(set(tids))},
fields=fields):
result[tdoc['doc_id']] = tdoc
return result
*/
+137
View File
@@ -0,0 +1,137 @@
const
{ UserNotFoundError, UserAlreadyExistError, DomainNotFoundError,
UserAlreadyDomainMemberError } = 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');
class USER {
constructor(user, domainUser = {}) {
if (!user) throw new UserNotFoundError();
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;
}
hasPerm(perm) {
console.log('PERM', perm, this.perm);
return this.perm == '-' || (this.perm || '').includes(perm);
}
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 getByUname(uname) {
let unameLower = uname.trim().toLowerCase();
let udoc = await coll.findOne({ unameLower });
return new USER(udoc);
}
async function getByEmail(email) {
let emailLower = email.trim().toLowerCase();
let udoc = await coll.findOne({ emailLower });
return new USER(udoc);
}
async function setPassword(uid, password) {
validator.checkPassword(password);
let salt = pwhash.salt();
return await coll.findOneAndUpdate({ _id: uid }, {
$set: { salt, hash: pwhash.hash(password, salt) }
});
}
async function setEmail(uid, email) {
validator.checkEmail(email);
return await setById(uid, { email, emailLower: email.trim().toLowerCase() });
}
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);
udoc.checkPassword(currentPassword);
let salt = pwhash.salt();
return await coll.findOneAndUpdate({
_id: udoc._id
}, {
$set: { salt, hash: pwhash.hash(newPassword, salt) }
});
}
async function create({ uid, email, uname, password, regip, perm = this.perm.PERM_DEFAULT || 0 }) {
validator.checkUname(uname);
validator.checkPassword(password);
validator.checkEmail(email);
let salt = pwhash.salt();
if (!uid) uid = system.incUserCounter();
try {
await coll.insertOne({
email,
emailLower: email.strip().toLowerCase(),
uname,
unameLower: uname.strip().toLowerCase(),
password: pwhash.hash(password, salt),
salt,
regat: new Date(),
regip,
loginat: new Date(),
loginip: regip,
perm,
gravatar: email
});
} catch (e) {
throw new UserAlreadyExistError(uid, uname, email);
}
}
function getMany(params) {
return coll.find(params);
}
module.exports = {
changePassword, create, getByEmail,
getById, getByUname, getMany,
setById, setEmail,
setPassword, setPriv
};