@internxt/cli 1.6.3 → 1.6.5
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.
- package/README.md +32 -32
- package/dist/commands/login-legacy.js +2 -0
- package/dist/commands/login.js +2 -0
- package/dist/commands/upload-file.js +25 -36
- package/dist/services/auth.service.js +10 -1
- package/dist/services/cache.service.d.ts +13 -0
- package/dist/services/cache.service.js +36 -0
- package/dist/services/config.service.js +19 -3
- package/dist/services/database/database.service.d.ts +1 -1
- package/dist/services/database/database.service.js +6 -5
- package/dist/services/database/drive-item/drive-item.attributes.d.ts +7 -0
- package/dist/services/database/drive-item/drive-item.domain.d.ts +10 -0
- package/dist/services/database/drive-item/drive-item.domain.js +27 -0
- package/dist/services/database/drive-item/drive-item.model.d.ts +8 -0
- package/dist/services/database/{drive-folder/drive-folder.model.js → drive-item/drive-item.model.js} +11 -23
- package/dist/services/database/drive-item/drive-item.repository.d.ts +12 -0
- package/dist/services/database/drive-item/drive-item.repository.js +88 -0
- package/dist/services/drive/drive-file.service.d.ts +2 -3
- package/dist/services/drive/drive-file.service.js +9 -34
- package/dist/services/drive/drive-folder.service.d.ts +1 -3
- package/dist/services/drive/drive-folder.service.js +9 -87
- package/dist/services/drive/drive-item.service.d.ts +8 -0
- package/dist/services/drive/drive-item.service.js +98 -0
- package/dist/services/network/network-facade.service.d.ts +7 -2
- package/dist/services/network/network-facade.service.js +11 -25
- package/dist/services/network/upload/upload-file.service.js +7 -8
- package/dist/services/thumbnail.service.js +7 -9
- package/dist/services/usage.service.d.ts +2 -0
- package/dist/services/usage.service.js +18 -0
- package/dist/utils/cli.utils.d.ts +3 -2
- package/dist/utils/cli.utils.js +22 -18
- package/dist/utils/format.utils.js +3 -2
- package/dist/utils/upload.utils.d.ts +19 -0
- package/dist/utils/upload.utils.js +40 -0
- package/dist/utils/webdav.utils.d.ts +4 -1
- package/dist/utils/webdav.utils.js +33 -18
- package/dist/utils/xml.utils.d.ts +1 -1
- package/dist/utils/xml.utils.js +5 -1
- package/dist/webdav/handlers/DELETE.handler.js +3 -17
- package/dist/webdav/handlers/LOCK.handler.d.ts +5 -0
- package/dist/webdav/handlers/LOCK.handler.js +50 -0
- package/dist/webdav/handlers/MKCOL.handler.js +15 -3
- package/dist/webdav/handlers/MOVE.handler.js +11 -0
- package/dist/webdav/handlers/OPTIONS.handler.js +4 -4
- package/dist/webdav/handlers/PROPFIND.handler.js +30 -9
- package/dist/webdav/handlers/PUT.handler.js +59 -59
- package/dist/webdav/handlers/UNLOCK.handler.d.ts +5 -0
- package/dist/webdav/handlers/UNLOCK.handler.js +12 -0
- package/dist/webdav/middewares/auth.middleware.js +9 -2
- package/dist/webdav/middewares/errors.middleware.js +2 -0
- package/dist/webdav/middewares/request-logger.middleware.js +10 -6
- package/dist/webdav/middewares/webdav-auth.middleware.js +0 -3
- package/dist/webdav/webdav-server.js +4 -0
- package/oclif.manifest.json +1 -1
- package/package.json +47 -46
- package/dist/services/database/drive-file/drive-file.attributes.d.ts +0 -14
- package/dist/services/database/drive-file/drive-file.domain.d.ts +0 -20
- package/dist/services/database/drive-file/drive-file.domain.js +0 -68
- package/dist/services/database/drive-file/drive-file.model.d.ts +0 -15
- package/dist/services/database/drive-file/drive-file.model.js +0 -67
- package/dist/services/database/drive-file/drive-file.repository.d.ts +0 -11
- package/dist/services/database/drive-file/drive-file.repository.js +0 -63
- package/dist/services/database/drive-folder/drive-folder.attributes.d.ts +0 -10
- package/dist/services/database/drive-folder/drive-folder.attributes.js +0 -2
- package/dist/services/database/drive-folder/drive-folder.domain.d.ts +0 -16
- package/dist/services/database/drive-folder/drive-folder.domain.js +0 -53
- package/dist/services/database/drive-folder/drive-folder.model.d.ts +0 -11
- package/dist/services/database/drive-folder/drive-folder.repository.d.ts +0 -13
- package/dist/services/database/drive-folder/drive-folder.repository.js +0 -99
- package/dist/types/fast-xml-parser.types.d.ts +0 -59
- package/dist/types/fast-xml-parser.types.js +0 -2
- package/dist/utils/database.utils.d.ts +0 -11
- package/dist/utils/database.utils.js +0 -26
- /package/dist/services/database/{drive-file/drive-file.attributes.js → drive-item/drive-item.attributes.js} +0 -0
|
@@ -31,13 +31,11 @@ class ThumbnailService {
|
|
|
31
31
|
}
|
|
32
32
|
if (thumbnailBuffer) {
|
|
33
33
|
const size = thumbnailBuffer.length;
|
|
34
|
-
const fileId = await
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
resolve(res);
|
|
40
|
-
}, () => { });
|
|
34
|
+
const fileId = await networkFacade.uploadFile({
|
|
35
|
+
from: node_stream_1.Readable.from(thumbnailBuffer),
|
|
36
|
+
size: size,
|
|
37
|
+
bucketId: userBucket,
|
|
38
|
+
progressCallback: () => { },
|
|
41
39
|
});
|
|
42
40
|
const createdThumbnailFile = await drive_file_service_1.DriveFileService.instance.createThumbnail({
|
|
43
41
|
fileUuid: file_id,
|
|
@@ -55,7 +53,7 @@ class ThumbnailService {
|
|
|
55
53
|
getThumbnailFromImageBuffer = async (buffer) => {
|
|
56
54
|
const sharp = await getSharp();
|
|
57
55
|
if (sharp) {
|
|
58
|
-
return sharp(buffer, {
|
|
56
|
+
return sharp(buffer, { failOn: 'none' })
|
|
59
57
|
.resize({
|
|
60
58
|
height: thumbnail_utils_1.ThumbnailConfig.MaxHeight,
|
|
61
59
|
width: thumbnail_utils_1.ThumbnailConfig.MaxWidth,
|
|
@@ -70,7 +68,7 @@ class ThumbnailService {
|
|
|
70
68
|
tryUploadThumbnail = async ({ bufferStream, fileType, bucket, fileUuid, networkFacade, size, }) => {
|
|
71
69
|
try {
|
|
72
70
|
const thumbnailBuffer = bufferStream?.getBuffer();
|
|
73
|
-
if (thumbnailBuffer) {
|
|
71
|
+
if (thumbnailBuffer && size > 0) {
|
|
74
72
|
await async_utils_1.AsyncUtils.withTimeout(ThumbnailService.instance.uploadThumbnail(thumbnailBuffer, fileType, bucket, fileUuid, networkFacade, size), ThumbnailService.MAX_THUMBNAIL_TIMEOUT, 'Thumbnail upload timeout');
|
|
75
73
|
}
|
|
76
74
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { StorageTypes } from '@internxt/sdk/dist/drive/storage';
|
|
1
2
|
export declare class UsageService {
|
|
2
3
|
static readonly instance: UsageService;
|
|
3
4
|
static readonly INFINITE_LIMIT: number;
|
|
4
5
|
fetchUsage: () => Promise<number>;
|
|
5
6
|
fetchSpaceLimit: () => Promise<number>;
|
|
7
|
+
fetchLimits: () => Promise<StorageTypes.FileLimitsResponse>;
|
|
6
8
|
}
|
|
@@ -2,18 +2,36 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.UsageService = void 0;
|
|
4
4
|
const sdk_manager_service_1 = require("./sdk-manager.service");
|
|
5
|
+
const cache_service_1 = require("./cache.service");
|
|
5
6
|
class UsageService {
|
|
6
7
|
static instance = new UsageService();
|
|
7
8
|
static INFINITE_LIMIT = 99 * Math.pow(1024, 4);
|
|
8
9
|
fetchUsage = async () => {
|
|
10
|
+
const cached = cache_service_1.CacheService.instance.get(cache_service_1.CacheService.FETCH_USAGE_CACHE_KEY);
|
|
11
|
+
if (cached !== null)
|
|
12
|
+
return cached;
|
|
9
13
|
const storageClient = sdk_manager_service_1.SdkManager.instance.getStorage();
|
|
10
14
|
const driveUsage = await storageClient.spaceUsageV2();
|
|
15
|
+
cache_service_1.CacheService.instance.set(cache_service_1.CacheService.FETCH_USAGE_CACHE_KEY, driveUsage.total);
|
|
11
16
|
return driveUsage.total;
|
|
12
17
|
};
|
|
13
18
|
fetchSpaceLimit = async () => {
|
|
19
|
+
const cached = cache_service_1.CacheService.instance.get(cache_service_1.CacheService.FETCH_SPACE_LIMIT_CACHE_KEY);
|
|
20
|
+
if (cached !== null)
|
|
21
|
+
return cached;
|
|
14
22
|
const storageClient = sdk_manager_service_1.SdkManager.instance.getStorage();
|
|
15
23
|
const spaceLimit = await storageClient.spaceLimitV2();
|
|
24
|
+
cache_service_1.CacheService.instance.set(cache_service_1.CacheService.FETCH_SPACE_LIMIT_CACHE_KEY, spaceLimit.maxSpaceBytes);
|
|
16
25
|
return spaceLimit.maxSpaceBytes;
|
|
17
26
|
};
|
|
27
|
+
fetchLimits = async () => {
|
|
28
|
+
const cached = cache_service_1.CacheService.instance.get(cache_service_1.CacheService.FETCH_LIMITS_CACHE_KEY);
|
|
29
|
+
if (cached !== null)
|
|
30
|
+
return cached;
|
|
31
|
+
const storageClient = sdk_manager_service_1.SdkManager.instance.getStorage();
|
|
32
|
+
const limits = await storageClient.getFileVersionLimits();
|
|
33
|
+
cache_service_1.CacheService.instance.set(cache_service_1.CacheService.FETCH_LIMITS_CACHE_KEY, limits);
|
|
34
|
+
return limits;
|
|
35
|
+
};
|
|
18
36
|
}
|
|
19
37
|
exports.UsageService = UsageService;
|
|
@@ -2,6 +2,8 @@ import cliProgress from 'cli-progress';
|
|
|
2
2
|
import { Header } from 'tty-table';
|
|
3
3
|
import { LoginCredentials, LoginUserDetails, PromptOptions } from '../types/command.types';
|
|
4
4
|
import { NetworkCredentials, NetworkOptions } from '../types/network.types';
|
|
5
|
+
import { AppError } from '@internxt/sdk';
|
|
6
|
+
import { AxiosResponseError } from '@internxt/sdk/dist/shared/types/errors';
|
|
5
7
|
export type LogReporter = (message: string) => void;
|
|
6
8
|
export declare class CLIUtils {
|
|
7
9
|
static readonly clearPreviousLine: (jsonFlag?: boolean) => void;
|
|
@@ -46,10 +48,9 @@ export declare class CLIUtils {
|
|
|
46
48
|
stop: () => number;
|
|
47
49
|
};
|
|
48
50
|
static readonly formatDuration: (milliseconds: number) => string;
|
|
49
|
-
static readonly formatBytesToString: (bytes: number) => string;
|
|
50
51
|
static readonly calculateThroughputMBps: (bytes: number, milliseconds: number) => number;
|
|
51
52
|
static readonly catchError: ({ error, logReporter, command, jsonFlag, }: {
|
|
52
|
-
error: Error;
|
|
53
|
+
error: Error | AppError | AxiosResponseError;
|
|
53
54
|
command?: string;
|
|
54
55
|
logReporter: LogReporter;
|
|
55
56
|
jsonFlag?: boolean;
|
package/dist/utils/cli.utils.js
CHANGED
|
@@ -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;
|
|
@@ -196,19 +185,34 @@ class CLIUtils {
|
|
|
196
185
|
};
|
|
197
186
|
static catchError = ({ error, logReporter, command, jsonFlag, }) => {
|
|
198
187
|
let message;
|
|
199
|
-
|
|
200
|
-
|
|
188
|
+
let requestId;
|
|
189
|
+
if ('requestId' in error) {
|
|
190
|
+
requestId = error.requestId;
|
|
201
191
|
}
|
|
202
|
-
else {
|
|
203
|
-
|
|
192
|
+
else if ('xRequestId' in error) {
|
|
193
|
+
requestId = error.xRequestId;
|
|
194
|
+
}
|
|
195
|
+
if ('data' in error) {
|
|
196
|
+
const errorData = error.data;
|
|
197
|
+
if (errorData.message && errorData.message.trim().length > 0) {
|
|
198
|
+
message = errorData.message;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (!message) {
|
|
202
|
+
if ('message' in error && error.message.trim().length > 0) {
|
|
203
|
+
message = error.message;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
message = JSON.stringify(error);
|
|
207
|
+
}
|
|
204
208
|
}
|
|
205
209
|
CLIUtils.failed(jsonFlag);
|
|
206
210
|
if (jsonFlag) {
|
|
207
|
-
CLIUtils.consoleLog(JSON.stringify({ success: false, message }));
|
|
211
|
+
CLIUtils.consoleLog(JSON.stringify({ success: false, message, requestId }));
|
|
208
212
|
}
|
|
209
213
|
else {
|
|
210
|
-
errors_utils_1.ErrorUtils.report(error, { command });
|
|
211
|
-
CLIUtils.error(logReporter, message);
|
|
214
|
+
errors_utils_1.ErrorUtils.report(error, { command, requestId });
|
|
215
|
+
CLIUtils.error(logReporter, message + (requestId ? ` (requestId: ${requestId})` : ''));
|
|
212
216
|
}
|
|
213
217
|
};
|
|
214
218
|
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
|
|
14
|
-
|
|
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,8 +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>;
|
|
12
|
+
static deleteOrTrashItem<T extends {
|
|
13
|
+
itemType: 'file' | 'folder';
|
|
14
|
+
uuid: string;
|
|
15
|
+
}>(driveItem: T): Promise<void>;
|
|
13
16
|
}
|
|
@@ -5,9 +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
|
|
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");
|
|
10
|
+
const config_service_1 = require("../services/config.service");
|
|
11
|
+
const trash_service_1 = require("../services/drive/trash.service");
|
|
12
|
+
const format_utils_1 = require("./format.utils");
|
|
13
|
+
const drive_item_repository_1 = require("../services/database/drive-item/drive-item.repository");
|
|
11
14
|
class WebDavUtils {
|
|
12
15
|
static joinURL(...pathComponents) {
|
|
13
16
|
return node_path_1.default.posix.join(...pathComponents);
|
|
@@ -46,28 +49,18 @@ class WebDavUtils {
|
|
|
46
49
|
parentPath,
|
|
47
50
|
};
|
|
48
51
|
}
|
|
49
|
-
static async tryGetFileOrFolderMetadata(url) {
|
|
50
|
-
try {
|
|
51
|
-
return await drive_file_service_1.DriveFileService.instance.getFileMetadataByPath(url);
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return await drive_folder_service_1.DriveFolderService.instance.getFolderMetadataByPath(url);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
52
|
static async getDriveFileFromResource(url) {
|
|
58
53
|
try {
|
|
59
|
-
return await
|
|
54
|
+
return await drive_item_service_1.DriveItemService.instance.getFileByPath(url);
|
|
60
55
|
}
|
|
61
|
-
catch
|
|
62
|
-
logger_utils_1.webdavLogger.error('Exception while getting the file metadata by path', err);
|
|
56
|
+
catch {
|
|
63
57
|
}
|
|
64
58
|
}
|
|
65
59
|
static async getDriveFolderFromResource(url) {
|
|
66
60
|
try {
|
|
67
|
-
return await
|
|
61
|
+
return await drive_item_service_1.DriveItemService.instance.getFolderByPath(url);
|
|
68
62
|
}
|
|
69
|
-
catch
|
|
70
|
-
logger_utils_1.webdavLogger.error('Exception while getting the folder metadata by path', err);
|
|
63
|
+
catch {
|
|
71
64
|
}
|
|
72
65
|
}
|
|
73
66
|
static async getDriveItemFromResource(resource) {
|
|
@@ -75,15 +68,37 @@ class WebDavUtils {
|
|
|
75
68
|
const isFolder = resource.url.endsWith('/');
|
|
76
69
|
try {
|
|
77
70
|
if (isFolder) {
|
|
78
|
-
item = await
|
|
71
|
+
item = await drive_item_service_1.DriveItemService.instance.getFolderByPath(resource.url);
|
|
79
72
|
}
|
|
80
73
|
else {
|
|
81
|
-
|
|
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
|
+
}
|
|
82
80
|
}
|
|
83
81
|
}
|
|
84
82
|
catch {
|
|
85
83
|
}
|
|
86
84
|
return item;
|
|
87
85
|
}
|
|
86
|
+
static async deleteOrTrashItem(driveItem) {
|
|
87
|
+
const configs = await config_service_1.ConfigService.instance.readWebdavConfig();
|
|
88
|
+
const type = format_utils_1.FormatUtils.capitalizeFirstLetter(driveItem.itemType);
|
|
89
|
+
if (configs.deleteFilesPermanently) {
|
|
90
|
+
logger_utils_1.webdavLogger.info(`[DELETE] [${driveItem.uuid}] Deleting permanently ${driveItem.itemType}`);
|
|
91
|
+
await trash_service_1.TrashService.instance.deleteItemPermanently(driveItem.itemType, driveItem.uuid);
|
|
92
|
+
logger_utils_1.webdavLogger.info(`[DELETE] [${driveItem.uuid}] ${type} deleted permanently successfully`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
logger_utils_1.webdavLogger.info(`[DELETE] [${driveItem.uuid}] Trashing ${driveItem.itemType}`);
|
|
96
|
+
await trash_service_1.TrashService.instance.trashItems({
|
|
97
|
+
items: [{ type: driveItem.itemType, uuid: driveItem.uuid }],
|
|
98
|
+
});
|
|
99
|
+
logger_utils_1.webdavLogger.info(`[DELETE] [${driveItem.uuid}] ${type} trashed successfully`);
|
|
100
|
+
}
|
|
101
|
+
await drive_item_repository_1.DriveItemRepository.instance.delete([driveItem.uuid]);
|
|
102
|
+
}
|
|
88
103
|
}
|
|
89
104
|
exports.WebDavUtils = WebDavUtils;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { X2jOptions, XmlBuilderOptions } from '
|
|
1
|
+
import { X2jOptions, XmlBuilderOptions } from 'fast-xml-parser';
|
|
2
2
|
export declare class XMLUtils {
|
|
3
3
|
static readonly DEFAULT_NAMESPACE_LETTER = "D";
|
|
4
4
|
static toJSON(xml: string, options?: X2jOptions): any;
|
package/dist/utils/xml.utils.js
CHANGED
|
@@ -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
|
|
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,12 +1,10 @@
|
|
|
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
|
-
const trash_service_1 = require("../../services/drive/trash.service");
|
|
6
6
|
const logger_utils_1 = require("../../utils/logger.utils");
|
|
7
7
|
const errors_utils_1 = require("../../utils/errors.utils");
|
|
8
|
-
const config_service_1 = require("../../services/config.service");
|
|
9
|
-
const format_utils_1 = require("../../utils/format.utils");
|
|
10
8
|
class DELETERequestHandler {
|
|
11
9
|
handle = async (req, res) => {
|
|
12
10
|
const resource = await webdav_utils_1.WebDavUtils.getRequestedResource(req.url);
|
|
@@ -15,20 +13,8 @@ class DELETERequestHandler {
|
|
|
15
13
|
if (!driveItem) {
|
|
16
14
|
throw new errors_utils_1.NotFoundError(`Resource not found on Internxt Drive at ${resource.url}`);
|
|
17
15
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (configs.deleteFilesPermanently) {
|
|
21
|
-
logger_utils_1.webdavLogger.info(`[DELETE] [${driveItem.uuid}] Deleting permanently ${driveItem.itemType}`);
|
|
22
|
-
await trash_service_1.TrashService.instance.deleteItemPermanently(driveItem.itemType, driveItem.uuid);
|
|
23
|
-
logger_utils_1.webdavLogger.info(`[DELETE] [${driveItem.uuid}] ${type} deleted permanently successfully`);
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
logger_utils_1.webdavLogger.info(`[DELETE] [${driveItem.uuid}] Trashing ${driveItem.itemType}`);
|
|
27
|
-
await trash_service_1.TrashService.instance.trashItems({
|
|
28
|
-
items: [{ type: driveItem.itemType, uuid: driveItem.uuid }],
|
|
29
|
-
});
|
|
30
|
-
logger_utils_1.webdavLogger.info(`[DELETE] [${driveItem.uuid}] ${type} trashed successfully`);
|
|
31
|
-
}
|
|
16
|
+
await webdav_utils_1.WebDavUtils.deleteOrTrashItem(driveItem);
|
|
17
|
+
await drive_item_repository_1.DriveItemRepository.instance.delete([driveItem.uuid]);
|
|
32
18
|
res.status(204).send();
|
|
33
19
|
};
|
|
34
20
|
}
|
|
@@ -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
|
|
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]
|
|
19
|
-
|
|
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',
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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();
|