resource-watch/widget

View on GitHub
app/src/services/widget.service.js

Summary

Maintainability
F
4 days
Test Coverage
B
84%
const logger = require('logger');
const Widget = require('models/widget.model');
const DatasetService = require('services/dataset.service');
const WidgetNotFound = require('errors/widgetNotFound.error');
const WidgetProtected = require('errors/widgetProtected.error');
const GraphService = require('services/graph.service');
const ScreenshotService = require('services/screenshot.service');
const RelationshipsService = require('services/relationships.service');
const { RWAPIMicroservice } = require('rw-api-microservice-node');
const slug = require('slug');
const isUndefined = require('lodash/isUndefined');

class WidgetService {

    static async getSlug(name) {
        const valid = false;
        let slugTemp = null;
        let i = 0;
        while (!valid) {
            slugTemp = slug(name);
            if (i > 0) {
                slugTemp += `_${i}`;
            }
            const currentDataset = await Widget.findOne({
                slug: slugTemp
            }).exec();
            if (!currentDataset) {
                return slugTemp;
            }
            i++;
        }

        return null;
    }

    static async update(id, widget, apiKey) {
        logger.debug(`[WidgetService]: Updating widget with id:  ${id}`);
        const currentWidget = await Widget.findById(id).exec() || await Widget.findOne({
            slug: id
        }).exec();
        logger.debug('Obtaining dataset');
        await DatasetService.checkDataset({
            params: { dataset: currentWidget.dataset },
            request: { headers: { 'x-api-key': apiKey } }
        });

        logger.debug(`[WidgetService]: Widget:  ${currentWidget}`);
        if (!currentWidget) {
            logger.info(`[WidgetService]: Widget with id ${id} doesn't exist`);
            throw new WidgetNotFound(`Widget with id '${id}' doesn't exist`);
        }

        currentWidget.name = widget.name || currentWidget.name;
        currentWidget.description = isUndefined(widget.description) ? currentWidget.description : widget.description;

        // currentWidget.userId = user.id || currentWidget.userId;
        currentWidget.usedId = currentWidget.userId;
        // ^discuss
        currentWidget.source = isUndefined(widget.source) ? currentWidget.source : widget.source;
        currentWidget.sourceUrl = widget.sourceUrl || currentWidget.sourceUrl;
        currentWidget.application = widget.application || currentWidget.application;
        currentWidget.layerId = widget.layerId || currentWidget.layerId;
        currentWidget.authors = isUndefined(widget.authors) ? currentWidget.authors : widget.authors;
        currentWidget.queryUrl = isUndefined(widget.queryUrl) ? currentWidget.queryUrl : widget.queryUrl;
        currentWidget.thumbnailUrl = widget.thumbnailUrl || currentWidget.thumbnailUrl;
        currentWidget.widgetConfig = widget.widgetConfig || currentWidget.widgetConfig;
        currentWidget.env = widget.env || currentWidget.env;
        if (widget.protected === false || widget.protected === true) {
            currentWidget.protected = widget.protected;
        }
        // Those == null wrapped in parens are totally on purpose: undefined is being coerced
        if (!(widget.template == null)) {
            currentWidget.template = widget.template;
        }
        if (!(widget.verified == null)) {
            currentWidget.verified = widget.verified;
        }
        if (!(widget.default == null)) {
            currentWidget.default = widget.default;
        }
        if (!(widget.defaultEditableWidget == null)) {
            currentWidget.defaultEditableWidget = widget.defaultEditableWidget;
        }
        if (!(widget.published == null)) {
            currentWidget.published = widget.published;
        }
        if (!(widget.freeze == null)) {
            currentWidget.freeze = widget.freeze;
        }

        currentWidget.updatedAt = new Date();

        const newWidget = await currentWidget.save();
        logger.debug(`[WidgetService]: Widget:  ${newWidget}`);

        WidgetService.generateThumbnail(newWidget.id, apiKey);

        return newWidget;
    }

    static async create(widget, datasetId, dataset, userId, apiKey) {
        logger.debug(`[WidgetService]: Creating widget with name: ${widget.name}`);
        const tempSlug = await WidgetService.getSlug(widget.name);

        const newWidget = await new Widget({
            name: widget.name,
            dataset: datasetId || widget.dataset,
            userId,
            slug: tempSlug,
            description: widget.description,
            source: widget.source,
            sourceUrl: widget.sourceUrl,
            application: widget.application,
            verified: widget.verified,
            default: widget.default,
            defaultEditableWidget: widget.defaultEditableWidget,
            published: widget.published,
            freeze: widget.freeze,
            protected: widget.protected,
            authors: widget.authors,
            queryUrl: widget.queryUrl,
            thumbnailUrl: widget.thumbnailUrl,
            env: widget.env,
            widgetConfig: widget.widgetConfig,
            template: widget.template,
            layerId: widget.layerId
        }).save();

        logger.debug('[WidgetService]: Creating in graph');
        try {
            await GraphService.createWidget(datasetId || widget.dataset, newWidget._id);
        } catch (err) {
            logger.error('Error creating widget in graph. Removing widget');
            await newWidget.remove();
            throw new Error(err);
        }

        WidgetService.generateThumbnail(newWidget.id, apiKey);

        return newWidget;
    }

    static async clone(id, widget, userId, apiKey) {
        logger.debug(`[WidgetService]: Getting widget with id: ${id}`);
        logger.debug(`[WidgetService]: New user id: ${userId}`);
        const currentWidget = await Widget.findById(id).exec() || await Widget.findOne({
            slug: id
        }).exec();
        if (!currentWidget) {
            logger.info(`[WidgetService]: Widget with id ${id} doesn't exist`);
            throw new WidgetNotFound(`Widget with id '${id}' doesn't exist`);
        }
        const newWidget = {};
        newWidget.name = widget.name || `${currentWidget.name} - ${new Date().getTime()}`;
        newWidget.description = widget.description || currentWidget.description;
        newWidget.dataset = currentWidget.dataset;
        newWidget.userId = userId;
        newWidget.source = currentWidget.source;
        newWidget.sourceUrl = currentWidget.sourceUrl;
        newWidget.application = currentWidget.application;
        newWidget.verified = currentWidget.verified;
        newWidget.default = currentWidget.default;
        newWidget.defaultEditableWidget = currentWidget.defaultEditableWidget;
        newWidget.published = currentWidget.published;
        newWidget.freeze = currentWidget.freeze;
        newWidget.protected = currentWidget.protected;
        newWidget.authors = currentWidget.authors;
        newWidget.queryUrl = currentWidget.queryUrl;
        newWidget.env = currentWidget.env;
        newWidget.widgetConfig = currentWidget.widgetConfig;
        newWidget.template = currentWidget.template;
        newWidget.layerId = currentWidget.layerId;

        const createdWidget = await WidgetService.create(newWidget, currentWidget.dataset, null, userId, apiKey);

        WidgetService.generateThumbnail(createdWidget.id, apiKey);

        return createdWidget;
    }

    static async generateThumbnail(id, apiKey) {
        logger.debug('[WidgetService]: Creating thumbnail');
        let thumbURL = '';
        try {
            const widgetThumbnail = await ScreenshotService.takeWidgetScreenshot(id, apiKey);
            thumbURL = widgetThumbnail.data.widgetThumbnail;
        } catch (err) {
            logger.error(`Error generating widget thumbnail: ${err.message}`);
        }

        try {
            const widget = await Widget.findById(id).exec();
            widget.thumbnailUrl = thumbURL;
            widget.save();
        } catch (err) {
            logger.error(`Error updating widget after thumbnail generation: ${err.message}`);
        }
    }

    static async updateEnvironment(dataset, env) {
        logger.debug('Updating widgets with dataset', dataset);
        const widgets = await Widget.find({
            dataset
        }).exec();
        await Widget.updateMany({ dataset }, { $set: { env } }, { multi: true });
        return widgets;
    }

    static async get(id, dataset, apiKey, includes = [], user = null) {
        logger.debug(`[WidgetService]: Getting widget with id: ${id}`);
        const widget = await Widget.findById(id).exec();
        if (widget) {
            if (dataset && dataset !== widget.dataset) {
                throw new WidgetNotFound(`Widget not found with the id ${id} for the dataset ${dataset}`);
            } else {
                if (includes && includes.length > 0) {
                    logger.debug('Finding relationships');
                    const widgets = await RelationshipsService.getRelationships([widget], includes, user, apiKey);
                    return widgets[0];
                }
                return widget;
            }
        } else {
            throw new WidgetNotFound(`Widget not found with the id ${id}`);
        }
    }

    static async delete(id, dataset, apiKey) {
        logger.debug(`[WidgetService]: Deleting widget with id: ${id}`);
        const widget = await Widget.findById(id).exec();
        if (!widget) {
            logger.error(`[WidgetService]: Widget not found with the id ${id}`);
            throw new WidgetNotFound(`Widget not found with the id ${id}`);
        }
        if (widget.protected) {
            logger.error(`[WidgetService]: Widget with id ${id} is protected`);
            throw new WidgetProtected(`Widget is protected`);
        }
        logger.debug('[WidgetService]: Deleting in graph');
        try {
            await GraphService.deleteWidget(id);
        } catch (err) {
            logger.error('Error removing dataset of the graph', err);
        }
        logger.debug(`[DBACCESS-DELETE]: ID: ${id}`);
        try {
            await WidgetService.deleteMetadata(dataset || widget.dataset, widget._id, apiKey);
        } catch (err) {
            logger.error('Error removing metadata of the widget', err);
        }
        await widget.remove();
        return widget;
    }

    static async deleteByDataset(id, apiKey) {
        logger.debug(`[WidgetService]: Deleting widgets of dataset with id: ${id}`);
        const widgets = await Widget.find({
            dataset: id
        }).exec();
        for (let i = 0, { length } = widgets; i < length; i++) {
            const widget = widgets[i];
            logger.debug('[WidgetService]: Deleting in graph');
            try {
                await GraphService.deleteWidget(id);
            } catch (err) {
                logger.error('Error removing dataset of the graph', err);
            }
            logger.debug(`[DBACCESS-DELETE]: ID: ${id}`);
            await widget.remove();
            try {
                await WidgetService.deleteMetadata(id, widget._id, apiKey);
            } catch (err) {
                logger.error('Error removing metadata of the widget', err);
            }

        }
        return widgets;
    }

    static async deleteByUserId(userId, apiKey) {
        logger.debug(`[WidgetService]: Delete widgets for user with id:  ${userId}`);

        const filteredQuery = WidgetService.getFilteredQuery({ userId, env: 'all' });

        const unprotectedWidgets = await Widget.find({ ...filteredQuery, protected: { $ne: true } }).exec();
        const protectedWidgets = await Widget.find({ ...filteredQuery, protected: true }).exec();

        await Promise.all(unprotectedWidgets.map((widget) => WidgetService.delete(widget.id, null, apiKey)));

        return {
            deletedWidgets: unprotectedWidgets,
            protectedWidgets
        };
    }

    static async deleteMetadata(datasetId, widgetId, apiKey) {
        logger.debug('Removing metadata of the widget');
        await RWAPIMicroservice.requestToMicroservice({
            uri: `/v1/dataset/${datasetId}/widget/${widgetId}/metadata`,
            method: 'DELETE',
            headers: {
                'x-api-key': apiKey,
            }
        });
    }

    static async getAll(user, apiKey, query = {}, dataset = null) {
        const sort = query.sort || '';
        const page = query['page[number]'] ? parseInt(query['page[number]'], 10) : 1;
        logger.debug(`pageNumber param: ${page}`);
        const limit = query['page[size]'] ? parseInt(query['page[size]'], 10) : 10;
        logger.debug(`pageSize param: ${limit}`);
        const ids = query.ids ? query.ids.split(',').map((el) => el.trim()) : [];
        const includes = query.includes ? query.includes.split(',').map((elem) => elem.trim()) : [];
        logger.debug(`ids param: ${ids}`);
        if (dataset) {
            query.dataset = dataset;
            logger.debug(`[WidgetService] Dataset for filtering is ${dataset})`);
        }

        const filteredQuery = WidgetService.getFilteredQuery({ ...query }, ids);
        logger.debug(`filteredQuery: ${JSON.stringify(filteredQuery)}`);
        const filteredSort = WidgetService.getFilteredSort(sort);
        const options = {
            page,
            limit,
            sort: filteredSort
        };
        logger.debug(`[WidgetService] Query options: ${JSON.stringify(options)}`);
        let pages = await Widget.paginate(filteredQuery, options);
        pages = { ...pages };
        if (includes.length > 0) {
            logger.debug('Finding relationships');
            pages.docs = await RelationshipsService.getRelationships(pages.docs, includes, user, apiKey, query);
        }
        return pages;
    }

    static getFilteredQuery(query, ids = []) {
        const { collection, favourite } = query;
        if (!query.application && query.app) {
            query.application = query.app;
            if (favourite) {
                delete query.application;
            }
        }
        if (!query.env) { // default value
            query.env = 'production';
        }
        // if (!query.published) { // default value
        //     query.published = true;
        // }

        const widgetAttributes = Object.keys(Widget.schema.obj);
        logger.debug(`[getFilteredQuery] widgetAttributes: ${widgetAttributes}`);
        Object.keys(query).forEach((param) => {
            if (widgetAttributes.indexOf(param) < 0 && param !== 'usersRole') {
                delete query[param];
            } else if (!['env', 'usersRole'].includes(param)) {
                switch (Widget.schema.paths[param].instance) {

                    case 'String':
                        query[param] = {
                            $regex: query[param],
                            $options: 'i'
                        };
                        break;
                    case 'Array':
                        if (query[param].indexOf('@') >= 0) {
                            query[param] = {
                                $all: query[param].split('@').map((elem) => elem.trim())
                            };
                        } else {
                            query[param] = {
                                $in: query[param].split(',').map((elem) => elem.trim())
                            };
                        }
                        break;
                    case 'Object':
                        // eslint-disable-next-line no-self-assign
                        query[param] = query[param];
                        break;
                    case 'Date':
                        // eslint-disable-next-line no-self-assign
                        query[param] = query[param];
                        break;
                    default:
                        // eslint-disable-next-line no-self-assign
                        query[param] = query[param];

                }
            } else if (param === 'usersRole') {
                logger.debug('Params users roles');
                query.userId = { ...query.userId || {}, $in: query[param] };
                delete query.usersRole;
            } else if (param === 'env') {
                if (query[param] === 'all') {
                    logger.debug('Applying all environments filter');
                    delete query.env;
                } else {
                    query.env = {
                        $in: query[param].split(',')
                    };
                }
            }
        });
        if (ids.length > 0 || collection || favourite) {
            query._id = {
                $in: ids
            };
        }
        return query;
    }

    static async getAllWidgetsUserIds() {
        logger.debug(`[WidgetService]: Getting the user ids of all widgets`);
        const widgets = await Widget.find({}, 'userId').lean();
        const userIds = widgets.map((w) => w.userId);
        return userIds.filter((item, idx) => userIds.indexOf(item) === idx && item !== 'legacy');
    }

    static processSortParam(sort) {
        return sort.replace(/user.role/g, 'userRole,_id').replace(/user.name/g, 'userName,_id');
    }

    static getFilteredSort(sort) {
        const sortParams = WidgetService.processSortParam(sort).split(',');
        const filteredSort = {};
        const widgetAttributes = Object.keys(Widget.schema.obj);
        sortParams.forEach((param) => {
            let sign = param.substr(0, 1);
            let signlessParam = param.substr(1);
            if (sign !== '-' && sign !== '+') {
                signlessParam = param;
                sign = '+';
            }
            if (widgetAttributes.indexOf(signlessParam) >= 0) {
                filteredSort[signlessParam] = parseInt(sign + 1, 10);
            }
        });
        return filteredSort;
    }

    static async getByDataset(resource) {
        logger.debug(`[WidgetService] Getting widgets for datasets with ids ${resource.ids}`);
        if (resource.app) {
            if (resource.app.indexOf('@') >= 0) {
                resource.app = {
                    $all: resource.app.split('@').map((elem) => elem.trim())
                };
            } else {
                resource.app = {
                    $in: resource.app.split(',').map((elem) => elem.trim())
                };
            }
        }

        const query = {
            dataset: {
                $in: resource.ids
            }
        };

        if (resource.env) {
            query.env = resource.env;
        }

        if (resource.app) {
            query.application = resource.app;
        }
        logger.debug(`[WidgetService] IDs query: ${JSON.stringify(query)}`);
        return Widget.find(query).exec();
    }

    static async hasPermission(id, user, apiKey) {
        let permission = true;
        const widget = await WidgetService.get(id, null, apiKey, []);
        const appPermission = widget.application.find((widgetApp) => user.extraUserData.apps.find((app) => app === widgetApp));
        if (!appPermission) {
            permission = false;
        }
        if ((user.role === 'MANAGER' || user.role === 'USER') && (!widget.userId || widget.userId !== user.id)) {
            permission = false;
        }
        return permission;
    }

}

module.exports = WidgetService;