diff --git a/hydro/error.js b/hydro/error.js index aec077d0d..de039ee0e 100644 --- a/hydro/error.js +++ b/hydro/error.js @@ -21,7 +21,7 @@ class ForbiddenError extends UserFacingError { } class NotFoundError extends UserFacingError { constructor(type) { - super(type); + super(type || 'NotFoundError'); this.code = 404; } } @@ -107,7 +107,7 @@ class ContestNotFoundError extends NotFoundError { module.exports = { BadRequestError, ForbiddenError, NotFoundError, LoginError, UserAlreadyExistError, InvalidTokenError, - UserNotFoundError, VerifyPasswordError, + UserNotFoundError, VerifyPasswordError, OpcountExceededError, PermissionError, NoProblemError, ValidationError, ProblemNotFoundError, TrainingNotFoundError, ContestNotFoundError, RecordNotFoundError diff --git a/hydro/handler/base.js b/hydro/handler/base.js index d36a2d603..47a9192ea 100644 --- a/hydro/handler/base.js +++ b/hydro/handler/base.js @@ -44,6 +44,7 @@ MIDDLEWARE(async (ctx, next) => { } } }; + ctx.csrf_token = await token.add(token.TYPE_CSRF_TOKEN, 600, ctx.path); console.log(ctx.session.uid); await next(); if (ctx.session.sid) diff --git a/hydro/handler/problem.js b/hydro/handler/problem.js index 56803a995..744593420 100644 --- a/hydro/handler/problem.js +++ b/hydro/handler/problem.js @@ -41,9 +41,18 @@ GET('/p/:pid', requirePerm(PERM_VIEW_PROBLEM), async ctx => { ctx.templateName = 'problem_detail.html'; ctx.body = { pdoc, udoc, title: pdoc.title }; }); +GET('/p/:pid/submit', requirePerm(PERM_SUBMIT_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.templateName = 'problem_submit.html'; + 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, + uid: ctx.state.user._id, lang: ctx.request.body.language, code: ctx.request.body.code, pid: ctx.params.pid diff --git a/hydro/handler/record.js b/hydro/handler/record.js index 9b952d35a..e88b8c9e3 100644 --- a/hydro/handler/record.js +++ b/hydro/handler/record.js @@ -3,19 +3,28 @@ const { constants } = require('../options'), { GET } = require('../service/server'), record = require('../model/record'), + user = require('../model/user'), + problem = require('../model/problem'), { PERM_READ_RECORD_CODE, PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD } = require('../permission'); GET('/r', async ctx => { let q = {}, page = ctx.query.page || 1; let rdocs = await record.getMany(q, { rid: 1 }, page, constants.RECORD_PER_PAGE); - ctx.body = { page, rdocs }; + let pdict, udict; + for (let rdoc in rdocs) { + udict[rdoc.uid] = await user.getById(rdoc.uid); + pdict[rdoc.pid] = await problem.get({ pid: rdoc.pid, uid: ctx.state.user._id }); + } + ctx.templateName = 'record_main.html'; + ctx.body = { page, rdocs, pdict, udict }; }); GET('/r/:rid', async ctx => { let uid = ctx.state.user._id, rid = new bson.ObjectID(ctx.params.rid); let rdoc = await record.get(rid); if (rdoc.hidden) ctx.checkPerm(PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD); - if (rdoc.creator != uid && !ctx.state.user.hasPerm(PERM_READ_RECORD_CODE)) rdoc.code = null; + if (rdoc.uid != uid && !ctx.state.user.hasPerm(PERM_READ_RECORD_CODE)) rdoc.code = null; + ctx.templateName = 'record_detail.html'; ctx.body = { rdoc }; }); diff --git a/hydro/handler/ui.js b/hydro/handler/ui.js index 7858a78b0..b61e204bb 100644 --- a/hydro/handler/ui.js +++ b/hydro/handler/ui.js @@ -70,7 +70,8 @@ MIDDLEWARE(async (ctx, next) => { ctx.render = async (name, context) => { ctx.user = ctx.state.user; ctx.translate = str => { - return str.translate(ctx.state.user.language); + if (!str) return ''; + return str.toString().translate(ctx.state.user.language); }; ctx.has_perm = perm => ctx.state.user.hasPerm(perm); ctx.body = await new Promise((resolve, reject) => { @@ -87,15 +88,18 @@ MIDDLEWARE(async (ctx, next) => { try { try { await next(); - if (!ctx.body) throw new NotFoundError(); - if (ctx.query.template || ctx.templateName) { - Object.assign(ctx.body, JSON.parse(ctx.query.data || '{}')); - await ctx.render(ctx.query.template || ctx.templateName, ctx.body); - } else if (ctx.setRedirect) { - ctx.response.type = 'application/octet-stream'; - ctx.redirect(ctx.setRedirect); - } + if (ctx.body || ctx.templateName) { + if (ctx.query.template || ctx.templateName) { + ctx.body = ctx.body || {}; + Object.assign(ctx.body, JSON.parse(ctx.query.data || '{}')); + await ctx.render(ctx.query.template || ctx.templateName, ctx.body); + } else if (ctx.setRedirect) { + ctx.response.type = 'application/octet-stream'; + ctx.redirect(ctx.setRedirect); + } + } else throw new NotFoundError(); } catch (error) { + console.log(error); if (error.toString().startsWith('Template render error')) throw error; await ctx.render('error.html', { error }); } diff --git a/hydro/handler/user.js b/hydro/handler/user.js index 7405dedf5..63396967b 100644 --- a/hydro/handler/user.js +++ b/hydro/handler/user.js @@ -1,5 +1,5 @@ const - { GET, POST, CONTEXT } = require('../service/server.js'), + { GET, POST } = require('../service/server.js'), user = require('../model/user'), token = require('../model/token'), system = require('../model/system'), @@ -30,7 +30,8 @@ POST('/login', async ctx => { ctx.session.uid = udoc._id; ctx.session.rememberme = rememberme; ctx.body = {}; - ctx.setRedirect = ctx.request.headers.referer || '/'; + let referer = ctx.request.headers.referer || '/'; + ctx.setRedirect = referer.endsWith('/login') ? '/' : referer; }); POST('/logout', requirePerm(PERM_LOGGEDIN), async ctx => { ctx.session = { uid: 1 }; diff --git a/hydro/model/builtin.js b/hydro/model/builtin.js index 1795ce2ae..d6f6b8828 100644 --- a/hydro/model/builtin.js +++ b/hydro/model/builtin.js @@ -177,6 +177,40 @@ const FOOTER_EXTRA_HTMLS = []; const VIEW_LANGS = [ { code: 'zh-CN', name: 'zh-CN' } ]; +const LANGS = [ + { id: 'cc', name: 'cpp' } +]; +const LANG_TEXTS = { + c: 'C', + cc: 'C++', + cs: 'C#', + pas: 'Pascal', + java: 'Java', + py: 'Python', + py3: 'Python 3', + php: 'PHP', + rs: 'Rust', + hs: 'Haskell', + js: 'JavaScript', + go: 'Go', + rb: 'Ruby', +}; +const LANG_HIGHLIGHT_ID = { + c: 'c', + cc: 'cpp', + cs: 'csharp', + pas: 'pascal', + java: 'java', + py: 'python', + py3: 'python', + php: 'php', + rs: 'rust', + hs: 'haskell', + js: 'javascript', + go: 'go', + rb: 'ruby', +}; module.exports = { - BUILTIN_USERS, BUILTIN_ROLES, CATEGORIES, VIEW_LANGS, FOOTER_EXTRA_HTMLS + BUILTIN_USERS, BUILTIN_ROLES, CATEGORIES, VIEW_LANGS, FOOTER_EXTRA_HTMLS, LANGS, + LANG_TEXTS, LANG_HIGHLIGHT_ID }; \ No newline at end of file diff --git a/hydro/model/token.js b/hydro/model/token.js index 8718e0eb8..f6939c152 100644 --- a/hydro/model/token.js +++ b/hydro/model/token.js @@ -4,6 +4,7 @@ const module.exports = { TYPE_SESSION: 0, + TYPE_CSRF_TOKEN: 1, TYPE_REGISTER: 2, /** * Add a token. diff --git a/templates/components/form.html b/templates/components/form.html index deefd9739..468e05c07 100644 --- a/templates/components/form.html +++ b/templates/components/form.html @@ -1,12 +1,12 @@ {% macro form_begin(args) %} -{% set label = args.pop('label') %} -{% set columns = args.pop('columns') %} -{% set last_column = args.pop('last_column', true) %} -{% set required = args.pop('required', false) %} -{% set hotkeys = args.pop('hotkeys', none) %} -{% set row = args.pop('row', true) %} -{% set no_label = args.pop('no_label', false) %} -{% set label_wrap = args.pop('label_wrap', true) %} +{% set label = args.label %} +{% set columns = args.columns %} +{% set last_column = args.last_column or true %} +{% set required = args.required or false %} +{% set hotkeys = args.hotkeys or none %} +{% set row = args.row or true %} +{% set no_label = args.no_label or false %} +{% set label_wrap = args.label_wrap or true %} {% if row %}
{% endif %} @@ -27,10 +27,10 @@ {% endmacro %} {% macro form_end(args) %} -{% set help_text = args.pop('help_text', none) %} -{% set row = args.pop('row', true) %} -{% set no_label = args.pop('no_label', false) %} -{% set label_wrap = args.pop('label_wrap', true) %} +{% set help_text = args.help_text or none %} +{% set row = args.row or true %} +{% set no_label = args.no_label or false %} +{% set label_wrap = args.label_wrap or true %} {% if not no_label and label_wrap %} {% endif %} @@ -44,11 +44,11 @@ {% endmacro %} {% macro form_attr(form_class, args) %} -{% set disabled = args.pop('disabled', false) %} -{% set required = args.pop('required', false) %} -{% set autofocus = args.pop('autofocus', false) %} -{% set date = args.pop('date', false) %} -{% set time = args.pop('time', false) %} +{% set disabled = args.disabled or false %} +{% set required = args.required or false %} +{% set autofocus = args.autofocus or false %} +{% set date = args.date or false %} +{% set time = args.time or false %} class="{{ form_class }}{% if extra_class %} {{ extra_class }}{% endif %}" {% if disabled %}disabled{% endif %} {% if required %}required{% endif %} @@ -58,22 +58,22 @@ {% endmacro %} {% macro container_attr(container_class, args) %} -{% set extra_class = args.pop('extra_class', none) %} +{% set extra_class = args.extra_class or none %} class="{{ container_class }}{% if extra_class %} {{ extra_class }}{% endif %}" {% endmacro %} -{% macro select(options, name='', value='', args) %} +{% macro select(args) %}
@@ -86,20 +86,20 @@ {{ form_end({columns:columns}|assign(args)) }} {% endmacro %} -{% macro radio(options, name='', value='', args) %} +{% macro radio(args) %}
-{% for k, v in options %} +{% for item in options %} {% endfor %}
@@ -111,22 +111,22 @@ {{ form_end({columns:columns, label_warp:false}|assign(args)) }} {% endmacro %} -{% macro image_radio(options, name='', value='', image_class='', args) %} +{% macro image_radio(args) %}
-{% for k, v in options %} +{% for item in options %}