diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 986652cccb3cdcfee0d659c0a69533943ae9a495..1f23dd9ee0b5c083c7f66d6d6650982a34a748a6 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -12,7 +12,7 @@ const nodemon = require('gulp-nodemon'); const Cache = require('gulp-file-cache'); -const jsdoc = require('gulp-jsdoc3'); +const docco = require('gulp-docco'); const cache = new Cache(); @@ -52,21 +52,23 @@ gulp.task('compile', () => { gulp.task('build', ['compile']); -gulp.task('doc', (cb) => { - let config = require('./jsdoc.json'); - gulp.src(['README.md', './src/**/*.js'], {read: false}) - .pipe(jsdoc(config, cb)); +gulp.task('docco', () => { + gulp.src('./src/**/*.js') + .pipe(docco()) + .pipe(gulp.dest('./docs')); }); +gulp.task('doc', ['docco']); + gulp.task('test', ['build'], () => { - gulp.src('test/test.js', {read: false}) - .pipe(mocha()) - .once('error', () => { - process.exit(1); - }) - .once('end', () => { - process.exit(); - }); + gulp.src('test/test.js', {read: false}) + .pipe(mocha()) + .once('error', () => { + process.exit(1); + }) + .once('end', () => { + process.exit(); + }); }); gulp.task('watch', ['compile'], () => { @@ -79,14 +81,14 @@ gulp.task('watch', ['compile'], () => { }); gulp.task('run', () => { - process.chdir('build'); - nodemon({ - script: 'server.js', - tasks: ['watch'], - ignore: ["test/test.js", "gulpfile.babel.js"], - ext: 'js html json', - env: { 'NODE_ENV': 'development' } - }); + process.chdir('build'); + nodemon({ + script: 'server.js', + tasks: ['watch'], + ignore: ["test/test.js", "gulpfile.babel.js"], + ext: 'js html json', + env: { 'NODE_ENV': 'development' } + }); }); gulp.task('default', ['run']); diff --git a/package.json b/package.json index 87652e4ad3aadf65ba1a5cdf3b06b8f20da31854..5973ed2a9f2897def179077fada4c2075032f841 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "gulp": "^3.9.1", "gulp-babel": "^6.1.2", "gulp-cli": "^1.2.2", + "gulp-docco": "0.0.4", "gulp-eslint": "^3.0.1", "gulp-file-cache": "0.0.1", "gulp-jsdoc3": "^0.3.0", diff --git a/src/libs/app.js b/src/libs/app.js index d00a28876f2f5d73f68d236a8457052d32905947..4e3e51c6bf3d216a519ce5a33e16b45941c75040 100644 --- a/src/libs/app.js +++ b/src/libs/app.js @@ -4,6 +4,7 @@ const bodyParser = require('body-parser'); const methodOverride = require('method-override'); const cors = require('cors'); const compression = require('compression'); +const squel = require('squel'); const libs = `${process.cwd()}/libs`; @@ -20,25 +21,35 @@ const mongoose = require(`${libs}/db/mongoose`); const db = mongoose(); +// Set default node environment process.env.NODE_ENV = process.env.NODE_ENV || 'development'; +// Parse json received in requests app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); +// Enable Cross-Origin Resource Sharing (CORS) app.use(cors()); app.use(methodOverride()); +// Enable cache for 1 day app.use(cache('1 day')); +// Enable maximum compression app.use(compression(9)); -app.use(api); +// Middleware tha adds the squel object to req +app.use((req, res, next) => { + req.sql = squel.select(); + next(); +}); +app.use('/api/v1', api); -// catch 404 and forward to error handler +// 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 +// Error handlers app.use((err, req, res, next) => { res.status(err.status || 500); log.error('%s %d %s', req.method, res.statusCode, err.message); diff --git a/src/libs/config.js b/src/libs/config.js index ef5a26cddbb3946b89ac690e8cf52d28884463aa..5aea5a9ca50efd0dac2253dd23690229431236dc 100644 --- a/src/libs/config.js +++ b/src/libs/config.js @@ -1,5 +1,6 @@ const nconf = require('nconf'); +// Exports the config.json as an object with get functions nconf.argv() .env() .file({ file: `${process.cwd()}/config.json` }); diff --git a/src/libs/db/monet.js b/src/libs/db/monet.js index 1cf874cc9ca5a2ada3e20bdd077c34fbcfbe8226..dd7e620adcf5b6a1dbe29358697fa71a3970ea03 100644 --- a/src/libs/db/monet.js +++ b/src/libs/db/monet.js @@ -4,10 +4,12 @@ const libs = `${process.cwd()}/libs`; const config = require(`${libs}/config`); +// Connection options const poolOptions = { nrConnections: config.get('monetdb:nrConnections'), }; +// Configuration options const options = { host: config.get('monetdb:host'), port: config.get('monetdb:port'), @@ -16,6 +18,7 @@ const options = { password: config.get('monetdb:password'), }; +// Connection singleton const conn = new MonetDBPool(poolOptions, options); conn.connect(); diff --git a/src/libs/db/mongoose.js b/src/libs/db/mongoose.js index 13b3950da47638b2183ffb5e9c72dab41303c6d3..b1a078fd26b5a13c987c5c3e17a899cd0f10cdf2 100644 --- a/src/libs/db/mongoose.js +++ b/src/libs/db/mongoose.js @@ -7,8 +7,10 @@ const log = require(`${libs}/log`)(module); const mongoose = require('mongoose'); module.exports = () => { + // Get mongodb URI (ip and port) in config file const mongoUri = config.get('mongodb:uri'); log.debug(`Connecting to MongDB on URI ${mongoUri}`); + // Connection singleton const db = mongoose.connect(mongoUri); mongoose.connection.once('open', () => { log.info("MongoDB connected"); }); diff --git a/src/libs/db/query_exec.js b/src/libs/db/query_exec.js index bd921d1d02deec90101260046707b7929434a10a..71ae6d9824ac304eb5fd4e4d0e122b16d8d159a2 100644 --- a/src/libs/db/query_exec.js +++ b/src/libs/db/query_exec.js @@ -1,40 +1,41 @@ + const libs = `${process.cwd()}/libs`; const log = require(`${libs}/log`)(module); const conn = require(`${libs}/db/monet`); -/** - * Promise that executes an SQL query with optional parameters - * - * Examples: - * Query with no parameters: - * execSqlQuery('SELECT * FROM people'); - * Query with one parameter: - * execSqlQuery('SELECT name, age FROM people WHERE id = ?', [1]); - * Query with more than one parameter: - * execSqlQuery('SELECT name, age FROM people WHERE city = ? AND age > ?', ['São Paulo', 35]); - * - * @param {string} sqlQuery - SQL query to be executed by the Promise - * @param {array} sqlQueryParams - SQL query parameters - */ + // Promise that executes an SQL query with optional parameters + // ``` + // Examples: + // Query with no parameters: + // execSqlQuery('SELECT * FROM people'); + // Query with one parameter: + // execSqlQuery('SELECT name, age FROM people WHERE id = ?', [1]); + // Query with more than one parameter: + // execSqlQuery('SELECT name, age FROM people WHERE city = ? AND age > ?', ['São Paulo', 35]); + // ``` function execSqlQuery(sqlQuery, sqlQueryParams = []) { log.debug(`Executing SQL query '${sqlQuery}' with params '${sqlQueryParams}'`); return new Promise((resolve, reject) => { + // Prepare statement conn.prepare(sqlQuery, true).then( (dbQuery) => { + // Execute query dbQuery.exec(sqlQueryParams).then( + // Success (dbResult) => { log.debug(`Query result: ${dbResult.data}`); log.debug(dbResult.data); resolve(dbResult.data); }, + // Error (dbError) => { log.error(`SQL query execution error: ${dbError.message}`); reject(new Error(dbError.message)); } ); - // release resources allocated for prepared statement + // Release resources allocated for prepared statement conn.release(); } ); diff --git a/src/libs/middlewares/dimensions.js b/src/libs/middlewares/dimensions.js deleted file mode 100644 index 618a510e028742dbbf4a8e9ca04d19d936d9e39e..0000000000000000000000000000000000000000 --- a/src/libs/middlewares/dimensions.js +++ /dev/null @@ -1,58 +0,0 @@ -/** -* Dimensions middleware -* -* EXAMPLE: -* Use it with no parameters to get all the dimensions specified -* app.get('/', dimensions(), function(req, res, next){}) -* -* Use it with an array of accepted values -* app.get('/', dimensions(['year', 'location']), function(req, res, next){}) -* -* Use it globally -* app.use(dimensions()) -*/ - -/** - * This function returns the intersection of two arrays - * @param {array} a [description] - * @param {array} b [description] - * @return {array} [description] - */ -function intersect(a, b) { - let t; - if (b.length > a.length) { - t = b; b = a; a = t; - } - return a.filter((e) => b.indexOf(e) !== -1); -} - -function dimensions(dims) { - return (req, res, next) => { - req.dims = {}; - if (req.query.dims) { - const params = req.query.dims.split(','); - const dimObj = {}; - for (const param of params) { - const kv = param.split(':'); - dimObj[kv[0]] = (typeof kv[1] === 'undefined') ? null : kv[1]; - } - // for(let i=0; i<params.length; ++i) { - // let kv = params[i].split(':'); - // dimObj[kv[0]] = (typeof kv[1] === 'undefined') ? null : kv[1]; - // } - - // If the dims array exists and is not empty - if (typeof dims !== 'undefined' && dims.length > 0) { - const intersection = intersect(dims, Object.keys(dimObj)); - for (let i = 0; i < intersection.length; ++i) { - req.dims[intersection[i]] = dimObj[intersection[i]]; - } - } else { - req.dims = dimObj; - } - } - next(); - }; -} - -module.exports = dimensions; diff --git a/src/libs/middlewares/parseParams.js b/src/libs/middlewares/parseParams.js new file mode 100644 index 0000000000000000000000000000000000000000..c74b6b491bc445b22cd480c85d9f232494a139de --- /dev/null +++ b/src/libs/middlewares/parseParams.js @@ -0,0 +1,66 @@ +/** +* ParseParams middleware +* +* EXAMPLE: +* Use it with no parameters to get all the params specified +* app.get('/', parseParams('dims'), function(req, res, next){}) +* +* Use it with an array of accepted values +* app.get('/', parseParams('filter', ['year', 'location']), function(req, res, next){}) +* +* Use it globally +* app.use(parseParams('dims')) +*/ + +const libs = `${process.cwd()}/libs`; + +const log = require(`${libs}/log`)(module); + + // This function returns the intersection of two arrays +function intersect(a, b) { + let t; + if (b.length > a.length) { + t = b; b = a; a = t; + } + return a.filter((e) => b.indexOf(e) !== -1); +} + +function parseParams(queryParam, arr) { + return (req, res, next) => { + req[queryParam] = {}; + if (req.query[queryParam]) { + const params = req.query[queryParam].split(','); + // Temporary object to hold the params and it's values + const obj = {}; + for (const param of params) { + // Get the key and the value - state:41 is key 'state' whith value 41 + const kv = param.split(':'); + // Check if there is a value. If there isn't, assign null + obj[kv[0]] = (typeof kv[1] === 'undefined') ? null : kv[1]; + } + + // If the array exists and is not empty we intersect + if (typeof arr !== 'undefined' && arr.length > 0) { + // Intersect the keys of the obj with the array arr. + // The intersection array is assigned with the keys + const intersection = intersect(arr, Object.keys(obj)); + // This is a bit tricky... + // For each key in the intersection array we get it's value in the obj + // and assign it to the custom attribute in the req obj. + // For example: instersection => ["state"] so + // obj[intersection[i]] (with i=0) is obj["state"], that is 41 + // and req[queryParam]["state"] = 41 + for (let i = 0; i < intersection.length; ++i) { + req[queryParam][intersection[i]] = obj[intersection[i]]; + } + req[queryParam].size = intersection.length; + } else { + req[queryParam] = obj; + req[queryParam].size = Object.keys(obj).length; + } + } + next(); + }; +} + +module.exports = parseParams; diff --git a/src/libs/middlewares/query.js b/src/libs/middlewares/query.js index 74a0c505cf02285dde3a35c0d0fc6365b8b4ce86..a4f20e3ba5bdd31cf7dab85a44121b8e8e456fc0 100644 --- a/src/libs/middlewares/query.js +++ b/src/libs/middlewares/query.js @@ -2,15 +2,11 @@ const libs = `${process.cwd()}/libs`; const log = require(`${libs}/log`)(module); const execQuery = require(`${libs}/db/query_exec`); -/** - * Middleware that executes a query defined by a squel object in req.sql - * @param {Object} req [description] - * @param {Object} res [description] - * @param {Function} next [description] - */ + // Middleware that executes a query defined by a squel object in req.sql function query(req, res, next) { - log.debug(req.sql); - execQuery(req.sql.text, req.sql.values).then((result) => { + let sql = req.sql.toParam(); + log.debug(sql); + execQuery(sql.text, sql.values).then((result) => { log.debug(result); req.result = result; next(); diff --git a/src/libs/middlewares/response.js b/src/libs/middlewares/response.js index 0b91b39e92f6c73555f3d2f0a6eef5dd11caeb93..d93961442e3b7e6dd14d6eb9c6e634e04957b758 100644 --- a/src/libs/middlewares/response.js +++ b/src/libs/middlewares/response.js @@ -3,10 +3,8 @@ const log = require(`${libs}/log`)(module); const xml = require('js2xmlparser'); const csv = require('csv-express'); -/** - * Custom generic middleware used to send api responses - * @param {string} value text to be used in the xml response - */ + // Custom generic middleware used respond requests. + // The function reads the req.query.format param and respond in json, xml or csv function response(value) { return (req, res, next) => { log.debug(req.query.format); diff --git a/src/libs/routes/api.js b/src/libs/routes/api.js index 61c7121d073f28fa7e5ca7e44d9a05f326850eac..cb474d86ccf45991988531055fa016f083896296 100644 --- a/src/libs/routes/api.js +++ b/src/libs/routes/api.js @@ -14,16 +14,16 @@ const school = require('./school'); const simulation = require('./simulation'); -api.get('/api/v1', (req, res) => { +api.get('/', (req, res) => { res.json({ msg: 'SimCAQ API is running' }); }); // mount API routes -api.use('/api/v1/enrollment', enrollment); -api.use('/api/v1/state', state); -api.use('/api/v1/region', region); -api.use('/api/v1/city', city); -api.use('/api/v1/school', school); -api.use('/api/v1/simulation', simulation); +api.use('/enrollment', enrollment); +api.use('/state', state); +api.use('/region', region); +api.use('/city', city); +api.use('/school', school); +api.use('/simulation', simulation); module.exports = api; diff --git a/src/libs/routes/city.js b/src/libs/routes/city.js index 030aa3cecda6c538a45d12543aa6d9449e685937..733da6f341cc37a7b6b792b8f5383076eae75067 100644 --- a/src/libs/routes/city.js +++ b/src/libs/routes/city.js @@ -1,6 +1,6 @@ const express = require('express'); -const cityApp = express(); +const cityApp = express.Router(); const libs = `${process.cwd()}/libs`; @@ -10,26 +10,30 @@ const query = require(`${libs}/middlewares/query`); const response = require(`${libs}/middlewares/response`); +// Return all cities cityApp.get('/', (req, res, next) => { - req.sql = squel.select().from('municipios').toParam(); + req.sql.from('municipios'); next(); }, query, response('city')); +// Return a specific city by it's id cityApp.get('/:id', (req, res, next) => { - req.sql = squel.select().from('municipios').where('pk_municipio_id = ?', - parseInt(req.params.id, 10)).toParam(); + req.sql.from('municipios') + .where('pk_municipio_id = ?', parseInt(req.params.id, 10)); next(); }, query, response('city')); +// Return a specific city by it's IBGE code cityApp.get('/ibge/:id', (req, res, next) => { - req.sql = squel.select().from('municipios').where('codigo_ibge = ?', - req.params.id).toParam(); + req.sql.from('municipios') + .where('codigo_ibge = ?', req.params.id); next(); }, query, response('city')); +// Return all the cities from a specific state cityApp.get('/state/:id', (req, res, next) => { - req.sql = squel.select().from('municipios').where('fk_estado_id = ?', - parseInt(req.params.id, 10)).toParam(); + req.sql.from('municipios') + .where('fk_estado_id = ?', parseInt(req.params.id, 10)); next(); }, query, response('city')); diff --git a/src/libs/routes/enrollment.js b/src/libs/routes/enrollment.js index 0ec49b84abb4dac94d3bce2b838c6c51ac038175..a73f203fe0cdbf8c5cbd4300541f3d2383cfec9d 100644 --- a/src/libs/routes/enrollment.js +++ b/src/libs/routes/enrollment.js @@ -1,6 +1,6 @@ const express = require('express'); -const enrollmentApp = express(); +const enrollmentApp = express.Router(); const libs = `${process.cwd()}/libs`; @@ -12,231 +12,207 @@ const query = require(`${libs}/middlewares/query`); const response = require(`${libs}/middlewares/response`); -// **Temporary** solution to add where clauses that are common to all requests -function filter(req, q) { - if (typeof req.min_year !== 'undefined') { - q.where('ano_censo>=?', req.min_year); - } +const parseParams = require(`${libs}/middlewares/parseParams`); - if (typeof req.max_year !== 'undefined') { - q.where('ano_censo<=?', req.max_year); - } - - if (typeof req.adm_dependency_id !== 'undefined') { - q.where('fk_dependencia_adm_id=?', req.adm_dependency_id); - } - - if (typeof req.location_id !== 'undefined') { - q.where('id_localizacao=?', req.location_id); - } +// **Temporary** solution to add where clauses that are common to all requests - if (typeof req.education_level_id !== 'undefined') { - q.where('fk_etapa_ensino_id=?', req.education_level_id); - } -} -/** - * Complete range of the enrollments dataset - * - * Returns a tuple of start and ending years of the complete enrollments dataset. - */ +// Complete range of the enrollments dataset. +// Returns a tuple of start and ending years of the complete enrollments dataset. enrollmentApp.get('/year_range', (req, res, next) => { req.sql = squel.select() .from('turmas') .field('MIN(turmas.ano_censo)', 'start_year') - .field('MAX(turmas.ano_censo)', 'end_year') - .toParam(); + .field('MAX(turmas.ano_censo)', 'end_year'); next(); }, query, response('range')); -/** - * Returns all educational levels avaible - * - */ +// Returns all educational levels avaible enrollmentApp.get('/education_level', (req, res, next) => { req.sql = squel.select() .from('etapa_ensino') .field('pk_etapa_ensino_id', 'id') - .field('desc_etapa', 'name') - .toParam(); + .field('desc_etapa', 'name'); next(); }, query, response('education_level')); -/** - * Returns all adm dependency - * - */ +// Returns all adm dependencies enrollmentApp.get('/adm_dependency', (req, res, next) => { req.sql = squel.select() .from('dependencia_adms') .field('pk_dependencia_adm_id', 'id') - .field('nome', 'name') - .toParam(); + .field('nome', 'name'); next(); }, query, response('adm_dependency')); enrollmentApp.get('/data', (req, res, next) => { - req.sql = squel.select().from('turmas').toParam(); + req.sql = squel.select().from('turmas'); next(); }, query, response('data')); -enrollmentApp.use('/', (req, res, next) => { - const params = req.query; - req.paramCnt = 0; +// Parse the filters and dimensions parameter in the query +enrollmentApp.use('/', parseParams('filter', [ + 'min_year', + 'max_year', + 'adm_dependency_id', + 'location_id', + 'education_level_id', + 'region', + 'state', + 'city', + 'school' +]), parseParams('dims', [ + 'adm_dependency_id', + 'location_id', + 'education_level_id', + 'region', + 'state', + 'city', + 'school' +]), (req, res, next) => { + log.debug(req.filter); + log.debug(req.dims); - if (typeof params.id !== 'undefined') { - req.id = parseInt(params.id, 10); - req.paramCnt += 1; + // Do the joins + if(typeof req.filter.adm_dependency_id !== 'undefined' + || typeof req.dims.adm_dependency_id !== 'undefined') { + req.sql.join('dependencia_adms', null, 'fk_dependencia_adm_id=dependencia_adms.pk_dependencia_adm_id'); } - if (typeof params.location_id !== 'undefined') { - req.location_id = parseInt(params.location_id, 10); - req.paramCnt += 1; + if(typeof req.filter.education_level_id !== 'undefined' + || typeof req.dims.education_level_id !== 'undefined') { + req.sql.join('etapa_ensino', null, 'fk_etapa_ensino_id=etapa_ensino.pk_etapa_ensino_id'); } - if (typeof params.adm_dependency_id !== 'undefined') { - req.adm_dependency_id = parseInt(params.adm_dependency_id, 10); - req.paramCnt += 1; + if(typeof req.filter.region !== 'undefined' + || typeof req.dims.region !== 'undefined') { + req.sql.join('municipios', null, 'fk_municipio_id=municipios.pk_municipio_id') + .join('estados', null, 'municipios.fk_estado_id=estados.pk_estado_id') + .join('regioes', null, 'estados.fk_regiao_id=regioes.pk_regiao_id'); } - if (typeof params.min_year !== 'undefined') { - req.min_year = parseInt(params.min_year, 10); - req.paramCnt += 1; + if((typeof req.filter.state !== 'undefined' + || typeof req.dims.state !== 'undefined') + && (typeof req.filter.region === 'undefined' + && typeof req.dims.region === 'undefined')) { + req.sql.join('municipios', null, 'fk_municipio_id=municipios.pk_municipio_id') + .join('estados', null, 'municipios.fk_estado_id=estados.pk_estado_id'); } - if (typeof params.max_year !== 'undefined') { - req.max_year = parseInt(params.max_year, 10); - req.paramCnt += 1; + if((typeof req.filter.city !== 'undefined' + || typeof req.dims.city !== 'undefined') + && (typeof req.filter.state === 'undefined' + && typeof req.dims.state === 'undefined') + && (typeof req.filter.region === 'undefined' + && typeof req.dims.region === 'undefined')) { + req.sql.join('municipios', null, 'fk_municipio_id=municipios.pk_municipio_id'); } - if (typeof params.education_level_id !== 'undefined') { - req.education_level_id = parseInt(params.education_level_id, 10); - req.paramCnt += 1; + if(typeof req.dims.school !== 'undefined') { + req.sql.join('escolas', null, 'fk_escola_id=escolas.pk_escola_id'); } - next(); -}); + // Dimensions (add fields) -enrollmentApp.use('/', (req, res, next) => { - const params = req.query; - if (typeof params.aggregate !== 'undefined' && params.aggregate === 'region') { - log.debug('Using enrollments query for regions'); - const q = squel.select().from('mat_regioes') - .field('name') - .field('SUM(total)', 'total') - .field('ano_censo', 'year'); + if(typeof req.dims.education_level_id !== 'undefined') { + req.sql.field('desc_etapa', 'education_level') + .group('desc_etapa') + .order('desc_etapa'); + } - filter(req, q); + if(typeof req.dims.region !== 'undefined') { + req.sql.field('regioes.nome', 'region_name') + .group('regioes.nome') + .order('regioes.nome'); + } - if (typeof req.id !== 'undefined') { - q.where('pk_regiao_id=?', req.id); - } - req.sql = q.group('name').group('ano_censo').order('ano_censo').toParam(); + if(typeof req.dims.state !== 'undefined') { + req.sql.field('estados.nome', 'state_name') + .group('estados.nome') + .order('estados.nome'); } - next(); -}); -enrollmentApp.use('/', (req, res, next) => { - const params = req.query; - if (typeof params.aggregate !== 'undefined' && params.aggregate === 'state') { - log.debug('Using enrollments query for states'); - const q = squel.select().from('mat_estados') - .field('name') - .field('SUM(total)', 'total') - .field('ano_censo', 'year'); + if(typeof req.dims.city !== 'undefined') { + req.sql.field('municipios.nome', 'city_name') + .group('municipios.nome') + .order('municipios.nome'); + } - filter(req, q); + if(typeof req.dims.school !== 'undefined') { + req.sql.field('escolas.nome_entidade', 'school_name') + .group('escolas.nome_entidade') + .order('escolas.nome_entidade'); + } - if (typeof req.id !== 'undefined') { - q.where('pk_estado_id=?', req.id); - } - req.sql = q.group('name').group('ano_censo').order('ano_censo').toParam(); + if(typeof req.dims.adm_dependency_id !== 'undefined') { + req.sql.field('dependencia_adms.nome', 'adm_dependency_name') + .group('dependencia_adms.nome') + .order('dependencia_adms.nome'); } - next(); -}); -enrollmentApp.use('/', (req, res, next) => { - const params = req.query; - if (typeof params.aggregate !== 'undefined' && params.aggregate === 'city') { - log.debug('Using enrollments query for cities'); - const q = squel.select().from('mat_municipios') - .field('name') - .field('SUM(total)', 'total') - .field('ano_censo', 'year'); + if(typeof req.dims.location_id !== 'undefined') { + req.sql.field('turmas.id_localizacao', 'location') + .group('turmas.id_localizacao') + .order('turmas.id_localizacao'); + } - filter(req, q); + if(typeof req.dims.region === 'undefined' + && typeof req.dims.state === 'undefined' + && typeof req.dims.city === 'undefined') { + req.sql.field("'Brasil'", 'name'); + } - if (typeof req.id !== 'undefined') { - q.where('pk_municipio_id=?', req.id); - } - req.sql = q.group('name').group('ano_censo').order('ano_censo').toParam(); + // Filter (add where) + + if (typeof req.filter.min_year !== 'undefined') { + req.sql.where('turmas.ano_censo>=?', parseInt(req.filter.min_year, 10)); } - next(); -}); -enrollmentApp.use('/', (req, res, next) => { - const params = req.query; - if (typeof params.aggregate !== 'undefined' && params.aggregate === 'school') { - log.debug('Using enrollments query for schools'); - const q = squel.select().from('mat_escolas') - .field('name') - .field('SUM(total)', 'total') - .field('ano_censo', 'year'); + if (typeof req.filter.max_year !== 'undefined') { + req.sql.where('turmas.ano_censo<=?', parseInt(req.filter.max_year, 10)); + } - filter(req, q); + if (typeof req.filter.adm_dependency_id !== 'undefined') { + req.sql.where('pk_dependencia_adm_id=?', parseInt(req.filter.adm_dependency_id, 10)); + } - if (typeof req.id !== 'undefined') { - q.where('pk_escola_id=?', req.id); - } - req.sql = q.group('name').group('ano_censo').order('ano_censo').toParam(); + if (typeof req.filter.location_id !== 'undefined') { + req.sql.where('turmas.id_localizacao=?', parseInt(req.filter.location_id, 10)); + } + + if (typeof req.filter.education_level_id !== 'undefined') { + req.sql.where('pk_etapa_ensino_id=?', parseInt(req.filter.education_level_id, 10)); + } + + if (typeof req.filter.region !== 'undefined') { + req.sql.where('pk_regiao_id=?', parseInt(req.filter.region, 10)); } - next(); -}); -enrollmentApp.use('/', (req, res, next) => { - const params = req.query; - if (typeof params.aggregate === 'undefined') { - log.debug('Using enrollments query for the whole country'); - const q = squel.select().from('turmas').field("'Brasil'", 'name') - .field('COALESCE(SUM(num_matriculas),0)', 'total') - .field('ano_censo', 'year'); + if (typeof req.filter.state !== 'undefined') { + req.sql.where('pk_estado_id=?', parseInt(req.filter.state, 10)); + } - filter(req, q); + if (typeof req.filter.city !== 'undefined') { + req.sql.where('turmas.fk_municipio_id=?', parseInt(req.filter.city, 10)); + } - req.sql = q.group('ano_censo').order('ano_censo').toParam(); + if (typeof req.filter.school !== 'undefined') { + req.sql.where('turmas.fk_escola_id=?', parseInt(req.filter.school, 10)); } + log.debug(req.sql.toParam()); next(); }); enrollmentApp.get('/', (req, res, next) => { - log.debug(`Request parameters: ${req}`); + req.sql.field('COALESCE(SUM(num_matriculas), 0)', 'total') + .field('turmas.ano_censo', 'year') + .from('turmas') + .group('turmas.ano_censo') + .order('turmas.ano_censo'); next(); -}, query, response('enrollments')); - -// enrollmentApp.get('/', (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!'); -// next('Internal error, request could not be satisfied at this moment. Please, ' -// + 'try again later'); -// } else { -// log.debug('SQL query: ${ req.sqlQuery }?'); -// log.debug(req.sqlQuery); -// dbQuery(req.sqlQuery).then((result) => { -// req.result = result; -// return response(req, res); -// }, (error) => { -// log.error(`[${req.originalUrl}] SQL query error: ${error}`); -// next('Internal error, request could not be satisfied at this moment. Please, ' -// + 'try again later'); -// }); -// } -// }); +}, query, response('test')); module.exports = enrollmentApp; diff --git a/src/libs/routes/region.js b/src/libs/routes/region.js index 754cd6164725bebf8c5d6cfb387b3f0656eb615b..773ad347959049dd2137d96abb255a3bc809a2d4 100644 --- a/src/libs/routes/region.js +++ b/src/libs/routes/region.js @@ -1,6 +1,6 @@ const express = require('express'); -const regionApp = express(); +const regionApp = express.Router(); const libs = `${process.cwd()}/libs`; @@ -10,14 +10,16 @@ const query = require(`${libs}/middlewares/query`); const response = require(`${libs}/middlewares/response`); +// Get all regions regionApp.get('/', (req, res, next) => { - req.sql = squel.select().from('regioes').toParam(); + req.sql.from('regioes'); next(); }, query, response('region')); +// Get a region by it's id regionApp.get('/:id', (req, res, next) => { - req.sql = squel.select().from('regioes').where('pk_regiao_id = ?', - parseInt(req.params.id, 10)).toParam(); + req.sql.from('regioes') + .where('pk_regiao_id = ?', parseInt(req.params.id, 10)); next(); }, query, response('region')); diff --git a/src/libs/routes/school.js b/src/libs/routes/school.js index bf3ee4fb1ec97ad326f68f4d536d27776ad64531..8662d945f4f39989bc318709e7beab9802a3bfb8 100644 --- a/src/libs/routes/school.js +++ b/src/libs/routes/school.js @@ -1,6 +1,6 @@ const express = require('express'); -const schoolApp = express(); +const schoolApp = express.Router(); const libs = `${process.cwd()}/libs`; @@ -22,40 +22,38 @@ const response = require(`${libs}/middlewares/response`); // .field('nome_entidade', 'name') // .field('ano_censo', 'year') // .field('fk_cod_estado') -// .field('fk_cod_municipio') -// .toParam(); +// .field('fk_cod_municipio'); // next(); // }, query, response('school')); +// Get a school by it's id schoolApp.get('/:id', (req, res, next) => { - req.sql = squel.select().from('escolas').where('pk_escola_id = ?', - parseInt(req.params.id, 10)).toParam(); + req.sql.from('escolas') + .where('pk_escola_id = ?', parseInt(req.params.id, 10)); next(); }, query, response('school')); +// Get all schools from a state schoolApp.get('/state/:id', (req, res, next) => { - req.sql = squel.select().from('escolas') + req.sql.from('escolas') .field('pk_escola_id') .field('nome_entidade', 'name') .field('ano_censo', 'year') .field('fk_cod_estado') .field('fk_cod_municipio') - .where('fk_cod_estado = ?', - parseInt(req.params.id, 10)) - .toParam(); + .where('fk_cod_estado = ?', parseInt(req.params.id, 10)); next(); }, query, response('school')); +// Get all schools from a city schoolApp.get('/city/:id', (req, res, next) => { - req.sql = squel.select().from('escolas') + req.sql.from('escolas') .field('pk_escola_id') .field('nome_entidade', 'name') .field('ano_censo', 'year') .field('fk_cod_estado') .field('fk_cod_municipio') - .where('fk_cod_municipio = ?', - parseInt(req.params.id, 10)) - .toParam(); + .where('fk_cod_municipio = ?', parseInt(req.params.id, 10)); next(); }, query, response('school')); diff --git a/src/libs/routes/state.js b/src/libs/routes/state.js index 637149f1a9b9db0fb90a7ccdfaffb4381c406152..75e1c0b7835901804ea78e459b1fe0050ec03631 100644 --- a/src/libs/routes/state.js +++ b/src/libs/routes/state.js @@ -1,6 +1,6 @@ const express = require('express'); -const stateApp = express(); +const stateApp = express.Router(); const libs = `${process.cwd()}/libs`; @@ -10,20 +10,23 @@ const query = require(`${libs}/middlewares/query`); const response = require(`${libs}/middlewares/response`); +// Get all states stateApp.get('/', (req, res, next) => { - req.sql = squel.select().from('estados').toParam(); + req.sql.from('estados'); next(); }, query, response('state')); +// Get a state stateApp.get('/:id', (req, res, next) => { - req.sql = squel.select().from('estados').where('pk_estado_id = ?', - parseInt(req.params.id, 10)).toParam(); + req.sql.from('estados') + .where('pk_estado_id = ?', parseInt(req.params.id, 10)); next(); }, query, response('state')); +// Get all states from a region stateApp.get('/region/:id', (req, res, next) => { - req.sql = squel.select().from('estados').where('fk_regiao_id = ?', - parseInt(req.params.id, 10)).toParam(); + req.sql.from('estados') + .where('fk_regiao_id = ?', parseInt(req.params.id, 10)); next(); }, query, response('state')); diff --git a/src/server.js b/src/server.js index 4dde9b8e4231800ac40678ff66fec3086ffcd6af..d9e37caca1a103588bc2ecb90bc0034cea1d3efd 100644 --- a/src/server.js +++ b/src/server.js @@ -8,8 +8,10 @@ const log = require(`${libs}/log`)(module); const app = require(`${libs}/app`); +// Set default port: first environment variable PORT, then configuration and last 3000 app.set('port', process.env.PORT || config.get('port') || 3000); +// Set default ip: first environment variable IOP, then configuration and last '127.0.0.1' app.set('ip', process.env.IP || config.get('ip') || '127.0.0.1'); const server = app.listen(app.get('port'), () => { diff --git a/src/test/test.js b/src/test/test.js index dde277d0dec15186290a5a0aa992dd60d8e5c7a4..e6bdd1fd0edf6ce272f73a68d6749401705859b6 100644 --- a/src/test/test.js +++ b/src/test/test.js @@ -19,7 +19,7 @@ chai.use(chaiHttp); describe('request enrollments', () => { it('should list enrollments', (done) => { chai.request(server) - .get('/v1/enrollment') + .get('/api/v1/enrollment') .end((err, res) => { res.should.have.status(200); res.should.be.json; @@ -35,7 +35,7 @@ describe('request enrollments', () => { describe('request regions', () => { it('should list all regions', (done) => { chai.request(server) - .get('/v1/region') + .get('/api/v1/region') .end((err, res) => { res.should.have.status(200); res.should.be.json; @@ -49,7 +49,7 @@ describe('request regions', () => { it('should list region by id', (done) => { chai.request(server) - .get('/v1/region/1') + .get('/api/v1/region/1') .end((err, res) => { res.should.have.status(200); res.should.be.json; @@ -66,7 +66,7 @@ describe('request regions', () => { describe('request states', () => { it('should list all states', (done) => { chai.request(server) - .get('/v1/state') + .get('/api/v1/state') .end((err, res) => { res.should.have.status(200); res.should.be.json; @@ -81,7 +81,7 @@ describe('request states', () => { it('should list a state by id', (done) => { chai.request(server) - .get('/v1/state/11') + .get('/api/v1/state/11') .end((err, res) => { res.should.have.status(200); res.should.be.json; @@ -97,7 +97,7 @@ describe('request states', () => { it('should list states by region id', (done) => { chai.request(server) - .get('/v1/state/region/1') + .get('/api/v1/state/region/1') .end((err, res) => { res.should.have.status(200); res.should.be.json; @@ -114,7 +114,7 @@ describe('request states', () => { describe('request cities', () => { it('should list all cities', (done) => { chai.request(server) - .get('/v1/city') + .get('/api/v1/city') .end((err, res) => { res.should.have.status(200); res.should.be.json; @@ -130,7 +130,7 @@ describe('request cities', () => { it('should list a city by id', (done) => { chai.request(server) - .get('/v1/city/1') + .get('/api/v1/city/1') .end((err, res) => { res.should.have.status(200); res.should.be.json; @@ -146,7 +146,7 @@ describe('request cities', () => { it('should list a city by codigo_ibge', (done) => { chai.request(server) - .get('/v1/city/ibge/1200013') + .get('/api/v1/city/ibge/1200013') .end((err, res) => { res.should.have.status(200); res.should.be.json;