From 00a87decca32d5333e8bfe0864e2734c9dd9f29d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Victor=20Risso?= <jvtr12@c3sl.ufpr.br> Date: Wed, 17 Aug 2016 19:50:19 -0300 Subject: [PATCH] Change API to use ECMAScript6 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major modifications: - Source code is now in the src directory - All source files were adapted to ECMAScript6 - Add Gulpfile and Babel to transcompile the project from ES6 to ES5 (until Node.js fully supports ES6) - By running gulp one generates the build directory with the files transcompiled, from which the API can be run - Add ESLint to check for syntax errors and enforce code standards - Javascript coding standard currently adopted is AirBnB with 4 spaces - Replace monetdb package with monetdb-pool, which allows for concurrent queries over a pool of connections while maintaining the same interface TODO (in order of priority, from high to low): - Add Gulp tasks to handle automatic building, tests and running the server in order to deprecate using npm - Implement decorator to execute the SQL queries and reduce code duplication. - Implement SQL query builder (e.g. squel.js) to erradicate the need for embedding SQL directly into the code (which is error-prone). - Change enrollments route not to use route chaining in order to decide which SQL query is appropriate to respond the user's request. - Implement decorator for API responses and also reduce code duplication. - Split up tests into and add more test cases. Signed-off-by: João Victor Risso <jvtr12@c3sl.ufpr.br> --- .babelrc | 1 + .eslintrc.json | 13 ++ .gitignore | 1 - .gitlab-ci.yml | 2 +- build/config.json | 16 ++ build/libs/app.js | 43 +++++ build/libs/config.js | 7 + build/libs/db/monet.js | 24 +++ build/libs/db/query_decorator.js | 22 +++ build/libs/enrollment/all.js | 1 + build/libs/enrollment/city.js | 1 + build/libs/enrollment/common.js | 15 ++ build/libs/enrollment/country.js | 1 + build/libs/enrollment/region.js | 1 + build/libs/enrollment/state.js | 1 + build/libs/log.js | 33 ++++ build/libs/query_decorator.js | 22 +++ build/libs/routes/api.js | 308 +++++++++++++++++++++++++++++++ build/libs/routes/cities.js | 85 +++++++++ build/libs/routes/regions.js | 54 ++++++ build/libs/routes/states.js | 70 +++++++ build/logs/.gitignore | 1 + build/server.js | 18 ++ build/test/test.js | 139 ++++++++++++++ config.json | 2 +- gulpfile.babel.js | 15 ++ libs/app.js | 53 ------ libs/config.js | 9 - libs/db/monet.js | 19 -- libs/log.js | 35 ---- libs/routes/cities.js | 66 ------- libs/routes/regions.js | 45 ----- libs/routes/states.js | 55 ------ package.json | 13 +- server.js | 13 -- src/libs/app.js | 41 ++++ src/libs/config.js | 7 + src/libs/db/monet.js | 22 +++ src/libs/db/query_decorator.js | 25 +++ src/libs/enrollment/all.js | 0 src/libs/enrollment/city.js | 0 src/libs/enrollment/common.js | 14 ++ src/libs/enrollment/country.js | 0 src/libs/enrollment/region.js | 0 src/libs/enrollment/state.js | 0 src/libs/log.js | 34 ++++ {libs => src/libs}/routes/api.js | 193 ++++++++++--------- src/libs/routes/cities.js | 95 ++++++++++ src/libs/routes/regions.js | 58 ++++++ src/libs/routes/states.js | 77 ++++++++ src/server.js | 16 ++ src/test/test.js | 155 ++++++++++++++++ test/test.js | 158 ---------------- 53 files changed, 1544 insertions(+), 555 deletions(-) create mode 100644 .babelrc create mode 100644 .eslintrc.json create mode 100644 build/config.json create mode 100644 build/libs/app.js create mode 100644 build/libs/config.js create mode 100644 build/libs/db/monet.js create mode 100644 build/libs/db/query_decorator.js create mode 100644 build/libs/enrollment/all.js create mode 100644 build/libs/enrollment/city.js create mode 100644 build/libs/enrollment/common.js create mode 100644 build/libs/enrollment/country.js create mode 100644 build/libs/enrollment/region.js create mode 100644 build/libs/enrollment/state.js create mode 100644 build/libs/log.js create mode 100644 build/libs/query_decorator.js create mode 100644 build/libs/routes/api.js create mode 100644 build/libs/routes/cities.js create mode 100644 build/libs/routes/regions.js create mode 100644 build/libs/routes/states.js create mode 100644 build/logs/.gitignore create mode 100644 build/server.js create mode 100644 build/test/test.js create mode 100644 gulpfile.babel.js delete mode 100644 libs/app.js delete mode 100644 libs/config.js delete mode 100644 libs/db/monet.js delete mode 100644 libs/log.js delete mode 100644 libs/routes/cities.js delete mode 100644 libs/routes/regions.js delete mode 100644 libs/routes/states.js delete mode 100644 server.js create mode 100644 src/libs/app.js create mode 100644 src/libs/config.js create mode 100644 src/libs/db/monet.js create mode 100644 src/libs/db/query_decorator.js create mode 100644 src/libs/enrollment/all.js create mode 100644 src/libs/enrollment/city.js create mode 100644 src/libs/enrollment/common.js create mode 100644 src/libs/enrollment/country.js create mode 100644 src/libs/enrollment/region.js create mode 100644 src/libs/enrollment/state.js create mode 100644 src/libs/log.js rename {libs => src/libs}/routes/api.js (65%) create mode 100644 src/libs/routes/cities.js create mode 100644 src/libs/routes/regions.js create mode 100644 src/libs/routes/states.js create mode 100644 src/server.js create mode 100644 src/test/test.js delete mode 100644 test/test.js diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000..834a48a4 --- /dev/null +++ b/.babelrc @@ -0,0 +1 @@ +{ 'presets': ['es2015'] } diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..52b136d7 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,13 @@ +{ + "extends": "airbnb", + "plugins": [ + "react", + "jsx-a11y", + "import" + ], + "rules": { + "indent": [ "error", 4 ], + "no-unused-vars": [ "error", { "args": "none" }], + "no-param-reassign": [ "off" ] + } +} diff --git a/.gitignore b/.gitignore index 1be0326a..f4e4627c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ lib-cov *.gz pids -logs results npm-debug.log diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3171c9f7..a3c44c8f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,6 +7,6 @@ before_script: run_tests: stage: test script: - - npm test + - gulp && cd build/ && mocha tags: - node diff --git a/build/config.json b/build/config.json new file mode 100644 index 00000000..c919cf1c --- /dev/null +++ b/build/config.json @@ -0,0 +1,16 @@ +{ + "port": 3000, + "monetdb": { + "host": "localhost", + "port": 50000, + "dbname": "simcaq_dev", + "user": "monetdb", + "password":"monetdb", + "nrConnections": 16 + }, + "default": { + "api": { + "version" : "v1" + } + } +} diff --git a/build/libs/app.js b/build/libs/app.js new file mode 100644 index 00000000..a678b1c4 --- /dev/null +++ b/build/libs/app.js @@ -0,0 +1,43 @@ +'use strict'; + +var express = require('express'); +var cookieParser = require('cookie-parser'); +var bodyParser = require('body-parser'); +var methodOverride = require('method-override'); +var cors = require('cors'); + +var log = require('./log')(module); + +var api = require('./routes/api'); +var states = require('./routes/states'); +var regions = require('./routes/regions'); +var cities = require('./routes/cities'); + +var app = express(); + +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(cors()); +app.use(methodOverride()); + +app.use('/v1/', api); +app.use('/v1/states', states); +app.use('/v1/regions', regions); +app.use('/v1/cities', cities); + +// catch 404 and forward to error handler +app.use(function (req, res, next) { + res.status(404); + log.debug('%s %d %s', req.method, res.statusCode, req.url); + res.json({ error: 'Not found' }).end(); +}); + +// error handlers +app.use(function (err, req, res, next) { + res.status(err.status || 500); + log.error('%s %d %s', req.method, res.statusCode, err.message); + res.json({ error: err.message }).end(); +}); + +module.exports = app; \ No newline at end of file diff --git a/build/libs/config.js b/build/libs/config.js new file mode 100644 index 00000000..2f7bd18e --- /dev/null +++ b/build/libs/config.js @@ -0,0 +1,7 @@ +'use strict'; + +var nconf = require('nconf'); + +nconf.argv().env().file({ file: process.cwd() + '/config.json' }); + +module.exports = nconf; \ No newline at end of file diff --git a/build/libs/db/monet.js b/build/libs/db/monet.js new file mode 100644 index 00000000..c8d29099 --- /dev/null +++ b/build/libs/db/monet.js @@ -0,0 +1,24 @@ +'use strict'; + +var MonetDBPool = require('monetdb-pool'); + +var libs = process.cwd() + '/libs'; + +var config = require(libs + '/config'); + +var poolOptions = { + nrConnections: config.get('monetdb:nrConnections') +}; + +var options = { + host: config.get('monetdb:host'), + port: config.get('monetdb:port'), + dbname: config.get('monetdb:dbname'), + user: config.get('monetdb:user'), + password: config.get('monetdb:password') +}; + +var conn = new MonetDBPool(poolOptions, options); +conn.connect(); + +module.exports = conn; \ No newline at end of file diff --git a/build/libs/db/query_decorator.js b/build/libs/db/query_decorator.js new file mode 100644 index 00000000..ce0697d7 --- /dev/null +++ b/build/libs/db/query_decorator.js @@ -0,0 +1,22 @@ +"use strict"; + +var libs = process.cwd() + "/libs"; +var log = require(libs + "/log")(module); +var conn = require(libs + "/db/monet"); + +/** + * Basic decorator to wrap SQL query strings + */ +exports.execQuery = function (sqlQuery, sqlQueryParams) { + log.debug("Executing SQL query '" + sqlQuery + "' with params '" + sqlQueryParams + "'"); + conn.prepare(sqlQuery, true).then(function (dbQuery) { + dbQuery.exec(sqlQueryParams).then(function (dbResult) { + log.debug(dbResult.data); + log.debug("Query result: " + dbResult.data); + return dbResult; + }, function (error) { + log.error("SQL query execution error: " + error.message); + return error; + }); + }); +}; \ No newline at end of file diff --git a/build/libs/enrollment/all.js b/build/libs/enrollment/all.js new file mode 100644 index 00000000..9a390c31 --- /dev/null +++ b/build/libs/enrollment/all.js @@ -0,0 +1 @@ +"use strict"; \ No newline at end of file diff --git a/build/libs/enrollment/city.js b/build/libs/enrollment/city.js new file mode 100644 index 00000000..9a390c31 --- /dev/null +++ b/build/libs/enrollment/city.js @@ -0,0 +1 @@ +"use strict"; \ No newline at end of file diff --git a/build/libs/enrollment/common.js b/build/libs/enrollment/common.js new file mode 100644 index 00000000..9c788951 --- /dev/null +++ b/build/libs/enrollment/common.js @@ -0,0 +1,15 @@ +'use strict'; + +var libs = process.cwd() + '/libs'; + +var log = require(libs + '/log')(module); + +var sqlDecorator = require(libs + '/query_decorator'); + +var yearRange = function yearRange() { + var yearSql = 'SELECT MIN(t.ano_censo) AS start_year, MAX(t.ano_censo)' + 'AS end_year FROM turmas AS t'; + log.debug('Generated SQL query for enrollments\' year range'); + return sqlDecorator.execQuery(yearSql, []); +}; + +module.exports = yearRange; \ No newline at end of file diff --git a/build/libs/enrollment/country.js b/build/libs/enrollment/country.js new file mode 100644 index 00000000..9a390c31 --- /dev/null +++ b/build/libs/enrollment/country.js @@ -0,0 +1 @@ +"use strict"; \ No newline at end of file diff --git a/build/libs/enrollment/region.js b/build/libs/enrollment/region.js new file mode 100644 index 00000000..9a390c31 --- /dev/null +++ b/build/libs/enrollment/region.js @@ -0,0 +1 @@ +"use strict"; \ No newline at end of file diff --git a/build/libs/enrollment/state.js b/build/libs/enrollment/state.js new file mode 100644 index 00000000..9a390c31 --- /dev/null +++ b/build/libs/enrollment/state.js @@ -0,0 +1 @@ +"use strict"; \ No newline at end of file diff --git a/build/libs/log.js b/build/libs/log.js new file mode 100644 index 00000000..e93d2ff2 --- /dev/null +++ b/build/libs/log.js @@ -0,0 +1,33 @@ +'use strict'; + +var winston = require('winston'); + +winston.emitErrs = true; + +function getFilePath(module) { + // using filename in log statements + return module.filename.split('/').slice(-2).join('/'); +} + +function logger(module) { + return new winston.Logger({ + transports: [new winston.transports.File({ + level: 'info', + filename: process.cwd() + '/logs/all.log', + handleException: true, + json: false, + maxSize: 5242880, // 5MB + maxFiles: 2, + colorize: false + }), new winston.transports.Console({ + level: 'debug', + label: getFilePath(module), + handleException: true, + json: true, + colorize: true + })], + exitOnError: false + }); +} + +module.exports = logger; \ No newline at end of file diff --git a/build/libs/query_decorator.js b/build/libs/query_decorator.js new file mode 100644 index 00000000..ce0697d7 --- /dev/null +++ b/build/libs/query_decorator.js @@ -0,0 +1,22 @@ +"use strict"; + +var libs = process.cwd() + "/libs"; +var log = require(libs + "/log")(module); +var conn = require(libs + "/db/monet"); + +/** + * Basic decorator to wrap SQL query strings + */ +exports.execQuery = function (sqlQuery, sqlQueryParams) { + log.debug("Executing SQL query '" + sqlQuery + "' with params '" + sqlQueryParams + "'"); + conn.prepare(sqlQuery, true).then(function (dbQuery) { + dbQuery.exec(sqlQueryParams).then(function (dbResult) { + log.debug(dbResult.data); + log.debug("Query result: " + dbResult.data); + return dbResult; + }, function (error) { + log.error("SQL query execution error: " + error.message); + return error; + }); + }); +}; \ No newline at end of file diff --git a/build/libs/routes/api.js b/build/libs/routes/api.js new file mode 100644 index 00000000..812eae43 --- /dev/null +++ b/build/libs/routes/api.js @@ -0,0 +1,308 @@ +'use strict'; + +var express = require('express'); + +var xml = require('js2xmlparser'); + +var enrollmentApp = express(); + +var libs = process.cwd() + '/libs'; + +var log = require(libs + '/log')(module); + +var conn = require(libs + '/db/monet'); + +function response(req, res) { + if (req.query.format === 'csv') { + res.csv(req.result.data); + } else if (req.query.format === 'xml') { + res.send(xml('result', JSON.stringify({ state: req.result.data }))); + } else { + res.json({ result: req.result.data }); + } +} + +enrollmentApp.get('/', function (req, res) { + res.json({ msg: 'SimCAQ API is running' }); +}); + +/** + * Complete range of the enrollments dataset + * + * Returns a tuple of start and ending years of the complete enrollments dataset. + */ +enrollmentApp.get('/year_range', function (req, res) { + var yearSql = 'SELECT MIN(t.ano_censo) AS start_year, MAX(t.ano_censo)' + 'AS end_year FROM turmas AS t'; + + conn.query(yearSql, true).then(function (dbResult) { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, function (dbError) { + log.error('SQL query execution error: ' + dbError.message); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + }); +}); + +enrollmentApp.get('/education_level', function (req, res) { + var edLevelSql = 'SELECT pk_etapa_ensino_id AS id, desc_etapa AS ' + 'education_level FROM etapa_ensino'; + conn.query(edLevelSql, true).then(function (dbResult) { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, function (dbError) { + log.error('SQL query error: ' + dbError.message); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + }); +}); + +enrollmentApp.get('/data', function (req, res) { + log.debug(req.query); + log.debug(req.query.met); + log.debug(req.query.dim); + var schoolClassSql = 'SELECT * FROM turmas'; + conn.query(schoolClassSql, true).then(function (dbResult) { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, function (dbError) { + log.error('SQL query error: ' + dbError.message); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + }); +}); + +enrollmentApp.use('/enrollments', function (req, res, next) { + var params = req.query; + req.paramCnt = 0; + + if (typeof params.id !== 'undefined') { + req.id = parseInt(params.id, 10); + req.paramCnt += 1; + } + + if (typeof params.location_id !== 'undefined') { + req.location_id = parseInt(params.location_id, 10); + req.paramCnt += 1; + } + + if (typeof params.adm_dependency_id !== 'undefined') { + req.adm_dependency_id = parseInt(params.adm_dependency_id, 10); + req.paramCnt += 1; + } + + if (typeof params.census_year !== 'undefined') { + req.census_year = parseInt(params.census_year, 10); + req.paramCnt += 1; + } + + if (typeof params.education_level_id !== 'undefined') { + req.education_level_id = parseInt(params.education_level_id, 10); + req.paramCnt += 1; + } + + next(); +}); + +enrollmentApp.use('/enrollments', function (req, res, next) { + var params = req.query; + if (typeof params.aggregate !== 'undefined' && params.aggregate === 'region') { + log.debug('Using enrollments query for regions'); + req.sqlQuery = 'SELECT r.nome AS name, COALESCE(SUM(t.num_matriculas), 0) AS total ' + 'FROM regioes AS r ' + 'INNER JOIN estados AS e ON r.pk_regiao_id = e.fk_regiao_id ' + 'INNER JOIN municipios AS m ON e.pk_estado_id = m.fk_estado_id ' + 'LEFT OUTER JOIN turmas AS t ON ( ' + 'm.pk_municipio_id = t.fk_municipio_id '; + req.sqlQueryParams = []; + + if (typeof req.census_year !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.ano_censo = ?'; + req.sqlQueryParams.push(req.census_year); + } + + if (typeof req.adm_dependency_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.fk_dependencia_adm_id = ?'; + req.sqlQueryParams.push(req.adm_dependency_id); + } + + if (typeof req.location_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.id_localizacao = ?'; + req.sqlQueryParams.push(req.location_id); + } + + if (typeof req.education_level_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.fk_etapa_ensino_id = ?'; + req.sqlQueryParams.push(req.education_level_id); + } + + req.sqlQuery += ')'; + if (typeof req.id !== 'undefined') { + req.sqlQuery += ' WHERE '; + req.sqlQuery += 'r.pk_regiao_id = ?'; + req.sqlQueryParams.push(req.id); + } + req.sqlQuery += ' GROUP BY r.nome'; + } + next(); +}); + +enrollmentApp.use('/enrollments', function (req, res, next) { + var params = req.query; + if (typeof params.aggregate !== 'undefined' && params.aggregate === 'state') { + log.debug('Using enrollments query for states'); + req.sqlQuery = 'SELECT e.nome AS name, COALESCE(SUM(t.num_matriculas), 0) as total ' + 'FROM estados AS e ' + 'INNER JOIN municipios AS m ON m.fk_estado_id = e.pk_estado_id ' + 'LEFT OUTER JOIN turmas AS t ON (' + 'm.pk_municipio_id = t.fk_municipio_id '; + req.sqlQueryParams = []; + + if (typeof req.census_year !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.ano_censo = ?'; + req.sqlQueryParams.push(req.census_year); + } + + if (typeof req.adm_dependency_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.fk_dependencia_adm_id = ?'; + req.sqlQueryParams.push(req.adm_dependency_id); + } + + if (typeof req.location_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.id_localizacao = ?'; + req.sqlQueryParams.push(req.location_id); + } + + if (typeof req.education_level_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.fk_etapa_ensino_id = ?'; + req.sqlQueryParams.push(req.education_level_id); + } + + req.sqlQuery += ')'; + + if (typeof req.id !== 'undefined') { + req.sqlQuery += ' WHERE '; + req.sqlQuery += 'e.pk_estado_id = ?'; + req.sqlQueryParams.push(req.id); + } + + req.sqlQuery += ' GROUP BY e.nome'; + } + next(); +}); + +enrollmentApp.use('/enrollments', function (req, res, next) { + var params = req.query; + if (typeof params.aggregate !== 'undefined' && params.aggregate === 'city') { + log.debug('Using enrollments query for cities'); + req.sqlQuery = 'SELECT m.nome AS name, COALESCE(SUM(t.num_matriculas), 0) as total ' + 'FROM municipios AS m ' + 'LEFT OUTER JOIN turmas AS t ON ( ' + 'm.pk_municipio_id = t.fk_municipio_id'; + req.sqlQueryParams = []; + + if (typeof req.census_year !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.ano_censo = ?'; + req.sqlQueryParams.push(req.census_year); + } + + if (typeof req.adm_dependency_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.fk_dependencia_adm_id = ?'; + req.sqlQueryParams.push(req.adm_dependency_id); + } + + if (typeof req.location_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.id_localizacao = ?'; + req.sqlQueryParams.push(req.location_id); + } + + if (typeof req.education_level_id !== 'undefined') { + req.sqlQuery += ' AND '; + req.sqlQuery += 't.fk_etapa_ensino_id = ?'; + req.sqlQueryParams.push(req.education_level_id); + } + + req.sqlQuery += ')'; + + if (typeof req.id !== 'undefined') { + req.sqlQuery += ' WHERE '; + req.sqlQuery += 'm.pk_municipio_id = ?'; + req.sqlQueryParams.push(req.id); + } + + req.sqlQuery += 'GROUP BY m.nome'; + } + next(); +}); + +enrollmentApp.use('/enrollments', function (req, res, next) { + var params = req.query; + if (typeof params.aggregate === 'undefined') { + log.debug('Using enrollments query for the whole country'); + req.sqlQuery = 'SELECT \'Brasil\' AS name, COALESCE(SUM(t.num_matriculas),0) AS total ' + 'FROM turmas AS t'; + req.sqlQueryParams = []; + + if (req.paramCnt > 0) { + req.sqlQuery += ' WHERE '; + } + + if (typeof req.census_year !== 'undefined') { + req.sqlQuery += 't.ano_censo = ?'; + req.sqlQueryParams.push(req.census_year); + } + + if (typeof req.adm_dependency_id !== 'undefined') { + if (req.sqlQueryParams.length > 0) { + req.sqlQuery += ' AND '; + } + req.sqlQuery += 't.fk_dependencia_adm_id = ?'; + req.sqlQueryParams.push(req.adm_dependency_id); + } + + if (typeof req.location_id !== 'undefined') { + if (req.sqlQueryParams.length > 0) { + req.sqlQuery += ' AND '; + } + req.sqlQuery += 't.id_localizacao = ?'; + req.sqlQueryParams.push(req.location_id); + } + + if (typeof req.education_level_id !== 'undefined') { + if (req.sqlQueryParams.length > 0) { + req.sqlQuery += ' AND '; + } + req.sqlQuery += 't.fk_etapa_ensino_id = ?'; + req.sqlQueryParams.push(req.education_level_id); + } + } + next(); +}); + +enrollmentApp.get('/enrollments', function (req, res, next) { + log.debug('Request parameters: ' + req); + if (typeof req.sqlQuery === 'undefined') { + // Should only happen if there is a bug in the chaining of the + // '/enrollments' route, since when no +aggregate+ parameter is given, + // it defaults to use the query for the whole country. + log.error('BUG -- No SQL query was found to be executed!'); + res.send('Request could not be satisfied due to an internal error'); + } else { + log.debug('SQL query: ${ req.sqlQuery }?'); + log.debug(req.sqlQuery); + + conn.prepare(req.sqlQuery, true).then(function (dbQuery) { + dbQuery.exec(req.sqlQueryParams).then(function (dbResult) { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, function (dbError) { + log.error('SQL query execution error: ' + dbError.message); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + }); + }); + } +}); + +module.exports = enrollmentApp; \ No newline at end of file diff --git a/build/libs/routes/cities.js b/build/libs/routes/cities.js new file mode 100644 index 00000000..f549624e --- /dev/null +++ b/build/libs/routes/cities.js @@ -0,0 +1,85 @@ +'use strict'; + +var express = require('express'); + +var xml = require('js2xmlparser'); + +var cityApp = express(); + +var libs = process.cwd() + '/libs'; + +var log = require(libs + '/log')(module); + +var conn = require(libs + '/db/monet'); + +function response(req, res) { + if (req.query.format === 'csv') { + res.csv(req.result.data); + } else if (req.query.format === 'xml') { + res.send(xml('result', JSON.stringify({ city: req.result.data }))); + } else { + res.json({ result: req.result.data }); + } +} + +cityApp.get('/', function (req, res) { + conn.query('SELECT * FROM municipios', true).then(function (dbResult) { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, function (dbError) { + log.error('SQL query execution error: ' + dbError.message); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + }); +}); + +cityApp.get('/:id', function (req, res) { + var citySql = 'SELECT * FROM municipios WHERE pk_municipio_id = ?'; + var cityId = parseInt(req.params.id, 10); + conn.prepare(citySql, true).then(function (dbQuery) { + dbQuery.exec([cityId]).then(function (dbResult) { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, function (dbError) { + log.error('SQL query execution error: ' + dbError.message); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + }); + }); +}); + +cityApp.get('/ibge/:id', function (req, res) { + var citySql = 'SELECT * FROM municipios WHERE codigo_ibge = ?'; + var cityId = parseInt(req.params.id, 10); + conn.prepare(citySql, true).then(function (dbQuery) { + dbQuery.exec([cityId]).then(function (dbResult) { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, function (dbError) { + log.error('SQL query execution error: ' + dbError.message); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + }); + }); +}); + +cityApp.get('/state/:id', function (req, res) { + var citySql = 'SELECT * FROM municipios WHERE fk_estado_id = ?'; + var stateId = parseInt(req.params.id, 10); + conn.prepare(citySql, true).then(function (dbQuery) { + dbQuery.exec([stateId]).then(function (dbResult) { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, function (dbError) { + log.error('SQL query execution error: ' + dbError.message); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + }); + }); +}); + +module.exports = cityApp; \ No newline at end of file diff --git a/build/libs/routes/regions.js b/build/libs/routes/regions.js new file mode 100644 index 00000000..75cf2bee --- /dev/null +++ b/build/libs/routes/regions.js @@ -0,0 +1,54 @@ +'use strict'; + +var express = require('express'); + +var xml = require('js2xmlparser'); + +var regionApp = express(); + +var libs = process.cwd() + '/libs'; + +var log = require(libs + '/log')(module); + +var conn = require(libs + '/db/monet'); + +function response(req, res) { + if (req.query.format === 'csv') { + res.csv(req.result.data); + } else if (req.query.format === 'xml') { + res.send(xml('result', JSON.stringify({ state: req.result.data }))); + } else { + res.json({ result: req.result.data }); + } +} + +regionApp.get('/', function (req, res) { + var regionSql = 'SELECT * FROM regioes'; + conn.query(regionSql, true).then(function (dbResult) { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, function (dbError) { + log.error('SQL query execution error: ' + dbError.message); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + }); +}); + +regionApp.get('/:id', function (req, res) { + var regionSql = 'SELECT * FROM regioes WHERE pk_regiao_id = ?'; + var regionId = parseInt(req.params.id, 10); + conn.prepare(regionSql, true).then(function (dbQuery) { + dbQuery.exec([regionId]).then(function (dbResult) { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, function (dbError) { + log.error('SQL query execution error: ' + dbError.message); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + }); + }); +}); + +module.exports = regionApp; \ No newline at end of file diff --git a/build/libs/routes/states.js b/build/libs/routes/states.js new file mode 100644 index 00000000..8579f392 --- /dev/null +++ b/build/libs/routes/states.js @@ -0,0 +1,70 @@ +'use strict'; + +var express = require('express'); + +var xml = require('js2xmlparser'); + +var stateApp = express(); + +var libs = process.cwd() + '/libs'; + +var log = require(libs + '/log')(module); + +var conn = require(libs + '/db/monet'); + +function response(req, res) { + if (req.query.format === 'csv') { + res.csv(req.result.data); + } else if (req.query.format === 'xml') { + res.send(xml('result', JSON.stringify({ state: req.result.data }))); + } else { + res.json({ result: req.result.data }); + } +} + +stateApp.get('/', function (req, res, next) { + var stateSql = 'SELECT * FROM estados'; + conn.query(stateSql, true).then(function (dbResult) { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, function (dbError) { + log.error('SQL query execution error: ' + dbError.message); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + }); +}); + +stateApp.get('/:id', function (req, res, next) { + var stateSql = 'SELECT * FROM estados WHERE pk_estado_id = ?'; + var stateId = parseInt(req.params.id, 10); + conn.prepare(stateSql, true).then(function (dbQuery) { + dbQuery.exec([stateId]).then(function (dbResult) { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, function (dbError) { + log.error('SQL query execution error: ' + dbError.message); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + }); + }); +}); + +stateApp.get('/region/:id', function (req, res, next) { + var stateSql = 'SELECT * FROM estados WHERE fk_regiao_id = ?'; + var regionId = parseInt(req.params.id, 10); + conn.prepare(stateSql, true).then(function (dbQuery) { + dbQuery.exec([regionId]).then(function (dbResult) { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, function (dbError) { + log.error('SQL query execution error: ' + dbError.message); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + }); + }); +}); + +module.exports = stateApp; \ No newline at end of file diff --git a/build/logs/.gitignore b/build/logs/.gitignore new file mode 100644 index 00000000..397b4a76 --- /dev/null +++ b/build/logs/.gitignore @@ -0,0 +1 @@ +*.log diff --git a/build/server.js b/build/server.js new file mode 100644 index 00000000..5f2171cc --- /dev/null +++ b/build/server.js @@ -0,0 +1,18 @@ +'use strict'; + +var debug = require('debug')('node-express-base'); + +var libs = process.cwd() + '/libs'; + +var config = require(libs + '/config'); + +var log = require(libs + '/log')(module); + +var app = require(libs + '/app'); + +app.set('port', config.get('port') || 3000); + +var server = app.listen(app.get('port'), function () { + debug('Express server listening on port ' + server.address().port); + log.info('Express server listening on port ' + config.get('port')); +}); \ No newline at end of file diff --git a/build/test/test.js b/build/test/test.js new file mode 100644 index 00000000..55caa55c --- /dev/null +++ b/build/test/test.js @@ -0,0 +1,139 @@ +'use strict'; + +var chai = require('chai'); +var chaiHttp = require('chai-http'); +var assert = chai.assert; +var expect = chai.expect; +var should = chai.should(); // actually call the function +var server = require('../libs/app'); + +chai.use(chaiHttp); + +describe('request enrollments', function () { + it('should list enrollments', function (done) { + chai.request(server).get('/v1/enrollments').end(function (err, res) { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('name'); + res.body.result[0].should.have.property('total'); + done(); + }); + }); +}); + +describe('request regions', function () { + it('should list all regions', function (done) { + chai.request(server).get('/v1/regions').end(function (err, res) { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_regiao_id'); + res.body.result[0].should.have.property('nome'); + done(); + }); + }); + + it('should list region by id', function (done) { + chai.request(server).get('/v1/regions/1').end(function (err, res) { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result.should.have.length(1); + res.body.result[0].should.have.property('pk_regiao_id'); + res.body.result[0].should.have.property('nome'); + done(); + }); + }); +}); + +describe('request states', function () { + + it('should list all states', function (done) { + chai.request(server).get('/v1/states').end(function (err, res) { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_estado_id'); + res.body.result[0].should.have.property('fk_regiao_id'); + res.body.result[0].should.have.property('nome'); + done(); + }); + }); + + it('should list a state by id', function (done) { + chai.request(server).get('/v1/states/11').end(function (err, res) { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result.should.have.length(1); + res.body.result[0].should.have.property('pk_estado_id'); + res.body.result[0].should.have.property('fk_regiao_id'); + res.body.result[0].should.have.property('nome'); + done(); + }); + }); + + it('should list states by region id', function (done) { + chai.request(server).get('/v1/states/region/1').end(function (err, res) { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_estado_id'); + res.body.result[0].should.have.property('fk_regiao_id'); + res.body.result[0].should.have.property('nome'); + done(); + }); + }); +}); + +describe('request cities', function () { + + it('should list all cities', function (done) { + chai.request(server).get('/v1/cities').end(function (err, res) { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_municipio_id'); + res.body.result[0].should.have.property('fk_estado_id'); + res.body.result[0].should.have.property('nome'); + res.body.result[0].should.have.property('codigo_ibge'); + done(); + }); + }); + + it('should list a city by id', function (done) { + chai.request(server).get('/v1/cities/1').end(function (err, res) { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_municipio_id'); + res.body.result[0].should.have.property('fk_estado_id'); + res.body.result[0].should.have.property('nome'); + res.body.result[0].should.have.property('codigo_ibge'); + done(); + }); + }); + + it('should list a city by codigo_ibge', function (done) { + chai.request(server).get('/v1/cities/ibge/1200013').end(function (err, res) { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_municipio_id'); + res.body.result[0].should.have.property('fk_estado_id'); + res.body.result[0].should.have.property('nome'); + res.body.result[0].should.have.property('codigo_ibge'); + done(); + }); + }); +}); \ No newline at end of file diff --git a/config.json b/config.json index 8f60036a..1da8951e 100644 --- a/config.json +++ b/config.json @@ -1,7 +1,7 @@ { "port": 3000, "monetdb": { - "host": "simcaqdb1", + "host": "localhost", "port": 50000, "dbname": "simcaq_dev", "user": "monetdb", diff --git a/gulpfile.babel.js b/gulpfile.babel.js new file mode 100644 index 00000000..e07481f9 --- /dev/null +++ b/gulpfile.babel.js @@ -0,0 +1,15 @@ +const gulp = require('gulp'); +const babel = require('gulp-babel'); +const eslint = require('gulp-eslint'); + +gulp.task('default', () => { + // run ESLint + gulp.src('src/**/*.js') + .pipe(eslint()) + .pipe(eslint.format()); + + // compile source to ES5 + gulp.src('src/**/*.js') + .pipe(babel()) + .pipe(gulp.dest('build')); +}); diff --git a/libs/app.js b/libs/app.js deleted file mode 100644 index 544f649b..00000000 --- a/libs/app.js +++ /dev/null @@ -1,53 +0,0 @@ -var express = require('express') -var path = require('path') -var cookieParser = require('cookie-parser') -var bodyParser = require('body-parser') -var csv = require('csv-express') -var xml = require('js2xmlparser') -var methodOverride = require('method-override') -var cors = require('cors') - -var libs = process.cwd() + '/libs/' - -var config = require('./config') -var log = require('./log')(module) - -var api = require('./routes/api') -var states = require('./routes/states') -var regions = require('./routes/regions') -var cities = require('./routes/cities') - -var app = express() - -app.use(bodyParser.json()) -app.use(bodyParser.urlencoded({ extended: false })) -app.use(cookieParser()) -app.use(cors()) -app.use(methodOverride()) - -app.use('/v1/', api) -app.use('/v1/states', states) -app.use('/v1/regions', regions) -app.use('/v1/cities', cities) - -// catch 404 and forward to error handler -app.use(function(req, res, next){ - res.status(404) - log.debug('%s %d %s', req.method, res.statusCode, req.url) - res.json({ - error: 'Not found' - }) - return -}) - -// error handlers -app.use(function(err, req, res, next){ - res.status(err.status || 500) - log.error('%s %d %s', req.method, res.statusCode, err.message) - res.json({ - error: err.message - }) - return -}) - -module.exports = app diff --git a/libs/config.js b/libs/config.js deleted file mode 100644 index 78f7831a..00000000 --- a/libs/config.js +++ /dev/null @@ -1,9 +0,0 @@ -var nconf = require('nconf') - -nconf.argv() - .env() - .file({ - file: process.cwd() + '/config.json' - }) - -module.exports = nconf diff --git a/libs/db/monet.js b/libs/db/monet.js deleted file mode 100644 index 0b554285..00000000 --- a/libs/db/monet.js +++ /dev/null @@ -1,19 +0,0 @@ -var mdb = require('monetdb')() - -var libs = process.cwd() + '/libs/' - -var log = require(libs + 'log')(module) -var config = require(libs + 'config') - -var options = { - host: config.get('monetdb:host'), - port: config.get('monetdb:port'), - dbname: config.get('monetdb:dbname'), - user: config.get('monetdb:user'), - password: config.get('monetdb:password') -} - -var conn = new mdb(options) -conn.connect() - -module.exports = conn diff --git a/libs/log.js b/libs/log.js deleted file mode 100644 index 419b3e4b..00000000 --- a/libs/log.js +++ /dev/null @@ -1,35 +0,0 @@ -var winston = require('winston') - -winston.emitErrs = true - -function logger(module) { - - return new winston.Logger({ - transports : [ - new winston.transports.File({ - level: 'info', - filename: process.cwd() + '/logs/all.log', - handleException: true, - json: false, - maxSize: 5242880, //5mb - maxFiles: 2, - colorize: false - }), - new winston.transports.Console({ - level: 'debug', - label: getFilePath(module), - handleException: true, - json: true, - colorize: true - }) - ], - exitOnError: false - }) -} - -function getFilePath (module ) { - //using filename in log statements - return module.filename.split('/').slice(-2).join('/') -} - -module.exports = logger diff --git a/libs/routes/cities.js b/libs/routes/cities.js deleted file mode 100644 index 6a222049..00000000 --- a/libs/routes/cities.js +++ /dev/null @@ -1,66 +0,0 @@ -var express = require('express') -var xml = require('js2xmlparser') -var router = express.Router() - -var libs = process.cwd() + '/libs/' - -var log = require(libs + 'log')(module) -var config = require(libs + 'config') - -var conn = require(libs + 'db/monet') - -function response(req, res) { - if (req.query.format === 'csv') { - res.csv(req.result.data) - } else if (req.query.format === 'xml') { - res.send(xml("result", JSON.stringify({city: req.result.data}))) - } - else { - res.json({ - result: req.result.data - }) - } -} - -router.get('/', function(req, res) { - conn.query( - 'SELECT * FROM municipios', true - ).then(function(result) { - log.debug(result) - req.result = result - response(req, res) - }) - -}) - -router.get('/:id', function(req, res) { - conn.query( - 'SELECT * FROM municipios WHERE pk_municipio_id='+req.params.id, true - ).then(function(result) { - log.debug(result) - req.result = result - response(req, res) - }) -}) - -router.get('/ibge/:id', function(req, res) { - conn.query( - 'SELECT * FROM municipios WHERE codigo_ibge='+req.params.id, true - ).then(function(result) { - log.debug(result) - req.result = result - response(req, res) - }) -}) - -router.get('/state/:id', function(req, res) { - conn.query( - 'SELECT * FROM municipios WHERE fk_estado_id='+req.params.id, true - ).then(function(result) { - log.debug(result) - req.result = result - response(req, res) - }) -}) - -module.exports = router diff --git a/libs/routes/regions.js b/libs/routes/regions.js deleted file mode 100644 index 6c85fccb..00000000 --- a/libs/routes/regions.js +++ /dev/null @@ -1,45 +0,0 @@ -var express = require('express') -var xml = require('js2xmlparser') -var router = express.Router() - -var libs = process.cwd() + '/libs/' - -var log = require(libs + 'log')(module) -var config = require(libs + 'config') - -var conn = require(libs + 'db/monet') - -function response(req, res) { - if (req.query.format === 'csv') { - res.csv(req.result.data) - } else if (req.query.format === 'xml') { - res.send(xml("result", JSON.stringify({state: req.result.data}))) - } - else { - res.json({ - result: req.result.data - }) - } -} - -router.get('/', function(req, res) { - conn.query( - 'SELECT * FROM regioes', true - ).then(function(result) { - log.debug(result) - req.result = result - response(req, res) - }) -}) - -router.get('/:id', function(req, res) { - conn.query( - 'SELECT * FROM regioes WHERE pk_regiao_id='+req.params.id, true - ).then(function(result) { - log.debug(result) - req.result = result - response(req, res) - }) -}) - -module.exports = router diff --git a/libs/routes/states.js b/libs/routes/states.js deleted file mode 100644 index 2192c537..00000000 --- a/libs/routes/states.js +++ /dev/null @@ -1,55 +0,0 @@ -var express = require('express') -var xml = require('js2xmlparser') -var router = express.Router() - -var libs = process.cwd() + '/libs/' - -var log = require(libs + 'log')(module) -var config = require(libs + 'config') - -var conn = require(libs + 'db/monet') - -function response(req, res) { - if (req.query.format === 'csv') { - res.csv(req.result.data) - } else if (req.query.format === 'xml') { - res.send(xml("result", JSON.stringify({state: req.result.data}))) - } - else { - res.json({ - result: req.result.data - }) - } -} - -router.get('/', function(req, res, next) { - conn.query( - 'SELECT * FROM estados', true - ).then(function(result) { - log.debug(result) - req.result = result - response(req, res) - }) -}) - -router.get('/:id', function(req, res, next) { - conn.query( - 'SELECT * FROM estados WHERE pk_estado_id='+req.params.id, true - ).then(function(result) { - log.debug(result) - req.result = result - response(req, res) - }) -}) - -router.get('/region/:id', function(req, res, next) { - conn.query( - 'SELECT * FROM estados WHERE fk_regiao_id='+req.params.id, true - ).then(function(result) { - log.debug(result) - req.result = result - response(req, res) - }) -}) - -module.exports = router diff --git a/package.json b/package.json index fd0765bb..cee6b8e0 100644 --- a/package.json +++ b/package.json @@ -19,14 +19,25 @@ "forever": "^0.15.2", "js2xmlparser": "^1.0.0", "method-override": "^2.3.3", - "monetdb": "^1.1.2", + "monetdb-pool": "0.0.8", "nconf": "^0.6.x", "winston": "^2.2.0" }, "license": "MIT", "devDependencies": { + "babel-preset-es2015": "^6.13.2", + "babelify": "^7.3.0", + "browserify": "^13.1.0", "chai": "^3.5.0", "chai-http": "^3.0.0", + "eslint": "^3.3.1", + "eslint-config-airbnb": "^10.0.1", + "eslint-plugin-import": "^1.13.0", + "eslint-plugin-jsx-a11y": "^2.1.0", + "eslint-plugin-react": "^6.1.1", + "gulp": "^3.9.1", + "gulp-babel": "^6.1.2", + "gulp-eslint": "^3.0.1", "mocha": "^2.5.3" } } diff --git a/server.js b/server.js deleted file mode 100644 index dc0c1ce3..00000000 --- a/server.js +++ /dev/null @@ -1,13 +0,0 @@ -var debug = require('debug')('node-express-base') - -var libs = process.cwd() + '/libs/' -var config = require(libs + 'config') -var log = require(libs + 'log')(module) -var app = require(libs + 'app') - -app.set('port', config.get('port') || 3000) - -var server = app.listen(app.get('port'), function() { - debug('Express server listening on port ' + server.address().port) - log.info('Express server listening on port ' + config.get('port')) -}) diff --git a/src/libs/app.js b/src/libs/app.js new file mode 100644 index 00000000..c2396420 --- /dev/null +++ b/src/libs/app.js @@ -0,0 +1,41 @@ +const express = require('express'); +const cookieParser = require('cookie-parser'); +const bodyParser = require('body-parser'); +const methodOverride = require('method-override'); +const cors = require('cors'); + +const log = require('./log')(module); + +const api = require('./routes/api'); +const states = require('./routes/states'); +const regions = require('./routes/regions'); +const cities = require('./routes/cities'); + +const app = express(); + +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(cors()); +app.use(methodOverride()); + +app.use('/v1/', api); +app.use('/v1/states', states); +app.use('/v1/regions', regions); +app.use('/v1/cities', cities); + +// catch 404 and forward to error handler +app.use((req, res, next) => { + res.status(404); + log.debug('%s %d %s', req.method, res.statusCode, req.url); + res.json({ error: 'Not found' }).end(); +}); + +// error handlers +app.use((err, req, res, next) => { + res.status(err.status || 500); + log.error('%s %d %s', req.method, res.statusCode, err.message); + res.json({ error: err.message }).end(); +}); + +module.exports = app; diff --git a/src/libs/config.js b/src/libs/config.js new file mode 100644 index 00000000..ef5a26cd --- /dev/null +++ b/src/libs/config.js @@ -0,0 +1,7 @@ +const nconf = require('nconf'); + +nconf.argv() + .env() + .file({ file: `${process.cwd()}/config.json` }); + +module.exports = nconf; diff --git a/src/libs/db/monet.js b/src/libs/db/monet.js new file mode 100644 index 00000000..1cf874cc --- /dev/null +++ b/src/libs/db/monet.js @@ -0,0 +1,22 @@ +const MonetDBPool = require('monetdb-pool'); + +const libs = `${process.cwd()}/libs`; + +const config = require(`${libs}/config`); + +const poolOptions = { + nrConnections: config.get('monetdb:nrConnections'), +}; + +const options = { + host: config.get('monetdb:host'), + port: config.get('monetdb:port'), + dbname: config.get('monetdb:dbname'), + user: config.get('monetdb:user'), + password: config.get('monetdb:password'), +}; + +const conn = new MonetDBPool(poolOptions, options); +conn.connect(); + +module.exports = conn; diff --git a/src/libs/db/query_decorator.js b/src/libs/db/query_decorator.js new file mode 100644 index 00000000..361f3509 --- /dev/null +++ b/src/libs/db/query_decorator.js @@ -0,0 +1,25 @@ +const libs = `${process.cwd()}/libs`; +const log = require(`${libs}/log`)(module); +const conn = require(`${libs}/db/monet`); + +/** + * Basic decorator to wrap SQL query strings + */ +exports.execQuery = (sqlQuery, sqlQueryParams) => { + log.debug(`Executing SQL query '${sqlQuery}' with params '${sqlQueryParams}'`); + conn.prepare(sqlQuery, true).then( + (dbQuery) => { + dbQuery.exec(sqlQueryParams).then( + (dbResult) => { + log.debug(dbResult.data); + log.debug(`Query result: ${dbResult.data}`); + return dbResult; + }, + (error) => { + log.error(`SQL query execution error: ${error.message}`); + return error; + } + ); + } + ); +}; diff --git a/src/libs/enrollment/all.js b/src/libs/enrollment/all.js new file mode 100644 index 00000000..e69de29b diff --git a/src/libs/enrollment/city.js b/src/libs/enrollment/city.js new file mode 100644 index 00000000..e69de29b diff --git a/src/libs/enrollment/common.js b/src/libs/enrollment/common.js new file mode 100644 index 00000000..207f44c3 --- /dev/null +++ b/src/libs/enrollment/common.js @@ -0,0 +1,14 @@ +const libs = `${process.cwd()}/libs`; + +const log = require(`${libs}/log`)(module); + +const sqlDecorator = require(`${libs}/query_decorator`); + +const yearRange = () => { + const yearSql = 'SELECT MIN(t.ano_censo) AS start_year, MAX(t.ano_censo)' + + 'AS end_year FROM turmas AS t'; + log.debug('Generated SQL query for enrollments\' year range'); + return sqlDecorator.execQuery(yearSql, []); +}; + +module.exports = yearRange; diff --git a/src/libs/enrollment/country.js b/src/libs/enrollment/country.js new file mode 100644 index 00000000..e69de29b diff --git a/src/libs/enrollment/region.js b/src/libs/enrollment/region.js new file mode 100644 index 00000000..e69de29b diff --git a/src/libs/enrollment/state.js b/src/libs/enrollment/state.js new file mode 100644 index 00000000..e69de29b diff --git a/src/libs/log.js b/src/libs/log.js new file mode 100644 index 00000000..0eb64119 --- /dev/null +++ b/src/libs/log.js @@ -0,0 +1,34 @@ +const winston = require('winston'); + +winston.emitErrs = true; + +function getFilePath(module) { + // using filename in log statements + return module.filename.split('/').slice(-2).join('/'); +} + +function logger(module) { + return new winston.Logger({ + transports: [ + new winston.transports.File({ + level: 'info', + filename: `${process.cwd()}/logs/all.log`, + handleException: true, + json: false, + maxSize: 5242880, // 5MB + maxFiles: 2, + colorize: false, + }), + new winston.transports.Console({ + level: 'debug', + label: getFilePath(module), + handleException: true, + json: true, + colorize: true, + }), + ], + exitOnError: false, + }); +} + +module.exports = logger; diff --git a/libs/routes/api.js b/src/libs/routes/api.js similarity index 65% rename from libs/routes/api.js rename to src/libs/routes/api.js index 9911bf10..d5c46ef6 100644 --- a/libs/routes/api.js +++ b/src/libs/routes/api.js @@ -1,20 +1,27 @@ -'use strict'; - const express = require('express'); + const xml = require('js2xmlparser'); -const enrollmentsApp = express(); -const libs = process.cwd() + '/libs/'; +const enrollmentApp = express(); + +const libs = `${process.cwd()}/libs`; -const log = require(libs + 'log')(module); -const config = require(libs + 'config'); +const log = require(`${libs}/log`)(module); -const conn = require(libs + 'db/monet'); +const conn = require(`${libs}/db/monet`); + +function response(req, res) { + if (req.query.format === 'csv') { + res.csv(req.result.data); + } else if (req.query.format === 'xml') { + res.send(xml('result', JSON.stringify({ state: req.result.data }))); + } else { + res.json({ result: req.result.data }); + } +} -enrollmentsApp.get('/', function (req, res) { - res.json({ - msg: 'SimCAQ API is running' - }); +enrollmentApp.get('/', (req, res) => { + res.json({ msg: 'SimCAQ API is running' }); }); /** @@ -22,69 +29,61 @@ enrollmentsApp.get('/', function (req, res) { * * Returns a tuple of start and ending years of the complete enrollments dataset. */ -enrollmentsApp.get('/year_range', function(req, res) { - var yearSql = "SELECT MIN(t.ano_censo) AS start_year, MAX(t.ano_censo) AS end_year FROM turmas AS t"; - conn.query(yearSql, true).then(function(result) { - if (req.query.format === 'csv') { - res.csv(result.data); - } else if (req.query.format === 'xml') { - res.send(xml("result", JSON.stringify({year_range: result.data}))) - } - else { - res.json({ - result: result.data - }); - } - }, function(error) { - log.error('SQL query error: ${ error.message }?'); - log.debug(error); - res.send('Request could not be satisfied due to an internal error'); - }); +enrollmentApp.get('/year_range', (req, res) => { + const yearSql = 'SELECT MIN(t.ano_censo) AS start_year, MAX(t.ano_censo)' + + 'AS end_year FROM turmas AS t'; + + conn.query(yearSql, true).then( + (dbResult) => { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, + (dbError) => { + log.error(`SQL query execution error: ${dbError.message}`); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + } + ); }); -enrollmentsApp.get('/education_level', function(req, res) { - var edLevelId = "SELECT pk_etapa_ensino_id AS id, desc_etapa AS education_level FROM etapa_ensino"; - conn.query(edLevelId, true).then(function(result) { - if (req.query.format === 'csv') { - res.csv(result.data); - } else if (req.query.format === 'xml') { - res.send(xml("result", JSON.stringify({year_range: result.data}))) - } - else { - res.json({ - result: result.data - }); - } - }, function(error) { - log.error('SQL query error: ${ error.message }?'); - log.debug(error); - res.send('Request could not be satisfied due to an internal error'); - }); +enrollmentApp.get('/education_level', (req, res) => { + const edLevelSql = 'SELECT pk_etapa_ensino_id AS id, desc_etapa AS ' + + 'education_level FROM etapa_ensino'; + conn.query(edLevelSql, true).then( + (dbResult) => { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, + (dbError) => { + log.error(`SQL query error: ${dbError.message}`); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + } + ); }); -enrollmentsApp.get('/data', function(req, res) { - log.debug(req.query) - log.debug(req.query.met) - log.debug(req.query.dim) - conn.query('SELECT * FROM turmas LIMIT 5', true).then(function(result) { - if (req.query.format === 'csv') { - res.csv(result.data); - } else if (req.query.format === 'xml') { - res.send(xml("result", JSON.stringify({data: result.data}))) - } - else { - res.json({ - result: result.data - }); - } - }, function(error) { - log.error('SQL query error: ${ error.message }?'); - log.debug(error); - res.send('Request could not be satisfied due to an internal error'); - }); -}) +enrollmentApp.get('/data', (req, res) => { + log.debug(req.query); + log.debug(req.query.met); + log.debug(req.query.dim); + const schoolClassSql = 'SELECT * FROM turmas'; + conn.query(schoolClassSql, true).then( + (dbResult) => { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, + (dbError) => { + log.error(`SQL query error: ${dbError.message}`); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + } + ); +}); -enrollmentsApp.use('/enrollments', function(req, res, next) { +enrollmentApp.use('/enrollments', (req, res, next) => { const params = req.query; req.paramCnt = 0; @@ -116,7 +115,7 @@ enrollmentsApp.use('/enrollments', function(req, res, next) { next(); }); -enrollmentsApp.use('/enrollments', function(req, res, next) { +enrollmentApp.use('/enrollments', (req, res, next) => { const params = req.query; if (typeof params.aggregate !== 'undefined' && params.aggregate === 'region') { log.debug('Using enrollments query for regions'); @@ -163,7 +162,7 @@ enrollmentsApp.use('/enrollments', function(req, res, next) { next(); }); -enrollmentsApp.use('/enrollments', function(req, res, next) { +enrollmentApp.use('/enrollments', (req, res, next) => { const params = req.query; if (typeof params.aggregate !== 'undefined' && params.aggregate === 'state') { log.debug('Using enrollments query for states'); @@ -201,8 +200,8 @@ enrollmentsApp.use('/enrollments', function(req, res, next) { req.sqlQuery += ')'; if (typeof req.id !== 'undefined') { - req.sqlQuery += " WHERE "; - req.sqlQuery += "e.pk_estado_id = ?"; + req.sqlQuery += ' WHERE '; + req.sqlQuery += 'e.pk_estado_id = ?'; req.sqlQueryParams.push(req.id); } @@ -211,7 +210,7 @@ enrollmentsApp.use('/enrollments', function(req, res, next) { next(); }); -enrollmentsApp.use('/enrollments', function(req, res, next) { +enrollmentApp.use('/enrollments', (req, res, next) => { const params = req.query; if (typeof params.aggregate !== 'undefined' && params.aggregate === 'city') { log.debug('Using enrollments query for cities'); @@ -248,8 +247,8 @@ enrollmentsApp.use('/enrollments', function(req, res, next) { req.sqlQuery += ')'; if (typeof req.id !== 'undefined') { - req.sqlQuery += " WHERE "; - req.sqlQuery += "m.pk_municipio_id = ?"; + req.sqlQuery += ' WHERE '; + req.sqlQuery += 'm.pk_municipio_id = ?'; req.sqlQueryParams.push(req.id); } @@ -258,7 +257,7 @@ enrollmentsApp.use('/enrollments', function(req, res, next) { next(); }); -enrollmentsApp.use('/enrollments', function(req, res, next) { +enrollmentApp.use('/enrollments', (req, res, next) => { const params = req.query; if (typeof params.aggregate === 'undefined') { log.debug('Using enrollments query for the whole country'); @@ -302,35 +301,33 @@ enrollmentsApp.use('/enrollments', function(req, res, next) { next(); }); -enrollmentsApp.get('/enrollments', function(req, res, next) { - log.debug('Request parameters: ${ req }?'); +enrollmentApp.get('/enrollments', (req, res, next) => { + log.debug(`Request parameters: ${req}`); if (typeof req.sqlQuery === 'undefined') { - /* Should only happen if there is a bug in the chaining of the - * '/enrollments' route, since when no +aggregate+ parameter is given, - * it defaults to use the query for the whole country. - */ + // Should only happen if there is a bug in the chaining of the + // '/enrollments' route, since when no +aggregate+ parameter is given, + // it defaults to use the query for the whole country. log.error('BUG -- No SQL query was found to be executed!'); res.send('Request could not be satisfied due to an internal error'); } else { log.debug('SQL query: ${ req.sqlQuery }?'); log.debug(req.sqlQuery); - conn.prepare(req.sqlQuery, true).then(function(dbQuery) { - dbQuery.exec(req.sqlQueryParams).then(function(dbResult) { - log.debug(dbResult); - if (req.query.format === 'csv') { - res.csv(dbResult.data); - } else if (req.query.format === 'xml') { - res.send(xml('result', JSON.stringify({enrollments: dbResult.data}))); - } else { - res.json({ result: dbResult.data }); + + conn.prepare(req.sqlQuery, true).then((dbQuery) => { + dbQuery.exec(req.sqlQueryParams).then( + (dbResult) => { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, + (dbError) => { + log.error(`SQL query execution error: ${dbError.message}`); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); } - }); - }, function(error) { - log.error('SQL query error: ${ error.message }?'); - log.debug(error); - res.send('Request could not be satisfied due to an internal error'); + ); }); } }); -module.exports = enrollmentsApp +module.exports = enrollmentApp; diff --git a/src/libs/routes/cities.js b/src/libs/routes/cities.js new file mode 100644 index 00000000..1ae6c48d --- /dev/null +++ b/src/libs/routes/cities.js @@ -0,0 +1,95 @@ +const express = require('express'); + +const xml = require('js2xmlparser'); + +const cityApp = express(); + +const libs = `${process.cwd()}/libs`; + +const log = require(`${libs}/log`)(module); + +const conn = require(`${libs}/db/monet`); + +function response(req, res) { + if (req.query.format === 'csv') { + res.csv(req.result.data); + } else if (req.query.format === 'xml') { + res.send(xml('result', JSON.stringify({ city: req.result.data }))); + } else { + res.json({ result: req.result.data }); + } +} + +cityApp.get('/', (req, res) => { + conn.query('SELECT * FROM municipios', true).then( + (dbResult) => { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, + (dbError) => { + log.error(`SQL query execution error: ${dbError.message}`); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + } + ); +}); + +cityApp.get('/:id', (req, res) => { + const citySql = 'SELECT * FROM municipios WHERE pk_municipio_id = ?'; + const cityId = parseInt(req.params.id, 10); + conn.prepare(citySql, true).then((dbQuery) => { + dbQuery.exec([cityId]).then( + (dbResult) => { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, + (dbError) => { + log.error(`SQL query execution error: ${dbError.message}`); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + } + ); + }); +}); + +cityApp.get('/ibge/:id', (req, res) => { + const citySql = 'SELECT * FROM municipios WHERE codigo_ibge = ?'; + const cityId = parseInt(req.params.id, 10); + conn.prepare(citySql, true).then((dbQuery) => { + dbQuery.exec([cityId]).then( + (dbResult) => { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, + (dbError) => { + log.error(`SQL query execution error: ${dbError.message}`); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + } + ); + }); +}); + +cityApp.get('/state/:id', (req, res) => { + const citySql = 'SELECT * FROM municipios WHERE fk_estado_id = ?'; + const stateId = parseInt(req.params.id, 10); + conn.prepare(citySql, true).then((dbQuery) => { + dbQuery.exec([stateId]).then( + (dbResult) => { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, + (dbError) => { + log.error(`SQL query execution error: ${dbError.message}`); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + } + ); + }); +}); + +module.exports = cityApp; diff --git a/src/libs/routes/regions.js b/src/libs/routes/regions.js new file mode 100644 index 00000000..d76037c0 --- /dev/null +++ b/src/libs/routes/regions.js @@ -0,0 +1,58 @@ +const express = require('express'); + +const xml = require('js2xmlparser'); + +const regionApp = express(); + +const libs = `${process.cwd()}/libs`; + +const log = require(`${libs}/log`)(module); + +const conn = require(`${libs}/db/monet`); + +function response(req, res) { + if (req.query.format === 'csv') { + res.csv(req.result.data); + } else if (req.query.format === 'xml') { + res.send(xml('result', JSON.stringify({ state: req.result.data }))); + } else { + res.json({ result: req.result.data }); + } +} + +regionApp.get('/', (req, res) => { + const regionSql = 'SELECT * FROM regioes'; + conn.query(regionSql, true).then( + (dbResult) => { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, + (dbError) => { + log.error(`SQL query execution error: ${dbError.message}`); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + } + ); +}); + +regionApp.get('/:id', (req, res) => { + const regionSql = 'SELECT * FROM regioes WHERE pk_regiao_id = ?'; + const regionId = parseInt(req.params.id, 10); + conn.prepare(regionSql, true).then((dbQuery) => { + dbQuery.exec([regionId]).then( + (dbResult) => { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, + (dbError) => { + log.error(`SQL query execution error: ${dbError.message}`); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + } + ); + }); +}); + +module.exports = regionApp; diff --git a/src/libs/routes/states.js b/src/libs/routes/states.js new file mode 100644 index 00000000..23cc7a3d --- /dev/null +++ b/src/libs/routes/states.js @@ -0,0 +1,77 @@ +const express = require('express'); + +const xml = require('js2xmlparser'); + +const stateApp = express(); + +const libs = `${process.cwd()}/libs`; + +const log = require(`${libs}/log`)(module); + +const conn = require(`${libs}/db/monet`); + +function response(req, res) { + if (req.query.format === 'csv') { + res.csv(req.result.data); + } else if (req.query.format === 'xml') { + res.send(xml('result', JSON.stringify({ state: req.result.data }))); + } else { + res.json({ result: req.result.data }); + } +} + +stateApp.get('/', (req, res, next) => { + const stateSql = 'SELECT * FROM estados'; + conn.query(stateSql, true).then( + (dbResult) => { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, + (dbError) => { + log.error(`SQL query execution error: ${dbError.message}`); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + } + ); +}); + +stateApp.get('/:id', (req, res, next) => { + const stateSql = 'SELECT * FROM estados WHERE pk_estado_id = ?'; + const stateId = parseInt(req.params.id, 10); + conn.prepare(stateSql, true).then((dbQuery) => { + dbQuery.exec([stateId]).then( + (dbResult) => { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, + (dbError) => { + log.error(`SQL query execution error: ${dbError.message}`); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + } + ); + }); +}); + +stateApp.get('/region/:id', (req, res, next) => { + const stateSql = 'SELECT * FROM estados WHERE fk_regiao_id = ?'; + const regionId = parseInt(req.params.id, 10); + conn.prepare(stateSql, true).then((dbQuery) => { + dbQuery.exec([regionId]).then( + (dbResult) => { + log.debug(dbResult); + req.result = dbResult; + response(req, res); + }, + (dbError) => { + log.error(`SQL query execution error: ${dbError.message}`); + // FIXME: change response to HTTP 501 status + res.json({ error: 'An internal error has occurred' }).end(); + } + ); + }); +}); + +module.exports = stateApp; diff --git a/src/server.js b/src/server.js new file mode 100644 index 00000000..7b3927e3 --- /dev/null +++ b/src/server.js @@ -0,0 +1,16 @@ +const debug = require('debug')('node-express-base'); + +const libs = `${process.cwd()}/libs`; + +const config = require(`${libs}/config`); + +const log = require(`${libs}/log`)(module); + +const app = require(`${libs}/app`); + +app.set('port', config.get('port') || 3000); + +const server = app.listen(app.get('port'), () => { + debug(`Express server listening on port ${server.address().port}`); + log.info(`Express server listening on port ${config.get('port')}`); +}); diff --git a/src/test/test.js b/src/test/test.js new file mode 100644 index 00000000..f9fec313 --- /dev/null +++ b/src/test/test.js @@ -0,0 +1,155 @@ +const chai = require('chai'); +const chaiHttp = require('chai-http'); +const assert = chai.assert; +const expect = chai.expect; +const should = chai.should(); // actually call the function +const server = require('../libs/app'); + +chai.use(chaiHttp); + +describe('request enrollments', () => { + it('should list enrollments', (done) => { + chai.request(server) + .get('/v1/enrollments') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('name'); + res.body.result[0].should.have.property('total'); + done(); + }); + }); +}); + +describe('request regions', () => { + it('should list all regions', (done) => { + chai.request(server) + .get('/v1/regions') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_regiao_id'); + res.body.result[0].should.have.property('nome'); + done(); + }); + }); + + it('should list region by id', (done) => { + chai.request(server) + .get('/v1/regions/1') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result.should.have.length(1); + res.body.result[0].should.have.property('pk_regiao_id'); + res.body.result[0].should.have.property('nome'); + done(); + }); + }); +}); + +describe('request states', () => { + + it('should list all states', (done) => { + chai.request(server) + .get('/v1/states') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_estado_id'); + res.body.result[0].should.have.property('fk_regiao_id'); + res.body.result[0].should.have.property('nome'); + done(); + }); + }); + + it('should list a state by id', (done) => { + chai.request(server) + .get('/v1/states/11') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result.should.have.length(1); + res.body.result[0].should.have.property('pk_estado_id'); + res.body.result[0].should.have.property('fk_regiao_id'); + res.body.result[0].should.have.property('nome'); + done(); + }); + }); + + it('should list states by region id', (done) => { + chai.request(server) + .get('/v1/states/region/1') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_estado_id'); + res.body.result[0].should.have.property('fk_regiao_id'); + res.body.result[0].should.have.property('nome'); + done(); + }); + }); +}); + +describe('request cities', () => { + + it('should list all cities', (done) => { + chai.request(server) + .get('/v1/cities') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_municipio_id'); + res.body.result[0].should.have.property('fk_estado_id'); + res.body.result[0].should.have.property('nome'); + res.body.result[0].should.have.property('codigo_ibge'); + done(); + }); + }); + + it('should list a city by id', (done) => { + chai.request(server) + .get('/v1/cities/1') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_municipio_id'); + res.body.result[0].should.have.property('fk_estado_id'); + res.body.result[0].should.have.property('nome'); + res.body.result[0].should.have.property('codigo_ibge'); + done(); + }); + }); + + it('should list a city by codigo_ibge', (done) => { + chai.request(server) + .get('/v1/cities/ibge/1200013') + .end((err, res) => { + res.should.have.status(200); + res.should.be.json; + res.body.should.have.property('result'); + res.body.result.should.be.a('array'); + res.body.result[0].should.have.property('pk_municipio_id'); + res.body.result[0].should.have.property('fk_estado_id'); + res.body.result[0].should.have.property('nome'); + res.body.result[0].should.have.property('codigo_ibge'); + done(); + }); + }); +}); diff --git a/test/test.js b/test/test.js deleted file mode 100644 index a9b67bb2..00000000 --- a/test/test.js +++ /dev/null @@ -1,158 +0,0 @@ -var chai = require('chai'); -var chaiHttp = require('chai-http'); -var assert = chai.assert; -var expect = chai.expect; -var should = chai.should(); //actually call the function -var server = require('../libs/app'); - -chai.use(chaiHttp); - -describe('request enrollments', function(){ - - it('should list enrollments', function(done){ - chai.request(server) - .get('/v1/enrollments') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result[0].should.have.property('name'); - res.body.result[0].should.have.property('total'); - done(); - }) - }); -}); - -describe('request regions', function(){ - - it('should list all regions', function(done){ - chai.request(server) - .get('/v1/regions') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result[0].should.have.property('pk_regiao_id'); - res.body.result[0].should.have.property('nome'); - done(); - }) - }); - - it('should list region by id', function(done){ - chai.request(server) - .get('/v1/regions/1') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result.should.have.length(1); - res.body.result[0].should.have.property('pk_regiao_id'); - res.body.result[0].should.have.property('nome'); - done(); - }) - }); -}); - -describe('request states', function(){ - - it('should list all states', function(done){ - chai.request(server) - .get('/v1/states') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result[0].should.have.property('pk_estado_id'); - res.body.result[0].should.have.property('fk_regiao_id'); - res.body.result[0].should.have.property('nome'); - done(); - }) - }); - - it('should list a state by id', function(done){ - chai.request(server) - .get('/v1/states/11') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result.should.have.length(1); - res.body.result[0].should.have.property('pk_estado_id'); - res.body.result[0].should.have.property('fk_regiao_id'); - res.body.result[0].should.have.property('nome'); - done(); - }) - }); - - it('should list states by region id', function(done){ - chai.request(server) - .get('/v1/states/region/1') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result[0].should.have.property('pk_estado_id'); - res.body.result[0].should.have.property('fk_regiao_id'); - res.body.result[0].should.have.property('nome'); - done(); - }) - }); -}); - -describe('request cities', function(){ - - it('should list all cities', function(done){ - chai.request(server) - .get('/v1/cities') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result[0].should.have.property('pk_municipio_id'); - res.body.result[0].should.have.property('fk_estado_id'); - res.body.result[0].should.have.property('nome'); - res.body.result[0].should.have.property('codigo_ibge'); - done(); - }) - }); - - it('should list a city by id', function(done){ - chai.request(server) - .get('/v1/cities/1') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result[0].should.have.property('pk_municipio_id'); - res.body.result[0].should.have.property('fk_estado_id'); - res.body.result[0].should.have.property('nome'); - res.body.result[0].should.have.property('codigo_ibge'); - done(); - }) - }); - - it('should list a city by codigo_ibge', function(done){ - chai.request(server) - .get('/v1/cities/ibge/1200013') - .end(function(err, res){ - res.should.have.status(200); - res.should.be.json; - res.body.should.have.property('result'); - res.body.result.should.be.a('array'); - res.body.result[0].should.have.property('pk_municipio_id'); - res.body.result[0].should.have.property('fk_estado_id'); - res.body.result[0].should.have.property('nome'); - res.body.result[0].should.have.property('codigo_ibge'); - done(); - }) - }); - -}); -- GitLab