1
1
import { Injectable } from '@nestjs/common' ;
2
2
import { Logger } from '@us-epa-camd/easey-common/logger' ;
3
- import { validate } from 'class-validator' ;
4
3
import { EntityManager , In } from 'typeorm' ;
5
4
6
- import { throwIfErrors } from '../utilities/functions ' ;
5
+ import { MatsDataSubmissionFileNamesDTO } from '../dto/mats-data-submission-create-payload.dto ' ;
7
6
import { MatsDataSubmissionBaseDTO } from '../dto/mats-data-submission.dto' ;
8
7
import { MatsFileTypeCode } from '../entities/mats-file-type-code.entity' ;
9
8
import { MatsPollutantCode } from '../entities/mats-pollutant-code.entity' ;
10
9
import { MatsReportTypeCode } from '../entities/mats-report-type-code.entity' ;
11
10
import { MatsTestMethodCode } from '../entities/mats-test-method-code.entity' ;
12
- import { MatsDataSubmissionService } from './mats-data-submission.service' ;
11
+ import { throwIfErrors } from '../utilities/functions' ;
12
+ import {
13
+ MatsDataSubmissionService ,
14
+ METADATA_XML_FILE_NAME ,
15
+ } from './mats-data-submission.service' ;
13
16
14
17
@Injectable ( )
15
18
export class MatsDataSubmissionChecksService {
@@ -21,6 +24,14 @@ export class MatsDataSubmissionChecksService {
21
24
this . logger . setContext ( MatsDataSubmissionChecksService . name ) ;
22
25
}
23
26
27
+ private getMimeType = async ( fileName : string , locationId : string ) => {
28
+ const filePath = this . matsDataSubmissionService . createStagingFilePath (
29
+ locationId ,
30
+ fileName ,
31
+ ) ;
32
+ return this . matsDataSubmissionService . getRemoteFileMimeType ( filePath ) ;
33
+ } ;
34
+
24
35
private async pollutantToTestMethodCrosscheck (
25
36
selectedPollutants : MatsPollutantCode [ ] = [ ] ,
26
37
selectedTestMethods : MatsTestMethodCode [ ] = [ ] ,
@@ -88,23 +99,15 @@ export class MatsDataSubmissionChecksService {
88
99
89
100
async runChecks (
90
101
metadata : MatsDataSubmissionBaseDTO ,
91
- files : MatsDataSubmissionFiles ,
102
+ fileNames : MatsDataSubmissionFileNamesDTO ,
103
+ locationId : string ,
92
104
) : Promise < Array < string > > {
93
105
const errors : string [ ] = [ ] ;
94
106
95
- // Validate the DTO.
96
- const dtoErrors = await validate ( metadata , {
97
- groups : [ metadata . reportTypeCode ] ,
98
- } ) ;
99
- errors . push ( ...dtoErrors . map ( ( e ) => Object . values ( e . constraints ) ) . flat ( ) ) ;
100
-
101
- // Throw immediately if initial validation fails.
102
- throwIfErrors ( errors , { asArray : true } ) ;
103
-
104
107
// Conditional validation of `testNumber`.
105
108
if (
106
109
metadata . reportTypeCode === 'NOTIFY' &&
107
- files . ertFile &&
110
+ fileNames . ertFile &&
108
111
! metadata . testNumber
109
112
) {
110
113
errors . push (
@@ -157,7 +160,11 @@ export class MatsDataSubmissionChecksService {
157
160
throwIfErrors ( errors , { asArray : true } ) ;
158
161
159
162
// Validate the provided files.
160
- const warnings = await this . validateFiles ( reportType , files ) ;
163
+ const warnings = await this . validateFiles (
164
+ reportType ,
165
+ fileNames ,
166
+ locationId ,
167
+ ) ;
161
168
162
169
/* CROSSCHECK VALIDATION */
163
170
@@ -217,22 +224,34 @@ export class MatsDataSubmissionChecksService {
217
224
218
225
private async validateFiles (
219
226
reportType : MatsReportTypeCode ,
220
- files : MatsDataSubmissionFiles ,
227
+ fileNames : MatsDataSubmissionFileNamesDTO ,
228
+ locationId : string ,
221
229
) : Promise < string [ ] > {
222
230
const errors : string [ ] = [ ] ;
223
231
const warnings : string [ ] = [ ] ;
224
232
225
- errors . push ( ...this . validateFileMimetypes ( files ) ) ;
233
+ const fileNameErrors = this . validateFileNames ( fileNames ) ;
234
+ if ( fileNameErrors . length ) {
235
+ errors . push ( ...fileNameErrors ) ;
236
+ }
237
+
238
+ const mimetypeErrors = await this . validateFileMimetypes (
239
+ fileNames ,
240
+ locationId ,
241
+ ) ;
242
+ if ( mimetypeErrors . length ) errors . push ( ...mimetypeErrors ) ;
226
243
227
244
const attachmentErrors = await this . validateFileAttachments (
228
- files ,
245
+ fileNames ,
229
246
reportType ,
247
+ locationId ,
230
248
) ;
231
-
232
- if ( reportType . enforceAttachmentRules ) {
233
- errors . push ( ...attachmentErrors ) ;
234
- } else {
235
- warnings . push ( ...attachmentErrors ) ;
249
+ if ( attachmentErrors . length ) {
250
+ if ( reportType . enforceAttachmentRules ) {
251
+ errors . push ( ...attachmentErrors ) ;
252
+ } else {
253
+ warnings . push ( ...attachmentErrors ) ;
254
+ }
236
255
}
237
256
238
257
throwIfErrors ( errors , { asArray : true } ) ;
@@ -241,12 +260,13 @@ export class MatsDataSubmissionChecksService {
241
260
}
242
261
243
262
private async validateFileAttachments (
244
- files : MatsDataSubmissionFiles ,
263
+ fileNames : MatsDataSubmissionFileNamesDTO ,
245
264
reportType : MatsReportTypeCode ,
265
+ locationId : string ,
246
266
) {
247
267
const errors : string [ ] = [ ] ;
248
268
249
- const { ertFile, payloadFile, supportingFiles } = files ;
269
+ const { ertFile, payloadFile, supportingFiles } = fileNames ;
250
270
251
271
const fileTypes = await this . entityManager . find ( MatsFileTypeCode ) ;
252
272
const ertFileCheck = ( ) => {
@@ -280,7 +300,8 @@ export class MatsDataSubmissionChecksService {
280
300
// A payload PDF file OR (ERT file & at least one supporting file) are required.
281
301
let hasRequiredFiles = true ;
282
302
if ( payloadFile ) {
283
- if ( payloadFile . mimetype !== 'application/pdf' ) {
303
+ const mimetype = await this . getMimeType ( payloadFile , locationId ) ;
304
+ if ( mimetype !== 'application/pdf' ) {
284
305
hasRequiredFiles = false ;
285
306
}
286
307
} else if ( ! ertFile || ! supportingFiles ?. length ) {
@@ -309,42 +330,67 @@ export class MatsDataSubmissionChecksService {
309
330
return errors ;
310
331
}
311
332
312
- private validateFileMimetypes ( files : MatsDataSubmissionFiles ) {
333
+ private async validateFileMimetypes (
334
+ fileNames : MatsDataSubmissionFileNamesDTO ,
335
+ locationId : string ,
336
+ ) {
313
337
const errors : string [ ] = [ ] ;
314
338
315
- const { ertFile, payloadFile, supportingFiles } = files ;
339
+ const { ertFile, payloadFile, supportingFiles } = fileNames ;
316
340
317
341
// ERT file must be XML.
318
- if ( ertFile && ertFile ?. mimetype !== 'text/xml' ) {
319
- errors . push (
320
- `Expected ERT file to be of type XML, but got ${ ertFile . mimetype } ` ,
321
- ) ;
342
+ if ( ertFile ) {
343
+ const mimetype = await this . getMimeType ( ertFile , locationId ) ;
344
+ if ( ! [ 'application/xml' , 'text/xml' ] . includes ( mimetype ) ) {
345
+ errors . push ( `Expected ERT file to be of type XML, but got ${ mimetype } ` ) ;
346
+ }
322
347
}
323
348
324
349
// Payload file must be PDF, JSON, or XML.
325
350
const validPayloadFileTypes = [
326
- 'application/pdf' ,
327
351
'application/json' ,
352
+ 'application/pdf' ,
353
+ 'application/xml' ,
354
+ 'text/json' ,
328
355
'text/xml' ,
329
356
] ;
330
- if ( payloadFile && ! validPayloadFileTypes . includes ( payloadFile . mimetype ) ) {
331
- errors . push (
332
- `Expected Payload file to be of type ${ validPayloadFileTypes . join (
333
- ', ' ,
334
- ) } but got ${ payloadFile . mimetype } `,
335
- ) ;
357
+ if ( payloadFile ) {
358
+ const mimetype = await this . getMimeType ( payloadFile , locationId ) ;
359
+ if ( ! validPayloadFileTypes . includes ( mimetype ) ) {
360
+ errors . push (
361
+ `Expected Payload file to be of type ${ validPayloadFileTypes . join (
362
+ ', ' ,
363
+ ) } but got ${ mimetype } `,
364
+ ) ;
365
+ }
336
366
}
337
367
338
368
// Supporting files must be PDF.
339
- if (
340
- supportingFiles &&
341
- ! supportingFiles . every ( ( file ) => file . mimetype === 'application/pdf' )
342
- ) {
343
- errors . push (
344
- `Expected Supporting files to be of type PDF, but got ${ supportingFiles
345
- . map ( ( file ) => file . mimetype )
346
- . join ( ', ' ) } `,
369
+ if ( supportingFiles ) {
370
+ const mimetypes = await Promise . all (
371
+ supportingFiles . map ( ( file ) => this . getMimeType ( file , locationId ) ) ,
347
372
) ;
373
+ if ( ! mimetypes . every ( ( mimetype ) => mimetype === 'application/pdf' ) ) {
374
+ errors . push (
375
+ `Expected Supporting files to be of type PDF, but got ${ mimetypes . join ( ', ' ) } ` ,
376
+ ) ;
377
+ }
378
+ }
379
+
380
+ return errors ;
381
+ }
382
+
383
+ private validateFileNames ( fileNames : MatsDataSubmissionFileNamesDTO ) {
384
+ const errors = [ ] ;
385
+
386
+ const fileNamesFlat = Object . values ( fileNames ) . flat ( ) ;
387
+
388
+ if ( fileNamesFlat . length !== new Set ( fileNamesFlat ) . size ) {
389
+ errors . push ( 'File names must be unique.' ) ;
390
+ }
391
+
392
+ if ( fileNamesFlat . some ( ( name ) => name === METADATA_XML_FILE_NAME ) ) {
393
+ errors . push ( `File name [${ METADATA_XML_FILE_NAME } ] is reserved.` ) ;
348
394
}
349
395
350
396
return errors ;
0 commit comments