@talkpilot/core-db 1.3.89 → 1.3.90
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/README.md +0 -72
- package/dist/talkpilot/calls/calls.constants.d.ts +1 -11
- package/dist/talkpilot/calls/calls.constants.d.ts.map +1 -1
- package/dist/talkpilot/calls/calls.constants.js +1 -11
- package/dist/talkpilot/calls/calls.constants.js.map +1 -1
- package/dist/talkpilot/calls/calls.getters.d.ts +1 -8
- package/dist/talkpilot/calls/calls.getters.d.ts.map +1 -1
- package/dist/talkpilot/calls/calls.getters.js +1 -216
- package/dist/talkpilot/calls/calls.getters.js.map +1 -1
- package/dist/talkpilot/calls/calls.types.d.ts +0 -45
- package/dist/talkpilot/calls/calls.types.d.ts.map +1 -1
- package/dist/talkpilot/clients/clients.getters.d.ts +0 -6
- package/dist/talkpilot/clients/clients.getters.d.ts.map +1 -1
- package/dist/talkpilot/clients/clients.getters.js +1 -62
- package/dist/talkpilot/clients/clients.getters.js.map +1 -1
- package/dist/talkpilot/clients/clients.types.d.ts +0 -53
- package/dist/talkpilot/clients/clients.types.d.ts.map +1 -1
- package/dist/talkpilot/clients/index.d.ts +0 -2
- package/dist/talkpilot/clients/index.d.ts.map +1 -1
- package/dist/talkpilot/clients/index.js +0 -2
- package/dist/talkpilot/clients/index.js.map +1 -1
- package/dist/talkpilot/flows/flows.schema.d.ts +9 -0
- package/dist/talkpilot/flows/flows.schema.d.ts.map +1 -1
- package/dist/talkpilot/flows/flows.schema.js +7 -0
- package/dist/talkpilot/flows/flows.schema.js.map +1 -1
- package/dist/talkpilot/flows/flows.types.d.ts +3 -0
- package/dist/talkpilot/flows/flows.types.d.ts.map +1 -1
- package/dist/talkpilot/index.d.ts +0 -1
- package/dist/talkpilot/index.d.ts.map +1 -1
- package/dist/talkpilot/index.js +0 -1
- package/dist/talkpilot/index.js.map +1 -1
- package/dist/talkpilot/phone_numbers/phone_numbers.getter.d.ts +1 -9
- package/dist/talkpilot/phone_numbers/phone_numbers.getter.d.ts.map +1 -1
- package/dist/talkpilot/phone_numbers/phone_numbers.getter.js +1 -70
- package/dist/talkpilot/phone_numbers/phone_numbers.getter.js.map +1 -1
- package/dist/talkpilot/phone_numbers/phone_numbers.types.d.ts +0 -5
- package/dist/talkpilot/phone_numbers/phone_numbers.types.d.ts.map +1 -1
- package/dist/talkpilot/subscriptions/subscriptions.utils.d.ts +4 -0
- package/dist/talkpilot/subscriptions/subscriptions.utils.d.ts.map +1 -0
- package/dist/talkpilot/subscriptions/subscriptions.utils.js +20 -0
- package/dist/talkpilot/subscriptions/subscriptions.utils.js.map +1 -0
- package/dist/test-utils/factories/index.d.ts +0 -2
- package/dist/test-utils/factories/index.d.ts.map +1 -1
- package/dist/test-utils/factories/index.js +0 -2
- package/dist/test-utils/factories/index.js.map +1 -1
- package/dist/websitalk/scanResources/scanResources.getters.d.ts.map +1 -1
- package/dist/websitalk/scanResources/scanResources.getters.js +1 -2
- package/dist/websitalk/scanResources/scanResources.getters.js.map +1 -1
- package/dist/websitalk/scanResources/scanResources.utils.d.ts +1 -1
- package/dist/websitalk/scanResources/scanResources.utils.d.ts.map +1 -1
- package/dist/websitalk/scanResources/scanResources.utils.js +3 -3
- package/dist/websitalk/scanResources/scanResources.utils.js.map +1 -1
- package/package.json +1 -1
- package/src/talkpilot/calls/calls.constants.ts +0 -14
- package/src/talkpilot/calls/calls.getters.ts +0 -320
- package/src/talkpilot/calls/calls.types.ts +0 -52
- package/src/talkpilot/clients/clients.getters.ts +2 -91
- package/src/talkpilot/clients/clients.types.ts +0 -58
- package/src/talkpilot/clients/index.ts +0 -2
- package/src/talkpilot/flows/__tests__/flows.schema.spec.ts +11 -0
- package/src/talkpilot/flows/flows.schema.ts +7 -0
- package/src/talkpilot/flows/flows.types.ts +1 -0
- package/src/talkpilot/index.ts +0 -1
- package/src/talkpilot/phone_numbers/__tests__/phone_numbers.spec.ts +13 -284
- package/src/talkpilot/phone_numbers/phone_numbers.getter.ts +1 -89
- package/src/talkpilot/phone_numbers/phone_numbers.types.ts +0 -6
- package/src/test-utils/factories/index.ts +0 -2
- package/src/websitalk/scanResources/scanResources.getters.ts +6 -2
- package/src/websitalk/scanResources/scanResources.utils.ts +3 -2
- package/dist/talkpilot/auditLogs/auditLogs.getters.d.ts +0 -8
- package/dist/talkpilot/auditLogs/auditLogs.getters.d.ts.map +0 -1
- package/dist/talkpilot/auditLogs/auditLogs.getters.js +0 -26
- package/dist/talkpilot/auditLogs/auditLogs.getters.js.map +0 -1
- package/dist/talkpilot/auditLogs/auditLogs.types.d.ts +0 -11
- package/dist/talkpilot/auditLogs/auditLogs.types.d.ts.map +0 -1
- package/dist/talkpilot/auditLogs/auditLogs.types.js +0 -3
- package/dist/talkpilot/auditLogs/auditLogs.types.js.map +0 -1
- package/dist/talkpilot/auditLogs/index.d.ts +0 -3
- package/dist/talkpilot/auditLogs/index.d.ts.map +0 -1
- package/dist/talkpilot/auditLogs/index.js +0 -19
- package/dist/talkpilot/auditLogs/index.js.map +0 -1
- package/dist/talkpilot/clients/clients.constants.d.ts +0 -4
- package/dist/talkpilot/clients/clients.constants.d.ts.map +0 -1
- package/dist/talkpilot/clients/clients.constants.js +0 -7
- package/dist/talkpilot/clients/clients.constants.js.map +0 -1
- package/dist/talkpilot/clients/clients.lifecycle.d.ts +0 -10
- package/dist/talkpilot/clients/clients.lifecycle.d.ts.map +0 -1
- package/dist/talkpilot/clients/clients.lifecycle.js +0 -109
- package/dist/talkpilot/clients/clients.lifecycle.js.map +0 -1
- package/dist/test-utils/factories/talkpilot/clients.d.ts +0 -14
- package/dist/test-utils/factories/talkpilot/clients.d.ts.map +0 -1
- package/dist/test-utils/factories/talkpilot/clients.js +0 -19
- package/dist/test-utils/factories/talkpilot/clients.js.map +0 -1
- package/dist/test-utils/factories/talkpilot/results.d.ts +0 -16
- package/dist/test-utils/factories/talkpilot/results.d.ts.map +0 -1
- package/dist/test-utils/factories/talkpilot/results.js +0 -40
- package/dist/test-utils/factories/talkpilot/results.js.map +0 -1
- package/dist/utils/regex.utils.d.ts +0 -2
- package/dist/utils/regex.utils.d.ts.map +0 -1
- package/dist/utils/regex.utils.js +0 -6
- package/dist/utils/regex.utils.js.map +0 -1
- package/src/talkpilot/auditLogs/__tests__/auditLogs.spec.ts +0 -91
- package/src/talkpilot/auditLogs/auditLogs.getters.ts +0 -26
- package/src/talkpilot/auditLogs/auditLogs.types.ts +0 -12
- package/src/talkpilot/auditLogs/index.ts +0 -2
- package/src/talkpilot/calls/__tests__/calls.statistics.getters.spec.ts +0 -559
- package/src/talkpilot/clients/__tests__/clients.getters.spec.ts +0 -224
- package/src/talkpilot/clients/__tests__/clients.lifecycle.spec.ts +0 -194
- package/src/talkpilot/clients/clients.constants.ts +0 -4
- package/src/talkpilot/clients/clients.lifecycle.ts +0 -132
- package/src/test-utils/factories/talkpilot/clients.ts +0 -21
- package/src/test-utils/factories/talkpilot/results.ts +0 -43
- package/src/utils/regex.utils.ts +0 -2
|
@@ -1,21 +1,8 @@
|
|
|
1
1
|
import { ObjectId } from "mongodb";
|
|
2
2
|
import { ClientQuotaSettings } from "./clients.quota.types";
|
|
3
|
-
import { BACKOFFICE_ROLES } from "./clients.constants";
|
|
4
3
|
|
|
5
4
|
export type { QuotaThresholdAlert, ClientQuotaSettings, QuotaAlertInput, ClientQuotaUsage, ClientQuotaState } from "./clients.quota.types";
|
|
6
5
|
|
|
7
|
-
export type BackofficeRole = (typeof BACKOFFICE_ROLES)[number];
|
|
8
|
-
|
|
9
|
-
export type BackofficeRoleFlags = {
|
|
10
|
-
admin?: boolean;
|
|
11
|
-
viewer?: boolean;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type ClientRoles = {
|
|
15
|
-
admin?: boolean;
|
|
16
|
-
backoffice?: BackofficeRoleFlags;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
6
|
export type Client = {
|
|
20
7
|
_id?: ObjectId;
|
|
21
8
|
createdAt: Date;
|
|
@@ -26,51 +13,6 @@ export type Client = {
|
|
|
26
13
|
credits: number;
|
|
27
14
|
managedBy?: string;
|
|
28
15
|
quota?: ClientQuotaSettings;
|
|
29
|
-
roles?: ClientRoles;
|
|
30
16
|
};
|
|
31
17
|
|
|
32
18
|
export type ClientDoc = Client & { _id: ObjectId };
|
|
33
|
-
|
|
34
|
-
export type ClientsPageParams = {
|
|
35
|
-
skip: number;
|
|
36
|
-
limit: number;
|
|
37
|
-
/** Exact clientId match. */
|
|
38
|
-
clientId?: string;
|
|
39
|
-
/** Only these clients, e.g. the ones with calls in a date range. */
|
|
40
|
-
clientIds?: string[];
|
|
41
|
-
/** Every client except these, e.g. the ones that called since an inactivity cutoff. */
|
|
42
|
-
excludeClientIds?: string[];
|
|
43
|
-
/** Case-insensitive "contains" on the clientId or on any of the client's phone numbers. */
|
|
44
|
-
search?: string;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export type ClientsPageRow = Pick<ClientDoc, "_id" | "clientId" | "credits">;
|
|
48
|
-
|
|
49
|
-
export type ClientsPage = {
|
|
50
|
-
clients: ClientsPageRow[];
|
|
51
|
-
total: number;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export type CollectionMatch = { collectionName: string; matched: boolean };
|
|
55
|
-
|
|
56
|
-
export type CollectionDeleteResult = {
|
|
57
|
-
collectionName: string;
|
|
58
|
-
deletedCount: number;
|
|
59
|
-
success: boolean;
|
|
60
|
-
error?: string;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export type AuditLogResult = {
|
|
64
|
-
auditLogged: boolean;
|
|
65
|
-
auditError?: string;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
/** Discriminated union: `deleted` tells you which shape you got without guessing. */
|
|
69
|
-
export type DeleteClientDataSummary =
|
|
70
|
-
| { clientId: string; deleted: false; reason: string }
|
|
71
|
-
| ({
|
|
72
|
-
clientId: string;
|
|
73
|
-
deleted: true;
|
|
74
|
-
results: CollectionDeleteResult[];
|
|
75
|
-
allSucceeded: boolean;
|
|
76
|
-
} & AuditLogResult);
|
|
@@ -3,6 +3,17 @@ import { getDb } from "../../index";
|
|
|
3
3
|
import { flowMongoSchema } from "../flows.schema";
|
|
4
4
|
|
|
5
5
|
describe("flowMongoSchema", () => {
|
|
6
|
+
it("defines keypadInput as an optional object", () => {
|
|
7
|
+
const schema: any = flowMongoSchema;
|
|
8
|
+
|
|
9
|
+
expect(schema.properties).toHaveProperty("keypadInput");
|
|
10
|
+
expect(schema.properties.keypadInput.bsonType).toEqual(["object", "null"]);
|
|
11
|
+
expect(schema.properties.keypadInput.properties.enabled.bsonType).toBe(
|
|
12
|
+
"bool",
|
|
13
|
+
);
|
|
14
|
+
expect(schema.required).not.toContain("keypadInput");
|
|
15
|
+
});
|
|
16
|
+
|
|
6
17
|
it("defines disableGenericPrompt as optional bool", () => {
|
|
7
18
|
const schema: any = flowMongoSchema;
|
|
8
19
|
|
|
@@ -13,6 +13,13 @@ export const flowMongoSchema = {
|
|
|
13
13
|
systemInstructions: { bsonType: "string" },
|
|
14
14
|
initialSentence: { bsonType: "string" },
|
|
15
15
|
disableGenericPrompt: { bsonType: "bool" },
|
|
16
|
+
keypadInput: {
|
|
17
|
+
bsonType: ["object", "null"],
|
|
18
|
+
properties: {
|
|
19
|
+
enabled: { bsonType: "bool" },
|
|
20
|
+
},
|
|
21
|
+
additionalProperties: false,
|
|
22
|
+
},
|
|
16
23
|
voice: { bsonType: ["string", "null"] },
|
|
17
24
|
conversationSettings: {
|
|
18
25
|
bsonType: ["object", "null"],
|
package/src/talkpilot/index.ts
CHANGED
|
@@ -7,11 +7,6 @@ import {
|
|
|
7
7
|
createPhoneNumberEntity,
|
|
8
8
|
createPurchasedPhoneNumber,
|
|
9
9
|
getPrimaryPhoneFlowId,
|
|
10
|
-
getClientPhones,
|
|
11
|
-
findPhonesByClientIds,
|
|
12
|
-
getDistinctClientPhonePairs,
|
|
13
|
-
findClientsByPhoneNumbers,
|
|
14
|
-
deletePhoneNumberRecords,
|
|
15
10
|
findPhoneNumbersByClientId,
|
|
16
11
|
getOwnedPhoneNumber,
|
|
17
12
|
getPhoneNumberByClientAndFlow,
|
|
@@ -25,7 +20,7 @@ import { ObjectId } from "mongodb";
|
|
|
25
20
|
|
|
26
21
|
describe("db.phoneNumbers", () => {
|
|
27
22
|
describe("getPhoneDataByPhoneNumber", () => {
|
|
28
|
-
it("
|
|
23
|
+
it("return phone number data with flow", async () => {
|
|
29
24
|
const flow = createFlow({
|
|
30
25
|
clientId: "test-client-id",
|
|
31
26
|
conversationSettings: {
|
|
@@ -73,7 +68,7 @@ describe("db.phoneNumbers", () => {
|
|
|
73
68
|
});
|
|
74
69
|
|
|
75
70
|
describe("getClientPhoneNumber", () => {
|
|
76
|
-
it("
|
|
71
|
+
it("return phone number by client id", async () => {
|
|
77
72
|
const flow = createFlow();
|
|
78
73
|
const phoneData = createPhoneNumber({
|
|
79
74
|
flow_id: flow._id,
|
|
@@ -89,7 +84,7 @@ describe("db.phoneNumbers", () => {
|
|
|
89
84
|
});
|
|
90
85
|
|
|
91
86
|
describe("getClientPhoneData", () => {
|
|
92
|
-
it("
|
|
87
|
+
it("returns primary phone data by default", async () => {
|
|
93
88
|
const clientId = "multiPhoneClient";
|
|
94
89
|
const phone1 = createPhoneNumber({
|
|
95
90
|
client_id: clientId,
|
|
@@ -111,7 +106,7 @@ describe("db.phoneNumbers", () => {
|
|
|
111
106
|
expect(result?.phone_number).toBe("2");
|
|
112
107
|
});
|
|
113
108
|
|
|
114
|
-
it("
|
|
109
|
+
it("returns most recent non-primary phone data if isPrimary is false", async () => {
|
|
115
110
|
const clientId = "multiPhoneClient2";
|
|
116
111
|
const phone1 = createPhoneNumber({
|
|
117
112
|
client_id: clientId,
|
|
@@ -142,7 +137,7 @@ describe("db.phoneNumbers", () => {
|
|
|
142
137
|
});
|
|
143
138
|
|
|
144
139
|
describe("getPhoneNumbersForFlows", () => {
|
|
145
|
-
it("
|
|
140
|
+
it("returns all phone numbers for the client with flow ObjectIds, newest first", async () => {
|
|
146
141
|
const clientId = "flowsClient";
|
|
147
142
|
const flow1 = createFlow({ clientId });
|
|
148
143
|
const flow2 = createFlow({ clientId });
|
|
@@ -180,12 +175,12 @@ describe("db.phoneNumbers", () => {
|
|
|
180
175
|
});
|
|
181
176
|
});
|
|
182
177
|
|
|
183
|
-
it("
|
|
178
|
+
it("returns an empty array when the client has no phone numbers", async () => {
|
|
184
179
|
const result = await getPhoneNumbersForFlows("noSuchClient");
|
|
185
180
|
expect(result).toEqual([]);
|
|
186
181
|
});
|
|
187
182
|
|
|
188
|
-
it("
|
|
183
|
+
it("does not include phone numbers for other clients", async () => {
|
|
189
184
|
const clientA = "clientA";
|
|
190
185
|
const clientB = "clientB";
|
|
191
186
|
const flow = createFlow();
|
|
@@ -202,7 +197,7 @@ describe("db.phoneNumbers", () => {
|
|
|
202
197
|
});
|
|
203
198
|
|
|
204
199
|
describe("createPhoneNumberEntity", () => {
|
|
205
|
-
it("
|
|
200
|
+
it("creates first phone number as primary", async () => {
|
|
206
201
|
const clientId = "newClient";
|
|
207
202
|
const flowId = new ObjectId().toHexString();
|
|
208
203
|
const phoneNumber = "+123456789";
|
|
@@ -216,7 +211,7 @@ describe("db.phoneNumbers", () => {
|
|
|
216
211
|
expect(result.is_primary).toBe(true);
|
|
217
212
|
});
|
|
218
213
|
|
|
219
|
-
it("
|
|
214
|
+
it("creates subsequent phone number as non-primary", async () => {
|
|
220
215
|
const clientId = "existingClient";
|
|
221
216
|
const flowId = new ObjectId().toHexString();
|
|
222
217
|
|
|
@@ -231,7 +226,7 @@ describe("db.phoneNumbers", () => {
|
|
|
231
226
|
});
|
|
232
227
|
|
|
233
228
|
describe("createPurchasedPhoneNumber", () => {
|
|
234
|
-
it("
|
|
229
|
+
it("persists a phone_numbers document with provider payload and flow", async () => {
|
|
235
230
|
const clientId = "purchasedClientDb";
|
|
236
231
|
const flowId = new ObjectId().toHexString();
|
|
237
232
|
const phoneNumber = "+15551234567";
|
|
@@ -263,7 +258,7 @@ describe("db.phoneNumbers", () => {
|
|
|
263
258
|
});
|
|
264
259
|
|
|
265
260
|
describe("getPrimaryPhoneFlowId", () => {
|
|
266
|
-
it("
|
|
261
|
+
it("returns flow_id from primary phone as string", async () => {
|
|
267
262
|
const clientId = "primaryFlowClient";
|
|
268
263
|
const flowId = new ObjectId();
|
|
269
264
|
await getPhoneNumbersCollection().insertOne(
|
|
@@ -277,11 +272,11 @@ describe("db.phoneNumbers", () => {
|
|
|
277
272
|
expect(await getPrimaryPhoneFlowId(clientId)).toBe(flowId.toHexString());
|
|
278
273
|
});
|
|
279
274
|
|
|
280
|
-
it("
|
|
275
|
+
it("returns null when client has no primary phone", async () => {
|
|
281
276
|
expect(await getPrimaryPhoneFlowId("no-phone-client")).toBeNull();
|
|
282
277
|
});
|
|
283
278
|
|
|
284
|
-
it("
|
|
279
|
+
it("returns null when primary phone has no flow_id", async () => {
|
|
285
280
|
const clientId = "phoneNoFlow";
|
|
286
281
|
const phone = createPhoneNumber({
|
|
287
282
|
client_id: clientId,
|
|
@@ -294,272 +289,6 @@ describe("db.phoneNumbers", () => {
|
|
|
294
289
|
});
|
|
295
290
|
});
|
|
296
291
|
|
|
297
|
-
describe("getClientPhones", () => {
|
|
298
|
-
it("should return all phone numbers for the given client, projected to phoneNumber only", async () => {
|
|
299
|
-
const clientId = "phonesClientA";
|
|
300
|
-
await getPhoneNumbersCollection().insertMany([
|
|
301
|
-
createPhoneNumber({ client_id: clientId, phone_number: "+100" }),
|
|
302
|
-
createPhoneNumber({ client_id: clientId, phone_number: "+200" }),
|
|
303
|
-
]);
|
|
304
|
-
|
|
305
|
-
const result = await getClientPhones(clientId);
|
|
306
|
-
|
|
307
|
-
expect(result).toHaveLength(2);
|
|
308
|
-
expect(result).toEqual(
|
|
309
|
-
expect.arrayContaining([
|
|
310
|
-
{ phoneNumber: "+100" },
|
|
311
|
-
{ phoneNumber: "+200" },
|
|
312
|
-
]),
|
|
313
|
-
);
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
it("should not include phone numbers belonging to other clients", async () => {
|
|
317
|
-
await getPhoneNumbersCollection().insertOne(
|
|
318
|
-
createPhoneNumber({ client_id: "someOtherClient" }),
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
const result = await getClientPhones("phonesClientB");
|
|
322
|
-
expect(result).toEqual([]);
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
it("should remove exact duplicate phone records for the same client", async () => {
|
|
326
|
-
const clientId = "phonesClientDup";
|
|
327
|
-
await getPhoneNumbersCollection().insertMany([
|
|
328
|
-
createPhoneNumber({ client_id: clientId, phone_number: "+300" }),
|
|
329
|
-
createPhoneNumber({ client_id: clientId, phone_number: "+300" }),
|
|
330
|
-
createPhoneNumber({ client_id: clientId, phone_number: "+400" }),
|
|
331
|
-
]);
|
|
332
|
-
|
|
333
|
-
const result = await getClientPhones(clientId);
|
|
334
|
-
|
|
335
|
-
expect(result).toHaveLength(2);
|
|
336
|
-
expect(result).toEqual(
|
|
337
|
-
expect.arrayContaining([
|
|
338
|
-
{ phoneNumber: "+300" },
|
|
339
|
-
{ phoneNumber: "+400" },
|
|
340
|
-
]),
|
|
341
|
-
);
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
describe("findPhonesByClientIds", () => {
|
|
346
|
-
it("should return a Map of clientId to phone number for the requested clients only", async () => {
|
|
347
|
-
await getPhoneNumbersCollection().insertMany([
|
|
348
|
-
createPhoneNumber({ client_id: "mapClientA", phone_number: "+111" }),
|
|
349
|
-
createPhoneNumber({ client_id: "mapClientB", phone_number: "+222" }),
|
|
350
|
-
createPhoneNumber({ client_id: "mapClientC", phone_number: "+333" }),
|
|
351
|
-
]);
|
|
352
|
-
|
|
353
|
-
const result = await findPhonesByClientIds(["mapClientA", "mapClientB"]);
|
|
354
|
-
|
|
355
|
-
expect(result).toBeInstanceOf(Map);
|
|
356
|
-
expect(result.get("mapClientA")).toBe("+111");
|
|
357
|
-
expect(result.get("mapClientB")).toBe("+222");
|
|
358
|
-
expect(result.has("mapClientC")).toBe(false);
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it("should return an empty Map for an empty list of clients", async () => {
|
|
362
|
-
await getPhoneNumbersCollection().insertOne(
|
|
363
|
-
createPhoneNumber({ client_id: "mapClientA", phone_number: "+111" }),
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
const result = await findPhonesByClientIds([]);
|
|
367
|
-
|
|
368
|
-
expect(result.size).toBe(0);
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
it("should skip clients that have no phone number", async () => {
|
|
372
|
-
const result = await findPhonesByClientIds(["clientWithoutPhone"]);
|
|
373
|
-
expect(result.size).toBe(0);
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
it("should return the most recently updated phone number when a client has several", async () => {
|
|
377
|
-
const clientId = "mapClientRecent";
|
|
378
|
-
await getPhoneNumbersCollection().insertMany([
|
|
379
|
-
createPhoneNumber({
|
|
380
|
-
client_id: clientId,
|
|
381
|
-
phone_number: "+old",
|
|
382
|
-
updatedAt: new Date("2023-01-01"),
|
|
383
|
-
}),
|
|
384
|
-
createPhoneNumber({
|
|
385
|
-
client_id: clientId,
|
|
386
|
-
phone_number: "+new",
|
|
387
|
-
updatedAt: new Date("2023-06-01"),
|
|
388
|
-
}),
|
|
389
|
-
]);
|
|
390
|
-
|
|
391
|
-
const result = await findPhonesByClientIds([clientId]);
|
|
392
|
-
|
|
393
|
-
expect(result.get(clientId)).toBe("+new");
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
it("should prefer the is_primary number over a more recently updated non-primary one", async () => {
|
|
397
|
-
const clientId = "mapClientPrimary";
|
|
398
|
-
await getPhoneNumbersCollection().insertMany([
|
|
399
|
-
createPhoneNumber({
|
|
400
|
-
client_id: clientId,
|
|
401
|
-
phone_number: "+primary",
|
|
402
|
-
is_primary: true,
|
|
403
|
-
updatedAt: new Date("2023-01-01"),
|
|
404
|
-
}),
|
|
405
|
-
createPhoneNumber({
|
|
406
|
-
client_id: clientId,
|
|
407
|
-
phone_number: "+secondary",
|
|
408
|
-
is_primary: false,
|
|
409
|
-
updatedAt: new Date("2023-06-01"),
|
|
410
|
-
}),
|
|
411
|
-
]);
|
|
412
|
-
|
|
413
|
-
const result = await findPhonesByClientIds([clientId]);
|
|
414
|
-
|
|
415
|
-
expect(result.get(clientId)).toBe("+primary");
|
|
416
|
-
});
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
describe("getDistinctClientPhonePairs", () => {
|
|
420
|
-
it("should return unique client+phone pairs, collapsing exact duplicates", async () => {
|
|
421
|
-
await getPhoneNumbersCollection().insertMany([
|
|
422
|
-
createPhoneNumber({ client_id: "pairClientA", phone_number: "+500" }),
|
|
423
|
-
createPhoneNumber({ client_id: "pairClientA", phone_number: "+500" }),
|
|
424
|
-
createPhoneNumber({ client_id: "pairClientB", phone_number: "+600" }),
|
|
425
|
-
]);
|
|
426
|
-
|
|
427
|
-
const result = await getDistinctClientPhonePairs();
|
|
428
|
-
|
|
429
|
-
expect(result).toEqual(
|
|
430
|
-
expect.arrayContaining([
|
|
431
|
-
{ clientId: "pairClientA", phoneNumber: "+500", isPrimary: true },
|
|
432
|
-
{ clientId: "pairClientB", phoneNumber: "+600", isPrimary: true },
|
|
433
|
-
]),
|
|
434
|
-
);
|
|
435
|
-
expect(
|
|
436
|
-
result.filter(
|
|
437
|
-
(p) => p.clientId === "pairClientA" && p.phoneNumber === "+500",
|
|
438
|
-
),
|
|
439
|
-
).toHaveLength(1);
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
it("should keep separate pairs when the same number is shared by different clients", async () => {
|
|
443
|
-
await getPhoneNumbersCollection().insertMany([
|
|
444
|
-
createPhoneNumber({ client_id: "sharedClientA", phone_number: "+700" }),
|
|
445
|
-
createPhoneNumber({ client_id: "sharedClientB", phone_number: "+700" }),
|
|
446
|
-
]);
|
|
447
|
-
|
|
448
|
-
const result = await getDistinctClientPhonePairs();
|
|
449
|
-
|
|
450
|
-
expect(result).toEqual(
|
|
451
|
-
expect.arrayContaining([
|
|
452
|
-
{ clientId: "sharedClientA", phoneNumber: "+700", isPrimary: true },
|
|
453
|
-
{ clientId: "sharedClientB", phoneNumber: "+700", isPrimary: true },
|
|
454
|
-
]),
|
|
455
|
-
);
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
it("should report isPrimary: false for a non-primary number", async () => {
|
|
459
|
-
await getPhoneNumbersCollection().insertMany([
|
|
460
|
-
createPhoneNumber({ client_id: "mixedClient", phone_number: "+900", is_primary: true }),
|
|
461
|
-
createPhoneNumber({ client_id: "mixedClient", phone_number: "+901", is_primary: false }),
|
|
462
|
-
]);
|
|
463
|
-
|
|
464
|
-
const result = await getDistinctClientPhonePairs();
|
|
465
|
-
|
|
466
|
-
expect(result).toEqual(
|
|
467
|
-
expect.arrayContaining([
|
|
468
|
-
{ clientId: "mixedClient", phoneNumber: "+900", isPrimary: true },
|
|
469
|
-
{ clientId: "mixedClient", phoneNumber: "+901", isPrimary: false },
|
|
470
|
-
]),
|
|
471
|
-
);
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
it("should report isPrimary: false when no phone_numbers document for that client is marked primary", async () => {
|
|
475
|
-
await getPhoneNumbersCollection().insertOne(
|
|
476
|
-
createPhoneNumber({ client_id: "noPrimaryClient", phone_number: "+902", is_primary: false }),
|
|
477
|
-
);
|
|
478
|
-
|
|
479
|
-
const result = await getDistinctClientPhonePairs();
|
|
480
|
-
|
|
481
|
-
expect(result).toContainEqual({
|
|
482
|
-
clientId: "noPrimaryClient",
|
|
483
|
-
phoneNumber: "+902",
|
|
484
|
-
isPrimary: false,
|
|
485
|
-
});
|
|
486
|
-
});
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
describe("getDistinctClientPhonePairs with clientIds", () => {
|
|
490
|
-
it("should return only the pairs of the given clients", async () => {
|
|
491
|
-
await getPhoneNumbersCollection().insertMany([
|
|
492
|
-
createPhoneNumber({ client_id: "wantedPairClient", phone_number: "+111" }),
|
|
493
|
-
createPhoneNumber({ client_id: "otherPairClient", phone_number: "+222" }),
|
|
494
|
-
]);
|
|
495
|
-
|
|
496
|
-
const result = await getDistinctClientPhonePairs(["wantedPairClient"]);
|
|
497
|
-
|
|
498
|
-
expect(result).toEqual([
|
|
499
|
-
{ clientId: "wantedPairClient", phoneNumber: "+111", isPrimary: true },
|
|
500
|
-
]);
|
|
501
|
-
});
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
describe("findClientsByPhoneNumbers", () => {
|
|
505
|
-
it("should return other clients sharing each phone number, excluding the given client", async () => {
|
|
506
|
-
await getPhoneNumbersCollection().insertMany([
|
|
507
|
-
createPhoneNumber({ client_id: "excludeMe", phone_number: "+800" }),
|
|
508
|
-
createPhoneNumber({ client_id: "otherClient1", phone_number: "+800" }),
|
|
509
|
-
createPhoneNumber({ client_id: "otherClient2", phone_number: "+800" }),
|
|
510
|
-
]);
|
|
511
|
-
|
|
512
|
-
const result = await findClientsByPhoneNumbers(["+800"], "excludeMe");
|
|
513
|
-
|
|
514
|
-
expect(result.get("+800")).toEqual(
|
|
515
|
-
expect.arrayContaining(["otherClient1", "otherClient2"]),
|
|
516
|
-
);
|
|
517
|
-
expect(result.get("+800")).not.toContain("excludeMe");
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
it("should return an empty array for a phone number with no other clients", async () => {
|
|
521
|
-
await getPhoneNumbersCollection().insertOne(
|
|
522
|
-
createPhoneNumber({ client_id: "soleOwner", phone_number: "+900" }),
|
|
523
|
-
);
|
|
524
|
-
|
|
525
|
-
const result = await findClientsByPhoneNumbers(["+900"], "soleOwner");
|
|
526
|
-
|
|
527
|
-
expect(result.get("+900")).toEqual([]);
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
it("should return an empty map immediately when given no phone numbers", async () => {
|
|
531
|
-
const result = await findClientsByPhoneNumbers([]);
|
|
532
|
-
expect(result.size).toBe(0);
|
|
533
|
-
});
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
describe("deletePhoneNumberRecords", () => {
|
|
537
|
-
it("should delete every record for the given phone number across all clients", async () => {
|
|
538
|
-
await getPhoneNumbersCollection().insertMany([
|
|
539
|
-
createPhoneNumber({ client_id: "delClientA", phone_number: "+1000" }),
|
|
540
|
-
createPhoneNumber({ client_id: "delClientB", phone_number: "+1000" }),
|
|
541
|
-
createPhoneNumber({ client_id: "delClientC", phone_number: "+2000" }),
|
|
542
|
-
]);
|
|
543
|
-
|
|
544
|
-
const deletedCount = await deletePhoneNumberRecords("+1000");
|
|
545
|
-
|
|
546
|
-
expect(deletedCount).toBe(2);
|
|
547
|
-
const remaining = await getPhoneNumbersCollection()
|
|
548
|
-
.find({ phone_number: "+1000" })
|
|
549
|
-
.toArray();
|
|
550
|
-
expect(remaining).toHaveLength(0);
|
|
551
|
-
const untouched = await getPhoneNumbersCollection().findOne({
|
|
552
|
-
phone_number: "+2000",
|
|
553
|
-
});
|
|
554
|
-
expect(untouched?.client_id).toBe("delClientC");
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
it("should return 0 when no records match the phone number", async () => {
|
|
558
|
-
const deletedCount = await deletePhoneNumberRecords("+9999999");
|
|
559
|
-
expect(deletedCount).toBe(0);
|
|
560
|
-
});
|
|
561
|
-
});
|
|
562
|
-
|
|
563
292
|
describe("findPhoneNumbersByClientId", () => {
|
|
564
293
|
it("returns only that client's numbers", async () => {
|
|
565
294
|
const clientId = "list-client";
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
} from "../index";
|
|
10
10
|
import {
|
|
11
11
|
ClientPhoneNumberForFlow,
|
|
12
|
-
ClientPhonePair,
|
|
13
12
|
PhoneNumber,
|
|
14
13
|
PhoneNumberWithFlow,
|
|
15
14
|
PurchasedPhoneProviderPayload,
|
|
@@ -159,9 +158,7 @@ export const findClientByPhoneNumber = async (
|
|
|
159
158
|
return client;
|
|
160
159
|
};
|
|
161
160
|
|
|
162
|
-
export const getPrimaryPhoneFlowId = async (
|
|
163
|
-
clientId: string,
|
|
164
|
-
): Promise<string | null> => {
|
|
161
|
+
export const getPrimaryPhoneFlowId = async (clientId: string): Promise<string | null> => {
|
|
165
162
|
const phone = await getClientPhoneData(clientId);
|
|
166
163
|
return phone?.flow_id ? String(phone.flow_id) : null;
|
|
167
164
|
};
|
|
@@ -179,91 +176,6 @@ export const bindClientPhoneToFlow = async (
|
|
|
179
176
|
}
|
|
180
177
|
};
|
|
181
178
|
|
|
182
|
-
export const getClientPhones = async (
|
|
183
|
-
clientId: string,
|
|
184
|
-
): Promise<{ phoneNumber: string }[]> => {
|
|
185
|
-
const docs = await getPhoneNumbersCollection()
|
|
186
|
-
.aggregate<{ _id: string }>([
|
|
187
|
-
{ $match: { client_id: clientId } },
|
|
188
|
-
{ $group: { _id: "$phone_number" } },
|
|
189
|
-
])
|
|
190
|
-
.toArray();
|
|
191
|
-
return docs.map((d) => ({ phoneNumber: d._id }));
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
export const findPhonesByClientIds = async (
|
|
195
|
-
clientIds: string[],
|
|
196
|
-
): Promise<Map<string, string>> => {
|
|
197
|
-
if (clientIds.length === 0) return new Map();
|
|
198
|
-
|
|
199
|
-
const docs = await getPhoneNumbersCollection()
|
|
200
|
-
.aggregate<{ _id: string; phone_number: string }>([
|
|
201
|
-
{ $match: { client_id: { $in: clientIds } } },
|
|
202
|
-
{ $sort: { is_primary: -1, updatedAt: -1 } },
|
|
203
|
-
{ $group: { _id: "$client_id", phone_number: { $first: "$phone_number" } } },
|
|
204
|
-
])
|
|
205
|
-
.toArray();
|
|
206
|
-
return new Map(docs.map((d) => [d._id, d.phone_number]));
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
export const getDistinctClientPhonePairs = async (
|
|
210
|
-
clientIds?: string[],
|
|
211
|
-
): Promise<ClientPhonePair[]> => {
|
|
212
|
-
const docs = await getPhoneNumbersCollection()
|
|
213
|
-
.aggregate<{
|
|
214
|
-
_id: { client_id: string; phone_number: string };
|
|
215
|
-
isPrimary: boolean;
|
|
216
|
-
}>([
|
|
217
|
-
...(clientIds ? [{ $match: { client_id: { $in: clientIds } } }] : []),
|
|
218
|
-
{
|
|
219
|
-
$group: {
|
|
220
|
-
_id: { client_id: "$client_id", phone_number: "$phone_number" },
|
|
221
|
-
isPrimary: { $max: "$is_primary" },
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
])
|
|
225
|
-
.toArray();
|
|
226
|
-
return docs.map((d) => ({
|
|
227
|
-
clientId: d._id.client_id,
|
|
228
|
-
phoneNumber: d._id.phone_number,
|
|
229
|
-
isPrimary: Boolean(d.isPrimary),
|
|
230
|
-
}));
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
export const findClientsByPhoneNumbers = async (
|
|
234
|
-
phoneNumbers: string[],
|
|
235
|
-
excludeClientId?: string,
|
|
236
|
-
): Promise<Map<string, string[]>> => {
|
|
237
|
-
const result = new Map<string, string[]>(
|
|
238
|
-
phoneNumbers.map((p) => [p, [] as string[]]),
|
|
239
|
-
);
|
|
240
|
-
if (phoneNumbers.length === 0) return result;
|
|
241
|
-
|
|
242
|
-
const docs = await getPhoneNumbersCollection()
|
|
243
|
-
.aggregate<{ _id: string; clientIds: string[] }>([
|
|
244
|
-
{
|
|
245
|
-
$match: {
|
|
246
|
-
phone_number: { $in: phoneNumbers },
|
|
247
|
-
...(excludeClientId ? { client_id: { $ne: excludeClientId } } : {}),
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
|
-
{ $group: { _id: "$phone_number", clientIds: { $addToSet: "$client_id" } } },
|
|
251
|
-
])
|
|
252
|
-
.toArray();
|
|
253
|
-
|
|
254
|
-
docs.forEach((d) => result.set(d._id, d.clientIds));
|
|
255
|
-
return result;
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
/** Deletes this number's record for every client that references it. Check `findClientsByPhoneNumbers` first to see who's affected. */
|
|
259
|
-
export const deletePhoneNumberRecords = async (
|
|
260
|
-
phoneNumber: string,
|
|
261
|
-
): Promise<number> => {
|
|
262
|
-
const result = await getPhoneNumbersCollection().deleteMany({
|
|
263
|
-
phone_number: phoneNumber,
|
|
264
|
-
});
|
|
265
|
-
return result.deletedCount;
|
|
266
|
-
};
|
|
267
179
|
export const findPhoneNumbersByClientId = (
|
|
268
180
|
clientId: string,
|
|
269
181
|
): Promise<PhoneNumber[]> =>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export * from "./talkpilot/agents";
|
|
2
|
-
export * from "./talkpilot/clients";
|
|
3
2
|
export * from "./talkpilot/calls";
|
|
4
3
|
export * from "./talkpilot/clientAudioBuffers";
|
|
5
4
|
export * from "./talkpilot/clientsConfig";
|
|
@@ -8,7 +7,6 @@ export * from "./talkpilot/contextNotes";
|
|
|
8
7
|
export * from "./talkpilot/groups";
|
|
9
8
|
export * from "./talkpilot/phone_numbers";
|
|
10
9
|
export * from "./talkpilot/sessions";
|
|
11
|
-
export * from "./talkpilot/results";
|
|
12
10
|
export * from "./municipal/cities";
|
|
13
11
|
export * from "./municipal/departmentsSubjects";
|
|
14
12
|
export * from "./municipal/muniIssues";
|
|
@@ -17,8 +17,12 @@ import type {
|
|
|
17
17
|
ScanResourcePageSummary,
|
|
18
18
|
UrlTaskStatus,
|
|
19
19
|
} from "./scanResources.types";
|
|
20
|
-
import {
|
|
21
|
-
|
|
20
|
+
import {
|
|
21
|
+
decodeCursor,
|
|
22
|
+
encodeCursor,
|
|
23
|
+
escapeRegex,
|
|
24
|
+
statusesForColumn,
|
|
25
|
+
} from "./scanResources.utils";
|
|
22
26
|
|
|
23
27
|
export const getScanResourcesCollection = (): Collection<ScanResource> =>
|
|
24
28
|
getDb().collection<ScanResource>(SCAN_RESOURCES_COLLECTION);
|