UNPKG

@sync-in/server

Version:

The secure, open-source platform for file storage, sharing, collaboration, and sync

1,010 lines 55.8 kB
/*
 * Copyright (C) 2012-2025 Johan Legrand <johan.legrand@sync-in.com>
 * This file is part of Sync-in | The open source file sync and share solution
 * See the LICENSE file for licensing details
 */ "use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "SharesManager", {
    enumerable: true,
    get: function() {
        return SharesManager;
    }
});
const _common = require("@nestjs/common");
const _nodepath = /*#__PURE__*/ _interop_require_default(require("node:path"));
const _constants = require("../../../common/constants");
const _functions = require("../../../common/functions");
const _contextmanagerservice = require("../../../infrastructure/context/services/context-manager.service");
const _fileerror = require("../../files/models/file-error");
const _files = require("../../files/utils/files");
const _links = require("../../links/constants/links");
const _linksqueriesservice = require("../../links/services/links-queries.service");
const _notifications = require("../../notifications/constants/notifications");
const _notificationsmanagerservice = require("../../notifications/services/notifications-manager.service");
const _spaces = require("../../spaces/constants/spaces");
const _spaceenvmodel = require("../../spaces/models/space-env.model");
const _spacesqueriesservice = require("../../spaces/services/spaces-queries.service");
const _permissions = require("../../spaces/utils/permissions");
const _member = require("../../users/constants/member");
const _user = require("../../users/constants/user");
const _usersqueriesservice = require("../../users/services/users-queries.service");
const _shares = require("../constants/shares");
const _sharesqueriesservice = require("./shares-queries.service");
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
function _ts_decorate(decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function _ts_metadata(k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
}
let SharesManager = class SharesManager {
    permissions(user, spaceAlias) {
        return this.sharesQueries.permissions(user.id, spaceAlias, +user.isAdmin);
    }
    listShares(user) {
        return this.sharesQueries.listShares(user);
    }
    listSpaceShares(spaceId) {
        return this.sharesQueries.listSpaceShares(spaceId);
    }
    listChildShares(user, shareId) {
        return this.sharesQueries.listChildShares(user.id, shareId, +user.isAdmin);
    }
    async setAllowedPermissions(user, share, asAdmin = false) {
        if (share.file?.ownerId === user.id || share.externalPath && user.isAdmin) {
            // current user is the file owner (personal space case)
            share.file.permissions = _shares.SHARE_ALL_OPERATIONS;
        } else if (share.file?.space?.alias) {
            share.file.ownerId = null;
            // retrieve space permissions (cached query)
            const spacePermissions = await this.spaceQueries.permissions(user.id, share.file.space.alias, share.file.space.root?.alias);
            if (!spacePermissions) {
                this.logger.warn(`${this.setAllowedPermissions.name} - missing space permissions : ${JSON.stringify(share)}`);
                throw new _common.HttpException('Space not found', _common.HttpStatus.NOT_FOUND);
            }
            // compute permissions
            const spaceEnv = new _spaceenvmodel.SpaceEnv(spacePermissions);
            spaceEnv.setPermissions(true);
            share.file.permissions = spaceEnv.envPermissions;
        } else if (share.parent?.alias) {
            // retrieve parent share permissions (cached query)
            // use current the user permissions on the share or the share owner permissions if we request the share as admin
            const userId = asAdmin ? share.ownerId : user.id;
            const sharePermissions = await this.sharesQueries.permissions(userId, share.parent.alias, +user.isAdmin);
            if (!sharePermissions) {
                this.logger.warn(`${this.setAllowedPermissions.name} - missing share permissions : ${JSON.stringify(share)}`);
                throw new _common.HttpException('Share not found', _common.HttpStatus.NOT_FOUND);
            }
            share.file.permissions = sharePermissions.permissions;
        } else {
            this.logger.error(`${this.setAllowedPermissions.name} - case not handled ${JSON.stringify(share)}`);
            throw new _common.HttpException('Missing information', _common.HttpStatus.BAD_REQUEST);
        }
    }
    async getShareWithMembers(user, shareId, asAdmin = false) {
        // asAdmin : true if the user is the owner of the parent share or if the share is requested from the administration
        const share = await this.sharesQueries.getShareWithMembers(user, shareId, asAdmin);
        if (!share) {
            throw new _common.HttpException('Not authorized', _common.HttpStatus.FORBIDDEN);
        }
        await this.setAllowedPermissions(user, share, asAdmin);
        return share;
    }
    async createShare(user, createOrUpdateShareDto) {
        const share = {
            name: createOrUpdateShareDto.name,
            alias: await this.sharesQueries.uniqueShareAlias(createOrUpdateShareDto.name),
            description: createOrUpdateShareDto.description,
            externalPath: createOrUpdateShareDto.externalPath,
            enabled: createOrUpdateShareDto.enabled,
            disabledAt: createOrUpdateShareDto.enabled ? null : new Date(),
            type: createOrUpdateShareDto.type || _shares.SHARE_TYPE.COMMON
        };
        if (share.externalPath) {
            /* EXTERNAL PATH CASE */ if (!user.isAdmin) {
                throw new _common.HttpException('Not authorized', _common.HttpStatus.FORBIDDEN);
            }
            try {
                await (0, _files.checkExternalPath)(share.externalPath);
            } catch (e) {
                throw new _common.HttpException(e.message, e instanceof _fileerror.FileError ? e.httpCode : _common.HttpStatus.INTERNAL_SERVER_ERROR);
            }
            share.ownerId = null;
        } else {
            /* SPACES CASE */ share.externalPath = null;
            share.ownerId = user.id;
            if (createOrUpdateShareDto.file.ownerId) {
                /* PERSONAL SPACE CASE */ // check file
                const realPath = _nodepath.default.join(user.filesPath, createOrUpdateShareDto.file.path);
                if (!await (0, _files.isPathExists)(realPath)) {
                    this.logger.warn(`${this.createShare.name} - location does not exist : ${realPath}`);
                    throw new _common.HttpException('The location does not exist', _common.HttpStatus.NOT_FOUND);
                }
                const fileProps = {
                    ...await (0, _files.getProps)(realPath, createOrUpdateShareDto.file.path),
                    id: createOrUpdateShareDto.file.id
                };
                share.fileId = await this.spaceQueries.getOrCreateUserFile(user.id, fileProps);
            } else if (createOrUpdateShareDto.file.space?.alias) {
                /* SPACE CASE */ const spacePermissions = await this.spaceQueries.permissions(user.id, createOrUpdateShareDto.file.space.alias, createOrUpdateShareDto.file.space.root.alias);
                if (!spacePermissions) {
                    throw new _common.HttpException('Space not found', _common.HttpStatus.NOT_FOUND);
                }
                // compute space permissions
                const space = new _spaceenvmodel.SpaceEnv(spacePermissions);
                space.setPermissions(true);
                // intersect space permissions for members
                for (const m of createOrUpdateShareDto.members){
                    m.permissions = (0, _functions.intersectPermissions)(space.envPermissions, m.permissions);
                }
                // intersect space permissions for links
                for (const l of createOrUpdateShareDto.links){
                    l.permissions = (0, _functions.intersectPermissions)(space.envPermissions, l.permissions);
                }
                // check file
                try {
                    space.setPaths(user, createOrUpdateShareDto.file.space.root.alias, createOrUpdateShareDto.file.path.split('/').slice(space.root.id ? 1 : 0));
                } catch (e) {
                    if (e instanceof _fileerror.FileError) {
                        throw new _common.HttpException(e.message, e.httpCode);
                    }
                    throw new _common.HttpException(e.message, _common.HttpStatus.BAD_REQUEST);
                }
                if (!await (0, _files.isPathExists)(space.realPath)) {
                    this.logger.warn(`${this.createShare.name} - space location does not exist : *${space.alias}* (${space.id}) : ${space.realPath}`);
                    throw new _common.HttpException('The location does not exist', _common.HttpStatus.NOT_FOUND);
                }
                share.spaceId = space.id;
                share.spaceRootId = space.root?.id || null;
                // define share.fileId
                // if the file is the same as the space root, ignores share.fileId and only uses spaceId and spaceRootId
                const isExternalSpaceRoot = createOrUpdateShareDto.file?.id < 0 && createOrUpdateShareDto.file?.path === createOrUpdateShareDto.file?.space?.root?.alias && createOrUpdateShareDto.file?.space?.root?.alias === spacePermissions.root?.alias && createOrUpdateShareDto.file?.space?.root?.name === spacePermissions.root?.name;
                const isSpaceRoot = Number(createOrUpdateShareDto.file.id) === Number(spacePermissions.root?.file?.id);
                if (!isSpaceRoot && !isExternalSpaceRoot) {
                    const fileProps = {
                        ...await (0, _files.getProps)(space.realPath, space.dbFile.path),
                        id: undefined
                    };
                    // get or create file id
                    share.fileId = await this.spaceQueries.getOrCreateSpaceFile(createOrUpdateShareDto.file.id, fileProps, space.dbFile);
                }
            } else {
                // unexpected case
                throw new _common.HttpException('Missing information', _common.HttpStatus.BAD_REQUEST);
            }
        }
        // create share
        share.id = await this.sharesQueries.createShare(share);
        // check & update members
        await this.createOrUpdateLinksAsMembers(user, share, _links.LINK_TYPE.SHARE, createOrUpdateShareDto.links);
        await this.updateMembers(user, share, [], createOrUpdateShareDto.members);
        return this.getShareWithMembers(user, share.id);
    }
    async updateShare(user, shareId, createOrUpdateShareDto, asAdmin = false) {
        // asAdmin : true if the user is the owner of the parent share or if the share is requested from the administration
        const share = await this.getShareWithMembers(user, shareId, asAdmin);
        // check & update share info
        const shareDiffProps = {
            modifiedAt: new Date()
        };
        for (const prop of [
            'name',
            'description',
            'enabled'
        ]){
            if (createOrUpdateShareDto[prop] !== share[prop]) {
                shareDiffProps[prop] = createOrUpdateShareDto[prop];
                if (prop === 'name') {
                    shareDiffProps.alias = await this.sharesQueries.uniqueShareAlias(shareDiffProps.name);
                } else if (prop === 'enabled') {
                    shareDiffProps.disabledAt = shareDiffProps[prop] ? null : new Date();
                }
            }
        }
        // update in db
        this.sharesQueries.updateShare(shareDiffProps, {
            id: shareId
        }).catch((e)=>this.logger.error(`${this.updateShare.name} - ${e}`));
        // check & update members
        const linkMembers = await this.createOrUpdateLinksAsMembers(user, share, _links.LINK_TYPE.SHARE, createOrUpdateShareDto.links);
        // intersect share permissions for members
        for (const m of createOrUpdateShareDto.members){
            m.permissions = (0, _functions.intersectPermissions)(share.file.permissions, m.permissions);
        }
        // intersect share permissions for links
        for (const l of linkMembers){
            l.permissions = (0, _functions.intersectPermissions)(share.file.permissions, l.permissions);
        }
        await this.updateMembers(user, share, share.members, [
            ...createOrUpdateShareDto.members,
            ...linkMembers
        ]);
        return this.getShareWithMembers(user, share.id, asAdmin);
    }
    async deleteShare(user, shareId, asAdmin = false) {
        // asAdmin : true if the user is the owner of the parent share or if the share is requested from an admin
        if (!asAdmin && !user.isAdmin && !await this.sharesQueries.shareExistsForOwner(user.id, shareId)) {
            throw new _common.HttpException('Not authorized', _common.HttpStatus.FORBIDDEN);
        }
        try {
            await this.deleteAllLinkMembers(shareId, _links.LINK_TYPE.SHARE);
            await this.removeShareFromOwners(shareId, 'all', false, user.id);
        } catch (e) {
            this.logger.error(`${this.deleteShare.name} - unable to delete share (${shareId}) (asAdmin = ${asAdmin}) : ${e}`);
            throw new _common.HttpException('Unable to delete share', _common.HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
    async getChildShare(user, shareId, childId, isLink) {
        if (await this.checkChildSharePermissions(user, shareId, childId)) {
            if (isLink) {
                return this.getShareLink(user, childId, true);
            }
            return this.getShareWithMembers(user, childId, true);
        }
    }
    async updateChildShare(user, shareId, childId, createOrUpdateShareDto) {
        if (await this.checkChildSharePermissions(user, shareId, childId)) {
            return this.updateShare(user, childId, createOrUpdateShareDto, true);
        }
    }
    async deleteChildShare(user, shareId, childId) {
        if (await this.checkChildSharePermissions(user, shareId, childId)) {
            return this.deleteShare(user, childId, true);
        }
    }
    async createChildShare(user, createOrUpdateShareDto) {
        // check parent share
        const pSharePermissions = await this.sharesQueries.permissions(user.id, createOrUpdateShareDto.parent.alias, +user.isAdmin);
        if (!pSharePermissions) {
            this.logger.warn(`${this.createChildShare.name} - parent share does not exist or not authorized : ${createOrUpdateShareDto.parent.alias}`);
            throw new _common.HttpException('Parent share not found', _common.HttpStatus.NOT_FOUND);
        }
        if (!(0, _permissions.haveSpacePermission)(pSharePermissions, _spaces.SPACE_OPERATION.SHARE_OUTSIDE)) {
            this.logger.warn(`${this.createChildShare.name} - is not allowed to share outside of : *${pSharePermissions.alias}* (${pSharePermissions.id})`);
            throw new _common.HttpException('You are not allowed to do this action', _common.HttpStatus.FORBIDDEN);
        }
        if (!pSharePermissions.enabled) {
            this.logger.warn(`${this.createChildShare.name} - parent share is disabled : ${createOrUpdateShareDto.parent.alias}`);
            throw new _common.HttpException('Parent share is disabled', _common.HttpStatus.FORBIDDEN);
        }
        let pShare;
        let filePath;
        if (pSharePermissions.root.externalPath) {
            const highestParentId = await this.sharesQueries.findHighestParentShare(pSharePermissions.id);
            if (!highestParentId) {
                this.logger.warn(`${this.createChildShare.name} - unable to find the highest parent of : *${pSharePermissions.alias}* (${pSharePermissions.id})`);
                throw new _common.HttpException('Parent share not found', _common.HttpStatus.NOT_FOUND);
            }
            pShare = await this.sharesQueries.shareEnv(highestParentId);
            filePath = _nodepath.default.join(pSharePermissions.root?.file?.path || '', createOrUpdateShareDto.file.path);
        } else {
            pShare = await this.sharesQueries.shareEnv(pSharePermissions.id);
            filePath = createOrUpdateShareDto.file.path;
        }
        // create a fake space env -> share env
        const pShareEnv = new _spaceenvmodel.SpaceEnv(pShare, null, false);
        try {
            pShareEnv.setPaths(user, null, filePath.split('/'));
        } catch (e) {
            if (e instanceof _fileerror.FileError) {
                throw new _common.HttpException(e.message, e.httpCode);
            }
            throw new _common.HttpException(e.message, _common.HttpStatus.BAD_REQUEST);
        }
        // check file
        if (!await (0, _files.isPathExists)(pShareEnv.realPath)) {
            this.logger.warn(`${this.createChildShare.name} - parent share location does not exist : ${pShareEnv.alias} (${pShareEnv.id}) : ${pShareEnv.realPath}`);
            throw new _common.HttpException('The location does not exist', _common.HttpStatus.NOT_FOUND);
        }
        /* Manage the case where the child share is created from the parent share itself */ // special case, the parent share is directly linked to the space root file
        const isLinkedToShareSpaceRoot = pShareEnv.fileId === null && Number(createOrUpdateShareDto.file.id) === Number(pSharePermissions.root?.id) && createOrUpdateShareDto.file.path === '.';
        // special case, the parent share is directly linked to the share with an external path
        const isLinkedToShareExternalPath = createOrUpdateShareDto.file.id < 0 && !!pShareEnv.root.externalPath && createOrUpdateShareDto.file.path === '.';
        let fileId = null;
        if (!isLinkedToShareSpaceRoot && !isLinkedToShareExternalPath) {
            // fileId is mandatory for a file in a child share
            const fileProps = {
                ...await (0, _files.getProps)(pShareEnv.realPath, pShareEnv.dbFile.path),
                id: undefined
            };
            fileId = await this.spaceQueries.getOrCreateSpaceFile(createOrUpdateShareDto.file.id, fileProps, pShareEnv.dbFile);
        }
        const share = {
            name: createOrUpdateShareDto.name,
            alias: await this.sharesQueries.uniqueShareAlias(createOrUpdateShareDto.name),
            ownerId: user.id,
            spaceId: pShareEnv.spaceId,
            spaceRootId: pShareEnv.spaceRootId,
            parentId: pSharePermissions.id,
            fileId: fileId,
            description: createOrUpdateShareDto.description,
            externalPath: pShareEnv.root.externalPath,
            enabled: createOrUpdateShareDto.enabled,
            disabledAt: createOrUpdateShareDto.enabled ? null : new Date(),
            type: createOrUpdateShareDto.type || _shares.SHARE_TYPE.COMMON
        };
        // create child share
        share.id = await this.sharesQueries.createShare(share);
        // intersect parent share permissions for members
        for (const m of createOrUpdateShareDto.members){
            m.permissions = (0, _functions.intersectPermissions)(pSharePermissions.permissions, m.permissions);
        }
        // intersect parent share permissions for links
        for (const l of createOrUpdateShareDto.links){
            l.permissions = (0, _functions.intersectPermissions)(pSharePermissions.permissions, l.permissions);
        }
        // check & update members
        await this.createOrUpdateLinksAsMembers(user, share, _links.LINK_TYPE.SHARE, createOrUpdateShareDto.links);
        await this.updateMembers(user, share, [], createOrUpdateShareDto.members);
        return this.getShareWithMembers(user, share.id);
    }
    async updateSharesFromSpace(/*
      In this case the space is considered as a parent share
      The shares and child shares of the member should be deleted if the member is removed from the space
      Member permissions on shares and its child shares must be updated if the parent share owner's permissions are updated on the space
    */ spaceId, currentMembers, toRemove, toUpdate) {
        // skip if no actions
        if (!toRemove.length && !toUpdate.length) return;
        // get all space manager ids (ignore them, they have all permissions on the space)
        const spaceManagerIds = currentMembers.filter((m)=>m.spaceRole === _spaces.SPACE_ROLE.IS_MANAGER).map((m)=>m.id);
        const [rmOwners, upOwners] = await this.diffSharesPermissions(currentMembers, toRemove, toUpdate, spaceManagerIds);
        if (!rmOwners.length && !upOwners.length) return;
        const owners = {
            ...Object.fromEntries(rmOwners.map((uId)=>[
                    uId,
                    {
                        type: _constants.ACTION.DELETE
                    }
                ])),
            ...Object.fromEntries(upOwners.reduce((acc, o)=>{
                for (const id of o.ids){
                    acc.push([
                        id,
                        {
                            type: _constants.ACTION.UPDATE,
                            rmPermissions: o.rmPermissions
                        }
                    ]);
                }
                return acc;
            }, []))
        };
        // find all parent shares which are owned by the modified/removed members of the space
        for (const share of (await this.sharesQueries.selectParentSharesFromSpaceId(spaceId, Object.keys(owners).map((id)=>parseInt(id))))){
            if (share.ownerId in owners) {
                const action = owners[share.ownerId];
                if (action.type === _constants.ACTION.UPDATE) {
                    this.removeChildSharesPermissions(share.id, [
                        {
                            ids: [
                                share.ownerId
                            ],
                            rmPermissions: action.rmPermissions
                        }
                    ], false).catch((e)=>this.logger.error(`${this.updateSharesFromSpace.name} - ${e}`));
                } else {
                    this.removeShareFromOwners(share.id, [
                        share.ownerId
                    ], false).catch((e)=>this.logger.error(`${this.updateSharesFromSpace.name} - ${e}`));
                }
                this.logger.log(`${this.updateSharesFromSpace.name} - ${action.type} share (${share.id}) for owner ${share.ownerId} from space ${spaceId}`);
            }
        }
    }
    async updateSharesFromSpaceRoots(/* update or remove shares related to space roots changes */ spaceId, toRemove, toUpdate) {
        // skip if no actions
        if (!toRemove.length && !toUpdate.length) return;
        for (const root of toUpdate){
            for (const share of (await this.sharesQueries.selectShares({
                spaceId: spaceId,
                spaceRootId: root.id,
                parentId: null
            }))){
                this.removeChildSharesPermissions(share.id, [
                    {
                        ids: 'all',
                        rmPermissions: root.rmPermissions
                    }
                ], false).catch((e)=>this.logger.error(`${this.updateSharesFromSpaceRoots.name} - ${e}`));
            }
        }
        for (const rootId of toRemove){
            for (const share of (await this.sharesQueries.selectShares({
                spaceId: spaceId,
                spaceRootId: rootId,
                parentId: null
            }))){
                // use await ! avoid database lock ! next action is to delete the root space which is cascaded with share.spaceRootId
                await this.removeShareFromOwners(share.id, [
                    share.ownerId
                ], false);
            }
        }
    }
    async removeSharesFromSpace(spaceId) {
        for (const share of (await this.sharesQueries.selectShares({
            spaceId: spaceId,
            parentId: null
        }))){
            // use await ! avoid database lock ! next action is to delete the space which is cascaded with share.spaceId
            await this.removeShareFromOwners(share.id, [
                share.ownerId
            ], false);
        }
    }
    async generateLinkUUID(userId) {
        let uuid = (0, _functions.generateShortUUID)();
        while(!await this.linksQueries.isUniqueUUID(userId, uuid)){
            uuid = (0, _functions.generateShortUUID)();
        }
        return {
            uuid: uuid
        };
    }
    listShareLinks(user) {
        return this.sharesQueries.listShareLinks(user);
    }
    async getShareLink(user, shareId, asAdmin = false) {
        const shareLink = await this.sharesQueries.listShareLinks(user, shareId, asAdmin);
        if (!shareLink) {
            throw new _common.HttpException('Not authorized', _common.HttpStatus.FORBIDDEN);
        }
        await this.setAllowedPermissions(user, shareLink);
        if (shareLink.file?.permissions) {
            // share link does not have these permissions
            shareLink.file.permissions = (0, _permissions.removePermissions)(shareLink.file.permissions, [
                _spaces.SPACE_OPERATION.SHARE_INSIDE,
                _spaces.SPACE_OPERATION.SHARE_OUTSIDE
            ]);
        }
        return shareLink;
    }
    async getLinkFromSpaceOrShare(user, linkId, spaceOrShareId, type) {
        let linkGuest;
        if (type === _links.LINK_TYPE.SPACE) {
            linkGuest = await this.linksQueries.linkFromSpace(user.id, linkId, spaceOrShareId);
        } else {
            linkGuest = await this.linksQueries.linkFromShare(user.id, linkId, spaceOrShareId, +user.isAdmin);
        }
        if (!linkGuest) {
            this.logger.warn(`${this.getLinkFromSpaceOrShare.name} - unable to find link (${linkId}) on ${type} (${spaceOrShareId})`);
            throw new _common.HttpException('Link not found', _common.HttpStatus.NOT_FOUND);
        }
        return linkGuest;
    }
    async createOrUpdateLinksAsMembers(user, spaceOrShare, type, links) {
        /* only used during the share creation from the share manager */ const linkMembers = [];
        for (const link of links){
            if (link.id < 0) {
                // new link (permissions are needed to create guest link)
                await this.createLinkFromSpaceOrShare(user, link.linkSettings.uuid, spaceOrShare.id, type, {
                    ...link.linkSettings,
                    permissions: link.permissions
                });
                // notify the guest link (if email is specified)
                this.notifyGuestLink(user, link, spaceOrShare.name, type === _links.LINK_TYPE.SHARE ? _constants.ACTION.ADD : _constants.ACTION.UPDATE).catch((e)=>this.logger.error(`${this.createOrUpdateLinksAsMembers.name} - ${e}`));
            } else {
                if (link.linkSettings) {
                    // modified link
                    await this.updateLinkFromSpaceOrShare(user, link.linkId, spaceOrShare.id, type, link.linkSettings);
                }
                // unmodified link
                linkMembers.push(link);
            }
        }
        return linkMembers;
    }
    async updateLinkFromSpaceOrShare(user, linkId, spaceOrShareId, type, createOrUpdateLinkDto, fromAPI = false) {
        const link = await this.getLinkFromSpaceOrShare(user, linkId, spaceOrShareId, type);
        if (!link) {
            this.logger.error(`${this.updateLinkFromSpaceOrShare.name} - (${linkId}) from ${type} (${spaceOrShareId}) and user (${user.id}) was not found`);
            throw new _common.HttpException('Unable to find link', _common.HttpStatus.NOT_FOUND);
        }
        const fieldsWhiteList = [
            'name',
            'email',
            'requireAuth',
            'limitAccess',
            'expiresAt',
            'language',
            'isActive',
            'password',
            'permissions',
            'shareName',
            'shareDescription'
        ];
        const [updateUser, updateLink, updateShare, updateMember] = [
            {},
            {},
            {},
            {}
        ];
        for (const [k, v] of Object.entries(createOrUpdateLinkDto)){
            if (fieldsWhiteList.indexOf(k) > -1 && link[k] !== v) {
                switch(k){
                    case 'password':
                        if (v) {
                            updateUser.password = await (0, _functions.hashPassword)(v);
                        }
                        break;
                    case 'permissions':
                        if (fromAPI) {
                            // permissions are only present if the share type is link
                            // intersect permissions to ensure that the user does not attempt to exceed his rights
                            const shareLink = await this.getShareLink(user, spaceOrShareId);
                            updateMember.permissions = (0, _functions.intersectPermissions)(shareLink.file.permissions, v);
                        }
                        break;
                    case 'language':
                        updateUser.language = v;
                        break;
                    case 'isActive':
                        updateUser.isActive = v;
                        break;
                    case 'shareName':
                        updateShare.name = v;
                        updateShare.alias = await this.sharesQueries.uniqueShareAlias(v);
                        break;
                    case 'shareDescription':
                        updateShare.description = v;
                        break;
                    case 'expiresAt':
                        if (JSON.stringify(link[k]) !== JSON.stringify(v)) {
                            updateLink[k] = v;
                        }
                        break;
                    default:
                        updateLink[k] = v;
                }
            }
        }
        if (!Object.keys(updateUser).length && !Object.keys(updateLink).length && !Object.keys(updateShare).length && !Object.keys(updateMember).length) {
            this.logger.warn(`${this.updateLinkFromSpaceOrShare.name} - no diff to update`);
            return fromAPI ? link : null;
        }
        try {
            await this.linksQueries.updateLinkFromSpaceOrShare(link, spaceOrShareId, updateUser, updateLink, updateShare, updateMember);
            this.logger.debug(`${this.updateLinkFromSpaceOrShare.name} - link (${linkId}) updated : ${JSON.stringify({
                ...{
                    user: (0, _functions.anonymizePassword)(updateUser)
                },
                ...{
                    link: updateLink
                },
                ...{
                    share: updateShare
                },
                ...{
                    member: updateMember
                }
            })}`);
        } catch (e) {
            this.logger.error(`${this.updateLinkFromSpaceOrShare.name} - ${e}`);
            throw new _common.HttpException('Unable to update link', _common.HttpStatus.INTERNAL_SERVER_ERROR);
        }
        if (fromAPI) {
            // for security reasons
            delete updateUser.password;
            Object.assign(link, updateUser, updateLink, updateMember);
            return link;
        }
    }
    async createGuestLink(permission, password, language, isActive = true) {
        const random = (0, _functions.generateShortUUID)(32);
        const guestLink = {
            login: random,
            email: `${random}@sync-in`,
            firstName: 'Guest',
            lastName: 'Link',
            language: language || null,
            permissions: permission,
            password: await (0, _functions.hashPassword)(password || (0, _functions.generateShortUUID)(12)),
            role: _user.USER_ROLE.LINK,
            isActive: isActive
        };
        try {
            ;
            guestLink.id = await this.usersQueries.createUserOrGuest(guestLink, _user.USER_ROLE.LINK);
            return guestLink;
        } catch (e) {
            this.logger.error(`${this.createGuestLink.name} - unable to create guest link : ${e}`);
            throw new _common.HttpException('Unable to create guest link', _common.HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
    /* MANAGE SHARE LINKS */ async deleteAllLinkMembers(spaceOrShareId, type) {
        const ids = await this.linksQueries.allLinksFromSpaceOrShare(spaceOrShareId, type);
        await this.deleteGuestLinks(ids);
    }
    async deleteLinkMembers(members) {
        await this.deleteGuestLinks(members);
    }
    async updateMembers(user, share, oldMembers, currentMembers) {
        if (oldMembers.length === 0 && currentMembers.length === 0) {
            return;
        }
        // diff
        const [add, update, remove] = (0, _functions.diffCollection)(oldMembers, currentMembers, [
            'permissions'
        ], [
            'id',
            'type'
        ]);
        // check members whitelists
        let toAdd = [];
        if (add.length) {
            const [userIdsWhitelist, groupIdsWhitelist] = await Promise.all([
                this.usersQueries.usersWhitelist(user.id),
                this.usersQueries.groupsWhitelist(user.id)
            ]);
            toAdd = add.filter((m)=>{
                if ((m.type === _member.MEMBER_TYPE.USER || m.type === _member.MEMBER_TYPE.GUEST) && !m.linkId && userIdsWhitelist.indexOf(m.id) === -1 || (m.type === _member.MEMBER_TYPE.GROUP || m.type === _member.MEMBER_TYPE.PGROUP) && groupIdsWhitelist.indexOf(m.id) === -1) {
                    this.logger.warn(`${this.updateMembers.name} cannot add ${m.type} (${m.id}) to share *${share.alias}* (${share.id}) : not in the members whitelist`);
                    return false;
                }
                return true;
            });
        }
        // filter links
        const toRemove = remove.filter((m)=>!m.linkId);
        // do remove links
        this.deleteLinkMembers(remove.filter((m)=>!!m.linkId)).catch((e)=>this.logger.error(`${this.updateMembers.name} - ${e}`));
        // do update members
        const status = await this.sharesQueries.updateMembers(share.id, toAdd, (0, _functions.convertDiffUpdate)(update), toRemove);
        // lists deleted and updated members as potential share owners
        const [rmMembersChildShares, upMembersChildShares] = [
            [],
            []
        ];
        for (const [action, members] of Object.entries(status)){
            if (!members.userIds.length && !members.groupIds.length) continue;
            if (action === _constants.ACTION.DELETE) {
                // stores the removed members who might own child shares from the current share
                rmMembersChildShares.push(...toRemove.filter((m)=>(m.type === _member.MEMBER_TYPE.USER || m.type === _member.MEMBER_TYPE.GUEST) && members.userIds.indexOf(m.id) > -1 || (m.type === _member.MEMBER_TYPE.GROUP || m.type === _member.MEMBER_TYPE.PGROUP) && members.groupIds.indexOf(m.id) > -1));
            } else if (action === _constants.ACTION.UPDATE) {
                // stores permissions updates and members who might own child shares created from the current share
                for (const m of update){
                    if ((m.object.type === _member.MEMBER_TYPE.USER || m.object.type === _member.MEMBER_TYPE.GUEST) && members.userIds.indexOf(m.object.id) > -1 || (m.object.type === _member.MEMBER_TYPE.GROUP || m.object.type === _member.MEMBER_TYPE.PGROUP) && members.groupIds.indexOf(m.object.id) > -1) {
                        const diffPermissions = (0, _functions.differencePermissions)(m.permissions.old, m.permissions.new);
                        if (diffPermissions.length) {
                            upMembersChildShares.push({
                                object: m.object,
                                rmPermissions: diffPermissions
                            });
                        }
                    }
                }
            }
            // clear cache &|| notify
            this.onShareActionForMembers(share, action, members, user).catch((e)=>this.logger.error(`${this.updateMembers.name} - ${e}`));
        }
        // do updates
        // remove or update potential child shares
        this.updateMembersChildSharesPermissions(share.id, currentMembers, rmMembersChildShares, upMembersChildShares).catch((e)=>this.logger.error(`${this.updateMembers.name} - ${e}`));
    }
    async updateMembersChildSharesPermissions(parentShareId, currentMembers, toRemove, toUpdate) {
        /*
      child shares of the member should be deleted if the member is removed from the parent share
      the permissions of the child shares members should be updated if the member's permissions are updated
    */ const [removeUsersChildShares, updateUsersChildShares] = await this.diffSharesPermissions(currentMembers, toRemove, toUpdate);
        await Promise.all([
            this.removeShareFromOwners(parentShareId, removeUsersChildShares),
            this.removeChildSharesPermissions(parentShareId, updateUsersChildShares)
        ]);
    }
    async diffSharesPermissions(currentMembers, toRemove, toUpdate, ignoreUserIds = []) {
        // remove shares from members
        const [removeUsersChildShares, removeGroupsChildShares] = [
            [],
            []
        ];
        for (const m of toRemove){
            if (m.type === _member.MEMBER_TYPE.USER || m.type === _member.MEMBER_TYPE.GUEST) {
                if (ignoreUserIds.indexOf(m.id) > -1) {
                    continue;
                }
                // do not remove child shares if the user is a member of a group with share permission
                const memberGroupIds = await this.usersQueries.groupsWhitelist(m.id);
                const groupSharePermission = currentMembers.find((m)=>(m.type === _member.MEMBER_TYPE.GROUP || m.type === _member.MEMBER_TYPE.PGROUP) && memberGroupIds.indexOf(m.id) > -1 && (0, _permissions.havePermission)(m.permissions, _spaces.SPACE_OPERATION.SHARE_OUTSIDE));
                if (groupSharePermission) {
                    this.logger.debug(`${this.diffSharesPermissions.name} - ignore user (${m.id}) removal : is a member of the group (${groupSharePermission.id}) with share permission`);
                    continue;
                }
                removeUsersChildShares.push(m.id);
            } else {
                removeGroupsChildShares.push(m.id);
            }
        }
        // update shares permissions from members
        const [updateUsersChildShares, updateGroupsChildShares] = [
            [],
            []
        ];
        for (const m of toUpdate){
            // all child share permissions must be updated
            if (m.object.type === _member.MEMBER_TYPE.USER || m.object.type === _member.MEMBER_TYPE.GUEST) {
                if (ignoreUserIds.indexOf(m.object.id) > -1) continue;
                // check if the user is a member of the existing groups on the share
                // since group and user permissions are aggregated, we should check the group permissions
                const memberGroupIds = await this.usersQueries.groupsWhitelist(m.object.id);
                const groupPermissions = currentMembers.filter((m)=>(m.type === _member.MEMBER_TYPE.GROUP || m.type === _member.MEMBER_TYPE.PGROUP) && memberGroupIds.indexOf(m.id) > -1).reduce((perms, m)=>{
                    for (const p of m.permissions.split(_spaces.SPACE_PERMS_SEP).filter((p)=>p !== '' && perms.indexOf(p) === -1)){
                        perms.push(p);
                    }
                    return perms;
                }, []);
                // find all permissions that the user must keep
                const [permsToKeep, permsToRemove] = [
                    [],
                    []
                ];
                // compare the permissions removed from group to user's
                m.rmPermissions.forEach((p)=>groupPermissions.indexOf(p) > -1 ? permsToKeep.push(p) : permsToRemove.push(p));
                if (!permsToRemove.length) {
                    continue;
                }
                // remove only unmatched permissions
                m.rmPermissions = permsToRemove;
            }
            // group members by permissions to optimize queries
            const memberTypeChildShares = m.object.type === _member.MEMBER_TYPE.USER || m.object.type === _member.MEMBER_TYPE.GUEST ? updateUsersChildShares : updateGroupsChildShares;
            const memberWithSamePermissions = memberTypeChildShares.find((p)=>p.rmPermissions.toString() === m.rmPermissions.toString());
            if (memberWithSamePermissions) {
                memberWithSamePermissions.ids.push(m.object.id);
            } else {
                memberTypeChildShares.push({
                    ids: [
                        m.object.id
                    ],
                    rmPermissions: m.rmPermissions
                });
            }
        }
        // retrieves all users from groups and subgroups & add them to the remove and update lists
        if (removeGroupsChildShares.length) {
            // ignore user id if the user is already removed or is a member of the parent share
            const rmUsersFromGroups = (await this.usersQueries.allUserIdsFromGroupsAndSubGroups(removeGroupsChildShares)).filter((id)=>ignoreUserIds.indexOf(id) === -1 && removeUsersChildShares.indexOf(id) === -1 && !currentMembers.find((m)=>(m.type === _member.MEMBER_TYPE.USER || m.type === _member.MEMBER_TYPE.GUEST) && m.id === id));
            removeUsersChildShares.push(...rmUsersFromGroups);
        }
        if (updateGroupsChildShares.length) {
            for (const g of updateGroupsChildShares){
                // ignore user id if the user is already removed or is a member of the parent share
                const rmUsersPermissionsFromGroups = [];
                for (const uId of (await this.usersQueries.allUserIdsFromGroupsAndSubGroups(g.ids))){
                    if (ignoreUserIds.indexOf(uId) > -1 || removeUsersChildShares.indexOf(uId) > -1) {
                        continue;
                    }
                    // check if the user is a member of the existing groups on the share
                    // since group and user permissions are aggregated, we should check the group permissions
                    const memberGroupIds = await this.usersQueries.groupsWhitelist(uId);
                    const groupPermissions = currentMembers.filter((m)=>(m.type === _member.MEMBER_TYPE.GROUP || m.type === _member.MEMBER_TYPE.PGROUP) && memberGroupIds.indexOf(m.id) > -1 || (m.type === _member.MEMBER_TYPE.USER || m.type === _member.MEMBER_TYPE.GUEST) && m.id === uId).reduce((perms, m)=>{
                        for (const p of m.permissions.split(_spaces.SPACE_PERMS_SEP).filter((p)=>p !== '' && perms.indexOf(p) === -1)){
                            perms.push(p);
                        }
                        return perms;
                    }, []);
                    // find all permissions that the user must keep
                    const [permsToKeep, permsToRemove] = [
                        [],
                        []
                    ];
                    // compare the permissions removed from group to user's
                    g.rmPermissions.forEach((p)=>groupPermissions.indexOf(p) > -1 ? permsToKeep.push(p) : permsToRemove.push(p));
                    if (!permsToKeep.length) {
                        // user does not have the removed permissions from groups or from himself, we can remove the permissions to his child shares
                        rmUsersPermissionsFromGroups.push(uId);
                    } else if (permsToRemove.length) {
                        // remove only unmatched permissions
                        updateUsersChildShares.push({
                            ids: [
                                uId
                            ],
                            rmPermissions: permsToRemove
                        });
                    }
                }
                if (!rmUsersPermissionsFromGroups.length) continue;
                // group users by permissions to optimize queries
                const memberWithSamePermissions = updateUsersChildShares.find((p)=>p.rmPermissions.toString() === g.rmPermissions.toString());
                if (memberWithSamePermissions) {
                    memberWithSamePermissions.ids.push(...rmUsersPermissionsFromGroups);
                } else {
                    updateUsersChildShares.push({
                        ids: rmUsersPermissionsFromGroups,
                        rmPermissions: g.rmPermissions
                    });
                }
            }
        }
        return [
            removeUsersChildShares,
            updateUsersChildShares
        ];
    }
    async removeShareFromOwners(shareId, ownerIds, asParent = true, fromUserId) {
        // deletes only the first (child) shares, child shares will be deleted in cascade
        const where = {
            ...asParent ? {
                parentId: shareId
            } : {
                id: shareId
            },
            ...ownerIds !== 'all' && {
                ownerId: ownerIds
            }
        };
        for (const share of (await this.sharesQueries.selectShares(where))){
            try {
                // store current child shares members before delete parent share
                const members = await this.sharesQueries.membersFromChildSharesPermissions(share.id, [
                    share.ownerId
                ], null, false);
                await this.sharesQueries.deleteShare(share.id);
                this.logger.log(`${this.removeShareFromOwners.name} - share *${share.alias}* (${share.id}) from owner (${share.ownerId}) was removed`);
                // clear cache & notify users
                if (!fromUserId || fromUserId !== share.ownerId) {
                    this.clearCachePermissionsAndOrNotify(share, _constants.ACTION.DELETE_PERMANENTLY, [
                        share.ownerId
                    ]).catch((e)=>this.logger.error(`${this.removeShareFromOwners.name} - ${e}`));
                }
                members.forEach((m)=>this.clearCachePermissionsAndOrNotify({
                        alias: m.shareAlias,
                        name: m.shareName
                    }, _constants.ACTION.DELETE, [
                        m.userId
                    ]));
            } catch (e) {
                this.logger.error(`${this.removeShareFromOwners.name} - share *${share.alias}* (${share.id}) from owner (${share.ownerId}) was not removed : ${e}`);
            }
        }
    }
    async removeChildSharesPermissions(shareId, userPermissions, asParent = true) {
        // remove permissions of all members of the child shares
        if (!userPermissions.length) return;
        for (const userPerm of userPermissions){
            const members = await this.sharesQueries.membersFromChildSharesPermissions(shareId, userPerm.ids, userPerm.rmPermissions.join('|'), asParent);
            for (const m of members){
                const permissions = (0, _permissions.removePermissions)(m.userPermissions, userPerm.rmPermissions);
                try {
                    await this.sharesQueries.updateMember({
                        permissions: permissions
                    }, {
                        id: m.id
                    });
                    this.clearCachePermissionsAndOrNotify({
                        alias: m.shareAlias,
                        name: m.shareName
                    }, _constants.ACTION.UPDATE, [
                        m.userId
                    ]).catch((e)=>this.logger.error(`${this.removeChildSharesPermissions.name} - ${e}`));
                    this.logger.log(`${this.removeChildSharesPermissions.name} - user (${m.id}) permissions ${JSON.stringify(userPerm.rmPermissions)} on share : ${m.shareAlias} (${m.shareId}) was removed`);
                } catch (e) {
                    this.logger.error(`${this.removeChildSharesPermissions.name} - user (${m.id}) permissions on share *${m.shareAlias}* (${m.shareId}) was not removed : ${e}`);
                }
            }
        }
    }
    async checkChildSharePermissions(user, shareId, childId) {
        const isOwnerForChildShare = await this.sharesQueries.childExistsForShareOwner(user.id, shareId, childId, user.isAdmin);
        if (isOwnerForChildShare !== childId) {
            this.logger.warn(`${this.checkChildSharePermissions.name} - is not allowed to manage child share (${childId}) from share (${shareId})`);
            throw new _common.HttpException('Not authorized', _common.HttpStatus.FORBIDDEN);
        }
        return true;
    }
    async onShareActionForMembers(share, action, members, user) {
        this.clearCachePermissionsAndOrNotify(share, action, Array.from(new Set([
            ...await this.usersQueries.allUserIdsFromGroupsAndSubGroups(members.groupIds),
            ...members.userIds
        ])).filter((uid)=>uid !== user?.id), user).catch((e)=>this.logger.error(`${this.onShareActionForMembers.name} - ${e}`));
    }
    async createLinkFromSpaceOrShare(user, uuid, spaceOrShareId, type, createOrUpdateLinkDto) {
        /* only used during the share creation from this manager */ if (!await this.linksQueries.isReservedUUID(user.id, uuid)) {
            this.logger.error(`${this.createLinkFromSpaceOrShare.name} - user attempted to use UUID (${uuid}) was not reserved`);
            throw new _common.HttpException('UUID was not reserved', _common.HttpStatus.BAD_REQUEST);
        }
        const permission = type === _links.LINK_TYPE.SPACE ? _user.GUEST_PERMISSION.SPACES : _user.GUEST_PERMISSION.SHARES;
        const guestLink = await this.createGuestLink(permission, createOrUpdateLinkDto.password, createOrUpdateLinkDto.language, createOrUpdateLinkDto.isActive !== undefined ? createOrUpdateLinkDto.isActive : true);
        this.logger.debug(`${this.createLinkFromSpaceOrShare.name} - guest link (${guestLink.id}) created`);
        let linkId;
        try {
            linkId = await this.linksQueries.createLinkToSpaceOrShare(guestLink.id, spaceOrShareId, type, {
                ...createOrUpdateLinkDto,
                uuid: uuid,
                userId: guestLink.id
            });
            this.logger.debug(`${this.createLinkFromSpaceOrShare.name} - link (${linkId}) for guest link (${guestLink.id}) created : ${JSON.stringify(createOrUpdateLinkDto)}`);
        } catch (e) {
            this.logger.error(`${this.createLinkFromSpaceOrShare.name} - unable to create link with uuid (${uuid}) : ${e}`);
            throw new _common.HttpException('Unable to update link', _common.HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
    async deleteGuestLinks(guestLinks) {
        for (const guestLink of guestLinks){
            try {
                await this.usersQueries.deleteGuestLink(guestLink.id);
                this.logger.log(`${this.deleteGuestLinks.name} - guest (${guestLink.id}) (link: ${guestLink.linkId}) was removed`);
            } catch (e) {
                this.logger.error(`${this.deleteGuestLinks.name} - guest (${guestLink.id}) (link: ${guestLink.linkId}) was not removed : ${e}`);
            }
        }
    }
    /* MANAGE CACHE PERMISSIONS AND NOTIFY */ async clearCachePermissionsAndOrNotify(share, action, memberIds, user) {
        if (!memberIds?.length) {
            return;
        }
        this.logger.verbose(`${this.clearCachePermissionsAndOrNotify.name} - share:${share.alias} ${action} members:${JSON.stringify(memberIds)}`);
        if (action !== _constants.ACTION.ADD) {
            // clear permissions for share members
            this.sharesQueries.clearCachePermissions(share.alias, memberIds).catch((e)=>this.logger.error(`${this.clearCachePermissionsAndOrNotify.name} - ${e}`));
        }
        if (action !== _constants.ACTION.UPDATE) {
            // notify the members who have joined or left the share
            const notification = {
                app: _notifications.NOTIFICATION_APP.SHARES,
                event: user ? _notifications.NOTIFICATION_APP_EVENT.SHARES[action] : _notifications.NOTIFICATION_APP_EVENT.SHARES_WITHOUT_OWNER[action],
                element: share.name,
                url: _spaces.SPACE_REPOSITORY.SHARES
            };
            this.notificationsManager.create(memberIds, notification, {
                currentUrl: this.contextManager.get('headerOriginUrl'),
                author: user,
                action: action
            }).catch((e)=>this.logger.error(`${this.clearCachePermissionsAndOrNotify.name} - ${e}`));
        }
    }
    async notifyGuestLink(user, link, spaceOrShareName, action) {
        if (!link.linkSettings.email) {
            return;
        }
        this.notificationsManager.sendEmailNotification([
            {
                id: -1,
                email: link.linkSettings.email,
                language: link.linkSettings.language,
                notification: _user.USER_NOTIFICATION.APPLICATION_EMAIL
            }
        ], {
            app: _notifications.NOTIFICATION_APP.LINKS,
            event: _notifications.NOTIFICATION_APP_EVENT.LINKS[action],
            element: spaceOrShareName,
            url: null
        }, {
            author: user,
            linkUUID: link.linkSettings.uuid,
            currentUrl: this.contextManager.get('headerOriginUrl'),
            action: action
        }).catch((e)=>this.logger.error(`${this.notifyGuestLink.name} - ${e}`));
    }
    constructor(contextManager, notificationsManager, sharesQueries, spaceQueries, usersQueries, linksQueries){
        this.contextManager = contextManager;
        this.notificationsManager = notificationsManager;
        this.sharesQueries = sharesQueries;
        this.spaceQueries = spaceQueries;
        this.usersQueries = usersQueries;
        this.linksQueries = linksQueries;
        this.logger = new _common.Logger(SharesManager.name);
    }
};
SharesManager = _ts_decorate([
    (0, _common.Injectable)(),
    _ts_metadata("design:type", Function),
    _ts_metadata("design:paramtypes", [
        typeof _contextmanagerservice.ContextManager === "undefined" ? Object : _contextmanagerservice.ContextManager,
        typeof _notificationsmanagerservice.NotificationsManager === "undefined" ? Object : _notificationsmanagerservice.NotificationsManager,
        typeof _sharesqueriesservice.SharesQueries === "undefined" ? Object : _sharesqueriesservice.SharesQueries,
        typeof _spacesqueriesservice.SpacesQueries === "undefined" ? Object : _spacesqueriesservice.SpacesQueries,
        typeof _usersqueriesservice.UsersQueries === "undefined" ? Object : _usersqueriesservice.UsersQueries,
        typeof _linksqueriesservice.LinksQueries === "undefined" ? Object : _linksqueriesservice.LinksQueries
    ])
], SharesManager);

//# sourceMappingURL=shares-manager.service.js.map