feat: search with sonic

This commit is contained in:
undefined committed 2021-04-02 00:39:21 +08:00
1 parent 5d01c81607
commit 43c4bfd8e7
43 files changed
+512 -317

No files matched your search

+5 -11
View File
@@ -1,6 +1,4 @@
import nodejieba from 'nodejieba';
import { Pdoc } from 'hydrooj';
import * as document from 'hydrooj/dist/model/document';
import * as bus from 'hydrooj/dist/service/bus';
declare module 'hydrooj' {
@@ -9,15 +7,11 @@ declare module 'hydrooj' {
}
}
bus.on('document/add', async (doc) => {
if (doc.type !== document.TYPE_PROBLEM) return;
const pdoc = doc as Pdoc;
const jiebaHook = async (pdoc) => {
if (!pdoc.title) return;
const segments = nodejieba.cutForSearch(pdoc.title);
pdoc.search = segments.join(' ');
});
};
bus.on('document/set', async (domainId, docType, docId, $set) => {
if (docType !== document.TYPE_PROBLEM || !$set.title) return;
const segments = nodejieba.cutForSearch($set.title);
$set.search = segments.join(' ');
});
bus.on('problem/before-add', jiebaHook);
bus.on('problem/before-edit', jiebaHook);
+1 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/better-search",
"version": "1.0.2",
"version": "1.0.3",
"description": "Enhance Problem Search",
"main": "package.json",
"repository": "https://github.com/hydro-dev/Hydro.git",
@@ -8,13 +8,5 @@
"license": "AGPL-3.0-only",
"dependencies": {
"nodejieba": "^2.5.1"
},
"devDependencies": {},
"peerDependencies": {
"hydrooj": "^2.13.2"
},
"scripts": {
"build": "tsc",
"lint": "eslint . --ext ts --fix"
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@hydrooj/hydrojudge",
"bin": "bin/hydrojudge.js",
"version": "2.4.15",
"version": "2.4.16",
"os": [
"linux"
],
+2 -2
View File
@@ -44,10 +44,10 @@ async function postInit() {
if (system.get('hydrojudge.disable')) return;
const info = await sysinfo.get();
monitor.updateJudger(info);
monitor.updateJudge(info);
setInterval(async () => {
const [mid, inf] = await sysinfo.update();
monitor.updateJudger({ mid, ...inf });
monitor.updateJudge({ mid, ...inf });
}, 1200000);
async function processData(folder: string) {
+4
View File
@@ -206,6 +206,10 @@ export async function service(pending: string[], fail: string[]) {
}
}
}
for (const key in global.Hydro.service) {
const srv = global.Hydro.service[key];
if (!srv.started && srv.start) await srv.start();
}
await bus.serial('app/load/service');
}
+1 -1
View File
@@ -12,7 +12,7 @@ import { PERM, PRIV } from '../model/builtin';
import * as contest from '../model/contest';
import * as system from '../model/system';
import * as document from '../model/document';
import * as problem from '../model/problem';
import problem from '../model/problem';
import domain from '../model/domain';
import record from '../model/record';
import user from '../model/user';
+8 -8
View File
@@ -17,11 +17,11 @@ import {
import { gravatar } from '../lib/misc';
import paginate from '../lib/paginate';
import {
Route, Handler, Types, param,
Route, Handler, Types, param, query, post,
} from '../service/server';
class DomainRankHandler extends Handler {
@param('page', Types.PositiveInt, true)
@query('page', Types.PositiveInt, true)
async get(domainId: string, page = 1) {
const [dudocs, upcount, ucount] = await paginate(
domain.getMultiUserInDomain(domainId).sort({ rp: -1 }),
@@ -129,8 +129,8 @@ class DomainUserHandler extends ManageHandler {
};
}
@param('uid', Types.Int)
@param('role', Types.String)
@post('uid', Types.Int)
@post('role', Types.String)
async postSetUser(domainId: string, uid: number, role: string) {
await domain.setUserRole(domainId, uid, role);
this.back();
@@ -228,10 +228,10 @@ class DomainJoinApplicationsHandler extends ManageHandler {
this.response.template = 'domain_join_applications.html';
}
@param('method', Types.Range([domain.JOIN_METHOD_NONE, domain.JOIN_METHOD_ALL, domain.JOIN_METHOD_CODE]))
@param('role', Types.String, true)
@param('expire', Types.Int, true)
@param('invitationCode', Types.String, true)
@post('method', Types.Range([domain.JOIN_METHOD_NONE, domain.JOIN_METHOD_ALL, domain.JOIN_METHOD_CODE]))
@post('role', Types.String, true)
@post('expire', Types.Int, true)
@post('invitationCode', Types.String, true)
async post(domainId: string, method: number, role: string, expire: number, invitationCode = '') {
const r = await domain.getRoles(this.domain);
const roles = r.map((rl) => rl._id);
+1 -1
View File
@@ -18,7 +18,7 @@ import user from '../model/user';
import * as system from '../model/system';
import * as contest from '../model/contest';
import * as discussion from '../model/discussion';
import * as problem from '../model/problem';
import problem from '../model/problem';
import record from '../model/record';
import * as document from '../model/document';
import paginate from '../lib/paginate';
+1 -1
View File
@@ -10,7 +10,7 @@ import yaml from 'js-yaml';
import { ValidationError, RemoteOnlineJudgeError } from '../error';
import { Logger } from '../logger';
import type { ContentNode } from '../interface';
import * as problem from '../model/problem';
import problem from '../model/problem';
import { PERM } from '../model/builtin';
import {
Route, Handler, Types, post,
+3 -3
View File
@@ -4,7 +4,7 @@ import { sleep } from '../utils';
import { Logger } from '../logger';
import difficultyAlgorithm from '../lib/difficulty';
import record from '../model/record';
import * as problem from '../model/problem';
import problem from '../model/problem';
import * as builtin from '../model/builtin';
import * as contest from '../model/contest';
import domain from '../model/domain';
@@ -14,7 +14,7 @@ import {
Route, Handler, Connection, ConnectionHandler, post, Types,
} from '../service/server';
import storage from '../service/storage';
import { updateJudger } from '../service/monitor';
import { updateJudge } from '../service/monitor';
const logger = new Logger('judge');
@@ -145,7 +145,7 @@ class JudgeConnectionHandler extends ConnectionHandler {
this.processing = null;
await this.newTask();
} else if (msg.key === 'status') {
await updateJudger(msg.info);
await updateJudge(msg.info);
}
}
+5 -1
View File
@@ -3,6 +3,7 @@ import * as yaml from 'js-yaml';
import * as judge from './judge';
import { ValidationError } from '../error';
import * as check from '../check';
import { Logger } from '../logger';
import * as setting from '../model/setting';
import * as system from '../model/system';
import user from '../model/user';
@@ -17,6 +18,8 @@ import {
validate, isEmail, isUname, isPassword,
} from '../lib/validator';
const logger = new Logger('manage');
function set(key: string, value: any) {
if (setting.SYSTEM_SETTINGS_BY_KEY[key]) {
const s = setting.SYSTEM_SETTINGS_BY_KEY[key];
@@ -125,11 +128,12 @@ class SystemScriptHandler extends SystemHandler {
})
.catch((err: Error) => {
const time = new Date().getTime() - start;
logger.error(err);
judge.end({
domainId,
rid,
status: STATUS.STATUS_SYSTEM_ERROR,
message: `${err} \n${err.stack} `,
message: `${err.message} \n${err.stack} `,
judger: 1,
time,
memory: 0,
+2 -2
View File
@@ -32,9 +32,9 @@ class StatusHandler extends Handler {
class StatusUpdateHandler extends Handler {
async post(args) {
this.checkPriv(PRIV.PRIV_JUDGE);
args.type = 'judger';
args.type = 'judge';
return coll.updateOne(
{ mid: args.mid, type: 'judger' },
{ mid: args.mid, type: 'judge' },
{ $set: args },
{ upsert: true },
);
+11 -5
View File
@@ -12,7 +12,7 @@ import paginate from '../lib/paginate';
import { isTitle, isContent, isPid } from '../lib/validator';
import difficultyAlgorithm from '../lib/difficulty';
import * as system from '../model/system';
import * as problem from '../model/problem';
import problem from '../model/problem';
import record from '../model/record';
import domain from '../model/domain';
import user from '../model/user';
@@ -62,18 +62,26 @@ export class ProblemMainHandler extends ProblemHandler {
['Hydro', 'homepage'],
['problem_main', null],
];
const search = global.Hydro.lib.problemSearch;
let sort: number[];
if (q) {
query.$text = { $search: q };
path[1][1] = 'problem_main';
path.push([q, null, null, true]);
if (search) {
const result = await search(domainId, q);
query.docId = { $in: result };
sort = result;
} else query.$text = { $search: q };
}
if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) query.hidden = false;
await bus.serial('problem/list', query, this);
const [pdocs, ppcount, pcount] = await paginate(
// eslint-disable-next-line prefer-const
let [pdocs, ppcount, pcount] = await paginate(
problem.getMulti(domainId, query).sort({ pid: 1, docId: 1 }),
page,
system.get('pagination.problem'),
);
if (sort) pdocs = pdocs.sort((a, b) => sort.indexOf(a.docId) - sort.indexOf(b.docId));
if (q && +q) {
const pdoc = await problem.get(domainId, +q, this.user._id, problem.PROJECTION_LIST);
if (pdoc) pdocs.unshift(pdoc);
@@ -341,9 +349,7 @@ export class ProblemEditHandler extends ProblemManageHandler {
};
let pdoc = await problem.get(domainId, pid);
$update.difficulty = difficultyAlgorithm(pdoc.nSubmit, pdoc.nAccept);
await bus.serial('problem/before-edit', $update);
pdoc = await problem.edit(domainId, pdoc.docId, $update);
await bus.emit('problem/edit', pdoc);
this.response.redirect = this.url('problem_detail', { pid: newPid || pdoc.docId });
}
}
+4 -4
View File
@@ -3,7 +3,7 @@ import { PermissionError, RecordNotFoundError } from '../error';
import { Rdoc } from '../interface';
import { PERM, STATUS, PRIV } from '../model/builtin';
import * as system from '../model/system';
import * as problem from '../model/problem';
import problem from '../model/problem';
import record from '../model/record';
import * as contest from '../model/contest';
import user from '../model/user';
@@ -49,7 +49,7 @@ class RecordListHandler extends RecordHandler {
user.getList(domainId, rdocs.map((rdoc) => rdoc.uid)),
canViewProblem
? problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid), canViewProblemHidden, false)
: Object.fromEntries([rdocs.map((rdoc) => [rdoc.pid, problem.Pdoc.create(rdoc.pid, rdoc.pid)])]),
: Object.fromEntries([rdocs.map((rdoc) => [rdoc.pid, problem.create(rdoc.pid, rdoc.pid)])]),
]);
const path = [
['Hydro', 'homepage'],
@@ -88,11 +88,11 @@ class RecordDetailHandler extends RecordHandler {
user.getById(domainId, rdoc.uid),
]);
if (!(pdoc && this.user.hasPerm(PERM.PERM_VIEW_PROBLEM))) {
pdoc = problem.Pdoc.create(pdoc?.docId || 0, pdoc?.pid || '*');
pdoc = problem.create(pdoc?.docId || 0, pdoc?.pid || '*');
}
if (!rdoc.contest && pdoc.hidden && pdoc.owner !== this.user._id) {
if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) {
pdoc = problem.Pdoc.create(pdoc.docId, pdoc.pid);
pdoc = problem.create(pdoc.docId, pdoc.pid);
}
}
this.response.body = {
+4 -3
View File
@@ -6,7 +6,7 @@ import { Logger } from '../logger';
import { ProblemAdd } from '../lib/ui';
import { PERM } from '../model/builtin';
import token from '../model/token';
import * as problem from '../model/problem';
import problem from '../model/problem';
import * as system from '../model/system';
import {
Handler, Types, Route, post,
@@ -24,9 +24,10 @@ export class ProblemSendHandler extends Handler {
@post('target', Types.String)
@post('pids', Types.Array)
async postSend(domainId: string, target: any, _pids: (number | string)[]) {
async postSend(domainId: string, _target: string, _pids: (number | string)[]) {
this.checkPerm(PERM.PERM_VIEW_PROBLEM);
target = target.split('@');
const target = _target.split('@');
if (target[1].endsWith('/')) target[1] = target[1].substr(0, target[1].length - 1);
const getHidden = this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
const getData = this.user.hasPerm(PERM.PERM_READ_PROBLEM_DATA);
const pdocs = await problem.getList(domainId, _pids, true, true, ['docId', 'owner', 'hidden']);
+1 -1
View File
@@ -6,7 +6,7 @@ import { isTitle, isContent, isDescription } from '../lib/validator';
import paginate from '../lib/paginate';
import * as system from '../model/system';
import { PERM, PRIV } from '../model/builtin';
import * as problem from '../model/problem';
import problem from '../model/problem';
import * as builtin from '../model/builtin';
import * as training from '../model/training';
import user from '../model/user';
+3 -3
View File
@@ -11,7 +11,7 @@ import user from '../model/user';
import oauth from '../model/oauth';
import token from '../model/token';
import record from '../model/record';
import * as problem from '../model/problem';
import problem, { Pdoc } from '../model/problem';
import task from '../model/task';
import * as system from '../model/system';
import { PERM, PRIV } from '../model/builtin';
@@ -183,7 +183,7 @@ class UserDetailHandler extends Handler {
1,
100,
)
: [[], 0, 0] as [problem.Pdoc[], number, number],
: [[], 0, 0] as [Pdoc[], number, number],
]);
const pdict = this.user.hasPerm(PERM.PERM_VIEW_PROBLEM)
? await problem.getList(
@@ -191,7 +191,7 @@ class UserDetailHandler extends Handler {
this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN), false,
)
: Object.fromEntries(rdocs.map(
(rdoc) => [rdoc.pid, problem.Pdoc.create(undefined, rdoc.pid.toString())],
(rdoc) => [rdoc.pid, problem.create(undefined, rdoc.pid.toString())],
));
// Remove sensitive data
if (!isSelfProfile && sdoc) {
+17 -10
View File
@@ -458,6 +458,13 @@ export interface HydroFileSystem {
export type PathComponent = [string, string, Dictionary<any>?, boolean?];
export interface BaseService {
started: boolean;
error?: Error | string;
start: Function;
stop?: Function;
}
export interface Collections {
'blacklist': Bdoc,
'contest': Tdoc,
@@ -483,25 +490,25 @@ export interface Collections {
}
export interface Model {
blacklist: typeof import('./model/blacklist'),
blacklist: typeof import('./model/blacklist').default,
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'),
message: typeof import('./model/message'),
domain: typeof import('./model/domain').default,
message: typeof import('./model/message').default,
opcount: typeof import('./model/opcount'),
problem: typeof import('./model/problem'),
record: typeof import('./model/record'),
problem: typeof import('./model/problem').default,
record: typeof import('./model/record').default,
setting: typeof import('./model/setting'),
solution: typeof import('./model/solution'),
solution: typeof import('./model/solution').default,
system: typeof import('./model/system'),
task: typeof import('./model/task'),
task: typeof import('./model/task').default,
oplog: typeof import('./model/oplog'),
token: typeof import('./model/token'),
token: typeof import('./model/token').default,
training: typeof import('./model/training'),
user: typeof import('./model/user'),
oauth: typeof import('./model/oauth'),
user: typeof import('./model/user').default,
oauth: typeof import('./model/oauth').default,
}
export interface Service {
+1 -1
View File
@@ -32,5 +32,5 @@ async function ensureIndexes() {
}
bus.once('app/started', ensureIndexes);
export = BlackListModel;
export default BlackListModel;
global.Hydro.model.blacklist = BlackListModel;
+1 -1
View File
@@ -1,6 +1,6 @@
import { ObjectID, FilterQuery } from 'mongodb';
import user from './user';
import * as problem from './problem';
import problem from './problem';
import * as document from './document';
import { PERM } from './builtin';
import {
+1 -1
View File
@@ -1,5 +1,5 @@
import { FilterQuery, ObjectID } from 'mongodb';
import * as problem from './problem';
import problem from './problem';
import * as contest from './contest';
import * as training from './training';
import * as document from './document';
+1 -1
View File
@@ -235,5 +235,5 @@ class DomainModel {
}
}
export = DomainModel;
export default DomainModel;
global.Hydro.model.domain = DomainModel;
+1 -1
View File
@@ -76,5 +76,5 @@ function ensureIndexes() {
}
bus.once('app/started', ensureIndexes);
export = MessageModel;
export default MessageModel;
global.Hydro.model.message = MessageModel;
+1 -1
View File
@@ -25,5 +25,5 @@ class OauthModel {
}
}
export = OauthModel;
export default OauthModel;
global.Hydro.model.oauth = OauthModel;
+207 -221
View File
@@ -13,245 +13,231 @@ import storage from '../service/storage';
import * as bus from '../service/bus';
export interface Pdoc extends Document { }
export namespace Pdoc {
export type Field = keyof Pdoc;
export const fields: Field[] = [];
type Getter = (docId?: number, pid?: string) => Partial<Pdoc>;
const getters: Getter[] = [];
export function extend(getter: Getter) {
export type Field = keyof Pdoc;
const fields: Field[] = [];
export type Getter = (docId?: number, pid?: string) => Partial<Pdoc>;
const getters: Getter[] = [];
export class ProblemModel {
static PROJECTION_LIST: Field[] = [
'_id', 'domainId', 'docType', 'docId', 'pid',
'owner', 'title', 'nSubmit', 'nAccept', 'difficulty',
'tag', 'hidden',
];
static PROJECTION_PUBLIC: Field[] = [
...ProblemModel.PROJECTION_LIST,
'content', 'html', 'data', 'config', 'acMsg',
'additional_file',
];
static extend(getter: Getter) {
getters.push(getter);
fields.push(...Object.keys(getter(0, '0')) as any);
}
extend((docId, pid) => ({
_id: new ObjectID(),
domainId: 'system',
docType: document.TYPE_PROBLEM,
docId: docId || -1,
pid: pid || docId.toString(),
owner: 1,
title: '*',
content: '',
html: false,
nSubmit: 0,
nAccept: 0,
tag: [],
data: [],
additional_file: [],
hidden: true,
config: {},
acMsg: '',
difficulty: 0,
}));
export function create(docId?: number, pid?: string) {
static create(docId?: number, pid?: string) {
const result = {} as Pdoc;
for (const getter of getters) {
Object.assign(result, getter(docId, pid));
}
return result;
}
}
export const PROJECTION_LIST: Pdoc.Field[] = [
'_id', 'domainId', 'docType', 'docId', 'pid',
'owner', 'title', 'nSubmit', 'nAccept', 'difficulty',
'tag', 'hidden',
];
export const PROJECTION_PUBLIC: Pdoc.Field[] = [
...PROJECTION_LIST,
'content', 'html', 'data', 'config', 'acMsg',
'additional_file',
];
export async function add(
domainId: string, pid: string = null, title: string, content: Content, owner: number,
tag: string[] = [], hidden = false,
) {
const pidCounter = await domain.inc(domainId, 'pidCounter', 1);
const args: Partial<Pdoc> = {
title, tag, hidden, nSubmit: 0, nAccept: 0,
};
if (pid) args.pid = pid;
return await document.add(domainId, content, owner, document.TYPE_PROBLEM, pidCounter, null, null, args);
}
export async function get(
domainId: string, pid: string | number,
uid: number = null, projection: Projection<Pdoc> = PROJECTION_PUBLIC,
): Promise<Pdoc> {
if (typeof pid !== 'number') {
if (Number.isSafeInteger(parseInt(pid, 10))) pid = parseInt(pid, 10);
static async add(
domainId: string, pid: string = null, title: string, content: Content, owner: number,
tag: string[] = [], hidden = false,
) {
const pidCounter = await domain.inc(domainId, 'pidCounter', 1);
const args: Partial<Pdoc> = {
title, tag, hidden, nSubmit: 0, nAccept: 0,
};
if (pid) args.pid = pid;
await bus.serial('problem/before-add', args);
const result = await document.add(domainId, content, owner, document.TYPE_PROBLEM, pidCounter, null, null, args);
await bus.emit('problem/add', args, result);
return result;
}
const pdoc = typeof pid === 'number'
? await document.get(domainId, document.TYPE_PROBLEM, pid, projection)
: (await document.getMulti(domainId, document.TYPE_PROBLEM, { pid }).toArray())[0];
if (!pdoc) return null;
if (uid) {
pdoc.psdoc = await document.getStatus(domainId, document.TYPE_PROBLEM, pdoc.docId, uid);
static async get(
domainId: string, pid: string | number,
uid: number = null, projection: Projection<Pdoc> = ProblemModel.PROJECTION_PUBLIC,
): Promise<Pdoc> {
if (typeof pid !== 'number') {
if (Number.isSafeInteger(parseInt(pid, 10))) pid = parseInt(pid, 10);
}
const pdoc = typeof pid === 'number'
? await document.get(domainId, document.TYPE_PROBLEM, pid, projection)
: (await document.getMulti(domainId, document.TYPE_PROBLEM, { pid }).toArray())[0];
if (!pdoc) return null;
if (uid) {
pdoc.psdoc = await document.getStatus(domainId, document.TYPE_PROBLEM, pdoc.docId, uid);
}
return pdoc;
}
return pdoc;
}
export function getMulti(domainId: string, query: FilterQuery<Pdoc>, projection = PROJECTION_LIST) {
return document.getMulti(domainId, document.TYPE_PROBLEM, query, projection);
}
export function getMultiStatus(domainId: string, query: FilterQuery<Pdoc>) {
return document.getMultiStatus(domainId, document.TYPE_PROBLEM, query);
}
export function edit(domainId: string, _id: number, $set: Partial<Pdoc>): Promise<Pdoc> {
const delpid = $set.pid === '';
if (delpid) delete $set.pid;
return document.set(domainId, document.TYPE_PROBLEM, _id, $set, delpid ? { pid: '' } : undefined);
}
export function push<T extends ArrayKeys<Pdoc>>(domainId: string, _id: number, key: ArrayKeys<Pdoc>, value: Pdoc[T][0]) {
return document.push(domainId, document.TYPE_PROBLEM, _id, key, value);
}
export function pull<T extends ArrayKeys<Pdoc>>(domainId: string, pid: number, key: ArrayKeys<Pdoc>, values: Pdoc[T][0][]) {
return document.deleteSub(domainId, document.TYPE_PROBLEM, pid, key, values);
}
export function inc(domainId: string, _id: number, field: NumberKeys<Pdoc>, n: number): Promise<Pdoc> {
return document.inc(domainId, document.TYPE_PROBLEM, _id, field, n);
}
export function count(domainId: string, query: FilterQuery<Pdoc>) {
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 }),
storage.list(`problem/${domainId}/${docId}/`).then((items) => storage.del(items.map((item) => item.prefix + item.name))),
bus.parallel('problem/delete', domainId, docId),
]);
}
export async function addTestdata(domainId: string, pid: number, name: string, f: Readable | Buffer | string) {
if (!name) throw new ValidationError('name');
const [[, fileinfo]] = await Promise.all([
document.getSub(domainId, document.TYPE_PROBLEM, pid, 'data', name),
storage.put(`problem/${domainId}/${pid}/testdata/${name}`, f),
]);
const meta = await storage.getMeta(`problem/${domainId}/${pid}/testdata/${name}`);
if (!meta) throw new Error('Upload failed');
const payload = { name, ...pick(meta, ['size', 'lastModified', 'etag']) };
if (!fileinfo) await push(domainId, pid, 'data', { _id: name, ...payload });
else await document.setSub(domainId, document.TYPE_PROBLEM, pid, 'data', name, payload);
}
export async function delTestdata(domainId: string, pid: number, name: string | string[]) {
const names = (name instanceof Array) ? name : [name];
await storage.del(names.map((t) => `problem/${domainId}/${pid}/testdata/${t}`));
await pull(domainId, pid, 'data', names);
}
export async function addAdditionalFile(domainId: string, pid: number, name: string, f: Readable | Buffer | string) {
const [[, fileinfo]] = await Promise.all([
document.getSub(domainId, document.TYPE_PROBLEM, pid, 'additional_file', name),
storage.put(`problem/${domainId}/${pid}/additional_file/${name}`, f),
]);
const meta = await storage.getMeta(`problem/${domainId}/${pid}/additional_file/${name}`);
const payload = { name, ...pick(meta, ['size', 'lastModified', 'etag']) };
if (!fileinfo) await push(domainId, pid, 'additional_file', { _id: name, ...payload });
else await document.setSub(domainId, document.TYPE_PROBLEM, pid, 'additional_file', name, payload);
}
export async function delAdditionalFile(domainId: string, pid: number, name: string | string[]) {
const names = (name instanceof Array) ? name : [name];
await storage.del(names.map((t) => `problem/${domainId}/${pid}/additional_file/${t}`));
await pull(domainId, pid, 'additional_file', names);
}
export async function random(domainId: string, query: FilterQuery<Pdoc>): Promise<string | null> {
const cursor = document.getMulti(domainId, document.TYPE_PROBLEM, query);
const pcount = await cursor.count();
if (pcount) {
const pdoc = await cursor.skip(Math.floor(Math.random() * pcount)).limit(1).toArray();
return pdoc[0].pid;
} return null;
}
export async function getList(
domainId: string, pids: Array<number | string>,
getHidden = false, doThrow = true, projection = PROJECTION_PUBLIC,
): Promise<Pdict> {
pids = Array.from(new Set(pids)).map((i) => (+i ? +i : i));
const r = {};
const l = {};
const q: any = { $or: [{ docId: { $in: pids } }, { pid: { $in: pids } }] };
if (!getHidden) q.hidden = false;
const pdocs = await document.getMulti(domainId, document.TYPE_PROBLEM, q)
.project(buildProjection(projection)).toArray();
for (const pdoc of pdocs) {
r[pdoc.docId] = pdoc;
l[pdoc.pid] = pdoc;
static getMulti(domainId: string, query: FilterQuery<Pdoc>, projection = ProblemModel.PROJECTION_LIST) {
return document.getMulti(domainId, document.TYPE_PROBLEM, query, projection);
}
// TODO enhance
if (pdocs.length !== pids.length) {
for (const pid of pids) {
if (!(r[pid] || l[pid])) {
if (doThrow) throw new ProblemNotFoundError(domainId, pid);
else r[pid] = Pdoc.create(undefined, pid.toString());
static getMultiStatus(domainId: string, query: FilterQuery<Pdoc>) {
return document.getMultiStatus(domainId, document.TYPE_PROBLEM, query);
}
static async edit(domainId: string, _id: number, $set: Partial<Pdoc>): Promise<Pdoc> {
const delpid = $set.pid === '';
if (delpid) delete $set.pid;
await bus.serial('problem/before-edit', $set);
const result = await document.set(domainId, document.TYPE_PROBLEM, _id, $set, delpid ? { pid: '' } : undefined);
await bus.emit('problem/edit', result);
return result;
}
static push<T extends ArrayKeys<Pdoc>>(domainId: string, _id: number, key: ArrayKeys<Pdoc>, value: Pdoc[T][0]) {
return document.push(domainId, document.TYPE_PROBLEM, _id, key, value);
}
static pull<T extends ArrayKeys<Pdoc>>(domainId: string, pid: number, key: ArrayKeys<Pdoc>, values: Pdoc[T][0][]) {
return document.deleteSub(domainId, document.TYPE_PROBLEM, pid, key, values);
}
static inc(domainId: string, _id: number, field: NumberKeys<Pdoc>, n: number): Promise<Pdoc> {
return document.inc(domainId, document.TYPE_PROBLEM, _id, field, n);
}
static count(domainId: string, query: FilterQuery<Pdoc>) {
return document.count(domainId, document.TYPE_PROBLEM, query);
}
static async del(domainId: string, docId: number) {
await bus.serial('problem/before-del', domainId, docId);
const res = await Promise.all([
document.deleteOne(domainId, document.TYPE_PROBLEM, docId),
document.deleteMultiStatus(domainId, document.TYPE_PROBLEM, { docId }),
storage.list(`problem/${domainId}/${docId}/`).then((items) => storage.del(items.map((item) => item.prefix + item.name))),
bus.parallel('problem/delete', domainId, docId),
]);
await bus.emit('problem/del', domainId, docId);
return res;
}
static async addTestdata(domainId: string, pid: number, name: string, f: Readable | Buffer | string) {
if (!name) throw new ValidationError('name');
const [[, fileinfo]] = await Promise.all([
document.getSub(domainId, document.TYPE_PROBLEM, pid, 'data', name),
storage.put(`problem/${domainId}/${pid}/testdata/${name}`, f),
]);
const meta = await storage.getMeta(`problem/${domainId}/${pid}/testdata/${name}`);
if (!meta) throw new Error('Upload failed');
const payload = { name, ...pick(meta, ['size', 'lastModified', 'etag']) };
if (!fileinfo) await ProblemModel.push(domainId, pid, 'data', { _id: name, ...payload });
else await document.setSub(domainId, document.TYPE_PROBLEM, pid, 'data', name, payload);
}
static async delTestdata(domainId: string, pid: number, name: string | string[]) {
const names = (name instanceof Array) ? name : [name];
await storage.del(names.map((t) => `problem/${domainId}/${pid}/testdata/${t}`));
await ProblemModel.pull(domainId, pid, 'data', names);
}
static async addAdditionalFile(domainId: string, pid: number, name: string, f: Readable | Buffer | string) {
const [[, fileinfo]] = await Promise.all([
document.getSub(domainId, document.TYPE_PROBLEM, pid, 'additional_file', name),
storage.put(`problem/${domainId}/${pid}/additional_file/${name}`, f),
]);
const meta = await storage.getMeta(`problem/${domainId}/${pid}/additional_file/${name}`);
const payload = { name, ...pick(meta, ['size', 'lastModified', 'etag']) };
if (!fileinfo) await ProblemModel.push(domainId, pid, 'additional_file', { _id: name, ...payload });
else await document.setSub(domainId, document.TYPE_PROBLEM, pid, 'additional_file', name, payload);
}
static async delAdditionalFile(domainId: string, pid: number, name: string | string[]) {
const names = (name instanceof Array) ? name : [name];
await storage.del(names.map((t) => `problem/${domainId}/${pid}/additional_file/${t}`));
await ProblemModel.pull(domainId, pid, 'additional_file', names);
}
static async random(domainId: string, query: FilterQuery<Pdoc>): Promise<string | null> {
const cursor = document.getMulti(domainId, document.TYPE_PROBLEM, query);
const pcount = await cursor.count();
if (pcount) {
const pdoc = await cursor.skip(Math.floor(Math.random() * pcount)).limit(1).toArray();
return pdoc[0].pid;
} return null;
}
static async getList(
domainId: string, pids: Array<number | string>,
getHidden = false, doThrow = true, projection = ProblemModel.PROJECTION_PUBLIC,
): Promise<Pdict> {
pids = Array.from(new Set(pids)).map((i) => (+i ? +i : i));
const r = {};
const l = {};
const q: any = { $or: [{ docId: { $in: pids } }, { pid: { $in: pids } }] };
if (!getHidden) q.hidden = false;
const pdocs = await document.getMulti(domainId, document.TYPE_PROBLEM, q)
.project(buildProjection(projection)).toArray();
for (const pdoc of pdocs) {
r[pdoc.docId] = pdoc;
l[pdoc.pid] = pdoc;
}
// TODO enhance
if (pdocs.length !== pids.length) {
for (const pid of pids) {
if (!(r[pid] || l[pid])) {
if (doThrow) throw new ProblemNotFoundError(domainId, pid);
else r[pid] = ProblemModel.create(undefined, pid.toString());
}
}
}
return Object.assign(r, l);
}
static async getListStatus(domainId: string, uid: number, pids: number[]) {
const psdocs = await ProblemModel.getMultiStatus(
domainId, { uid, docId: { $in: Array.from(new Set(pids)) } },
).toArray();
const r: Dictionary<ProblemStatusDoc> = {};
for (const psdoc of psdocs) r[psdoc.docId] = psdoc;
return r;
}
static async updateStatus(
domainId: string, pid: number, uid: number,
rid: ObjectID, status: number,
) {
const res = await document.setIfNotStatus(
domainId, document.TYPE_PROBLEM, pid, uid,
'status', status, STATUS.STATUS_ACCEPTED, { rid },
);
return !!res;
}
static setStar(domainId: string, pid: number, uid: number, star: boolean) {
return document.setStatus(domainId, document.TYPE_PROBLEM, pid, uid, { star });
}
return Object.assign(r, l);
}
export async function getListStatus(domainId: string, uid: number, pids: number[]) {
const psdocs = await getMultiStatus(
domainId, { uid, docId: { $in: Array.from(new Set(pids)) } },
).toArray();
const r: Dictionary<ProblemStatusDoc> = {};
for (const psdoc of psdocs) r[psdoc.docId] = psdoc;
return r;
}
ProblemModel.extend((docId, pid) => ({
_id: new ObjectID(),
domainId: 'system',
docType: document.TYPE_PROBLEM,
docId: docId || -1,
pid: pid || docId.toString(),
owner: 1,
title: '*',
content: '',
html: false,
nSubmit: 0,
nAccept: 0,
tag: [],
data: [],
additional_file: [],
hidden: true,
config: {},
acMsg: '',
difficulty: 0,
}));
export async function updateStatus(
domainId: string, pid: number, uid: number,
rid: ObjectID, status: number,
) {
const res = await document.setIfNotStatus(
domainId, document.TYPE_PROBLEM, pid, uid,
'status', status, STATUS.STATUS_ACCEPTED, { rid },
);
return !!res;
}
export function setStar(domainId: string, pid: number, uid: number, star: boolean) {
return document.setStatus(domainId, document.TYPE_PROBLEM, pid, uid, { star });
}
global.Hydro.model.problem = {
Pdoc,
PROJECTION_LIST,
PROJECTION_PUBLIC,
add,
inc,
get,
del,
edit,
count,
push,
pull,
random,
addTestdata,
delTestdata,
addAdditionalFile,
delAdditionalFile,
getMulti,
getList,
getListStatus,
getMultiStatus,
setStar,
updateStatus,
};
global.Hydro.model.problem = ProblemModel;
export default ProblemModel;
+2 -2
View File
@@ -5,7 +5,7 @@ import { Dictionary } from 'lodash';
import moment from 'moment';
import { STATUS } from './builtin';
import task from './task';
import * as problem from './problem';
import problem from './problem';
import { Rdoc, ContestInfo, ProblemConfig } from '../interface';
import { ArgMethod, Time } from '../utils';
import * as bus from '../service/bus';
@@ -166,5 +166,5 @@ class RecordModel {
}
bus.on('problem/delete', (domainId, docId) => RecordModel.coll.deleteMany({ domainId, pid: docId }));
export = RecordModel;
export default RecordModel;
global.Hydro.model.record = RecordModel;
+1 -1
View File
@@ -92,5 +92,5 @@ bus.on('problem/delete', async (domainId, docId) => {
]);
});
export = SolutionModel;
export default SolutionModel;
global.Hydro.model.solution = SolutionModel;
+1 -1
View File
@@ -97,5 +97,5 @@ class TaskModel {
};
}
export = TaskModel;
export default TaskModel;
global.Hydro.model.task = TaskModel;
+1 -1
View File
@@ -101,5 +101,5 @@ function ensureIndexes() {
}
bus.once('app/started', ensureIndexes);
export = TokenModel;
export default TokenModel;
global.Hydro.model.token = TokenModel;
+1 -1
View File
@@ -271,5 +271,5 @@ function ensureIndexes() {
}
bus.once('app/started', ensureIndexes);
export = UserModel;
export default UserModel;
global.Hydro.model.user = UserModel;
+1 -1
View File
@@ -4,7 +4,7 @@ import { ObjectID, FilterQuery } from 'mongodb';
import { Tdoc, Pdoc, Udoc } from '../interface';
import domain from '../model/domain';
import * as contest from '../model/contest';
import * as problem from '../model/problem';
import problem from '../model/problem';
import record from '../model/record';
import { STATUS } from '../model/builtin';
import rating from '../lib/rating';
+5 -2
View File
@@ -45,7 +45,7 @@ export interface EventMap extends Record<string, any> {
'system/setting': (args: Record<string, any>) => VoidReturn
'monitor/update': (type: 'server' | 'judger', $set: any) => VoidReturn
'monitor/update': (type: 'server' | 'judge', $set: any) => VoidReturn
'user/message': (uid: number, mdoc: Mdoc, udoc: User) => void
'user/get': (udoc: User) => void
@@ -76,10 +76,13 @@ export interface EventMap extends Record<string, any> {
ip: string | null, highlight: boolean, pin: boolean
) => VoidReturn
'problem/before-add': (doc: Partial<Pdoc>) => VoidReturn
'problem/add': (doc: Partial<Pdoc>, docId: number) => VoidReturn
'problem/before-edit': (doc: Partial<Pdoc>) => VoidReturn
'problem/edit': (doc: Pdoc) => VoidReturn
'problem/before-del': (domainId: string, docId: number) => VoidReturn
'problem/del': (domainId: string, docId: number) => VoidReturn
'problem/list': (query: FilterQuery<Pdoc>, handler: any) => VoidReturn
'problem/setting': (update: Partial<Pdoc>, handler: any) => VoidReturn
'problem/get': (doc: Pdoc, handler: any) => VoidReturn
'problem/delete': (domainId: string, docId: number) => VoidReturn
+5 -3
View File
@@ -1,6 +1,6 @@
import { Collection, Db, MongoClient } from 'mongodb';
import * as bus from './bus';
import { Collections } from '../interface';
import { Collections, BaseService } from '../interface';
interface MongoConfig {
protocol?: string,
@@ -13,11 +13,12 @@ interface MongoConfig {
prefix?: string,
}
class MongoService {
class MongoService implements BaseService {
public client: MongoClient;
public client2: MongoClient;
public db: Db;
public db2: Db;
public started = false;
private opts: MongoConfig;
static buildUrl(opts: MongoConfig) {
@@ -35,7 +36,8 @@ class MongoService {
this.db = this.client.db(opts.name);
this.client2 = await MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: true });
this.db2 = this.client2.db(opts.name);
bus.parallel('database/connect', this.db);
await bus.parallel('database/connect', this.db);
this.started = true;
}
public collection<K extends keyof Collections>(c: K): Collection<Collections[K]> {
+4 -4
View File
@@ -70,11 +70,11 @@ export async function update() {
global.Hydro.stat.reqCount = 0;
}
export async function updateJudger(args) {
export async function updateJudge(args) {
const $set = { ...args, updateAt: new Date() };
await bus.serial('monitor/update', 'judger', $set);
await bus.serial('monitor/update', 'judge', $set);
return await coll.updateOne(
{ mid: args.mid, type: 'judger' },
{ mid: args.mid, type: 'judge' },
{ $set },
{ upsert: true },
);
@@ -93,4 +93,4 @@ if (cluster.isMaster) {
});
}
global.Hydro.service.monitor = { feedback, update, updateJudger };
global.Hydro.service.monitor = { feedback, update, updateJudge };
+7
View File
@@ -210,6 +210,7 @@ type DescriptorBuilder =
& ((name: string, ...args: Array<Type | boolean | Validator | Converter>) => MethodDecorator);
export const get: DescriptorBuilder = (name, ...args) => _descriptor(_buildParam(name, 'get', ...args));
export const query: 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));
@@ -319,6 +320,7 @@ export class HandlerCommon {
url(name: string, kwargs: any = {}) {
let res = '#';
const args: any = {};
// eslint-disable-next-line @typescript-eslint/no-shadow
const query: any = {};
for (const key in kwargs) {
if (kwargs[key] instanceof ObjectID) args[key] = kwargs[key].toHexString();
@@ -803,12 +805,16 @@ export function Connection(
sock.installHandlers(server);
}
let started = false;
// TODO use postInit?
export function start() {
if (started) return;
const port = system.get('server.port');
app.use(router.routes()).use(router.allowedMethods());
server.listen(argv.port || port);
logger.success('Server listening at: %d', argv.port || port);
started = true;
}
global.Hydro.service.server = {
@@ -818,6 +824,7 @@ global.Hydro.service.server = {
router,
UiContextBase,
get,
query,
post,
route,
param,
+6 -6
View File
@@ -11,7 +11,7 @@ import { streamToBuffer } from './utils';
import gridfs from './service/gridfs';
import storage from './service/storage';
import db from './service/db';
import * as problem from './model/problem';
import problem, { Field, Pdoc } from './model/problem';
import domain from './model/domain';
import * as document from './model/document';
import * as system from './model/system';
@@ -25,13 +25,13 @@ export async function iterateAllDomain(cb: (ddoc: DomainDoc, current?: number, t
for (const i in ddocs) await cb(ddocs[i], +i, ddocs.length);
}
interface PartialPdoc extends problem.Pdoc {
interface PartialPdoc extends Pdoc {
[key: string]: any,
}
export async function iterateAllProblemInDomain(
domainId: string,
fields: (problem.Pdoc.Field | string)[],
fields: (Field | string)[],
cb: (pdoc: PartialPdoc, current?: number, total?: number) => Promise<any>,
) {
if (!fields.includes('domainId')) fields.push('domainId');
@@ -48,7 +48,7 @@ export async function iterateAllProblemInDomain(
}
export async function iterateAllProblem(
fields: (problem.Pdoc.Field | string)[],
fields: (Field | string)[],
cb: (pdoc: PartialPdoc, current?: number, total?: number) => Promise<any>,
) {
await iterateAllDomain(async (d) => {
@@ -171,7 +171,7 @@ const scripts: UpgradeScript[] = [
logger.info('%s/%s', pdoc.domainId, pdoc.docId);
const [data, additional_file] = await Promise.all([
storage.list(`problem/${pdoc.domainId}/${pdoc.docId}/testdata/`),
storage.list(`problem/${pdoc.domainId}/${pdoc.docId}/additional_files/`),
storage.list(`problem/${pdoc.domainId}/${pdoc.docId}/additional_file/`),
]);
await problem.edit(pdoc.domainId, pdoc.docId, { data, additional_file });
if (!pdoc.config) return;
@@ -187,7 +187,7 @@ const scripts: UpgradeScript[] = [
logger.info('%s/%s', pdoc.domainId, pdoc.docId);
const [data, additional_file] = await Promise.all([
storage.list(`problem/${pdoc.domainId}/${pdoc.docId}/testdata/`),
storage.list(`problem/${pdoc.domainId}/${pdoc.docId}/additional_files/`),
storage.list(`problem/${pdoc.domainId}/${pdoc.docId}/additional_file/`),
]) as any;
for (let i = 0; i < data.length; i++) {
data[i]._id = data[i].name;
+1 -1
View File
@@ -8,7 +8,7 @@ import type { ContentNode, ProblemConfig } from 'hydrooj';
import { Route, Handler } from 'hydrooj/dist/service/server';
import { BadRequestError, ValidationError } from 'hydrooj/dist/error';
import { ProblemAdd } from 'hydrooj/dist/lib/ui';
import * as problem from 'hydrooj/dist/model/problem';
import problem from 'hydrooj/dist/model/problem';
import { PERM } from 'hydrooj/dist/model/builtin';
fs.ensureDirSync('/tmp/hydro/import-qduoj');
+27
View File
@@ -0,0 +1,27 @@
import * as bus from 'hydrooj/dist/service/bus';
import * as system from 'hydrooj/dist/model/system';
import sonic from './service';
bus.on('problem/add', async (doc, docId) => {
await sonic.push('problem', `${doc.domainId}@title`, docId.toString(), doc.title);
await sonic.push('problem', `${doc.domainId}@content`, docId.toString(), doc.content.toString());
});
bus.on('problem/edit', async (pdoc) => {
await sonic.flusho('problem', `${pdoc.domainId}@title`, pdoc.docId.toString());
await sonic.flusho('problem', `${pdoc.domainId}@content`, pdoc.docId.toString());
await sonic.push('problem', `${pdoc.domainId}@title`, pdoc.docId.toString(), pdoc.title);
await sonic.push('problem', `${pdoc.domainId}@content`, pdoc.docId.toString(), pdoc.content.toString());
});
bus.on('problem/del', async (domainId, docId) => {
await sonic.flusho('problem', `${domainId}@title`, docId.toString());
await sonic.flusho('problem', `${domainId}@content`, docId.toString());
});
global.Hydro.lib.problemSearch = async (domainId: string, query: string) => {
const c = system.get('pagination.problem');
const docIds = await sonic.query('problem', `${domainId}@title`, query, { limit: c });
docIds.push(...await sonic.query('problem', `${domainId}@content`, query, { limit: c - docIds.length }));
return docIds.map(Number);
};
+13
View File
@@ -0,0 +1,13 @@
{
"name": "@hydrooj/sonic",
"version": "1.0.0",
"description": "Sonic search service",
"main": "service.js",
"typings": "service.d.ts",
"repository": "https://github.com/hydro-dev/Hydro.git",
"author": "undefined <i@undefined.moe>",
"license": "AGPL-3.0-only",
"dependencies": {
"sonic-channel": "^1.2.6"
}
}
+25
View File
@@ -0,0 +1,25 @@
import { iterateAllProblem, iterateAllProblemInDomain } from 'hydrooj/dist/upgrade';
import sonic from './service';
export const description = 'Sonic problem search re-index';
export async function run({ domainId }) {
if (domainId) await sonic.flushb('problem', domainId);
else await sonic.flushc('problem');
const cb = async (pdoc) => {
await sonic.push('problem', `${pdoc.domainId}@title`, pdoc.docId.toString(), pdoc.title);
await sonic.push('problem', `${pdoc.domainId}@content`, pdoc.docId.toString(), pdoc.content.toString());
};
if (domainId) await iterateAllProblemInDomain(domainId, ['title', 'content'], cb);
else await iterateAllProblem(['title', 'content'], cb);
return true;
}
export const validate = {
$or: [
{ domainId: 'string' },
{ domainId: 'undefined' },
],
};
global.Hydro.script.ensureSonicSearch = { run, description, validate };
+117
View File
@@ -0,0 +1,117 @@
import { BaseService } from 'hydrooj';
import * as system from 'hydrooj/dist/model/system';
import {
Search, Ingest, QueryOptions, SuggestOptions, PushOptions,
} from 'sonic-channel';
import { Logger } from 'hydrooj/dist/logger';
const logger = new Logger('sonic');
declare module 'hydrooj/dist/interface' {
interface SystemKeys {
'sonic.host': string;
'sonic.port': number;
'sonic.auth': string;
}
}
function getHandler(type: string, that: any) {
return {
connected() {
that.started = true;
logger.info(`Sonic Channel succeeded to connect to host (${type}).`);
},
disconnected() {
that.started = false;
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);
},
};
}
class SonicService implements BaseService {
public search: Search;
public ingest: Ingest;
public started = false;
public error = '';
async start() {
const [host, port, auth] = system.getMany(['sonic.host', 'sonic.port', 'sonic.auth']);
const cfg = {
host: host || '127.0.0.1',
port: port || 1491,
auth: auth || 'sonic-passwd',
};
this.search = new Search(cfg);
this.ingest = new Ingest(cfg);
await this.connect();
}
async connect() {
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(),
]);
}
}
const service = new SonicService();
global.Hydro.service.sonic = service;
export = service;
declare module 'hydrooj/dist/interface' {
interface Service {
sonic: typeof service
}
}
+7
View File
@@ -0,0 +1,7 @@
host:
value: localhost
port:
type: number
value: 1491
auth:
type: text
+1 -1
View File
@@ -1,6 +1,6 @@
import { FilterQuery } from 'mongodb';
import { Pdoc } from 'hydrooj/dist/interface';
import * as problem from 'hydrooj/dist/model/problem';
import problem from 'hydrooj/dist/model/problem';
import record from 'hydrooj/dist/model/record';
import user from 'hydrooj/dist/model/user';
import { PERM, PRIV } from 'hydrooj/dist/model/builtin';