diff --git a/packages/hydrooj/locales/en.yaml b/packages/hydrooj/locales/en.yaml index b28e8eed5..7b5d4bde8 100644 --- a/packages/hydrooj/locales/en.yaml +++ b/packages/hydrooj/locales/en.yaml @@ -1,3 +1,4 @@ +__id: en __langname: English Can be Extended For: Extension contest_create: Contest Create diff --git a/packages/hydrooj/locales/ko.yaml b/packages/hydrooj/locales/ko.yaml index b8a26bf22..2c6c55ec4 100644 --- a/packages/hydrooj/locales/ko.yaml +++ b/packages/hydrooj/locales/ko.yaml @@ -1,3 +1,4 @@ +__id: ko __langname: 한국어 '"{data}" copied to clipboard!': “{data}”복사 완료 '{} is a moderator of this domain.': '{}가 그룹의 관리자입니다.' diff --git a/packages/hydrooj/locales/zh.yaml b/packages/hydrooj/locales/zh.yaml index 0ddccd525..7646b2f71 100644 --- a/packages/hydrooj/locales/zh.yaml +++ b/packages/hydrooj/locales/zh.yaml @@ -1,3 +1,4 @@ +__id: zh __langname: 简体中文 '"{data}" copied to clipboard!': “{data}”已复制到剪贴板! '{} is a moderator of this domain.': '{}是这个域的管理者之一。' diff --git a/packages/hydrooj/locales/zh_TW.yaml b/packages/hydrooj/locales/zh_TW.yaml index 3902ec0e6..0dce065fb 100644 --- a/packages/hydrooj/locales/zh_TW.yaml +++ b/packages/hydrooj/locales/zh_TW.yaml @@ -1,3 +1,4 @@ +__id: zh_TW __langname: 正體中文 '{0} comments': '{0} 條評論' '{0} discussions': '{0} 條討論' diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index f97ca5b05..3dc876eb9 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.15.17", + "version": "2.15.18", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", diff --git a/packages/hydrooj/src/entry/master.ts b/packages/hydrooj/src/entry/master.ts index cec349439..0afb9a8d8 100644 --- a/packages/hydrooj/src/entry/master.ts +++ b/packages/hydrooj/src/entry/master.ts @@ -69,9 +69,14 @@ export async function load(call: Entry) { for (const i of builtinModel) require(`../model/${i}`); const scripts = require('../upgrade'); let dbVer = (await modelSystem.get('db.ver')) ?? 0; + const isFresh = !dbVer; const expected = scripts.length; while (dbVer < expected) { logger.info('Upgrading database: from %d to %d', dbVer, expected); + if (isFresh) { + const func = scripts[dbVer].toString(); + if (func.includes('_FRESH_INSTALL_IGNORE')) continue; + } const result = await scripts[dbVer](); if (!result) break; dbVer++; diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 1c182f225..9e7c910ab 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -329,6 +329,7 @@ export interface DomainDoc extends Dictionary { bulletin: string, pidCounter: number, join?: any, + hostname?: string, } // Message diff --git a/packages/hydrooj/src/lib/i18n.ts b/packages/hydrooj/src/lib/i18n.ts index 40558fa3b..e0053137d 100644 --- a/packages/hydrooj/src/lib/i18n.ts +++ b/packages/hydrooj/src/lib/i18n.ts @@ -46,12 +46,8 @@ String.prototype.translate = function translate(...languages: string[]) { languages.push(global.Hydro.model.system.get('server.language')); for (const language of languages) { if (!language) continue; - if (locales[language]) { - if (locales[language][this] !== undefined) return locales[language][this]; - } - if (locales[language.split('_')[0]]) { - if (locales[language.split('_')[0]][this] !== undefined) return locales[language.split('_')[0]][this]; - } + const curr = (locales[language] || {})[this] || (locales[language.split('_')[0]] || {})[this]; + if (curr) return curr; } return this; }; diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index ba4adab03..703c37e80 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -137,6 +137,14 @@ export function count(domainId: string, query: FilterQuery) { return document.count(domainId, document.TYPE_PROBLEM, query); } +export function del(domainId: string, docId: number) { + return Promise.all([ + document.deleteOne(domainId, document.TYPE_PROBLEM, docId), + document.deleteMultiStatus(domainId, document.TYPE_PROBLEM, { docId }), + bus.parallel('problem/delete', domainId, docId), + ]); +} + export async function addTestdata(domainId: string, pid: number, name: string, f: Readable | Buffer | string) { await storage.put(`problem/${domainId}/${pid}/testdata/${name}`, f); const meta = await storage.getMeta(`problem/${domainId}/${pid}/testdata/${name}`); @@ -208,11 +216,6 @@ export function setStar(domainId: string, pid: number, uid: number, star: boolea return document.setStatus(domainId, document.TYPE_PROBLEM, pid, uid, { star }); } -bus.on('problem/delete', (domainId, docId) => Promise.all([ - document.deleteOne(domainId, document.TYPE_PROBLEM, docId), - document.deleteMultiStatus(domainId, document.TYPE_PROBLEM, { docId }), -])); - global.Hydro.model.problem = { Pdoc, @@ -227,6 +230,7 @@ global.Hydro.model.problem = { add, inc, get, + del, edit, count, push, diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index ce8069393..7b3234a90 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -194,69 +194,17 @@ function _descriptor(v: ParamOption) { }; } -export function get( - name: string, type: Type, validate: null, convert: Converter -): MethodDecorator; -export function get( - name: string, type: Type, validate?: Validator, convert?: Converter, -): MethodDecorator; -export function get( - name: string, type?: Type, isOptional?: boolean, validate?: Validator, convert?: Converter, -): MethodDecorator; -export function get( - name: string, ...args: Array -): MethodDecorator; -export function get(name: string, ...args: any): MethodDecorator { - return _descriptor(_buildParam(name, 'get', ...args)); -} +type DescriptorBuilder = + ((name: string, type: Type) => MethodDecorator) + & ((name: string, type: Type, validate: null, convert: Converter) => MethodDecorator) + & ((name: string, type: Type, validate?: Validator, convert?: Converter) => MethodDecorator) + & ((name: string, type?: Type, isOptional?: boolean, validate?: Validator, convert?: Converter) => MethodDecorator) + & ((name: string, ...args: Array) => MethodDecorator) -export function post( - name: string, type: Type, validate: null, convert: Converter -): MethodDecorator; -export function post( - name: string, type?: Type, validate?: Validator, convert?: Converter, -): MethodDecorator; -export function post( - name: string, type?: Type, isOptional?: boolean, validate?: Validator, convert?: Converter, -): MethodDecorator; -export function post( - name: string, ...args: Array -): MethodDecorator; -export function post(name: string, ...args: any): MethodDecorator { - return _descriptor(_buildParam(name, 'post', ...args)); -} - -export function route( - name: string, type: Type, validate: null, convert: Converter -): MethodDecorator; -export function route( - name: string, type?: Type, validate?: Validator, convert?: Converter, -): MethodDecorator; -export function route( - name: string, type?: Type, isOptional?: boolean, validate?: Validator, convert?: Converter, -): MethodDecorator; -export function route( - name: string, ...args: Array -): MethodDecorator; -export function route(name: string, ...args: any): MethodDecorator { - return _descriptor(_buildParam(name, 'route', ...args)); -} - -export function param( - name: string, type: Type, validate: null, convert: Converter -): MethodDecorator; -export function param( - name: string, type?: Type, validate?: Validator, convert?: Converter, -): MethodDecorator; -export function param( - name: string, type?: Type, isOptional?: boolean, validate?: Validator, convert?: Converter, -): MethodDecorator; -export function param( - name: string, ...args: Array -): MethodDecorator; -export function param(name: string, ...args: any): MethodDecorator { - return _descriptor(_buildParam(name, 'all', ...args)); -} +export const get: DescriptorBuilder = (name, ...args) => _descriptor(_buildParam(name, 'get', ...args)); +export const post: DescriptorBuilder = (name, ...args) => _descriptor(_buildParam(name, 'post', ...args)); +export const route: DescriptorBuilder = (name, ...args) => _descriptor(_buildParam(name, 'route', ...args)); +export const param: DescriptorBuilder = (name, ...args) => _descriptor(_buildParam(name, 'all', ...args)); export function requireCsrfToken(target: any, funcName: string, obj: any) { const originalMethod = obj.value; @@ -323,6 +271,24 @@ export class HandlerCommon { if (!this.user.hasPriv(...args)) throw new PrivilegeError(...args); } + async renderHTML(name: string, context: any): Promise { + const UserContext: any = { + ...this.user, + gravatar: misc.gravatar(this.user.gravatar || '', 128), + perm: this.user.perm.toString(), + }; + UserContext.viewLang = this.translate('__id'); + if (!global.Hydro.lib.template) return JSON.stringify(context); + const res = await global.Hydro.lib.template.render(name, { + handler: this, + UserContext, + url: this.url.bind(this), + _: this.translate.bind(this), + ...context, + }); + return res; + } + url(name: string, kwargs: any = {}) { let res = '#'; const args: any = {}; @@ -395,9 +361,7 @@ export class Handler extends HandlerCommon { ip: ctx.request.ip, headers: ctx.request.headers, cookies: ctx.cookies, - // @ts-ignore body: ctx.request.body, - // @ts-ignore files: ctx.request.files, query: ctx.query, path: ctx.path, @@ -428,6 +392,8 @@ export class Handler extends HandlerCommon { url_prefix: '/', }; this.session = {}; + const xff = system.get('server.xff'); + if (xff) this.request.ip = this.request.headers[xff.toLowerCase()] || this.request.ip; } @lrucache @@ -436,23 +402,6 @@ export class Handler extends HandlerCommon { return hash('csrf_token', id); } - async renderHTML(name: string, context: any): Promise { - const UserContext = { - ...this.user, - gravatar: misc.gravatar(this.user.gravatar || '', 128), - perm: this.user.perm.toString(), - }; - if (!global.Hydro.lib.template) return JSON.stringify(context); - const res = await global.Hydro.lib.template.render(name, { - handler: this, - UserContext, - url: this.url.bind(this), - _: this.translate.bind(this), - ...context, - }); - return res; - } - async render(name: string, context: any) { this.response.body = await this.renderHTML(name, context); this.response.type = 'text/html'; @@ -482,10 +431,9 @@ export class Handler extends HandlerCommon { } async init({ domainId }) { - const xff = system.get('server.xff') as string; - if (xff) this.request.ip = this.request.headers[xff.toLowerCase()]; [this.domain] = await Promise.all([ domain.get(domainId), + this.limitRate('global', 10, 100), this.getSession(), this.getBdoc(), ]); @@ -733,15 +681,6 @@ export class ConnectionHandler extends HandlerCommon { for (const i in p) this.request.params[p[i][0]] = decodeURIComponent(p[i][1]); } - async renderHTML(name: string, context: any): Promise { - const res = await global.Hydro.lib.template.render(name, Object.assign(context, { - handler: this, - url: this.url.bind(this), - _: this.translate.bind(this), - })); - return res; - } - send(data: any) { this.conn.write(JSON.stringify(data)); } @@ -782,10 +721,13 @@ export class ConnectionHandler extends HandlerCommon { domain.get(domainId), this.getSession(cookie), ]); - const bdoc = await blacklist.get(this.request.ip); + const [bdoc, udoc] = await Promise.all([ + blacklist.get(this.request.ip), + user.getById(domainId, this.session.uid, this.session.scope), + ]); if (bdoc) throw new BlacklistedError(this.request.ip); - this.user = await user.getById(domainId, this.session.uid, this.session.scope); - if (!this.user) throw new UserNotFoundError(this.session.uid); + if (!udoc) throw new UserNotFoundError(this.session.uid); + this.user = udoc; if (this.user._id === 0 && this.session.viewLang) this.user.viewLang = this.session.viewLang; } } @@ -844,17 +786,6 @@ export function start() { logger.debug(`${ctx.request.method} ${ctx.request.path} ${ctx.response.status} ${endTime - startTime}ms ${ctx.response.length}`); }); } - app.use(async (ctx, next) => { - const xff = system.get('server.xff'); - const ip = xff ? ctx.request.headers[xff] : ctx.request.ip; - try { - await opcount.inc('global', ip, 10, 100); - } catch (e) { - ctx.status = 429; - return null; - } - return await next(); - }); app.use(router.routes()).use(router.allowedMethods()); server.listen(argv.port || port); logger.success('Server listening at: %d', argv.port || port); diff --git a/packages/hydrooj/src/ui.ts b/packages/hydrooj/src/ui.ts index 1e92f7adb..93d15e3a4 100644 --- a/packages/hydrooj/src/ui.ts +++ b/packages/hydrooj/src/ui.ts @@ -13,7 +13,7 @@ declare module 'terminal-kit/Terminal' { } } -const useTerminal = cluster.isMaster && process.stdout.isTTY && !argv.legacy; +const useTerminal = cluster.isMaster && process.stdout.isTTY && !argv.legacy && !argv._.length; export namespace Progress { export class Progress { diff --git a/packages/hydrooj/src/upgrade.ts b/packages/hydrooj/src/upgrade.ts index 1dc3fcc87..19c431f5f 100644 --- a/packages/hydrooj/src/upgrade.ts +++ b/packages/hydrooj/src/upgrade.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable no-await-in-loop */ import { ObjectID, Collection } from 'mongodb'; import AdmZip from 'adm-zip'; @@ -28,6 +29,7 @@ const scripts: UpgradeScript[] = [ }, // Add history column to ddoc,drdoc,psdoc async function _1_2() { + const _FRESH_INSTALL_IGNORE = 1; const domains = await domain.getMulti().project({ _id: 1 }).toArray(); for (const d of domains) { const bulk = document.coll.initializeUnorderedBulkOp(); @@ -46,10 +48,12 @@ const scripts: UpgradeScript[] = [ return true; }, async function _2_3() { + const _FRESH_INSTALL_IGNORE = 1; await db.collection('document').updateMany({ pid: /^\d+$/i }, { $unset: { pid: '' } }); return true; }, async function _3_4() { + const _FRESH_INSTALL_IGNORE = 1; if (storage.error) { logger.error('Cannot upgrade. Please change storage config.'); return false; @@ -133,6 +137,7 @@ const scripts: UpgradeScript[] = [ return true; }, async function _4_5() { + const _FRESH_INSTALL_IGNORE = 1; const domains = await domain.getMulti().project({ _id: 1 }).toArray(); for (const d of domains) { const bulk = document.coll.initializeUnorderedBulkOp(); @@ -147,6 +152,7 @@ const scripts: UpgradeScript[] = [ }, async function _5_6() { // Issue #58 + const _FRESH_INSTALL_IGNORE = 1; await domain.edit('system', { owner: 1 }); }, ];