@internxt/cli 1.6.4 → 1.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +94 -40
  2. package/dist/commands/add-cert.d.ts +4 -1
  3. package/dist/commands/add-cert.js +4 -1
  4. package/dist/commands/config.js +1 -0
  5. package/dist/commands/create-folder.js +1 -0
  6. package/dist/commands/delete-permanently-file.js +1 -0
  7. package/dist/commands/delete-permanently-folder.js +1 -0
  8. package/dist/commands/download-file.js +1 -0
  9. package/dist/commands/list.js +1 -0
  10. package/dist/commands/login-legacy.js +3 -0
  11. package/dist/commands/login.d.ts +2 -0
  12. package/dist/commands/login.js +4 -0
  13. package/dist/commands/logout.d.ts +4 -1
  14. package/dist/commands/logout.js +4 -1
  15. package/dist/commands/logs.d.ts +4 -1
  16. package/dist/commands/logs.js +4 -1
  17. package/dist/commands/move-file.js +1 -0
  18. package/dist/commands/move-folder.js +1 -0
  19. package/dist/commands/rename-file.js +1 -0
  20. package/dist/commands/rename-folder.js +1 -0
  21. package/dist/commands/trash-clear.js +1 -0
  22. package/dist/commands/trash-file.js +1 -0
  23. package/dist/commands/trash-folder.js +1 -0
  24. package/dist/commands/trash-list.d.ts +2 -0
  25. package/dist/commands/trash-list.js +2 -0
  26. package/dist/commands/trash-restore-file.js +1 -0
  27. package/dist/commands/trash-restore-folder.js +1 -0
  28. package/dist/commands/upload-file.js +16 -26
  29. package/dist/commands/upload-folder.js +1 -0
  30. package/dist/commands/webdav-config.js +1 -0
  31. package/dist/commands/webdav.d.ts +4 -1
  32. package/dist/commands/webdav.js +4 -1
  33. package/dist/commands/whoami.d.ts +4 -1
  34. package/dist/commands/whoami.js +4 -1
  35. package/dist/commands/workspaces-list.js +1 -0
  36. package/dist/commands/workspaces-unset.js +1 -0
  37. package/dist/commands/workspaces-use.js +1 -0
  38. package/dist/hooks/prerun/auth_check.js +2 -0
  39. package/dist/services/auth.service.js +10 -1
  40. package/dist/services/cache.service.d.ts +13 -0
  41. package/dist/services/cache.service.js +36 -0
  42. package/dist/services/config.service.js +19 -3
  43. package/dist/services/database/database.service.d.ts +1 -1
  44. package/dist/services/database/database.service.js +6 -5
  45. package/dist/services/database/drive-item/drive-item.attributes.d.ts +7 -0
  46. package/dist/services/database/drive-item/drive-item.domain.d.ts +10 -0
  47. package/dist/services/database/drive-item/drive-item.domain.js +27 -0
  48. package/dist/services/database/drive-item/drive-item.model.d.ts +8 -0
  49. package/dist/services/database/{drive-folder/drive-folder.model.js → drive-item/drive-item.model.js} +11 -23
  50. package/dist/services/database/drive-item/drive-item.repository.d.ts +12 -0
  51. package/dist/services/database/drive-item/drive-item.repository.js +88 -0
  52. package/dist/services/drive/drive-file.service.d.ts +2 -3
  53. package/dist/services/drive/drive-file.service.js +9 -34
  54. package/dist/services/drive/drive-folder.service.d.ts +1 -3
  55. package/dist/services/drive/drive-folder.service.js +9 -87
  56. package/dist/services/drive/drive-item.service.d.ts +8 -0
  57. package/dist/services/drive/drive-item.service.js +98 -0
  58. package/dist/services/network/network-facade.service.d.ts +1 -3
  59. package/dist/services/network/network-facade.service.js +5 -8
  60. package/dist/services/network/upload/upload-file.service.js +2 -1
  61. package/dist/services/thumbnail.service.js +2 -2
  62. package/dist/services/usage.service.d.ts +2 -0
  63. package/dist/services/usage.service.js +18 -0
  64. package/dist/utils/cli.utils.d.ts +5 -3
  65. package/dist/utils/cli.utils.js +36 -18
  66. package/dist/utils/format.utils.js +3 -2
  67. package/dist/utils/upload.utils.d.ts +19 -0
  68. package/dist/utils/upload.utils.js +40 -0
  69. package/dist/utils/webdav.utils.d.ts +4 -2
  70. package/dist/utils/webdav.utils.js +14 -18
  71. package/dist/utils/xml.utils.js +5 -1
  72. package/dist/webdav/handlers/DELETE.handler.js +2 -0
  73. package/dist/webdav/handlers/LOCK.handler.d.ts +5 -0
  74. package/dist/webdav/handlers/LOCK.handler.js +50 -0
  75. package/dist/webdav/handlers/MKCOL.handler.js +15 -3
  76. package/dist/webdav/handlers/MOVE.handler.js +11 -0
  77. package/dist/webdav/handlers/OPTIONS.handler.js +4 -4
  78. package/dist/webdav/handlers/PROPFIND.handler.js +30 -9
  79. package/dist/webdav/handlers/PUT.handler.js +44 -40
  80. package/dist/webdav/handlers/UNLOCK.handler.d.ts +5 -0
  81. package/dist/webdav/handlers/UNLOCK.handler.js +12 -0
  82. package/dist/webdav/middewares/auth.middleware.js +9 -2
  83. package/dist/webdav/middewares/errors.middleware.js +2 -0
  84. package/dist/webdav/middewares/request-logger.middleware.js +10 -6
  85. package/dist/webdav/middewares/webdav-auth.middleware.js +0 -3
  86. package/dist/webdav/webdav-server.js +4 -0
  87. package/oclif.manifest.json +134 -1
  88. package/package.json +47 -46
  89. package/dist/services/database/drive-file/drive-file.attributes.d.ts +0 -14
  90. package/dist/services/database/drive-file/drive-file.domain.d.ts +0 -20
  91. package/dist/services/database/drive-file/drive-file.domain.js +0 -68
  92. package/dist/services/database/drive-file/drive-file.model.d.ts +0 -15
  93. package/dist/services/database/drive-file/drive-file.model.js +0 -67
  94. package/dist/services/database/drive-file/drive-file.repository.d.ts +0 -11
  95. package/dist/services/database/drive-file/drive-file.repository.js +0 -63
  96. package/dist/services/database/drive-folder/drive-folder.attributes.d.ts +0 -10
  97. package/dist/services/database/drive-folder/drive-folder.attributes.js +0 -2
  98. package/dist/services/database/drive-folder/drive-folder.domain.d.ts +0 -16
  99. package/dist/services/database/drive-folder/drive-folder.domain.js +0 -53
  100. package/dist/services/database/drive-folder/drive-folder.model.d.ts +0 -11
  101. package/dist/services/database/drive-folder/drive-folder.repository.d.ts +0 -13
  102. package/dist/services/database/drive-folder/drive-folder.repository.js +0 -99
  103. package/dist/utils/database.utils.d.ts +0 -11
  104. package/dist/utils/database.utils.js +0 -26
  105. /package/dist/services/database/{drive-file/drive-file.attributes.js → drive-item/drive-item.attributes.js} +0 -0
@@ -175,17 +175,6 @@ class CLIUtils {
175
175
  const msFormated = ms.toString().padStart(3, '0');
176
176
  return `${hoursFormated}:${minutesFormated}:${secondsFormated}.${msFormated}`;
177
177
  };
178
- static formatBytesToString = (bytes) => {
179
- if (bytes <= 0) {
180
- return '0.00 KB';
181
- }
182
- const kb = bytes / 1024;
183
- if (kb < 1024) {
184
- return `${kb.toFixed(2)} KB`;
185
- }
186
- const mb = kb / 1024;
187
- return `${mb.toFixed(2)} MB`;
188
- };
189
178
  static calculateThroughputMBps = (bytes, milliseconds) => {
190
179
  if (bytes <= 0 || milliseconds <= 0) {
191
180
  return 0;
@@ -194,21 +183,50 @@ class CLIUtils {
194
183
  const seconds = milliseconds / 1000;
195
184
  return megabytes / seconds;
196
185
  };
197
- static catchError = ({ error, logReporter, command, jsonFlag, }) => {
198
- let message;
199
- if ('message' in error && error.message.trim().length > 0) {
186
+ static catchError = ({ error, logReporter, command, jsonFlag, debugMode, }) => {
187
+ let message = '';
188
+ let requestId;
189
+ if ('requestId' in error) {
190
+ requestId = error.requestId;
191
+ }
192
+ else if ('xRequestId' in error) {
193
+ requestId = error.xRequestId;
194
+ }
195
+ if ('message' in error && typeof error.message === 'string' && error.message?.trim?.().length > 0) {
200
196
  message = error.message;
201
197
  }
202
- else {
198
+ if ('data' in error) {
199
+ const errorData = error.data;
200
+ if (typeof errorData.message === 'string' && errorData.message?.trim?.().length > 0) {
201
+ message += ' [' + errorData.message + ']';
202
+ }
203
+ else if (Array.isArray(errorData.message) && errorData.message.length > 0) {
204
+ message += ' [' + errorData.message.join(', ') + ']';
205
+ }
206
+ }
207
+ if (message.length === 0) {
203
208
  message = JSON.stringify(error);
204
209
  }
205
210
  CLIUtils.failed(jsonFlag);
211
+ const jsonLog = {
212
+ success: false,
213
+ message,
214
+ command,
215
+ };
216
+ if (requestId) {
217
+ jsonLog.requestId = requestId;
218
+ }
219
+ if (command) {
220
+ jsonLog.command = command;
221
+ }
206
222
  if (jsonFlag) {
207
- CLIUtils.consoleLog(JSON.stringify({ success: false, message }));
223
+ CLIUtils.consoleLog(JSON.stringify(jsonLog));
208
224
  }
209
225
  else {
210
- errors_utils_1.ErrorUtils.report(error, { command });
211
- CLIUtils.error(logReporter, message);
226
+ if (debugMode) {
227
+ errors_utils_1.ErrorUtils.report(error);
228
+ }
229
+ CLIUtils.error(logReporter, message + (requestId ? ` (requestId: ${requestId})` : ''));
212
230
  }
213
231
  };
214
232
  static parseEmpty = async (input) => (input.trim().length === 0 ? ' ' : input);
@@ -10,8 +10,9 @@ const usage_service_1 = require("../services/usage.service");
10
10
  dayjs_1.default.extend(utc_1.default);
11
11
  class FormatUtils {
12
12
  static humanFileSize = (size) => {
13
- const i = size == 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
14
- return Number((size / Math.pow(1024, i)).toFixed(2)) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
13
+ const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
14
+ const unitIndex = size === 0 ? 0 : Math.min(Math.floor(Math.log(size) / Math.log(1024)), units.length - 1);
15
+ return Number((size / Math.pow(1024, unitIndex)).toFixed(2)) + ' ' + units[unitIndex];
15
16
  };
16
17
  static formatDate = (date) => {
17
18
  return (0, dayjs_1.default)(date).format('D MMMM, YYYY [at] HH:mm');
@@ -0,0 +1,19 @@
1
+ import { Readable } from 'node:stream';
2
+ import { BufferStream } from './stream.utils';
3
+ export declare class UploadUtils {
4
+ static readonly checkUploadSizeLimits: (size: number) => Promise<void>;
5
+ static readonly prepareUploadStreams: (readable: Readable, fileType: string) => {
6
+ fileStream: Readable;
7
+ thumbnailStream: BufferStream | undefined;
8
+ isThumbnailable: boolean;
9
+ };
10
+ static readonly getTimings: (size: number, timings: {
11
+ networkUpload: number;
12
+ driveUpload: number;
13
+ thumbnailUpload: number;
14
+ }) => {
15
+ totalTime: number;
16
+ throughputMBps: number;
17
+ timingBreakdown: string;
18
+ };
19
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UploadUtils = void 0;
4
+ const usage_service_1 = require("../services/usage.service");
5
+ const format_utils_1 = require("./format.utils");
6
+ const stream_utils_1 = require("./stream.utils");
7
+ const thumbnail_utils_1 = require("./thumbnail.utils");
8
+ const cli_utils_1 = require("./cli.utils");
9
+ const DEFAULT_MAX_FILE_SIZE = 100 * 1024 * 1024 * 1024;
10
+ class UploadUtils {
11
+ static checkUploadSizeLimits = async (size) => {
12
+ const limits = await usage_service_1.UsageService.instance.fetchLimits();
13
+ if (limits?.maxUploadFileSize && size > limits.maxUploadFileSize) {
14
+ const formattedSize = format_utils_1.FormatUtils.humanFileSize(size);
15
+ const formattedLimit = format_utils_1.FormatUtils.humanFileSize(limits.maxUploadFileSize);
16
+ throw new Error(`File is too big (${formattedSize} exceeds account upload limit of ${formattedLimit})`);
17
+ }
18
+ if (size > DEFAULT_MAX_FILE_SIZE) {
19
+ throw new Error('File is too big (more than 100 GB)');
20
+ }
21
+ };
22
+ static prepareUploadStreams = (readable, fileType) => {
23
+ const isThumbnailable = thumbnail_utils_1.ThumbnailUtils.isFileThumbnailable(fileType);
24
+ if (!isThumbnailable) {
25
+ return { fileStream: readable, thumbnailStream: undefined, isThumbnailable };
26
+ }
27
+ const bufferStream = new stream_utils_1.BufferStream();
28
+ const fileStream = readable.pipe(bufferStream);
29
+ return { fileStream, thumbnailStream: bufferStream, isThumbnailable };
30
+ };
31
+ static getTimings = (size, timings) => {
32
+ const totalTime = Object.values(timings).reduce((sum, time) => sum + time, 0);
33
+ const throughputMBps = cli_utils_1.CLIUtils.calculateThroughputMBps(size, timings.networkUpload);
34
+ const timingBreakdown = `Network upload: ${cli_utils_1.CLIUtils.formatDuration(timings.networkUpload)} (${throughputMBps.toFixed(2)} MB/s)\n` +
35
+ `Drive upload: ${cli_utils_1.CLIUtils.formatDuration(timings.driveUpload)}\n` +
36
+ `Thumbnail: ${cli_utils_1.CLIUtils.formatDuration(timings.thumbnailUpload)}\n`;
37
+ return { totalTime, throughputMBps, timingBreakdown };
38
+ };
39
+ }
40
+ exports.UploadUtils = UploadUtils;
@@ -6,9 +6,11 @@ export declare class WebDavUtils {
6
6
  static decodeUrl(requestUrl: string, decodeUri?: boolean): string;
7
7
  static normalizeFolderPath(path: string): string;
8
8
  static getRequestedResource(requestUrl: string, decodeUri?: boolean): Promise<WebDavRequestedResource>;
9
- static tryGetFileOrFolderMetadata(url: string): Promise<DriveItem | undefined>;
10
9
  static getDriveFileFromResource(url: string): Promise<DriveFileItem | undefined>;
11
10
  static getDriveFolderFromResource(url: string): Promise<DriveFolderItem | undefined>;
12
11
  static getDriveItemFromResource(resource: WebDavRequestedResource): Promise<DriveItem | undefined>;
13
- static deleteOrTrashItem(driveItem: DriveItem): Promise<void>;
12
+ static deleteOrTrashItem<T extends {
13
+ itemType: 'file' | 'folder';
14
+ uuid: string;
15
+ }>(driveItem: T): Promise<void>;
14
16
  }
@@ -5,12 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.WebDavUtils = void 0;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
- const drive_folder_service_1 = require("../services/drive/drive-folder.service");
9
- const drive_file_service_1 = require("../services/drive/drive-file.service");
8
+ const drive_item_service_1 = require("../services/drive/drive-item.service");
10
9
  const logger_utils_1 = require("./logger.utils");
11
10
  const config_service_1 = require("../services/config.service");
12
11
  const trash_service_1 = require("../services/drive/trash.service");
13
12
  const format_utils_1 = require("./format.utils");
13
+ const drive_item_repository_1 = require("../services/database/drive-item/drive-item.repository");
14
14
  class WebDavUtils {
15
15
  static joinURL(...pathComponents) {
16
16
  return node_path_1.default.posix.join(...pathComponents);
@@ -49,28 +49,18 @@ class WebDavUtils {
49
49
  parentPath,
50
50
  };
51
51
  }
52
- static async tryGetFileOrFolderMetadata(url) {
53
- try {
54
- return await drive_file_service_1.DriveFileService.instance.getFileMetadataByPath(url);
55
- }
56
- catch {
57
- return await drive_folder_service_1.DriveFolderService.instance.getFolderMetadataByPath(url);
58
- }
59
- }
60
52
  static async getDriveFileFromResource(url) {
61
53
  try {
62
- return await drive_file_service_1.DriveFileService.instance.getFileMetadataByPath(url);
54
+ return await drive_item_service_1.DriveItemService.instance.getFileByPath(url);
63
55
  }
64
- catch (err) {
65
- logger_utils_1.webdavLogger.error('Exception while getting the file metadata by path', err);
56
+ catch {
66
57
  }
67
58
  }
68
59
  static async getDriveFolderFromResource(url) {
69
60
  try {
70
- return await drive_folder_service_1.DriveFolderService.instance.getFolderMetadataByPath(url);
61
+ return await drive_item_service_1.DriveItemService.instance.getFolderByPath(url);
71
62
  }
72
- catch (err) {
73
- logger_utils_1.webdavLogger.error('Exception while getting the folder metadata by path', err);
63
+ catch {
74
64
  }
75
65
  }
76
66
  static async getDriveItemFromResource(resource) {
@@ -78,10 +68,15 @@ class WebDavUtils {
78
68
  const isFolder = resource.url.endsWith('/');
79
69
  try {
80
70
  if (isFolder) {
81
- item = await drive_folder_service_1.DriveFolderService.instance.getFolderMetadataByPath(resource.url);
71
+ item = await drive_item_service_1.DriveItemService.instance.getFolderByPath(resource.url);
82
72
  }
83
73
  else {
84
- item = await this.tryGetFileOrFolderMetadata(resource.url);
74
+ try {
75
+ item = await drive_item_service_1.DriveItemService.instance.getFileByPath(resource.url);
76
+ }
77
+ catch {
78
+ item = await drive_item_service_1.DriveItemService.instance.getFolderByPath(resource.url);
79
+ }
85
80
  }
86
81
  }
87
82
  catch {
@@ -103,6 +98,7 @@ class WebDavUtils {
103
98
  });
104
99
  logger_utils_1.webdavLogger.info(`[DELETE] [${driveItem.uuid}] ${type} trashed successfully`);
105
100
  }
101
+ await drive_item_repository_1.DriveItemRepository.instance.delete([driveItem.uuid]);
106
102
  }
107
103
  }
108
104
  exports.WebDavUtils = WebDavUtils;
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.XMLUtils = void 0;
4
7
  const fast_xml_parser_1 = require("fast-xml-parser");
8
+ const fast_xml_builder_1 = __importDefault(require("fast-xml-builder"));
5
9
  class XMLUtils {
6
10
  static DEFAULT_NAMESPACE_LETTER = 'D';
7
11
  static toJSON(xml, options = {}) {
@@ -9,7 +13,7 @@ class XMLUtils {
9
13
  return parser.parse(xml);
10
14
  }
11
15
  static toXML(object, options) {
12
- const builder = new fast_xml_parser_1.XMLBuilder(options);
16
+ const builder = new fast_xml_builder_1.default(options);
13
17
  return builder.build(object);
14
18
  }
15
19
  static toWebDavXML(object, options, rootObject = 'multistatus') {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DELETERequestHandler = void 0;
4
+ const drive_item_repository_1 = require("../../services/database/drive-item/drive-item.repository");
4
5
  const webdav_utils_1 = require("../../utils/webdav.utils");
5
6
  const logger_utils_1 = require("../../utils/logger.utils");
6
7
  const errors_utils_1 = require("../../utils/errors.utils");
@@ -13,6 +14,7 @@ class DELETERequestHandler {
13
14
  throw new errors_utils_1.NotFoundError(`Resource not found on Internxt Drive at ${resource.url}`);
14
15
  }
15
16
  await webdav_utils_1.WebDavUtils.deleteOrTrashItem(driveItem);
17
+ await drive_item_repository_1.DriveItemRepository.instance.delete([driveItem.uuid]);
16
18
  res.status(204).send();
17
19
  };
18
20
  }
@@ -0,0 +1,5 @@
1
+ import { Request, Response } from 'express';
2
+ import { WebDavMethodHandler } from '../../types/webdav.types';
3
+ export declare class LOCKRequestHandler implements WebDavMethodHandler {
4
+ handle: (req: Request, res: Response) => Promise<void>;
5
+ }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LOCKRequestHandler = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const logger_utils_1 = require("../../utils/logger.utils");
6
+ const xml_utils_1 = require("../../utils/xml.utils");
7
+ const LOCK_TIMEOUT_SECONDS = 300;
8
+ class LOCKRequestHandler {
9
+ handle = async (req, res) => {
10
+ const lockToken = `opaquelocktoken:${(0, node_crypto_1.randomUUID)()}`;
11
+ logger_utils_1.webdavLogger.info(`[LOCK] Granted lock token ${lockToken} for ${req.url}`);
12
+ const depth = req.headers['depth'] ?? '0';
13
+ const timeout = req.headers['timeout'] ?? `Second-${LOCK_TIMEOUT_SECONDS}`;
14
+ let owner;
15
+ try {
16
+ const parsed = xml_utils_1.XMLUtils.toJSON(req.body, { ignoreAttributes: false });
17
+ const maybeOwner = parsed?.['D:lockinfo']?.['D:owner'];
18
+ if (maybeOwner != null) {
19
+ owner = { [xml_utils_1.XMLUtils.addDefaultNamespace('owner')]: maybeOwner };
20
+ }
21
+ }
22
+ catch {
23
+ }
24
+ const lockDiscoveryXml = xml_utils_1.XMLUtils.toWebDavXML({
25
+ [xml_utils_1.XMLUtils.addDefaultNamespace('lockdiscovery')]: {
26
+ [xml_utils_1.XMLUtils.addDefaultNamespace('activelock')]: {
27
+ [xml_utils_1.XMLUtils.addDefaultNamespace('locktype')]: {
28
+ [xml_utils_1.XMLUtils.addDefaultNamespace('write')]: '',
29
+ },
30
+ [xml_utils_1.XMLUtils.addDefaultNamespace('lockscope')]: {
31
+ [xml_utils_1.XMLUtils.addDefaultNamespace('exclusive')]: '',
32
+ },
33
+ [xml_utils_1.XMLUtils.addDefaultNamespace('depth')]: depth,
34
+ [xml_utils_1.XMLUtils.addDefaultNamespace('timeout')]: timeout,
35
+ [xml_utils_1.XMLUtils.addDefaultNamespace('locktoken')]: {
36
+ [xml_utils_1.XMLUtils.addDefaultNamespace('href')]: lockToken,
37
+ },
38
+ [xml_utils_1.XMLUtils.addDefaultNamespace('lockroot')]: {
39
+ [xml_utils_1.XMLUtils.addDefaultNamespace('href')]: req.url,
40
+ },
41
+ ...owner,
42
+ },
43
+ },
44
+ }, { suppressEmptyNode: true }, 'prop');
45
+ res.set('Lock-Token', `<${lockToken}>`);
46
+ res.set('Content-Type', 'application/xml; charset="utf-8"');
47
+ res.status(200).send(lockDiscoveryXml);
48
+ };
49
+ }
50
+ exports.LOCKRequestHandler = LOCKRequestHandler;
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MKCOLRequestHandler = void 0;
4
+ const drive_item_repository_1 = require("../../services/database/drive-item/drive-item.repository");
4
5
  const webdav_utils_1 = require("../../utils/webdav.utils");
5
6
  const logger_utils_1 = require("../../utils/logger.utils");
6
7
  const xml_utils_1 = require("../../utils/xml.utils");
7
8
  const webdav_folder_service_1 = require("../../services/webdav/webdav-folder.service");
8
- const errors_utils_1 = require("../../utils/errors.utils");
9
+ const async_utils_1 = require("../../utils/async.utils");
9
10
  class MKCOLRequestHandler {
10
11
  handle = async (req, res) => {
11
12
  const resource = await webdav_utils_1.WebDavUtils.getRequestedResource(req.url);
@@ -15,14 +16,25 @@ class MKCOLRequestHandler {
15
16
  const driveFolderItem = await webdav_utils_1.WebDavUtils.getDriveFolderFromResource(resource.url);
16
17
  const folderAlreadyExists = !!driveFolderItem;
17
18
  if (folderAlreadyExists) {
18
- logger_utils_1.webdavLogger.info(`[MKCOL] ❌ Folder '${resource.url}' already exists`);
19
- throw new errors_utils_1.MethodNotAllowed('Folder already exists');
19
+ logger_utils_1.webdavLogger.info(`[MKCOL] Folder '${resource.url}' already exists, ignoring the creation request`);
20
+ res.status(200).send(xml_utils_1.XMLUtils.toWebDavXML({}, {}));
21
+ return;
20
22
  }
21
23
  const newFolder = await webdav_folder_service_1.WebDavFolderService.instance.createFolder({
22
24
  folderName: resource.path.base,
23
25
  parentFolderUuid: parentDriveFolderItem.uuid,
24
26
  });
25
27
  logger_utils_1.webdavLogger.info(`[MKCOL] ✅ Folder created with UUID ${newFolder.uuid}`);
28
+ await drive_item_repository_1.DriveItemRepository.instance.createOrUpdate([
29
+ {
30
+ uuid: newFolder.uuid,
31
+ path: resource.url,
32
+ type: 'folder',
33
+ createdAt: newFolder.createdAt,
34
+ updatedAt: new Date(),
35
+ },
36
+ ]);
37
+ await async_utils_1.AsyncUtils.sleep(500);
26
38
  res.status(201).send(xml_utils_1.XMLUtils.toWebDavXML({}, {}));
27
39
  };
28
40
  }
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MOVERequestHandler = void 0;
4
4
  const drive_file_service_1 = require("../../services/drive/drive-file.service");
5
5
  const drive_folder_service_1 = require("../../services/drive/drive-folder.service");
6
+ const drive_item_repository_1 = require("../../services/database/drive-item/drive-item.repository");
6
7
  const errors_utils_1 = require("../../utils/errors.utils");
7
8
  const logger_utils_1 = require("../../utils/logger.utils");
8
9
  const webdav_utils_1 = require("../../utils/webdav.utils");
@@ -64,6 +65,16 @@ class MOVERequestHandler {
64
65
  });
65
66
  }
66
67
  }
68
+ await drive_item_repository_1.DriveItemRepository.instance.delete([originalDriveItem.uuid]);
69
+ await drive_item_repository_1.DriveItemRepository.instance.createOrUpdate([
70
+ {
71
+ uuid: originalDriveItem.uuid,
72
+ path: destinationResource.url,
73
+ type: originalDriveItem.itemType,
74
+ createdAt: new Date(originalDriveItem.createdAt),
75
+ updatedAt: new Date(),
76
+ },
77
+ ]);
67
78
  res.status(204).send();
68
79
  };
69
80
  }
@@ -8,21 +8,21 @@ class OPTIONSRequestHandler {
8
8
  const resource = await webdav_utils_1.WebDavUtils.getRequestedResource(req.url);
9
9
  logger_utils_1.webdavLogger.info(`[OPTIONS] Request received for item at ${resource.url}`);
10
10
  if (resource.url === '/' || resource.url === '') {
11
- const allowedMethods = 'DELETE, GET, HEAD, MKCOL, MOVE, OPTIONS, PROPFIND, PUT';
11
+ const allowedMethods = 'DELETE, GET, HEAD, LOCK, MKCOL, MOVE, OPTIONS, PROPFIND, PUT, UNLOCK';
12
12
  logger_utils_1.webdavLogger.info(`[OPTIONS] Returning Allowed Options: ${allowedMethods}`);
13
- res.header('Allow', 'DELETE, GET, HEAD, MKCOL, MOVE, OPTIONS, PROPFIND, PUT');
13
+ res.header('Allow', allowedMethods);
14
14
  res.header('DAV', '1, 2, ordered-collections');
15
15
  res.status(200).send();
16
16
  }
17
17
  else if (resource.url.endsWith('/')) {
18
- const allowedMethods = 'DELETE, HEAD, MKCOL, MOVE, OPTIONS, PROPFIND';
18
+ const allowedMethods = 'DELETE, HEAD, LOCK, MKCOL, MOVE, OPTIONS, PROPFIND, UNLOCK';
19
19
  logger_utils_1.webdavLogger.info(`[OPTIONS] Returning Allowed Options: ${allowedMethods}`);
20
20
  res.header('Allow', allowedMethods);
21
21
  res.header('DAV', '1, 2, ordered-collections');
22
22
  res.status(200).send();
23
23
  }
24
24
  else {
25
- const allowedMethods = 'DELETE, GET, HEAD, MOVE, OPTIONS, PROPFIND, PUT';
25
+ const allowedMethods = 'DELETE, GET, HEAD, LOCK, MOVE, OPTIONS, PROPFIND, PUT, UNLOCK';
26
26
  logger_utils_1.webdavLogger.info(`[OPTIONS] Returning Allowed Options: ${allowedMethods}`);
27
27
  res.header('Allow', allowedMethods);
28
28
  res.header('DAV', '1, 2, ordered-collections');
@@ -5,6 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.PROPFINDRequestHandler = void 0;
7
7
  const xml_utils_1 = require("../../utils/xml.utils");
8
+ const drive_item_domain_1 = require("../../services/database/drive-item/drive-item.domain");
9
+ const drive_item_repository_1 = require("../../services/database/drive-item/drive-item.repository");
8
10
  const drive_folder_service_1 = require("../../services/drive/drive-folder.service");
9
11
  const format_utils_1 = require("../../utils/format.utils");
10
12
  const node_crypto_1 = require("node:crypto");
@@ -76,9 +78,11 @@ class PROPFINDRequestHandler {
76
78
  };
77
79
  getFolderChildsXMLNode = async (relativePath, folderUuid) => {
78
80
  const folderContent = await drive_folder_service_1.DriveFolderService.instance.getFolderContent(folderUuid);
79
- const foldersXML = folderContent.folders.map((folder) => {
81
+ const xmlNodes = [];
82
+ const cachedItems = [];
83
+ for (const folder of folderContent.folders) {
80
84
  const folderRelativePath = webdav_utils_1.WebDavUtils.joinURL(relativePath, folder.plainName, '/');
81
- return this.driveFolderItemToXMLNode({
85
+ xmlNodes.push(this.driveFolderItemToXMLNode({
82
86
  itemType: 'folder',
83
87
  name: folder.plainName,
84
88
  bucket: folder.bucket,
@@ -89,11 +93,18 @@ class PROPFINDRequestHandler {
89
93
  modificationTime: new Date(folder.modificationTime),
90
94
  uuid: folder.uuid,
91
95
  parentUuid: folder.parentUuid,
92
- }, folderRelativePath);
93
- });
94
- const filesXML = folderContent.files.map((file) => {
96
+ }, folderRelativePath));
97
+ cachedItems.push(new drive_item_domain_1.DriveItemBD({
98
+ uuid: folder.uuid,
99
+ path: folderRelativePath,
100
+ type: 'folder',
101
+ createdAt: new Date(folder.createdAt),
102
+ updatedAt: new Date(),
103
+ }));
104
+ }
105
+ for (const file of folderContent.files) {
95
106
  const fileRelativePath = webdav_utils_1.WebDavUtils.joinURL(relativePath, file.type ? `${file.plainName}.${file.type}` : file.plainName);
96
- return this.driveFileItemToXMLNode({
107
+ xmlNodes.push(this.driveFileItemToXMLNode({
97
108
  itemType: 'file',
98
109
  name: file.plainName,
99
110
  bucket: file.bucket,
@@ -107,9 +118,19 @@ class PROPFINDRequestHandler {
107
118
  modificationTime: new Date(file.modificationTime),
108
119
  createdAt: new Date(file.createdAt),
109
120
  updatedAt: new Date(file.updatedAt),
110
- }, fileRelativePath);
111
- });
112
- return foldersXML.concat(filesXML);
121
+ }, fileRelativePath));
122
+ cachedItems.push(new drive_item_domain_1.DriveItemBD({
123
+ uuid: file.uuid,
124
+ path: fileRelativePath,
125
+ type: 'file',
126
+ createdAt: new Date(file.createdAt),
127
+ updatedAt: new Date(),
128
+ }));
129
+ }
130
+ if (cachedItems.length > 0) {
131
+ await drive_item_repository_1.DriveItemRepository.instance.createOrUpdate(cachedItems);
132
+ }
133
+ return xmlNodes;
113
134
  };
114
135
  driveFolderRootStatsToXMLNode = async (driveFolderItem, relativePath) => {
115
136
  const totalUsage = await usage_service_1.UsageService.instance.fetchUsage();
@@ -2,30 +2,27 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PUTRequestHandler = void 0;
4
4
  const drive_file_service_1 = require("../../services/drive/drive-file.service");
5
+ const drive_item_repository_1 = require("../../services/database/drive-item/drive-item.repository");
5
6
  const auth_service_1 = require("../../services/auth.service");
6
7
  const errors_utils_1 = require("../../utils/errors.utils");
7
8
  const webdav_utils_1 = require("../../utils/webdav.utils");
8
9
  const logger_utils_1 = require("../../utils/logger.utils");
9
10
  const types_1 = require("@internxt/sdk/dist/drive/storage/types");
10
11
  const cli_utils_1 = require("../../utils/cli.utils");
11
- const stream_utils_1 = require("../../utils/stream.utils");
12
12
  const webdav_folder_service_1 = require("../../services/webdav/webdav-folder.service");
13
- const async_utils_1 = require("../../utils/async.utils");
14
- const thumbnail_utils_1 = require("../../utils/thumbnail.utils");
15
13
  const thumbnail_service_1 = require("../../services/thumbnail.service");
14
+ const format_utils_1 = require("../../utils/format.utils");
15
+ const upload_utils_1 = require("../../utils/upload.utils");
16
16
  class PUTRequestHandler {
17
17
  handle = async (req, res) => {
18
18
  let contentLength = Number(req.headers['content-length']);
19
19
  if (!contentLength || Number.isNaN(contentLength) || contentLength <= 0) {
20
20
  contentLength = 0;
21
21
  }
22
+ await upload_utils_1.UploadUtils.checkUploadSizeLimits(contentLength);
22
23
  const resource = await webdav_utils_1.WebDavUtils.getRequestedResource(req.url);
23
- const driveFileItem = await webdav_utils_1.WebDavUtils.getDriveItemFromResource(resource);
24
- if (driveFileItem?.itemType === 'folder') {
25
- throw new errors_utils_1.NotFoundError('Folders cannot be created with PUT. Use MKCOL instead.');
26
- }
27
24
  logger_utils_1.webdavLogger.info(`[PUT] Request received for file at ${resource.url}`);
28
- logger_utils_1.webdavLogger.info(`[PUT] Uploading '${resource.name}' (${cli_utils_1.CLIUtils.formatBytesToString(contentLength)}) to '${resource.parentPath}'`);
25
+ logger_utils_1.webdavLogger.info(`[PUT] Uploading '${resource.name}' (${format_utils_1.FormatUtils.humanFileSize(contentLength)}) to '${resource.parentPath}'`);
29
26
  const timings = {
30
27
  networkUpload: 0,
31
28
  driveUpload: 0,
@@ -33,23 +30,25 @@ class PUTRequestHandler {
33
30
  };
34
31
  const parentDriveFolderItem = (await webdav_folder_service_1.WebDavFolderService.instance.getDriveFolderItemFromPath(resource.parentPath)) ??
35
32
  (await webdav_folder_service_1.WebDavFolderService.instance.createParentPathOrThrow(resource.parentPath));
36
- try {
37
- if (driveFileItem && driveFileItem.status === 'EXISTS') {
38
- logger_utils_1.webdavLogger.info(`[PUT] File '${resource.name}' already exists in '${resource.path.dir}', it will be replaced...`);
33
+ let isReplacement = false;
34
+ const driveFileItem = await webdav_utils_1.WebDavUtils.getDriveItemFromResource(resource);
35
+ if (driveFileItem && driveFileItem.status === 'EXISTS') {
36
+ if (driveFileItem.itemType === 'folder') {
37
+ logger_utils_1.webdavLogger.info('[PUT] ❌ A folder exists on the cloud with the same name.');
38
+ throw new errors_utils_1.ConflictError('A folder exists on the cloud with the same name');
39
+ }
40
+ isReplacement = true;
41
+ logger_utils_1.webdavLogger.info(`[PUT] File '${resource.name}' already exists in '${resource.path.dir}', it will be replaced...`);
42
+ try {
39
43
  await webdav_utils_1.WebDavUtils.deleteOrTrashItem(driveFileItem);
44
+ await drive_item_repository_1.DriveItemRepository.instance.delete([driveFileItem.uuid]);
45
+ }
46
+ catch {
40
47
  }
41
- }
42
- catch {
43
48
  }
44
49
  const { user } = await auth_service_1.AuthService.instance.getAuthDetails();
45
50
  const fileType = resource.path.ext.replace('.', '');
46
- let bufferStream;
47
- let fileStream = req;
48
- const isThumbnailable = thumbnail_utils_1.ThumbnailUtils.isFileThumbnailable(fileType);
49
- if (isThumbnailable) {
50
- bufferStream = new stream_utils_1.BufferStream();
51
- fileStream = req.pipe(bufferStream);
52
- }
51
+ const { fileStream, thumbnailStream } = upload_utils_1.UploadUtils.prepareUploadStreams(req, fileType);
53
52
  const { networkFacade, bucket } = await cli_utils_1.CLIUtils.prepareNetwork(user);
54
53
  let fileId;
55
54
  if (contentLength > 0) {
@@ -90,28 +89,33 @@ class PUTRequestHandler {
90
89
  encryptVersion: types_1.EncryptionVersion.Aes03,
91
90
  });
92
91
  timings.driveUpload = driveTimer.stop();
92
+ await drive_item_repository_1.DriveItemRepository.instance.createOrUpdate([
93
+ {
94
+ uuid: file.uuid,
95
+ path: resource.url,
96
+ type: 'file',
97
+ createdAt: file.createdAt,
98
+ updatedAt: new Date(),
99
+ },
100
+ ]);
93
101
  const thumbnailTimer = cli_utils_1.CLIUtils.timer();
94
- if (contentLength > 0 && isThumbnailable && bufferStream) {
95
- await thumbnail_service_1.ThumbnailService.instance.tryUploadThumbnail({
96
- fileUuid: file.uuid,
97
- bufferStream,
98
- fileType,
99
- bucket,
100
- networkFacade,
101
- size: contentLength,
102
- });
103
- }
102
+ await thumbnail_service_1.ThumbnailService.instance.tryUploadThumbnail({
103
+ fileUuid: file.uuid,
104
+ bufferStream: thumbnailStream,
105
+ fileType,
106
+ bucket,
107
+ networkFacade,
108
+ size: contentLength,
109
+ });
104
110
  timings.thumbnailUpload = thumbnailTimer.stop();
105
- const totalTime = Object.values(timings).reduce((sum, time) => sum + time, 0);
106
- const throughputMBps = cli_utils_1.CLIUtils.calculateThroughputMBps(contentLength, timings.networkUpload);
107
- logger_utils_1.webdavLogger.info(`[PUT] ✅ File uploaded in ${cli_utils_1.CLIUtils.formatDuration(totalTime)} to Internxt Drive`);
108
- logger_utils_1.webdavLogger.info('[PUT] Timing breakdown:\n' +
109
- `Network upload: ${cli_utils_1.CLIUtils.formatDuration(timings.networkUpload)} (${throughputMBps.toFixed(2)} MB/s)\n` +
110
- `Drive upload: ${cli_utils_1.CLIUtils.formatDuration(timings.driveUpload)}\n` +
111
- `Thumbnail: ${cli_utils_1.CLIUtils.formatDuration(timings.thumbnailUpload)}\n`);
112
- await async_utils_1.AsyncUtils.sleep(500);
113
- logger_utils_1.webdavLogger.info(`[PUT] [RESPONSE-201] ${resource.url} - Returning 201 Created after ${cli_utils_1.CLIUtils.formatDuration(totalTime)}`);
114
- res.status(201).send();
111
+ const { totalTime, timingBreakdown } = upload_utils_1.UploadUtils.getTimings(contentLength, timings);
112
+ logger_utils_1.webdavLogger.info(`[PUT] ✅ File uploaded in ${cli_utils_1.CLIUtils.formatDuration(totalTime)} to Internxt Drive\n` +
113
+ '[PUT] Timing breakdown:\n' +
114
+ timingBreakdown);
115
+ const statusCode = isReplacement ? 204 : 201;
116
+ logger_utils_1.webdavLogger.info(`[PUT] [RESPONSE-${statusCode}] ${resource.url} - Returning ${statusCode} ` +
117
+ `after ${cli_utils_1.CLIUtils.formatDuration(totalTime)}`);
118
+ res.status(statusCode).send();
115
119
  };
116
120
  }
117
121
  exports.PUTRequestHandler = PUTRequestHandler;
@@ -0,0 +1,5 @@
1
+ import { Request, Response } from 'express';
2
+ import { WebDavMethodHandler } from '../../types/webdav.types';
3
+ export declare class UNLOCKRequestHandler implements WebDavMethodHandler {
4
+ handle: (req: Request, res: Response) => Promise<void>;
5
+ }