diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000000000000000000000000000000000000..834a48a40945a182db82602de135718f81d471b6 --- /dev/null +++ b/.babelrc @@ -0,0 +1 @@ +{ 'presets': ['es2015'] } diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000000000000000000000000000000000000..52b136d7a6031bf708bec43b27c56478555241b3 --- /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 1be0326ac9a0235b55fa03f244f261b16eed1832..f4e4627c8c4135f6a6d064b016e457de3042aae0 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 3171c9f7e5653d1e4edbfd1064779609693773c3..a3c44c8f571f3fe61cc8cd42e07b6159a8c47cea 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 0000000000000000000000000000000000000000..c919cf1c932e57792896a210082c83386e193a6d --- /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 0000000000000000000000000000000000000000..a678b1c48f2fc93277fa47d2aff07c1fdf3aa659 --- /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 0000000000000000000000000000000000000000..2f7bd18e7d52121f1f78d197bc31d79cebd0bb25 --- /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 0000000000000000000000000000000000000000..c8d29099ae9f4802b09ac38136583ecc979aa2df --- /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 0000000000000000000000000000000000000000..ce0697d756a4f0f7fba74542889c47976fc5dc9a --- /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 0000000000000000000000000000000000000000..9a390c31f71bc7eae1522a280a2dc8f6723185bf --- /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 0000000000000000000000000000000000000000..9a390c31f71bc7eae1522a280a2dc8f6723185bf --- /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 0000000000000000000000000000000000000000..9c78895181d5b81053b58d89d2bc2d3c93340e02 --- /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 0000000000000000000000000000000000000000..9a390c31f71bc7eae1522a280a2dc8f6723185bf --- /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 0000000000000000000000000000000000000000..9a390c31f71bc7eae1522a280a2dc8f6723185bf --- /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 0000000000000000000000000000000000000000..9a390c31f71bc7eae1522a280a2dc8f6723185bf --- /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 0000000000000000000000000000000000000000..e93d2ff2b7a0a81141b85099b8b97835a74159b3 --- /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 0000000000000000000000000000000000000000..ce0697d756a4f0f7fba74542889c47976fc5dc9a --- /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 0000000000000000000000000000000000000000..812eae438319d38aaabeeb2a15cf0f7aa02953ea --- /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 0000000000000000000000000000000000000000..f549624eb0908065775f216ef472fa45c705617d --- /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 0000000000000000000000000000000000000000..75cf2bee0d1923460e3899b81c4fbdfcd5ae6d57 --- /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 0000000000000000000000000000000000000000..8579f3929b4ba24ff1308f1126706eb70874c339 --- /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 0000000000000000000000000000000000000000..397b4a7624e35fa60563a9c03b1213d93f7b6546 --- /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 0000000000000000000000000000000000000000..5f2171cc5b90c1149027cd7df7e6ec464de483c7 --- /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 0000000000000000000000000000000000000000..55caa55c10bdd703997e6355a6fb432ebb66cd1a --- /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 8f60036adf20a0512e709e683f876de75124d714..1da8951e6da701180969231dfa6151489e2794f9 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 0000000000000000000000000000000000000000..e07481f9eecc0b8219d5355a994eaf2ee1d56d74 --- /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 544f649b66c258f98cad7ef867217c2a159dfae6..0000000000000000000000000000000000000000 --- 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 78f7831a7b1bb614fcebd8de04be18ff447d84a1..0000000000000000000000000000000000000000 --- 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 0b554285d1b9d889ca1f40f7607672490eda73b3..0000000000000000000000000000000000000000 --- 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 419b3e4b86d5d1e5a8e17858882c905204bab079..0000000000000000000000000000000000000000 --- 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 6a2220497fa90a47200fce15b0879c8cd5ed90c8..0000000000000000000000000000000000000000 --- 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 6c85fccb8a8b856769ccb46ca9fb821c81bab460..0000000000000000000000000000000000000000 --- 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 2192c5377a0e705ad59393dacf46a469491b7ad7..0000000000000000000000000000000000000000 --- 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 fd0765bb333bc1fa382aae1e82ecb4f500774df4..cee6b8e062bde5c7b96d92adcc9235ab99352ea7 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 dc0c1ce36563fd3fb4d967480e54548b7c9fff4c..0000000000000000000000000000000000000000 --- 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 0000000000000000000000000000000000000000..c239642062506c72c801dd6d3140fd7ea138cd3a --- /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 0000000000000000000000000000000000000000..ef5a26cddbb3946b89ac690e8cf52d28884463aa --- /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 0000000000000000000000000000000000000000..1cf874cc9ca5a2ada3e20bdd077c34fbcfbe8226 --- /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 0000000000000000000000000000000000000000..361f350978622de154aa8aebfa6143cddcf23127 --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/libs/enrollment/city.js b/src/libs/enrollment/city.js new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/libs/enrollment/common.js b/src/libs/enrollment/common.js new file mode 100644 index 0000000000000000000000000000000000000000..207f44c35f315bdd9cda4ed85b7cc8d8e8d687b3 --- /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 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/libs/enrollment/region.js b/src/libs/enrollment/region.js new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/libs/enrollment/state.js b/src/libs/enrollment/state.js new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/libs/log.js b/src/libs/log.js new file mode 100644 index 0000000000000000000000000000000000000000..0eb64119f54d8de753bb2d2cd01390f6dd48f20b --- /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 9911bf10c84a45c3f661be4bcd5fce399f3cff04..d5c46ef65c7914eb5bb63f5242933c1b9233ff60 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 0000000000000000000000000000000000000000..1ae6c48d8d2a71232c1d71e8f8875de0dd13770e --- /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 0000000000000000000000000000000000000000..d76037c08049be5e7b500ae6cb13cc8acec7d82a --- /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 0000000000000000000000000000000000000000..23cc7a3dd01a1911e55d02c263739da789338138 --- /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 0000000000000000000000000000000000000000..7b3927e3fc64fe9f0001f01e95b0a2805feb33eb --- /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 0000000000000000000000000000000000000000..f9fec3136184373433aaab391825b1f66e7c5aab --- /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 a9b67bb2269983afbbe479cd72fea744330e81da..0000000000000000000000000000000000000000 --- 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(); - }) - }); - -});