resource-watch/rw_metadata

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

Summary

Maintainability
C
1 day
Test Coverage
C
79%

const Router = require('koa-router');
const logger = require('logger');
const MetadataService = require('services/metadata.service');
const MetadataSerializer = require('serializers/metadata.serializer');
const MetadataValidator = require('validators/metadata.validator');
const MetadataNotFound = require('errors/metadataNotFound.error');
const MetadataDuplicated = require('errors/metadataDuplicated.error');
const MetadataNotValid = require('errors/metadataNotValid.error');
const InvalidSortParameter = require('errors/invalidSortParameter.error');
const CloneNotValid = require('errors/cloneNotValid.error');
const { USER_ROLES } = require('app.constants');

const router = new Router();

class MetadataRouter {

    static getResource(params) {
        let resource = { id: params.dataset, type: 'dataset' };
        if (params.layer) {
            resource = { id: params.layer, type: 'layer' };
        } else if (params.widget) {
            resource = { id: params.widget, type: 'widget' };
        } else {
            // do nothing
        }
        return resource;
    }

    static getResourceTypeByPath(path) {
        let type = 'dataset';
        if (path.indexOf('layer') > -1) {
            type = 'layer';
        } else if (path.indexOf('widget') > -1) {
            type = 'widget';
        } else {
            // do nothing
        }
        return type;
    }

    static async get(ctx) {
        const resource = MetadataRouter.getResource(ctx.params);
        logger.info(`Getting metadata of ${resource.type}: ${resource.id}`);
        const filter = {};
        if (ctx.query.application) { filter.application = ctx.query.application; }
        if (ctx.query.language) { filter.language = ctx.query.language; }
        if (ctx.query.limit) { filter.limit = ctx.query.limit; }
        const result = await MetadataService.get(ctx.params.dataset, resource, filter);
        ctx.set('cache', `${resource.id}-metadata-all`);
        ctx.body = MetadataSerializer.serialize(result);
    }

    static async create(ctx) {
        const resource = MetadataRouter.getResource(ctx.params);
        logger.info(`Creating metadata of ${resource.type}: ${resource.id}`);
        try {
            const user = ctx.request.body.loggedUser;
            const result = await MetadataService.create(user, ctx.params.dataset, resource, ctx.request.body);
            ctx.set('uncache', `metadata ${resource.id}-metadata ${resource.id}-metadata-all`);
            ctx.body = MetadataSerializer.serialize(result);
        } catch (err) {
            if (err instanceof MetadataDuplicated) {
                ctx.throw(400, err.message);
                return;
            }
            throw err;
        }
    }

    static async update(ctx) {
        const resource = MetadataRouter.getResource(ctx.params);
        logger.info(`Updating metadata of ${resource.type}: ${resource.id}`);
        try {
            const result = await MetadataService.update(ctx.params.dataset, resource, ctx.request.body);
            ctx.set('uncache', `metadata ${resource.id}-metadata ${resource.id}-metadata-all ${result.id}`);
            ctx.body = MetadataSerializer.serialize(result);
        } catch (err) {
            if (err instanceof MetadataNotFound) {
                ctx.throw(404, err.message);
                return;
            }
            throw err;
        }
    }

    static async delete(ctx) {
        const resource = MetadataRouter.getResource(ctx.params);
        logger.info(`Deleting metadata of ${resource.type}: ${resource.id}`);
        const filter = {};
        if (ctx.query.application) { filter.application = ctx.query.application; }
        if (ctx.query.language) { filter.language = ctx.query.language; }
        try {
            const result = await MetadataService.delete(ctx.params.dataset, resource, filter);
            ctx.set('uncache', `metadata ${resource.id}-metadata ${resource.id}-metadata-all ${result.id}`);
            ctx.body = MetadataSerializer.serialize(result);
        } catch (err) {
            if (err instanceof MetadataNotFound) {
                ctx.throw(404, err.message);
                return;
            }
            throw err;
        }
    }

    static async getAll(ctx) {
        logger.info('Getting all metadata');
        const filter = {};
        const extendedFilter = {};
        filter.search = ctx.query.search ? ctx.query.search.split(' ').map((elem) => elem.trim()) : [];
        if (ctx.query.application) { filter.application = ctx.query.application; }
        if (ctx.query.language) { filter.language = ctx.query.language; }
        if (ctx.query.limit) { filter.limit = ctx.query.limit; }
        if (ctx.query.type) { extendedFilter.type = ctx.query.type; }
        if (ctx.query.sort) { filter.sort = ctx.query.sort; }
        try {
            const result = await MetadataService.getAll(filter, extendedFilter);
            ctx.set('cache', `metadata`);
            ctx.body = MetadataSerializer.serialize(result);
        } catch (err) {
            if (err instanceof InvalidSortParameter) {
                ctx.throw(400, err.message);
                return;
            }
            throw err;
        }
    }

    static async findByIds(ctx) {
        if (!ctx.request.body.ids) {
            ctx.throw(400, 'Bad request - Missing \'ids\' from request body');
            return;
        }
        logger.info(`Getting metadata by ids: ${ctx.request.body.ids}`);
        const resource = {
            ids: ctx.request.body.ids
        };
        if (typeof resource.ids === 'string') {
            resource.ids = resource.ids.split(',').map((elem) => elem.trim());
        }
        resource.type = MetadataRouter.getResourceTypeByPath(ctx.path);
        const filter = {};
        if (ctx.query.application) { filter.application = ctx.query.application; }
        if (ctx.query.language) { filter.language = ctx.query.language; }
        const result = await MetadataService.getByIds(resource, filter);
        ctx.body = MetadataSerializer.serialize(result);
    }

    static async clone(ctx) {
        const resource = MetadataRouter.getResource(ctx.params);
        const { newDataset } = ctx.request.body;
        logger.info(`Cloning metadata of ${resource.type}: ${resource.id} in ${newDataset}`);
        try {
            const user = ctx.request.body.loggedUser;
            const result = await MetadataService.clone(user, ctx.params.dataset, resource, ctx.request.body);
            ctx.set('uncache', `metadata`);
            ctx.body = MetadataSerializer.serialize(result);
        } catch (err) {
            if (err instanceof MetadataDuplicated) {
                ctx.throw(400, err.message);
                return;
            }
            throw err;
        }
    }

}

// Negative checking
const authorizationMiddleware = async (ctx, next) => {
    // Get user from query (delete) or body (post-patch)
    const user = { ...(ctx.request.query.loggedUser ? JSON.parse(ctx.request.query.loggedUser) : {}), ...ctx.request.body.loggedUser };
    if (user.id === 'microservice') {
        await next();
        return;
    }

    // Check delete
    if (ctx.request.method === 'DELETE' && (!ctx.request.query.language || !ctx.request.query.application)) {
        ctx.throw(400, 'Bad request');
        return;
    }

    if (!user || USER_ROLES.indexOf(user.role) === -1) {
        ctx.throw(401, 'Unauthorized'); // if not logged or invalid ROLE-> out
        return;
    }
    if (user.role === 'USER') {
        ctx.throw(403, 'Forbidden'); // if USER -> out
        return;
    }
    // Get application from query (delete) or body (post-patch)
    const application = ctx.request.query.application ? ctx.request.query.application : ctx.request.body.application;
    if (user.role === 'MANAGER' || user.role === 'ADMIN') {
        if (user.extraUserData.apps.indexOf(application) === -1) {
            ctx.throw(403, 'Forbidden'); // if manager or admin but no application -> out
            return;
        }
        if (user.role === 'MANAGER' && ctx.request.method !== 'POST') { // extra check if a MANAGER wants to update or delete
            const resource = MetadataRouter.getResource(ctx.params);
            const permission = await MetadataService.hasPermission(user, ctx.params.dataset, resource, ctx.request.body);
            if (!permission) {
                ctx.throw(403, 'Forbidden');
                return;
            }
        }
    }
    await next(); // SUPERADMIN is included here
};

// Validator Wrapper
const validationMiddleware = async (ctx, next) => {
    try {
        MetadataValidator.validate(ctx);
    } catch (err) {
        if (err instanceof MetadataNotValid) {
            ctx.throw(400, err.getMessages());
            return;
        }
        throw err;
    }
    await next();
};

// Validator Wrapper
const cloneValidationMiddleware = async (ctx, next) => {
    try {
        MetadataValidator.validateClone(ctx);
    } catch (err) {
        if (err instanceof CloneNotValid) {
            ctx.throw(400, err.getMessages());
            return;
        }
        throw err;
    }
    await next();
};

const isAuthenticatedMiddleware = async (ctx, next) => {
    logger.info(`Verifying if user is authenticated`);
    const { query, body } = ctx.request;

    const user = { ...(query.loggedUser ? JSON.parse(query.loggedUser) : {}), ...body.loggedUser };

    if (!user || !user.id) {
        ctx.throw(401, 'Unauthorized');
        return;
    }
    await next();
};

// dataset
router.get('/dataset/:dataset/metadata', MetadataRouter.get);
router.post('/dataset/:dataset/metadata', isAuthenticatedMiddleware, validationMiddleware, authorizationMiddleware, MetadataRouter.create);
router.post('/dataset/:dataset/metadata/clone', isAuthenticatedMiddleware, cloneValidationMiddleware, authorizationMiddleware, MetadataRouter.clone);
router.patch('/dataset/:dataset/metadata', isAuthenticatedMiddleware, validationMiddleware, authorizationMiddleware, MetadataRouter.update);
router.delete('/dataset/:dataset/metadata', isAuthenticatedMiddleware, authorizationMiddleware, MetadataRouter.delete);
// widget
router.get('/dataset/:dataset/widget/:widget/metadata', MetadataRouter.get);
router.post('/dataset/:dataset/widget/:widget/metadata', isAuthenticatedMiddleware, validationMiddleware, authorizationMiddleware, MetadataRouter.create);
router.patch('/dataset/:dataset/widget/:widget/metadata', isAuthenticatedMiddleware, validationMiddleware, authorizationMiddleware, MetadataRouter.update);
router.delete('/dataset/:dataset/widget/:widget/metadata', isAuthenticatedMiddleware, authorizationMiddleware, MetadataRouter.delete);
// layer
router.get('/dataset/:dataset/layer/:layer/metadata', MetadataRouter.get);
router.post('/dataset/:dataset/layer/:layer/metadata', isAuthenticatedMiddleware, validationMiddleware, authorizationMiddleware, MetadataRouter.create);
router.patch('/dataset/:dataset/layer/:layer/metadata', isAuthenticatedMiddleware, validationMiddleware, authorizationMiddleware, MetadataRouter.update);
router.delete('/dataset/:dataset/layer/:layer/metadata', isAuthenticatedMiddleware, authorizationMiddleware, MetadataRouter.delete);
// generic
router.get('/metadata', MetadataRouter.getAll);
// find by id
router.post('/dataset/metadata/find-by-ids', MetadataRouter.findByIds);
router.post('/dataset/:dataset/widget/metadata/find-by-ids', MetadataRouter.findByIds);
router.post('/dataset/:dataset/layer/metadata/find-by-ids', MetadataRouter.findByIds);

module.exports = router;