From 8063657daf781d32ef7e9117fba6b5d0c83127e1 Mon Sep 17 00:00:00 2001
From: Vytor Calixto <vytorcalixto@gmail.com>
Date: Tue, 10 Jan 2017 10:04:39 -0200
Subject: [PATCH] Added a "query builder" middleware for fields

It uses a object to set the url fields accepted and it's values, parse the
fields and build the query with the values automagically
---
 src/libs/middlewares/reqQueryFields.js | 167 +++++++++++++++++++++++++
 1 file changed, 167 insertions(+)
 create mode 100644 src/libs/middlewares/reqQueryFields.js

diff --git a/src/libs/middlewares/reqQueryFields.js b/src/libs/middlewares/reqQueryFields.js
new file mode 100644
index 00000000..10836590
--- /dev/null
+++ b/src/libs/middlewares/reqQueryFields.js
@@ -0,0 +1,167 @@
+const libs = `${process.cwd()}/libs`;
+
+const log = require(`${libs}/log`)(module);
+
+const parseParams = require(`${libs}/middlewares/parseParams`);
+
+const _ = require('lodash');
+
+class ReqQueryFields {
+    constructor(fields = {}, fieldValues = {}) {
+        // Parâmetros no campo query da requisição.
+        // Exemplo de field:
+        // {
+        //    name: 'dims',
+        //    field: true,
+        //    where: false
+        // }
+        this.fields = fields;
+        this.fieldValues = fieldValues;
+    }
+
+    addField(field) {
+        // Parâmetro no campo query da requisição.
+        // Exemplo de field:
+        // {
+        //    name: 'dims',
+        //    field: true,
+        //    where: false,
+        //    fieldValues: {}
+        // }
+        if(typeof this.fields[field.name] === 'undefined') {
+            this.fields[field.name] = field;
+        }
+        return this;
+    }
+
+    addValue(fieldValue) {
+        // Array de valores aceitos pelo campo
+        // Exemplo de valor:
+        // {
+        //     name: 'location',
+        //     table: 'localizacao',
+        //     tableField: 'descricao'
+        //     resultField: 'location_name',
+        //     where: {
+        //         relation: '=',
+        //         type: 'integer',
+        //         field: 'id_localizacao',
+        //         table: 'turma'
+        //     },
+        //     join: {
+        //         primary: 'pk_localizacao_id',
+        //         foreign: 'id_localizacao',
+        //         foreignTable: 'turma'
+        //     }
+        // }
+
+        if(typeof this.fieldValues[fieldValue.name] === 'undefined') {
+            this.fieldValues[fieldValue.name] = fieldValue;
+        }
+        return this;
+    }
+
+    parse() {
+        return (req, res, next) => {
+            Object.keys(this.fields).map((key, index) => {
+                let params = [];
+                let f = this.fields[key];
+                log.debug('f');
+                log.debug(f);
+                Object.keys(this.fieldValues).map((k, i) => {
+                    let value = this.fieldValues[k];
+                    log.debug('value');
+                    log.debug(value);
+                    params.push(value.name);
+                });
+                let queryField = f.name;
+                let arrayOfParams = params;
+                req[queryField] = {};
+                if (req.query[queryField]) {
+                    const params = req.query[queryField].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.
+                        // kv is then an array [key, value] or [key] if there is no value
+                        const kv = param.split(':');
+                        // Check if there is a value. If there isn't, assign true
+                        obj[kv[0]] = (typeof kv[1] === 'undefined') ? true : kv[1];
+                        // obj is now an object {kv[0]: kv[1]} ou {kv[0]: true}
+                    }
+
+                    // If the array exists and is not empty we intersect
+                    if (typeof arrayOfParams !== 'undefined' && arrayOfParams.length > 0) {
+                        // Intersect the keys of the obj with the array arrayOfParams
+                        // The intersection array is assigned with the keys
+                        const intersection = _.intersection(arrayOfParams, 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[queryField]["state"] = 41
+                        for (let i = 0; i < intersection.length; ++i) {
+                            req[queryField][intersection[i]] = obj[intersection[i]];
+                        }
+                        req[queryField].size = intersection.length;
+                    } else {
+                        req[queryField] = obj;
+                        req[queryField].size = Object.keys(obj).length;
+                    }
+                }
+            });
+            next();
+        };
+    }
+
+    build() {
+        return (req, res, next) => {
+            Object.keys(this.fields).map((key, index) => {
+                let field = this.fields[key];
+                log.debug(field);
+                let param = req[field.name];
+                log.debug(param);
+                Object.keys(param).map((k, i) => {
+                    let values = this.fieldValues;
+                    log.debug(k);
+                    if(typeof values[k] !== 'undefined') {
+                        // Clonamos para não alterar
+                        let value = _.clone(values[k]);
+                        log.debug(value);
+                        // Checa se não fizemos o join para este valor e se é necessário fazer
+                        if(!value.hasJoined && typeof value.join !== 'undefined') {
+                            let foreignTable = '';
+                            if(value.join.foreignTable) foreignTable = value.join.foreignTable+'.';
+                            // Fazemos o join
+                            req.sql.join(value.table, null, foreignTable+value.join.foreign+'='+value.table+'.'+value.join.primary);
+                            // Marcamos o join como feito para não ter problemas
+                            value.hasJoined = true;
+                        }
+                        // Se o valor é um campo a ser incluído no SELECT
+                        if(typeof field.field !== 'undefined' && field.field) {
+                            log.debug('SELECT');
+                            req.sql.field(value.table+'.'+value.tableField, value.resultField || value.tableField)
+                                .group(value.table+'.'+value.tableField)
+                                .order(value.table+'.'+value.tableField);
+                        }
+                        // Se o valor é um campo para ser usado no WHERE
+                        if(typeof field.where !== 'undefined' && field.where) {
+                            log.debug('WHERE');
+                            // Valor do where
+                            let whereValue = param[k];
+                            // Valor sempre vem como string, necessário fazer parse para o banco
+                            if(value.where.type === 'integer') whereValue = parseInt(whereValue, 10);
+                            if(value.where.type === 'double') whereValue = parseFloat(whereValue);
+                            let tbl = value.where.table || value.table;
+                            req.sql.where(tbl+'.'+value.where.field+' '+value.where.relation+' ?', whereValue);
+                        }
+                    }
+                });
+            });
+            next();
+        };
+    }
+}
+
+module.exports = ReqQueryFields;
-- 
GitLab