clhq-s3-module 1.1.0-alpha.90
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/dist/aws-s3.service.d.ts +19 -0
- package/dist/aws-s3.service.js +124 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +22 -0
- package/dist/s3.module.d.ts +2 -0
- package/dist/s3.module.js +30 -0
- package/dist/s3.service.d.ts +34 -0
- package/dist/s3.service.js +277 -0
- package/dist/services/timeline-storage.service.d.ts +12 -0
- package/dist/services/timeline-storage.service.js +139 -0
- package/dist/stream-to-disk-helper.d.ts +11 -0
- package/dist/stream-to-disk-helper.js +93 -0
- package/dist/utils/compression.util.d.ts +7 -0
- package/dist/utils/compression.util.js +43 -0
- package/package.json +48 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ConfigService } from '@nestjs/config';
|
|
2
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
3
|
+
export declare class AwsService {
|
|
4
|
+
private readonly configService;
|
|
5
|
+
private readonly awsUserKey;
|
|
6
|
+
private readonly awsUserPassword;
|
|
7
|
+
private readonly region;
|
|
8
|
+
private readonly bucket;
|
|
9
|
+
private readonly logger;
|
|
10
|
+
constructor(configService: ConfigService);
|
|
11
|
+
getS3Client(): S3Client;
|
|
12
|
+
uploadPublicFile(dataBuffer: Buffer, filename: string, mimetype: string): Promise<import("@aws-sdk/client-s3").CompleteMultipartUploadCommandOutput | import("@aws-sdk/client-s3").AbortMultipartUploadCommandOutput>;
|
|
13
|
+
putObject(params: any): Promise<any>;
|
|
14
|
+
getObject(params: any): Promise<any>;
|
|
15
|
+
listObjects(params: any): Promise<any>;
|
|
16
|
+
deleteObject(params: any): Promise<any>;
|
|
17
|
+
deleteFile(key: string): Promise<void>;
|
|
18
|
+
delay(milisec: number): Promise<string>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var AwsService_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.AwsService = void 0;
|
|
14
|
+
const common_1 = require("@nestjs/common");
|
|
15
|
+
const config_1 = require("@nestjs/config");
|
|
16
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
17
|
+
const lib_storage_1 = require("@aws-sdk/lib-storage");
|
|
18
|
+
let AwsService = AwsService_1 = class AwsService {
|
|
19
|
+
configService;
|
|
20
|
+
awsUserKey;
|
|
21
|
+
awsUserPassword;
|
|
22
|
+
region;
|
|
23
|
+
bucket;
|
|
24
|
+
logger = new common_1.Logger(AwsService_1.name);
|
|
25
|
+
constructor(configService) {
|
|
26
|
+
this.configService = configService;
|
|
27
|
+
this.awsUserKey = this.configService.get('CLIPPY_AWS_USER_KEY');
|
|
28
|
+
this.awsUserPassword = this.configService.get('CLIPPY_AWS_USER_SECRET');
|
|
29
|
+
this.region = this.configService.get('CLIPPY_AWS_DEFAULT_REGION');
|
|
30
|
+
this.bucket = this.configService.get('CLIPPY_S3_BUCKET');
|
|
31
|
+
if (!this.bucket) {
|
|
32
|
+
this.logger.error('CLIPPY_S3_BUCKET is not set in environment variables');
|
|
33
|
+
throw new Error('CLIPPY_S3_BUCKET environment variable is required');
|
|
34
|
+
}
|
|
35
|
+
this.logger.debug(`S3 Bucket configured: ${this.bucket}`);
|
|
36
|
+
}
|
|
37
|
+
getS3Client() {
|
|
38
|
+
const region = this.configService.get('CLIPPY_AWS_DEFAULT_REGION');
|
|
39
|
+
const client = new client_s3_1.S3Client({
|
|
40
|
+
region: region,
|
|
41
|
+
credentials: {
|
|
42
|
+
accessKeyId: this.awsUserKey,
|
|
43
|
+
secretAccessKey: this.awsUserPassword,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
return client;
|
|
47
|
+
}
|
|
48
|
+
async uploadPublicFile(dataBuffer, filename, mimetype) {
|
|
49
|
+
const params = {
|
|
50
|
+
Bucket: this.bucket,
|
|
51
|
+
Key: String(`client/${filename}`),
|
|
52
|
+
Body: dataBuffer,
|
|
53
|
+
ContentType: mimetype || 'video/webm',
|
|
54
|
+
ACL: 'public-read',
|
|
55
|
+
CreateBucketConfiguration: {
|
|
56
|
+
LocationConstraint: this.region,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
const parallelUploads3 = new lib_storage_1.Upload({
|
|
60
|
+
client: this.getS3Client(),
|
|
61
|
+
params: params,
|
|
62
|
+
tags: [],
|
|
63
|
+
queueSize: 4,
|
|
64
|
+
partSize: 1024 * 1024 * 5,
|
|
65
|
+
leavePartsOnError: false,
|
|
66
|
+
});
|
|
67
|
+
parallelUploads3.on('httpUploadProgress', (progress) => {
|
|
68
|
+
console.log(progress);
|
|
69
|
+
});
|
|
70
|
+
await parallelUploads3.done();
|
|
71
|
+
await this.delay(2000);
|
|
72
|
+
this.logger.debug('upload success');
|
|
73
|
+
const result = await parallelUploads3.done();
|
|
74
|
+
console.log(`File uploaded successfully: ${JSON.stringify(result)}`);
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
async putObject(params) {
|
|
78
|
+
const client = this.getS3Client();
|
|
79
|
+
const command = new client_s3_1.PutObjectCommand({
|
|
80
|
+
Bucket: this.bucket,
|
|
81
|
+
...params,
|
|
82
|
+
});
|
|
83
|
+
return await client.send(command);
|
|
84
|
+
}
|
|
85
|
+
async getObject(params) {
|
|
86
|
+
const client = this.getS3Client();
|
|
87
|
+
const command = new client_s3_1.GetObjectCommand({
|
|
88
|
+
Bucket: this.bucket,
|
|
89
|
+
...params,
|
|
90
|
+
});
|
|
91
|
+
return await client.send(command);
|
|
92
|
+
}
|
|
93
|
+
async listObjects(params) {
|
|
94
|
+
const client = this.getS3Client();
|
|
95
|
+
const command = new client_s3_1.ListObjectsCommand({
|
|
96
|
+
Bucket: this.bucket,
|
|
97
|
+
...params,
|
|
98
|
+
});
|
|
99
|
+
return await client.send(command);
|
|
100
|
+
}
|
|
101
|
+
async deleteObject(params) {
|
|
102
|
+
const client = this.getS3Client();
|
|
103
|
+
const command = new client_s3_1.DeleteObjectCommand({
|
|
104
|
+
Bucket: this.bucket,
|
|
105
|
+
...params,
|
|
106
|
+
});
|
|
107
|
+
return await client.send(command);
|
|
108
|
+
}
|
|
109
|
+
async deleteFile(key) {
|
|
110
|
+
await this.deleteObject({ Key: key });
|
|
111
|
+
}
|
|
112
|
+
async delay(milisec) {
|
|
113
|
+
return new Promise((resolve) => {
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
resolve('');
|
|
116
|
+
}, milisec);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
exports.AwsService = AwsService;
|
|
121
|
+
exports.AwsService = AwsService = AwsService_1 = __decorate([
|
|
122
|
+
(0, common_1.Injectable)(),
|
|
123
|
+
__metadata("design:paramtypes", [config_1.ConfigService])
|
|
124
|
+
], AwsService);
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./s3.module"), exports);
|
|
18
|
+
__exportStar(require("./aws-s3.service"), exports);
|
|
19
|
+
__exportStar(require("./s3.service"), exports);
|
|
20
|
+
__exportStar(require("./stream-to-disk-helper"), exports);
|
|
21
|
+
__exportStar(require("./services/timeline-storage.service"), exports);
|
|
22
|
+
__exportStar(require("./utils/compression.util"), exports);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.S3Module = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const aws_s3_service_1 = require("./aws-s3.service");
|
|
12
|
+
const s3_service_1 = require("./s3.service");
|
|
13
|
+
const timeline_storage_service_1 = require("./services/timeline-storage.service");
|
|
14
|
+
let S3Module = class S3Module {
|
|
15
|
+
};
|
|
16
|
+
exports.S3Module = S3Module;
|
|
17
|
+
exports.S3Module = S3Module = __decorate([
|
|
18
|
+
(0, common_1.Module)({
|
|
19
|
+
providers: [
|
|
20
|
+
s3_service_1.S3Service,
|
|
21
|
+
aws_s3_service_1.AwsService,
|
|
22
|
+
timeline_storage_service_1.TimelineStorageService,
|
|
23
|
+
],
|
|
24
|
+
exports: [
|
|
25
|
+
s3_service_1.S3Service,
|
|
26
|
+
aws_s3_service_1.AwsService,
|
|
27
|
+
timeline_storage_service_1.TimelineStorageService,
|
|
28
|
+
],
|
|
29
|
+
})
|
|
30
|
+
], S3Module);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ConfigService } from '@nestjs/config';
|
|
2
|
+
import * as S3 from '@aws-sdk/client-s3';
|
|
3
|
+
import { Readable } from 'stream';
|
|
4
|
+
export declare class S3Service {
|
|
5
|
+
private readonly configService;
|
|
6
|
+
private readonly awsUserKey;
|
|
7
|
+
private readonly awsUserPassword;
|
|
8
|
+
private readonly region;
|
|
9
|
+
private readonly bucket;
|
|
10
|
+
private readonly logger;
|
|
11
|
+
constructor(configService: ConfigService);
|
|
12
|
+
getS3Client(): S3.S3Client;
|
|
13
|
+
getBucketDetails(): {
|
|
14
|
+
bucket: string;
|
|
15
|
+
region: string;
|
|
16
|
+
};
|
|
17
|
+
listOfObjects(path: string): Promise<any>;
|
|
18
|
+
getObjectByKey(key: string): Promise<any>;
|
|
19
|
+
uploadS3(rawData: any, fileKey: any, contentType: any): Promise<any>;
|
|
20
|
+
streamToString(stream: any): Promise<unknown>;
|
|
21
|
+
streamToBuffer(stream: any): Promise<unknown>;
|
|
22
|
+
asStream: (s3Response: any) => Promise<Readable>;
|
|
23
|
+
getFileStream(fileKey: any): Promise<any>;
|
|
24
|
+
getFileExtension: (file: any) => any;
|
|
25
|
+
getFileNameWithoutExtension: (file: any) => any;
|
|
26
|
+
getStreamFromAws(fileKey: string): Promise<Readable>;
|
|
27
|
+
getSignedFileUrl(fileName: any, uniqueId: any, parts: number, fileType: any): Promise<{
|
|
28
|
+
uploadPartSignedUrls: any[];
|
|
29
|
+
UploadId: string;
|
|
30
|
+
s3Key: string;
|
|
31
|
+
}>;
|
|
32
|
+
uploadPart(fileName: any, uploadId: any, buffer: any, partNumber: any): Promise<S3.UploadPartCommandOutput>;
|
|
33
|
+
completeMultiPartUplaod(fileName: any, uploadId: any, partsWithETag?: any[]): Promise<S3.CompleteMultipartUploadCommandOutput>;
|
|
34
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
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;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
42
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
43
|
+
};
|
|
44
|
+
var S3Service_1;
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.S3Service = void 0;
|
|
47
|
+
const common_1 = require("@nestjs/common");
|
|
48
|
+
const config_1 = require("@nestjs/config");
|
|
49
|
+
const S3 = __importStar(require("@aws-sdk/client-s3"));
|
|
50
|
+
const stream_1 = require("stream");
|
|
51
|
+
const lib_storage_1 = require("@aws-sdk/lib-storage");
|
|
52
|
+
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
|
|
53
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
54
|
+
let S3Service = S3Service_1 = class S3Service {
|
|
55
|
+
configService;
|
|
56
|
+
awsUserKey;
|
|
57
|
+
awsUserPassword;
|
|
58
|
+
region;
|
|
59
|
+
bucket;
|
|
60
|
+
logger = new common_1.Logger(S3Service_1.name);
|
|
61
|
+
constructor(configService) {
|
|
62
|
+
this.configService = configService;
|
|
63
|
+
this.awsUserKey = this.configService.get('CLIPPY_AWS_USER_KEY');
|
|
64
|
+
this.awsUserPassword = this.configService.get('CLIPPY_AWS_USER_SECRET');
|
|
65
|
+
this.region = this.configService.get('CLIPPY_AWS_DEFAULT_REGION');
|
|
66
|
+
this.bucket = this.configService.get('CLIPPY_S3_BUCKET');
|
|
67
|
+
}
|
|
68
|
+
getS3Client() {
|
|
69
|
+
const client = new S3.S3Client({
|
|
70
|
+
region: this.region,
|
|
71
|
+
credentials: {
|
|
72
|
+
accessKeyId: this.awsUserKey,
|
|
73
|
+
secretAccessKey: this.awsUserPassword,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
return client;
|
|
77
|
+
}
|
|
78
|
+
getBucketDetails() {
|
|
79
|
+
return {
|
|
80
|
+
bucket: this.bucket,
|
|
81
|
+
region: this.region,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
async listOfObjects(path) {
|
|
85
|
+
const client = this.getS3Client();
|
|
86
|
+
const params = {
|
|
87
|
+
Bucket: this.bucket,
|
|
88
|
+
Prefix: String(`client/${path}`),
|
|
89
|
+
MaxKeys: 100,
|
|
90
|
+
};
|
|
91
|
+
const command = new S3.ListObjectsCommand(params);
|
|
92
|
+
const response = await client.send(command);
|
|
93
|
+
return response;
|
|
94
|
+
}
|
|
95
|
+
async getObjectByKey(key) {
|
|
96
|
+
const client = this.getS3Client();
|
|
97
|
+
const params = {
|
|
98
|
+
Bucket: this.bucket,
|
|
99
|
+
Key: String(key),
|
|
100
|
+
};
|
|
101
|
+
const command = new S3.GetObjectCommand(params);
|
|
102
|
+
const response = await client.send(command);
|
|
103
|
+
const bufferedResponse = await this.streamToString(response.Body);
|
|
104
|
+
return bufferedResponse;
|
|
105
|
+
}
|
|
106
|
+
async uploadS3(rawData, fileKey, contentType) {
|
|
107
|
+
const client = this.getS3Client();
|
|
108
|
+
const params = {
|
|
109
|
+
Bucket: this.bucket,
|
|
110
|
+
Key: String(`client/${fileKey}`),
|
|
111
|
+
Body: rawData,
|
|
112
|
+
ContentType: contentType || 'video/webm',
|
|
113
|
+
};
|
|
114
|
+
const parallelUploadS3 = new lib_storage_1.Upload({
|
|
115
|
+
client: client,
|
|
116
|
+
partSize: 5242880,
|
|
117
|
+
leavePartsOnError: false,
|
|
118
|
+
params: params,
|
|
119
|
+
});
|
|
120
|
+
parallelUploadS3.on('httpUploadProgress', (progress) => {
|
|
121
|
+
this.logger.debug('running progress');
|
|
122
|
+
this.logger.debug(progress);
|
|
123
|
+
});
|
|
124
|
+
const response = await parallelUploadS3.done();
|
|
125
|
+
const newParams = {
|
|
126
|
+
...params,
|
|
127
|
+
ACL: 'public-read',
|
|
128
|
+
};
|
|
129
|
+
try {
|
|
130
|
+
const aclUpdate = new S3.PutObjectAclCommand(newParams);
|
|
131
|
+
const putAclResposne = await client.send(aclUpdate);
|
|
132
|
+
this.logger.debug('putAclResponse');
|
|
133
|
+
this.logger.debug(putAclResposne);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
this.logger.debug('Acl Update failed');
|
|
137
|
+
this.logger.error(err);
|
|
138
|
+
this.logger.error(err?.message);
|
|
139
|
+
}
|
|
140
|
+
return response;
|
|
141
|
+
}
|
|
142
|
+
async streamToString(stream) {
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
const chunks = [];
|
|
145
|
+
stream.on('data', (chunk) => chunks.push(chunk));
|
|
146
|
+
stream.on('error', reject);
|
|
147
|
+
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async streamToBuffer(stream) {
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
const chunks = [];
|
|
153
|
+
stream.on('data', (chunk) => chunks.push(chunk));
|
|
154
|
+
stream.on('error', reject);
|
|
155
|
+
stream.on('end', () => resolve(Buffer.concat(chunks)));
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
asStream = async (s3Response) => {
|
|
159
|
+
return s3Response.Body;
|
|
160
|
+
};
|
|
161
|
+
async getFileStream(fileKey) {
|
|
162
|
+
const client = this.getS3Client();
|
|
163
|
+
console.log('s3Key', fileKey);
|
|
164
|
+
const params = {
|
|
165
|
+
Bucket: this.bucket,
|
|
166
|
+
Key: String(fileKey),
|
|
167
|
+
};
|
|
168
|
+
const command = new S3.GetObjectCommand(params);
|
|
169
|
+
const response = await client.send(command);
|
|
170
|
+
const { Body } = response;
|
|
171
|
+
return Body;
|
|
172
|
+
}
|
|
173
|
+
getFileExtension = (file) => {
|
|
174
|
+
const fileName = file.originalname;
|
|
175
|
+
return fileName.split('.').pop();
|
|
176
|
+
};
|
|
177
|
+
getFileNameWithoutExtension = (file) => {
|
|
178
|
+
return file.split('.').slice(0, -1).join('.');
|
|
179
|
+
};
|
|
180
|
+
async getStreamFromAws(fileKey) {
|
|
181
|
+
const s3Client = this.getS3Client();
|
|
182
|
+
const fileKeyPath = fileKey.startsWith('client')
|
|
183
|
+
? fileKey.replace('client/', '')
|
|
184
|
+
: fileKey;
|
|
185
|
+
const s3Params = {
|
|
186
|
+
Bucket: this.bucket,
|
|
187
|
+
Key: `client/${String(fileKeyPath)}`,
|
|
188
|
+
};
|
|
189
|
+
try {
|
|
190
|
+
const response = await s3Client.send(new S3.GetObjectCommand(s3Params));
|
|
191
|
+
if (response.Body instanceof stream_1.Readable) {
|
|
192
|
+
return response.Body;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
const readable = new stream_1.Readable();
|
|
196
|
+
readable._read = () => { };
|
|
197
|
+
response.Body.on('data', (chunk) => readable.push(chunk));
|
|
198
|
+
response.Body.on('end', () => readable.push(null));
|
|
199
|
+
return readable;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
console.error('Error getting stream from S3:', error);
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async getSignedFileUrl(fileName, uniqueId, parts = 1, fileType) {
|
|
208
|
+
const client = this.getS3Client();
|
|
209
|
+
const s3Key = `client/${fileName}`;
|
|
210
|
+
const multipartParams = new S3.CreateMultipartUploadCommand({
|
|
211
|
+
Bucket: this.bucket,
|
|
212
|
+
Key: s3Key,
|
|
213
|
+
ContentType: fileType || 'video/webm',
|
|
214
|
+
ACL: 'public-read',
|
|
215
|
+
});
|
|
216
|
+
const getMultipleUploadId = await client.send(multipartParams);
|
|
217
|
+
console.log('getMultipartUploadId', getMultipleUploadId);
|
|
218
|
+
const { UploadId } = getMultipleUploadId;
|
|
219
|
+
const uploadPartSignedUrls = [];
|
|
220
|
+
const partsArray = Array.from(Array(parts).keys());
|
|
221
|
+
for (const part of partsArray) {
|
|
222
|
+
console.log('partnumber', part + 1);
|
|
223
|
+
const command = new client_s3_1.UploadPartCommand({
|
|
224
|
+
Bucket: this.bucket,
|
|
225
|
+
Key: s3Key,
|
|
226
|
+
UploadId: UploadId,
|
|
227
|
+
PartNumber: Number(part + 1),
|
|
228
|
+
});
|
|
229
|
+
const uploadSignedUrl = await (0, s3_request_presigner_1.getSignedUrl)(client, command, {
|
|
230
|
+
expiresIn: 3600,
|
|
231
|
+
});
|
|
232
|
+
uploadPartSignedUrls.push(uploadSignedUrl);
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
uploadPartSignedUrls,
|
|
236
|
+
UploadId,
|
|
237
|
+
s3Key: s3Key,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
async uploadPart(fileName, uploadId, buffer, partNumber) {
|
|
241
|
+
const client = this.getS3Client();
|
|
242
|
+
const params = {
|
|
243
|
+
Bucket: this.bucket,
|
|
244
|
+
Key: fileName,
|
|
245
|
+
Body: buffer,
|
|
246
|
+
PartNumber: partNumber,
|
|
247
|
+
UploadId: uploadId,
|
|
248
|
+
};
|
|
249
|
+
const command = new S3.UploadPartCommand(params);
|
|
250
|
+
const response = await client.send(command);
|
|
251
|
+
return response;
|
|
252
|
+
}
|
|
253
|
+
async completeMultiPartUplaod(fileName, uploadId, partsWithETag = []) {
|
|
254
|
+
const client = this.getS3Client();
|
|
255
|
+
const completeMultipartUploadCommand = new S3.CompleteMultipartUploadCommand({
|
|
256
|
+
Bucket: this.bucket,
|
|
257
|
+
Key: fileName,
|
|
258
|
+
UploadId: uploadId,
|
|
259
|
+
MultipartUpload: {
|
|
260
|
+
Parts: partsWithETag.map((part) => ({
|
|
261
|
+
ETag: part.ETag,
|
|
262
|
+
PartNumber: part.PartNumber,
|
|
263
|
+
})),
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
this.logger.debug('completeMultipartUploadCommand');
|
|
267
|
+
this.logger.debug(JSON.stringify(completeMultipartUploadCommand));
|
|
268
|
+
const getMultipleUploadId = await client.send(completeMultipartUploadCommand);
|
|
269
|
+
console.log('getMultipartUploadId', getMultipleUploadId);
|
|
270
|
+
return getMultipleUploadId;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
exports.S3Service = S3Service;
|
|
274
|
+
exports.S3Service = S3Service = S3Service_1 = __decorate([
|
|
275
|
+
(0, common_1.Injectable)(),
|
|
276
|
+
__metadata("design:paramtypes", [config_1.ConfigService])
|
|
277
|
+
], S3Service);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AwsService } from '../aws-s3.service';
|
|
2
|
+
export declare class TimelineStorageService {
|
|
3
|
+
private s3Service;
|
|
4
|
+
constructor(s3Service: AwsService);
|
|
5
|
+
saveTimeline(projectId: string, timeline: any): Promise<string>;
|
|
6
|
+
loadTimeline(projectId: string): Promise<any>;
|
|
7
|
+
saveTimelineVersion(projectId: string, version: number, timeline: any): Promise<string>;
|
|
8
|
+
listVersions(projectId: string): Promise<string[]>;
|
|
9
|
+
getTimelineVersion(projectId: string, version: number): Promise<any>;
|
|
10
|
+
deleteTimeline(projectId: string): Promise<void>;
|
|
11
|
+
private streamToBuffer;
|
|
12
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.TimelineStorageService = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const aws_s3_service_1 = require("../aws-s3.service");
|
|
15
|
+
const compression_util_1 = require("../utils/compression.util");
|
|
16
|
+
let TimelineStorageService = class TimelineStorageService {
|
|
17
|
+
s3Service;
|
|
18
|
+
constructor(s3Service) {
|
|
19
|
+
this.s3Service = s3Service;
|
|
20
|
+
}
|
|
21
|
+
async saveTimeline(projectId, timeline) {
|
|
22
|
+
const startTime = Date.now();
|
|
23
|
+
const compressed = await compression_util_1.CompressionUtil.compressJSON(timeline);
|
|
24
|
+
const originalSize = JSON.stringify(timeline).length;
|
|
25
|
+
const ratio = compression_util_1.CompressionUtil.compressionRatio(timeline, compressed);
|
|
26
|
+
const key = `client/projects/${projectId}/timeline.json.gz`;
|
|
27
|
+
await this.s3Service.putObject({
|
|
28
|
+
Key: key,
|
|
29
|
+
Body: compressed,
|
|
30
|
+
ContentType: 'application/json',
|
|
31
|
+
ContentEncoding: 'gzip',
|
|
32
|
+
Metadata: {
|
|
33
|
+
'original-size': originalSize.toString(),
|
|
34
|
+
'compressed-size': compressed.length.toString(),
|
|
35
|
+
'compression-ratio': ratio,
|
|
36
|
+
'compressed-at': new Date().toISOString(),
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const duration = Date.now() - startTime;
|
|
40
|
+
console.log(`โ
Timeline saved: ${key} ` +
|
|
41
|
+
`(${compression_util_1.CompressionUtil.formatBytes(compressed.length)}, ` +
|
|
42
|
+
`${ratio}% reduction, ${duration}ms)`);
|
|
43
|
+
return key;
|
|
44
|
+
}
|
|
45
|
+
async loadTimeline(projectId) {
|
|
46
|
+
const startTime = Date.now();
|
|
47
|
+
try {
|
|
48
|
+
const key = `client/projects/${projectId}/timeline.json.gz`;
|
|
49
|
+
const object = await this.s3Service.getObject({ Key: key });
|
|
50
|
+
const buffer = await this.streamToBuffer(object.Body);
|
|
51
|
+
const timeline = await compression_util_1.CompressionUtil.decompressJSON(buffer);
|
|
52
|
+
const duration = Date.now() - startTime;
|
|
53
|
+
console.log(`โ
Timeline loaded (compressed): ${key} ` +
|
|
54
|
+
`(${compression_util_1.CompressionUtil.formatBytes(buffer.length)}, ${duration}ms)`);
|
|
55
|
+
return timeline;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error.name === 'NoSuchKey' || error.statusCode === 404) {
|
|
59
|
+
try {
|
|
60
|
+
const key = `client/projects/${projectId}/timeline.json`;
|
|
61
|
+
const object = await this.s3Service.getObject({ Key: key });
|
|
62
|
+
const buffer = await this.streamToBuffer(object.Body);
|
|
63
|
+
const timeline = JSON.parse(buffer.toString());
|
|
64
|
+
const duration = Date.now() - startTime;
|
|
65
|
+
console.log(`โ ๏ธ Timeline loaded (uncompressed): ${key} ` +
|
|
66
|
+
`(${compression_util_1.CompressionUtil.formatBytes(buffer.length)}, ${duration}ms)`);
|
|
67
|
+
return timeline;
|
|
68
|
+
}
|
|
69
|
+
catch (uncompressedError) {
|
|
70
|
+
console.error(`โ Timeline not found: ${projectId}`);
|
|
71
|
+
throw new Error(`Timeline not found for project: ${projectId}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async saveTimelineVersion(projectId, version, timeline) {
|
|
78
|
+
const compressed = await compression_util_1.CompressionUtil.compressJSON(timeline);
|
|
79
|
+
const key = `client/projects/${projectId}/versions/v${version}.json.gz`;
|
|
80
|
+
await this.s3Service.putObject({
|
|
81
|
+
Key: key,
|
|
82
|
+
Body: compressed,
|
|
83
|
+
ContentType: 'application/json',
|
|
84
|
+
ContentEncoding: 'gzip',
|
|
85
|
+
Metadata: {
|
|
86
|
+
'version': version.toString(),
|
|
87
|
+
'compressed-size': compressed.length.toString(),
|
|
88
|
+
'saved-at': new Date().toISOString(),
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
console.log(`โ
Timeline version saved: ${key} (v${version})`);
|
|
92
|
+
return key;
|
|
93
|
+
}
|
|
94
|
+
async listVersions(projectId) {
|
|
95
|
+
const prefix = `client/projects/${projectId}/versions/`;
|
|
96
|
+
const objects = await this.s3Service.listObjects({ Prefix: prefix });
|
|
97
|
+
return objects.Contents?.map(obj => obj.Key) || [];
|
|
98
|
+
}
|
|
99
|
+
async getTimelineVersion(projectId, version) {
|
|
100
|
+
const key = `client/projects/${projectId}/versions/v${version}.json.gz`;
|
|
101
|
+
const object = await this.s3Service.getObject({ Key: key });
|
|
102
|
+
const buffer = await this.streamToBuffer(object.Body);
|
|
103
|
+
return await compression_util_1.CompressionUtil.decompressJSON(buffer);
|
|
104
|
+
}
|
|
105
|
+
async deleteTimeline(projectId) {
|
|
106
|
+
const mainKey = `client/projects/${projectId}/timeline.json.gz`;
|
|
107
|
+
try {
|
|
108
|
+
await this.s3Service.deleteObject({ Key: mainKey });
|
|
109
|
+
console.log(`โ
Deleted timeline: ${mainKey}`);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
console.warn(`โ ๏ธ Timeline not found: ${mainKey}`);
|
|
113
|
+
}
|
|
114
|
+
const fallbackKey = `client/projects/${projectId}/timeline.json`;
|
|
115
|
+
try {
|
|
116
|
+
await this.s3Service.deleteObject({ Key: fallbackKey });
|
|
117
|
+
console.log(`โ
Deleted uncompressed timeline: ${fallbackKey}`);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
}
|
|
121
|
+
const versions = await this.listVersions(projectId);
|
|
122
|
+
for (const key of versions) {
|
|
123
|
+
await this.s3Service.deleteObject({ Key: key });
|
|
124
|
+
}
|
|
125
|
+
console.log(`โ
Deleted ${versions.length} timeline versions`);
|
|
126
|
+
}
|
|
127
|
+
async streamToBuffer(stream) {
|
|
128
|
+
const chunks = [];
|
|
129
|
+
for await (const chunk of stream) {
|
|
130
|
+
chunks.push(Buffer.from(chunk));
|
|
131
|
+
}
|
|
132
|
+
return Buffer.concat(chunks);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
exports.TimelineStorageService = TimelineStorageService;
|
|
136
|
+
exports.TimelineStorageService = TimelineStorageService = __decorate([
|
|
137
|
+
(0, common_1.Injectable)(),
|
|
138
|
+
__metadata("design:paramtypes", [aws_s3_service_1.AwsService])
|
|
139
|
+
], TimelineStorageService);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ConfigService } from '@nestjs/config';
|
|
2
|
+
import { S3Service } from './s3.service';
|
|
3
|
+
export declare class StreamToDiskHelper {
|
|
4
|
+
private readonly s3Service;
|
|
5
|
+
private readonly configService;
|
|
6
|
+
private readonly isDev;
|
|
7
|
+
private readonly logger;
|
|
8
|
+
constructor(s3Service: S3Service, configService: ConfigService);
|
|
9
|
+
readStreamToDisk(pathToDisk: string, s3FilePath: string): Promise<void>;
|
|
10
|
+
readAndWriteStream(fileUrl: string, pathToDisk: string): Promise<unknown>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
var StreamToDiskHelper_1;
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.StreamToDiskHelper = void 0;
|
|
17
|
+
const common_1 = require("@nestjs/common");
|
|
18
|
+
const config_1 = require("@nestjs/config");
|
|
19
|
+
const s3_service_1 = require("./s3.service");
|
|
20
|
+
const fs_1 = __importDefault(require("fs"));
|
|
21
|
+
const path_1 = __importDefault(require("path"));
|
|
22
|
+
const axios_1 = __importDefault(require("axios"));
|
|
23
|
+
let StreamToDiskHelper = StreamToDiskHelper_1 = class StreamToDiskHelper {
|
|
24
|
+
s3Service;
|
|
25
|
+
configService;
|
|
26
|
+
isDev;
|
|
27
|
+
logger = new common_1.Logger(StreamToDiskHelper_1.name);
|
|
28
|
+
constructor(s3Service, configService) {
|
|
29
|
+
this.s3Service = s3Service;
|
|
30
|
+
this.configService = configService;
|
|
31
|
+
this.isDev = this.configService.get('isDevelopment');
|
|
32
|
+
}
|
|
33
|
+
async readStreamToDisk(pathToDisk, s3FilePath) {
|
|
34
|
+
if (this.isDev) {
|
|
35
|
+
if (!pathToDisk.toLowerCase().includes('c:')) {
|
|
36
|
+
pathToDisk = path_1.default.join(process.cwd(), pathToDisk);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const readStream = await this.s3Service.getStreamFromAws(s3FilePath);
|
|
40
|
+
const writeStream = fs_1.default.createWriteStream(path_1.default.join(pathToDisk));
|
|
41
|
+
const readStreamToFile = () => new Promise((resolve, reject) => {
|
|
42
|
+
readStream
|
|
43
|
+
.pipe(writeStream)
|
|
44
|
+
.on('finish', () => {
|
|
45
|
+
this.logger.debug('finsihed writing to disk');
|
|
46
|
+
resolve(null);
|
|
47
|
+
return;
|
|
48
|
+
})
|
|
49
|
+
.on('error', (err) => {
|
|
50
|
+
this.logger.debug('error in writing to disk');
|
|
51
|
+
this.logger.error(err.message);
|
|
52
|
+
return reject(new Error(err.message));
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
await readStreamToFile().catch((e) => {
|
|
56
|
+
throw e;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async readAndWriteStream(fileUrl, pathToDisk) {
|
|
60
|
+
if (this.isDev) {
|
|
61
|
+
if (!pathToDisk.toLowerCase().includes('c:')) {
|
|
62
|
+
pathToDisk = path_1.default.join(process.cwd(), pathToDisk);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const writer = fs_1.default.createWriteStream(pathToDisk);
|
|
66
|
+
return (0, axios_1.default)({
|
|
67
|
+
method: 'get',
|
|
68
|
+
url: fileUrl,
|
|
69
|
+
responseType: 'stream',
|
|
70
|
+
}).then((response) => {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
response.data.pipe(writer);
|
|
73
|
+
let error = null;
|
|
74
|
+
writer.on('error', (err) => {
|
|
75
|
+
error = err;
|
|
76
|
+
writer.close();
|
|
77
|
+
reject(err);
|
|
78
|
+
});
|
|
79
|
+
writer.on('close', () => {
|
|
80
|
+
if (!error) {
|
|
81
|
+
resolve(true);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
exports.StreamToDiskHelper = StreamToDiskHelper;
|
|
89
|
+
exports.StreamToDiskHelper = StreamToDiskHelper = StreamToDiskHelper_1 = __decorate([
|
|
90
|
+
(0, common_1.Injectable)(),
|
|
91
|
+
__metadata("design:paramtypes", [s3_service_1.S3Service,
|
|
92
|
+
config_1.ConfigService])
|
|
93
|
+
], StreamToDiskHelper);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare class CompressionUtil {
|
|
2
|
+
static compressJSON(data: any): Promise<Buffer>;
|
|
3
|
+
static decompressJSON(buffer: Buffer): Promise<any>;
|
|
4
|
+
static compressionRatio(original: any, compressed: Buffer): string;
|
|
5
|
+
static formatBytes(bytes: number): string;
|
|
6
|
+
static testCompression(sampleData: any): Promise<void>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CompressionUtil = void 0;
|
|
4
|
+
const util_1 = require("util");
|
|
5
|
+
const zlib_1 = require("zlib");
|
|
6
|
+
class CompressionUtil {
|
|
7
|
+
static async compressJSON(data) {
|
|
8
|
+
const json = JSON.stringify(data);
|
|
9
|
+
const gzipPromise = (0, util_1.promisify)(zlib_1.gzip);
|
|
10
|
+
return await gzipPromise(json);
|
|
11
|
+
}
|
|
12
|
+
static async decompressJSON(buffer) {
|
|
13
|
+
const gunzipPromise = (0, util_1.promisify)(zlib_1.gunzip);
|
|
14
|
+
const decompressed = await gunzipPromise(buffer);
|
|
15
|
+
return JSON.parse(decompressed.toString());
|
|
16
|
+
}
|
|
17
|
+
static compressionRatio(original, compressed) {
|
|
18
|
+
const originalSize = JSON.stringify(original).length;
|
|
19
|
+
const compressedSize = compressed.length;
|
|
20
|
+
return ((1 - compressedSize / originalSize) * 100).toFixed(2);
|
|
21
|
+
}
|
|
22
|
+
static formatBytes(bytes) {
|
|
23
|
+
if (bytes === 0)
|
|
24
|
+
return '0 Bytes';
|
|
25
|
+
const k = 1024;
|
|
26
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
27
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
28
|
+
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
|
|
29
|
+
}
|
|
30
|
+
static async testCompression(sampleData) {
|
|
31
|
+
console.log('\n๐งช Testing Compression...');
|
|
32
|
+
const originalSize = JSON.stringify(sampleData).length;
|
|
33
|
+
console.log(`๐ฆ Original size: ${this.formatBytes(originalSize)}`);
|
|
34
|
+
const compressed = await this.compressJSON(sampleData);
|
|
35
|
+
console.log(`๐๏ธ Compressed size: ${this.formatBytes(compressed.length)}`);
|
|
36
|
+
const ratio = this.compressionRatio(sampleData, compressed);
|
|
37
|
+
console.log(`๐ Compression ratio: ${ratio}%`);
|
|
38
|
+
const decompressed = await this.decompressJSON(compressed);
|
|
39
|
+
const isValid = JSON.stringify(sampleData) === JSON.stringify(decompressed);
|
|
40
|
+
console.log(`โ
Decompression valid: ${isValid}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.CompressionUtil = CompressionUtil;
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clhq-s3-module",
|
|
3
|
+
"version": "1.1.0-alpha.90",
|
|
4
|
+
"description": "Reusable S3 service module for NestJS.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist/**/*"
|
|
9
|
+
],
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/reacthub-pricematch/clhq-api-monorepo"
|
|
13
|
+
},
|
|
14
|
+
"registry": "https://registry.npmjs.org/",
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"registry": "https://registry.npmjs.org/",
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/reacthub-pricematch/clhq-api-monorepo/issues"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "yarn exec tsc -p tsconfig.json",
|
|
25
|
+
"test": "jest",
|
|
26
|
+
"version:upgrade:alpha": "standard-version --prerelease alpha",
|
|
27
|
+
"release": "npm publish --tag alpha",
|
|
28
|
+
"release:alpha": "npm publish --tag alpha",
|
|
29
|
+
"release:prealpha": "",
|
|
30
|
+
"clean:build": "rimraf .build",
|
|
31
|
+
"clean:dist": "rimraf dist",
|
|
32
|
+
"clean:webpack": "rimraf .webpack",
|
|
33
|
+
"clean:serverless": "rimraf .serverless",
|
|
34
|
+
"clean:all": "npm run clean:build&& npm run clean:dist&& npm run clean:webpack&& npm run clean:serverless",
|
|
35
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
36
|
+
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@nestjs/common": ">=11.0.0",
|
|
40
|
+
"@nestjs/core": ">=11.0.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@aws-sdk/client-s3": "3.128.0",
|
|
44
|
+
"reflect-metadata": "^0.2.2",
|
|
45
|
+
"rxjs": "^7.8.1",
|
|
46
|
+
"standard-version": "^9.5.0"
|
|
47
|
+
}
|
|
48
|
+
}
|