模块化,添加webpack支持

This commit is contained in:
undefined committed 2020-04-29 19:08:03 +08:00
1 parent 4ec2a46877
commit 75ebc1a746
30 files changed
+622 -459

No files matched your search

+22 -8
View File
@@ -267,11 +267,25 @@ class ContestCreateHandler extends ContestHandler {
}
}
Route('/c', ContestListHandler);
Route('/c/:tid', ContestDetailHandler);
Route('/c/:tid/edit', ContestEditHandler);
Route('/c/:tid/scoreboard', ContestScoreboardHandler);
Route('/c/:tid/export/:ext', ContestScoreboardDownloadHandler);
Route('/c/:tid/p/:pid', ContestProblemHandler);
Route('/c/:tid/p/:pid/submit', ContestProblemSubmitHandler);
Route('/contest/create', ContestCreateHandler);
function apply() {
Route('/c', module.exports.ContestListHandler);
Route('/c/:tid', module.exports.ContestDetailHandler);
Route('/c/:tid/edit', module.exports.ContestEditHandler);
Route('/c/:tid/scoreboard', module.exports.ContestScoreboardHandler);
Route('/c/:tid/export/:ext', module.exports.ContestScoreboardDownloadHandler);
Route('/c/:tid/p/:pid', module.exports.ContestProblemHandler);
Route('/c/:tid/p/:pid/submit', module.exports.ContestProblemSubmitHandler);
Route('/contest/create', module.exports.ContestCreateHandler);
}
module.exports = {
ContestListHandler,
ContestDetailHandler,
ContestEditHandler,
ContestScoreboardHandler,
ContestScoreboardDownloadHandler,
ContestProblemHandler,
ContestProblemSubmitHandler,
ContestCreateHandler,
apply,
};
+22 -8
View File
@@ -257,11 +257,25 @@ class DiscussionEditHandler extends DiscussionHandler {
}
}
Route('/discuss', DiscussionMainHandler);
Route('/discuss/:did', DiscussionDetailHandler);
Route('/discuss/:did/edit', DiscussionEditHandler);
Route('/discuss/:did/raw', DiscussionDetailRawHandler);
Route('/discuss/:did/:drid/raw', DiscussionReplyRawHandler);
Route('/discuss/:did/:drid/:drrid/raw', DiscussionTailReplyRawHandler);
Route('/discuss/:type/:docId', DiscussionNodeHandler);
Route('/discuss/:type/:docId/create', DiscussionCreateHandler);
function apply() {
Route('/discuss', module.exports.DiscussionMainHandler);
Route('/discuss/:did', module.exports.DiscussionDetailHandler);
Route('/discuss/:did/edit', module.exports.DiscussionEditHandler);
Route('/discuss/:did/raw', module.exports.DiscussionDetailRawHandler);
Route('/discuss/:did/:drid/raw', module.exports.DiscussionReplyRawHandler);
Route('/discuss/:did/:drid/:drrid/raw', module.exports.DiscussionTailReplyRawHandler);
Route('/discuss/:type/:docId', module.exports.DiscussionNodeHandler);
Route('/discuss/:type/:docId/create', module.exports.DiscussionCreateHandler);
}
module.exports = {
DiscussionMainHandler,
DiscussionDetailHandler,
DiscussionEditHandler,
DiscussionDetailRawHandler,
DiscussionReplyRawHandler,
DiscussionTailReplyRawHandler,
DiscussionNodeHandler,
DiscussionCreateHandler,
apply,
};
+228 -1
View File
@@ -1,8 +1,19 @@
const {
VerifyPasswordError, UserAlreadyExistError, InvalidTokenError,
NotFoundError,
} = require('../error');
const options = require('../options');
const { Route, Handler } = require('../service/server');
const md5 = require('../lib/md5');
const contest = require('../model/contest');
const user = require('../model/user');
const setting = require('../model/setting');
const token = require('../model/token');
const training = require('../model/training');
const { PERM_VIEW_TRAINING, PERM_VIEW_CONTEST, PERM_VIEW_DISCUSSION } = require('../permission');
const {
PERM_VIEW_TRAINING, PERM_VIEW_CONTEST, PERM_VIEW_DISCUSSION,
PERM_LOGGEDIN,
} = require('../permission');
const { CONTESTS_ON_MAIN, TRAININGS_ON_MAIN, DISCUSSIONS_ON_MAIN } = require('../options').constants;
class HomeHandler extends Handler {
@@ -57,4 +68,220 @@ class HomeHandler extends Handler {
}
}
class HomeSecurityHandler extends Handler {
async prepare() {
this.checkPerm(PERM_LOGGEDIN);
}
async get() {
// TODO(iceboy): pagination? or limit session count for uid?
const sessions = await token.getSessionListByUid(this.user._id);
const parsed = sessions.map((session) => ({
...session,
updateUa: useragent.parse(session.updateUa || session.createUa || ''),
updateGeoip: geoip.ip2geo(session.updateIp || session.createIp, this.user.viewLang),
_id: md5(session._id),
isCurrent: session._id === this.session._id,
}));
this.response.template = 'home_security.html';
this.response.body = { sessions: parsed };
}
async postChangePassword({ current, password, verifyPassword }) {
if (password !== verifyPassword) throw new VerifyPasswordError();
await user.changePassword(this.user._id, current, password);
this.back();
}
async postChangeMail({ currentPassword, mail }) {
this.limitRate('send_mail', 3600, 30);
this.user.checkPassword(currentPassword);
const udoc = await user.getByMail(mail);
if (udoc) throw new UserAlreadyExistError(mail);
const [rid] = await token.add(
token.TYPE_CHANGEMAIL,
options.changemail_token_expire_seconds,
{ uid: this.udoc._id, mail },
);
await mail.sendMail(mail, 'Change Email', 'user_changemail_mail.html', {
url: `/changeMail/${rid}`, uname: this.udoc.uname,
});
this.response.template = 'user_changemail_mail_sent.html';
}
async postDeleteToken({ tokenDigest }) {
const sessions = await token.getSessionListByUid(this.user._id);
for (const session in sessions) {
if (tokenDigest === md5(session._id)) {
await token.delete(session._id, token.TYPE_SESSION);
return this.back();
}
}
throw new InvalidTokenError(tokenDigest);
}
async postDeleteAllTokens() {
await token.deleteByUid(this.user._id);
this.back();
}
}
class HomeSettingsHandler extends Handler {
async prepare() {
this.checkPerm(PERM_LOGGEDIN);
}
async get({ category }) {
this.response.template = 'home_settings.html';
if (category === 'preference') {
this.response.body = {
category,
page_name: `home_${category}`,
settings: setting.PREFERENCE_SETTINGS,
};
} else if (category === 'account') {
this.response.body = {
category,
page_name: `home_${category}`,
settings: setting.ACCOUNT_SETTINGS,
};
} else throw new NotFoundError();
}
async post(args) {
// FIXME validation
await user.setById(this.user._id, args);
this.back();
}
}
class UserChangemailWithCodeHandler extends Handler {
async get({ code }) {
const tdoc = await token.get(code, token.TYPE_CHANGEMAIL);
if (!tdoc || tdoc.uid !== this.user._id) {
throw new InvalidTokenError(code);
}
const udoc = await user.getByEmail(tdoc.mail);
if (udoc) throw new UserAlreadyExistError(tdoc.mail);
// TODO(twd2): Ensure mail is unique.
await user.set_mail(this.user._id, tdoc.mail);
await token.delete(code, token.TYPE_CHANGEMAIL);
this.response.body = {};
this.response.redirect = '/home/security';
}
}
Route('/', HomeHandler);
Route('/home/security', HomeSecurityHandler);
Route('/home/changeMail/:code', UserChangemailWithCodeHandler);
Route('/home/settings/:category', HomeSettingsHandler);
/*
@app.route('/home/messages', 'home_messages', global_route=True)
class HomeMessagesHandler(base.OperationHandler):
def modify_udoc(this, udict, key):
udoc = udict.get(key)
if not udoc:
return
gravatar_url = misc.gravatar_url(udoc.get('gravatar'))
if 'gravatar' in udoc and udoc['gravatar']:
udict[key] = {**udoc,
'gravatar_url': gravatar_url,
'gravatar': ''}
@base.require_priv(builtin.PRIV_USER_PROFILE)
async def get(this):
# TODO(iceboy): projection, pagination.
mdocs = await message.get_multi(this.user['_id']).sort([('_id', -1)]).limit(50).to_list()
udict = await user.get_dict(
itertools.chain.from_iterable((mdoc['sender_uid'], mdoc['sendee_uid']) for mdoc in mdocs),
fields=user.PROJECTION_PUBLIC)
# TODO(twd2): improve here:
for mdoc in mdocs:
this.modify_udoc(udict, mdoc['sender_uid'])
this.modify_udoc(udict, mdoc['sendee_uid'])
this.json_or_render('home_messages.html', messages=mdocs, udict=udict)
@base.require_priv(builtin.PRIV_USER_PROFILE)
@base.require_csrf_token
@base.sanitize
async def post_send_message(this, *, uid: int, content: str):
udoc = await user.get_by_uid(uid, user.PROJECTION_PUBLIC)
if not udoc:
raise error.UserNotFoundError(uid)
mdoc = await message.add(this.user['_id'], udoc['_id'], content)
# TODO(twd2): improve here:
# projection
sender_udoc = await user.get_by_uid(this.user['_id'], user.PROJECTION_PUBLIC)
mdoc['sender_udoc'] = sender_udoc
this.modify_udoc(mdoc, 'sender_udoc')
mdoc['sendee_udoc'] = udoc
this.modify_udoc(mdoc, 'sendee_udoc')
if this.user['_id'] != uid:
await bus.publish('message_received-' + str(uid), {'type': 'new', 'data': mdoc})
this.json_or_redirect(this.url, mdoc=mdoc)
@base.require_priv(builtin.PRIV_USER_PROFILE)
@base.require_csrf_token
@base.sanitize
async def post_reply_message(this, *, message_id: objectid.ObjectId, content: str):
mdoc, reply = await message.add_reply(message_id, this.user['_id'], content)
if not mdoc:
return error.MessageNotFoundError(message_id)
if mdoc['sender_uid'] != mdoc['sendee_uid']:
if mdoc['sender_uid'] == this.user['_id']:
other_uid = mdoc['sendee_uid']
else:
other_uid = mdoc['sender_uid']
mdoc['reply'] = [reply]
await bus.publish('message_received-' + str(other_uid), {'type': 'reply', 'data': mdoc})
this.json_or_redirect(this.url, reply=reply)
@base.require_priv(builtin.PRIV_USER_PROFILE)
@base.require_csrf_token
@base.sanitize
async def post_delete_message(this, *, message_id: objectid.ObjectId):
await message.delete(message_id, this.user['_id'])
this.back();
@app.connection_route('/home/messages-conn', 'home_messages-conn', global_route=True)
class HomeMessagesConnection(base.Connection):
@base.require_priv(builtin.PRIV_USER_PROFILE)
async def on_open(this):
await super(HomeMessagesConnection, this).on_open()
bus.subscribe(this.on_message_received, ['message_received-' + str(this.user['_id'])])
async def on_message_received(this, e):
this.send(**e['value'])
async def on_close(this):
bus.unsubscribe(this.on_message_received)
@app.route('/home/file', 'home_file', global_route=True)
class HomeFileHandler(base.OperationHandler):
def file_url(this, fdoc):
return options.cdn_prefix.rstrip('/') + \
this.reverse_url('fs_get', domain_id=builtin.DOMAIN_ID_SYSTEM,
secret=fdoc['metadata']['secret'])
@base.require_priv(builtin.PRIV_USER_PROFILE)
async def get(this):
ufdocs = await userfile.get_multi(owner_uid=this.user['_id']).to_list()
fdict = await fs.get_meta_dict(ufdoc.get('file_id') for ufdoc in ufdocs)
this.render('home_file.html', ufdocs=ufdocs, fdict=fdict)
@base.require_priv(builtin.PRIV_USER_PROFILE)
@base.post_argument
@base.require_csrf_token
@base.sanitize
async def post_delete(this, *, ufid: document.convert_doc_id):
ufdoc = await userfile.get(ufid)
if not this.own(ufdoc, priv=builtin.PRIV_DELETE_FILE_this):
this.check_priv(builtin.PRIV_DELETE_FILE)
result = await userfile.delete(ufdoc['doc_id'])
if result:
await userfile.dec_usage(this.user['_id'], ufdoc['length'])
this.redirect(this.referer_or_main)
*/
+9 -50
View File
@@ -1,57 +1,10 @@
const assert = require('assert');
const download = require('../lib/download');
const problem = require('../model/problem');
const { Route, Handler } = require('../service/server');
const { PERM_CREATE_PROBLEM } = require('../permission');
const axios = require('../lib/axios');
const { ValidationError, RemoteOnlineJudgeError } = require('../error');
const { ValidationError } = require('../error');
class ProblemImportHandler extends Handler {
async syzoj(url) {
const RE_SYZOJ = /https?:\/\/([a-zA-Z0-9.]+)\/problem\/([0-9]+)\/?/i;
assert(url.match(RE_SYZOJ), new ValidationError('url'));
if (!url.endsWith('/')) url += '/';
const [, host, pid] = RE_SYZOJ.exec(url);
const res = await axios.get(`${url}export`);
assert(res.status === 200, new RemoteOnlineJudgeError('Cannot connect to target server'));
assert(res.data.success, new RemoteOnlineJudgeError((res.data.error || {}).message));
const p = res.data.obj;
const content = [
this.translate('problem.import.problem_description'),
p.description,
this.translate('problem.import.input_format'),
p.input_format,
this.translate('problem.import.output_format'),
p.output_format,
this.translate('problem.import.hint'),
p.hint,
this.translate('problem.import.limit_and_hint'),
p.limit_and_hint,
];
if (p.have_additional_file) {
content.push(
this.translate('problem.import.additional_file'),
`${url}download/additional_file`,
);
}
const pdoc = {
title: p.title,
content: content.join(' \n'),
owner: this.user._id,
from: url,
pid: `${host}_${pid}`,
config: {
time: p.time_limit,
memory: p.memory_limit * 1024,
filename: p.file_io_input_name,
type: p.type,
tags: p.tags,
},
};
const r = await download(`${url}testdata/download`);
return [pdoc, r];
}
async prepare() {
this.checkPerm(PERM_CREATE_PROBLEM);
}
@@ -82,4 +35,10 @@ class ProblemImportHandler extends Handler {
}
}
Route('/problem/import', ProblemImportHandler);
async function apply() {
Route('/problem/import', module.exports.ProblemImportHandler);
}
global.Hydro['handler.import'] = module.exports = {
ProblemImportHandler, apply,
};