diff --git a/server/.vscode/launch.json b/server/.vscode/launch.json index 47a65b1..94af8a5 100644 --- a/server/.vscode/launch.json +++ b/server/.vscode/launch.json @@ -17,7 +17,8 @@ //"outFiles": [ "/home/alex/Projects/hastic-server/server.js" ], "port": 9229, "restart": true, - "trace": true + "trace": true, + "timeout": 100000 } ] } \ No newline at end of file diff --git a/server/src/config.ts b/server/src/config.ts index b6b3c46..6a486f5 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -1,5 +1,6 @@ import * as path from 'path'; import * as fs from 'fs'; +import * as os from 'os'; import { getJsonDataSync } from './services/json_service'; @@ -10,25 +11,17 @@ export const ANALYTICS_PATH = path.join(__dirname, '../../analytics'); export const DATA_PATH = path.join(__dirname, '../../data'); -export const ANALYTIC_UNITS_DATABASE_PATH = path.join(DATA_PATH, 'analyticUnits.db'); -export const METRICS_DATABASE_PATH = path.join(DATA_PATH, 'metrics.db'); +export const ANALYTIC_UNITS_DATABASE_PATH = path.join(DATA_PATH, 'analytic_units.db'); export const SEGMENTS_DATABASE_PATH = path.join(DATA_PATH, 'segments.db'); -export const FILES_DATABASE_PATH = path.join(DATA_PATH, 'files.db'); - -export const DATASETS_PATH = path.join(DATA_PATH, 'datasets'); -//export const ANALYTIC_UNITS_PATH = path.join(DATA_PATH, 'analytic_units'); -export const MODELS_PATH = path.join(DATA_PATH, 'models'); -export const METRICS_PATH = path.join(DATA_PATH, 'metrics'); -export const SEGMENTS_PATH = path.join(DATA_PATH, 'segments'); export const HASTIC_PORT = getConfigField('HASTIC_PORT', '8000'); export const ZMQ_CONNECTION_STRING = getConfigField('ZMQ_CONNECTION_STRING', null); -export const ZMQ_IPC_PATH = getConfigField('ZMQ_IPC_PATH', path.join('/tmp', 'hastic')); +export const ZMQ_IPC_PATH = getConfigField('ZMQ_IPC_PATH', path.join(os.tmpdir(), 'hastic')); export const ZMQ_DEV_PORT = getConfigField('ZMQ_DEV_PORT', '8002'); export const ANLYTICS_PING_INTERVAL = 500; // ms -function getConfigField(field, defaultVal?) { +function getConfigField(field: string, defaultVal?: any) { let val = defaultVal; if(process.env[field] !== undefined) { diff --git a/server/src/controllers/alerts_controller.ts b/server/src/controllers/alerts_controller.ts index 0989ae2..0eaba4f 100644 --- a/server/src/controllers/alerts_controller.ts +++ b/server/src/controllers/alerts_controller.ts @@ -2,7 +2,7 @@ * Alarting is not supported yet */ -throw new console.error("Not supported"); +throw new Error('not supported'); // import { runPredict } from './analytics_controller'; diff --git a/server/src/controllers/analytics_controller.ts b/server/src/controllers/analytics_controller.ts index 6e1a627..ae65a91 100644 --- a/server/src/controllers/analytics_controller.ts +++ b/server/src/controllers/analytics_controller.ts @@ -1,11 +1,10 @@ -import * as DataService from '../services/data_service'; -import { getTarget } from './metrics_controler'; -import { getLabeledSegments, insertSegments, removeSegments } from './segments_controller'; -import * as AnalyticUnit from '../models/analytic_unit' +import { Task } from '../models/task_model'; +import * as SegmentsController from '../models/segment_model'; +import * as AnalyticUnit from '../models/analytic_unit_model'; import { AnalyticsService, AnalyticsMessage } from '../services/analytics_service'; -const taskMap = {}; +const taskMap = new Map(); let nextTaskId = 0; let analyticsService: AnalyticsService = undefined; @@ -16,21 +15,13 @@ function onTaskResult(taskResult: any) { let status = taskResult.status; if(status === 'SUCCESS' || status === 'FAILED') { if(taskId in taskMap) { - let resolver = taskMap[taskId]; + let resolver: any = taskMap.get(taskId); resolver(taskResult); - delete taskMap[taskId]; + taskMap.delete(taskId); } } } -async function onFileSave(payload: any): Promise { - return DataService.saveFile(payload.filename, payload.content); -} - -async function onFileLoad(payload: any): Promise { - return DataService.loadFile(payload.filename); -} - async function onMessage(message: AnalyticsMessage) { let responsePayload = null; let resolvedMethod = false; @@ -40,15 +31,6 @@ async function onMessage(message: AnalyticsMessage) { resolvedMethod = true; } - if(message.method === 'FILE_SAVE') { - responsePayload = await onFileSave(message.payload); - resolvedMethod = true; - } - if(message.method === 'FILE_LOAD') { - responsePayload = await onFileLoad(message.payload); - resolvedMethod = true; - } - if(!resolvedMethod) { throw new TypeError('Unknown method ' + message.method); } @@ -68,76 +50,82 @@ export function terminate() { analyticsService.close(); } -async function runTask(task): Promise { - let anomaly: AnalyticUnit.AnalyticUnit = AnalyticUnit.findById(task.analyticUnitId); - task.metric = { - datasource: anomaly.metric.datasource, - targets: anomaly.metric.targets.map(getTarget) - }; +async function runTask(task: Task): Promise { + // let anomaly: AnalyticUnit.AnalyticUnit = await AnalyticUnit.findById(task.analyticUnitId); + // task.metric = { + // datasource: anomaly.metric.datasource, + // targets: anomaly.metric.targets.map(getTarget) + // }; - task._taskId = nextTaskId++; - await analyticsService.sendTask(task); + // task._taskId = nextTaskId++; + // await analyticsService.sendTask(task); - return new Promise(resolve => { - taskMap[task._taskId] = resolve; - }) + // return new Promise(resolve => { + // taskMap[task._taskId] = resolve; + // }) } export async function runLearning(id: AnalyticUnit.AnalyticUnitId) { - let segments = getLabeledSegments(id); - AnalyticUnit.setStatus(id, 'LEARNING'); - let unit = AnalyticUnit.findById(id); - let pattern = unit.type; - let task = { - analyticUnitId: id, - type: 'LEARN', - pattern, - segments: segments - }; - - let result = await runTask(task); - - if (result.status === 'SUCCESS') { - AnalyticUnit.setStatus(id, 'READY'); - insertSegments(id, result.segments, false); - AnalyticUnit.setPredictionTime(id, result.lastPredictionTime); - } else { - AnalyticUnit.setStatus(id, 'FAILED', result.error); - } + // let segments = getLabeledSegments(id); + // AnalyticUnit.setStatus(id, 'LEARNING'); + // let unit = await AnalyticUnit.findById(id); + // let pattern = unit.type; + // let task = { + // analyticUnitId: id, + // type: 'LEARN', + // pattern, + // segments: segments + // }; + + // let result = await runTask(task); + + // if (result.status === 'SUCCESS') { + // AnalyticUnit.setStatus(id, 'READY'); + // insertSegments(id, result.segments, false); + // AnalyticUnit.setPredictionTime(id, result.lastPredictionTime); + // } else { + // AnalyticUnit.setStatus(id, 'FAILED', result.error); + // } } - export async function runPredict(id: AnalyticUnit.AnalyticUnitId) { - let unit = AnalyticUnit.findById(id); - let pattern = unit.type; - let task = { - type: 'predict', - analyticUnitId: id, - pattern, - lastPredictionTime: unit.lastPredictionTime - }; - let result = await runTask(task); - - if(result.status === 'FAILED') { - return []; - } - // Merging segments - let segments = getLabeledSegments(id); - if(segments.length > 0 && result.segments.length > 0) { - let lastOldSegment = segments[segments.length - 1]; - let firstNewSegment = result.segments[0]; - - if(firstNewSegment.start <= lastOldSegment.finish) { - result.segments[0].start = lastOldSegment.start; - removeSegments(id, [lastOldSegment.id]); - } - } - - insertSegments(id, result.segments, false); - AnalyticUnit.setPredictionTime(id, result.lastPredictionTime); - return result.segments; + // let unit = await AnalyticUnit.findById(id); + // let pattern = unit.type; + // let task = { + // type: 'PREDICT', + // analyticUnitId: id, + // pattern, + // lastPredictionTime: unit.lastPredictionTime + // }; + // let result = await runTask(task); + + // if(result.status === 'FAILED') { + // return []; + // } + // // Merging segments + // let segments = getLabeledSegments(id); + // if(segments.length > 0 && result.segments.length > 0) { + // let lastOldSegment = segments[segments.length - 1]; + // let firstNewSegment = result.segments[0]; + + // if(firstNewSegment.start <= lastOldSegment.finish) { + // result.segments[0].start = lastOldSegment.start; + // removeSegments(id, [lastOldSegment.id]); + // } + // } + + // insertSegments(id, result.segments, false); + // AnalyticUnit.setPredictionTime(id, result.lastPredictionTime); + // return result.segments; } export function isAnalyticReady(): boolean { return analyticsService.ready; } + +export async function createAnalyticUnitFromObject(obj: any): Promise { + let unit: AnalyticUnit.AnalyticUnit = AnalyticUnit.AnalyticUnit.fromObject(obj); + let id = await AnalyticUnit.create(unit); + // runLearning(unit); + return id; +} \ No newline at end of file diff --git a/server/src/controllers/metrics_controler.ts b/server/src/controllers/metrics_controler.ts deleted file mode 100644 index 7cf5d45..0000000 --- a/server/src/controllers/metrics_controler.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { getJsonDataSync, writeJsonDataSync } from '../services/json_service'; -import { METRICS_PATH } from '../config'; - -import * as crypto from 'crypto'; - -import * as path from 'path'; - - -function saveTargets(targets) { - let metrics = []; - for (let target of targets) { - metrics.push(saveTarget(target)); - } - return metrics; -} - -function saveTarget(target) { - //const md5 = crypto.createHash('md5') - const targetId = crypto.createHash('md5').update(JSON.stringify(target)).digest('hex'); - let filename = path.join(METRICS_PATH, `${targetId}.json`); - writeJsonDataSync(filename, target); - return targetId; -} - -function getTarget(targetId) { - let filename = path.join(METRICS_PATH, `${targetId}.json`); - return getJsonDataSync(filename); -} - -export { saveTargets, getTarget } diff --git a/server/src/controllers/segments_controller.ts b/server/src/controllers/segments_controller.ts deleted file mode 100644 index 358b0b7..0000000 --- a/server/src/controllers/segments_controller.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { getJsonDataSync, writeJsonDataSync } from '../services/json_service'; -import { AnalyticUnitId, findById, save } from '../models/analytic_unit'; -import { SEGMENTS_PATH } from '../config'; - -import * as _ from 'lodash'; - -import * as path from 'path'; -import * as fs from 'fs'; - - -export function getLabeledSegments(id: AnalyticUnitId) { - let filename = path.join(SEGMENTS_PATH, `${id}_labeled.json`); - - if(!fs.existsSync(filename)) { - return []; - } else { - let segments = getJsonDataSync(filename); - for(let segment of segments) { - if(segment.labeled === undefined) { - segment.labeled = false; - } - } - return segments; - } -} - -export function getPredictedSegments(id: AnalyticUnitId) { - let filename = path.join(SEGMENTS_PATH, `${id}_segments.json`); - - let jsonData; - try { - jsonData = getJsonDataSync(filename); - } catch(e) { - console.error(e.message); - jsonData = []; - } - return jsonData; -} - -export function saveSegments(id: AnalyticUnitId, segments) { - let filename = path.join(SEGMENTS_PATH, `${id}_labeled.json`); - - try { - return writeJsonDataSync(filename, _.uniqBy(segments, 'start')); - } catch(e) { - console.error(e.message); - throw new Error('Can`t write to db'); - } -} - -export function insertSegments(id: AnalyticUnitId, addedSegments, labeled: boolean) { - // Set status - let info = findById(id); - let segments = getLabeledSegments(id); - - let nextId = info.nextId; - let addedIds = [] - for (let segment of addedSegments) { - segment.id = nextId; - segment.labeled = labeled; - addedIds.push(nextId); - nextId++; - segments.push(segment); - } - info.nextId = nextId; - saveSegments(id, segments); - save(id, info); - return addedIds; -} - -export function removeSegments(id: AnalyticUnitId, removedSegments) { - let segments = getLabeledSegments(id); - for (let segmentId of removedSegments) { - segments = segments.filter(el => el.id !== segmentId); - } - saveSegments(id, segments); -} diff --git a/server/src/index.ts b/server/src/index.ts index 31ca62d..0676fbc 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,10 +1,9 @@ import { router as anomaliesRouter } from './routes/analytic_units_router'; import { router as segmentsRouter } from './routes/segments_router'; -//import { router as alertsRouter } from './routes/alerts_router'; + import * as AnalyticsController from './controllers/analytics_controller'; -import * as Data from './services/data_service'; import * as ProcessService from './services/process_service'; import { HASTIC_PORT } from './config'; @@ -14,19 +13,24 @@ import * as Router from 'koa-router'; import * as bodyParser from 'koa-bodyparser'; -Data.checkDataFolders(); AnalyticsController.init(); ProcessService.registerExitHandler(AnalyticsController.terminate); var app = new Koa(); +app.on('error', (err, ctx) => { + console.log('got server error:'); + console.log(err); +}); + + app.use(bodyParser()) app.use(async function(ctx, next) { ctx.set('Access-Control-Allow-Origin', '*'); ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS'); ctx.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); - next(); + await next(); }); diff --git a/server/src/models/analytic_unit.ts b/server/src/models/analytic_unit.ts deleted file mode 100644 index d4d3ef5..0000000 --- a/server/src/models/analytic_unit.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { getJsonDataSync, writeJsonDataSync } from '../services/json_service' -import { ANALYTIC_UNITS_PATH } from '../config' - -import * as crypto from 'crypto'; - -import * as path from 'path' -import * as fs from 'fs' - - -export type Datasource = { - method: string, - data: Object, - params: Object, - type: string, - url: string -} - -export type Metric = { - datasource: string, - targets: string[] -} - -export type AnalyticUnitId = string; - -export type AnalyticUnit = { - id?: AnalyticUnitId, - name: string, - panelUrl: string, - type: string, - metric: Metric, - datasource: Datasource - status: string, - error?: string, - lastPredictionTime: number, - nextId: number -} - -export function createItem(item: AnalyticUnit): AnalyticUnitId { - const hashString = item.name + (new Date()).toString(); - const newId: AnalyticUnitId = crypto.createHash('md5').update(hashString).digest('hex'); - let filename = path.join(ANALYTIC_UNITS_PATH, `${newId}.json`); - if(fs.existsSync(filename)) { - throw new Error(`Can't create item with id ${newId}`); - } - save(newId, item); - item.id = newId; - return newId; -} - -export function remove(id: AnalyticUnitId) { - let filename = path.join(ANALYTIC_UNITS_PATH, `${id}.json`); - fs.unlinkSync(filename); -} - -export function save(id: AnalyticUnitId, unit: AnalyticUnit) { - let filename = path.join(ANALYTIC_UNITS_PATH, `${id}.json`); - return writeJsonDataSync(filename, unit); -} - -// TODO: make async -export function findById(id: AnalyticUnitId): AnalyticUnit { - let filename = path.join(ANALYTIC_UNITS_PATH, `${id}.json`); - if(!fs.existsSync(filename)) { - throw new Error(`Can't find Analytic Unit with id ${id}`); - } - return getJsonDataSync(filename); -} - -export function setStatus(predictorId: AnalyticUnitId, status: string, error?: string) { - let info = findById(predictorId); - info.status = status; - if(error !== undefined) { - info.error = error; - } else { - info.error = ''; - } - save(predictorId, info); -} - -export function setPredictionTime(id: AnalyticUnitId, time: number) { - let info = findById(id); - info.lastPredictionTime = time; - save(id, info); -} diff --git a/server/src/models/analytic_unit_model.ts b/server/src/models/analytic_unit_model.ts new file mode 100644 index 0000000..26b0278 --- /dev/null +++ b/server/src/models/analytic_unit_model.ts @@ -0,0 +1,98 @@ +import { Metric } from './metric_model'; +import { Collection, makeDBQ } from '../services/data_service'; + + +let db = makeDBQ(Collection.ANALYTIC_UNITS); + + +export type AnalyticUnitId = string; + +export class AnalyticUnit { + constructor( + public name: string, + public panelUrl: string, + public type: string, + public metric: Metric, + public id?: AnalyticUnitId, + public lastPredictionTime?: number, + public status?: string, + public error?: string, + ) { + if(name === undefined) { + throw new Error(`Missing field "name"`); + } + if(panelUrl === undefined) { + throw new Error(`Missing field "panelUrl"`); + } + if(type === undefined) { + throw new Error(`Missing field "type"`); + } + if(metric === undefined) { + throw new Error(`Missing field "metric"`); + } + } + + public toObject() { + return { + _id: this.id, + name: this.name, + panelUrl: this.panelUrl, + type: this.type, + metric: this.metric.toObject(), + lastPredictionTime: this.lastPredictionTime, + status: this.status, + error: this.error + }; + } + + static fromObject(obj: any): AnalyticUnit { + if(obj === undefined) { + throw new Error('obj is undefined'); + } + return new AnalyticUnit( + obj.name, + obj.panelUrl, + obj.type, + Metric.fromObject(obj.metric), + obj.status, + obj.lastPredictionTime, + obj._id, + obj.error, + ); + } + +} + + +export async function findById(id: AnalyticUnitId): Promise { + return AnalyticUnit.fromObject(await db.findOne(id)); +} + +/** + * Creates and updates new unit.id + * + * @param unit to create + * @returns unit.id + */ +export async function create(unit: AnalyticUnit): Promise { + var obj = unit.toObject(); + var r = await db.insertOne(obj); + return r; +} + +export async function remove(id: AnalyticUnitId): Promise { + await db.removeOne(id); + return; +} + +export async function update(id: AnalyticUnitId, unit: AnalyticUnit) { + return db.updateOne(id, unit); +} + +export async function setStatus(id: AnalyticUnitId, status: string, error?: string) { + return db.updateOne(id, { status, error }); +} + +export async function setPredictionTime(id: AnalyticUnitId, lastPredictionTime: number) { + return db.updateOne(id, { lastPredictionTime }); +} diff --git a/server/src/models/metric_model.ts b/server/src/models/metric_model.ts new file mode 100644 index 0000000..2d97b2d --- /dev/null +++ b/server/src/models/metric_model.ts @@ -0,0 +1,32 @@ +export class Metric { + constructor(public datasource: string, public targets: any[]) { + if(datasource === undefined) { + throw new Error('datasource is undefined'); + } + if(targets === undefined) { + throw new Error('targets is undefined'); + } + if(targets.length === 0) { + throw new Error('targets is empty'); + } + } + + public toObject() { + return { + datasource: this.datasource, + targets: this.targets + }; + } + + static fromObject(obj: any): Metric { + if(obj === undefined) { + throw new Error('obj is undefined'); + } + return new Metric( + obj.datasource, + obj.targets + ); + } +} + + diff --git a/server/src/models/segment_model.ts b/server/src/models/segment_model.ts new file mode 100644 index 0000000..507622a --- /dev/null +++ b/server/src/models/segment_model.ts @@ -0,0 +1,87 @@ +import { AnalyticUnitId } from './analytic_unit_model'; + +import { Collection, makeDBQ } from '../services/data_service'; + +let db = makeDBQ(Collection.SEGMENTS); + + +export type SegmentId = string; + +export class Segment { + constructor( + public analyticUnitId: AnalyticUnitId, + public from: number, + public to: number, + public labeled: boolean, + public id?: SegmentId + ) { + if(analyticUnitId === undefined) { + throw new Error('AnalyticUnitId is undefined'); + } + if(from === undefined) { + throw new Error('from is undefined'); + } + if(isNaN(from)) { + throw new Error('from is NaN'); + } + if(to === undefined) { + throw new Error('to is undefined'); + } + if(isNaN(to)) { + throw new Error('to is NaN'); + } + if(labeled === undefined) { + throw new Error('labeled is undefined'); + } + } + + public toObject() { + return { + _id: this.id, + analyticUnitId: this.analyticUnitId, + from: this.from, + to: this.to, + labeled: this.labeled + }; + } + + static fromObject(obj: any): Segment { + if(obj === undefined) { + throw new Error('obj is undefined'); + } + return new Segment( + obj.analyticUnitId, + +obj.from, +obj.to, + obj.labeled, obj._id + ); + } +} + +export type FindManyQuery = { + timeFromGTE?: number, + timeToLTE?: number, + intexGT?: number +} + +export async function findMany(id: AnalyticUnitId, query: FindManyQuery): Promise { + var dbQuery: any = { analyticUnitId: id }; + if(query.timeFromGTE !== undefined) { + dbQuery.from = { $gte: query.timeFromGTE }; + } + if(query.timeToLTE !== undefined) { + dbQuery.to = { $lte: query.timeToLTE }; + } + let segs = await db.findMany(dbQuery); + if(segs === null) { + return []; + } + return segs.map(Segment.fromObject); +} + +export async function insertSegments(segments: Segment[]) { + return db.insertMany(segments.map(s => s.toObject())); +} + +export function removeSegments(idsToRemove: SegmentId[]) { + return db.removeMany(idsToRemove); +} diff --git a/server/src/models/task_model.ts b/server/src/models/task_model.ts new file mode 100644 index 0000000..c19e3d4 --- /dev/null +++ b/server/src/models/task_model.ts @@ -0,0 +1,32 @@ +import { AnalyticUnitId } from "./analytic_unit_model"; + + +export type TaskId = string; + +export class Task { + constructor( + public analyticUnitId: AnalyticUnitId, + public id?: TaskId + ) { + if(analyticUnitId === undefined) { + throw new Error('analyticUnitId is undefined'); + } + } + + public toObject() { + return { + _id: this.id, + analyticUnitId: this.analyticUnitId + }; + } + + static fromObject(obj: any): Task { + if(obj === undefined) { + throw new Error('obj is undefined'); + } + return new Task( + obj.analyticUnitId, + obj._id, + ); + } +} diff --git a/server/src/routes/analytic_units_router.ts b/server/src/routes/analytic_units_router.ts index dee28bb..37a85d8 100644 --- a/server/src/routes/analytic_units_router.ts +++ b/server/src/routes/analytic_units_router.ts @@ -1,32 +1,22 @@ import * as Router from 'koa-router'; -import * as AnalyticUnit from '../models/analytic_unit'; +import * as AnalyticUnit from '../models/analytic_unit_model'; -import { runLearning } from '../controllers/analytics_controller' -import { saveTargets } from '../controllers/metrics_controler'; +import { createAnalyticUnitFromObject } from '../controllers/analytics_controller' -async function sendStatus(ctx: Router.IRouterContext) { - try { - let id = ctx.request.query.id; - if(id === undefined) { - throw new Error('Id is undefined'); - } - let unit = AnalyticUnit.findById(id); - if(unit.status === undefined) { - throw new Error('status is undefined'); - } - ctx.response.body = { status: unit.status, errorMessage: unit.error }; +async function getStatus(ctx: Router.IRouterContext) { + try { + ctx.response.body = { status: 'READY', errorMessage: undefined }; } catch(e) { console.error(e); // TODO: better send 404 when we know than isn`t found ctx.response.status = 500; ctx.response.body = { error: 'Can`t return anything' }; } - } -async function findItem(ctx: Router.IRouterContext) { +async function getUnit(ctx: Router.IRouterContext) { try { let id = ctx.request.query.id; @@ -34,7 +24,7 @@ async function findItem(ctx: Router.IRouterContext) { throw new Error('No id param in query'); } - let unit: AnalyticUnit.AnalyticUnit = AnalyticUnit.findById(id); + let unit: AnalyticUnit.AnalyticUnit = await AnalyticUnit.findById(id); ctx.response.body = { name: unit.name, @@ -50,58 +40,10 @@ async function findItem(ctx: Router.IRouterContext) { } } -async function createItem(ctx: Router.IRouterContext) { +async function createUnit(ctx: Router.IRouterContext) { try { - - let body = ctx.request.body; - - if(body.type === undefined) { - throw new Error(`Missing field "type"`); - } - if(body.name === undefined) { - throw new Error(`Missing field "name"`); - } - if(body.panelUrl === undefined) { - throw new Error(`Missing field "panelUrl"`); - } - if(body.metric === undefined) { - throw new Error(`Missing field "datasource"`); - } - if(body.metric.datasource === undefined) { - throw new Error(`Missing field "metric.datasource"`); - } - if(body.metric.targets === undefined) { - throw new Error(`Missing field "metric.targets"`); - } - - const metric: AnalyticUnit.Metric = { - datasource: body.metric.datasource, - targets: saveTargets(body.metric.targets) - }; - - const unit: AnalyticUnit.AnalyticUnit = { - name: body.name, - panelUrl: body.panelUrl, - type: body.type, - datasource: body.datasource, - metric: metric, - status: 'learning', - lastPredictionTime: 0, - nextId: 0 - }; - - let newId = AnalyticUnit.createItem(unit); - if(newId === null) { - ctx.response.status = 403; - ctx.response.body = { - code: 403, - message: 'Item exists' - }; - } - + let newId = await createAnalyticUnitFromObject(ctx.request.body); ctx.response.body = { id: newId }; - - runLearning(newId); } catch(e) { ctx.response.status = 500; ctx.response.body = { @@ -109,16 +51,12 @@ async function createItem(ctx: Router.IRouterContext) { message: `Creation error: ${e.message}` }; } + } -function deleteItem(ctx: Router.IRouterContext) { +async function deleteUnit(ctx: Router.IRouterContext) { try { - let id = ctx.request.query.id; - - if(id !== undefined) { - AnalyticUnit.remove(id); - } - + await AnalyticUnit.remove(ctx.request.query.id); ctx.response.body = { code: 200, message: 'Success' @@ -135,7 +73,7 @@ function deleteItem(ctx: Router.IRouterContext) { export var router = new Router(); -router.get('/status', sendStatus); -router.get('/', findItem); -router.post('/', createItem); -router.delete('/', deleteItem); +router.get('/', getUnit); +router.get('/status', getStatus); +router.post('/', createUnit); +router.delete('/', deleteUnit); diff --git a/server/src/routes/segments_router.ts b/server/src/routes/segments_router.ts index 7315171..07bc1fc 100644 --- a/server/src/routes/segments_router.ts +++ b/server/src/routes/segments_router.ts @@ -1,49 +1,53 @@ import * as Router from 'koa-router'; -import { AnalyticUnitId } from '../models/analytic_unit'; +import { AnalyticUnitId } from '../models/analytic_unit_model'; -import { - getLabeledSegments, - insertSegments, - removeSegments, -} from '../controllers/segments_controller'; +import * as SegmentModel from '../models/segment_model'; import { runLearning } from '../controllers/analytics_controller'; -async function sendSegments(ctx: Router.IRouterContext) { +async function getSegments(ctx: Router.IRouterContext) { let id: AnalyticUnitId = ctx.request.query.id; - - let lastSegmentId = ctx.request.query.lastSegmentId; - let timeFrom = ctx.request.query.from; - let timeTo = ctx.request.query.to; - - let segments = getLabeledSegments(id); - - // Id filtering - if(lastSegmentId !== undefined) { - segments = segments.filter(el => el.id > lastSegmentId); + if(id === undefined || id === '') { + throw new Error('analyticUnitId (id) is missing'); } + let query: SegmentModel.FindManyQuery = {}; - // Time filtering - if(timeFrom !== undefined) { - segments = segments.filter(el => el.finish > timeFrom); + if(!isNaN(+ctx.request.query.lastSegmentId)) { + query.intexGT = +ctx.request.query.lastSegmentId; } - - if(timeTo !== undefined) { - segments = segments.filter(el => el.start < timeTo); + if(!isNaN(+ctx.request.query.from)) { + query.timeFromGTE = +ctx.request.query.from; + } + if(!isNaN(+ctx.request.query.to)) { + query.timeToLTE = +ctx.request.query.to; } - ctx.response.body = { segments } + let segments = await SegmentModel.findMany(id, query); + + ctx.response.body = { segments }; } async function updateSegments(ctx: Router.IRouterContext) { try { - let segmentsUpdate = ctx.request.body; - let id = segmentsUpdate.id; - let addedIds = insertSegments(id, segmentsUpdate.addedSegments, true); - removeSegments(id, segmentsUpdate.removedSegments); - ctx.response.body = { addedIds }; + + let { + addedSegments, id, removedSegments: removedIds + } = ctx.request.body as { + addedSegments: any[], id: AnalyticUnitId, removedSegments: SegmentModel.SegmentId[] + }; + + let segmentsToInsert: SegmentModel.Segment[] = addedSegments.map( + s => SegmentModel.Segment.fromObject({ analyticUnitId: id, labeled: true, ...s }) + ); + + let [addedIds, removed] = await Promise.all([ + SegmentModel.insertSegments(segmentsToInsert), + SegmentModel.removeSegments(removedIds) + ]); + + ctx.response.body = { addedIds, removed }; runLearning(id); } catch(e) { ctx.response.status = 500; @@ -56,5 +60,5 @@ async function updateSegments(ctx: Router.IRouterContext) { export const router = new Router(); -router.get('/', sendSegments); +router.get('/', getSegments); router.patch('/', updateSegments); diff --git a/server/src/services/analytics_service.ts b/server/src/services/analytics_service.ts index eb25bb6..940207f 100644 --- a/server/src/services/analytics_service.ts +++ b/server/src/services/analytics_service.ts @@ -1,232 +1,232 @@ -import * as config from '../config'; - -const zmq = require('zeromq'); - -import * as childProcess from 'child_process' -import * as fs from 'fs'; -import * as path from 'path'; - - -export class AnalyticsMessage { - public constructor(public method: string, public payload?: string, public requestId?: number) { - - } - - static fromJSON(obj: any): AnalyticsMessage { - if(obj.method === undefined) { - throw new Error('No method in obj:' + obj); - } - return new AnalyticsMessage(obj.method, obj.payload, obj.requestId); - } -} - -function analyticsMessageFromJson(obj: any): AnalyticsMessage { - return new AnalyticsMessage(obj); -} - -export class AnalyticsService { - - private _requester: any; - private _ready: boolean = false; - private _pingResponded = false; - private _zmqConnectionString = null; - private _ipcPath = null; - private _analyticsPinger: NodeJS.Timer = null; - private _isClosed = false; - - constructor(private _onMessage: (message: AnalyticsMessage) => void) { - this._init(); - } - - public async sendTask(taskObj: any): Promise { - if(!this._ready) { - return Promise.reject("Analytics is not ready"); - } - let message = { - method: 'TASK', - payload: taskObj - } - return this.sendMessage(message); - } - - public async sendMessage(message: AnalyticsMessage): Promise { - let strMessage = JSON.stringify(message); - if(message.method === 'PING') { - strMessage = 'PING'; - } - return new Promise((resolve, reject) => { - this._requester.send(strMessage, undefined, (err) => { - if(err) { - reject(err); - } else { - resolve(); - } - }); - }); - } - - public close() { - this._isClosed = true; - console.log('Terminating analytics service...'); - clearInterval(this._analyticsPinger); - if(this._ipcPath !== null) { - console.log('Remove ipc path: ' + this._ipcPath); - fs.unlinkSync(this._ipcPath); - } - this._requester.close(); - console.log('Ok'); - } - - public get ready(): boolean { return this._ready; } - - private async _init() { - this._requester = zmq.socket('pair'); - let productionMode = process.env.NODE_ENV !== 'development'; - - this._zmqConnectionString = `tcp://127.0.0.1:${config.ZMQ_DEV_PORT}`; // debug mode - if(productionMode) { - this._zmqConnectionString = config.ZMQ_CONNECTION_STRING; - if(this._zmqConnectionString === null) { - var createResult = await AnalyticsService.createIPCAddress(); - this._zmqConnectionString = createResult.address; - this._ipcPath = createResult.file; - } - } - - console.log("Binding to zmq... %s", this._zmqConnectionString); - this._requester.connect(this._zmqConnectionString); - this._requester.on("message", this._onAnalyticsMessage.bind(this)); - console.log('Ok'); - - if(productionMode) { - console.log('Creating analytics process...'); - try { - var cp = await AnalyticsService._runAnalyticsProcess(this._zmqConnectionString); - } catch(error) { - console.error('Can`t run analytics process: %s', error); - return; - } - console.log('Ok, pid: %s', cp.pid); - } - - console.log('Start analytics pinger...'); - this._runAlalyticsPinger(); - console.log('Ok'); - - } - - /** - * Spawns analytics process. Reads process stderr and fails if it isn`t empty. - * No need to stop the process later. - * - * @returns Creaded child process - * @throws Process start error or first exception during python start - */ - private static async _runAnalyticsProcess(zmqConnectionString: string): Promise { - let cp: childProcess.ChildProcess; - let cpOptions = { - cwd: config.ANALYTICS_PATH, - env: { - ...process.env, - ZMQ_CONNECTION_STRING: zmqConnectionString - } - }; - - if(fs.existsSync(path.join(config.ANALYTICS_PATH, 'dist/worker/worker'))) { - console.log('dist/worker/worker'); - cp = childProcess.spawn('dist/worker/worker', [], cpOptions); - } else { - console.log('python3 server.py'); - // If compiled analytics script doesn't exist - fallback to regular python - console.log(config.ANALYTICS_PATH); - cp = childProcess.spawn('python3', ['server.py'], cpOptions); - } - - if(cp.pid === undefined) { - return new Promise((resolve, reject) => { - cp.on('error', reject); - }); - } - - return new Promise((resolve, reject) => { - var resolved = false; - - cp.stdout.on('data', () => { - if(resolved) { - return; - } else { - resolved = true; - } - resolve(cp); - }); - - cp.stderr.on('data', function(data) { - if(resolved) { - return; - } else { - resolved = true; - } - reject(data); - }); - - }); - - } - - private _onAnalyticsUp() { - console.log('Analytics is up'); - } - - private async _onAnalyticsDown() { - console.log('Analytics is down'); - if(process.env.NODE_ENV !== 'development') { - await AnalyticsService._runAnalyticsProcess(this._zmqConnectionString); - } - } - - private _onAnalyticsMessage(data: any, error) { - if(data.toString() === 'PONG') { - this._pingResponded = true; - if(!this._ready) { - this._ready = true; - this._onAnalyticsUp(); - } - return; - } - - - let text = data.toString(); - let response; - try { - response = JSON.parse(text); - } catch (e) { - console.error("Can`t parse response from analytics as json:"); - console.error(text); - throw new Error('Unexpected response'); - } - this._onMessage(AnalyticsMessage.fromJSON(response)); - } - - private async _runAlalyticsPinger() { - this._analyticsPinger = setInterval(() => { - if(this._isClosed) { - return; - } - if(!this._pingResponded && this._ready) { - this._ready = false; - this._onAnalyticsDown(); - } - this._pingResponded = false; - // TODO: set life limit for this ping - this.sendMessage({ method: 'PING' }); - }, config.ANLYTICS_PING_INTERVAL); - } - - private static async createIPCAddress(): Promise<{ address: string, file: string }> { - let filename = `${process.pid}.ipc` - let p = path.join(config.ZMQ_IPC_PATH, filename); - fs.writeFileSync(p, ''); - return Promise.resolve({ address: 'ipc://' + p, file: p }); - } - -} +import * as config from '../config'; + +const zmq = require('zeromq'); + +import * as childProcess from 'child_process' +import * as fs from 'fs'; +import * as path from 'path'; + + +export class AnalyticsMessage { + public constructor(public method: string, public payload?: string, public requestId?: number) { + + } + + static fromJSON(obj: any): AnalyticsMessage { + if(obj.method === undefined) { + throw new Error('No method in obj:' + obj); + } + return new AnalyticsMessage(obj.method, obj.payload, obj.requestId); + } +} + +function analyticsMessageFromJson(obj: any): AnalyticsMessage { + return new AnalyticsMessage(obj); +} + +export class AnalyticsService { + + private _requester: any; + private _ready: boolean = false; + private _pingResponded = false; + private _zmqConnectionString: string = null; + private _ipcPath: string = null; + private _analyticsPinger: NodeJS.Timer = null; + private _isClosed = false; + + constructor(private _onMessage: (message: AnalyticsMessage) => void) { + this._init(); + } + + public async sendTask(taskObj: any): Promise { + if(!this._ready) { + return Promise.reject("Analytics is not ready"); + } + let message = { + method: 'TASK', + payload: taskObj + } + return this.sendMessage(message); + } + + public async sendMessage(message: AnalyticsMessage): Promise { + let strMessage = JSON.stringify(message); + if(message.method === 'PING') { + strMessage = 'PING'; + } + return new Promise((resolve, reject) => { + this._requester.send(strMessage, undefined, (err: any) => { + if(err) { + reject(err); + } else { + resolve(); + } + }); + }); + } + + public close() { + this._isClosed = true; + console.log('Terminating analytics service...'); + clearInterval(this._analyticsPinger); + if(this._ipcPath !== null) { + console.log('Remove ipc path: ' + this._ipcPath); + fs.unlinkSync(this._ipcPath); + } + this._requester.close(); + console.log('Ok'); + } + + public get ready(): boolean { return this._ready; } + + private async _init() { + this._requester = zmq.socket('pair'); + let productionMode = process.env.NODE_ENV !== 'development'; + + this._zmqConnectionString = `tcp://127.0.0.1:${config.ZMQ_DEV_PORT}`; // debug mode + if(productionMode) { + this._zmqConnectionString = config.ZMQ_CONNECTION_STRING; + if(this._zmqConnectionString === null) { + var createResult = await AnalyticsService.createIPCAddress(); + this._zmqConnectionString = createResult.address; + this._ipcPath = createResult.file; + } + } + + console.log("Binding to zmq... %s", this._zmqConnectionString); + this._requester.connect(this._zmqConnectionString); + this._requester.on("message", this._onAnalyticsMessage.bind(this)); + console.log('Ok'); + + if(productionMode) { + console.log('Creating analytics process...'); + try { + var cp = await AnalyticsService._runAnalyticsProcess(this._zmqConnectionString); + } catch(error) { + console.error('Can`t run analytics process: %s', error); + return; + } + console.log('Ok, pid: %s', cp.pid); + } + + console.log('Start analytics pinger...'); + this._runAlalyticsPinger(); + console.log('Ok'); + + } + + /** + * Spawns analytics process. Reads process stderr and fails if it isn`t empty. + * No need to stop the process later. + * + * @returns Creaded child process + * @throws Process start error or first exception during python start + */ + private static async _runAnalyticsProcess(zmqConnectionString: string): Promise { + let cp: childProcess.ChildProcess; + let cpOptions = { + cwd: config.ANALYTICS_PATH, + env: { + ...process.env, + ZMQ_CONNECTION_STRING: zmqConnectionString + } + }; + + if(fs.existsSync(path.join(config.ANALYTICS_PATH, 'dist/worker/worker'))) { + console.log('dist/worker/worker'); + cp = childProcess.spawn('dist/worker/worker', [], cpOptions); + } else { + console.log('python3 server.py'); + // If compiled analytics script doesn't exist - fallback to regular python + console.log(config.ANALYTICS_PATH); + cp = childProcess.spawn('python3', ['server.py'], cpOptions); + } + + if(cp.pid === undefined) { + return new Promise((resolve, reject) => { + cp.on('error', reject); + }); + } + + return new Promise((resolve, reject) => { + var resolved = false; + + cp.stdout.on('data', () => { + if(resolved) { + return; + } else { + resolved = true; + } + resolve(cp); + }); + + cp.stderr.on('data', function(data) { + if(resolved) { + return; + } else { + resolved = true; + } + reject(data); + }); + + }); + + } + + private _onAnalyticsUp() { + console.log('Analytics is up'); + } + + private async _onAnalyticsDown() { + console.log('Analytics is down'); + if(process.env.NODE_ENV !== 'development') { + await AnalyticsService._runAnalyticsProcess(this._zmqConnectionString); + } + } + + private _onAnalyticsMessage(data: any) { + if(data.toString() === 'PONG') { + this._pingResponded = true; + if(!this._ready) { + this._ready = true; + this._onAnalyticsUp(); + } + return; + } + + + let text = data.toString(); + let response; + try { + response = JSON.parse(text); + } catch (e) { + console.error("Can`t parse response from analytics as json:"); + console.error(text); + throw new Error('Unexpected response'); + } + this._onMessage(AnalyticsMessage.fromJSON(response)); + } + + private async _runAlalyticsPinger() { + this._analyticsPinger = setInterval(() => { + if(this._isClosed) { + return; + } + if(!this._pingResponded && this._ready) { + this._ready = false; + this._onAnalyticsDown(); + } + this._pingResponded = false; + // TODO: set life limit for this ping + this.sendMessage({ method: 'PING' }); + }, config.ANLYTICS_PING_INTERVAL); + } + + private static async createIPCAddress(): Promise<{ address: string, file: string }> { + let filename = `${process.pid}.ipc` + let p = path.join(config.ZMQ_IPC_PATH, filename); + fs.writeFileSync(p, ''); + return Promise.resolve({ address: 'ipc://' + p, file: p }); + } + +} diff --git a/server/src/services/data_service.ts b/server/src/services/data_service.ts index a6005ea..ec9908c 100644 --- a/server/src/services/data_service.ts +++ b/server/src/services/data_service.ts @@ -1,77 +1,180 @@ -import * as config from '../config'; - -import * as nedb from 'nedb'; -import * as fs from 'fs'; - -export const db = { - analyticUnits: new nedb({ filename: config.ANALYTIC_UNITS_DATABASE_PATH, autoload: true }), - metrics: new nedb({ filename: config.METRICS_DATABASE_PATH, autoload: true }), - segments: new nedb({ filename: config.SEGMENTS_DATABASE_PATH, autoload: true }), - files: new nedb({ filename: config.FILES_DATABASE_PATH, autoload: true }) -}; - - -let dbUpsertFile = (query: any, updateQuery: any) => { - return new Promise((resolve, reject) => { - db.files.update(query, updateQuery, { upsert: true }, (err: Error) => { - if(err) { - reject(err); - } else { - console.log('saved shit with query '); - console.log(query); - console.log('saved shit with updateQuery '); - console.log(updateQuery); - resolve(); - } - }); - }); -} - -let dbLoadFile = (query: any) => { - return new Promise((resolve, reject) => { - db.files.findOne(query, (err, doc) => { - if(err) { - reject(err); - } else { - console.log('got shit with query'); - console.log(query); - console.log('doc'); - console.log(doc); - resolve(doc); - } - }); - }); -} - -function maybeCreate(path: string): void { - if(fs.existsSync(path)) { - return; - } - console.log('mkdir: ' + path); - fs.mkdirSync(path); - console.log('exists: ' + fs.existsSync(path)); -} - -export async function saveFile(filename: string, content: string): Promise { - return dbUpsertFile({ filename } , { filename, content }); -} - -export async function loadFile(filename: string): Promise { - let doc = await dbLoadFile({ filename }); - if(doc === null) { - return null; - } - return doc.content; -} - -export function checkDataFolders(): void { - [ - config.DATA_PATH, - config.DATASETS_PATH, - config.ANALYTIC_UNITS_PATH, - config.MODELS_PATH, - config.METRICS_PATH, - config.SEGMENTS_PATH, - config.ZMQ_IPC_PATH - ].forEach(maybeCreate); -} +import * as config from '../config'; + +import * as nedb from 'nedb'; +import * as fs from 'fs'; + + +export enum Collection { ANALYTIC_UNITS, SEGMENTS }; + + +/** + * Class which helps to make queries to your collection + * + * @param { string | object } query: a key as a string or mongodb-style query + */ +export type DBQ = { + findOne: (query: string | object) => Promise, + findMany: (query: string[] | object) => Promise, + insertOne: (document: object) => Promise, + insertMany: (documents: object[]) => Promise, + updateOne: (query: string | object, updateQuery: any) => Promise, + removeOne: (query: string) => Promise + removeMany: (query: string[] | object) => Promise +} + +export function makeDBQ(collection: Collection): DBQ { + return { + findOne: dbFindOne.bind(null, collection), + findMany: dbFindMany.bind(null, collection), + insertOne: dbInsertOne.bind(null, collection), + insertMany: dbInsertMany.bind(null, collection), + updateOne: dbUpdateOne.bind(null, collection), + removeOne: dbRemoveOne.bind(null, collection), + removeMany: dbRemoveMany.bind(null, collection) + } +} + +function wrapIdToQuery(query: string | object): any { + if(typeof query === 'string') { + return { _id: query }; + } + return query; +} + +function wrapIdsToQuery(query: string[] | object): any { + if(Array.isArray(query)) { + return { _id: { $in: query } }; + } + return query; +} + +function isEmptyArray(obj: any): boolean { + if(!Array.isArray(obj)) { + return false; + } + return obj.length == 0; +} + +const db = new Map(); + +let dbInsertOne = (collection: Collection, doc: object) => { + return new Promise((resolve, reject) => { + db.get(collection).insert(doc, (err, newDoc: any) => { + if(err) { + reject(err); + } else { + resolve(newDoc._id); + } + }); + }); +} + +let dbInsertMany = (collection: Collection, docs: object[]) => { + if(docs.length === 0) { + return Promise.resolve([]); + } + return new Promise((resolve, reject) => { + db.get(collection).insert(docs, (err, newDocs: any[]) => { + if(err) { + reject(err); + } else { + resolve(newDocs.map(d => d._id)); + } + }); + }); +} + +let dbUpdateOne = (collection: Collection, query: string | object, updateQuery: object) => { + query = wrapIdToQuery(query); + return new Promise((resolve, reject) => { + db.get(collection).update(query, updateQuery, { /* options */ }, (err: Error) => { + if(err) { + reject(err); + } else { + resolve(); + } + }); + }); +} + +let dbFindOne = (collection: Collection, query: string | object) => { + query = wrapIdToQuery(query); + return new Promise((resolve, reject) => { + db.get(collection).findOne(query, (err, doc) => { + if(err) { + reject(err); + } else { + resolve(doc); + } + }); + }); +} + +let dbFindMany = (collection: Collection, query: string[] | object) => { + if(isEmptyArray(query)) { + return Promise.resolve([]); + } + query = wrapIdsToQuery(query); + return new Promise((resolve, reject) => { + db.get(collection).find(query, (err, docs: any[]) => { + if(err) { + reject(err); + } else { + resolve(docs); + } + }); + }); +} + +let dbRemoveOne = (collection: Collection, query: string | object) => { + query = wrapIdToQuery(query); + return new Promise((resolve, reject) => { + db.get(collection).remove(query, { /* options */ }, (err, numRemoved) => { + if(err) { + reject(err); + } else { + if(numRemoved > 1) { + throw new Error(`Removed ${numRemoved} elements with query: ${JSON.stringify(query)}. Only one is Ok.`); + } else { + resolve(numRemoved == 1); + } + } + }); + }); +} + +let dbRemoveMany = (collection: Collection, query: string[] | object) => { + if(isEmptyArray(query)) { + return Promise.resolve([]); + } + query = wrapIdsToQuery(query); + return new Promise((resolve, reject) => { + db.get(collection).remove(query, { multi: true }, (err, numRemoved) => { + if(err) { + reject(err); + } else { + resolve(numRemoved); + } + }); + }); +} + +function maybeCreateDir(path: string): void { + if(fs.existsSync(path)) { + return; + } + console.log('mkdir: ' + path); + fs.mkdirSync(path); +} + +function checkDataFolders(): void { + [ + config.DATA_PATH, + config.ZMQ_IPC_PATH + ].forEach(maybeCreateDir); +} +checkDataFolders(); + +// TODO: it's better if models request db which we create if it`s needed +db.set(Collection.ANALYTIC_UNITS, new nedb({ filename: config.ANALYTIC_UNITS_DATABASE_PATH, autoload: true })); +db.set(Collection.SEGMENTS, new nedb({ filename: config.SEGMENTS_DATABASE_PATH, autoload: true })); diff --git a/server/src/services/notification_service.ts b/server/src/services/notification_service.ts index 4b39a6b..6160ef9 100644 --- a/server/src/services/notification_service.ts +++ b/server/src/services/notification_service.ts @@ -1,11 +1,11 @@ -import { findById } from '../models/analytic_unit'; +import { findById, AnalyticUnitId } from '../models/analytic_unit_model'; import axios from 'axios'; // TODO: send notification with payload without dep to AnalyticUnit -export async function sendNotification(predictorId, active) { - let anomalyName = findById(predictorId).name; +export async function sendNotification(id: AnalyticUnitId, active: boolean) { + let anomalyName = (await findById(id)).name console.log('Notification ' + anomalyName); let notification = { diff --git a/server/src/services/process_service.ts b/server/src/services/process_service.ts index 1d9ce5b..b3e882e 100644 --- a/server/src/services/process_service.ts +++ b/server/src/services/process_service.ts @@ -1,38 +1,44 @@ - - -var exitHandlers = [] -var exitHandled = false; - -/** - * Add a callback for closing programm bacause of any reason - * - * @param callback a sync function - */ -export function registerExitHandler(callback: () => void) { - exitHandlers.push(callback); -} - -function exitHandler(options, err) { - if(exitHandled) { - return; - } - exitHandled = true; - for(let i = 0; i < exitHandlers.length; i++) { - exitHandlers[i](); - } - console.log('process exit'); - process.exit(); -} - -//do something when app is closing -process.on('exit', exitHandler.bind(null,{cleanup:true})); - -//catches ctrl+c event -process.on('SIGINT', exitHandler.bind(null, {exit:true})); - -// catches "kill pid" (for example: nodemon restart) -process.on('SIGUSR1', exitHandler.bind(null, {exit:true})); -process.on('SIGUSR2', exitHandler.bind(null, {exit:true})); - -//catches uncaught exceptions -process.on('uncaughtException', exitHandler.bind(null, {exit:true})); \ No newline at end of file + + +var exitHandlers: (() => void)[] = []; +var exitHandled = false; + +/** + * Add a callback for closing programm bacause of any reason + * + * @param callback a sync function + */ +export function registerExitHandler(callback: () => void) { + exitHandlers.push(callback); +} + +function exitHandler(options: any, err?: any) { + if(exitHandled) { + return; + } + exitHandled = true; + for(let i = 0; i < exitHandlers.length; i++) { + exitHandlers[i](); + } + console.log('process exit'); + process.exit(); +} + +function catchException(options: any, err: any) { + console.log('Server exception:'); + console.log(err); + exitHandler({ exit: true }); +} + +//do something when app is closing +process.on('exit', exitHandler.bind(null, { cleanup:true })); + +//catches ctrl+c event +process.on('SIGINT', exitHandler.bind(null, { exit:true })); + +// catches "kill pid" (for example: nodemon restart) +process.on('SIGUSR1', exitHandler.bind(null, { exit:true })); +process.on('SIGUSR2', exitHandler.bind(null, { exit:true })); + +//catches uncaught exceptions +process.on('uncaughtException', catchException.bind(null, { exit:true })); \ No newline at end of file diff --git a/server/tsconfig.json b/server/tsconfig.json index e28057c..d5cef27 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,9 +1,7 @@ { "compilerOptions": { "sourceMap": true, - "noImplicitAny": false, "module": "commonjs", - "target": "es6", - "allowJs": true + "target": "es6" } }