From a5886ecb99f964b5b4845a490dc48c541133e946 Mon Sep 17 00:00:00 2001 From: undefined Date: Thu, 6 Aug 2020 14:24:16 +0800 Subject: [PATCH] update --- .eslintrc.js | 1 + .github/workflows/build.yml | 1 + .gitignore | 7 +- build/start.ts | 39 ++ package.json | 4 + packages/geoip/README.md | 5 + packages/geoip/lib.ts | 26 ++ packages/geoip/locale/zh_CN.yaml | 1 + packages/geoip/locale/zh_TW.yaml | 1 + packages/geoip/package.json | 14 + packages/geoip/prepublish.sh | 4 + packages/hydrooj/bin/hydrooj.js | 56 +++ packages/hydrooj/package.json | 8 +- packages/hydrooj/src/entry/master.ts | 5 +- packages/hydrooj/src/handler/contest.ts | 4 +- packages/hydrooj/src/handler/homework.ts | 2 +- packages/hydrooj/src/handler/user.ts | 5 +- packages/hydrooj/src/interface.ts | 120 +++--- packages/hydrooj/src/lib/superagent.ts | 52 +++ packages/hydrooj/src/model/blacklist.ts | 7 +- packages/hydrooj/src/model/file.ts | 31 +- packages/hydrooj/src/model/record.ts | 4 +- packages/hydrooj/src/model/token.ts | 5 +- packages/hydrooj/src/model/user.ts | 12 +- packages/hydrooj/src/utils.ts | 48 --- packages/hydrooj/tsconfig.json | 4 + packages/login-with-github/lib.ts | 5 +- packages/login-with-github/package.json | 8 +- packages/login-with-google/lib.ts | 5 +- packages/login-with-google/package.json | 8 +- packages/migrate-vijos/README.md | 17 + packages/migrate-vijos/lib.ts | 17 + packages/migrate-vijos/package.json | 24 ++ packages/migrate-vijos/script.ts | 524 +++++++++++++++++++++++ packages/migrate-vijos/tsconfig.json | 11 + tsconfig.build.json | 13 + tsconfig.json | 4 +- 37 files changed, 940 insertions(+), 162 deletions(-) create mode 100644 build/start.ts create mode 100644 packages/geoip/README.md create mode 100644 packages/geoip/lib.ts create mode 100644 packages/geoip/locale/zh_CN.yaml create mode 100644 packages/geoip/locale/zh_TW.yaml create mode 100644 packages/geoip/package.json create mode 100644 packages/geoip/prepublish.sh create mode 100755 packages/hydrooj/bin/hydrooj.js create mode 100644 packages/hydrooj/src/lib/superagent.ts create mode 100644 packages/migrate-vijos/README.md create mode 100644 packages/migrate-vijos/lib.ts create mode 100644 packages/migrate-vijos/package.json create mode 100644 packages/migrate-vijos/script.ts create mode 100644 packages/migrate-vijos/tsconfig.json create mode 100644 tsconfig.build.json diff --git a/.eslintrc.js b/.eslintrc.js index c07334458..34cf399a0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { 'guard-for-in': 'off', 'implicit-arrow-linebreak': 'off', 'import/extensions': 'off', + 'import/no-named-as-default-member': 'off', 'import/prefer-default-export': 'off', indent: ['warn', 4], 'max-classes-per-file': 'off', diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 61eea6cdc..007d6ff9e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,7 @@ jobs: build: runs-on: ubuntu-latest env: + LICENSE_KEY: ${{ secrets.MAXMIND_KEY }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} steps: - name: Check out diff --git a/.gitignore b/.gitignore index 3b9fe481c..f65a932fd 100644 --- a/.gitignore +++ b/.gitignore @@ -14,9 +14,12 @@ build/*.js *.js.map *.hydro !.gitkeep -tsconfig.tsbuildinfo +*.tsbuildinfo yarn.lock packages/**/*.js +!**/bin/*.js packages/**/*.js.map -packages/**/*.d.ts \ No newline at end of file +packages/**/*.d.ts + +*.mmdb \ No newline at end of file diff --git a/build/start.ts b/build/start.ts new file mode 100644 index 000000000..0952a3acc --- /dev/null +++ b/build/start.ts @@ -0,0 +1,39 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import os from 'os'; +import path from 'path'; +import cluster from 'cluster'; +import fs from 'fs-extra'; +import { filter } from 'lodash'; +import { argv } from 'yargs'; +import * as hydro from 'hydrooj'; + +if (!cluster.isMaster) { + // Forked by hydro + hydro.load().catch((e) => { + console.error(e); + process.exit(1); + }); +} else { + fs.ensureDirSync(path.resolve(os.homedir(), '.hydro')); + const addonPath = path.resolve(os.homedir(), '.hydro', 'addon.json'); + let addons = filter( + fs.readdirSync(path.resolve(__dirname, '../packages')), + (i) => i !== 'hydrooj', + ).map((i) => `@hydrooj/${i}`); + fs.writeFileSync(addonPath, JSON.stringify(addons, null, 2)); + + try { + const ui = argv.ui as string || '@hydrooj/ui-default'; + require.resolve(ui); + addons.push(ui); + } catch (e) { + console.error('Please also install @hydrooj/ui-default'); + } + + addons = Array.from(new Set(addons)); + for (const addon of addons) hydro.addon(addon); + (argv._[0] === 'cli' ? hydro.loadCli : hydro.load)().catch((e) => { + console.error(e); + process.exit(1); + }); +} diff --git a/package.json b/package.json index 426337a48..9c555ddef 100644 --- a/package.json +++ b/package.json @@ -31,5 +31,9 @@ "semver": "^7.3.2", "ts-jest": "^26.1.4", "typescript": "^3.9.7" + }, + "dependencies": { + "fs-extra": "^9.0.1", + "yargs": "^15.4.1" } } diff --git a/packages/geoip/README.md b/packages/geoip/README.md new file mode 100644 index 000000000..99df0d4e6 --- /dev/null +++ b/packages/geoip/README.md @@ -0,0 +1,5 @@ +# GeoIp + +显示用户登录地。 + +IP geo-location data is provided by [Maxmind](http://www.maxmind.com) diff --git a/packages/geoip/lib.ts b/packages/geoip/lib.ts new file mode 100644 index 000000000..43ede5d77 --- /dev/null +++ b/packages/geoip/lib.ts @@ -0,0 +1,26 @@ +import fs from 'fs'; +import path from 'path'; +import { Reader } from 'maxmind'; + +const buffer = fs.readFileSync(path.resolve(__dirname, 'GeoLite2-City.mmdb')); +const reader = new Reader(buffer); + +function lookup(ip: string, locale: string) { + const res: any = reader.get(ip); + if (!res) return {}; + const ret: any = {}; + if (res.location) ret.location = res.location; + if (res.continent) ret.continent = res.continent.names[locale] || res.continent.names.en; + if (res.country || res.registered_country) { + ret.country = (res.country || res.registered_country).names[locale] + || (res.country || res.registered_country).names.en; + } + if (res.city) ret.city = res.city.names[locale] || res.city.names.en; + ret.display = `${ret.continent} ${ret.country} ${ret.city}`; + return ret; +} + +global.Hydro.lib.geoip = exports = { + provider: 'MaxMind', + lookup, +}; diff --git a/packages/geoip/locale/zh_CN.yaml b/packages/geoip/locale/zh_CN.yaml new file mode 100644 index 000000000..98cad2d36 --- /dev/null +++ b/packages/geoip/locale/zh_CN.yaml @@ -0,0 +1 @@ +geoip_locale: zh-CN \ No newline at end of file diff --git a/packages/geoip/locale/zh_TW.yaml b/packages/geoip/locale/zh_TW.yaml new file mode 100644 index 000000000..98cad2d36 --- /dev/null +++ b/packages/geoip/locale/zh_TW.yaml @@ -0,0 +1 @@ +geoip_locale: zh-CN \ No newline at end of file diff --git a/packages/geoip/package.json b/packages/geoip/package.json new file mode 100644 index 000000000..2dab5c1c7 --- /dev/null +++ b/packages/geoip/package.json @@ -0,0 +1,14 @@ +{ + "name": "@hydrooj/geoip", + "version": "1.1.6", + "main": "package.json", + "repository": "git@github.com:hydro-dev/Hydro.git", + "author": "undefined ", + "license": "MIT", + "dependencies": { + "maxmind": "^4.1.3" + }, + "peerDependencies": { + "hydrooj": "^2.12.0" + } +} \ No newline at end of file diff --git a/packages/geoip/prepublish.sh b/packages/geoip/prepublish.sh new file mode 100644 index 000000000..e32fa0850 --- /dev/null +++ b/packages/geoip/prepublish.sh @@ -0,0 +1,4 @@ +wget -O ./GeoLite2-City.tar.gz "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=${LICENSE_KEY}&suffix=tar.gz" +tar zxvf ./GeoLite2-City.tar.gz -C . +mv ./GeoLite2-City_*/GeoLite2-City.mmdb ./GeoLite2-City.mmdb +rm -r ./GeoLite2-City_* ./GeoLite2-City.tar.gz \ No newline at end of file diff --git a/packages/hydrooj/bin/hydrooj.js b/packages/hydrooj/bin/hydrooj.js new file mode 100755 index 000000000..696233a2b --- /dev/null +++ b/packages/hydrooj/bin/hydrooj.js @@ -0,0 +1,56 @@ +#!/usr/bin/env node + +const os = require('os'); +const path = require('path'); +const cluster = require('cluster'); +const fs = require('fs-extra'); +const { argv } = require('yargs'); +const hydro = require('../dist/loader'); + +if (!cluster.isMaster) { + // Forked by hydro + hydro.load().catch((e) => { + console.error(e); + process.exit(1); + }); +} else { + fs.ensureDirSync(path.resolve(os.homedir(), '.hydro')); + const addonPath = path.resolve(os.homedir(), '.hydro', 'addon.json'); + if (!fs.existsSync(addonPath)) fs.writeFileSync(addonPath, '[]'); + let addons = JSON.parse(fs.readFileSync(addonPath).toString()); + + try { + const ui = argv.ui || '@hydrooj/ui-default'; + require.resolve(ui); + addons.push(ui); + } catch (e) { + console.error('Please also install @hydrooj/ui-default'); + } + + if (argv._[0] && argv._[0] !== 'cli') { + const operation = argv._[0]; + const arg1 = argv._[1]; + const arg2 = argv._[2]; + if (operation === 'addon') { + if (arg1 === 'add') addons.push(arg2); + else if (arg1 === 'remove') { + for (let i = 0; i < addons.length; i++) { + if (addons[i] === arg2) { + addons.splice(i, 1); + break; + } + } + } + addons = Array.from(new Set(addons)); + console.log('Current Addons: ', addons); + fs.writeFileSync(addonPath, JSON.stringify(addons, null, 2)); + } + } else { + addons = Array.from(new Set(addons)); + for (const addon of addons) hydro.addon(addon); + (argv._[0] === 'cli' ? hydro.loadCli : hydro.load)().catch((e) => { + console.error(e); + process.exit(1); + }); + } +} diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 8c84fa049..47e4cb906 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,8 +1,8 @@ { "name": "hydrooj", - "version": "2.12.2", - "main": "dist/loader.js", + "version": "2.12.3", "bin": "bin/hydrooj.js", + "main": "dist/loader.js", "repository": "https://github.com/hydro-dev/Hydro.git", "author": "masnn", "license": "GPL-3.0-only", @@ -40,7 +40,7 @@ "ps-list": "^7.2.0", "serialize-javascript": "^4.0.0", "sockjs": "^0.3.20", - "superagent": "^5.2.2", + "superagent": "^5.3.1", "systeminformation": "^4.26.10", "yargs": "^15.4.1" }, @@ -70,4 +70,4 @@ "build": "tsc", "build:watch": "tsc --watch" } -} +} \ No newline at end of file diff --git a/packages/hydrooj/src/entry/master.ts b/packages/hydrooj/src/entry/master.ts index 18718925c..cb29ebff2 100644 --- a/packages/hydrooj/src/entry/master.ts +++ b/packages/hydrooj/src/entry/master.ts @@ -58,7 +58,10 @@ export async function load(call: Entry) { await ins.run({ username: 'Root', password: 'rootroot' }); } for (const postInit of global.Hydro.postInit) { - await postInit().catch(console.error); + await postInit().catch((e) => { + console.error('Error running postInit:', postInit.toString()); + console.error(e); + }); } return await modelSystem.get('server.worker'); } diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index af4525480..27fd08ee9 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -118,7 +118,7 @@ class ContestBoardcastHandler extends ContestHandler { const tdoc = await contest.get(domainId, tid); if (tdoc.owner !== this.user._id) throw new PermissionError('Boardcast Message'); const tsdocs = await contest.getMultiStatus(domainId, { docId: tid }).toArray(); - const uids = Array.from(new Set(tsdocs.map((tsdoc) => tsdoc.uid))); + const uids: number[] = Array.from(new Set(tsdocs.map((tsdoc) => tsdoc.uid))); await Promise.all( uids.map((uid) => message.send(this.user._id, uid, content, message.FLAG_ALERT)), ); @@ -343,7 +343,7 @@ class ContestCodeHandler extends ContestHandler { }, }).toArray(); for (const rdoc of rdocs) { - zip.addFile(`${rnames[rdoc._id]}.${rdoc.lang}`, rdoc.code); + zip.addFile(`${rnames[rdoc._id.toHexString()]}.${rdoc.lang}`, Buffer.from(rdoc.code)); } await this.binary(zip.toBuffer(), `${tdoc.title}.zip`); } diff --git a/packages/hydrooj/src/handler/homework.ts b/packages/hydrooj/src/handler/homework.ts index a4bb9a6cf..312ec032e 100644 --- a/packages/hydrooj/src/handler/homework.ts +++ b/packages/hydrooj/src/handler/homework.ts @@ -395,7 +395,7 @@ class HomeworkCodeHandler extends HomeworkHandler { }, }).toArray(); for (const rdoc of rdocs) { - zip.addFile(`${rnames[rdoc._id]}.${rdoc.lang}`, rdoc.code); + zip.addFile(`${rnames[rdoc._id.toHexString()]}.${rdoc.lang}`, Buffer.from(rdoc.code)); } await this.binary(zip.toBuffer(), `${tdoc.title}.zip`); } diff --git a/packages/hydrooj/src/handler/user.ts b/packages/hydrooj/src/handler/user.ts index c7d93a541..8d1833507 100644 --- a/packages/hydrooj/src/handler/user.ts +++ b/packages/hydrooj/src/handler/user.ts @@ -18,6 +18,7 @@ import { isEmail, isPassword, isUname } from '../lib/validator'; import { sendMail } from '../lib/mail'; import * as misc from '../lib/misc'; import paginate from '../lib/paginate'; +import { User } from '../interface'; class UserLoginHandler extends Handler { async get() { @@ -201,12 +202,12 @@ class UserSearchHandler extends Handler { @param('exectMatch', Types.Boolean) async get(domainId: string, q: string, exactMatch = false) { let udoc = await user.getById(domainId, parseInt(q, 10)); - const udocs = udoc ? [udoc] : []; + const udocs: User[] = udoc ? [udoc] : []; udoc = await user.getByUname(domainId, q); if (udoc) udocs.push(udoc); udoc = await user.getByEmail(domainId, q); if (udoc) udocs.push(udoc); - if (!exactMatch) udocs.push(...await user.getPrefixList(q, 20)); + if (!exactMatch) udocs.push(...await user.getPrefixList(domainId, q, 20)); for (const i in udocs) { udocs[i].gravatar = misc.gravatar(udocs[i].gravatar || ''); } diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index c7a13bd96..ee30309d3 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -247,9 +247,10 @@ export interface Mdoc { export interface Ufdoc { _id: ObjectID, secret: string, - md5: string, + md5?: string, owner: number, - size: number, + size?: number, + filename: string, } // Blacklist @@ -357,68 +358,75 @@ export interface JudgeResultBody { export type PathComponent = [string, string, any?, boolean?]; +export interface Model { + blacklist: typeof import('./model/blacklist'), + builtin: typeof import('./model/builtin'), + contest: typeof import('./model/contest'), + discussion: typeof import('./model/discussion'), + document: typeof import('./model/document'), + domain: typeof import('./model/domain'), + file: typeof import('./model/file'), + message: typeof import('./model/message'), + opcount: typeof import('./model/opcount'), + problem: typeof import('./model/problem'), + record: typeof import('./model/record'), + setting: typeof import('./model/setting'), + solution: typeof import('./model/solution'), + system: typeof import('./model/system'), + task: typeof import('./model/task'), + token: typeof import('./model/token'), + training: typeof import('./model/training'), + user: typeof import('./model/user'), + [key: string]: any, +} + +export interface Service { + bus: typeof import('./service/bus'), + db: typeof import('./service/db'), + gridfs: GridFSBucket, + monitor: typeof import('./service/monitor'), + server: typeof import('./service/server'), + [key: string]: any, +} + +export interface Lib { + download: typeof import('./lib/download').default, + superagent: typeof import('./lib/superagent'), + 'hash.hydro': typeof import('./lib/hash.hydro').default, + i18n: typeof import('./lib/i18n').default, + 'import.syzoj': typeof import('./lib/import.syzoj').syzoj, + jwt: typeof import('./lib/jwt'), + logger: typeof import('./lib/logger').default, + mail: typeof import('./lib/mail'), + md5: typeof import('./lib/md5').default, + misc: typeof import('./lib/misc'), + nav: typeof import('./lib/nav').default, + paginate: typeof import('./lib/paginate').default, + rank: typeof import('./lib/rank').default, + rating: typeof import('./lib/rating').default, + testdataConfig: typeof import('./lib/testdataConfig'), + sha1: typeof import('./lib/sha1').default, + sysinfo: typeof import('./lib/sysinfo'), + 'testdata.convert.ini': typeof import('./lib/testdata.convert.ini').default, + useragent: typeof import('./lib/useragent'), + validator: typeof import('./lib/validator'), + [key: string]: any +} + declare global { namespace NodeJS { interface Global { Hydro: { - model: { - blacklist: typeof import('./model/blacklist'), - builtin: typeof import('./model/builtin'), - contest: typeof import('./model/contest'), - discussion: typeof import('./model/discussion'), - document: typeof import('./model/document'), - domain: typeof import('./model/domain'), - file: typeof import('./model/file'), - message: typeof import('./model/message'), - opcount: typeof import('./model/opcount'), - problem: typeof import('./model/problem'), - record: typeof import('./model/record'), - setting: typeof import('./model/setting'), - solution: typeof import('./model/solution'), - system: typeof import('./model/system'), - task: typeof import('./model/task'), - token: typeof import('./model/token'), - training: typeof import('./model/training'), - user: typeof import('./model/user'), - [key: string]: any, - }, - handler: { [key: string]: Function }, - script: { [key: string]: Script }, - service: { - bus: typeof import('./service/bus'), - db: typeof import('./service/db'), - gridfs: GridFSBucket, - monitor: typeof import('./service/monitor'), - server: typeof import('./service/server'), - [key: string]: any, - }, - lib: { - download: typeof import('./lib/download').default, - 'hash.hydro': typeof import('./lib/hash.hydro').default, - i18n: typeof import('./lib/i18n').default, - 'import.syzoj': typeof import('./lib/import.syzoj').syzoj, - jwt: typeof import('./lib/jwt'), - logger: typeof import('./lib/logger').default, - mail: typeof import('./lib/mail'), - md5: typeof import('./lib/md5').default, - misc: typeof import('./lib/misc'), - nav: typeof import('./lib/nav').default, - paginate: typeof import('./lib/paginate').default, - rank: typeof import('./lib/rank').default, - rating: typeof import('./lib/rating').default, - testdataConfig: typeof import('./lib/testdataConfig'), - sha1: typeof import('./lib/sha1').default, - sysinfo: typeof import('./lib/sysinfo'), - 'testdata.convert.ini': typeof import('./lib/testdata.convert.ini').default, - useragent: typeof import('./lib/useragent'), - validator: typeof import('./lib/validator'), - [key: string]: any - }, + model: Model, + handler: Dict, + script: Dict