diff --git a/src/libs/convert/booleanVariable.js b/src/libs/convert/booleanVariable.js new file mode 100644 index 0000000000000000000000000000000000000000..4014da94c8cdc5dcbb380488b72cca544b1f817a --- /dev/null +++ b/src/libs/convert/booleanVariable.js @@ -0,0 +1,8 @@ +module.exports = function admDependency(id) { + if (id == null) + return 'Não DisponÃvel'; + else if (id == false) + return 'Não'; + else if (id == true) + return 'Sim'; +}; diff --git a/src/libs/middlewares/id2str.js b/src/libs/middlewares/id2str.js index 99bb7793c456fb5823bb30f3adc26b3bd7895266..f7c45b772ec73b601b1d8faec215457e356f5adf 100644 --- a/src/libs/middlewares/id2str.js +++ b/src/libs/middlewares/id2str.js @@ -5,6 +5,7 @@ const schoolYear = require(`${libs}/convert/schoolYear`); const admDependency = require(`${libs}/convert/admDependency`); const location = require(`${libs}/convert/location`); const ethnicGroup = require(`${libs}/convert/ethnicGroup`); +const booleanVariable = require(`${libs}/convert/booleanVariable`); const educationLevel = require(`${libs}/convert/educationLevel`); const educationLevelMod = require(`${libs}/convert/educationLevelMod`); @@ -17,7 +18,8 @@ const ids = { adm_dependency_id: admDependency, adm_dependency_detailed_id: admDependency, location_id: location, - ethnic_group_id: ethnicGroup + ethnic_group_id: ethnicGroup, + integral_time_id: booleanVariable }; function transform(removeId=false) { @@ -44,6 +46,7 @@ module.exports = { admDependency, location, ethnicGroup, + booleanVariable, educationLevel, educationLevelMod }; diff --git a/src/libs/middlewares/query.js b/src/libs/middlewares/query.js index ecef42951b95deabe9a11c3d90e9af11dc01d013..97df67e0666a6dde0749ee3cb3c7f1fc0134e81e 100644 --- a/src/libs/middlewares/query.js +++ b/src/libs/middlewares/query.js @@ -5,9 +5,8 @@ const execQuery = require(`${libs}/db/query_exec`); // Middleware that executes a query defined by a squel object in req.sql function query(req, res, next) { let sql = req.sql.toParam(); - log.debug(sql); + log.debug(req.sql.toString()); execQuery(sql.text, sql.values).then((result) => { - log.debug(result); req.result = result; if (result.length === 0) { next({status: 404, message: 'No results found in database'}); diff --git a/src/libs/routes/api.js b/src/libs/routes/api.js index d2119dd2694ab2aa24186a180f3b8bb67b82518f..f28dc6c1aaeffd19ca257b9af51ec4510d285719 100644 --- a/src/libs/routes/api.js +++ b/src/libs/routes/api.js @@ -8,7 +8,7 @@ const config = require(`${libs}/config`); const classes = require('./class'); -const cache = require('apicache').options({ debug: config.debug }).middleware; +const cache = require('apicache').options({ debug: config.debug, statusCodes: {include: [200]} }).middleware; const enrollment = require('./enrollment'); @@ -26,6 +26,8 @@ const simulation = require('./simulation'); const user = require('./user'); +const classroom = require('./classroom'); + api.get('/', (req, res) => { res.json({ msg: 'SimCAQ API is running' }); }); @@ -40,5 +42,6 @@ api.use('/region', cache('15 day'), region); api.use('/city', cache('15 day'), city); api.use('/school', cache('15 day'), school); api.use('/spatial', cache('1 day'), spatial); +api.use('/classroom', cache('15 day'), classroom); module.exports = api; diff --git a/src/libs/routes/class.js b/src/libs/routes/class.js index 51f35af4557743ad104bea93bfdd50a6927fee97..39291ddc4ab0073823c2b34972638da0e2f751ed 100644 --- a/src/libs/routes/class.js +++ b/src/libs/routes/class.js @@ -58,13 +58,23 @@ classApp.get('/period', (req, res, next) => { next(); }, query, response('period')); +// Returns integral-time avaible +classApp.get('/integral_time', (req, res, next) => { + req.result = [ + {id: null, name: 'Não DisponÃvel'}, + {id: 0, name: 'Não'}, + {id: 1, name: 'Sim'} + ]; + next(); +}, response('integral_time')); + // Returns all educational levels avaible -classApp.get('/education_level', (req, res, next) => { +classApp.get('/education_level_mod', (req, res, next) => { req.sql.from('etapas_mod_ensino_segmento') .field('id') .field('nome', 'name'); next(); -}, query, response('education_level')); +}, query, response('education_level_mod')); rqfCount.addField({ name: 'filter', @@ -162,10 +172,10 @@ rqfCount.addField({ field: 'localizacao_id' } }).addValue({ - name:'education_level', + name:'education_level_mod', table: 'turma', tableField: 'etapas_mod_ensino_segmento_id', - resultField: 'education_level_id', + resultField: 'education_level_mod_id', where: { relation: '=', type: 'integer', @@ -191,6 +201,16 @@ rqfCount.addField({ type: 'integer', field: 'turma_turno_id' } +}).addValue({ + name:'integral_time', + table: 'turma', + tableField: 'tempo_integral', + resultField: 'integral_time_id', + where: { + relation: '=', + type: 'boolean', + field: 'tempo_integral' + } }).addValue({ name: 'school', table: 'escola', diff --git a/src/libs/routes/classroom.js b/src/libs/routes/classroom.js new file mode 100644 index 0000000000000000000000000000000000000000..7140bba1b34a03138bbac1fccdf73f07c721ebe6 --- /dev/null +++ b/src/libs/routes/classroom.js @@ -0,0 +1,181 @@ +const express = require('express'); + +const classroomApp = express.Router(); + +const libs = `${process.cwd()}/libs`; + +const squel = require('squel'); + +const query = require(`${libs}/middlewares/query`); + +const response = require(`${libs}/middlewares/response`); + +const ReqQueryFields = require(`${libs}/middlewares/reqQueryFields`); + +const id2str = require(`${libs}/middlewares/id2str`); + +let rqf = new ReqQueryFields(); + +let rqfCount = new ReqQueryFields(); + +// Complete range of the enrollments dataset. +// Returns a tuple of start and ending years of the complete enrollments dataset. +classroomApp.get('/year_range', (req, res, next) => { + req.sql.from('escola') + .field('MIN(escola.ano_censo)', 'start_year') + .field('MAX(escola.ano_censo)', 'end_year'); + next(); +}, query, response('range')); + +classroomApp.get('/adm_dependency', (req, res, next) => { + req.sql.from('dependencia_adm') + .field('id') + .field('nome', 'name') + .where('id <= 4'); + next() +}, query, response('adm_dependency')); + +classroomApp.get('/adm_dependency_detailed', (req, res, next) => { + req.sql.from('dependencia_adm') + .field('id', 'id') + .field('nome', 'name'); + next() +}, query, response('adm_dependency_detailed')); + +classroomApp.get('/location', (req, res, next) => { + req.result = [ + {id: 1, name: 'Urbana'}, + {id: 2, name: 'Rural'} + ]; + next(); +}, response('location')); + +rqf.addField({ + name: 'filter', + field: false, + where: true +}).addField({ + name: 'dims', + field: true, + where: false +}).addValue({ + name: 'school', + table: 'escola', + tableField: 'id', + where: { + relation: '=', + type: 'integer', + field: 'id' + } +}).addValue({ + name: 'city', + table: 'municipio', + tableField: 'nome', + resultField: 'city_name', + where: { + relation: '=', + type: 'integer', + field: 'municipio_id', + table: 'escola' + }, + join: { + primary: 'id', + foreign: 'municipio_id', + foreignTable: 'escola' + } +}).addValue({ + name: 'state', + table: 'estado', + tableField: 'nome', + resultField: 'state_name', + where: { + relation: '=', + type: 'integer', + field: 'estado_id', + table: 'escola' + }, + join: { + primary: 'id', + foreign: 'estado_id', + foreignTable: 'escola' + } +}).addValue({ + name: 'region', + table: 'regiao', + tableField: 'nome', + resultField: 'region_name', + where: { + relation: '=', + type: 'integer', + field: 'id' + }, + join: { + primary: 'id', + foreign: 'regiao_id', + foreignTable: 'escola' + } +}).addValue({ + name: 'min_year', + table: 'escola', + tableField: 'ano_censo', + resultField: 'year', + where: { + relation: '>=', + type: 'integer', + field: 'ano_censo' + } +}).addValue({ + name: 'max_year', + table: 'escola', + tableField: 'ano_censo', + resultField: 'year', + where: { + relation: '<=', + type: 'integer', + field: 'ano_censo' + } +}).addValue({ + name: 'adm_dependency', + table: 'escola', + tableField: 'dependencia_adm_id', + resultField: 'adm_dependency_id', + where: { + relation: '=', + type: 'integer', + field: 'dependencia_adm_id' + } +}).addValue({ + name: 'adm_dependency_detailed', + table: 'escola', + tableField: 'dependencia_adm_priv', + resultField: 'adm_dependency_detailed_id', + where: { + relation: '=', + type: 'integer', + field: 'dependencia_adm_priv' + } +}).addValue({ + name: 'location', + table: 'escola', + tableField: 'cod_localizacao', + resultField: 'location_id', + where: { + relation: '=', + type: 'integer', + field: 'cod_localizacao' + } +}); + +classroomApp.get('/', rqf.parse(), rqf.build(), (req, res, next) => { + console.log(req.filter); + req.sql.from('escola') + .field('SUM(escola.num_salas)', 'total') + .field("'Brasil'", 'name') + .field('escola.ano_censo', 'year') + .group('escola.ano_censo') + .order('escola.ano_censo') + .where('escola.situacao_de_funcionamento = 1 AND escola.local_func_predio_escolar = 1'); + next(); +}, query, id2str.transform(true), response('classroom')); + +module.exports = classroomApp; diff --git a/src/server.js b/src/server.js index ccc5e884702641fca04e0fc0480860a4b72300ef..f91eb88bf71dd6cf36618e2d8debfdd3e3508f01 100644 --- a/src/server.js +++ b/src/server.js @@ -1,29 +1,44 @@ -const debug = require('debug')('node-express-base'); - +const debug = require('debug')('simcaq-api'); const libs = `${process.cwd()}/libs`; - const config = require(`${libs}/config`); - const log = require(`${libs}/log`)(module); - const app = require(`${libs}/app`); - const compatVersion = require(`${libs}/middlewares/checkVersion`); +const cluster = require('cluster'); // Check if Node version is compatible if (!compatVersion()) { process.exit(1); } -// Set default port: first environment variable PORT, then configuration and last 3000 -app.set('port', process.env.PORT || config.port || 3000); -process.env.NODE_ENV = process.env.NODE_ENV || 'development'; -// Set default ip: first environment variable IOP, then configuration and last '127.0.0.1' -app.set('ip', process.env.IP || config.ip || '127.0.0.1'); - -const server = app.listen(app.get('port'), () => { - log.info(`Express server listening on port ${server.address().port}`); -}); - -// For testing -module.exports = server; +if(cluster.isMaster) { + log.info(`Master ${process.pid} is running`); + + const numCPUs = require('os').cpus().length; + log.info(`Master will create ${numCPUs} workers`); + for(let i=0; i < numCPUs; ++i) { + cluster.fork(); + } + + // Caso uma instâcia morra + cluster.on('exit', (worker, code, signal) => { + log.info(`Worker ${worker.process.pid} died`); + // Revive a instância + cluster.fork(); + }); +} else { + // Set default port: first environment variable PORT, then configuration and last 3000 + app.set('port', process.env.PORT || config.port || 3000); + process.env.NODE_ENV = process.env.NODE_ENV || 'development'; + // Set default ip: first environment variable IOP, then configuration and last '127.0.0.1' + app.set('ip', process.env.IP || config.ip || '127.0.0.1'); + + const server = app.listen(app.get('port'), () => { + log.info(`Express server listening on port ${server.address().port}`); + }); + + log.info(`Worker ${cluster.worker.id} is running (${process.pid})`); + + // For testing + module.exports = server; +} diff --git a/src/test/class.js b/src/test/class.js index bcc0660acdf60b7bb38a1a7fc065512f8ff6c365..32f89e3bacec3313f59067b8ff1102da230b037b 100644 --- a/src/test/class.js +++ b/src/test/class.js @@ -40,7 +40,7 @@ describe('request class', () => { it('should list the education level', (done) => { chai.request(server) - .get('/api/v1/class/education_level') + .get('/api/v1/class/education_level_mod') .end((err, res) => { res.should.have.status(200); res.should.be.json; @@ -155,7 +155,7 @@ describe('request class', () => { it('should list class with valid dimensions and filters', (done) => { chai.request(server) - .get('/api/v1/class?dims=region,state,education_level,school,period&filter=min_year:2015,max_year:2015,city:4106902') + .get('/api/v1/class?dims=region,state,education_level_mod,school,period&filter=min_year:2015,max_year:2015,city:4106902') .end((err, res) => { res.should.have.status(200); res.should.be.json; @@ -164,7 +164,7 @@ describe('request class', () => { res.body.result[0].should.have.property('region_name'); res.body.result[0].should.have.property('state_name'); res.body.result[0].should.have.property('school_name'); - res.body.result[0].should.have.property('education_level_name'); + res.body.result[0].should.have.property('education_level_mod_name'); res.body.result[0].should.have.property('total'); res.body.result[0].should.have.property('year'); done(); diff --git a/src/test/classroom.js b/src/test/classroom.js new file mode 100644 index 0000000000000000000000000000000000000000..bd520cf8b67ad396f8251092bf0a4036bb65e36a --- /dev/null +++ b/src/test/classroom.js @@ -0,0 +1,265 @@ +process.env.NODE_ENV = 'test'; + +const chai = require('chai'); + +const dirtyChai = require('dirty-chai'); + +chai.use(dirtyChai); + +const chaiXml = require('chai-xml'); + +chai.use(chaiXml); + +const chaiHttp = require('chai-http'); + +const assert = chai.assert; + +const expect = chai.expect; + +const should = chai.should(); // actually call the function + +const libs = `${process.cwd()}/libs`; + +const server = require(`${libs}/app`); + +chai.use(chaiHttp); +describe('request classrooms', () => { + + it('should list the locations', (done) => { + chai.request(server) + .get('/api/v1/classroom/location') + .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('id'); + res.body.result[0].should.have.property('name'); + done(); + }); + }); + + it('should list the administrative dependencies', (done) => { + chai.request(server) + .get('/api/v1/classroom/adm_dependency') + .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('id'); + res.body.result[0].should.have.property('name'); + done(); + }); + }); + + it('should list the detailed administrative dependencies', (done) => { + chai.request(server) + .get('/api/v1/classroom/adm_dependency_detailed') + .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('id'); + res.body.result[0].should.have.property('name'); + done(); + }); + }); + + it('should list classrooms', (done) => { + chai.request(server) + .get('/api/v1/classroom') + .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(); + }); + }); + + it('should list classrooms with valid filters', (done) => { + chai.request(server) + .get('/api/v1/classroom?filter=city:4106902') + .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(); + }); + }); + + it('should list classrooms with valid filters', (done) => { + chai.request(server) + .get('/api/v1/classroom?filter=state:41') + .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(); + }); + }); + + it('should list classrooms with valid filters', (done) => { + chai.request(server) + .get('/api/v1/classroom?filter=region:4') + .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(); + }); + }); + + it('should list classrooms with valid filters', (done) => { + chai.request(server) + .get('/api/v1/classroom?filter=adm_dependency:3') + .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(); + }); + }); + + it('should list classrooms with valid filters', (done) => { + chai.request(server) + .get('/api/v1/classroom?filter=adm_dependency_detailed:5') + .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(); + }); + }); + + it('should list classrooms with valid filters', (done) => { + chai.request(server) + .get('/api/v1/classroom?filter=location: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('name'); + res.body.result[0].should.have.property('total'); + done(); + }); + }); + + it('should list classrooms with valid dimensions', (done) => { + chai.request(server) + .get('/api/v1/classroom?dims=city') + .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('city_name'); + res.body.result[0].should.have.property('total'); + done(); + }); + }); + + it('should list classrooms with valid dimensions', (done) => { + chai.request(server) + .get('/api/v1/classroom?dims=state') + .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('state_name'); + res.body.result[0].should.have.property('total'); + done(); + }); + }); + + it('should list classrooms with valid dimensions', (done) => { + chai.request(server) + .get('/api/v1/classroom?dims=region') + .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('region_name'); + res.body.result[0].should.have.property('total'); + done(); + }); + }); + + it('should list classrooms with valid dimensions', (done) => { + chai.request(server) + .get('/api/v1/classroom?dims=adm_dependency') + .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('adm_dependency_name'); + res.body.result[0].should.have.property('total'); + done(); + }); + }); + + it('should list classrooms with valid dimensions', (done) => { + chai.request(server) + .get('/api/v1/classroom?dims=adm_dependency_detailed') + .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('adm_dependency_detailed_name'); + res.body.result[0].should.have.property('total'); + done(); + }); + }); + + it('should list classrooms with valid dimensions', (done) => { + chai.request(server) + .get('/api/v1/classroom?dims=location') + .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(); + }); + }); + + it('should list classrooms with invalid dimensions', (done) => { + chai.request(server) + .get('/api/v1/class?dims=foo,bar') + .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(); + }); + }); +});