@tomei/media 0.1.0

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 (121) hide show
  1. package/.commitlintrc.json +22 -0
  2. package/.eslintrc.js +66 -0
  3. package/.husky/commit-msg +4 -0
  4. package/.husky/pre-commit +4 -0
  5. package/.prettierrc +4 -0
  6. package/.prettierrc copy +4 -0
  7. package/README.md +93 -0
  8. package/dist/base/base.medias.d.ts +37 -0
  9. package/dist/base/base.medias.js +161 -0
  10. package/dist/base/base.medias.js.map +1 -0
  11. package/dist/base/base.repository.abstract.d.ts +12 -0
  12. package/dist/base/base.repository.abstract.js +22 -0
  13. package/dist/base/base.repository.abstract.js.map +1 -0
  14. package/dist/base/base.repository.interface.d.ts +9 -0
  15. package/dist/base/base.repository.interface.js +3 -0
  16. package/dist/base/base.repository.interface.js.map +1 -0
  17. package/dist/common/common.module.d.ts +2 -0
  18. package/dist/common/common.module.js +23 -0
  19. package/dist/common/common.module.js.map +1 -0
  20. package/dist/common/common.service.d.ts +13 -0
  21. package/dist/common/common.service.js +108 -0
  22. package/dist/common/common.service.js.map +1 -0
  23. package/dist/common/dto/add-field-translation.dto.d.ts +7 -0
  24. package/dist/common/dto/add-field-translation.dto.js +42 -0
  25. package/dist/common/dto/add-field-translation.dto.js.map +1 -0
  26. package/dist/common/dto/get-media.dto.d.ts +5 -0
  27. package/dist/common/dto/get-media.dto.js +29 -0
  28. package/dist/common/dto/get-media.dto.js.map +1 -0
  29. package/dist/dto/external-media.dto.d.ts +10 -0
  30. package/dist/dto/external-media.dto.js +70 -0
  31. package/dist/dto/external-media.dto.js.map +1 -0
  32. package/dist/dto/internal-medias.dto.d.ts +12 -0
  33. package/dist/dto/internal-medias.dto.js +87 -0
  34. package/dist/dto/internal-medias.dto.js.map +1 -0
  35. package/dist/entities/medias.entity.d.ts +21 -0
  36. package/dist/entities/medias.entity.js +152 -0
  37. package/dist/entities/medias.entity.js.map +1 -0
  38. package/dist/enum/index.d.ts +2 -0
  39. package/dist/enum/index.js +6 -0
  40. package/dist/enum/index.js.map +1 -0
  41. package/dist/enum/medias.enum.d.ts +5 -0
  42. package/dist/enum/medias.enum.js +10 -0
  43. package/dist/enum/medias.enum.js.map +1 -0
  44. package/dist/helpers/error.d.ts +1 -0
  45. package/dist/helpers/error.js +14 -0
  46. package/dist/helpers/error.js.map +1 -0
  47. package/dist/helpers/index.d.ts +2 -0
  48. package/dist/helpers/index.js +6 -0
  49. package/dist/helpers/index.js.map +1 -0
  50. package/dist/index.d.ts +6 -0
  51. package/dist/index.js +14 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/interfaces/base.medias-attr.interface.d.ts +20 -0
  54. package/dist/interfaces/base.medias-attr.interface.js +3 -0
  55. package/dist/interfaces/base.medias-attr.interface.js.map +1 -0
  56. package/dist/interfaces/medias.repository.interface.d.ts +3 -0
  57. package/dist/interfaces/medias.repository.interface.js +3 -0
  58. package/dist/interfaces/medias.repository.interface.js.map +1 -0
  59. package/dist/medias.controller.d.ts +25 -0
  60. package/dist/medias.controller.js +225 -0
  61. package/dist/medias.controller.js.map +1 -0
  62. package/dist/medias.d.ts +42 -0
  63. package/dist/medias.js +373 -0
  64. package/dist/medias.js.map +1 -0
  65. package/dist/medias.module.d.ts +2 -0
  66. package/dist/medias.module.js +32 -0
  67. package/dist/medias.module.js.map +1 -0
  68. package/dist/medias.repository.d.ts +16 -0
  69. package/dist/medias.repository.js +47 -0
  70. package/dist/medias.repository.js.map +1 -0
  71. package/dist/pipe/append-id.pipe.d.ts +6 -0
  72. package/dist/pipe/append-id.pipe.js +28 -0
  73. package/dist/pipe/append-id.pipe.js.map +1 -0
  74. package/dist/pipe/validate-enum.pipe.d.ts +6 -0
  75. package/dist/pipe/validate-enum.pipe.js +34 -0
  76. package/dist/pipe/validate-enum.pipe.js.map +1 -0
  77. package/dist/pipe/validate-id.pipe.d.ts +6 -0
  78. package/dist/pipe/validate-id.pipe.js +31 -0
  79. package/dist/pipe/validate-id.pipe.js.map +1 -0
  80. package/dist/pipe/validate-search.pipe.d.ts +7 -0
  81. package/dist/pipe/validate-search.pipe.js +46 -0
  82. package/dist/pipe/validate-search.pipe.js.map +1 -0
  83. package/dist/responses/deleted.response.d.ts +3 -0
  84. package/dist/responses/deleted.response.js +21 -0
  85. package/dist/responses/deleted.response.js.map +1 -0
  86. package/dist/responses/pagination.response.d.ts +5 -0
  87. package/dist/responses/pagination.response.js +26 -0
  88. package/dist/responses/pagination.response.js.map +1 -0
  89. package/dist/tsconfig.tsbuildinfo +1 -0
  90. package/nest-cli.json +19 -0
  91. package/package.json +60 -0
  92. package/src/base/base.medias.ts +200 -0
  93. package/src/base/base.repository.abstract.ts +28 -0
  94. package/src/base/base.repository.interface.ts +9 -0
  95. package/src/common/common.module.ts +10 -0
  96. package/src/common/common.service.ts +109 -0
  97. package/src/common/dto/add-field-translation.dto.ts +23 -0
  98. package/src/common/dto/get-media.dto.ts +12 -0
  99. package/src/dto/external-media.dto.ts +49 -0
  100. package/src/dto/internal-medias.dto.ts +64 -0
  101. package/src/entities/medias.entity.ts +125 -0
  102. package/src/enum/index.ts +3 -0
  103. package/src/enum/medias.enum.ts +5 -0
  104. package/src/helpers/error.ts +12 -0
  105. package/src/helpers/index.ts +3 -0
  106. package/src/index.ts +13 -0
  107. package/src/interfaces/base.medias-attr.interface.ts +21 -0
  108. package/src/interfaces/medias.repository.interface.ts +4 -0
  109. package/src/medias.controller.ts +244 -0
  110. package/src/medias.module.ts +19 -0
  111. package/src/medias.repository.ts +44 -0
  112. package/src/medias.ts +494 -0
  113. package/src/pipe/append-id.pipe.ts +15 -0
  114. package/src/pipe/validate-enum.pipe.ts +31 -0
  115. package/src/pipe/validate-id.pipe.ts +18 -0
  116. package/src/pipe/validate-search.pipe.ts +41 -0
  117. package/src/responses/deleted.response.ts +6 -0
  118. package/src/responses/pagination.response.ts +10 -0
  119. package/tsconfig.build.json +4 -0
  120. package/tsconfig.json +30 -0
  121. package/tslint.json +18 -0
package/src/medias.ts ADDED
@@ -0,0 +1,494 @@
1
+ import { BlobServiceClient } from '@azure/storage-blob';
2
+ import { BadRequestException } from '@nestjs/common';
3
+ import * as crypto from 'crypto';
4
+ import * as fs from 'fs';
5
+ import { CommonService } from './common/common.service';
6
+ import * as util from 'util';
7
+ import { BaseMedias } from './base/base.medias';
8
+ import { MediasModel } from './entities/medias.entity';
9
+ import { IBaseMediasAttr } from './interfaces/base.medias-attr.interface';
10
+ import { IMediasRepository } from './interfaces/medias.repository.interface';
11
+ import { Readable } from 'stream';
12
+
13
+ type MediaFile = {
14
+ buffer: Buffer;
15
+ iv?: Buffer;
16
+ isEncrypted?: boolean;
17
+ };
18
+
19
+ type FilePath = {
20
+ filePath: string;
21
+ ivFilePath: string;
22
+ };
23
+
24
+ export class Medias extends BaseMedias {
25
+ blobServiceClient = BlobServiceClient.fromConnectionString(
26
+ process.env.AZURE_STORAGE_CONNECTION_STRING,
27
+ );
28
+ container: string;
29
+ algorithm = 'aes-256-cbc';
30
+ commonService: CommonService;
31
+ entity = typeof MediasModel;
32
+
33
+ constructor(
34
+ mediaRepository: IMediasRepository,
35
+ commonService: CommonService,
36
+ media?: IBaseMediasAttr,
37
+ ) {
38
+ super(mediaRepository, media);
39
+ this.commonService = commonService;
40
+ try {
41
+ this.container = process.env.MEDIA_AZUREBLOB_CONTAINER_NAME;
42
+ if (!this.container && this.IsStorageTypeAzure()) {
43
+ throw new BadRequestException(
44
+ 'CONTAINER_NAME Environtment Variable name not found',
45
+ );
46
+ }
47
+ } catch (error) {
48
+ throw error;
49
+ }
50
+ }
51
+
52
+ public async createMedias(
53
+ isExternalMedias: boolean,
54
+ stream?: Express.Multer.File,
55
+ ) {
56
+ try {
57
+ const media = await super.create();
58
+ let activityDescription = `Added a new external media Id: ${this.MediaId}.`;
59
+ if (!isExternalMedias) {
60
+ activityDescription = `Added a new media Id: ${this.MediaId}.`;
61
+ await this.createInternalMedias(stream);
62
+ }
63
+ if (media) {
64
+ await this.commonService.addActivityHistory(
65
+ {
66
+ Action: 'Add',
67
+ Activity: 'Add New Media',
68
+ Description: activityDescription,
69
+ EntityId: this.MediaId,
70
+ EntityValueBefore: '',
71
+ EntityValueAfter: JSON.stringify({ ...media.get({ plain: true }) }),
72
+ PerformedById: this.CreatedById,
73
+ PerformedAt: this.CreatedAt,
74
+ },
75
+ this.entity,
76
+ '',
77
+ );
78
+ }
79
+ return media;
80
+ } catch (error) {
81
+ throw error;
82
+ }
83
+ }
84
+
85
+ public async updateMedias(
86
+ isExternalMedias: boolean,
87
+ stream?: Express.Multer.File,
88
+ ) {
89
+ try {
90
+ const oldMedia = await super.findOne({where: { MediaId: this.MediaId}});
91
+ const media = await super.update();
92
+ let activityDescription = `Updated external media Id: ${this.MediaId}.`;
93
+ if (!isExternalMedias) {
94
+ await this.updateInternalMedia(stream, oldMedia);
95
+ activityDescription = `Updated media Id: ${this.MediaId}.`;
96
+ }
97
+ if (media) {
98
+ await this.commonService.addActivityHistory(
99
+ {
100
+ Action: 'Update',
101
+ Activity: 'Update Media',
102
+ Description: activityDescription,
103
+ EntityId: this.MediaId,
104
+ EntityValueBefore: JSON.stringify({
105
+ ...oldMedia.get({ plain: true }),
106
+ }),
107
+ EntityValueAfter: JSON.stringify({ ...media.get({ plain: true }) }),
108
+ PerformedById: this.UpdatedById,
109
+ PerformedAt: this.UpdatedAt,
110
+ },
111
+ this.entity,
112
+ '',
113
+ );
114
+ }
115
+
116
+ return media;
117
+ } catch (error) {
118
+ throw error;
119
+ }
120
+ }
121
+
122
+ private IsStorageTypeAzure(): boolean {
123
+ const mediaStorageType = process.env.MEDIA_STORAGE_TYPE;
124
+ if (mediaStorageType === 'azure') {
125
+ return true;
126
+ } else {
127
+ return false;
128
+ }
129
+ }
130
+
131
+ private async updateInternalMedia(
132
+ stream: Express.Multer.File,
133
+ oldMedia: MediasModel,
134
+ ): Promise<void> {
135
+ try {
136
+ this.FileExtension = this.FileExtension.split('.')[0];
137
+ this.FileName = this.FileName.replace(/\.[^/.]+$/, '');
138
+
139
+ let file: MediaFile;
140
+ if (this.IsEncryptedYN == 'Y') {
141
+ file = await this.encrypt(stream.buffer);
142
+ } else {
143
+ file = {
144
+ buffer: stream.buffer,
145
+ isEncrypted: false,
146
+ };
147
+ }
148
+ if (this.IsStorageTypeAzure()) {
149
+ await this.deleteUploadedFileFromAzure(
150
+ oldMedia.URL,
151
+ oldMedia.FileName,
152
+ oldMedia.FileExtension,
153
+ oldMedia.IsEncryptedYN,
154
+ );
155
+
156
+ await this.uploadFileToAzure(file);
157
+ } else {
158
+ const filePath = super.createSaveLocation();
159
+ const isDirExist = util.promisify(fs.exists);
160
+ const isDir = await isDirExist(filePath);
161
+ if (isDir) {
162
+ fs.unlinkSync(this.FilePath);
163
+ if (this.IsEncryptedYN == 'Y') {
164
+ fs.unlinkSync(this.getIvPath());
165
+ }
166
+ }
167
+ await this.uploadFileToLocal(file);
168
+ }
169
+ } catch (error) {
170
+ throw error;
171
+ }
172
+ }
173
+
174
+ private async createInternalMedias(
175
+ stream: Express.Multer.File,
176
+ ): Promise<void> {
177
+ try {
178
+ this.FileExtension = this.FileExtension.split('.')[0];
179
+ this.FileName = this.FileName.replace(/\.[^/.]+$/, '');
180
+ let file: MediaFile;
181
+ if (this.IsEncryptedYN == 'Y') {
182
+ file = await this.encrypt(stream.buffer);
183
+ } else {
184
+ file = {
185
+ buffer: stream.buffer,
186
+ isEncrypted: false,
187
+ };
188
+ }
189
+ if (this.IsStorageTypeAzure()) {
190
+ this.uploadFileToAzure(file);
191
+ } else {
192
+ this.uploadFileToLocal(file);
193
+ }
194
+ } catch (error) {
195
+ throw error;
196
+ }
197
+ }
198
+
199
+ private getIvPath() {
200
+ return super.createSaveLocation() + `/${this.FileName}iv.iv`;
201
+ }
202
+
203
+ private async uploadFileToLocal(file: MediaFile): Promise<FilePath> {
204
+ try {
205
+ const savelocation = super.createSaveLocation();
206
+ const isDirExist = util.promisify(fs.exists);
207
+ const createDir = util.promisify(fs.mkdir);
208
+ const isDir = await isDirExist(savelocation);
209
+ if (!isDir) {
210
+ await createDir(savelocation, { recursive: true });
211
+ }
212
+
213
+ fs.createWriteStream(this.FilePath).write(file.buffer);
214
+ fs.createWriteStream(this.getIvPath()).write(file.iv);
215
+ const path: FilePath = {
216
+ filePath: super.createSaveLocation(),
217
+ ivFilePath: this.getIvPath(),
218
+ };
219
+
220
+ return path;
221
+ } catch (error) {
222
+ throw error;
223
+ }
224
+ }
225
+
226
+ private async deleteUploadedFileFromLocal(
227
+ path:string,
228
+ isEncryptedYN:string,
229
+ ): Promise<void> {
230
+ try {
231
+ const isDirExist = util.promisify(fs.exists);
232
+ const isDir = await isDirExist(path);
233
+ if (isDir) {
234
+ const deleteFile = util.promisify(fs.unlink);
235
+ await deleteFile(path);
236
+ if (isEncryptedYN == 'Y') {
237
+ await deleteFile(this.getIvPath());
238
+ }
239
+ }
240
+ } catch (error) {
241
+ throw error;
242
+ }
243
+ }
244
+
245
+ private async uploadFileToAzure(file: MediaFile): Promise<FilePath> {
246
+ const filePath = await this.uploadFileToAzureStorage(
247
+ file.buffer,
248
+ this.ObjectType,
249
+ this.ObjectId,
250
+ this.FileName,
251
+ this.FileExtension,
252
+ );
253
+
254
+ let ivPath: string;
255
+ if (this.IsEncryptedYN == 'Y') {
256
+ ivPath = await this.uploadFileToAzureStorage(
257
+ file.iv,
258
+ this.ObjectType,
259
+ this.ObjectId,
260
+ this.FileName+"iv",
261
+ 'iv',
262
+ );
263
+ }
264
+
265
+ const path: FilePath = {
266
+ filePath: filePath,
267
+ ivFilePath: ivPath,
268
+ };
269
+
270
+ return path;
271
+ }
272
+
273
+ private async deleteUploadedFileFromAzure(
274
+ path: string,
275
+ FileName: string,
276
+ FileExtension: string,
277
+ isEncryptedYN: string,
278
+ ): Promise<void> {
279
+ try {
280
+ await this.deleteUploadedFileToAzureStorage(path, FileName, FileExtension);
281
+ if (isEncryptedYN == 'Y') {
282
+ await this.deleteUploadedFileToAzureStorage(path, FileName+"iv", '.iv');
283
+ }
284
+ } catch (error) {
285
+ throw error;
286
+ }
287
+ }
288
+
289
+ private async uploadFileToAzureStorage(
290
+ bufferFile: Buffer,
291
+ ObjectType: string,
292
+ ObjectId: string,
293
+ FileName: string,
294
+ FileExtension: string,
295
+ ): Promise<string> {
296
+ try {
297
+ const fileName = FileName.replace(/\.[^/.]+$/, '');
298
+ const path = `${this.container}/${ObjectType}/${ObjectId}`;
299
+ const blobcontainer = this.blobServiceClient.getContainerClient(path);
300
+ const blockBlobClient = await blobcontainer.getBlockBlobClient(
301
+ `${fileName}.${FileExtension}`,
302
+ );
303
+ await blockBlobClient.uploadData(bufferFile);
304
+ return path;
305
+ } catch (error) {
306
+ throw error;
307
+ }
308
+ }
309
+
310
+ async deleteUploadedFileToAzureStorage(
311
+ path: string,
312
+ FileName: string,
313
+ FileExtension: string,
314
+ ): Promise<any> {
315
+ const fileName = FileName.replace(/\.[^/.]+$/, '');
316
+ const blobcontainer = this.blobServiceClient.getContainerClient(path);
317
+ const blockBlobClient = await blobcontainer.getBlockBlobClient(
318
+ `${fileName}.${FileExtension}`,
319
+ );
320
+ return await blockBlobClient.deleteIfExists();
321
+ }
322
+
323
+ private setKey(): string {
324
+ try {
325
+ const key = process.env.MEDIA_ENCRYPT_KEY;
326
+ if (!key) {
327
+ throw new BadRequestException(
328
+ 'MEDIA_ENCRYPT_KEY Environtment Variable name not found',
329
+ );
330
+ }
331
+ return key;
332
+ } catch (error) {
333
+ throw error;
334
+ }
335
+ }
336
+
337
+ async getFile(): Promise<Buffer> {
338
+ try {
339
+ let fileBuffer: Buffer;
340
+ if (this.IsStorageTypeAzure()) {
341
+ fileBuffer = await this.getFileFromAzure(
342
+ this.URL,
343
+ this.FileName,
344
+ this.FileExtension,
345
+ );
346
+ } else {
347
+ fileBuffer = await this.getFileFromLocal(this.FilePath);
348
+ }
349
+
350
+ let file: MediaFile = {
351
+ buffer: fileBuffer,
352
+ isEncrypted: this.IsEncryptedYN === 'Y' ? true : false,
353
+ };
354
+ if (file.isEncrypted) {
355
+ if (this.IsStorageTypeAzure()) {
356
+ file.iv = await this.getFileFromAzure(
357
+ this.URL,
358
+ this.FileName+"iv",
359
+ 'iv'
360
+ );
361
+ } else {
362
+ file.iv = await this.getFileFromLocal(this.getIvPath());
363
+ }
364
+ file = await this.decrypt(file);
365
+ }
366
+ return file.buffer;
367
+ } catch (error) {
368
+ throw error;
369
+ }
370
+ }
371
+
372
+ private async stream2buffer(stream: Readable | fs.ReadStream): Promise<Buffer> {
373
+ return new Promise<Buffer>((resolve, reject) => {
374
+ const buffer = Array<any>();
375
+
376
+ stream.on('data', (chunk) => buffer.push(chunk));
377
+ stream.on('end', () => resolve(Buffer.concat(buffer)));
378
+ stream.on('error', (err) => reject(`error converting stream - ${err}`));
379
+ });
380
+ }
381
+
382
+ private async getFileFromAzure(
383
+ path: string,
384
+ FileName: string,
385
+ FileExtension: string,
386
+ ): Promise<Buffer> {
387
+ try {
388
+ const fileName = FileName.replace(/\.[^/.]+$/, '');
389
+ const blobcontainer = this.blobServiceClient.getContainerClient(path);
390
+ const blockBlobClient = await blobcontainer.getBlockBlobClient(
391
+ `${fileName}.${FileExtension}`,
392
+ );
393
+ const downloadResponse = await blockBlobClient.download();
394
+ const ReadStream = new Readable().wrap(downloadResponse.readableStreamBody);
395
+ const file = await this.stream2buffer(ReadStream);
396
+ return file
397
+ } catch (error) {
398
+ throw error;
399
+ }
400
+ }
401
+
402
+ private async getFileFromLocal(pathfile: string): Promise<Buffer> {
403
+ try {
404
+ const stream = fs.createReadStream(pathfile);
405
+ const file = await this.stream2buffer(stream);
406
+ return file;
407
+ } catch (error) {
408
+ throw error;
409
+ }
410
+ }
411
+
412
+ public async encrypt(fileBuffer: Buffer): Promise<MediaFile> {
413
+ try {
414
+ const key = this.setKey();
415
+ const iv = crypto.randomBytes(16);
416
+
417
+ const cipher = crypto.createCipheriv(this.algorithm, key, iv);
418
+ const resultBuffer = Buffer.concat([
419
+ cipher.update(fileBuffer),
420
+ cipher.final(),
421
+ ]);
422
+
423
+ const result: MediaFile = {
424
+ buffer: resultBuffer,
425
+ iv: iv,
426
+ isEncrypted: true,
427
+ };
428
+
429
+ return result;
430
+ } catch (error) {
431
+ throw error;
432
+ }
433
+ }
434
+
435
+ public async decrypt(file: MediaFile): Promise<MediaFile> {
436
+ try {
437
+ const key = this.setKey();
438
+ const iv = file.iv;
439
+ const decipher = crypto.createDecipheriv(this.algorithm, key, iv);
440
+ const resultBuffer = Buffer.concat([
441
+ decipher.update(file.buffer),
442
+ decipher.final(),
443
+ ]);
444
+
445
+ const result: MediaFile = {
446
+ buffer: resultBuffer,
447
+ isEncrypted: false,
448
+ };
449
+
450
+ return result;
451
+ } catch (error) {
452
+ throw error;
453
+ }
454
+ }
455
+
456
+ async delete(): Promise<any> {
457
+ try {
458
+ const data: MediasModel = await super.delete();
459
+ if (this.IsExternalYN == 'N') {
460
+ if (this.IsStorageTypeAzure()) {
461
+ await this.deleteUploadedFileFromAzure(
462
+ this.URL,
463
+ this.FileName,
464
+ this.FileExtension,
465
+ this.IsEncryptedYN,
466
+ );
467
+ } else {
468
+ await this.deleteUploadedFileFromLocal(
469
+ this.URL,
470
+ this.IsEncryptedYN
471
+ );
472
+ }
473
+ }
474
+
475
+ await this.commonService.addActivityHistory(
476
+ {
477
+ Action: 'Delete',
478
+ Activity: 'Delete Media',
479
+ Description: `Deleted media (ID: ${this.MediaId})`,
480
+ EntityId: this.MediaId,
481
+ EntityValueBefore: JSON.stringify(data),
482
+ EntityValueAfter: '',
483
+ PerformedById: this.UpdatedById,
484
+ PerformedAt: new Date(),
485
+ },
486
+ this.entity,
487
+ '',
488
+ );
489
+ return { message: 'Media has been deleted.' }
490
+ } catch (error) {
491
+ throw error
492
+ }
493
+ }
494
+ }
@@ -0,0 +1,15 @@
1
+ import { Injectable, PipeTransform } from '@nestjs/common';
2
+ import * as cuid from 'cuid';
3
+
4
+ @Injectable()
5
+ export class AppendIdPipe implements PipeTransform {
6
+ private readonly idName: string;
7
+
8
+ constructor(idName: string) {
9
+ this.idName = idName;
10
+ }
11
+
12
+ transform(value: any) {
13
+ return { ...value, [this.idName]: cuid() };
14
+ }
15
+ }
@@ -0,0 +1,31 @@
1
+ import { BadRequestException, PipeTransform } from '@nestjs/common';
2
+ import { isDefined, isEnum } from 'class-validator';
3
+
4
+ export class ValidateEnumPipe
5
+ implements PipeTransform<string, Promise<any> | string | string[]>
6
+ {
7
+ constructor(private enumEntity: any) {}
8
+
9
+ transform(value: string): Promise<any> | string | string[] {
10
+ const validateEnum = (enumValue) => {
11
+ if (isDefined(enumValue)) {
12
+ if (isEnum(enumValue, this.enumEntity)) {
13
+ return this.enumEntity[enumValue];
14
+ } else {
15
+ const errorMessage = `the value ${enumValue} is not valid. See the acceptable values: ${Object.keys(
16
+ this.enumEntity,
17
+ ).map((key) => this.enumEntity[key])}`;
18
+ throw new BadRequestException(errorMessage);
19
+ }
20
+ } else {
21
+ return value;
22
+ }
23
+ };
24
+
25
+ if (Array.isArray(value)) {
26
+ return value.map((item) => validateEnum(item));
27
+ } else {
28
+ return validateEnum(value);
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,18 @@
1
+ import { BadRequestException, Injectable, PipeTransform } from '@nestjs/common';
2
+ import * as cuid from 'cuid';
3
+
4
+ @Injectable()
5
+ export class ValidateIdPipe implements PipeTransform {
6
+ errorMessage: string;
7
+
8
+ constructor(messageOverrides = 'Invalid id.') {
9
+ this.errorMessage = messageOverrides;
10
+ }
11
+
12
+ transform(value: string) {
13
+ if (value && !cuid.isCuid(value)) {
14
+ throw new BadRequestException(this.errorMessage);
15
+ }
16
+ return value;
17
+ }
18
+ }
@@ -0,0 +1,41 @@
1
+ import {
2
+ BadRequestException,
3
+ Injectable,
4
+ // Logger,
5
+ PipeTransform,
6
+ } from '@nestjs/common';
7
+
8
+ @Injectable()
9
+ export class ValidateSearchPipe implements PipeTransform {
10
+ constructor(private model: any, private additonalAttributes: string[] = []) {}
11
+
12
+ transform(value: string) {
13
+ let search = {};
14
+
15
+ if (value) {
16
+ try {
17
+ search = JSON.parse(value);
18
+ } catch (err) {
19
+ throw new BadRequestException('Bad value for search.');
20
+ }
21
+ const attributes: string[] = [
22
+ ...this.additonalAttributes,
23
+ ...Object.keys(this.model['tableAttributes']),
24
+ ];
25
+ const searchAttributes: string[] = Object.keys(search);
26
+ const invalidKeys = searchAttributes.filter(
27
+ (value) => !attributes.includes(value),
28
+ );
29
+
30
+ if (invalidKeys.length > 0) {
31
+ throw new BadRequestException(
32
+ `${invalidKeys} ${
33
+ invalidKeys.length > 1 ? 'are invalid keys' : 'is an invalid key'
34
+ } for search query`,
35
+ );
36
+ }
37
+ }
38
+
39
+ return value;
40
+ }
41
+ }
@@ -0,0 +1,6 @@
1
+ import { ApiProperty } from '@nestjs/swagger';
2
+
3
+ export class DeletedMediasResponse {
4
+ @ApiProperty({ example: 'EzCash slot group has been deleted.' })
5
+ message: string;
6
+ }
@@ -0,0 +1,10 @@
1
+ import { ApiProperty } from '@nestjs/swagger';
2
+ import { MediasModel } from '../entities/medias.entity';
3
+
4
+ export class PaginatedMediasResponse {
5
+ @ApiProperty()
6
+ count: number;
7
+
8
+ @ApiProperty({ isArray: true, type: MediasModel })
9
+ rows: MediasModel[];
10
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "declaration": true,
5
+ "removeComments": true,
6
+ "emitDecoratorMetadata": true,
7
+ "experimentalDecorators": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "moduleResolution": "node",
10
+ "target": "es2017",
11
+ "sourceMap": true,
12
+ "outDir": "./dist",
13
+ "baseUrl": "./",
14
+ "incremental": true,
15
+ "skipLibCheck": true,
16
+ "strictNullChecks": false,
17
+ "noImplicitAny": false,
18
+ "strictBindCallApply": false,
19
+ "forceConsistentCasingInFileNames": false,
20
+ "noFallthroughCasesInSwitch": false,
21
+ "paths": {
22
+ "@app/media": [
23
+ "libs/media/src"
24
+ ],
25
+ "@app/media/*": [
26
+ "libs/media/src/*"
27
+ ]
28
+ }
29
+ }
30
+ }
package/tslint.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "defaultSeverity": "error",
3
+ "extends": ["tslint:recommended"],
4
+ "jsRules": {
5
+ "no-unused-expression": true
6
+ },
7
+ "rules": {
8
+ "quotemark": [true, "single"],
9
+ "member-access": [false],
10
+ "ordered-imports": [false],
11
+ "max-line-length": [true, 150],
12
+ "member-ordering": [false],
13
+ "interface-name": [false],
14
+ "arrow-parens": false,
15
+ "object-literal-sort-keys": false
16
+ },
17
+ "rulesDirectory": []
18
+ }