mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
config: remove all setting.yaml
This commit is contained in:
16 files changed
+284
-385
No files matched your search
@@ -6,24 +6,31 @@ import { fs, yaml } from '@hydrooj/utils';
|
|||||||
|
|
||||||
const argv = cac().parse();
|
const argv = cac().parse();
|
||||||
|
|
||||||
const JudgeSettings = Schema.object({
|
const defaultEnv = `\
|
||||||
cache_dir: Schema.string().default(path.resolve(os.homedir(), '.cache', 'hydro', 'judge')),
|
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
HOME=/w
|
||||||
|
# modify to your python version installed
|
||||||
|
PYTHONPATH=/lib/python3.12/site-packages
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const JudgeSettings = Schema.object({
|
||||||
|
cache_dir: Schema.string().default(path.resolve(os.homedir(), '.cache', 'hydro', 'judge')).description('Testdata cache directory'),
|
||||||
tmp_dir: Schema.string().default(path.resolve(os.tmpdir(), 'hydro', 'judge')),
|
tmp_dir: Schema.string().default(path.resolve(os.tmpdir(), 'hydro', 'judge')),
|
||||||
stdio_size: Schema.string().pattern(/^\d+[kmg]b?$/g).default('32m'),
|
stdio_size: Schema.string().pattern(/^\d+[kmg]b?$/g).default('32m'),
|
||||||
memoryMax: Schema.string().pattern(/^\d+[kmg]b?$/g).default('512m'),
|
memoryMax: Schema.string().pattern(/^\d+[kmg]b?$/g).default('512m'),
|
||||||
strict_memory: Schema.boolean().default(false),
|
strict_memory: Schema.boolean().default(false).description('Use address space memory limit'),
|
||||||
sandbox_host: Schema.string().role('link').default('http://localhost:5050'),
|
sandbox_host: Schema.string().role('url').default('http://localhost:5050'),
|
||||||
testcases_max: Schema.number().default(100),
|
testcases_max: Schema.number().default(100).min(1).step(1),
|
||||||
total_time_limit: Schema.number().default(60),
|
total_time_limit: Schema.number().default(60).min(1),
|
||||||
processLimit: Schema.number().default(32),
|
processLimit: Schema.number().default(32).min(1).step(1),
|
||||||
parallelism: Schema.number().default(2),
|
parallelism: Schema.number().default(2).min(1).step(1),
|
||||||
concurrency: Schema.number(),
|
concurrency: Schema.number(),
|
||||||
singleTaskParallelism: Schema.number().default(2),
|
singleTaskParallelism: Schema.number().default(2).min(1).step(1),
|
||||||
rerun: Schema.number().description('Re-Run testcase if time-limit-exceeded (max per submission)').default(0),
|
rerun: Schema.number().description('Re-Run testcase if time-limit-exceeded (max per submission)').default(0).min(0).step(1),
|
||||||
rate: Schema.number().default(1),
|
rate: Schema.number().default(1),
|
||||||
env: Schema.string().default('PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\nHOME=/w'),
|
env: Schema.string().default(defaultEnv),
|
||||||
host: Schema.any(),
|
host: Schema.any(),
|
||||||
secret: Schema.string().description('Judge Token Secret').default(String.random(32)),
|
secret: Schema.string().description('Judge Token Secret').default('Hydro-Judge-Secret'),
|
||||||
disable: Schema.boolean().description('Disable builtin judge').default(false),
|
disable: Schema.boolean().description('Disable builtin judge').default(false),
|
||||||
detail: Schema.boolean().description('Show diff detail').default(true),
|
detail: Schema.boolean().description('Show diff detail').default(true),
|
||||||
performance: Schema.boolean().description('Performance mode').default(false),
|
performance: Schema.boolean().description('Performance mode').default(false),
|
||||||
@@ -32,7 +39,7 @@ const JudgeSettings = Schema.object({
|
|||||||
const oldPath = path.resolve(os.homedir(), '.config', 'hydro', 'judge.yaml');
|
const oldPath = path.resolve(os.homedir(), '.config', 'hydro', 'judge.yaml');
|
||||||
const newPath = path.resolve(os.homedir(), '.hydro', 'judge.yaml');
|
const newPath = path.resolve(os.homedir(), '.hydro', 'judge.yaml');
|
||||||
|
|
||||||
const config = global.Hydro
|
let config = global.Hydro
|
||||||
? JudgeSettings({})
|
? JudgeSettings({})
|
||||||
: (() => {
|
: (() => {
|
||||||
const base: any = {};
|
const base: any = {};
|
||||||
@@ -54,6 +61,8 @@ const config = global.Hydro
|
|||||||
return JudgeSettings(cfg);
|
return JudgeSettings(cfg);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
export const getConfig: <K extends keyof typeof config>(key: K) => typeof config[K] = global.Hydro
|
export function overrideConfig(update: ReturnType<typeof JudgeSettings>) {
|
||||||
? (key) => global.Hydro.model.system.get(`hydrojudge.${key}`) ?? config[key]
|
config = JudgeSettings(update);
|
||||||
: (key) => config[key];
|
}
|
||||||
|
|
||||||
|
export const getConfig: <K extends keyof typeof config>(key: K) => typeof config[K] = (key) => config[key];
|
||||||
@@ -4,7 +4,7 @@ import path from 'path';
|
|||||||
import { fs } from '@hydrooj/utils';
|
import { fs } from '@hydrooj/utils';
|
||||||
import * as sysinfo from '@hydrooj/utils/lib/sysinfo';
|
import * as sysinfo from '@hydrooj/utils/lib/sysinfo';
|
||||||
import {
|
import {
|
||||||
db, JudgeHandler, JudgeResultBody, ObjectId, RecordModel,
|
Context as HydroContext, db, JudgeHandler, JudgeResultBody, ObjectId, RecordModel,
|
||||||
SettingModel, StorageModel, SystemModel, TaskModel,
|
SettingModel, StorageModel, SystemModel, TaskModel,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
import { langs } from 'hydrooj/src/model/setting';
|
import { langs } from 'hydrooj/src/model/setting';
|
||||||
@@ -59,10 +59,11 @@ const session: Session = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function postInit(ctx) {
|
export async function* apply(ctx: HydroContext) {
|
||||||
if (SystemModel.get('hydrojudge.disable')) return;
|
|
||||||
ctx.inject(['check'], (c) => {
|
ctx.inject(['check'], (c) => {
|
||||||
c.check.addChecker('Judge', (_ctx, log, warn, error) => versionCheck(warn, error));
|
c.check.addChecker('Judge', async (_ctx, log, warn, error) => {
|
||||||
|
await versionCheck(warn, error);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
await fs.ensureDir(getConfig('tmp_dir'));
|
await fs.ensureDir(getConfig('tmp_dir'));
|
||||||
const info = await sysinfo.get();
|
const info = await sysinfo.get();
|
||||||
@@ -76,6 +77,7 @@ export async function postInit(ctx) {
|
|||||||
};
|
};
|
||||||
const parallelism = Math.max(getConfig('parallelism'), 2);
|
const parallelism = Math.max(getConfig('parallelism'), 2);
|
||||||
const taskConsumer = TaskModel.consume({ type: 'judge' }, handle, true, parallelism);
|
const taskConsumer = TaskModel.consume({ type: 'judge' }, handle, true, parallelism);
|
||||||
|
yield () => taskConsumer.destroy();
|
||||||
async function collectInfo() {
|
async function collectInfo() {
|
||||||
const coll = db.collection('status');
|
const coll = db.collection('status');
|
||||||
const compilers = await compilerVersions(langs);
|
const compilers = await compilerVersions(langs);
|
||||||
@@ -91,11 +93,11 @@ export async function postInit(ctx) {
|
|||||||
{ upsert: true },
|
{ upsert: true },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ctx.on('system/setting', () => {
|
yield ctx.on('system/setting', () => {
|
||||||
taskConsumer.setConcurrency(Math.max(getConfig('parallelism'), 2));
|
taskConsumer.setConcurrency(Math.max(getConfig('parallelism'), 2));
|
||||||
collectInfo();
|
collectInfo();
|
||||||
});
|
});
|
||||||
collectInfo();
|
collectInfo();
|
||||||
TaskModel.consume({ type: 'judge', priority: { $gt: -50 } }, handle);
|
const generateConsumer = TaskModel.consume({ type: 'generate' }, handle);
|
||||||
TaskModel.consume({ type: 'generate' }, handle);
|
yield () => generateConsumer.destroy();
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,11 @@
|
|||||||
import { Context } from 'hydrooj';
|
import { Context } from 'hydrooj';
|
||||||
|
import { JudgeSettings, overrideConfig } from './config';
|
||||||
|
|
||||||
declare module 'hydrooj' {
|
export const Config = JudgeSettings;
|
||||||
interface SystemKeys {
|
|
||||||
'hydrojudge.cache_dir': string;
|
|
||||||
'hydrojudge.tmp_dir': string;
|
|
||||||
'hydrojudge.sandbox_host': string;
|
|
||||||
'hydrojudge.memoryMax': string;
|
|
||||||
'hydrojudge.testcases_max': number;
|
|
||||||
'hydrojudge.total_time_limit': number;
|
|
||||||
'hydrojudge.parallelism': number;
|
|
||||||
'hydrojudge.disable': boolean;
|
|
||||||
'hydrojudge.detail': boolean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function apply(ctx: Context) {
|
export function apply(ctx: Context, config: ReturnType<typeof Config>) {
|
||||||
if (process.env.NODE_APP_INSTANCE !== '0') return;
|
if (process.env.NODE_APP_INSTANCE !== '0') return;
|
||||||
ctx.once('app/started', () => require('./hosts/builtin').postInit(ctx));
|
overrideConfig(config);
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
if (!config.disable) return require('./hosts/builtin').apply(ctx);
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import type { DomainDoc, GeoIP, ModuleInterfaces } from './interface';
|
|||||||
import { inject } from './lib/ui';
|
import { inject } from './lib/ui';
|
||||||
import { Loader } from './loader';
|
import { Loader } from './loader';
|
||||||
import type { EventMap } from './service/bus';
|
import type { EventMap } from './service/bus';
|
||||||
import type { CheckService } from './service/check';
|
import type CheckService from './service/check';
|
||||||
import type { } from './service/migration';
|
import type { } from './service/migration';
|
||||||
|
|
||||||
// TODO: this is an broken declaration
|
// TODO: this is an broken declaration
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import record from '../model/record';
|
|||||||
import * as setting from '../model/setting';
|
import * as setting from '../model/setting';
|
||||||
import * as system from '../model/system';
|
import * as system from '../model/system';
|
||||||
import user from '../model/user';
|
import user from '../model/user';
|
||||||
import { check } from '../service/check';
|
|
||||||
import {
|
import {
|
||||||
ConnectionHandler, Handler, param, requireSudo, Types,
|
ConnectionHandler, Handler, param, requireSudo, Types,
|
||||||
} from '../service/server';
|
} from '../service/server';
|
||||||
@@ -70,11 +69,11 @@ class SystemCheckConnHandler extends ConnectionHandler {
|
|||||||
const log = (payload: any) => this.send({ type: 'log', payload });
|
const log = (payload: any) => this.send({ type: 'log', payload });
|
||||||
const warn = (payload: any) => this.send({ type: 'warn', payload });
|
const warn = (payload: any) => this.send({ type: 'warn', payload });
|
||||||
const error = (payload: any) => this.send({ type: 'error', payload });
|
const error = (payload: any) => this.send({ type: 'error', payload });
|
||||||
await check.run(this, log, warn, error, (id) => { this.id = id; });
|
await this.ctx.check.run(this, log, warn, error, (id) => { this.id = id; });
|
||||||
}
|
}
|
||||||
|
|
||||||
async cleanup() {
|
async cleanup() {
|
||||||
check.cancel(this.id);
|
this.ctx.check.cancel(this.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,7 +350,7 @@ class SystemUserPrivHandler extends SystemHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const inject = ['config'];
|
export const inject = ['config', 'check'];
|
||||||
export async function apply(ctx) {
|
export async function apply(ctx) {
|
||||||
ctx.Route('manage', '/manage', SystemMainHandler);
|
ctx.Route('manage', '/manage', SystemMainHandler);
|
||||||
ctx.Route('manage_dashboard', '/manage/dashboard', SystemDashboardHandler);
|
ctx.Route('manage_dashboard', '/manage/dashboard', SystemDashboardHandler);
|
||||||
|
|||||||
@@ -7,11 +7,49 @@ import * as system from '../model/system';
|
|||||||
import db from './db';
|
import db from './db';
|
||||||
import storage from './storage';
|
import storage from './storage';
|
||||||
|
|
||||||
type CheckItem = (context: any, log: Function, warn: Function, error: Function) => Promise<void>;
|
type ReportFunction = (message: string, ...args: any[]) => Promise<void>;
|
||||||
|
type CheckItem = (context: any, log: ReportFunction, warn: ReportFunction, error: ReportFunction) => Promise<void>;
|
||||||
|
|
||||||
export class CheckService extends Service {
|
export default class CheckService extends Service {
|
||||||
checkers: Dictionary<CheckItem> = {};
|
checkers: Dictionary<CheckItem> = {};
|
||||||
c = {};
|
c = {};
|
||||||
|
|
||||||
|
constructor(ctx: Context) {
|
||||||
|
super(ctx, 'check');
|
||||||
|
this.addChecker('Db', async (c, log, warn, error) => {
|
||||||
|
try {
|
||||||
|
const coll = db.collection('check');
|
||||||
|
const d = await coll.findOne({ _id: 'check' });
|
||||||
|
if (!d) await coll.insertOne({ _id: 'check', value: 'check' });
|
||||||
|
await coll.createIndex('check');
|
||||||
|
await coll.deleteOne({ _id: 'check' });
|
||||||
|
await coll.drop();
|
||||||
|
} catch (e) {
|
||||||
|
error(`Mongo Error: Database read-write failed.\n${e.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.addChecker('Storage', async (c, log, warn, error) => {
|
||||||
|
const status = await storage.status();
|
||||||
|
if (!status.status) error(`Connect to ${status.type} Storage ${status.bucket} Error: ${status.error}`);
|
||||||
|
});
|
||||||
|
this.addChecker('System', async (c, log, warn) => {
|
||||||
|
const platform = os.platform();
|
||||||
|
if (platform !== 'linux') {
|
||||||
|
warn(`Hydro is running on ${platform}, suggest to use linux4.4+.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.addChecker('Mail', async (c, log, warn) => {
|
||||||
|
const from = system.get('smtp.from');
|
||||||
|
if (!from) warn('SMTP account is not provided, email verification disabled.');
|
||||||
|
});
|
||||||
|
this.addChecker('Setting', async (c, log, warn) => {
|
||||||
|
const url = system.get('server.url');
|
||||||
|
if (url === '/') warn("server.url isn't set.");
|
||||||
|
const header = system.get('server.xff');
|
||||||
|
if (header && !c.request.ip) warn('IP header seems incorrect.\nCheck dashboard>settings>server.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
addChecker(type: string, checkFunc: CheckItem) {
|
addChecker(type: string, checkFunc: CheckItem) {
|
||||||
this.ctx.effect(() => {
|
this.ctx.effect(() => {
|
||||||
this.checkers[`check${type}`] = checkFunc;
|
this.checkers[`check${type}`] = checkFunc;
|
||||||
@@ -38,43 +76,3 @@ export class CheckService extends Service {
|
|||||||
this.c[id] = true;
|
this.c[id] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const check = new CheckService(app, 'check');
|
|
||||||
|
|
||||||
check.addChecker('Db', async (ctx, log, warn, error) => {
|
|
||||||
try {
|
|
||||||
const coll = db.collection('check');
|
|
||||||
const d = await coll.findOne({ _id: 'check' });
|
|
||||||
if (!d) await coll.insertOne({ _id: 'check', value: 'check' });
|
|
||||||
await coll.createIndex('check');
|
|
||||||
await coll.deleteOne({ _id: 'check' });
|
|
||||||
await coll.drop();
|
|
||||||
} catch (e) {
|
|
||||||
error(`Mongo Error: Database read-write failed.\n${e.message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
check.addChecker('Storage', async (ctx, log, warn, error) => {
|
|
||||||
const status = await storage.status();
|
|
||||||
if (!status.status) error(`Connect to ${status.type} Storage ${status.bucket} Error: ${status.error}`);
|
|
||||||
});
|
|
||||||
check.addChecker('System', async (ctx, log, warn) => {
|
|
||||||
const platform = os.platform();
|
|
||||||
if (platform !== 'linux') {
|
|
||||||
warn(`Hydro is running on ${platform}, suggest to use linux4.4+.`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
check.addChecker('Mail', async (ctx, log, warn) => {
|
|
||||||
const from = system.get('smtp.from');
|
|
||||||
if (!from) warn('SMTP account is not provided, email verification disabled.');
|
|
||||||
});
|
|
||||||
check.addChecker('Setting', async (ctx, log, warn) => {
|
|
||||||
const url = system.get('server.url');
|
|
||||||
if (url === '/') warn("server.url isn't set.");
|
|
||||||
const header = system.get('server.xff');
|
|
||||||
if (header && !ctx.request.ip) warn('IP header seems incorrect.\nCheck dashboard>settings>server.');
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function apply(ctx: Context) {
|
|
||||||
ctx.provide('check', CheckService, true);
|
|
||||||
ctx.check = check;
|
|
||||||
}
|
|
||||||
@@ -1,39 +1,27 @@
|
|||||||
import {
|
import {
|
||||||
Context, ForbiddenError, Handler, superagent, SystemModel,
|
Context, ForbiddenError, Handler, Schema, Service, superagent, SystemModel,
|
||||||
TokenModel, UserFacingError, ValidationError,
|
TokenModel, UserFacingError, ValidationError,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
|
|
||||||
declare module 'hydrooj' {
|
export default class LoginWithGithubService extends Service {
|
||||||
interface SystemKeys {
|
static Config = Schema.object({
|
||||||
'login-with-github.id': string;
|
id: Schema.string().description('Github OAuth AppID').required(),
|
||||||
'login-with-github.secret': string;
|
secret: Schema.string().description('Github OAuth Secret').role('secret').required(),
|
||||||
'login-with-github.endpoint': string;
|
endpoint: Schema.string().description('Github Endpoint'),
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
async function get(this: Handler) {
|
constructor(ctx: Context, config: ReturnType<typeof LoginWithGithubService.Config>) {
|
||||||
const [appid, [state]] = await Promise.all([
|
super(ctx, 'oauth.github');
|
||||||
SystemModel.get('login-with-github.id'),
|
ctx.provideModule('oauth', 'github', {
|
||||||
TokenModel.add(TokenModel.TYPE_OAUTH, 600, { redirect: this.request.referer }),
|
text: 'Login with Github',
|
||||||
]);
|
callback: async function callback({ state, code }) {
|
||||||
this.response.redirect = `https://github.com/login/oauth/authorize?client_id=${appid}&state=${state}&scope=read:user,user:email`;
|
const url = await SystemModel.get('server.url');
|
||||||
}
|
const s = await TokenModel.get(state, TokenModel.TYPE_OAUTH);
|
||||||
|
|
||||||
async function callback({ state, code }) {
|
|
||||||
const [[appid, secret, endpoint, url], s] = await Promise.all([
|
|
||||||
SystemModel.getMany([
|
|
||||||
'login-with-github.id',
|
|
||||||
'login-with-github.secret',
|
|
||||||
'login-with-github.endpoint',
|
|
||||||
'server.url',
|
|
||||||
]),
|
|
||||||
TokenModel.get(state, TokenModel.TYPE_OAUTH),
|
|
||||||
]);
|
|
||||||
if (!s) throw new ValidationError('token');
|
if (!s) throw new ValidationError('token');
|
||||||
const res = await superagent.post(`${endpoint || 'https://github.com'}/login/oauth/access_token`)
|
const res = await superagent.post(`${config.endpoint || 'https://github.com'}/login/oauth/access_token`)
|
||||||
.send({
|
.send({
|
||||||
client_id: appid,
|
client_id: config.id,
|
||||||
client_secret: secret,
|
client_secret: config.secret,
|
||||||
code,
|
code,
|
||||||
redirect_uri: `${url}oauth/github/callback`,
|
redirect_uri: `${url}oauth/github/callback`,
|
||||||
state,
|
state,
|
||||||
@@ -45,7 +33,7 @@ async function callback({ state, code }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const t = res.body.access_token;
|
const t = res.body.access_token;
|
||||||
const userInfo = await superagent.get(`${endpoint ? `${endpoint}/api` : 'https://api.github.com'}/user`)
|
const userInfo = await superagent.get(`${config.endpoint ? `${config.endpoint}/api` : 'https://api.github.com'}/user`)
|
||||||
.set('User-Agent', 'Hydro-OAuth')
|
.set('User-Agent', 'Hydro-OAuth')
|
||||||
.set('Accept', 'application/vnd.github.v3+json')
|
.set('Accept', 'application/vnd.github.v3+json')
|
||||||
.set('Authorization', `token ${t}`);
|
.set('Authorization', `token ${t}`);
|
||||||
@@ -57,7 +45,7 @@ async function callback({ state, code }) {
|
|||||||
avatar: `github:${userInfo.body.login}`,
|
avatar: `github:${userInfo.body.login}`,
|
||||||
};
|
};
|
||||||
if (!ret.email) {
|
if (!ret.email) {
|
||||||
const emailInfo = await superagent.get(`${endpoint ? `${endpoint}/api` : 'https://api.github.com'}/user/emails`)
|
const emailInfo = await superagent.get(`${config.endpoint ? `${config.endpoint}/api` : 'https://api.github.com'}/user/emails`)
|
||||||
.set('User-Agent', 'Hydro-OAuth')
|
.set('User-Agent', 'Hydro-OAuth')
|
||||||
.set('Accept', 'application/vnd.github.v3+json')
|
.set('Accept', 'application/vnd.github.v3+json')
|
||||||
.set('Authorization', `token ${t}`);
|
.set('Authorization', `token ${t}`);
|
||||||
@@ -68,15 +56,15 @@ async function callback({ state, code }) {
|
|||||||
await TokenModel.del(s._id, TokenModel.TYPE_OAUTH);
|
await TokenModel.del(s._id, TokenModel.TYPE_OAUTH);
|
||||||
if (!ret.email) throw new ForbiddenError("You don't have a verified email.");
|
if (!ret.email) throw new ForbiddenError("You don't have a verified email.");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
},
|
||||||
|
get: async function get(this: Handler) {
|
||||||
export function apply(ctx: Context) {
|
const [state] = await TokenModel.add(TokenModel.TYPE_OAUTH, 600, { redirect: this.request.referer });
|
||||||
ctx.provideModule('oauth', 'github', {
|
// eslint-disable-next-line max-len
|
||||||
text: 'Login with Github',
|
this.response.redirect = `https://github.com/login/oauth/authorize?client_id=${config.appid}&state=${state}&scope=read:user,user:email`;
|
||||||
callback,
|
},
|
||||||
get,
|
|
||||||
});
|
});
|
||||||
ctx.i18n.load('zh', {
|
ctx.i18n.load('zh', {
|
||||||
'Login With Github': '使用 Github 登录',
|
'Login With Github': '使用 Github 登录',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
id:
|
|
||||||
type: text
|
|
||||||
category: system
|
|
||||||
desc: Github OAuth AppID
|
|
||||||
name: id
|
|
||||||
secret:
|
|
||||||
type: text
|
|
||||||
category: system
|
|
||||||
desc: Github OAuth Secret
|
|
||||||
name: secret
|
|
||||||
endpoint:
|
|
||||||
type: text
|
|
||||||
category: system
|
|
||||||
desc: Github Api Endpoint
|
|
||||||
name: endpoint
|
|
||||||
@@ -1,24 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Context, Handler, superagent, SystemModel, TokenModel, UserFacingError,
|
Context, Handler, Schema, Service, superagent, SystemModel, TokenModel, UserFacingError,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
|
|
||||||
declare module 'hydrooj' {
|
|
||||||
interface SystemKeys {
|
|
||||||
'login-with-google.id': string,
|
|
||||||
'login-with-google.secret': string,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function get(this: Handler) {
|
|
||||||
const [appid, url, [state]] = await Promise.all([
|
|
||||||
SystemModel.get('login-with-google.id'),
|
|
||||||
SystemModel.get('server.url'),
|
|
||||||
TokenModel.add(TokenModel.TYPE_OAUTH, 600, { redirect: this.request.referer }),
|
|
||||||
]);
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
this.response.redirect = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${appid}&response_type=code&redirect_uri=${url}oauth/google/callback&scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile&state=${state}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function unescapedString(escapedString: string) {
|
function unescapedString(escapedString: string) {
|
||||||
escapedString += new Array(5 - (escapedString.length % 4)).join('=');
|
escapedString += new Array(5 - (escapedString.length % 4)).join('=');
|
||||||
return escapedString.replace(/-/g, '+').replace(/_/g, '/');
|
return escapedString.replace(/-/g, '+').replace(/_/g, '/');
|
||||||
@@ -42,20 +25,29 @@ function decodeJWT(idToken: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function callback(this: Handler, {
|
export default class GoogleOAuthService extends Service {
|
||||||
|
static Config = Schema.object({
|
||||||
|
id: Schema.string().description('Google OAuth AppID').required(),
|
||||||
|
secret: Schema.string().description('Google OAuth Secret').role('secret').required(),
|
||||||
|
proxy: Schema.string().description('Google OAuth Proxy').role('proxy'),
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(ctx: Context, config: ReturnType<typeof GoogleOAuthService.Config>) {
|
||||||
|
super(ctx, 'oauth.google');
|
||||||
|
ctx.provideModule('oauth', 'google', {
|
||||||
|
text: 'Login with Google',
|
||||||
|
callback: async function callback(this: Handler, {
|
||||||
state, code, error,
|
state, code, error,
|
||||||
}) {
|
}) {
|
||||||
if (error) throw new UserFacingError(error);
|
if (error) throw new UserFacingError(error);
|
||||||
const [[appid, secret, url], s] = await Promise.all([
|
const [url, s] = await Promise.all([
|
||||||
SystemModel.getMany([
|
SystemModel.get('server.url'),
|
||||||
'login-with-google.id', 'login-with-google.secret', 'server.url',
|
|
||||||
]),
|
|
||||||
TokenModel.get(state, TokenModel.TYPE_OAUTH),
|
TokenModel.get(state, TokenModel.TYPE_OAUTH),
|
||||||
]);
|
]);
|
||||||
const res = await superagent.post('https://oauth2.googleapis.com/token')
|
const res = await superagent.post('https://oauth2.googleapis.com/token')
|
||||||
.send({
|
.send({
|
||||||
client_id: appid,
|
client_id: config.id,
|
||||||
client_secret: secret,
|
client_secret: config.secret,
|
||||||
code,
|
code,
|
||||||
grant_type: 'authorization_code',
|
grant_type: 'authorization_code',
|
||||||
redirect_uri: `${url}oauth/google/callback`,
|
redirect_uri: `${url}oauth/google/callback`,
|
||||||
@@ -70,15 +62,20 @@ async function callback(this: Handler, {
|
|||||||
uname: [payload.given_name, payload.name, payload.family_name],
|
uname: [payload.given_name, payload.name, payload.family_name],
|
||||||
viewLang: payload.locale.replace('-', '_'),
|
viewLang: payload.locale.replace('-', '_'),
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
get: async function get(this: Handler) {
|
||||||
export function apply(ctx: Context) {
|
const [appid, url, [state]] = await Promise.all([
|
||||||
ctx.provideModule('oauth', 'google', {
|
config.id,
|
||||||
text: 'Login with Google',
|
config.proxy,
|
||||||
callback,
|
TokenModel.add(TokenModel.TYPE_OAUTH, 600, { redirect: this.request.referer }),
|
||||||
get,
|
]);
|
||||||
|
const scope = encodeURIComponent('https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile');
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
this.response.redirect = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${appid}&response_type=code&redirect_uri=${url}oauth/google/callback&scope=${scope}&state=${state}`;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
ctx.i18n.load('zh', {
|
ctx.i18n.load('zh', {
|
||||||
'Login With Google': '使用 Google 登录',
|
'Login With Google': '使用 Google 登录',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
id:
|
|
||||||
type: text
|
|
||||||
category: system
|
|
||||||
desc: Google OAuth AppID
|
|
||||||
name: id
|
|
||||||
secret:
|
|
||||||
type: text
|
|
||||||
category: system
|
|
||||||
desc: Google OAuth Secret
|
|
||||||
name: secret
|
|
||||||
proxy:
|
|
||||||
type: text
|
|
||||||
category: system
|
|
||||||
desc: Google OAuth Proxy
|
|
||||||
name: proxy
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { sign } from 'jsonwebtoken';
|
import { sign } from 'jsonwebtoken';
|
||||||
import { } from '@hydrooj/ui-default/backendlib/markdown-it-media';
|
import { } from '@hydrooj/ui-default/backendlib/markdown-it-media';
|
||||||
import {
|
import {
|
||||||
Context, Handler, superagent, SystemModel, UiContextBase, ValidationError,
|
Context, Handler, Schema, superagent, SystemModel, UiContextBase, ValidationError,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
|
|
||||||
declare module 'hydrooj' {
|
declare module 'hydrooj' {
|
||||||
@@ -72,19 +72,6 @@ class OnlyofficeJWTHandler extends Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function apply(ctx: Context) {
|
export function apply(ctx: Context) {
|
||||||
Object.defineProperty(UiContextBase, 'onlyofficeApi', {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
get() {
|
|
||||||
return SystemModel.get('onlyoffice.api');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
ctx.on('dispose', () => {
|
|
||||||
Object.defineProperty(UiContextBase, 'onlyofficeApi', {
|
|
||||||
enumerable: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.inject(['i18n'], (c) => {
|
ctx.inject(['i18n'], (c) => {
|
||||||
c.i18n.load('en', {
|
c.i18n.load('en', {
|
||||||
'onlyoffice.not_configured': 'Onlyoffice API not configured.',
|
'onlyoffice.not_configured': 'Onlyoffice API not configured.',
|
||||||
@@ -104,4 +91,24 @@ export function apply(ctx: Context) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
ctx.setting.SystemSetting(Schema.object({
|
||||||
|
onlyoffice: Schema.object({
|
||||||
|
api: Schema.string().description('OnlyOffice API URL').role('url').default('https://documentserver/web-apps/apps/api/documents/api.js'),
|
||||||
|
jwtsecret: Schema.string().description('JWT Secret').default('secret'),
|
||||||
|
pdf: Schema.boolean().description('Handle pdf documents').default(false),
|
||||||
|
externalSign: Schema.string().description('External Sign URL').default(''),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
Object.defineProperty(UiContextBase, 'onlyofficeApi', {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
get() {
|
||||||
|
return SystemModel.get('onlyoffice.api');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
Object.defineProperty(UiContextBase, 'onlyofficeApi', {
|
||||||
|
enumerable: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
api:
|
|
||||||
type: text
|
|
||||||
name: api
|
|
||||||
desc: OnlyOffice API URL
|
|
||||||
default: https://documentserver/web-apps/apps/api/documents/api.js
|
|
||||||
jwtsecret:
|
|
||||||
type: text
|
|
||||||
name: jwtsecret
|
|
||||||
desc: JWT Secret
|
|
||||||
default: secret
|
|
||||||
pdf:
|
|
||||||
type: boolean
|
|
||||||
name: pdf
|
|
||||||
desc: Handle pdf documents
|
|
||||||
default: false
|
|
||||||
externalSign:
|
|
||||||
type: text
|
|
||||||
name: externalSign
|
|
||||||
desc: External Sign URL
|
|
||||||
default: ''
|
|
||||||
@@ -100,7 +100,7 @@ export async function apply(ctx: Context) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
submissions: tsdocs.flatMap((i) => (i.journal || []).map((j) => {
|
submissions: tsdocs.flatMap((i) => (i.journal || []).map((j) => {
|
||||||
const submit = new ObjectId(j.rid).getTimestamp().getTime();
|
const submit = new ObjectId(j.rid as string).getTimestamp().getTime();
|
||||||
const curStatus = status[j.status] || 'SYSTEM_ERROR';
|
const curStatus = status[j.status] || 'SYSTEM_ERROR';
|
||||||
return {
|
return {
|
||||||
problem_id: tdoc.pids.indexOf(j.pid),
|
problem_id: tdoc.pids.indexOf(j.pid),
|
||||||
|
|||||||
+123
-48
@@ -1,18 +1,88 @@
|
|||||||
|
import {
|
||||||
|
Ingest, PushOptions, QueryOptions,
|
||||||
|
Search, SuggestOptions,
|
||||||
|
} from 'sonic-channel';
|
||||||
import {
|
import {
|
||||||
Context, iterateAllProblem, iterateAllProblemInDomain,
|
Context, iterateAllProblem, iterateAllProblemInDomain,
|
||||||
Logger, ProblemModel, Schema, SystemModel,
|
Logger, ProblemModel, Schema, Service, SystemModel,
|
||||||
} from 'hydrooj';
|
} from 'hydrooj';
|
||||||
import { SonicService } from './service';
|
|
||||||
|
declare module 'hydrooj' {
|
||||||
|
interface Context {
|
||||||
|
sonic?: SonicService;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const logger = new Logger('sonic');
|
const logger = new Logger('sonic');
|
||||||
|
|
||||||
export const inject = { 'sonic': { required: false } };
|
function getHandler(type: string, that: any) {
|
||||||
export function apply(ctx: Context) {
|
return {
|
||||||
ctx.plugin(SonicService);
|
connected() {
|
||||||
|
logger.info(`Sonic Channel succeeded to connect to host (${type}).`);
|
||||||
|
},
|
||||||
|
disconnected() {
|
||||||
|
logger.error(`Sonic Channel is now disconnected (${type}).`);
|
||||||
|
},
|
||||||
|
timeout() {
|
||||||
|
logger.error(`Sonic Channel connection timed out (${type}).`);
|
||||||
|
},
|
||||||
|
retrying() {
|
||||||
|
logger.error(`Trying to reconnect to Sonic Channel (${type})...`);
|
||||||
|
},
|
||||||
|
error(error) {
|
||||||
|
that.error = error;
|
||||||
|
logger.error(`Sonic Channel failed to connect to host (${type}).`, error);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
ctx.provideModule('problemSearch', 'sonic', async (domainId, query, opts) => {
|
export class SonicService extends Service {
|
||||||
|
public search: Search;
|
||||||
|
public ingest: Ingest;
|
||||||
|
public error = '';
|
||||||
|
|
||||||
|
static Config = Schema.object({
|
||||||
|
host: Schema.string().default('::1'),
|
||||||
|
port: Schema.number().default(1491),
|
||||||
|
auth: Schema.string().default(''),
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor(ctx: Context, private config: ReturnType<typeof SonicService.Config>) {
|
||||||
|
super(ctx, 'sonic');
|
||||||
|
}
|
||||||
|
|
||||||
|
*[Context.init]() {
|
||||||
|
this.search = new Search(this.config);
|
||||||
|
this.ingest = new Ingest(this.config);
|
||||||
|
this.search.connect(getHandler('search', this));
|
||||||
|
yield () => this.search.close();
|
||||||
|
this.ingest.connect(getHandler('ingest', this));
|
||||||
|
yield () => this.ingest.close();
|
||||||
|
yield this.ctx.on('problem/add', async (doc, docId) => {
|
||||||
|
Promise.all([
|
||||||
|
this.push('problem', `${doc.domainId}@title`, `${doc.domainId}/${docId}`, `${doc.pid || ''} ${doc.title} ${doc.tag?.join(' ')}`),
|
||||||
|
this.push('problem', `${doc.domainId}@content`, `${doc.domainId}/${docId}`, doc.content.toString()),
|
||||||
|
]).catch((e) => logger.error(e));
|
||||||
|
});
|
||||||
|
yield this.ctx.on('problem/edit', async (pdoc) => {
|
||||||
|
const id = `${pdoc.domainId}/${pdoc.docId}`;
|
||||||
|
Promise.all([
|
||||||
|
this.flusho('problem', `${pdoc.domainId}@title`, id)
|
||||||
|
.then(() => this.push('problem', `${pdoc.domainId}@title`, id, `${pdoc.pid || ''} ${pdoc.title} ${pdoc.tag?.join(' ')}`)),
|
||||||
|
this.flusho('problem', `${pdoc.domainId}@content`, id)
|
||||||
|
.then(() => this.push('problem', `${pdoc.domainId}@content`, id, pdoc.content.toString())),
|
||||||
|
]).catch((e) => logger.error(e));
|
||||||
|
});
|
||||||
|
yield this.ctx.on('problem/del', async (domainId, docId) => {
|
||||||
|
const id = `${domainId}/${docId}`;
|
||||||
|
await Promise.all([
|
||||||
|
this.flusho('problem', `${domainId}@title`, id),
|
||||||
|
this.flusho('problem', `${domainId}@content`, id),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
yield this.ctx.provideModule('problemSearch', 'sonic', async (domainId, query, opts) => {
|
||||||
const limit = opts?.limit || SystemModel.get('pagination.problem');
|
const limit = opts?.limit || SystemModel.get('pagination.problem');
|
||||||
let hits = await ctx.sonic.query('problem', `${domainId}@title`, query, { limit });
|
let hits = await this.query('problem', `${domainId}@title`, query, { limit });
|
||||||
if (!opts.skip) {
|
if (!opts.skip) {
|
||||||
let pdoc = await ProblemModel.get(domainId, +query || query, ProblemModel.PROJECTION_LIST);
|
let pdoc = await ProblemModel.get(domainId, +query || query, ProblemModel.PROJECTION_LIST);
|
||||||
if (pdoc) {
|
if (pdoc) {
|
||||||
@@ -23,69 +93,74 @@ export function apply(ctx: Context) {
|
|||||||
if (pdoc) hits.unshift(`${pdoc.domainId}/${pdoc.docId}`);
|
if (pdoc) hits.unshift(`${pdoc.domainId}/${pdoc.docId}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (limit - hits.length > 0) hits.push(...await ctx.sonic.query('problem', `${domainId}@content`, query, { limit: limit - hits.length }));
|
if (limit - hits.length > 0) hits.push(...await this.query('problem', `${domainId}@content`, query, { limit: limit - hits.length }));
|
||||||
return {
|
return {
|
||||||
countRelation: hits.length >= limit ? 'gte' : 'eq',
|
countRelation: hits.length >= limit ? 'gte' : 'eq',
|
||||||
total: hits.length,
|
total: hits.length,
|
||||||
hits,
|
hits,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
yield this.ctx.addScript(
|
||||||
async function run({ domainId }, report) {
|
'ensureSonicSearch', 'Sonic problem search re-index',
|
||||||
if (domainId) await ctx.sonic.flushb('problem', domainId);
|
Schema.object({
|
||||||
else await ctx.sonic.flushc('problem');
|
domainId: Schema.string(),
|
||||||
|
}),
|
||||||
|
async ({ domainId }, report) => {
|
||||||
|
if (domainId) await this.flushb('problem', domainId);
|
||||||
|
else await this.flushc('problem');
|
||||||
let i = 0;
|
let i = 0;
|
||||||
const cb = async (pdoc) => {
|
const cb = async (pdoc) => {
|
||||||
i++;
|
i++;
|
||||||
if (!(i % 1000)) report({ message: `${i} problems indexed` });
|
if (!(i % 1000)) report({ message: `${i} problems indexed` });
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
pdoc.title && ctx.sonic.push(
|
pdoc.title && this.push(
|
||||||
'problem', `${pdoc.domainid}@title`, `${pdoc.domainId}/${pdoc.docId}`,
|
'problem', `${pdoc.domainid}@title`, `${pdoc.domainId}/${pdoc.docId}`,
|
||||||
`${pdoc.pid || ''} ${pdoc.title} ${pdoc.tag.join(' ')}`,
|
`${pdoc.pid || ''} ${pdoc.title} ${pdoc.tag.join(' ')}`,
|
||||||
),
|
),
|
||||||
pdoc.content.toString()
|
pdoc.content.toString()
|
||||||
&& ctx.sonic.push('problem', `${pdoc.domainId}@content`, `${pdoc.domainId}/${pdoc.docId}`, pdoc.content.toString()),
|
&& this.push('problem', `${pdoc.domainId}@content`, `${pdoc.domainId}/${pdoc.docId}`, pdoc.content.toString()),
|
||||||
]).catch((e) => console.log(`${pdoc.domainId}/${pdoc.docId}`, e));
|
]).catch((e) => console.log(`${pdoc.domainId}/${pdoc.docId}`, e));
|
||||||
};
|
};
|
||||||
if (domainId) await iterateAllProblemInDomain(domainId, ['title', 'content'], cb);
|
if (domainId) await iterateAllProblemInDomain(domainId, ['title', 'content'], cb);
|
||||||
else await iterateAllProblem(['title', 'content', 'tag'], cb);
|
else await iterateAllProblem(['title', 'content', 'tag'], cb);
|
||||||
return true;
|
return true;
|
||||||
}
|
},
|
||||||
|
|
||||||
ctx.on('problem/add', async (doc, docId) => {
|
|
||||||
Promise.all([
|
|
||||||
ctx.sonic.push('problem', `${doc.domainId}@title`, `${doc.domainId}/${docId}`, `${doc.pid || ''} ${doc.title} ${doc.tag?.join(' ')}`),
|
|
||||||
ctx.sonic.push('problem', `${doc.domainId}@content`, `${doc.domainId}/${docId}`, doc.content.toString()),
|
|
||||||
]).catch((e) => logger.error(e));
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.on('problem/edit', async (pdoc) => {
|
|
||||||
const id = `${pdoc.domainId}/${pdoc.docId}`;
|
|
||||||
Promise.all([
|
|
||||||
ctx.sonic.flusho('problem', `${pdoc.domainId}@title`, id)
|
|
||||||
.then(() => ctx.sonic.push('problem', `${pdoc.domainId}@title`, id, `${pdoc.pid || ''} ${pdoc.title} ${pdoc.tag?.join(' ')}`)),
|
|
||||||
ctx.sonic.flusho('problem', `${pdoc.domainId}@content`, id)
|
|
||||||
.then(() => ctx.sonic.push('problem', `${pdoc.domainId}@content`, id, pdoc.content.toString())),
|
|
||||||
]).catch((e) => logger.error(e));
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.on('problem/del', async (domainId, docId) => {
|
|
||||||
const id = `${domainId}/${docId}`;
|
|
||||||
await Promise.all([
|
|
||||||
ctx.sonic.flusho('problem', `${domainId}@title`, id),
|
|
||||||
ctx.sonic.flusho('problem', `${domainId}@content`, id),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.addScript(
|
|
||||||
'ensureSonicSearch', 'Sonic problem search re-index',
|
|
||||||
Schema.object({
|
|
||||||
domainId: Schema.string(),
|
|
||||||
}),
|
|
||||||
run,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.i18n.load('zh', {
|
this.ctx.i18n.load('zh', {
|
||||||
'Sonic problem search re-index': '重建题目搜索索引。',
|
'Sonic problem search re-index': '重建题目搜索索引。',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async query(collection: string, bucket: string, terms: string, options?: QueryOptions) {
|
||||||
|
return await this.search.query(collection, bucket, terms, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async suggest(collection: string, bucket: string, word: string, options?: SuggestOptions) {
|
||||||
|
return await this.search.suggest(collection, bucket, word, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async push(collection: string, bucket: string, object: string, text: string, options?: PushOptions) {
|
||||||
|
return await this.ingest.push(collection, bucket, object, text, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async pop(collection: string, bucket: string, object: string, text: string) {
|
||||||
|
return await this.ingest.pop(collection, bucket, object, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
async count(collection: string, bucket?: string, object?: string) {
|
||||||
|
return await this.ingest.count(collection, bucket, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
async flusho(collection: string, bucket: string, object: string) {
|
||||||
|
return await this.ingest.flusho(collection, bucket, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
async flushb(collection: string, bucket: string) {
|
||||||
|
return await this.ingest.flushb(collection, bucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
async flushc(collection: string) {
|
||||||
|
return await this.ingest.flushc(collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import {
|
|
||||||
Ingest, PushOptions, QueryOptions,
|
|
||||||
Search, SuggestOptions,
|
|
||||||
} from 'sonic-channel';
|
|
||||||
import {
|
|
||||||
Context, Logger, Service, SystemModel,
|
|
||||||
} from 'hydrooj';
|
|
||||||
|
|
||||||
declare module 'hydrooj' {
|
|
||||||
interface SystemKeys {
|
|
||||||
'sonic.host': string;
|
|
||||||
'sonic.port': number;
|
|
||||||
'sonic.auth': string;
|
|
||||||
}
|
|
||||||
interface Context {
|
|
||||||
sonic?: SonicService;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const logger = new Logger('sonic');
|
|
||||||
|
|
||||||
function getHandler(type: string, that: any) {
|
|
||||||
return {
|
|
||||||
connected() {
|
|
||||||
logger.info(`Sonic Channel succeeded to connect to host (${type}).`);
|
|
||||||
},
|
|
||||||
disconnected() {
|
|
||||||
logger.error(`Sonic Channel is now disconnected (${type}).`);
|
|
||||||
},
|
|
||||||
timeout() {
|
|
||||||
logger.error(`Sonic Channel connection timed out (${type}).`);
|
|
||||||
},
|
|
||||||
retrying() {
|
|
||||||
logger.error(`Trying to reconnect to Sonic Channel (${type})...`);
|
|
||||||
},
|
|
||||||
error(error) {
|
|
||||||
that.error = error;
|
|
||||||
logger.error(`Sonic Channel failed to connect to host (${type}).`, error);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SonicService extends Service {
|
|
||||||
public search: Search;
|
|
||||||
public ingest: Ingest;
|
|
||||||
public error = '';
|
|
||||||
|
|
||||||
constructor(ctx: Context) {
|
|
||||||
super(ctx, 'sonic');
|
|
||||||
}
|
|
||||||
|
|
||||||
async start() {
|
|
||||||
const [host, port, auth] = SystemModel.getMany(['sonic.host', 'sonic.port', 'sonic.auth']);
|
|
||||||
const cfg = {
|
|
||||||
host: host || '::1',
|
|
||||||
port: port || 1491,
|
|
||||||
auth: auth || '',
|
|
||||||
};
|
|
||||||
this.search = new Search(cfg);
|
|
||||||
this.ingest = new Ingest(cfg);
|
|
||||||
try {
|
|
||||||
this.search.connect(getHandler('search', this));
|
|
||||||
this.ingest.connect(getHandler('ingest', this));
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn('Sonic init fail. will retry later.');
|
|
||||||
this.error = e.toString();
|
|
||||||
setTimeout(() => this.start(), 10000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async query(collection: string, bucket: string, terms: string, options?: QueryOptions) {
|
|
||||||
return await this.search.query(collection, bucket, terms, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
async suggest(collection: string, bucket: string, word: string, options?: SuggestOptions) {
|
|
||||||
return await this.search.suggest(collection, bucket, word, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
async push(collection: string, bucket: string, object: string, text: string, options?: PushOptions) {
|
|
||||||
return await this.ingest.push(collection, bucket, object, text, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
async pop(collection: string, bucket: string, object: string, text: string) {
|
|
||||||
return await this.ingest.pop(collection, bucket, object, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
async count(collection: string, bucket?: string, object?: string) {
|
|
||||||
return await this.ingest.count(collection, bucket, object);
|
|
||||||
}
|
|
||||||
|
|
||||||
async flusho(collection: string, bucket: string, object: string) {
|
|
||||||
return await this.ingest.flusho(collection, bucket, object);
|
|
||||||
}
|
|
||||||
|
|
||||||
async flushb(collection: string, bucket: string) {
|
|
||||||
return await this.ingest.flushb(collection, bucket);
|
|
||||||
}
|
|
||||||
|
|
||||||
async flushc(collection: string) {
|
|
||||||
return await this.ingest.flushc(collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
async stop() {
|
|
||||||
await Promise.all([
|
|
||||||
this.search.close(),
|
|
||||||
this.ingest.close(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
host:
|
|
||||||
value: ::1
|
|
||||||
port:
|
|
||||||
type: number
|
|
||||||
value: 1491
|
|
||||||
auth:
|
|
||||||
type: text
|
|
||||||
value: SecretPassword
|
|
||||||
Reference in new issue
Block a user