tango-app-api-audio-analytics 1.0.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.
@@ -0,0 +1,624 @@
1
+ /**
2
+ * Validation Middleware for Audio Analytics API
3
+ * Validates request data using DTOs before reaching controllers
4
+ */
5
+
6
+ import {
7
+ CohortAnalysisRequestDTO,
8
+ ConversationsListRequestDTO,
9
+ ConversationDetailsRequestDTO,
10
+ ConversationMetricsRequestDTO,
11
+ AIInsightsRequestDTO,
12
+ ConversationTranscriptionRequestDTO,
13
+ DashboardFilterRequestDTO,
14
+ ExportDataRequestDTO,
15
+
16
+ ExternalStreamAPIRequestDTO,
17
+ } from '../dtos/audioAnalytics.dtos.js';
18
+ import {
19
+ cohortCreationSchema,
20
+ cohortUpdateSchema,
21
+ cohortQuerySchema,
22
+ validateCohortData,
23
+ } from '../validations/cohort.validation.js';
24
+ import { logger } from 'tango-app-api-middleware';
25
+
26
+ /**
27
+ * Generic validation error handler
28
+ * @param {Object} res - Express response object
29
+ * @param {Error} error - Error object to handle
30
+ * @param {string} [code='VALIDATION_ERROR'] - Error code to return
31
+ * @return {void} Sends error response to client
32
+ */
33
+ const handleValidationError = ( res, error, code = 'VALIDATION_ERROR' ) => {
34
+ logger.error( `Validation error: ${error.message}` );
35
+ res.status( 400 ).json( {
36
+ status: 'error',
37
+ message: error.message,
38
+ code,
39
+ timestamp: new Date().toISOString(),
40
+ } );
41
+ };
42
+
43
+ /**
44
+ * Validate Cohort Analysis Request
45
+ * @param {Object} req - Express request object
46
+ * @param {Object} res - Express response object
47
+ * @param {Function} next - Express next middleware function
48
+ * @return {void} Calls next() if valid or sends error response
49
+ */
50
+ export const validateCohortAnalysis = ( req, res, next ) => {
51
+ try {
52
+ const { startDate, endDate, storeId, cohortType, clientId } = req.body;
53
+
54
+ const cohortDTO = new CohortAnalysisRequestDTO(
55
+ startDate,
56
+ endDate,
57
+ storeId,
58
+ cohortType,
59
+ clientId,
60
+ );
61
+
62
+ cohortDTO.validate();
63
+ req.validatedData = cohortDTO;
64
+ next();
65
+ } catch ( error ) {
66
+ handleValidationError( res, error, 'MISSING_PARAMETERS' );
67
+ }
68
+ };
69
+
70
+ /**
71
+ * Validate Conversations List Request
72
+ * @param {Object} req - Express request object
73
+ * @param {Object} res - Express response object
74
+ * @param {Function} next - Express next middleware function
75
+ * @return {void} Calls next() if valid or sends error response
76
+ */
77
+ export const validateConversationsList = ( req, res, next ) => {
78
+ try {
79
+ const conversationsDTO = new ConversationsListRequestDTO( req.body );
80
+ conversationsDTO.validate();
81
+ req.validatedData = conversationsDTO;
82
+ next();
83
+ } catch ( error ) {
84
+ handleValidationError( res, error, 'MISSING_PARAMETERS' );
85
+ }
86
+ };
87
+
88
+ /**
89
+ * Validate Conversation Details Request
90
+ * @param {Object} req - Express request object
91
+ * @param {Object} res - Express response object
92
+ * @param {Function} next - Express next middleware function
93
+ * @return {void} Calls next() if valid or sends error response
94
+ */
95
+ export const validateConversationDetails = ( req, res, next ) => {
96
+ try {
97
+ const { conversationId } = req.params;
98
+ const { storeId } = req.body;
99
+
100
+ const detailsDTO = new ConversationDetailsRequestDTO( conversationId, storeId );
101
+ detailsDTO.validate();
102
+ req.validatedData = detailsDTO;
103
+ next();
104
+ } catch ( error ) {
105
+ handleValidationError( res, error, 'MISSING_PARAMETERS' );
106
+ }
107
+ };
108
+
109
+ /**
110
+ * Validate Conversation Metrics Request
111
+ * @param {Object} req - Express request object
112
+ * @param {Object} res - Express response object
113
+ * @param {Function} next - Express next middleware function
114
+ * @return {void} Calls next() if valid or sends error response
115
+ */
116
+ export const validateConversationMetrics = ( req, res, next ) => {
117
+ try {
118
+ const { conversationId } = req.params;
119
+ const { storeId, includeBreakdown } = req.body;
120
+
121
+ const metricsDTO = new ConversationMetricsRequestDTO(
122
+ conversationId,
123
+ storeId,
124
+ includeBreakdown,
125
+ );
126
+ metricsDTO.validate();
127
+ req.validatedData = metricsDTO;
128
+ next();
129
+ } catch ( error ) {
130
+ handleValidationError( res, error, 'MISSING_PARAMETERS' );
131
+ }
132
+ };
133
+
134
+ /**
135
+ * Validate AI Insights Request
136
+ * @param {Object} req - Express request object
137
+ * @param {Object} res - Express response object
138
+ * @param {Function} next - Express next middleware function
139
+ * @return {void} Calls next() if valid or sends error response
140
+ */
141
+ export const validateAIInsights = ( req, res, next ) => {
142
+ try {
143
+ const { conversationId } = req.params;
144
+ const requestData = {
145
+ conversationId,
146
+ ...req.body,
147
+ };
148
+
149
+ const insightsDTO = new AIInsightsRequestDTO( requestData );
150
+ insightsDTO.validate();
151
+ req.validatedData = insightsDTO;
152
+ next();
153
+ } catch ( error ) {
154
+ handleValidationError( res, error );
155
+ }
156
+ };
157
+
158
+ /**
159
+ * Validate Conversation Transcription Request
160
+ * @param {Object} req - Express request object
161
+ * @param {Object} res - Express response object
162
+ * @param {Function} next - Express next middleware function
163
+ * @return {void} Calls next() if valid or sends error response
164
+ */
165
+ export const validateConversationTranscription = ( req, res, next ) => {
166
+ try {
167
+ const { conversationId } = req.params;
168
+ const { storeId, includeTimestamps, highlightKeyPhases } = req.body;
169
+
170
+ const transcriptionDTO = new ConversationTranscriptionRequestDTO(
171
+ conversationId,
172
+ storeId,
173
+ includeTimestamps,
174
+ highlightKeyPhases,
175
+ );
176
+ transcriptionDTO.validate();
177
+ req.validatedData = transcriptionDTO;
178
+ next();
179
+ } catch ( error ) {
180
+ handleValidationError( res, error, 'MISSING_PARAMETERS' );
181
+ }
182
+ };
183
+
184
+ /**
185
+ * Validate Dashboard Filter Request
186
+ * @param {Object} req - Express request object
187
+ * @param {Object} res - Express response object
188
+ * @param {Function} next - Express next middleware function
189
+ * @return {void} Calls next() if valid or sends error response
190
+ */
191
+ export const validateDashboardFilter = ( req, res, next ) => {
192
+ try {
193
+ const dashboardDTO = new DashboardFilterRequestDTO( req.body );
194
+ dashboardDTO.validate();
195
+ req.validatedData = dashboardDTO;
196
+ next();
197
+ } catch ( error ) {
198
+ handleValidationError( res, error );
199
+ }
200
+ };
201
+
202
+ /**
203
+ * Validate Export Data Request
204
+ * @param {Object} req - Express request object
205
+ * @param {Object} res - Express response object
206
+ * @param {Function} next - Express next middleware function
207
+ * @return {void} Calls next() if valid or sends error response
208
+ */
209
+ export const validateExportData = ( req, res, next ) => {
210
+ try {
211
+ const exportDTO = new ExportDataRequestDTO( req.body );
212
+ exportDTO.validate();
213
+ req.validatedData = exportDTO;
214
+ next();
215
+ } catch ( error ) {
216
+ handleValidationError( res, error );
217
+ }
218
+ };
219
+
220
+ /**
221
+ * Validate Date Format (YYYY-MM-DD)
222
+ * @param {Object} req - Express request object
223
+ * @param {Object} res - Express response object
224
+ * @param {Function} next - Express next middleware function
225
+ * @return {void} Calls next() if valid or sends error response
226
+ */
227
+ export const validateDateFormat = ( req, res, next ) => {
228
+ try {
229
+ const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
230
+ let hasDateError = false;
231
+
232
+ // Check startDate and endDate in body
233
+ if ( req.body?.startDate && !dateRegex.test( req.body.startDate ) ) {
234
+ hasDateError = true;
235
+ }
236
+ if ( req.body?.endDate && !dateRegex.test( req.body.endDate ) ) {
237
+ hasDateError = true;
238
+ }
239
+
240
+ // Check dateRange in body
241
+ if ( req.body?.dateRange ) {
242
+ if ( !dateRegex.test( req.body.dateRange.startDate ) ) {
243
+ hasDateError = true;
244
+ }
245
+ if ( !dateRegex.test( req.body.dateRange.endDate ) ) {
246
+ hasDateError = true;
247
+ }
248
+ }
249
+
250
+ if ( hasDateError ) {
251
+ return res.status( 400 ).json( {
252
+ status: 'error',
253
+ message: 'Invalid date format. Please use YYYY-MM-DD format',
254
+ code: 'INVALID_DATE_FORMAT',
255
+ expectedFormat: 'YYYY-MM-DD',
256
+ timestamp: new Date().toISOString(),
257
+ } );
258
+ }
259
+
260
+ next();
261
+ } catch ( error ) {
262
+ handleValidationError( res, error, 'DATE_VALIDATION_ERROR' );
263
+ }
264
+ };
265
+
266
+ /**
267
+ * Validate Required Fields
268
+ * @param {Array<{name: string, location: string}>} requiredFields - Array of required field definitions
269
+ * @return {Function} Middleware function that validates required fields
270
+ */
271
+ export const validateRequiredFields = ( requiredFields ) => {
272
+ return ( req, res, next ) => {
273
+ const missingFields = [];
274
+
275
+ requiredFields.forEach( ( field ) => {
276
+ if ( field.location === 'body' && !req.body?.[field.name] ) {
277
+ missingFields.push( field.name );
278
+ } else if ( field.location === 'params' && !req.params?.[field.name] ) {
279
+ missingFields.push( field.name );
280
+ }
281
+ } );
282
+
283
+ if ( missingFields.length > 0 ) {
284
+ return res.status( 400 ).json( {
285
+ status: 'error',
286
+ message: `Missing required parameters: ${missingFields.join( ', ' )}`,
287
+ code: 'MISSING_PARAMETERS',
288
+ missingFields,
289
+ timestamp: new Date().toISOString(),
290
+ } );
291
+ }
292
+
293
+ next();
294
+ };
295
+ };
296
+
297
+ /**
298
+ * Validate Pagination Parameters
299
+ * @param {Object} req - Express request object
300
+ * @param {Object} res - Express response object
301
+ * @param {Function} next - Express next middleware function
302
+ * @return {void} Calls next() if valid or sends error response
303
+ */
304
+ export const validatePagination = ( req, res, next ) => {
305
+ try {
306
+ const { limit = 10, offset = 0 } = req.body;
307
+
308
+ if ( typeof limit !== 'number' || limit < 1 || limit > 1000 ) {
309
+ return res.status( 400 ).json( {
310
+ status: 'error',
311
+ message: 'Invalid limit. Must be a number between 1 and 1000',
312
+ code: 'INVALID_PAGINATION',
313
+ timestamp: new Date().toISOString(),
314
+ } );
315
+ }
316
+
317
+ if ( typeof offset !== 'number' || offset < 0 ) {
318
+ return res.status( 400 ).json( {
319
+ status: 'error',
320
+ message: 'Invalid offset. Must be a non-negative number',
321
+ code: 'INVALID_PAGINATION',
322
+ timestamp: new Date().toISOString(),
323
+ } );
324
+ }
325
+
326
+ next();
327
+ } catch ( error ) {
328
+ handleValidationError( res, error, 'PAGINATION_ERROR' );
329
+ }
330
+ };
331
+
332
+ /**
333
+ * Validate Array Parameters
334
+ * @param {string} paramName - Name of parameter to validate
335
+ * @return {Function} Middleware function that validates array parameter
336
+ */
337
+ export const validateArrayParameter = ( paramName ) => {
338
+ return ( req, res, next ) => {
339
+ try {
340
+ const value = req.body[paramName];
341
+
342
+ if ( value && !Array.isArray( value ) ) {
343
+ return res.status( 400 ).json( {
344
+ status: 'error',
345
+ message: `${paramName} must be an array`,
346
+ code: 'INVALID_PARAMETER_TYPE',
347
+ parameter: paramName,
348
+ receivedType: typeof value,
349
+ timestamp: new Date().toISOString(),
350
+ } );
351
+ }
352
+
353
+ next();
354
+ } catch ( error ) {
355
+ handleValidationError( res, error, 'VALIDATION_ERROR' );
356
+ }
357
+ };
358
+ };
359
+
360
+ /**
361
+ * Validate Product Module IDs
362
+ * @param {Object} req - Express request object
363
+ * @param {Object} res - Express response object
364
+ * @param {Function} next - Express next middleware function
365
+ * @return {void} Calls next() if valid or sends error response
366
+ */
367
+ export const validateProductModules = ( req, res, next ) => {
368
+ try {
369
+ const validModules = [ 'LKST121', 'LKST122', 'LKST123', 'LKST124', 'LKST125' ];
370
+ const modules = req.body?.productModules || [];
371
+
372
+ if ( Array.isArray( modules ) ) {
373
+ const invalidModules = modules.filter( ( m ) => !validModules.includes( m ) );
374
+
375
+ if ( invalidModules.length > 0 ) {
376
+ return res.status( 400 ).json( {
377
+ status: 'error',
378
+ message: 'Invalid product module selected',
379
+ code: 'INVALID_FILTER_VALUE',
380
+ invalidValues: invalidModules,
381
+ validOptions: validModules,
382
+ timestamp: new Date().toISOString(),
383
+ } );
384
+ }
385
+ }
386
+
387
+ next();
388
+ } catch ( error ) {
389
+ handleValidationError( res, error, 'VALIDATION_ERROR' );
390
+ }
391
+ };
392
+
393
+ /**
394
+ * Validate Country Selection
395
+ * @param {Object} req - Express request object
396
+ * @param {Object} res - Express response object
397
+ * @param {Function} next - Express next middleware function
398
+ * @return {void} Calls next() if valid or sends error response
399
+ */
400
+ export const validateCountry = ( req, res, next ) => {
401
+ try {
402
+ const validCountries = [ 'USA', 'Canada', 'UK', 'Australia', 'Other' ];
403
+ const country = req.body?.country;
404
+
405
+ if ( country && !validCountries.includes( country ) ) {
406
+ return res.status( 400 ).json( {
407
+ status: 'error',
408
+ message: 'Invalid country selected',
409
+ code: 'INVALID_FILTER_VALUE',
410
+ invalidValue: country,
411
+ validOptions: validCountries,
412
+ timestamp: new Date().toISOString(),
413
+ } );
414
+ }
415
+
416
+ next();
417
+ } catch ( error ) {
418
+ handleValidationError( res, error, 'VALIDATION_ERROR' );
419
+ }
420
+ };
421
+
422
+ /**
423
+ * Validate Export Format
424
+ * @param {Object} req - Express request object
425
+ * @param {Object} res - Express response object
426
+ * @param {Function} next - Express next middleware function
427
+ * @return {void} Calls next() if valid or sends error response
428
+ */
429
+ export const validateExportFormat = ( req, res, next ) => {
430
+ try {
431
+ const validFormats = [ 'csv', 'json', 'xlsx' ];
432
+ const format = req.body?.exportFormat;
433
+
434
+ if ( format && !validFormats.includes( format ) ) {
435
+ return res.status( 400 ).json( {
436
+ status: 'error',
437
+ message: 'Invalid export format',
438
+ code: 'INVALID_EXPORT_FORMAT',
439
+ invalidValue: format,
440
+ validOptions: validFormats,
441
+ timestamp: new Date().toISOString(),
442
+ } );
443
+ }
444
+
445
+ next();
446
+ } catch ( error ) {
447
+ handleValidationError( res, error, 'VALIDATION_ERROR' );
448
+ }
449
+ };
450
+
451
+ /**
452
+ * Validate Date Range (startDate should be before endDate)
453
+ * @param {Object} req - Express request object
454
+ * @param {Object} res - Express response object
455
+ * @param {Function} next - Express next middleware function
456
+ * @return {void} Calls next() if valid or sends error response
457
+ */
458
+ export const validateDateRange = ( req, res, next ) => {
459
+ try {
460
+ let startDate; let endDate;
461
+
462
+ if ( req.body?.startDate && req.body?.endDate ) {
463
+ startDate = new Date( req.body.startDate );
464
+ endDate = new Date( req.body.endDate );
465
+ } else if ( req.body?.dateRange ) {
466
+ startDate = new Date( req.body.dateRange.startDate );
467
+ endDate = new Date( req.body.dateRange.endDate );
468
+ }
469
+
470
+ if ( startDate && endDate && startDate > endDate ) {
471
+ return res.status( 400 ).json( {
472
+ status: 'error',
473
+ message: 'Start date must be before end date',
474
+ code: 'INVALID_DATE_RANGE',
475
+ timestamp: new Date().toISOString(),
476
+ } );
477
+ }
478
+
479
+ next();
480
+ } catch ( error ) {
481
+ handleValidationError( res, error, 'DATE_VALIDATION_ERROR' );
482
+ }
483
+ };
484
+
485
+ // ==================== Cohort Validation Middleware ====================
486
+
487
+ /**
488
+ * Validate Cohort Creation Request using Joi
489
+ * Uses cohortCreationSchema for validation
490
+ * @param {Object} req - Express request object
491
+ * @param {Object} res - Express response object
492
+ * @param {Function} next - Express next middleware function
493
+ * @return {void} Calls next() if valid or sends error response
494
+ */
495
+ export const validateCohortCreation = async ( req, res, next ) => {
496
+ try {
497
+ logger.info( 'Validating cohort creation request' );
498
+
499
+ const { error, value: validatedData } = await validateCohortData(
500
+ req.body,
501
+ cohortCreationSchema,
502
+ );
503
+
504
+ if ( error ) {
505
+ logger.warn( 'Cohort validation failed', error );
506
+ return res.status( 400 ).json( {
507
+ status: 'error',
508
+ message: error.message,
509
+ code: 'VALIDATION_ERROR',
510
+ details: error.details,
511
+ timestamp: new Date().toISOString(),
512
+ } );
513
+ }
514
+
515
+ req.validatedData = validatedData;
516
+ next();
517
+ } catch ( error ) {
518
+ logger.error( `Validation error: ${error.message}` );
519
+ res.status( 400 ).json( {
520
+ status: 'error',
521
+ message: 'Validation error',
522
+ code: 'VALIDATION_ERROR',
523
+ timestamp: new Date().toISOString(),
524
+ } );
525
+ }
526
+ };
527
+
528
+ /**
529
+ * Validate Cohort Update Request using Joi
530
+ * @param {Object} req - Express request object
531
+ * @param {Object} res - Express response object
532
+ * @param {Function} next - Express next middleware function
533
+ * @return {void} Calls next() if valid or sends error response
534
+ */
535
+ export const validateCohortUpdate = async ( req, res, next ) => {
536
+ try {
537
+ logger.info( 'Validating cohort update request' );
538
+
539
+ const { error, value: validatedData } = await validateCohortData(
540
+ req.body,
541
+ cohortUpdateSchema,
542
+ );
543
+
544
+ if ( error ) {
545
+ logger.warn( 'Cohort update validation failed', error );
546
+ return res.status( 400 ).json( {
547
+ status: 'error',
548
+ message: error.message,
549
+ code: 'VALIDATION_ERROR',
550
+ details: error.details,
551
+ timestamp: new Date().toISOString(),
552
+ } );
553
+ }
554
+
555
+ req.validatedData = validatedData;
556
+ next();
557
+ } catch ( error ) {
558
+ logger.error( `Validation error: ${error.message}` );
559
+ res.status( 400 ).json( {
560
+ status: 'error',
561
+ message: 'Validation error',
562
+ code: 'VALIDATION_ERROR',
563
+ timestamp: new Date().toISOString(),
564
+ } );
565
+ }
566
+ };
567
+
568
+ /**
569
+ * Validate Cohort Query/Search Request using Joi
570
+ * @param {Object} req - Express request object
571
+ * @param {Object} res - Express response object
572
+ * @param {Function} next - Express next middleware function
573
+ * @return {void} Calls next() if valid or sends error response
574
+ */
575
+ export const validateCohortQuery = async ( req, res, next ) => {
576
+ try {
577
+ logger.info( 'Validating cohort query request' );
578
+
579
+ const { error, value: validatedData } = await validateCohortData(
580
+ req.body,
581
+ cohortQuerySchema,
582
+ );
583
+
584
+ if ( error ) {
585
+ logger.warn( 'Cohort query validation failed', error );
586
+ return res.status( 400 ).json( {
587
+ status: 'error',
588
+ message: error.message,
589
+ code: 'VALIDATION_ERROR',
590
+ details: error.details,
591
+ timestamp: new Date().toISOString(),
592
+ } );
593
+ }
594
+
595
+ req.validatedData = validatedData;
596
+ next();
597
+ } catch ( error ) {
598
+ logger.error( `Validation error: ${error.message}` );
599
+ res.status( 400 ).json( {
600
+ status: 'error',
601
+ message: 'Validation error',
602
+ code: 'VALIDATION_ERROR',
603
+ timestamp: new Date().toISOString(),
604
+ } );
605
+ }
606
+ };
607
+
608
+ /**
609
+ * Validate External Stream API Request
610
+ * @param {Object} req - Express request object
611
+ * @param {Object} res - Express response object
612
+ * @param {Function} next - Express next middleware function
613
+ * @return {void} Calls next() if valid or sends error response
614
+ */
615
+ export const validateExternalStreamAPIRequest = ( req, res, next ) => {
616
+ try {
617
+ const streamDTO = new ExternalStreamAPIRequestDTO( req.body );
618
+ streamDTO.validate();
619
+ req.validatedData = streamDTO;
620
+ next();
621
+ } catch ( error ) {
622
+ handleValidationError( res, error, 'INVALID_STREAM_REQUEST' );
623
+ }
624
+ };
@@ -0,0 +1,18 @@
1
+
2
+ import express from 'express';
3
+ import { validate } from 'tango-app-api-middleware';
4
+ import { createCohortValid } from '../dtos/audioAnalytics.dtos.js';
5
+ import { createCohort } from '../controllers/audioAnalytics.controller.js';
6
+
7
+ export const audioAnalyticsrouter = express.Router(); ;
8
+
9
+
10
+ // Health check
11
+ audioAnalyticsrouter.get( '/test', ( req, res ) => {
12
+ return res.json( 'Hello, world!' );
13
+ } );
14
+
15
+ audioAnalyticsrouter.post( '/create-cohort', validate( createCohortValid ), createCohort );
16
+
17
+
18
+ export default audioAnalyticsrouter;