@sync-in/server
Version:
The secure, open-source platform for file storage, sharing, collaboration, and sync
290 lines (289 loc) • 13.8 kB
JavaScript
/*
* 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