diff --git a/.gitignore b/.gitignore index a4964b7d85653500bd8d78d3e822e9c2053eee0e..e1b45dc5c40ab9197e66a0cbbfdd7ef71a4ae887 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ *.log *.out *.pid +*.js +*.js.map /.trash /pids /logs @@ -10,3 +12,4 @@ /config/*.yaml /doc/build /coverage +/typings diff --git a/package.json b/package.json index 87749f103ccbd79144687ffb214de8be9c802a1e..7fd92ed711301d2a7b0b2ac03193fe6a5adda91b 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,10 @@ "description": "BlenDB", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "prestart": "tsc", + "start": "node build/src/boot", + "pretest": "tsc", + "test": "mocha" }, "repository": { "type": "git", @@ -31,7 +34,9 @@ "jshint": "^2.9.2", "mongodb": "^2.2.5", "osprey": "^0.3.2", - "raml2html": "^2.4.0" + "raml2html": "^2.4.0", + "typescript": "^1.8.10", + "typings": "^1.3.2" }, "devDependencies": { "chai": "^3.5.0", diff --git a/src/core/mongo.js b/src/api/controllers/collect.ts similarity index 66% rename from src/core/mongo.js rename to src/api/controllers/collect.ts index f6f4dccd44872a32870e6de3790cace342634976..76b3e3bd9ff18bbe9ccacbafb423558262c9386e 100644 --- a/src/core/mongo.js +++ b/src/api/controllers/collect.ts @@ -18,25 +18,17 @@ * along with blendb. If not, see <http://www.gnu.org/licenses/>. */ -'use strict'; +import * as express from "express"; -const MongoClient = require('mongodb').MongoClient; +export class CollectCtrl { + static write(req: express.Request, res: express.Response, next: express.NextFunction) { + if ('_id' in req.body) { + res.status(400) + .json({ message: 'Property named \'_id\' is protected.' }); + return; + } -class Mongo { - constructor() { - this.db = undefined; - } - - connect(connectionString) { - MongoClient.connect(connectionString, (err, db) => { - if (err) { - console.error(err); - return; - } - - this.db = db; - }); + res.status(500) + .json({ message: 'Error while writing to the database.' }); } } - -module.exports = new Mongo(); diff --git a/src/api/controllers/data.js b/src/api/controllers/data.js deleted file mode 100644 index 8edbaa99b6d35c4ed4ae06b98788c8af118e4fb2..0000000000000000000000000000000000000000 --- a/src/api/controllers/data.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre - * Departamento de Informatica - Universidade Federal do Parana - * - * This file is part of blendb. - * - * blendb is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * blendb is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with blendb. If not, see <http://www.gnu.org/licenses/>. - */ - -'use strict'; - -const aggregator = require('core/aggregator'); - -class Data { - read(req, res, next) { - let metrics = req.query.metrics.split(','); - let dimensions = req.query.dimensions.split(','); - - aggregator.query(metrics, dimensions, (err, data) => { - if (err) { - console.error(err); - res.status(500).json({ message: 'Query execution failed ' + - 'because of an unknown error.' }); - return; - } - - res.json({ data }); - }); - } -} - -module.exports = new Data(); diff --git a/src/api/controllers/data.ts b/src/api/controllers/data.ts new file mode 100644 index 0000000000000000000000000000000000000000..3497c5b5981f65ac5ee93b113359df4b2107d625 --- /dev/null +++ b/src/api/controllers/data.ts @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of blendb. + * + * blendb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * blendb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with blendb. If not, see <http://www.gnu.org/licenses/>. + */ + +import * as express from "express"; + +export class DataCtrl { + static read(req: express.Request, res: express.Response, next: express.NextFunction) { + let metrics = req.query.metrics.split(','); + let dimensions = req.query.dimensions.split(','); + + res.status(500).json({ message: 'Query execution failed ' + + 'because of an unknown error.' }); + } +} diff --git a/src/api/router-v1.js b/src/api/router-v1.ts similarity index 78% rename from src/api/router-v1.js rename to src/api/router-v1.ts index 7a95b16b7d9ced0ce4f1b2c7f43af19111fc9509..d8c4211c02d1504559597b66f66bb23ff6c0511a 100644 --- a/src/api/router-v1.js +++ b/src/api/router-v1.ts @@ -18,15 +18,13 @@ * along with blendb. If not, see <http://www.gnu.org/licenses/>. */ -'use strict'; - const osprey = require('osprey'); // import controllers -const data = require('./controllers/data'); -const collect = require('./controllers/collect'); +import { DataCtrl } from './controllers/data'; +import { CollectCtrl } from './controllers/collect'; -const router = module.exports = osprey.Router(); +export const router = osprey.Router(); -router.get('/data', data.read); -router.post('/collect/{class}', collect.write); +router.get('/data', DataCtrl.read); +router.post('/collect/{class}', CollectCtrl.write); diff --git a/src/boot.ts b/src/boot.ts new file mode 100755 index 0000000000000000000000000000000000000000..1eb4b57c8e8102c1740b638451d48367c1a9af5e --- /dev/null +++ b/src/boot.ts @@ -0,0 +1,59 @@ +#!/usr/bin/env node +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of blendb. + * + * blendb is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * blendb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with blendb. If not, see <http://www.gnu.org/licenses/>. + */ + +// external libraries +import express = require('express'); +import path = require('path'); + +const osprey = require('osprey'); +const ramlParser = require('raml-parser'); + +// create a new express app +const app = module.exports = express(); + +// load router +import { router } from './api/router-v1'; + +// parse the RAML spec and load osprey middleware +ramlParser.loadFile('specs/blendb-api-v1.raml') + .then((raml: any) => { + app.use('/v1', + osprey.security(raml), + osprey.server(raml), + router); + + if (!module.parent) { + let port = process.env.PORT || 3000; + app.listen(port); + + if (app.get('env') === 'development') { + console.log('Server listening on port ' + port + '.'); + } + } + else { + // signalize to the test suite that the server is ready to be tested + app.locals.ready = true; + } + }, + (err: any) => { + console.error('RAML Parsing Error: ' + err.message); + process.exit(1); + }); diff --git a/src/api/controllers/collect.js b/src/core/aggregate.spec.ts similarity index 54% rename from src/api/controllers/collect.js rename to src/core/aggregate.spec.ts index 518bf502570c334dc6524c70475d8df76bd69e0a..5db3d9a2861d2df3443c9458e0eb8f1850c2d038 100644 --- a/src/api/controllers/collect.js +++ b/src/core/aggregate.spec.ts @@ -18,30 +18,23 @@ * along with blendb. If not, see <http://www.gnu.org/licenses/>. */ -'use strict'; +import { expect } from 'chai'; -const mongo = require('core/mongo'); +import { Aggregate } from './aggregate'; -class Collect { - write(req, res, next) { - let collection = mongo.db.collection('raw.' + req.params.class); +describe('aggregate class', () => { + it('should be instantiated with an array metrics and one of dimensions', () => { + let aggr = new Aggregate(['met:one'], ['dim:one', 'dim:two']); + expect(aggr).to.be.an('object'); + }); - if ('_id' in req.body) { - res.status(400) - .json({ message: 'Property named \'_id\' is protected.' }); - return; - } + it('should not be instantiated with an empty array of metrics', () => { + let aggr = new Aggregate([], ['dim:one', 'dim:two']); + expect(aggr).to.be.an('object'); + }); - collection.insertOne(req.body, function (err, r) { - if (err) { - res.status(500) - .json({ message: 'Error while writing to the database.' }); - return; - } - - res.status(200).json({ _id: r.insertedId }); - }); - } -} - -module.exports = new Collect(); + it('should not be instantiated with an empty array of dimensions', () => { + let aggr = new Aggregate(['met:one'], []); + expect(aggr).to.be.an('object'); + }); +}); diff --git a/src/core/aggregate.js b/src/core/aggregate.ts similarity index 84% rename from src/core/aggregate.js rename to src/core/aggregate.ts index aece430c41bc67c6ff4a7092b40144bbd0ffa507..aa46fd8e598d764f103757d11efc5c4d33a64ce3 100644 --- a/src/core/aggregate.js +++ b/src/core/aggregate.ts @@ -18,17 +18,19 @@ * along with blendb. If not, see <http://www.gnu.org/licenses/>. */ -'use strict'; +export class Aggregate { + metrics: string[]; + dimensions: string[]; + data: any[]; -class Aggregate { - constructor(metrics, dimensions, options) { + constructor(metrics: string[], dimensions: string[], options?: any) { this.metrics = metrics; this.dimensions = dimensions; this.data = []; } - push(data) { + push(data: any) { this.data.push(data); } @@ -36,5 +38,3 @@ class Aggregate { this.data = []; } } - -module.exports = Aggregate; diff --git a/src/blendb.js b/src/core/server.ts similarity index 78% rename from src/blendb.js rename to src/core/server.ts index d1968a761a91f2f7bda2223104105cc2d8a77123..097d10008545795030d809076dd8801d8b1edbd1 100644 --- a/src/blendb.js +++ b/src/core/server.ts @@ -20,20 +20,24 @@ 'use strict'; -const hash = require('util/hash'); +import { Hash } from '../util/hash'; -const Source = require('core/source'); -const Transformer = require('core/transformer'); -const Aggregate = require('core/aggregate'); +import { Source } from './source'; +import { Transformer } from './transformer'; +import { Aggregate } from './aggregate'; + +export class Server { + sources: Map<string,Source>; + transformers: Map<string,Transformer>; + aggregates: Map<string,Aggregate>; -class BlenDB { constructor() { this.sources = new Map(); this.transformers = new Map(); this.aggregates = new Map(); } - source(name, options) { + source(name: string, options?: any) { if (this.sources.has(name)) { return this.sources.get(name); } @@ -44,7 +48,7 @@ class BlenDB { } } - transformer(name, options) { + transformer(name: string, options?: any) { if (this.transformers.has(name)) { return this.transformers.get(name); } @@ -55,11 +59,8 @@ class BlenDB { } } - aggregate(metrics, dimensions, options) { - metrics = Array.from(metrics); - dimensions = Array.from(dimensions); - - const id = hash.sha1(metrics.sort() + dimensions.sort()); + aggregate(metrics: string[], dimensions: string[], options?: any) { + const id = Hash.sha1(metrics.sort(), dimensions.sort()); if (this.aggregates.has(id)) { return this.aggregates.get(id); @@ -72,12 +73,12 @@ class BlenDB { } process() { - this.transformers.forEach((transformer) => { + this.transformers.forEach((transformer: Transformer) => { const source = this.source(transformer.source); const aggr = this.aggregate(transformer.metrics, transformer.dimensions); - source.forEach((doc) => { + source.forEach((doc: any) => { aggr.push({ metrics: transformer.extractMetrics(doc), dimensions: transformer.extractDimensions(doc) @@ -93,5 +94,3 @@ class BlenDB { console.log(this.aggregates); } } - -module.exports = { BlenDB, Source, Transformer }; diff --git a/src/core/source.js b/src/core/source.ts similarity index 77% rename from src/core/source.js rename to src/core/source.ts index 07ec973568fadec1ef9df03ab2a22b9b4db50404..38c8833de18f16226c7c13a327b1b3958cf7b953 100644 --- a/src/core/source.js +++ b/src/core/source.ts @@ -20,20 +20,23 @@ 'use strict'; -class Source { - constructor(name, options) { +export class Source { + name: string; + data: any[]; + + constructor(name: string, options: any) { this.name = name; this.data = []; } - push(doc) { + push(doc: any) { this.data.push(doc); } - forEach(callback) { - this.data.forEach(callback); + forEach(callback: Function) { + this.data.forEach((value: any, index: number, array: any[]) => { + callback(value); + }); } } - -module.exports = Source; diff --git a/src/core/transformer.js b/src/core/transformer.ts similarity index 74% rename from src/core/transformer.js rename to src/core/transformer.ts index c8e27a257777a48494a2533d65b251d205569163..fab5549dd79a8af65676a84b91312bb6ca656d4b 100644 --- a/src/core/transformer.js +++ b/src/core/transformer.ts @@ -20,24 +20,27 @@ 'use strict'; -class Transformer { - constructor(name, options) { +export class Transformer { + source: string; + metrics: string[]; + dimensions: string[]; + extractors: any; + + constructor(name: string, options: any) { this.source = options.source || null; this.metrics = options.metrics || []; this.dimensions = options.dimensions || []; this.extractors = { - metrics: options.extractors.metrics || ((doc) => null), - dimensions: options.extractors.dimensions || ((doc) => null) + metrics: options.extractors.metrics || ((doc: any): any => null), + dimensions: options.extractors.dimensions || ((doc: any): any => null) }; } - extractMetrics(doc) { + extractMetrics(doc: any) { return this.extractors.metrics(doc); } - extractDimensions(doc) { + extractDimensions(doc: any) { return this.extractors.dimensions(doc); } } - -module.exports = Transformer; diff --git a/test/util/hash.spec.js b/src/util/hash.spec.ts similarity index 72% rename from test/util/hash.spec.js rename to src/util/hash.spec.ts index 2de7ca6e2187a77a07bb68ab91597c006cb8ee8d..0aa910736b9ef869b7ddd29d10c7ecdd293d1300 100644 --- a/test/util/hash.spec.js +++ b/src/util/hash.spec.ts @@ -18,26 +18,21 @@ * along with blendb. If not, see <http://www.gnu.org/licenses/>. */ -'use strict'; +import { expect } from 'chai'; -/* globals expect */ -/* jshint -W024 */ -/* jshint expr:true, maxlen:false */ -/* jscs:disable maximumLineLength */ - -const hash = require('util/hash'); +import { Hash } from './hash'; describe('hash utility library', () => { it('should generate a sha1 hash for a collection of objects', () => { - let h = hash.sha1('test', { obj: 'test' }, ['list', 'of', 'things']); + let h = Hash.sha1('test', { obj: 'test' }, ['list', 'of', 'things']); expect(h).to.be.a('string'); expect(h).to.not.be.empty; }); it('should generate the same hash for the same input', () => { - let h1 = hash.sha1('test', { obj: 'test' }, ['list', 'of', 'things']); - let h2 = hash.sha1('test', { obj: 'test' }, ['list', 'of', 'things']); + let h1 = Hash.sha1('test', { obj: 'test' }, ['list', 'of', 'things']); + let h2 = Hash.sha1('test', { obj: 'test' }, ['list', 'of', 'things']); expect(h1).to.be.a('string'); expect(h2).to.be.a('string'); @@ -45,8 +40,8 @@ describe('hash utility library', () => { }); it('should generate the same hash for different order of input', () => { - let h1 = hash.sha1('test', { obj: 'test' }, ['list', 'of', 'things']); - let h2 = hash.sha1('test', ['list', 'of', 'things'], { obj: 'test' }); + let h1 = Hash.sha1('test', { obj: 'test' }, ['list', 'of', 'things']); + let h2 = Hash.sha1('test', ['list', 'of', 'things'], { obj: 'test' }); expect(h1).to.be.a('string'); expect(h2).to.be.a('string'); @@ -54,8 +49,8 @@ describe('hash utility library', () => { }); it('should not generate the same hash for distinct input', () => { - let h1 = hash.sha1('test', { obj: 'test' }, ['list', 'of', 'things']); - let h2 = hash.sha1('test', { obj: 'test', x: true }, + let h1 = Hash.sha1('test', { obj: 'test' }, ['list', 'of', 'things']); + let h2 = Hash.sha1('test', { obj: 'test', x: true }, ['list', 'of', 'things']); expect(h1).to.be.a('string'); @@ -64,8 +59,8 @@ describe('hash utility library', () => { }); it('should not generate the same hash for different order in deep lists', () => { - let h1 = hash.sha1('test', { obj: 'test' }, ['list', 'of', 'things']); - let h2 = hash.sha1('test', { obj: 'test' }, ['of', 'list', 'things']); + let h1 = Hash.sha1('test', { obj: 'test' }, ['list', 'of', 'things']); + let h2 = Hash.sha1('test', { obj: 'test' }, ['of', 'list', 'things']); expect(h1).to.be.a('string'); expect(h2).to.be.a('string'); diff --git a/src/util/hash.js b/src/util/hash.ts similarity index 93% rename from src/util/hash.js rename to src/util/hash.ts index 9a7501f66f812ab18b2075ade47cac16218fc6d6..f89379f0f14629ad2ca5e0e7bf824242533f1a63 100644 --- a/src/util/hash.js +++ b/src/util/hash.ts @@ -20,10 +20,10 @@ 'use strict'; -const crypto = require('crypto'); +import crypto = require('crypto'); -class Hash { - sha1(...objects) { +export class Hash { + static sha1(...objects: any[]): string { let hash = crypto.createHash('sha1'); objects @@ -46,5 +46,3 @@ class Hash { return hash.digest('hex'); } } - -module.exports = new Hash(); diff --git a/test/global.js b/test/global.ts similarity index 76% rename from test/global.js rename to test/global.ts index b3bc09cac34de9bb936e4c9c1eaf891549114fe6..557810b6f373c18e3b808d46d48da77fb8cad3f7 100644 --- a/test/global.js +++ b/test/global.ts @@ -18,12 +18,4 @@ * along with blendb. If not, see <http://www.gnu.org/licenses/>. */ -'use strict'; - process.env.NODE_ENV = 'test'; - -global.expect = require('chai').expect; - -// Add the ./src directory to require's search path to facilitate import -// modules later on (avoiding the require('../../../../module') problem). -require('app-module-path').addPath(__dirname + '/../src'); diff --git a/test/mocha.opts b/test/mocha.opts index 150fdfab63872536b78cb253d8be6af0c4a66fee..701d3701b30095eeb86e1bb2d12120a777b6b4d3 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,4 +1,4 @@ ---require ./test/global.js +--require ./build/test/global.js --reporter spec --ui bdd --recursive @@ -7,3 +7,4 @@ --slow 300 --check-leaks --globals expect +./build/**/*.spec.js diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..00e2014e5a9dc88fe25778da3f6a91f423c8f940 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "pretty": true, + "target": "es5", + "module": "commonjs", + "outDir": "./build", + "noImplicitAny": true, + "removeComments": true, + "preserveConstEnums": true, + "sourceMap": true + }, + "exclude": [ + "node_modules" + ], + "compileOnSave": false +} diff --git a/typings.json b/typings.json new file mode 100644 index 0000000000000000000000000000000000000000..82bbd7cac0a64e7e8a7572c7dc5f8aeefc423b3f --- /dev/null +++ b/typings.json @@ -0,0 +1,11 @@ +{ + "globalDependencies": { + "chai": "registry:dt/chai#3.4.0+20160601211834", + "express": "registry:dt/express#4.0.0+20160708185218", + "express-serve-static-core": "registry:dt/express-serve-static-core#4.0.0+20160817171221", + "mime": "registry:dt/mime#0.0.0+20160316155526", + "mocha": "registry:env/mocha#2.2.5+20160723033700", + "node": "registry:env/node#6.0.0+20160813135048", + "serve-static": "registry:dt/serve-static#0.0.0+20160606155157" + } +}