resource-watch/vocabulary-tag

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

Summary

Maintainability
F
1 wk
Test Coverage
D
61%
const Router = require('koa-router');
const logger = require('logger');
const VocabularyService = require('services/vocabulary.service');
const ResourceService = require('services/resource.service');
const RelationshipService = require('services/relationship.service');
const VocabularySerializer = require('serializers/vocabulary.serializer');
const ResourceSerializer = require('serializers/resource.serializer');
const VocabularyValidator = require('validators/vocabulary.validator');
const RelationshipValidator = require('validators/relationship.validator');
const CloneValidator = require('validators/clone.validator');
const RelationshipsValidator = require('validators/relationships.validator');
const VocabularyNotFound = require('errors/vocabulary-not-found.error');
const VocabularyDuplicated = require('errors/vocabulary-duplicated.error');
const VocabularyNotValid = require('errors/vocabulary-not-valid.error');
const RelationshipDuplicated = require('errors/relationship-duplicated.error');
const RelationshipNotValid = require('errors/relationship-not-valid.error');
const CloneNotValid = require('errors/clone-not-valid.error');
const RelationshipsNotValid = require('errors/relationships-not-valid.error');
const RelationshipNotFound = require('errors/relationship-not-found.error');
const ResourceNotFound = require('errors/resource-not-found.error');
const { USER_ROLES } = require('app.constants');
const pick = require('lodash/pick');

const router = new Router();

const getUser = (ctx) => {
    const { query, body } = ctx.request;

    let user = { ...(query.loggedUser ? JSON.parse(query.loggedUser) : {}), ...ctx.request.body.loggedUser };
    if (body.fields && body.fields.loggedUser) {
        user = Object.assign(user, JSON.parse(body.fields.loggedUser));
    }
    return user;
};

class VocabularyRouter {

    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' };
        }
        return resource;
    }

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

    static async get(ctx) {
        const { query } = ctx.request;
        const queryKeys = Object.keys(query).filter((e) => e !== 'loggedUser');
        if (queryKeys.length === 0) {
            ctx.throw(400, 'Vocabulary and Tags are required in the queryParams');
            return;
        }
        logger.info(`Getting resources by vocabulary-tag`);
        const resource = {};
        resource.type = VocabularyRouter.getResourceTypeByPath(ctx.path);
        const result = await VocabularyService.get(resource, query);
        // Default cache
        ctx.body = VocabularySerializer.serialize(result);
    }

    static async create(ctx) {
        logger.info(`Creating vocabulary with name: ${ctx.request.body.name}`);
        try {
            const result = await VocabularyService.create(ctx.request.body);
            ctx.set('uncache', 'vocabulary');
            ctx.body = VocabularySerializer.serialize(result);
        } catch (err) {
            if (err instanceof VocabularyDuplicated) {
                ctx.throw(400, err.message);
                return;
            }
            throw err;
        }
    }

    static async getAll(ctx) {
        logger.info('Getting all vocabularies');
        const filter = pick(ctx.query, ['limit']);

        let vocabularies = await VocabularyService.getAll(filter);

        const relationshipQuery = {};
        if (ctx.query.env) {
            relationshipQuery.env = ctx.query.env;
        }

        vocabularies = await RelationshipService.getRelationships(vocabularies, ctx.request.headers['x-api-key'], relationshipQuery);

        ctx.set('cache', 'vocabulary');
        ctx.body = VocabularySerializer.serialize(vocabularies);
    }

    static async getById(ctx) {
        logger.info(`Getting vocabulary by name: ${ctx.params.vocabulary}`);
        const application = ctx.query.application || ctx.query.app || 'rw';

        const vocabularyDefinition = { name: ctx.params.vocabulary, application };
        let vocabulary = await VocabularyService.getById(vocabularyDefinition);

        const relationshipQuery = {};
        if (ctx.query.env) {
            relationshipQuery.env = ctx.query.env;
        }

        vocabulary = await RelationshipService.getRelationships([vocabulary], ctx.request.headers['x-api-key'], relationshipQuery);

        ctx.set('cache', `${vocabularyDefinition.name} ${vocabulary.id}`);
        ctx.body = VocabularySerializer.serialize(vocabulary);
    }

    static async getTagsById(ctx) {
        logger.info(`Getting vocabulary tags by name: ${ctx.params.vocabulary}`);
        const application = ctx.query.application || ctx.query.app || 'rw';

        const vocabularyDefinition = { name: ctx.params.vocabulary, application };
        const vocabulary = await VocabularyService.getById(vocabularyDefinition);

        const relationshipQuery = {
            env: ctx.query.env ? ctx.query.env : 'production'
        };

        const result = await RelationshipService.getRelationships([vocabulary], ctx.request.headers['x-api-key'], relationshipQuery);
        ctx.body = VocabularySerializer.serializeTags(result);
    }

    /* Using the Resource Service */
    static async getByResource(ctx) {
        const resource = VocabularyRouter.getResource(ctx.params);
        logger.info(`Getting vocabularies of ${resource.type}: ${resource.id}`);
        const application = ctx.query.application || ctx.query.app || 'rw';
        const vocabulary = { name: ctx.params.vocabulary, application };
        const result = await ResourceService.get(ctx.params.dataset, resource, vocabulary);
        ctx.set('cache', `${resource.id}-vocabulary-all`);
        ctx.body = ResourceSerializer.serialize(result);
    }

    static async findByIds(ctx) {
        if (!ctx.request.body.ids) {
            ctx.throw(400, 'Bad request - Missing \'ids\' from request body');
            return;
        }
        logger.info(`Getting vocabularies by ids: ${ctx.request.body.ids}`);
        const resource = {
            ids: ctx.request.body.ids
        };
        if (ctx.query.application) {
            resource.application = ctx.query.application;
        }
        if (typeof resource.ids === 'string') {
            resource.ids = resource.ids.split(',').map((elem) => elem.trim());
        }
        resource.type = VocabularyRouter.getResourceTypeByPath(ctx.path);
        const result = await ResourceService.getByIds(resource);
        // no cache
        ctx.body = ResourceSerializer.serializeByIds(result); //
    }

    static async createRelationship(ctx) {
        const { dataset } = ctx.params;
        const application = ctx.request.body.application || 'rw';
        const vocabulary = { name: ctx.params.vocabulary, application };
        const resource = VocabularyRouter.getResource(ctx.params);
        const { body } = ctx.request;
        logger.info(`Creating relationship between vocabulary: ${vocabulary.name} and resource: ${resource.type} - ${resource.id}`);
        try {
            const result = await RelationshipService.create(vocabulary, dataset, resource, body, ctx.request.headers['x-api-key']);
            ctx.set('uncache', `vocabulary ${resource.id}-vocabulary ${resource.id}-vocabulary-all ${vocabulary.name}`);
            ctx.body = ResourceSerializer.serialize(result);
        } catch (err) {
            if (err instanceof RelationshipDuplicated) {
                ctx.throw(400, err.message);
                return;
            }
            throw err;
        }
    }

    static async createRelationships(ctx) {
        const { dataset } = ctx.params;
        const resource = VocabularyRouter.getResource(ctx.params);
        const { body } = ctx.request;
        const vocabularies = [];
        Object.keys(body).forEach((key) => {
            if (key !== 'loggedUser') {
                vocabularies.push({
                    name: key,
                    application: body[key].application,
                    tags: body[key].tags
                });
            }
        });
        vocabularies.forEach((vocabulary) => {
            logger.info(`Creating relationships between vocabulary: ${vocabulary.name} and resource: ${resource.type} - ${resource.id}`);
        });
        try {
            const result = await RelationshipService.createSome(vocabularies, dataset, resource, ctx.request.headers['x-api-key']);
            ctx.set('uncache', `vocabulary ${resource.id}-vocabulary ${resource.id}-vocabulary-all ${vocabularies.map((el) => `${el.name}`).join(' ')}`);
            ctx.body = ResourceSerializer.serialize(result);
        } catch (err) {
            if (err instanceof RelationshipDuplicated) {
                ctx.throw(400, err.message);
                return;
            }
            throw err;
        }
    }

    static async updateRelationships(ctx) {
        const { dataset } = ctx.params;
        const resource = VocabularyRouter.getResource(ctx.params);
        const { body } = ctx.request;
        logger.info(`Deleting All Vocabularies of resource: ${resource.type} - ${resource.id}`);
        try {
            const result = await RelationshipService.deleteAll(dataset, resource, ctx.request.headers['x-api-key']);
            ctx.body = ResourceSerializer.serialize(result);
        } catch (err) {
            if (err instanceof VocabularyNotFound || err instanceof ResourceNotFound) {
                ctx.throw(404, err.message);
                return;
            }
            if (err instanceof RelationshipNotFound) {
                // do nothing
            } else {
                throw err;
            }
        }
        const vocabularies = [];
        Object.keys(body).forEach((key) => {
            if (key !== 'loggedUser') {
                vocabularies.push({
                    name: key,
                    application: body[key].application,
                    tags: body[key].tags
                });
            }
        });
        vocabularies.forEach((vocabulary) => {
            logger.info(`Creating relationships between vocabulary: ${vocabulary.name} and resource: ${resource.type} - ${resource.id}`);
        });
        try {
            const result = await RelationshipService.createSome(vocabularies, dataset, resource, ctx.request.headers['x-api-key']);
            ctx.set('uncache', `vocabulary ${resource.id}-vocabulary ${resource.id}-vocabulary-all ${vocabularies.map((el) => `${el.name}`).join(' ')}`);
            ctx.body = ResourceSerializer.serialize(result);
        } catch (err) {
            if (err instanceof RelationshipDuplicated) {
                ctx.throw(400, err.message);
                return;
            }
            throw err;
        }
    }

    static async deleteRelationship(ctx) {
        const { dataset } = ctx.params;
        const application = ctx.query.application || ctx.query.app || 'rw';
        const vocabulary = { name: ctx.params.vocabulary, application };
        const resource = VocabularyRouter.getResource(ctx.params);
        logger.info(`Deleting Relationship between: ${vocabulary.name} and resource: ${resource.type} - ${resource.id}`);
        try {
            const result = await RelationshipService.delete(vocabulary, dataset, resource, ctx.request.headers['x-api-key']);
            ctx.set('uncache', `vocabulary ${resource.id}-vocabulary ${resource.id}-vocabulary-all ${vocabulary.name}`);
            ctx.body = ResourceSerializer.serialize(result);
        } catch (err) {
            if (err instanceof VocabularyNotFound || err instanceof ResourceNotFound || err instanceof RelationshipNotFound) {
                ctx.throw(404, err.message);
                return;
            }
            throw err;
        }
    }

    static async deleteRelationships(ctx) {
        const { dataset } = ctx.params;
        const resource = VocabularyRouter.getResource(ctx.params);
        logger.info(`Deleting All Vocabularies of resource: ${resource.type} - ${resource.id}`);
        try {
            const result = await RelationshipService.deleteAll(dataset, resource, ctx.request.headers['x-api-key']);
            logger.debug(result);
            ctx.set('uncache', `vocabulary ${resource.id}-vocabulary ${resource.id}-vocabulary-all ${result.vocabularies.map((el) => `${el.id}`).join(' ')}`);
            ctx.body = ResourceSerializer.serialize(result);
        } catch (err) {
            if (err instanceof VocabularyNotFound || err instanceof ResourceNotFound || err instanceof RelationshipNotFound) {
                ctx.throw(404, err.message);
                return;
            }
            throw err;
        }
    }

    static async updateRelationshipTags(ctx) {
        const { dataset } = ctx.params;
        const application = ctx.request.body.application || 'rw';
        const vocabulary = { name: ctx.params.vocabulary, application };
        const resource = VocabularyRouter.getResource(ctx.params);
        const { body } = ctx.request;
        logger.info(`Updating tags of relationship: ${vocabulary.name} and resource: ${resource.type} - ${resource.id}`);
        try {
            const result = await RelationshipService.updateTagsFromRelationship(vocabulary, dataset, resource, body, ctx.request.headers['x-api-key']);
            ctx.set('uncache', `vocabulary ${resource.id}-vocabulary ${resource.id}-vocabulary-all ${vocabulary.name}`);
            ctx.body = ResourceSerializer.serialize(result);
        } catch (err) {
            if (err instanceof VocabularyNotFound || err instanceof ResourceNotFound || err instanceof RelationshipNotFound) {
                ctx.throw(404, err.message);
                return;
            }
            throw err;
        }
    }

    static async concatTags(ctx) {
        const { dataset } = ctx.params;
        const application = ctx.request.body.application || 'rw';
        const vocabulary = { name: ctx.params.vocabulary, application };
        const resource = VocabularyRouter.getResource(ctx.params);
        const { body } = ctx.request;
        logger.info(`Conacatenating more tags in relationship: ${vocabulary.name} and resource: ${resource.type} - ${resource.id}`);
        try {
            const result = await RelationshipService.concatTags(vocabulary, dataset, resource, body, ctx.request.headers['x-api-key']);
            ctx.set('uncache', `vocabulary ${resource.id}-vocabulary ${resource.id}-vocabulary-all ${vocabulary.name}`);
            ctx.body = ResourceSerializer.serialize(result);
        } catch (err) {
            if (err instanceof VocabularyNotFound || err instanceof ResourceNotFound || err instanceof RelationshipNotFound) {
                ctx.throw(404, err.message);
                return;
            }
            throw err;
        }
    }

    static async cloneVocabularyTags(ctx) {
        const { dataset } = ctx.params;
        const resource = VocabularyRouter.getResource(ctx.params);
        const { body } = ctx.request;
        const { newDataset } = body;
        logger.info(`Cloning relationships: of resource ${resource.type} - ${resource.id} in ${newDataset}`);
        try {
            const result = await RelationshipService.cloneVocabularyTags(dataset, resource, body, ctx.request.headers['x-api-key']);
            ctx.set('uncache', `vocabulary ${resource.id}-vocabulary ${resource.id}-vocabulary-all`);
            ctx.body = ResourceSerializer.serialize(result);
        } catch (err) {
            if (err instanceof VocabularyNotFound || err instanceof ResourceNotFound || err instanceof RelationshipNotFound) {
                ctx.throw(404, err.message);
                return;
            }
            throw err;
        }
    }

}

// Negative checking
const relationshipAuthorizationMiddleware = async (ctx, next) => {
    // Get user from query (delete) or body (post-patch)
    const user = getUser(ctx);
    if (user.id === 'microservice') {
        await next();
        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;
    }
    if (user.role === 'MANAGER' || user.role === 'ADMIN') {
        const resource = VocabularyRouter.getResource(ctx.params);
        const permission = await ResourceService.hasPermission(user, ctx.params.dataset, resource, ctx.request.headers['x-api-key']);
        if (!permission) {
            ctx.throw(403, 'Forbidden');
            return;
        }
    }
    await next(); // SUPERADMIN are included here
};

// Negative checking
const vocabularyAuthorizationMiddleware = async (ctx, next) => {
    // Get user from query (delete) or body (post-patch)
    const user = getUser(ctx);
    if (user.id === 'microservice') {
        await next();
        return;
    }
    if (!user || USER_ROLES.indexOf(user.role) === -1) {
        ctx.throw(401, 'Unauthorized'); // if not logged or invalid ROLE -> out
        return;
    }
    if (ctx.request.method === 'POST' && user.role === 'ADMIN') {
        await next();
        return;
    }
    if (user.role !== 'SUPERADMIN') {
        ctx.throw(403, 'Forbidden'); // Only SUPERADMIN
        return;
    }
    await next(); // SUPERADMIN is included here
};

// Resource Validator Wrapper
const relationshipValidationMiddleware = async (ctx, next) => {
    try {
        await RelationshipValidator.validate(ctx);
    } catch (err) {
        if (err instanceof RelationshipNotValid) {
            ctx.throw(400, err.getMessages());
            return;
        }
        throw err;
    }
    await next();
};

// RelationshipsValidator Wrapper
const relationshipsValidationMiddleware = async (ctx, next) => {
    try {
        await RelationshipsValidator.validate(ctx);
    } catch (err) {
        if (err instanceof RelationshipsNotValid) {
            ctx.throw(400, err.getMessages());
            return;
        }
        throw err;
    }
    await next();
};

// Vocabulary Validator Wrapper
const vocabularyValidationMiddleware = async (ctx, next) => {
    try {
        await VocabularyValidator.validate(ctx);
    } catch (err) {
        if (err instanceof VocabularyNotValid) {
            ctx.throw(400, err.getMessages());
            return;
        }
        throw err;
    }
    await next();
};

// Clone Validator Wrapper
const cloneValidationMiddleware = async (ctx, next) => {
    try {
        await CloneValidator.validate(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 user = getUser(ctx);

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

// dataset
router.get('/dataset/:dataset/vocabulary', VocabularyRouter.getByResource);
router.get('/dataset/:dataset/vocabulary/:vocabulary', VocabularyRouter.getByResource);
router.get('/dataset/vocabulary/find', VocabularyRouter.get);
router.post('/dataset/:dataset/vocabulary', isAuthenticatedMiddleware, relationshipsValidationMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.createRelationships);
router.put('/dataset/:dataset/vocabulary', isAuthenticatedMiddleware, relationshipsValidationMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.updateRelationships);
router.post('/dataset/:dataset/vocabulary/:vocabulary', isAuthenticatedMiddleware, relationshipValidationMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.createRelationship);
router.patch('/dataset/:dataset/vocabulary/:vocabulary', isAuthenticatedMiddleware, relationshipValidationMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.updateRelationshipTags);
router.post('/dataset/:dataset/vocabulary/:vocabulary/concat', isAuthenticatedMiddleware, relationshipValidationMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.concatTags);
router.post('/dataset/:dataset/vocabulary/clone/dataset', isAuthenticatedMiddleware, cloneValidationMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.cloneVocabularyTags);
router.delete('/dataset/:dataset/vocabulary/:vocabulary', isAuthenticatedMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.deleteRelationship);
router.delete('/dataset/:dataset/vocabulary', isAuthenticatedMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.deleteRelationships);

// widget
router.get('/dataset/:dataset/widget/:widget/vocabulary', VocabularyRouter.getByResource);
router.get('/dataset/:dataset/widget/:widget/vocabulary/:vocabulary', VocabularyRouter.getByResource);
router.get('/dataset/:dataset/widget/vocabulary/find', VocabularyRouter.get);
router.post('/dataset/:dataset/widget/:widget/vocabulary/', isAuthenticatedMiddleware, relationshipsValidationMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.createRelationships);
router.post('/dataset/:dataset/widget/:widget/vocabulary/:vocabulary', isAuthenticatedMiddleware, relationshipValidationMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.createRelationship);
router.patch('/dataset/:dataset/widget/:widget/vocabulary/:vocabulary', isAuthenticatedMiddleware, relationshipValidationMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.updateRelationshipTags);
router.delete('/dataset/:dataset/widget/:widget/vocabulary/:vocabulary', isAuthenticatedMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.deleteRelationship);
router.delete('/dataset/:dataset/widget/:widget/vocabulary', isAuthenticatedMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.deleteRelationships);

// layer
router.get('/dataset/:dataset/layer/:layer/vocabulary', VocabularyRouter.getByResource);
router.get('/dataset/:dataset/layer/:layer/vocabulary/:vocabulary', VocabularyRouter.getByResource);
router.get('/dataset/:dataset/layer/vocabulary/find', VocabularyRouter.get);
router.post('/dataset/:dataset/layer/:layer/vocabulary', isAuthenticatedMiddleware, relationshipsValidationMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.createRelationships);
router.post('/dataset/:dataset/layer/:layer/vocabulary/:vocabulary', isAuthenticatedMiddleware, relationshipValidationMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.createRelationship);
router.patch('/dataset/:dataset/layer/:layer/vocabulary/:vocabulary', isAuthenticatedMiddleware, relationshipValidationMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.updateRelationshipTags);
router.delete('/dataset/:dataset/layer/:layer/vocabulary/:vocabulary', isAuthenticatedMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.deleteRelationship);
router.delete('/dataset/:dataset/layer/:layer/vocabulary', isAuthenticatedMiddleware, relationshipAuthorizationMiddleware, VocabularyRouter.deleteRelationships);

// vocabulary (not the common use case)
router.get('/vocabulary', VocabularyRouter.getAll);
router.get('/vocabulary/:vocabulary', VocabularyRouter.getById);
router.get('/vocabulary/:vocabulary/tags', VocabularyRouter.getTagsById);
router.post('/vocabulary', isAuthenticatedMiddleware, vocabularyValidationMiddleware, vocabularyAuthorizationMiddleware, VocabularyRouter.create);
// router.patch('/vocabulary/:vocabulary', isAuthenticatedMiddleware, vocabularyValidationMiddleware, vocabularyAuthorizationMiddleware, VocabularyRouter.update);
// router.delete('/vocabulary/:vocabulary', isAuthenticatedMiddleware, vocabularyAuthorizationMiddleware, VocabularyRouter.delete);

// find by ids (to include queries)
router.post('/dataset/vocabulary/find-by-ids', VocabularyRouter.findByIds);
router.post('/dataset/:dataset/widget/vocabulary/find-by-ids', VocabularyRouter.findByIds);
router.post('/dataset/:dataset/layer/vocabulary/find-by-ids', VocabularyRouter.findByIds);

module.exports = router;