resource-watch/adapter-arcgis

View on GitHub
app/src/routes/api/v1/arcgis.router.js

Summary

Maintainability
D
2 days
Test Coverage
D
61%
const Router = require('koa-router');
const logger = require('logger');
const { RWAPIMicroservice } = require('rw-api-microservice-node');
const ArcgisService = require('services/arcgis.service');
const QueryService = require('services/query.service');
const FieldSerializer = require('serializers/field.serializer');
const passThrough = require('stream').PassThrough;
const ErrorSerializer = require('serializers/error.serializer');
const ArcgisServerError = require('errors/arcgis-server.error');
const DatasetMiddleware = require('middleware/dataset.middleware');

const router = new Router({
    prefix: '/arcgis',
});

const serializeObjToQuery = (obj) => Object.keys(obj).reduce((a, k) => {
    a.push(`${k}=${encodeURIComponent(obj[k])}`);
    return a;
}, []).join('&');

class ArcgisRouter {

    static getCloneUrl(url, idDataset) {
        return {
            http_method: 'POST',
            url: `/dataset/${idDataset}/clone`,
            body: {
                dataset: {
                    datasetUrl: url.replace('/arcgis', ''),
                    application: ['your', 'apps']
                }
            }
        };
    }

    static async query(ctx) {
        ctx.set('Content-type', 'application/json');
        const cloneUrl = ArcgisRouter.getCloneUrl(ctx.request.url, ctx.params.dataset);
        try {
            ctx.body = passThrough();
            const format = ctx.query.format ? ctx.query.format : 'json';
            const queryService = await new QueryService(ctx.query.sql, ctx.state.jsonSql, ctx.request.body.dataset, ctx.body, cloneUrl, false, format);
            await queryService.execute();
            logger.debug('Finished query');
        } catch (err) {
            if (err instanceof ArcgisServerError) {
                ctx.body = ErrorSerializer.serializeArcgisServerErrors(err);
                ctx.status = 500;
            } else {
                ctx.body = ErrorSerializer.serializeError(err.statusCode || 500, err.error && err.error.error ? err.error.error[0] : err.message);
                ctx.status = 500;
            }

        }
    }

    static async download(ctx) {
        try {
            ctx.body = passThrough();
            const format = ctx.query.format ? ctx.query.format : 'csv';
            let mimetype;
            switch (format) {

                case 'csv':
                    mimetype = 'text/csv';
                    break;
                case 'json':
                default:
                    mimetype = 'application/json';
                    break;

            }

            const cloneUrl = ArcgisRouter.getCloneUrl(ctx.request.url, ctx.params.dataset);
            const queryService = await new QueryService(ctx.query.sql, ctx.state.jsonSql, ctx.request.body.dataset, ctx.body, cloneUrl, true, format);

            ctx.set('Content-disposition', `attachment; filename=${ctx.request.body.dataset.id}.${format}`);
            ctx.set('Content-type', mimetype);

            await queryService.execute();
            logger.debug('Finished query');
        } catch (err) {
            ctx.body = ErrorSerializer.serializeError(err.statusCode || 500, err.error && err.error.error ? err.error.error[0] : err.message);
            ctx.status = 500;
        }
    }

    static async fields(ctx) {
        logger.info('Obtaining fields');
        const fields = await ArcgisService.getFields(ctx.request.body.dataset.connectorUrl);
        ctx.body = FieldSerializer.serialize(fields, ctx.request.body.dataset.tableName);
    }

    static async deleteDataset(ctx) {
        logger.info('Delete featureservice dataset');
        ctx.body = {};
        ctx.status = 204;
    }

    static async registerDataset(ctx) {
        logger.info('Registering dataset with data', ctx.request.body);
        try {
            await ArcgisService.getFields(ctx.request.body.connector.connector_url);
            await RWAPIMicroservice.requestToMicroservice({
                method: 'PATCH',
                uri: `/v1/dataset/${ctx.request.body.connector.id}`,
                body: {
                    dataset: {
                        status: 1
                    }
                },
                json: true,
                headers: {
                    'x-api-key': ctx.request.headers['x-api-key'],
                }
            });
        } catch (e) {
            await RWAPIMicroservice.requestToMicroservice({
                method: 'PATCH',
                uri: `/v1/dataset/${ctx.request.body.connector.id}`,
                body: {
                    dataset: {
                        status: 2,
                        errorMessage: `${e.name} - ${e.message}`
                    }
                },
                json: true,
                headers: {
                    'x-api-key': ctx.request.headers['x-api-key'],
                }
            });
        }
        ctx.body = {};
    }

}

const queryMiddleware = async (ctx, next) => {
    const options = {
        method: 'GET',
        json: true,
        resolveWithFullResponse: true,
        simple: false,
        headers: {
            'x-api-key': ctx.request.headers['x-api-key'],
        }
    };
    if (!ctx.query.sql && !ctx.request.body.sql && !ctx.query.outFields && !ctx.query.outStatistics && !ctx.query.returnCountOnly) {
        ctx.throw(400, 'sql or fs required');
        return;
    }

    if (ctx.query.sql || ctx.request.body.sql) {
        logger.debug('Checking sql correct');
        const params = { ...ctx.query, ...ctx.request.body };
        options.uri = `/v1/convert/sql2FS?sql=${encodeURI(params.sql)}`;
        if (params.geostore) {
            options.uri += `&geostore=${params.geostore}`;
        }
        if (params.geojson) {
            options.method = 'POST';
            options.body = {
                geojson: ctx.request.body.geojson
            };
        }

        if (params.format !== 'geojson') {
            if (options.method === 'POST') {
                options.excludeGeometries = true;
            } else {
                options.uri += `&excludeGeometries=true`;
            }
        }

        try {
            const result = await RWAPIMicroservice.requestToMicroservice(options);

            if (result.statusCode === 204 || result.statusCode === 200) {
                const json2sql = result.body.data.attributes.jsonSql;
                // convert alias in groupby
                if (result.body.data.attributes.fs.groupByFieldsForStatistics) {
                    const groups = result.body.data.attributes.fs.groupByFieldsForStatistics.split(',');
                    for (let j = 0; j < groups.length; j += 1) {
                        for (let i = 0; i < json2sql.select.length; i += 1) {
                            if (json2sql.select[i].type === 'literal') {
                                if (groups[j] === json2sql.select[i].alias) {
                                    groups[j] = json2sql.select[i].value;
                                }
                            }
                        }
                    }
                    result.body.data.attributes.fs.groupByFieldsForStatistics = groups.join(',');
                }

                ctx.query.sql = `?${serializeObjToQuery(result.body.data.attributes.fs)}`;

                ctx.state.jsonSql = result.body.data.attributes.jsonSql;
            } else if (result.statusCode === 400) {
                ctx.status = result.statusCode;
                ctx.body = result.body;
            } else {
                ctx.throw(result.statusCode, result.body);
            }

        } catch (e) {
            if (e.errors && e.errors.length > 0 && e.errors[0].status >= 400 && e.errors[0].status < 500) {
                ctx.status = e.errors[0].status;
                ctx.body = e;
            } else {
                throw e;
            }
        }
    } else {
        // fs provided
        const params = { ...ctx.query, ...ctx.request.body };
        delete params.dataset;
        delete params.loggedUser;
        if (!params.where) {
            params.where = '1=1';
        }
        let esriJson = null;
        if (params.geostore) {
            const result = await RWAPIMicroservice.requestToMicroservice({
                uri: `/v1/geostore/${params.geostore}?format=esri`,
                method: 'GET',
                json: true,
                headers: {
                    'x-api-key': ctx.request.headers['x-api-key'],
                }
            });
            esriJson = result.data.attributes.esrijson;
        }
        if (params.geojson) {
            esriJson = params.geojson;
        }
        delete params.geojson;
        delete params.geostore;
        ctx.query.sql = `?${serializeObjToQuery(params)}`;
        if (esriJson) {
            ctx.query.sql += `&geometryType=esriGeometryPolygon&spatialRel=esriSpatialRelIntersects&inSR={"wkid":4326}&geometry=${JSON.stringify(esriJson)}`;
        }
    }
    await next();
};

router.get('/query/:dataset', DatasetMiddleware.getDatasetById, queryMiddleware, ArcgisRouter.query);
router.get('/download/:dataset', DatasetMiddleware.getDatasetById, queryMiddleware, ArcgisRouter.download);
router.post('/download/:dataset', DatasetMiddleware.getDatasetById, queryMiddleware, ArcgisRouter.download);
router.get('/fields/:dataset', DatasetMiddleware.getDatasetById, ArcgisRouter.fields);
router.post('/rest-datasets/featureservice', ArcgisRouter.registerDataset);
router.delete('/rest-datasets/featureservice/:dataset', ArcgisRouter.deleteDataset);

module.exports = router;