Skip to content

Commit c9ffc06

Browse files
authored
Merge pull request #23 from ThePiratePhone/add-data-source
## Pull Request Overview This PR adds data source functionality by extending the client model with new fields for tracking integration details and enhancing search capabilities. The changes focus on adding `firstIntegration` date and `integrationReason` fields to the Client model while improving search functionality to include firstname searches. - Enhanced client model with integration tracking fields (`firstIntegration`, `integrationReason`) - Extended parameter validation to support Date type checking - Improved search functionality to include firstname and priority information ### Reviewed Changes Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments. <details> <summary>Show a summary per file</summary> | File | Description | | ---- | ----------- | | Models/Client.ts | Added firstIntegration and integrationReason fields to Client schema | | tools/utils.ts | Extended checkParameters function to support Date type validation | | router/admin/client/createClient.ts | Updated client creation to handle new integration fields | | router/admin/client/createClients.ts | Enhanced bulk client creation with integration data support | | router/admin/client/searchByName.ts | Extended search to include firstname matching | | router/admin/client/searchByPhone.ts | Added firstname to search results | | router/caller/getPhoneNumber.ts | Added priority information and improved client data handling | | router/admin/login.ts | Removed await from synchronous function call | | tests/admin/client/createClients.test.ts | Updated test to reflect new error message format | </details>
2 parents b1ce539 + 48fab33 commit c9ffc06

File tree

11 files changed

+138
-40
lines changed

11 files changed

+138
-40
lines changed

Models/Caller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const CallerSchema = new mongoose.Schema({
3636
},
3737
createdAt: {
3838
type: Date,
39-
default: new Date()
39+
default: Date.now
4040
}
4141
});
4242

Models/Campaign.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const CampaignSchema = new mongoose.Schema({
2626
},
2727
createdAt: {
2828
type: Date,
29-
default: Date.now()
29+
default: Date.now
3030
},
3131
password: {
3232
type: String,

Models/Client.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,17 @@ const ClientSchema = new mongoose.Schema({
3939
required: false,
4040
default: []
4141
},
42+
firstIntegration: {
43+
type: Date,
44+
default: Date.now
45+
},
46+
integrationReason: {
47+
type: String,
48+
default: 'unknown'
49+
},
4250
createdAt: {
4351
type: Date,
44-
default: Date.now()
52+
default: Date.now
4553
}
4654
});
4755

router/admin/client/createClient.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ import { ObjectId } from 'mongodb';
2222
* firstName?: string,
2323
* institution?: string,
2424
* updateIfExist?: boolean,
25-
* updateKey: ObjectId
25+
* updateKey: ObjectId,
26+
* priority?: Array<{ campaign: ObjectId, id: string (lenght= 8 || 2) }>,
27+
* firstIntegration?: Date,
28+
* integrationReason?: string
2629
* }
2730
*
2831
* @throws {400} if missing parameters
@@ -52,7 +55,9 @@ export default async function createClient(req: Request<any>, res: Response<any>
5255
['area', 'ObjectId'],
5356
['allreadyHaseded', 'boolean', true],
5457
['updateIfExist', 'boolean', true],
55-
['updateKey', 'ObjectId', true]
58+
['updateKey', 'ObjectId', true],
59+
['firstIntegration', 'Date', true],
60+
['integrationReason', 'string', true]
5661
],
5762
__filename
5863
)
@@ -130,13 +135,18 @@ export default async function createClient(req: Request<any>, res: Response<any>
130135
const client = await Client.updateOne(
131136
{ _id: req.body.updateKey },
132137
{
133-
name: sanitizeString(req.body.name),
138+
name: sanitizeString(req.body.name || 'unknown'),
134139
phone: phone,
135-
firstname: sanitizeString(req.body.firstName ?? ''),
136-
institution: sanitizeString(req.body.institution ?? ''),
140+
firstname: sanitizeString(req.body.firstName || ''),
141+
institution: sanitizeString(req.body.institution || ''),
137142
area: area._id,
138143
campaigns: [campaign._id],
139-
priority: req.body.priority ?? [{ campaign: campaign._id, id: '-1' }]
144+
priority: req.body.priority ?? [{ campaign: campaign._id, id: '-1' }],
145+
firstIntegration: (() => {
146+
const date = new Date(req.body.firstIntegration);
147+
return isNaN(date.getTime()) ? Date.now() : date.getTime();
148+
})(),
149+
integrationReason: sanitizeString(req.body.integrationReason) ?? 'unknown'
140150
}
141151
);
142152
if (client.matchedCount === 0) {
@@ -150,13 +160,18 @@ export default async function createClient(req: Request<any>, res: Response<any>
150160
}
151161
} else {
152162
client = new Client({
153-
name: sanitizeString(req.body.name),
163+
name: sanitizeString(req.body.name || 'unknown'),
154164
phone: phone,
155165
firstname: sanitizeString(req.body.firstName ?? ''),
156166
institution: sanitizeString(req.body.institution ?? ''),
157167
area: area._id,
158168
campaigns: [campaign._id],
159-
priority: req.body.priority ?? [{ campaign: campaign._id, id: '-1' }]
169+
priority: req.body.priority ?? [{ campaign: campaign._id, id: '-1' }],
170+
firstIntegration: (() => {
171+
const date = new Date(req.body.firstIntegration);
172+
return isNaN(date.getTime()) ? Date.now() : date.getTime();
173+
})(),
174+
integrationReason: sanitizeString(req.body.integrationReason) ?? 'unknown'
160175
});
161176
}
162177

router/admin/client/createClients.ts

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import { checkParameters, clearPhone, hashPasword, phoneNumberCheck, sanitizeStr
1212
* body:{
1313
* "adminCode": string,
1414
* "area": string,
15-
* "data": [phone, name, firstname, institution, priority],
15+
* "data": [{phone:string, name?:string, firstname?:string, institution?:string, priority?:string, firstIntegration?:date, integrationReason?:string}],
1616
* "allreadyHaseded": boolean
17+
* "defaultReason": string
1718
* }
1819
* @throws {400}: missing parameters,
1920
* @throws {400}: new password is not a hash
@@ -34,7 +35,8 @@ export default async function createClients(req: Request<any>, res: Response<any
3435
[
3536
['adminCode', 'string'],
3637
['area', 'ObjectId'],
37-
['allreadyHaseded', 'boolean', true]
38+
['allreadyHaseded', 'boolean', true],
39+
['defaultReason', 'string', true]
3840
],
3941
ip
4042
)
@@ -60,12 +62,18 @@ export default async function createClients(req: Request<any>, res: Response<any
6062
(usr.name === undefined || typeof usr.name === 'string') &&
6163
(usr.firstname === undefined || typeof usr.firstname === 'string') &&
6264
(usr.institution === undefined || typeof usr.institution === 'string') &&
63-
(usr.priority === undefined || typeof usr.priority === 'string')
65+
(usr.priority === undefined || typeof usr.priority === 'string') &&
66+
(usr.firstIntegration === undefined || !isNaN(new Date(usr.firstIntegration).getTime())) &&
67+
(usr.integrationReason === undefined || typeof usr.integrationReason === 'string')
6468
);
6569
});
6670

6771
if (!isValidData) {
68-
res.status(400).send({ message: 'Each data entry must be an object with valid properties', OK: false });
72+
res.status(400).send({
73+
message:
74+
'Each data entry must be an object with valid properties: {phone:string, name?:string, firstname?:string, institution?:string, priority?:string, firstIntegration?:date, integrationReason?:string}',
75+
OK: false
76+
});
6977
log(`[!${req.body.area}, ${ip}] Invalid data format`, 'WARNING', __filename);
7078
return;
7179
}
@@ -95,49 +103,73 @@ export default async function createClients(req: Request<any>, res: Response<any
95103

96104
const errors: Array<[string | undefined, string | undefined, string]> = [];
97105
const sleep: Array<Promise<boolean>> = req.body.data.map(
98-
async (usr: { phone: string; name: string; firstname?: string; institution?: string; priority?: string }) => {
106+
async (usr: {
107+
phone: string;
108+
name?: string;
109+
firstname?: string;
110+
institution?: string;
111+
priority?: string;
112+
firstIntegration?: Date;
113+
integrationReason?: string;
114+
}) => {
99115
const priorityId = campaign.sortGroup.find(e => e.name === usr.priority)?.id ?? '-1';
100116
const phone = clearPhone(usr.phone);
101117
try {
102118
if (!phoneNumberCheck(phone)) {
103119
errors.push([usr.name + ' ' + (usr.firstname || ''), usr.phone, 'Wrong phone number']);
104120
return false;
105121
}
106-
if ((await Client.countDocuments({ phone: phone })) == 0) {
122+
if ((await Client.countDocuments({ phone })) == 0) {
107123
const user = new Client({
108-
name: sanitizeString(usr.name),
124+
name: sanitizeString(usr.name || 'unknown'),
109125
firstname: sanitizeString(usr.firstname || ''),
110-
phone: phone,
126+
phone,
111127
campaigns: [campaign._id],
112-
priority: [{ campaign: campaign._id, id: priorityId }]
128+
priority: [{ campaign: campaign._id, id: priorityId }],
129+
firstIntegration: (() => {
130+
const date = new Date(usr.firstIntegration || '');
131+
return isNaN(date.getTime()) ? Date.now() : date.getTime();
132+
})(),
133+
integrationReason: sanitizeString(usr.integrationReason || '')
113134
});
114135
// create it
115136
await user.save();
116137
return true;
117-
} else if ((await Client.countDocuments({ phone: phone, campaigns: { $ne: campaign._id } })) == 1) {
138+
} else if ((await Client.countDocuments({ phone, campaigns: { $ne: campaign._id } })) == 1) {
118139
// client exist in another campaing
119140
await Client.updateOne(
141+
{ phone },
120142
{
121-
phone: phone,
122-
name: sanitizeString(usr.name),
123-
firstname: sanitizeString(usr.firstname || '')
143+
phone,
144+
name: sanitizeString(usr.name || 'unknown'),
145+
firstname: sanitizeString(usr.firstname || ''),
146+
firstIntegration: (() => {
147+
const date = new Date(usr.firstIntegration || '');
148+
return isNaN(date.getTime()) ? Date.now() : date.getTime();
149+
})(),
150+
integrationReason: sanitizeString(usr.integrationReason || req.body.defaultReason || '')
124151
},
125152
{ $push: { campaigns: campaign._id } }
126153
);
127154
} else {
128155
// client exist in this campaign, update name, firstnames and priority
129156
await Client.updateOne(
130-
{ phone: phone },
157+
{ phone },
131158
{
132-
phone: phone,
133-
name: sanitizeString(usr.name),
159+
phone,
160+
name: sanitizeString(usr.name || 'unknown'),
134161
firstname: sanitizeString(usr.firstname || ''),
135-
priority: [{ campaign: campaign._id, id: '-1' }]
162+
priority: [{ campaign: campaign._id, id: '-1' }],
163+
firstIntegration: (() => {
164+
const date = new Date(usr.firstIntegration || '');
165+
return isNaN(date.getTime()) ? Date.now() : date.getTime();
166+
})(),
167+
integrationReason: sanitizeString(usr.integrationReason || req.body.defaultReason || '')
136168
}
137169
);
138170
}
139171
} catch (error: any) {
140-
errors.push([usr.name + ' ' + (usr.firstname || ''), phone, error.message]);
172+
errors.push([(usr.name || '') + ' ' + (usr.firstname || ''), phone, error.message]);
141173
}
142174
}
143175
);

router/admin/client/searchByName.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ export default async function SearchByName(req: Request<any>, res: Response<any>
6666
const escapedNameParts = sanitizeString(req.body.name).split(' ').map(escapeRegExp);
6767
const regexParts = escapedNameParts.map(part => `(?=.*${part})`).join('');
6868
const regex = new RegExp(`^${regexParts}`, 'i');
69-
const output = await Client.find({ name: regex, campaigns: campaign }, ['name', 'phone']).limit(10);
69+
const output = await Client.find({ $or: [{ name: regex }, { firstname: regex }], campaigns: campaign }, [
70+
'name',
71+
'phone',
72+
'firstname'
73+
]).limit(10);
7074

7175
res.status(200).send({ message: 'OK', OK: true, data: output });
7276
log(`[${req.body.area}, ${ip}] Clients searched`, 'INFO', __filename);

router/admin/client/searchByPhone.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ export default async function SearchByPhone(req: Request<any>, res: Response<any
6262

6363
const output = await Client.find({ phone: { $regex: req.body.phone, $options: 'i' }, campaigns: campaign }, [
6464
'name',
65-
'phone'
65+
'phone',
66+
'firstname'
6667
]).limit(10);
6768
res.status(200).send({ message: 'OK', OK: true, data: output });
6869
log(`[${req.body.area}, ${ip}] Clients searched`, 'INFO', __filename);

router/admin/login.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export default async function login(req: Request<any>, res: Response<any>) {
5656
)
5757
return;
5858

59-
const password = await hashPasword(req.body.adminCode, req.body.allreadyHaseded, res);
59+
const password = hashPasword(req.body.adminCode, req.body.allreadyHaseded, res);
6060
if (!password) return;
6161
const area = await Area.findOne({ _id: { $eq: req.body.area }, adminPassword: password });
6262
if (!area) {

router/caller/getPhoneNumber.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export default async function getPhoneNumber(req: Request<any>, res: Response<an
7676
_id: { $eq: req.body.campaign },
7777
active: true
7878
},
79-
['script', 'callPermited', 'timeBetweenCall', 'nbMaxCallCampaign', 'active', 'status']
79+
['script', 'callPermited', 'timeBetweenCall', 'nbMaxCallCampaign', 'active', 'status', 'sortGroup']
8080
);
8181

8282
if (!campaign) {
@@ -104,10 +104,23 @@ export default async function getPhoneNumber(req: Request<any>, res: Response<an
104104
log(`[${req.body.phone}, ${ip}] Error while updating call ${caller.name} (${ip})`, 'ERROR', __filename);
105105
return;
106106
}
107+
const client = await Client.findOne({ _id: { $eq: call.client } }, [
108+
'name',
109+
'firstname',
110+
'institution',
111+
'phone',
112+
'priority'
113+
]);
114+
if (!client) {
115+
res.status(404).send({ message: 'Client not found', OK: false });
116+
log(`[${req.body.phone}, ${ip}] Client not found`, 'WARNING', __filename);
117+
return;
118+
}
119+
107120
res.status(200).send({
108121
message: 'Client to call',
109122
OK: true,
110-
client: await Client.findOne({ _id: { $eq: call.client } }, ['name', 'firstname', 'institution', 'phone']),
123+
client: client,
111124
callHistory: await Call.find(
112125
{
113126
client: { $eq: call.client },
@@ -117,7 +130,10 @@ export default async function getPhoneNumber(req: Request<any>, res: Response<an
117130
['status', 'satisfaction', 'duration', 'comment', 'start']
118131
),
119132
script: campaign.script,
120-
status: campaign.status
133+
status: campaign.status,
134+
priority:
135+
(campaign.sortGroup.find(group => group.id == (client as any).priority[0].id) as any)?.name ??
136+
'not found'
121137
});
122138
log(`[${req.body.phone}, ${ip}] Already in a call`, 'INFO', __filename);
123139
return;
@@ -282,7 +298,10 @@ export default async function getPhoneNumber(req: Request<any>, res: Response<an
282298
client: client[0],
283299
callHistory: lastsCall ?? [],
284300
script: campaign.script,
285-
status: campaign.status
301+
status: campaign.status,
302+
priority:
303+
(campaign.sortGroup.find(group => group.id == (client[0] as any).priority[0].id) as any)?.name ??
304+
'not found'
286305
});
287306
log(`[${req.body.phone}, ${ip}] Client to call`, 'INFO', __filename);
288307
}

tests/admin/client/createClients.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ describe('post on /admin/client/createClients', () => {
9999
.send({ adminCode: 'password', area: areaId, data: ['not an object'] });
100100
expect(res.status).toEqual(400);
101101
expect(res.body).toEqual({
102-
message: 'Each data entry must be an object with valid properties',
102+
message:
103+
'Each data entry must be an object with valid properties: {phone:string, name?:string, firstname?:string, institution?:string, priority?:string, firstIntegration?:date, integrationReason?:string}',
103104
OK: false
104105
});
105106
});

0 commit comments

Comments
 (0)