UNPKG

@sync-in/server

Version:

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

290 lines (289 loc) 13.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, "SyncPathsManager", { enumerable: true, get: function() { return SyncPathsManager; } }); const _common = require("@nestjs/common"); const _constants = require("../../../common/constants"); const _shared = require("../../../common/shared"); const _contextmanagerservice = require("../../../infrastructure/context/services/context-manager.service"); const _filesqueriesservice = require("../../files/services/files-queries.service"); const _files = require("../../files/utils/files"); const _notifications = require("../../notifications/constants/notifications"); const _notificationsmanagerservice = require("../../notifications/services/notifications-manager.service"); const _spacesmanagerservice = require("../../spaces/services/spaces-manager.service"); const _permissions = require("../../spaces/utils/permissions"); const _sync = require("../constants/sync"); const _routes = require("../utils/routes"); const _syncqueriesservice = require("./sync-queries.service"); 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 SyncPathsManager = class SyncPathsManager { async createPath(req, syncPathDto) { if (!req.user.clientId) { throw new _common.HttpException('Client id is missing', _common.HttpStatus.BAD_REQUEST); } if (req.space.quotaIsExceeded) { throw new _common.HttpException('Space quota is exceeded', _common.HttpStatus.INSUFFICIENT_STORAGE); } if (!await (0, _files.isPathExists)(req.space.realPath)) { throw new _common.HttpException(`Remote path not found : ${syncPathDto.remotePath}`, _common.HttpStatus.NOT_FOUND); } if (!await (0, _files.isPathIsDir)(req.space.realPath)) { throw new _common.HttpException('Remote path must be a directory', _common.HttpStatus.BAD_REQUEST); } const client = await this.syncQueries.getClient(req.user.clientId, req.user.id); if (!client) { throw new _common.HttpException('Client not found', _common.HttpStatus.NOT_FOUND); } const syncDBProps = await this.getDBProps(req.space); // important : ensures the right remote path is used and stored syncPathDto.remotePath = req.params['*']; // add permissions (skip end point protection using getEnvPermission) syncPathDto.permissions = (0, _permissions.getEnvPermissions)(req.space, req.space.root); const pathId = await this.syncQueries.createPath(client.id, syncDBProps, syncPathDto); return { id: pathId, permissions: syncPathDto.permissions }; } async deletePath(user, pathId, clientId) { clientId = user.clientId || clientId; if (!clientId) { throw new _common.HttpException('Client id is missing', _common.HttpStatus.BAD_REQUEST); } if (!await this.syncQueries.clientExistsForOwner(user.id, clientId)) { throw new _common.HttpException('You are not allowed to do this action', _common.HttpStatus.FORBIDDEN); } try { await this.syncQueries.deletePath(clientId, pathId); } catch (e) { this.logger.error(`${this.deletePath.name} - ${e}`); throw new _common.HttpException('Unable to remove path', _common.HttpStatus.BAD_REQUEST); } } async updatePath(user, clientId, pathId, syncPathUpdateDto) { if (!await this.syncQueries.clientExistsForOwner(user.id, clientId)) { throw new _common.HttpException('You are not allowed to do this action', _common.HttpStatus.FORBIDDEN); } const syncPathSettings = await this.syncQueries.getPathSettings(clientId, pathId); if (!syncPathSettings) { throw new _common.HttpException('Sync path not found', _common.HttpStatus.NOT_FOUND); } // delete possible id delete syncPathUpdateDto.id; // update current path settings Object.assign(syncPathSettings, syncPathUpdateDto); syncPathSettings.timestamp = (0, _shared.currentTimeStamp)(); try { await this.syncQueries.updatePathSettings(clientId, pathId, syncPathSettings); } catch (e) { this.logger.error(`${this.updatePath.name} - ${e}`); throw new _common.HttpException('Unable to update path', _common.HttpStatus.INTERNAL_SERVER_ERROR); } finally{ // clear cache this.syncQueries.clearCachePathSettings(clientId, pathId); } return syncPathSettings; } async updatePaths(user, syncPathsDto) { /* Update the client or server paths */ if (!user.clientId) { throw new _common.HttpException('Client id is missing', _common.HttpStatus.BAD_REQUEST); } if (!await this.syncQueries.clientExistsForOwner(user.id, user.clientId)) { throw new _common.HttpException('You are not allowed to do this action', _common.HttpStatus.FORBIDDEN); } const clientPathIds = syncPathsDto.map((p)=>p.id); const serverPathIds = []; const clientDiff = { add: [], update: [], delete: [] }; const serverPaths = await this.syncQueries.getPaths(user.clientId); for (const serverPath of serverPaths){ if (!serverPath.remotePath) { continue; } let space; try { space = await this.spacesManager.spaceEnv(user, (0, _routes.SYNC_PATH_TO_SPACE_SEGMENTS)(serverPath.remotePath), true); } catch (e) { throw new _common.HttpException(e.message, _common.HttpStatus.BAD_REQUEST); } if (!space) { continue; } serverPathIds.push(serverPath.id); if (clientPathIds.indexOf(serverPath.id) === -1) { // path exists on server but not on client, add it to client clientDiff.add.push({ ...serverPath.settings, id: serverPath.id, remotePath: serverPath.remotePath, permissions: space.envPermissions }); continue; } // path exists on both server and client side const clientPath = syncPathsDto.find((p)=>p.id === serverPath.id); // remotePath and permissions settings are only managed by server const updateClientInfo = { ...serverPath.remotePath !== clientPath.remotePath && { remotePath: serverPath.remotePath }, ...space.envPermissions !== clientPath.permissions && { permissions: space.envPermissions } }; const clientNewer = clientPath.timestamp > serverPath.settings.timestamp; const serverNewer = clientPath.timestamp < serverPath.settings.timestamp; const hasUpdates = Object.keys(updateClientInfo).length > 0; let updatedSettings = { ...serverPath.settings, ...updateClientInfo }; if (clientNewer) { updatedSettings = { ...clientPath, ...updateClientInfo }; } else if (serverNewer) { clientDiff.update.push({ id: clientPath.id, ...serverPath.settings, ...updateClientInfo }); } if (clientNewer || hasUpdates || serverPath.settings.lastSync !== clientPath.lastSync) { this.syncQueries.updatePathSettings(user.clientId, clientPath.id, { ...updatedSettings, lastSync: clientPath.lastSync }).catch((e)=>this.logger.error(`${this.updatePaths.name} - ${e}`)); } if (!clientNewer && hasUpdates) { clientDiff.update.push({ id: clientPath.id, ...updateClientInfo }); } } // path does not exist on server side clientDiff.delete = clientPathIds.filter((cid)=>serverPathIds.indexOf(cid) === -1); for (const cPathId of clientDiff.delete){ const cPath = syncPathsDto.find((p)=>p.id === cPathId); this.notify(user.id, _constants.ACTION.DELETE, cPath.remotePath).catch((e)=>this.logger.error(`${this.updatePaths.name} - ${e}`)); } // clear cache clientDiff.update.forEach((path)=>this.syncQueries.clearCachePathSettings(user.clientId, path.id)); return clientDiff; } async getDBProps(space) { if (space.inSharesList) { throw new _common.HttpException('Sync all shares is not supported, you must select a sub-directory', _common.HttpStatus.BAD_REQUEST); } else if (space.inPersonalSpace) { if (space.paths.length) { return { ownerId: space.dbFile.ownerId, fileId: await this.getOrCreateFileId(space) }; } else { return { ownerId: space.dbFile.ownerId }; } } else if (space.inFilesRepository) { if (!space?.root?.alias) { // The synchronization direction should be adapted for each root depending on the permissions, this is not yet supported throw new _common.HttpException('Sync all space is not yet supported, you must select a sub-directory', _common.HttpStatus.BAD_REQUEST); } if (space.root.id && !space.paths.length) { return { spaceId: space.id, spaceRootId: space.root.id }; } else { return { spaceId: space.id, spaceRootId: space?.root?.id || null, fileId: await this.getOrCreateFileId(space) }; } } else if (space.inSharesRepository) { if (space.paths.length) { return { shareId: space.id, fileId: await this.getOrCreateFileId(space) }; } else { return { shareId: space.id }; } } } async getOrCreateFileId(space) { const fileProps = await (0, _files.getProps)(space.realPath, space.dbFile.path); let fileId = await this.filesQueries.getSpaceFileId(fileProps, space.dbFile); if (!fileId) { fileId = await this.filesQueries.getOrCreateSpaceFile(fileId, { ...fileProps, id: undefined }, space.dbFile); } return fileId; } async notify(userId, action, remotePath) { const notification = { app: _notifications.NOTIFICATION_APP.SYNC, event: _notifications.NOTIFICATION_APP_EVENT.SYNC[action], element: remotePath, url: [ ..._sync.SYNC_PATH_REPOSITORY[remotePath.split('/').at(0)], ...remotePath.split('/').slice(1, -1) ].join('/') }; this.notificationsManager.create([ userId ], notification, { currentUrl: this.contextManager.get('headerOriginUrl'), action: action }).catch((e)=>this.logger.error(`${this.notify.name} - ${e}`)); } constructor(contextManager, spacesManager, filesQueries, syncQueries, notificationsManager){ this.contextManager = contextManager; this.spacesManager = spacesManager; this.filesQueries = filesQueries; this.syncQueries = syncQueries; this.notificationsManager = notificationsManager; this.logger = new _common.Logger(SyncPathsManager.name); } }; SyncPathsManager = _ts_decorate([ (0, _common.Injectable)(), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", [ typeof _contextmanagerservice.ContextManager === "undefined" ? Object : _contextmanagerservice.ContextManager, typeof _spacesmanagerservice.SpacesManager === "undefined" ? Object : _spacesmanagerservice.SpacesManager, typeof _filesqueriesservice.FilesQueries === "undefined" ? Object : _filesqueriesservice.FilesQueries, typeof _syncqueriesservice.SyncQueries === "undefined" ? Object : _syncqueriesservice.SyncQueries, typeof _notificationsmanagerservice.NotificationsManager === "undefined" ? Object : _notificationsmanagerservice.NotificationsManager ]) ], SyncPathsManager); //# sourceMappingURL=sync-paths-manager.service.js.map