tango-app-api-infra 3.7.1-beta.1 → 3.7.1-beta.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tango-app-api-infra",
3
- "version": "3.7.1-beta.1",
3
+ "version": "3.7.1-beta.2",
4
4
  "description": "infra",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -28,7 +28,7 @@
28
28
  "nodemon": "^3.1.0",
29
29
  "swagger-ui-express": "^5.0.0",
30
30
  "tango-api-schema": "^2.2.201",
31
- "tango-app-api-middleware": "^3.1.89",
31
+ "tango-app-api-middleware": "^3.1.90",
32
32
  "winston": "^3.12.0",
33
33
  "winston-daily-rotate-file": "^5.0.0"
34
34
  },
@@ -1,12 +1,15 @@
1
1
  import { logger } from 'tango-app-api-middleware';
2
- import { bulkUpdate, insertWithId } from 'tango-app-api-middleware/src/utils/openSearch.js';
2
+ import { bulkUpdate, getOpenSearchData, insertWithId } from 'tango-app-api-middleware/src/utils/openSearch.js';
3
+ // import { updateOneCamera } from '../services/camera.service.js';
3
4
 
4
5
  export async function createTicket( req, res ) {
5
6
  try {
6
7
  const openSearch = JSON.parse( process.env.OPENSEARCH );
8
+ const getStoreName = await findOneStore( { storeId: inputData.storeId }, { storeName: 1, _id: 0 } );
7
9
  const inputData = req.body;
8
10
  inputData.ticketId = 'TE_FDT_' + new Date().valueOf();
9
11
  inputData.clientId = inputData?.storeId?.split( '-' )[0];
12
+ inputData.storeName =getStoreName?.storeName;
10
13
  inputData.createdAt = new Date();
11
14
  inputData.updatedAt = new Date();
12
15
  inputData.status = 'open';
@@ -90,3 +93,151 @@ async function bulkUpdateStatusToPending( indexName, inputData ) {
90
93
  }
91
94
  }
92
95
 
96
+ export async function ticketSummary( req, res ) {
97
+ try {
98
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
99
+ const inputData = req.query;
100
+ inputData.clientId = inputData.clientId.split( ',' ); // convert strig to array
101
+ const getQuery = {
102
+ size: 0,
103
+ query: {
104
+ bool: {
105
+ filter: [
106
+ { terms: { 'clientId.keyword': inputData.clientId } },
107
+ {
108
+ range: {
109
+ dateString: {
110
+ gte: inputData.fromDate,
111
+ lte: inputData.toDate,
112
+ },
113
+ },
114
+ },
115
+ ],
116
+ },
117
+ },
118
+ aggs: {
119
+ totalTicketCount: {
120
+ value_count: { field: '_id' },
121
+ },
122
+ openStatusCount: {
123
+ filter: {
124
+ term: { 'status.keyword': 'open' },
125
+ },
126
+ },
127
+ closeTicketCount: {
128
+ filter: {
129
+ term: { 'status.keyword': 'closed' },
130
+ },
131
+ },
132
+ duplicateCount: {
133
+ sum: {
134
+ field: 'duplicateCount',
135
+ },
136
+ },
137
+ employeeCount: {
138
+ sum: {
139
+ field: 'employeeCount',
140
+ },
141
+ },
142
+ houseKeepingCount: {
143
+ sum: {
144
+ field: 'houseKeepingCount',
145
+ },
146
+ },
147
+ },
148
+ };
149
+
150
+ const getData = await getOpenSearchData( openSearch.footfallDirectory, getQuery );
151
+ const aggs = getData?.body?.aggregations;
152
+ logger.info( { aggs: aggs, body: getData?.body } );
153
+
154
+ const result = {
155
+ totalTickets: aggs.totalTicketCount.value,
156
+ openTickets: aggs.openStatusCount.doc_count,
157
+ closedTickets: aggs.closeTicketCount.doc_count,
158
+ duplicateCount: aggs.duplicateCount.value,
159
+ employeeCount: aggs.employeeCount.value,
160
+ houseKeepingCount: aggs.houseKeepingCount.value,
161
+ noShopper: aggs.duplicateCount.value + aggs.employeeCount.value + aggs.houseKeepingCount.value,
162
+ };
163
+
164
+ return res.sendSuccess( { result: result } );
165
+ } catch ( error ) {
166
+ const err = error.message || 'Internal Server Error';
167
+ logger.error( { error: error, messgage: req.query } );
168
+ return res.sendSuccess( err, 500 );
169
+ }
170
+ }
171
+
172
+ export async function ticketList( req, res ) {
173
+ try {
174
+ // let count = 0;
175
+ // const array =[
176
+ // { A: '3c88e8eb6d499a26d37ca075c390bd2d', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/101' },
177
+ // { A: 'c3dd981f485ccd25221c7da704f35d24', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/101' },
178
+ // { A: 'ee887e5546ab0e96ca0adbf8c2453c8d', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/1001' },
179
+ // { A: 'a59ddeb30d158602d6c672994af2f79a', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/1001' },
180
+ // { A: '1f3318280025f5456262b0322b806940', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/201' },
181
+ // { A: '925a70751a0988793057148b20158186', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/201',
182
+ // }, { A: 'd6d4ab301c322212fb612a7d9661b6be', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/301',
183
+ // }, { A: 'cb09aeedfa5ade043922634eb1c35e46', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/301',
184
+ // }, { A: '58f81e33f66ef7af15424c1dc44e7de1', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/401',
185
+ // }, { A: '7ac8f73f02347d29f6f2bff3c52275f5', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/401',
186
+ // }, { A: '9599acd19a48eb28957ad18e40a0a5b1', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/401',
187
+ // }, { A: '36584a3ce215a5fbd1c0e6535201020c', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/501',
188
+ // }, { A: 'cb7e91870e0a72d4eb691e2618aa5979', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/501',
189
+ // }, { A: 'cd072627e6924df745cba004616ec3c9', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/601',
190
+ // }, { A: 'a5891519a50f68a9d73a774539fe8496', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/601',
191
+ // }, { A: '2008517125b03b1b9a66348e6d017523', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/701',
192
+ // }, { A: '79fc93551b59f9ed07eab48d190ba7f4', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/701',
193
+ // }, { A: '89365abcfc8dc663b26471d81a7f68eb', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/801',
194
+ // }, { A: 'e9fb3b989a245994bd48b97287aad096', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/801',
195
+ // }, { A: '4493fb6dc07611bab03630b154ca2483', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/801',
196
+ // }, { A: 'e31caf56bb86f6e12377db006ac61756', B: 'rtsp://admin:Bli$$%40145@192.168.29.86/streaming/channels/901',
197
+ // }, { A: 'fe66c637bc8da296485494e371f67b99', B: 'rtsp://admin:Bli$$%40145@192.168.29.58/streaming/channels/901' },
198
+ // ];
199
+
200
+ // for ( const item of array ) {
201
+ // const a = await updateOneCamera( { streamName: item.A, clientId: '370' }, { RTSP: item.B } );
202
+ // count++;
203
+ // logger.info( { aaa: a, count: count } );
204
+ // }
205
+
206
+ // process.exit();
207
+ const openSearch = JSON.parse( process.env.OPENSEARCH );
208
+ const inputData = req.query;
209
+ const skip=( inputData.offset - 1 ) *inputData.limit;
210
+ inputData.clientId = inputData.clientId.split( ',' ); // convert strig to array
211
+ const getQuery = {
212
+ size: inputData.limit,
213
+ from: skip,
214
+ query: {
215
+ bool: {
216
+ filter: [
217
+ { terms: { 'clientId.keyword': inputData.clientId } },
218
+ {
219
+ range: {
220
+ dateString: {
221
+ gte: inputData.fromDate,
222
+ lte: inputData.toDate,
223
+ },
224
+ },
225
+ },
226
+ ],
227
+ },
228
+ },
229
+ };
230
+
231
+ const getData = await getOpenSearchData( openSearch.footfallDirectory, getQuery );
232
+ const response = getData?.body?.hits?.hits;
233
+ logger.info( { response: response, body: getData?.body } );
234
+
235
+
236
+ return res.sendSuccess( { result: response } );
237
+ } catch ( error ) {
238
+ const err = error.message || 'Internal Server Error';
239
+ logger.error( { error: error, messgage: req.query } );
240
+ return res.sendSuccess( err, 500 );
241
+ }
242
+ }
243
+
@@ -1,5 +1,5 @@
1
1
  import j2s from 'joi-to-swagger';
2
- import { createTicketSchema } from '../dtos/footfallDirectory.dtos.js';
2
+ import { createTicketSchema, ticketSummarySchema } from '../dtos/footfallDirectory.dtos.js';
3
3
 
4
4
  export const footfallDirectoryDocs = {
5
5
 
@@ -25,4 +25,73 @@ export const footfallDirectoryDocs = {
25
25
  },
26
26
  },
27
27
  },
28
+ '/v3/footfall-directory-tagging/ticket-summary': {
29
+ get: {
30
+ tags: [ 'Footfall Directory Ticket' ],
31
+ description: 'To get ticket summary by selction clients and date range',
32
+ operationId: 'ticket-summary',
33
+ parameters: [
34
+ {
35
+ in: 'query',
36
+ name: 'clientId',
37
+ scema: j2s( ticketSummarySchema ).swagger,
38
+ required: true,
39
+ },
40
+ {
41
+ in: 'query',
42
+ name: 'fromDate',
43
+ scema: j2s( ticketSummarySchema ).swagger,
44
+ required: true,
45
+ },
46
+ {
47
+ in: 'query',
48
+ name: 'toDate',
49
+ scema: j2s( ticketSummarySchema ).swagger,
50
+ required: true,
51
+ },
52
+ ],
53
+ responses: {
54
+ 200: { description: 'Successful' },
55
+ 401: { description: 'Unauthorized User' },
56
+ 422: { description: 'Field Error' },
57
+ 500: { description: 'Server Error' },
58
+ 204: { description: 'Not Found' },
59
+ },
60
+ },
61
+ },
62
+
63
+ '/v3/footfall-directory-tagging/ticket-list': {
64
+ get: {
65
+ tags: [ 'Footfall Directory Ticket' ],
66
+ description: 'To get ticket summary by selction clients and date range',
67
+ operationId: 'ticket-list',
68
+ parameters: [
69
+ {
70
+ in: 'query',
71
+ name: 'clientId',
72
+ scema: j2s( ticketSummarySchema ).swagger,
73
+ required: false,
74
+ },
75
+ {
76
+ in: 'query',
77
+ name: 'fromDate',
78
+ scema: j2s( ticketSummarySchema ).swagger,
79
+ required: false,
80
+ },
81
+ {
82
+ in: 'query',
83
+ name: 'toDate',
84
+ scema: j2s( ticketSummarySchema ).swagger,
85
+ required: false,
86
+ },
87
+ ],
88
+ responses: {
89
+ 200: { description: 'Successful' },
90
+ 401: { description: 'Unauthorized User' },
91
+ 422: { description: 'Field Error' },
92
+ 500: { description: 'Server Error' },
93
+ 204: { description: 'Not Found' },
94
+ },
95
+ },
96
+ },
28
97
  };
@@ -1,4 +1,5 @@
1
1
  import Joi from 'joi';
2
+ import dayjs from 'dayjs';
2
3
 
3
4
  export const createTicketSchema = Joi.object().keys( {
4
5
 
@@ -55,3 +56,128 @@ export const createTicketSchema = Joi.object().keys( {
55
56
  export const createTicketValid = {
56
57
  body: createTicketSchema,
57
58
  };
59
+
60
+ export const ticketSummarySchema = Joi.object().keys( {
61
+ clientId: Joi.string().required(),
62
+
63
+ fromDate: Joi.string()
64
+ .pattern( /^\d{4}-\d{2}-\d{2}$/, 'YYYY-MM-DD format' )
65
+ .required()
66
+ .messages( {
67
+ 'string.pattern.name': `'fromDate' must be in the format YYYY-MM-DD (e.g., 2025-07-19).`,
68
+ 'string.empty': `'fromDate' is required.`,
69
+ } )
70
+ .custom( ( value, helpers ) => {
71
+ const from = dayjs( value );
72
+ if ( !from.isValid() ) {
73
+ return helpers.error( 'any.invalid', { message: 'Invalid fromDate' } );
74
+ }
75
+ return value;
76
+ } ),
77
+
78
+ toDate: Joi.string()
79
+ .pattern( /^\d{4}-\d{2}-\d{2}$/, 'YYYY-MM-DD format' )
80
+ .required()
81
+ .messages( {
82
+ 'string.pattern.name': `'toDate' must be in the format YYYY-MM-DD (e.g., 2025-07-19).`,
83
+ 'string.empty': `'toDate' is required.`,
84
+ } )
85
+ .custom( ( value, helpers ) => {
86
+ const to = dayjs( value );
87
+ const today = dayjs();
88
+
89
+ if ( !to.isValid() ) {
90
+ return helpers.error( 'any.invalid', { message: 'Invalid toDate' } );
91
+ }
92
+ if ( to.isAfter( today, 'day' ) ) {
93
+ return helpers.error( 'any.invalid', { message: 'toDate cannot be in the future' } );
94
+ }
95
+
96
+ return value;
97
+ } ),
98
+ } ).custom( ( value, helpers ) => {
99
+ const from = dayjs( value.fromDate );
100
+ const to = dayjs( value.toDate );
101
+
102
+ if ( !from.isValid() || !to.isValid() ) {
103
+ return helpers.error( 'any.invalid', { message: 'Invalid dates' } );
104
+ }
105
+
106
+ if ( from.isAfter( to ) ) {
107
+ return helpers.error( 'any.invalid', { message: 'fromDate cannot be after toDate' } );
108
+ }
109
+
110
+ if ( to.diff( from, 'day' ) > 90 ) {
111
+ return helpers.error( 'any.invalid', { message: 'Date range cannot exceed 90 days' } );
112
+ }
113
+
114
+ return value;
115
+ } );
116
+
117
+ export const ticketSummaryValid = {
118
+ query: ticketSummarySchema,
119
+ };
120
+
121
+
122
+ export const ticketListSchema = Joi.object().keys( {
123
+ clientId: Joi.string().required(),
124
+ searchValue: Joi.string().optional().allow( '' ),
125
+ limit: Joi.number().optional(),
126
+ offset: Joi.number().optional(),
127
+ fromDate: Joi.string()
128
+ .pattern( /^\d{4}-\d{2}-\d{2}$/, 'YYYY-MM-DD format' )
129
+ .required()
130
+ .messages( {
131
+ 'string.pattern.name': `'fromDate' must be in the format YYYY-MM-DD (e.g., 2025-07-19).`,
132
+ 'string.empty': `'fromDate' is required.`,
133
+ } )
134
+ .custom( ( value, helpers ) => {
135
+ const from = dayjs( value );
136
+ if ( !from.isValid() ) {
137
+ return helpers.error( 'any.invalid', { message: 'Invalid fromDate' } );
138
+ }
139
+ return value;
140
+ } ),
141
+
142
+ toDate: Joi.string()
143
+ .pattern( /^\d{4}-\d{2}-\d{2}$/, 'YYYY-MM-DD format' )
144
+ .required()
145
+ .messages( {
146
+ 'string.pattern.name': `'toDate' must be in the format YYYY-MM-DD (e.g., 2025-07-19).`,
147
+ 'string.empty': `'toDate' is required.`,
148
+ } )
149
+ .custom( ( value, helpers ) => {
150
+ const to = dayjs( value );
151
+ const today = dayjs();
152
+
153
+ if ( !to.isValid() ) {
154
+ return helpers.error( 'any.invalid', { message: 'Invalid toDate' } );
155
+ }
156
+ if ( to.isAfter( today, 'day' ) ) {
157
+ return helpers.error( 'any.invalid', { message: 'toDate cannot be in the future' } );
158
+ }
159
+
160
+ return value;
161
+ } ),
162
+ } ).custom( ( value, helpers ) => {
163
+ const from = dayjs( value.fromDate );
164
+ const to = dayjs( value.toDate );
165
+
166
+ if ( !from.isValid() || !to.isValid() ) {
167
+ return helpers.error( 'any.invalid', { message: 'Invalid dates' } );
168
+ }
169
+
170
+ if ( from.isAfter( to ) ) {
171
+ return helpers.error( 'any.invalid', { message: 'fromDate cannot be after toDate' } );
172
+ }
173
+
174
+ if ( to.diff( from, 'day' ) > 90 ) {
175
+ return helpers.error( 'any.invalid', { message: 'Date range cannot exceed 90 days' } );
176
+ }
177
+
178
+ return value;
179
+ } );
180
+
181
+ export const ticketListValid = {
182
+ query: ticketSummarySchema,
183
+ };
@@ -1,9 +1,13 @@
1
1
  import express from 'express';
2
2
  import { isExist } from '../validations/footfallDirectory.validation.js';
3
- import { createTicket } from '../controllers/footfallDirectory.controllers.js';
4
- import { createTicketValid } from '../dtos/footfallDirectory.dtos.js';
5
- import { validate } from 'tango-app-api-middleware';
3
+ import { createTicket, ticketList, ticketSummary } from '../controllers/footfallDirectory.controllers.js';
4
+ import { createTicketValid, ticketListValid, ticketSummaryValid } from '../dtos/footfallDirectory.dtos.js';
5
+ import { bulkValidate, validate } from 'tango-app-api-middleware';
6
6
 
7
7
  export const footfallDirectoryRouter = express.Router();
8
8
 
9
9
  footfallDirectoryRouter.post( '/create-ticket', validate( createTicketValid ), isExist, createTicket );
10
+ footfallDirectoryRouter.get( '/ticket-summary', bulkValidate( ticketSummaryValid ), ticketSummary );
11
+
12
+ footfallDirectoryRouter.get( '/ticket-list', bulkValidate( ticketListValid ), ticketList );
13
+ // footfallDirectoryRouter.get( '/ticket-list', ticketList );
@@ -3,3 +3,6 @@ import cameraModel from 'tango-api-schema/schema/camera.model.js';
3
3
  export async function aggregateCamera( query ) {
4
4
  return await cameraModel.aggregate( query );
5
5
  }
6
+ export async function updateOneCamera( query, record ) {
7
+ return await cameraModel.updateOne( query, { $set: record } );
8
+ }