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.
- package/.eslintrc.cjs +41 -0
- package/API_EXAMPLES.md +310 -0
- package/COHORT_API.md +513 -0
- package/COHORT_API_EXAMPLES.sh +235 -0
- package/COHORT_API_IMPLEMENTATION.md +296 -0
- package/COHORT_API_QUICKSTART.md +387 -0
- package/index.js +6 -0
- package/package.json +36 -0
- package/src/controllers/audioAnalytics.controller.js +116 -0
- package/src/controllers/cohort.controller.js +357 -0
- package/src/controllers/cohortAnalytics.controller.js +46 -0
- package/src/controllers/conversationAnalytics.controller.js +92 -0
- package/src/dtos/audioAnalytics.dtos.js +537 -0
- package/src/middlewares/validation.middleware.js +624 -0
- package/src/routes/audioAnalytics.routes.js +18 -0
- package/src/services/cohort.service.js +332 -0
- package/src/validations/cohort.validation.js +113 -0
|
@@ -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;
|