mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core: add hydrooj install command
This commit is contained in:
1 parent
e92cbeb1ee
commit
bf37fb4848
14 files changed
+62
-26
No files matched your search
@@ -1,13 +1,16 @@
|
|||||||
import child, { execSync } from 'child_process';
|
import child from 'child_process';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import cac from 'cac';
|
import cac from 'cac';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
import superagent from 'superagent';
|
||||||
|
import tar from 'tar';
|
||||||
|
|
||||||
const argv = cac().parse();
|
const argv = cac().parse();
|
||||||
let isGlobal = null;
|
let yarnVersion = 0;
|
||||||
try {
|
try {
|
||||||
isGlobal = __dirname.startsWith(execSync('yarn global dir').toString().trim());
|
// eslint-disable-next-line no-unsafe-optional-chaining
|
||||||
|
yarnVersion = +child.execSync('yarn --version', { cwd: os.tmpdir() }).toString().split('v').pop()!.split('.')[0];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// yarn 2 does not support global dir
|
// yarn 2 does not support global dir
|
||||||
}
|
}
|
||||||
@@ -124,14 +127,35 @@ if (!argv.args[0] || argv.args[0] === 'cli') {
|
|||||||
console.log('Current Addons: ', addons);
|
console.log('Current Addons: ', addons);
|
||||||
fs.writeFileSync(addonPath, JSON.stringify(addons, null, 2));
|
fs.writeFileSync(addonPath, JSON.stringify(addons, null, 2));
|
||||||
});
|
});
|
||||||
cli.command('install [package]').action((name) => {
|
cli.command('install [package]').action(async (src) => {
|
||||||
if (!isGlobal) {
|
if (yarnVersion !== 1) throw new Error('Yarn 1 is required.');
|
||||||
console.warn('This is not a global installation, unable to install.');
|
const addonDir = path.join(hydroPath, 'addons');
|
||||||
return;
|
let newAddonPath: string = '';
|
||||||
}
|
fs.ensureDirSync(addonDir);
|
||||||
// TODO support install from tarball
|
// TODO: install from npm and check for update
|
||||||
child.execSync(`yarn global install '${name}'`, { stdio: 'inherit' });
|
if (src.startsWith('http')) {
|
||||||
child.execSync(`hydrooj addon add '${name}'`);
|
const url = new URL(src);
|
||||||
|
const filename = url.pathname.split('/').pop()!;
|
||||||
|
if (['.tar.gz', '.tgz', '.zip'].find((i) => filename.endsWith(i))) {
|
||||||
|
const name = filename.replace(/(-?\d+\.\d+\.\d+)?(\.tar\.gz|\.zip|\.tgz)$/g, '');
|
||||||
|
newAddonPath = path.join(addonDir, name);
|
||||||
|
console.log(`Downloading ${src} to ${newAddonPath}`);
|
||||||
|
fs.ensureDirSync(newAddonPath);
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
superagent.get(src)
|
||||||
|
.pipe(tar.x({
|
||||||
|
C: newAddonPath,
|
||||||
|
strip: 1,
|
||||||
|
}))
|
||||||
|
.on('finish', resolve)
|
||||||
|
.on('error', reject);
|
||||||
|
});
|
||||||
|
} else throw new Error('Unsupported file type');
|
||||||
|
} else throw new Error('Unsupported install schema');
|
||||||
|
if (!newAddonPath) throw new Error('Addon download failed');
|
||||||
|
console.log('Installing depedencies');
|
||||||
|
child.execSync('yarn --production', { stdio: 'inherit', cwd: newAddonPath });
|
||||||
|
child.execSync(`hydrooj addon add '${newAddonPath}'`);
|
||||||
});
|
});
|
||||||
cli.help();
|
cli.help();
|
||||||
cli.parse();
|
cli.parse();
|
||||||
|
|||||||
@@ -52,6 +52,7 @@
|
|||||||
"semver": "^7.3.8",
|
"semver": "^7.3.8",
|
||||||
"serialize-javascript": "^6.0.0",
|
"serialize-javascript": "^6.0.0",
|
||||||
"superagent": "^8.0.3",
|
"superagent": "^8.0.3",
|
||||||
|
"tar": "^6.1.12",
|
||||||
"thirty-two": "^1.0.2",
|
"thirty-two": "^1.0.2",
|
||||||
"ws": "^8.10.0"
|
"ws": "^8.10.0"
|
||||||
},
|
},
|
||||||
@@ -72,6 +73,7 @@
|
|||||||
"@types/semver": "^7.3.13",
|
"@types/semver": "^7.3.13",
|
||||||
"@types/serialize-javascript": "^5.0.2",
|
"@types/serialize-javascript": "^5.0.2",
|
||||||
"@types/superagent": "^4.1.15",
|
"@types/superagent": "^4.1.15",
|
||||||
|
"@types/tar": "^6.1.3",
|
||||||
"moment": "^2.29.4"
|
"moment": "^2.29.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,6 @@ import { ValidationError } from '../error';
|
|||||||
const RE_UID = /^-?\d+$/i;
|
const RE_UID = /^-?\d+$/i;
|
||||||
const RE_DOMAINID = /^[a-zA-Z][a-zA-Z0-9_]{3,31}$/i;
|
const RE_DOMAINID = /^[a-zA-Z][a-zA-Z0-9_]{3,31}$/i;
|
||||||
const RE_PID = /^[a-zA-Z]+[a-zA-Z0-9]*$/i;
|
const RE_PID = /^[a-zA-Z]+[a-zA-Z0-9]*$/i;
|
||||||
const RE_UNAME = /^.{3,31}$/i;
|
|
||||||
const RE_ROLE = /^[_0-9A-Za-z]{1,31}$/i;
|
const RE_ROLE = /^[_0-9A-Za-z]{1,31}$/i;
|
||||||
const RE_MAIL = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/i;
|
const RE_MAIL = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/i;
|
||||||
|
|
||||||
@@ -13,8 +12,6 @@ export const isDomainId = (s) => RE_DOMAINID.test(s);
|
|||||||
export const checkDomainId = (s) => { if (!isDomainId(s)) throw new ValidationError('domainId'); else return s; };
|
export const checkDomainId = (s) => { if (!isDomainId(s)) throw new ValidationError('domainId'); else return s; };
|
||||||
export const isUid = (s) => RE_UID.test(s);
|
export const isUid = (s) => RE_UID.test(s);
|
||||||
export const checkUid = (s) => { if (!isUid(s)) throw new ValidationError('uid'); else return s; };
|
export const checkUid = (s) => { if (!isUid(s)) throw new ValidationError('uid'); else return s; };
|
||||||
export const isUname = (s) => RE_UNAME.test(s);
|
|
||||||
export const checkUname = (s) => { if (!isUname(s)) throw new ValidationError('uname'); else return s; };
|
|
||||||
export const isRole = (s) => RE_ROLE.test(s);
|
export const isRole = (s) => RE_ROLE.test(s);
|
||||||
export const checkRole = (s) => { if (!isRole(s)) throw new ValidationError('role'); else return s; };
|
export const checkRole = (s) => { if (!isRole(s)) throw new ValidationError('role'); else return s; };
|
||||||
export const isPassword = (s) => s.length >= 5;
|
export const isPassword = (s) => s.length >= 5;
|
||||||
@@ -37,8 +34,6 @@ global.Hydro.lib.validator = {
|
|||||||
checkDomainId,
|
checkDomainId,
|
||||||
isUid,
|
isUid,
|
||||||
checkUid,
|
checkUid,
|
||||||
isUname,
|
|
||||||
checkUname,
|
|
||||||
isRole,
|
isRole,
|
||||||
checkRole,
|
checkRole,
|
||||||
isPassword,
|
isPassword,
|
||||||
|
|||||||
@@ -298,15 +298,15 @@ const oi = buildContestRule({
|
|||||||
const udict = await user.getList(tdoc.domainId, uids);
|
const udict = await user.getList(tdoc.domainId, uids);
|
||||||
const psdict = {};
|
const psdict = {};
|
||||||
const first = {};
|
const first = {};
|
||||||
for (const pid of tdoc.pids) {
|
await Promise.all(tdoc.pids.map(async (pid) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define, no-await-in-loop
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||||
const [data] = await getMultiStatus(tdoc.domainId, {
|
const [data] = await getMultiStatus(tdoc.domainId, {
|
||||||
docType: document.TYPE_CONTEST,
|
docType: document.TYPE_CONTEST,
|
||||||
docId: tdoc.docId,
|
docId: tdoc.docId,
|
||||||
[`detail.${pid}.status`]: STATUS.STATUS_ACCEPTED,
|
[`detail.${pid}.status`]: STATUS.STATUS_ACCEPTED,
|
||||||
}).sort({ [`detail.${pid}.rid`]: 1 }).limit(1).toArray();
|
}).sort({ [`detail.${pid}.rid`]: 1 }).limit(1).toArray();
|
||||||
first[pid] = data ? data.detail[pid].rid.generationTime : new ObjectID().generationTime;
|
first[pid] = data ? data.detail[pid].rid.generationTime : new ObjectID().generationTime;
|
||||||
}
|
}));
|
||||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||||
if (isDone(tdoc)) {
|
if (isDone(tdoc)) {
|
||||||
const psdocs = await Promise.all(
|
const psdocs = await Promise.all(
|
||||||
|
|||||||
@@ -76,4 +76,7 @@ export function apply(ctx: Context) {
|
|||||||
callback,
|
callback,
|
||||||
get,
|
get,
|
||||||
});
|
});
|
||||||
|
ctx.i18n.load('zh', {
|
||||||
|
'Login With Github': '使用 Github 登录',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
Login With Github: 使用 Github 登录
|
|
||||||
@@ -78,4 +78,7 @@ export function apply(ctx: Context) {
|
|||||||
callback,
|
callback,
|
||||||
get,
|
get,
|
||||||
});
|
});
|
||||||
|
ctx.i18n.load('zh', {
|
||||||
|
'Login With Google': '使用 Google 登录',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
Login With Google: 使用 Google 登录
|
|
||||||
@@ -9,6 +9,7 @@ export function apply(ctx: Context) {
|
|||||||
hustoj(ctx);
|
hustoj(ctx);
|
||||||
vijos(ctx);
|
vijos(ctx);
|
||||||
syzoj(ctx);
|
syzoj(ctx);
|
||||||
|
|
||||||
ctx.provideModule('hash', 'hust', ($password, $saved) => {
|
ctx.provideModule('hash', 'hust', ($password, $saved) => {
|
||||||
$password = md5($password);
|
$password = md5($password);
|
||||||
if (RE_MD5.test($saved)) return $password === $saved;
|
if (RE_MD5.test($saved)) return $password === $saved;
|
||||||
@@ -27,4 +28,10 @@ export function apply(ctx: Context) {
|
|||||||
return `${Buffer.from(uname).toString('base64')}|${mixedSha1}`;
|
return `${Buffer.from(uname).toString('base64')}|${mixedSha1}`;
|
||||||
});
|
});
|
||||||
ctx.provideModule('hash', 'syzoj', (password: string) => md5(`${password}syzoj2_xxx`));
|
ctx.provideModule('hash', 'syzoj', (password: string) => md5(`${password}syzoj2_xxx`));
|
||||||
|
|
||||||
|
ctx.i18n.load('zh', {
|
||||||
|
'migrate from hustoj': '从 HustOJ 导入',
|
||||||
|
'migrate from vijos': '从 Vijos 导入',
|
||||||
|
'migrate from syzoj': '从 SYZOJ 导入',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
migrate from hustoj: 从 HustOJ 导入
|
|
||||||
migrate from vijos: 从 Vijos 导入
|
|
||||||
@@ -15,7 +15,7 @@ export function apply(ctx: Context) {
|
|||||||
.field('secret', SystemModel.get('recaptcha.secret'))
|
.field('secret', SystemModel.get('recaptcha.secret'))
|
||||||
.field('response', thisArg.args.captcha)
|
.field('response', thisArg.args.captcha)
|
||||||
.field('remoteip', thisArg.request.ip);
|
.field('remoteip', thisArg.request.ip);
|
||||||
if (!response.body.success) throw new ForbiddenError('captcha fail');
|
if (!response.body.success) throw new ForbiddenError('Failed to solve the captcha.');
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.on('handler/after/UserRegister', async (thisArg) => {
|
ctx.on('handler/after/UserRegister', async (thisArg) => {
|
||||||
@@ -24,4 +24,8 @@ export function apply(ctx: Context) {
|
|||||||
<input type="text" name="captcha" id="_captcha" style="display:none">
|
<input type="text" name="captcha" id="_captcha" style="display:none">
|
||||||
<input type="submit" id="_submit" style="display:none">`;
|
<input type="submit" id="_submit" style="display:none">`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ctx.i18n.load('zh', {
|
||||||
|
'Failed to solve the captcha': '没有通过 ReCaptcha 验证。',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
captcha fail: 您没有通过 Recaptcha 验证。
|
|
||||||
@@ -92,4 +92,8 @@ export function apply(ctx: Context) {
|
|||||||
}),
|
}),
|
||||||
run,
|
run,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ctx.i18n.load('zh', {
|
||||||
|
'Sonic problem search re-index': '重建题目搜索索引。',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
Sonic problem search re-index: 重建题目搜索索引。
|
|
||||||
Reference in new issue
Block a user