@talkpilot/core-db 1.1.14 → 1.1.16
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/.cursor/rules/development.mdc +65 -65
- package/DEVELOPMENT.md +141 -98
- package/README.md +297 -160
- package/README_OLD.md +160 -0
- package/dist/connection.d.ts.map +1 -1
- package/dist/connection.js +10 -0
- package/dist/connection.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/municipal/index.d.ts +1 -0
- package/dist/municipal/index.d.ts.map +1 -1
- package/dist/municipal/index.js +1 -0
- package/dist/municipal/index.js.map +1 -1
- package/dist/municipal/muniIssues/index.d.ts +7 -0
- package/dist/municipal/muniIssues/index.d.ts.map +1 -0
- package/dist/municipal/muniIssues/index.js +29 -0
- package/dist/municipal/muniIssues/index.js.map +1 -0
- package/dist/municipal/muniIssues/muniIssues.constants.d.ts +14 -0
- package/dist/municipal/muniIssues/muniIssues.constants.d.ts.map +1 -0
- package/dist/municipal/muniIssues/muniIssues.constants.js +17 -0
- package/dist/municipal/muniIssues/muniIssues.constants.js.map +1 -0
- package/dist/municipal/muniIssues/muniIssues.getters.d.ts +7 -0
- package/dist/municipal/muniIssues/muniIssues.getters.d.ts.map +1 -0
- package/dist/municipal/muniIssues/muniIssues.getters.js +24 -0
- package/dist/municipal/muniIssues/muniIssues.getters.js.map +1 -0
- package/dist/municipal/muniIssues/muniIssues.schema.d.ts +4 -0
- package/dist/municipal/muniIssues/muniIssues.schema.d.ts.map +1 -0
- package/dist/municipal/muniIssues/muniIssues.schema.js +63 -0
- package/dist/municipal/muniIssues/muniIssues.schema.js.map +1 -0
- package/dist/municipal/muniIssues/muniIssues.setters.d.ts +4 -0
- package/dist/municipal/muniIssues/muniIssues.setters.d.ts.map +1 -0
- package/dist/municipal/muniIssues/muniIssues.setters.js +22 -0
- package/dist/municipal/muniIssues/muniIssues.setters.js.map +1 -0
- package/dist/municipal/muniIssues/muniIssues.setup.d.ts +3 -0
- package/dist/municipal/muniIssues/muniIssues.setup.d.ts.map +1 -0
- package/dist/municipal/muniIssues/muniIssues.setup.js +61 -0
- package/dist/municipal/muniIssues/muniIssues.setup.js.map +1 -0
- package/dist/municipal/muniIssues/muniIssues.types.d.ts +30 -0
- package/dist/municipal/muniIssues/muniIssues.types.d.ts.map +1 -0
- package/dist/municipal/muniIssues/muniIssues.types.js +3 -0
- package/dist/municipal/muniIssues/muniIssues.types.js.map +1 -0
- package/dist/municipal/tickets/index.d.ts +2 -1
- package/dist/municipal/tickets/index.d.ts.map +1 -1
- package/dist/municipal/tickets/index.js +1 -0
- package/dist/municipal/tickets/index.js.map +1 -1
- package/dist/municipal/tickets/tickets.constants.d.ts +7 -0
- package/dist/municipal/tickets/tickets.constants.d.ts.map +1 -0
- package/dist/municipal/tickets/tickets.constants.js +10 -0
- package/dist/municipal/tickets/tickets.constants.js.map +1 -0
- package/dist/municipal/tickets/tickets.getters.d.ts +0 -11
- package/dist/municipal/tickets/tickets.getters.d.ts.map +1 -1
- package/dist/municipal/tickets/tickets.getters.js +0 -128
- package/dist/municipal/tickets/tickets.getters.js.map +1 -1
- package/dist/municipal/tickets/tickets.statistics.aggregation.d.ts +69 -0
- package/dist/municipal/tickets/tickets.statistics.aggregation.d.ts.map +1 -0
- package/dist/municipal/tickets/tickets.statistics.aggregation.js +99 -0
- package/dist/municipal/tickets/tickets.statistics.aggregation.js.map +1 -0
- package/dist/municipal/tickets/tickets.statistics.getters.d.ts +12 -0
- package/dist/municipal/tickets/tickets.statistics.getters.d.ts.map +1 -0
- package/dist/municipal/tickets/tickets.statistics.getters.js +80 -0
- package/dist/municipal/tickets/tickets.statistics.getters.js.map +1 -0
- package/dist/municipal/tickets/tickets.types.d.ts +15 -5
- package/dist/municipal/tickets/tickets.types.d.ts.map +1 -1
- package/dist/talkpilot/calls/calls.constants.d.ts +17 -0
- package/dist/talkpilot/calls/calls.constants.d.ts.map +1 -0
- package/dist/talkpilot/calls/calls.constants.js +20 -0
- package/dist/talkpilot/calls/calls.constants.js.map +1 -0
- package/dist/talkpilot/calls/calls.dashboard.d.ts +3 -0
- package/dist/talkpilot/calls/calls.dashboard.d.ts.map +1 -0
- package/dist/talkpilot/calls/calls.dashboard.js +191 -0
- package/dist/talkpilot/calls/calls.dashboard.js.map +1 -0
- package/dist/talkpilot/calls/calls.getters.d.ts +3 -2
- package/dist/talkpilot/calls/calls.getters.d.ts.map +1 -1
- package/dist/talkpilot/calls/calls.getters.js +1 -2
- package/dist/talkpilot/calls/calls.getters.js.map +1 -1
- package/dist/talkpilot/calls/calls.statistics.getters.d.ts +28 -0
- package/dist/talkpilot/calls/calls.statistics.getters.d.ts.map +1 -0
- package/dist/talkpilot/calls/calls.statistics.getters.js +453 -0
- package/dist/talkpilot/calls/calls.statistics.getters.js.map +1 -0
- package/dist/talkpilot/calls/calls.statistics.types.d.ts +43 -0
- package/dist/talkpilot/calls/calls.statistics.types.d.ts.map +1 -0
- package/dist/talkpilot/calls/calls.statistics.types.js +3 -0
- package/dist/talkpilot/calls/calls.statistics.types.js.map +1 -0
- package/dist/talkpilot/calls/calls.types.d.ts +8 -7
- package/dist/talkpilot/calls/calls.types.d.ts.map +1 -1
- package/dist/talkpilot/calls/calls.types.js +0 -3
- package/dist/talkpilot/calls/calls.types.js.map +1 -1
- package/dist/talkpilot/calls/dashboard/calls.dashboard.d.ts +4 -0
- package/dist/talkpilot/calls/dashboard/calls.dashboard.d.ts.map +1 -0
- package/dist/talkpilot/calls/dashboard/calls.dashboard.js +223 -0
- package/dist/talkpilot/calls/dashboard/calls.dashboard.js.map +1 -0
- package/dist/talkpilot/calls/dashboard/calls.dashboard.types.d.ts +45 -0
- package/dist/talkpilot/calls/dashboard/calls.dashboard.types.d.ts.map +1 -0
- package/dist/talkpilot/calls/dashboard/calls.dashboard.types.js +3 -0
- package/dist/talkpilot/calls/dashboard/calls.dashboard.types.js.map +1 -0
- package/dist/talkpilot/calls/index.d.ts +4 -0
- package/dist/talkpilot/calls/index.d.ts.map +1 -1
- package/dist/talkpilot/calls/index.js +4 -0
- package/dist/talkpilot/calls/index.js.map +1 -1
- package/dist/talkpilot/clientsConfig/clientsConfig.getters.d.ts +2 -1
- package/dist/talkpilot/clientsConfig/clientsConfig.getters.d.ts.map +1 -1
- package/dist/talkpilot/clientsConfig/clientsConfig.getters.js +14 -0
- package/dist/talkpilot/clientsConfig/clientsConfig.getters.js.map +1 -1
- package/dist/talkpilot/clientsConfig/clientsConfig.types.d.ts +20 -7
- package/dist/talkpilot/clientsConfig/clientsConfig.types.d.ts.map +1 -1
- package/dist/talkpilot/clientsConfig/clientsConfig.types.js +6 -0
- package/dist/talkpilot/clientsConfig/clientsConfig.types.js.map +1 -1
- package/dist/talkpilot/contextNotes/contextNotes.getters.d.ts +19 -0
- package/dist/talkpilot/contextNotes/contextNotes.getters.d.ts.map +1 -0
- package/dist/talkpilot/contextNotes/contextNotes.getters.js +55 -0
- package/dist/talkpilot/contextNotes/contextNotes.getters.js.map +1 -0
- package/dist/talkpilot/contextNotes/contextNotes.types.d.ts +19 -0
- package/dist/talkpilot/contextNotes/contextNotes.types.d.ts.map +1 -0
- package/dist/talkpilot/contextNotes/contextNotes.types.js +3 -0
- package/dist/talkpilot/contextNotes/contextNotes.types.js.map +1 -0
- package/dist/talkpilot/contextNotes/index.d.ts +3 -0
- package/dist/talkpilot/contextNotes/index.d.ts.map +1 -0
- package/dist/talkpilot/contextNotes/index.js +19 -0
- package/dist/talkpilot/contextNotes/index.js.map +1 -0
- package/dist/talkpilot/flows/flows.schema.d.ts +3 -0
- package/dist/talkpilot/flows/flows.schema.d.ts.map +1 -1
- package/dist/talkpilot/flows/flows.schema.js +2 -1
- package/dist/talkpilot/flows/flows.schema.js.map +1 -1
- package/dist/talkpilot/flows/flows.types.d.ts +2 -0
- package/dist/talkpilot/flows/flows.types.d.ts.map +1 -1
- package/dist/talkpilot/index.d.ts +3 -0
- package/dist/talkpilot/index.d.ts.map +1 -1
- package/dist/talkpilot/index.js +3 -0
- package/dist/talkpilot/index.js.map +1 -1
- package/dist/talkpilot/phone_numbers/index.d.ts +2 -2
- package/dist/talkpilot/phone_numbers/phone_numbers.getter.d.ts +1 -1
- package/dist/talkpilot/phone_numbers/phone_numbers.getter.d.ts.map +1 -1
- package/dist/talkpilot/phone_numbers/phone_numbers.getter.js +5 -3
- package/dist/talkpilot/phone_numbers/phone_numbers.getter.js.map +1 -1
- package/dist/talkpilot/phone_numbers/phone_numbers.schema.js +12 -12
- package/dist/talkpilot/phone_numbers/phone_numbers.types.d.ts +4 -4
- package/dist/talkpilot/products/index.d.ts +3 -0
- package/dist/talkpilot/products/index.d.ts.map +1 -0
- package/dist/talkpilot/products/index.js +19 -0
- package/dist/talkpilot/products/index.js.map +1 -0
- package/dist/talkpilot/products/products.getters.d.ts +6 -0
- package/dist/talkpilot/products/products.getters.d.ts.map +1 -0
- package/dist/talkpilot/products/products.getters.js +14 -0
- package/dist/talkpilot/products/products.getters.js.map +1 -0
- package/dist/talkpilot/products/products.types.d.ts +9 -0
- package/dist/talkpilot/products/products.types.d.ts.map +1 -0
- package/dist/talkpilot/products/products.types.js +3 -0
- package/dist/talkpilot/products/products.types.js.map +1 -0
- package/dist/talkpilot/results/index.d.ts +1 -1
- package/dist/talkpilot/results/index.d.ts.map +1 -1
- package/dist/talkpilot/results/index.js +3 -1
- package/dist/talkpilot/results/index.js.map +1 -1
- package/dist/talkpilot/results/results.getter.d.ts +3 -1
- package/dist/talkpilot/results/results.getter.d.ts.map +1 -1
- package/dist/talkpilot/results/results.getter.js +11 -2
- package/dist/talkpilot/results/results.getter.js.map +1 -1
- package/dist/talkpilot/retry_analyze/index.d.ts +3 -0
- package/dist/talkpilot/retry_analyze/index.d.ts.map +1 -0
- package/dist/talkpilot/retry_analyze/index.js +19 -0
- package/dist/talkpilot/retry_analyze/index.js.map +1 -0
- package/dist/talkpilot/retry_analyze/retryAnalyze.getters.d.ts +10 -0
- package/dist/talkpilot/retry_analyze/retryAnalyze.getters.d.ts.map +1 -0
- package/dist/talkpilot/retry_analyze/retryAnalyze.getters.js +60 -0
- package/dist/talkpilot/retry_analyze/retryAnalyze.getters.js.map +1 -0
- package/dist/talkpilot/retry_analyze/retryAnalyze.types.d.ts +12 -0
- package/dist/talkpilot/retry_analyze/retryAnalyze.types.d.ts.map +1 -0
- package/dist/talkpilot/retry_analyze/retryAnalyze.types.js +3 -0
- package/dist/talkpilot/retry_analyze/retryAnalyze.types.js.map +1 -0
- package/dist/test-utils/db-utils.d.ts.map +1 -1
- package/dist/test-utils/db-utils.js +2 -0
- package/dist/test-utils/db-utils.js.map +1 -1
- package/dist/test-utils/factories/index.d.ts +3 -0
- package/dist/test-utils/factories/index.d.ts.map +1 -1
- package/dist/test-utils/factories/index.js +3 -0
- package/dist/test-utils/factories/index.js.map +1 -1
- package/dist/test-utils/factories/municipal/muniIssues.d.ts +10 -0
- package/dist/test-utils/factories/municipal/muniIssues.d.ts.map +1 -0
- package/dist/test-utils/factories/municipal/muniIssues.js +30 -0
- package/dist/test-utils/factories/municipal/muniIssues.js.map +1 -0
- package/dist/test-utils/factories/talkpilot/contextNotes.d.ts +16 -0
- package/dist/test-utils/factories/talkpilot/contextNotes.d.ts.map +1 -0
- package/dist/test-utils/factories/talkpilot/contextNotes.js +33 -0
- package/dist/test-utils/factories/talkpilot/contextNotes.js.map +1 -0
- package/dist/test-utils/factories/websitalk/scans.d.ts +5 -0
- package/dist/test-utils/factories/websitalk/scans.d.ts.map +1 -0
- package/dist/test-utils/factories/websitalk/scans.js +25 -0
- package/dist/test-utils/factories/websitalk/scans.js.map +1 -0
- package/dist/utils/date.utils.d.ts +49 -0
- package/dist/utils/date.utils.d.ts.map +1 -0
- package/dist/utils/date.utils.js +108 -0
- package/dist/utils/date.utils.js.map +1 -0
- package/dist/utils/shared.types.d.ts +5 -0
- package/dist/utils/shared.types.d.ts.map +1 -0
- package/dist/utils/shared.types.js +3 -0
- package/dist/utils/shared.types.js.map +1 -0
- package/dist/websitalk/index.d.ts +7 -0
- package/dist/websitalk/index.d.ts.map +1 -0
- package/dist/websitalk/index.js +34 -0
- package/dist/websitalk/index.js.map +1 -0
- package/dist/websitalk/mongodb-client.d.ts +13 -0
- package/dist/websitalk/mongodb-client.d.ts.map +1 -0
- package/dist/websitalk/mongodb-client.js +56 -0
- package/dist/websitalk/mongodb-client.js.map +1 -0
- package/dist/websitalk/scans/index.d.ts +3 -0
- package/dist/websitalk/scans/index.d.ts.map +1 -0
- package/dist/websitalk/scans/index.js +19 -0
- package/dist/websitalk/scans/index.js.map +1 -0
- package/dist/websitalk/scans/scans.getters.d.ts +12 -0
- package/dist/websitalk/scans/scans.getters.d.ts.map +1 -0
- package/dist/websitalk/scans/scans.getters.js +74 -0
- package/dist/websitalk/scans/scans.getters.js.map +1 -0
- package/dist/websitalk/scans/scans.types.d.ts +45 -0
- package/dist/websitalk/scans/scans.types.d.ts.map +1 -0
- package/dist/websitalk/scans/scans.types.js +3 -0
- package/dist/websitalk/scans/scans.types.js.map +1 -0
- package/jest.config.js +20 -19
- package/package.json +46 -45
- package/src/connection.ts +12 -0
- package/src/index.ts +10 -0
- package/src/municipal/index.ts +1 -0
- package/src/municipal/muniIssues/__tests__/muniIssues.setters.spec.ts +94 -0
- package/src/municipal/muniIssues/__tests__/muniIssues.setup.spec.ts +92 -0
- package/src/municipal/muniIssues/index.ts +17 -0
- package/src/municipal/muniIssues/muniIssues.constants.ts +18 -0
- package/src/municipal/muniIssues/muniIssues.getters.ts +24 -0
- package/src/municipal/muniIssues/muniIssues.schema.ts +66 -0
- package/src/municipal/muniIssues/muniIssues.setters.ts +24 -0
- package/src/municipal/muniIssues/muniIssues.setup.ts +65 -0
- package/src/municipal/muniIssues/muniIssues.types.ts +37 -0
- package/src/municipal/tickets/__tests__/tickets.getters.spec.ts +1 -37
- package/src/municipal/tickets/__tests__/tickets.statistics.spec.ts +99 -0
- package/src/municipal/tickets/index.ts +9 -2
- package/src/municipal/tickets/tickets.constants.ts +8 -0
- package/src/municipal/tickets/tickets.getters.ts +0 -140
- package/src/municipal/tickets/tickets.statistics.aggregation.ts +110 -0
- package/src/municipal/tickets/tickets.statistics.getters.ts +132 -0
- package/src/municipal/tickets/tickets.types.ts +54 -43
- package/src/talkpilot/calls/__tests__/calls.dashboard.spec.ts +149 -0
- package/src/talkpilot/calls/__tests__/calls.spec.ts +48 -30
- package/src/talkpilot/calls/__tests__/calls.statistics.spec.ts +481 -0
- package/src/talkpilot/calls/calls.constants.ts +20 -0
- package/src/talkpilot/calls/calls.getters.ts +6 -6
- package/src/talkpilot/calls/calls.statistics.getters.ts +668 -0
- package/src/talkpilot/calls/calls.statistics.types.ts +48 -0
- package/src/talkpilot/calls/calls.types.ts +18 -14
- package/src/talkpilot/calls/dashboard/calls.dashboard.ts +292 -0
- package/src/talkpilot/calls/dashboard/calls.dashboard.types.ts +57 -0
- package/src/talkpilot/calls/index.ts +4 -0
- package/src/talkpilot/clientsConfig/__tests__/clientsConfig.getters.spec.ts +53 -0
- package/src/talkpilot/clientsConfig/__tests__/clientsConfig.spec.ts +91 -7
- package/src/talkpilot/clientsConfig/clientsConfig.getters.ts +34 -1
- package/src/talkpilot/clientsConfig/clientsConfig.types.ts +127 -92
- package/src/talkpilot/contextNotes/__tests__/contextNotes.getters.spec.ts +217 -0
- package/src/talkpilot/contextNotes/contextNotes.getters.ts +85 -0
- package/src/talkpilot/contextNotes/contextNotes.types.ts +20 -0
- package/src/talkpilot/contextNotes/index.ts +2 -0
- package/src/talkpilot/flows/__tests__/flows.schema.spec.ts +31 -2
- package/src/talkpilot/flows/flows.schema.ts +2 -1
- package/src/talkpilot/flows/flows.types.ts +3 -0
- package/src/talkpilot/index.ts +3 -0
- package/src/talkpilot/phone_numbers/__tests__/phone_numbers.spec.ts +40 -35
- package/src/talkpilot/phone_numbers/index.ts +2 -2
- package/src/talkpilot/phone_numbers/phone_numbers.getter.ts +10 -6
- package/src/talkpilot/phone_numbers/phone_numbers.schema.ts +12 -12
- package/src/talkpilot/phone_numbers/phone_numbers.types.ts +4 -4
- package/src/talkpilot/products/__tests__/products.getters.spec.ts +44 -0
- package/src/talkpilot/products/index.ts +2 -0
- package/src/talkpilot/products/products.getters.ts +12 -0
- package/src/talkpilot/products/products.types.ts +9 -0
- package/src/talkpilot/results/index.ts +2 -0
- package/src/talkpilot/results/results.getter.ts +14 -2
- package/src/talkpilot/retry_analyze/__tests__/retryAnalyze.getters.spec.ts +156 -0
- package/src/talkpilot/retry_analyze/index.ts +2 -0
- package/src/talkpilot/retry_analyze/retryAnalyze.getters.ts +84 -0
- package/src/talkpilot/retry_analyze/retryAnalyze.types.ts +13 -0
- package/src/test-utils/db-utils.ts +3 -1
- package/src/test-utils/factories/index.ts +3 -0
- package/src/test-utils/factories/municipal/muniIssues.ts +32 -0
- package/src/test-utils/factories/talkpilot/contextNotes.ts +40 -0
- package/src/test-utils/factories/websitalk/scans.ts +23 -0
- package/src/utils/date.utils.ts +126 -0
- package/src/utils/shared.types.ts +4 -0
- package/src/websitalk/index.ts +15 -0
- package/src/websitalk/mongodb-client.ts +61 -0
- package/src/websitalk/scans/__tests__/scans.spec.ts +218 -0
- package/src/websitalk/scans/index.ts +2 -0
- package/src/websitalk/scans/scans.getters.ts +113 -0
- package/src/websitalk/scans/scans.types.ts +53 -0
- package/tsconfig.json +23 -23
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.countScans = exports.findScansByQuery = exports.updateScanDoc = exports.getActiveScanStatus = exports.createScanDoc = exports.getScanFieldsByClientAndDateRange = exports.getScansByClientAndDateRange = exports.getScansCollection = void 0;
|
|
4
|
+
const index_1 = require("../index");
|
|
5
|
+
const query_utils_1 = require("../../talkpilot/utils/query.utils");
|
|
6
|
+
const getScansCollection = () => {
|
|
7
|
+
return (0, index_1.getDb)().collection("scans");
|
|
8
|
+
};
|
|
9
|
+
exports.getScansCollection = getScansCollection;
|
|
10
|
+
const getScansByClientAndDateRange = (clientId, startDate, endDate) => {
|
|
11
|
+
return (0, exports.getScansCollection)()
|
|
12
|
+
.find({
|
|
13
|
+
clientId,
|
|
14
|
+
createdAt: {
|
|
15
|
+
$gte: startDate,
|
|
16
|
+
$lte: endDate,
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
.toArray();
|
|
20
|
+
};
|
|
21
|
+
exports.getScansByClientAndDateRange = getScansByClientAndDateRange;
|
|
22
|
+
const getScanFieldsByClientAndDateRange = (clientId, startDate, endDate, fields) => {
|
|
23
|
+
const projection = fields.reduce((acc, field) => {
|
|
24
|
+
acc[field] = 1;
|
|
25
|
+
return acc;
|
|
26
|
+
}, {});
|
|
27
|
+
return (0, exports.getScansCollection)()
|
|
28
|
+
.find({
|
|
29
|
+
clientId,
|
|
30
|
+
createdAt: {
|
|
31
|
+
$gte: startDate,
|
|
32
|
+
$lte: endDate,
|
|
33
|
+
},
|
|
34
|
+
}, { projection })
|
|
35
|
+
.toArray();
|
|
36
|
+
};
|
|
37
|
+
exports.getScanFieldsByClientAndDateRange = getScanFieldsByClientAndDateRange;
|
|
38
|
+
const createScanDoc = (scan) => {
|
|
39
|
+
return (0, exports.getScansCollection)().insertOne({
|
|
40
|
+
...scan,
|
|
41
|
+
status: "CHECKING",
|
|
42
|
+
activeTasksCount: 0,
|
|
43
|
+
pagesScanned: 0,
|
|
44
|
+
pagesSkipped: 0,
|
|
45
|
+
skipped: [],
|
|
46
|
+
siteChrome: {},
|
|
47
|
+
createdAt: new Date(),
|
|
48
|
+
updatedAt: new Date(),
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
exports.createScanDoc = createScanDoc;
|
|
52
|
+
const FINISHED_SCAN_STATUSES = ["COMPLETED", "FAILED"];
|
|
53
|
+
const getActiveScanStatus = async (clientId) => {
|
|
54
|
+
const latestScan = await (0, exports.getScansCollection)().findOne({ clientId }, { sort: { createdAt: -1 } });
|
|
55
|
+
if (!latestScan || FINISHED_SCAN_STATUSES.includes(latestScan.status)) {
|
|
56
|
+
return { isActive: false };
|
|
57
|
+
}
|
|
58
|
+
return { isActive: true, status: latestScan.status };
|
|
59
|
+
};
|
|
60
|
+
exports.getActiveScanStatus = getActiveScanStatus;
|
|
61
|
+
const updateScanDoc = async (scanId, updates) => {
|
|
62
|
+
return await (0, exports.getScansCollection)().findOneAndUpdate({ _id: scanId }, { $set: { ...updates, updatedAt: new Date() } }, { returnDocument: "after" });
|
|
63
|
+
};
|
|
64
|
+
exports.updateScanDoc = updateScanDoc;
|
|
65
|
+
const findScansByQuery = async (query, options) => {
|
|
66
|
+
const cursor = (0, exports.getScansCollection)().find(query);
|
|
67
|
+
return await (0, query_utils_1.applyQueryOptions)(cursor, options).toArray();
|
|
68
|
+
};
|
|
69
|
+
exports.findScansByQuery = findScansByQuery;
|
|
70
|
+
const countScans = async (query) => {
|
|
71
|
+
return (0, exports.getScansCollection)().countDocuments(query);
|
|
72
|
+
};
|
|
73
|
+
exports.countScans = countScans;
|
|
74
|
+
//# sourceMappingURL=scans.getters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scans.getters.js","sourceRoot":"","sources":["../../../src/websitalk/scans/scans.getters.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { WithId } from "mongodb";
|
|
2
|
+
export type ScanStatus = "CHECKING" | "PREPARING" | "SCANNING" | "PROCESSING" | "COMPLETED" | "FAILED";
|
|
3
|
+
export type ActiveScanStatus = {
|
|
4
|
+
isActive: true;
|
|
5
|
+
status: ScanStatus;
|
|
6
|
+
} | {
|
|
7
|
+
isActive: false;
|
|
8
|
+
};
|
|
9
|
+
export type SiteChromeEntry = {
|
|
10
|
+
url: string;
|
|
11
|
+
urlCanonical?: string | null;
|
|
12
|
+
title?: string | null;
|
|
13
|
+
h1?: string | null;
|
|
14
|
+
language?: string | null;
|
|
15
|
+
sourceType?: string | null;
|
|
16
|
+
status?: string | null;
|
|
17
|
+
context?: string | null;
|
|
18
|
+
contentHash?: string | null;
|
|
19
|
+
errorMessage?: string | null;
|
|
20
|
+
breadcrumbs?: unknown;
|
|
21
|
+
description?: string | null;
|
|
22
|
+
sourceUrl?: string | null;
|
|
23
|
+
fetchedAt?: Date | null;
|
|
24
|
+
createdAt?: Date;
|
|
25
|
+
updatedAt?: Date;
|
|
26
|
+
};
|
|
27
|
+
export type SkippedEntry = {
|
|
28
|
+
path: string;
|
|
29
|
+
reason: string;
|
|
30
|
+
};
|
|
31
|
+
export type Scan = {
|
|
32
|
+
clientId: string;
|
|
33
|
+
baseUrl: string;
|
|
34
|
+
notifyPhoneNumber?: string | null;
|
|
35
|
+
status: ScanStatus;
|
|
36
|
+
activeTasksCount: number;
|
|
37
|
+
pagesScanned: number;
|
|
38
|
+
pagesSkipped: number;
|
|
39
|
+
skipped: SkippedEntry[];
|
|
40
|
+
siteChrome: SiteChromeEntry;
|
|
41
|
+
createdAt: Date;
|
|
42
|
+
updatedAt: Date;
|
|
43
|
+
};
|
|
44
|
+
export type ScanDoc = WithId<Scan>;
|
|
45
|
+
//# sourceMappingURL=scans.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scans.types.d.ts","sourceRoot":"","sources":["../../../src/websitalk/scans/scans.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC,MAAM,MAAM,UAAU,GAClB,UAAU,GACV,WAAW,GACX,UAAU,GACV,YAAY,GACZ,WAAW,GACX,QAAQ,CAAC;AAEb,MAAM,MAAM,gBAAgB,GACxB;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GACtC;IAAE,QAAQ,EAAE,KAAK,CAAA;CAAE,CAAC;AAExB,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,MAAM,EAAE,UAAU,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,UAAU,EAAE,eAAe,CAAC;IAC5B,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scans.types.js","sourceRoot":"","sources":["../../../src/websitalk/scans/scans.types.ts"],"names":[],"mappings":""}
|
package/jest.config.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
|
2
|
-
module.exports = {
|
|
3
|
-
preset: 'ts-jest',
|
|
4
|
-
testEnvironment: 'node',
|
|
5
|
-
roots: ['<rootDir>/src'],
|
|
6
|
-
testPathIgnorePatterns: ['<rootDir>/src/__tests__/legacy/'],
|
|
7
|
-
testMatch: ['**/__tests__/**/*.spec.ts', '**/*.spec.ts'],
|
|
8
|
-
transform: {
|
|
9
|
-
'^.+\\.tsx?$': ['ts-jest', {
|
|
10
|
-
tsconfig: 'tsconfig.json',
|
|
11
|
-
}],
|
|
12
|
-
},
|
|
13
|
-
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts'],
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
preset: 'ts-jest',
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
roots: ['<rootDir>/src'],
|
|
6
|
+
testPathIgnorePatterns: ['<rootDir>/src/__tests__/legacy/'],
|
|
7
|
+
testMatch: ['**/__tests__/**/*.spec.ts', '**/*.spec.ts'],
|
|
8
|
+
transform: {
|
|
9
|
+
'^.+\\.tsx?$': ['ts-jest', {
|
|
10
|
+
tsconfig: 'tsconfig.json',
|
|
11
|
+
}],
|
|
12
|
+
},
|
|
13
|
+
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts'],
|
|
14
|
+
maxWorkers: 1,
|
|
15
|
+
verbose: true,
|
|
16
|
+
forceExit: true,
|
|
17
|
+
clearMocks: true,
|
|
18
|
+
resetMocks: true,
|
|
19
|
+
restoreMocks: true,
|
|
20
|
+
};
|
package/package.json
CHANGED
|
@@ -1,45 +1,46 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@talkpilot/core-db",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "Core database package for centralized connections and ORM integration.",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"dev": "tsc --watch",
|
|
10
|
-
"test": "jest",
|
|
11
|
-
"lint": "eslint 'src/**/*.{ts,tsx}'",
|
|
12
|
-
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
13
|
-
"prepare": "npm run build"
|
|
14
|
-
},
|
|
15
|
-
"keywords": [
|
|
16
|
-
"database",
|
|
17
|
-
"orm",
|
|
18
|
-
"typescript",
|
|
19
|
-
"postgres",
|
|
20
|
-
"db-client"
|
|
21
|
-
],
|
|
22
|
-
"author": "",
|
|
23
|
-
"license": "MIT",
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"@
|
|
32
|
-
"@types/
|
|
33
|
-
"@types/
|
|
34
|
-
"@types/
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@talkpilot/core-db",
|
|
3
|
+
"version": "1.1.16",
|
|
4
|
+
"description": "Core database package for centralized connections and ORM integration.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"dev": "tsc --watch",
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"lint": "eslint 'src/**/*.{ts,tsx}'",
|
|
12
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
13
|
+
"prepare": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"database",
|
|
17
|
+
"orm",
|
|
18
|
+
"typescript",
|
|
19
|
+
"postgres",
|
|
20
|
+
"db-client"
|
|
21
|
+
],
|
|
22
|
+
"author": "",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"dayjs": "^1.11.21",
|
|
26
|
+
"express": "^4.18.0",
|
|
27
|
+
"google-libphonenumber": "^3.2.0",
|
|
28
|
+
"mongodb": "^6.11.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@faker-js/faker": "^8.0.0",
|
|
32
|
+
"@types/express": "^4.17.0",
|
|
33
|
+
"@types/google-libphonenumber": "^7.4.0",
|
|
34
|
+
"@types/jest": "^29.0.0",
|
|
35
|
+
"@types/node": "^20.0.0",
|
|
36
|
+
"fishery": "^2.4.0",
|
|
37
|
+
"jest": "^29.0.0",
|
|
38
|
+
"mongodb-memory-server": "^10.0.0",
|
|
39
|
+
"prettier": "^3.8.2",
|
|
40
|
+
"ts-jest": "^29.0.0",
|
|
41
|
+
"typescript": "^5.0.0"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/src/connection.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mongodbClient } from "./talkpilot";
|
|
2
2
|
import { municipalDataMongodbClient } from "./municipal";
|
|
3
|
+
import { websitalkMongodbClient } from "./websitalk";
|
|
3
4
|
|
|
4
5
|
let isConnected = false;
|
|
5
6
|
|
|
@@ -15,6 +16,7 @@ export async function ensureDbConnected() {
|
|
|
15
16
|
const mongoUri = process.env.MONGO_URI || process.env.MONGODB_URI;
|
|
16
17
|
const talkpilotUri = mongoUri;
|
|
17
18
|
const municipalUri = mongoUri;
|
|
19
|
+
const websiteTalkUri = mongoUri;
|
|
18
20
|
|
|
19
21
|
if (!talkpilotUri) {
|
|
20
22
|
console.warn("[core-db] MONGO_URI is not set");
|
|
@@ -38,5 +40,15 @@ export async function ensureDbConnected() {
|
|
|
38
40
|
);
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
if (!websiteTalkUri) {
|
|
44
|
+
console.warn("[core-db] MONGO_URI is not set");
|
|
45
|
+
} else {
|
|
46
|
+
const dbName = process.env.WEBSITALK_DB_NAME;
|
|
47
|
+
await websitalkMongodbClient.connect(websiteTalkUri, dbName);
|
|
48
|
+
console.info(
|
|
49
|
+
`[core-db] Connected to Website Talk MongoDB: ${dbName || "website-talk (default)"}`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
41
53
|
isConnected = true;
|
|
42
54
|
}
|
package/src/index.ts
CHANGED
|
@@ -13,4 +13,14 @@ export * from "./municipal/streets";
|
|
|
13
13
|
export * from "./municipal/departmentsSubjects";
|
|
14
14
|
export * from "./municipal/tickets";
|
|
15
15
|
export * from "./municipal/systemInstructions";
|
|
16
|
+
export * from "./municipal/muniIssues";
|
|
16
17
|
export * from "./municipal/utils/types";
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
websitalkMongodbClient,
|
|
21
|
+
getDb as getWebsitalkDb,
|
|
22
|
+
setDb as setWebsitalkDb,
|
|
23
|
+
ObjectId as WebsitalkObjectId,
|
|
24
|
+
} from "./websitalk";
|
|
25
|
+
export * from "./websitalk/scans/scans.getters";
|
|
26
|
+
export type * from "./websitalk/scans/scans.types";
|
package/src/municipal/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./streets";
|
|
|
5
5
|
export * from "./departmentsSubjects";
|
|
6
6
|
export * from "./tickets";
|
|
7
7
|
export * from "./systemInstructions";
|
|
8
|
+
export * from "./muniIssues";
|
|
8
9
|
export * from "./utils/types";
|
|
9
10
|
export { CityName } from "./utils/types";
|
|
10
11
|
export { municipalDataMongodbClient } from "./mongodb-client";
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { createMuniIssue } from "../muniIssues.setters";
|
|
2
|
+
import {
|
|
3
|
+
generateMuniIssueId,
|
|
4
|
+
getMuniIssueById,
|
|
5
|
+
getMuniIssueByJiraKey,
|
|
6
|
+
} from "../muniIssues.getters";
|
|
7
|
+
import { createMuniIssueInput } from "../../../test-utils/factories";
|
|
8
|
+
|
|
9
|
+
describe("muniIssues setters", () => {
|
|
10
|
+
describe("createMuniIssue", () => {
|
|
11
|
+
it("should insert a well-formed document with createdAt and nested content/jira", async () => {
|
|
12
|
+
const input = createMuniIssueInput();
|
|
13
|
+
const id = generateMuniIssueId();
|
|
14
|
+
|
|
15
|
+
const insertedId = await createMuniIssue(input, id);
|
|
16
|
+
|
|
17
|
+
const saved = await getMuniIssueById(insertedId.toString());
|
|
18
|
+
expect(saved).toMatchObject({
|
|
19
|
+
callSid: input.callSid,
|
|
20
|
+
clientId: input.clientId,
|
|
21
|
+
cityName: input.cityName,
|
|
22
|
+
issueContent: input.issueContent,
|
|
23
|
+
jira: input.jira,
|
|
24
|
+
});
|
|
25
|
+
expect(saved?.createdAt).toBeInstanceOf(Date);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should default jira.syncStatus to created when not provided", async () => {
|
|
29
|
+
const id = generateMuniIssueId();
|
|
30
|
+
|
|
31
|
+
const insertedId = await createMuniIssue(
|
|
32
|
+
createMuniIssueInput({ jira: { key: "CP-9" } }),
|
|
33
|
+
id,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const saved = await getMuniIssueById(insertedId.toString());
|
|
37
|
+
expect(saved?.jira.syncStatus).toBe("created");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should store long issueDescription and desiredBehavior in full", async () => {
|
|
41
|
+
const issueDescription = "a".repeat(2000);
|
|
42
|
+
const desiredBehavior = "b".repeat(2000);
|
|
43
|
+
const id = generateMuniIssueId();
|
|
44
|
+
|
|
45
|
+
const insertedId = await createMuniIssue(
|
|
46
|
+
createMuniIssueInput({
|
|
47
|
+
issueContent: {
|
|
48
|
+
issuerName: "Dana",
|
|
49
|
+
issuerRole: "Agent",
|
|
50
|
+
issueDescription,
|
|
51
|
+
desiredBehavior,
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
id,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const saved = await getMuniIssueById(insertedId.toString());
|
|
58
|
+
expect(saved?.issueContent.issueDescription).toHaveLength(2000);
|
|
59
|
+
expect(saved?.issueContent.desiredBehavior).toBe(desiredBehavior);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should persist the caller-supplied _id so the Jira ticket can link back", async () => {
|
|
63
|
+
const id = generateMuniIssueId();
|
|
64
|
+
|
|
65
|
+
const insertedId = await createMuniIssue(createMuniIssueInput(), id);
|
|
66
|
+
|
|
67
|
+
expect(insertedId.toString()).toBe(id.toString());
|
|
68
|
+
expect(await getMuniIssueById(id.toString())).not.toBeNull();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("getMuniIssueById", () => {
|
|
73
|
+
it("should return null for an unknown or malformed id instead of throwing", async () => {
|
|
74
|
+
expect(
|
|
75
|
+
await getMuniIssueById(generateMuniIssueId().toString()),
|
|
76
|
+
).toBeNull();
|
|
77
|
+
expect(await getMuniIssueById("not-a-valid-id")).toBeNull();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("getMuniIssueByJiraKey", () => {
|
|
82
|
+
it("should find the issue with the given Jira key", async () => {
|
|
83
|
+
await createMuniIssue(
|
|
84
|
+
createMuniIssueInput({ jira: { key: "CP-555" } }),
|
|
85
|
+
generateMuniIssueId(),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const found = await getMuniIssueByJiraKey("CP-555");
|
|
89
|
+
|
|
90
|
+
expect(found?.jira.key).toBe("CP-555");
|
|
91
|
+
expect(await getMuniIssueByJiraKey("CP-nope")).toBeNull();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureMuniIssuesCollection,
|
|
3
|
+
ensureMuniIssuesCollectionOnce,
|
|
4
|
+
} from "../muniIssues.setup";
|
|
5
|
+
import {
|
|
6
|
+
generateMuniIssueId,
|
|
7
|
+
getMuniIssuesCollection,
|
|
8
|
+
} from "../muniIssues.getters";
|
|
9
|
+
import { createMuniIssue } from "../muniIssues.setters";
|
|
10
|
+
import { getDb } from "../../index";
|
|
11
|
+
import { MUNI_ISSUES_COLLECTION } from "../muniIssues.constants";
|
|
12
|
+
import { createMuniIssueInput } from "../../../test-utils/factories";
|
|
13
|
+
|
|
14
|
+
describe("muniIssues setup", () => {
|
|
15
|
+
describe("ensureMuniIssuesCollection", () => {
|
|
16
|
+
it("should create the collection with a validator and the declared indexes", async () => {
|
|
17
|
+
await ensureMuniIssuesCollection();
|
|
18
|
+
|
|
19
|
+
const [collection] = (await getDb()
|
|
20
|
+
.listCollections({ name: MUNI_ISSUES_COLLECTION })
|
|
21
|
+
.toArray()) as { options?: { validator?: unknown } }[];
|
|
22
|
+
expect(collection?.options?.validator).toHaveProperty("$jsonSchema");
|
|
23
|
+
|
|
24
|
+
const indexNames = (await getMuniIssuesCollection().indexes()).map(
|
|
25
|
+
(i) => i.name,
|
|
26
|
+
);
|
|
27
|
+
expect(indexNames).toEqual(
|
|
28
|
+
expect.arrayContaining([
|
|
29
|
+
"callSid_1",
|
|
30
|
+
"createdAt_-1",
|
|
31
|
+
"jira_key_unique",
|
|
32
|
+
]),
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should accept a valid document and reject ones that violate the schema", async () => {
|
|
37
|
+
await ensureMuniIssuesCollection();
|
|
38
|
+
|
|
39
|
+
await expect(
|
|
40
|
+
createMuniIssue(createMuniIssueInput(), generateMuniIssueId()),
|
|
41
|
+
).resolves.toBeDefined();
|
|
42
|
+
|
|
43
|
+
// Missing required issueContent fields → DB validation rejects it.
|
|
44
|
+
await expect(
|
|
45
|
+
getMuniIssuesCollection().insertOne({
|
|
46
|
+
callSid: "CA-bad",
|
|
47
|
+
createdAt: new Date(),
|
|
48
|
+
} as never),
|
|
49
|
+
).rejects.toThrow();
|
|
50
|
+
|
|
51
|
+
// jira.syncStatus outside the enum → DB validation rejects it.
|
|
52
|
+
await expect(
|
|
53
|
+
createMuniIssue(
|
|
54
|
+
createMuniIssueInput({
|
|
55
|
+
jira: { key: "CP-99", syncStatus: "weird" as never },
|
|
56
|
+
}),
|
|
57
|
+
generateMuniIssueId(),
|
|
58
|
+
),
|
|
59
|
+
).rejects.toThrow();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should enforce the unique jira.key index", async () => {
|
|
63
|
+
await ensureMuniIssuesCollection();
|
|
64
|
+
const input = createMuniIssueInput();
|
|
65
|
+
await createMuniIssue(input, generateMuniIssueId());
|
|
66
|
+
|
|
67
|
+
// Same jira.key on a second insert violates the unique index.
|
|
68
|
+
await expect(
|
|
69
|
+
createMuniIssue(input, generateMuniIssueId()),
|
|
70
|
+
).rejects.toThrow();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should be idempotent when run twice", async () => {
|
|
74
|
+
await ensureMuniIssuesCollection();
|
|
75
|
+
await expect(ensureMuniIssuesCollection()).resolves.toBeUndefined();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("ensureMuniIssuesCollectionOnce", () => {
|
|
80
|
+
it("should run the provisioning work at most once per process", async () => {
|
|
81
|
+
const createSpy = jest.spyOn(getDb(), "createCollection");
|
|
82
|
+
|
|
83
|
+
await Promise.all([
|
|
84
|
+
ensureMuniIssuesCollectionOnce(),
|
|
85
|
+
ensureMuniIssuesCollectionOnce(),
|
|
86
|
+
]);
|
|
87
|
+
await ensureMuniIssuesCollectionOnce();
|
|
88
|
+
|
|
89
|
+
expect(createSpy).toHaveBeenCalledTimes(1);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from "./muniIssues.getters";
|
|
2
|
+
export * from "./muniIssues.setters";
|
|
3
|
+
export * from "./muniIssues.setup";
|
|
4
|
+
export {
|
|
5
|
+
MUNI_ISSUES_COLLECTION,
|
|
6
|
+
MUNI_ISSUE_SYNC_STATUSES,
|
|
7
|
+
MUNI_ISSUE_TEXT_MAX_LENGTH,
|
|
8
|
+
MUNI_ISSUE_NAME_MAX_LENGTH,
|
|
9
|
+
} from "./muniIssues.constants";
|
|
10
|
+
export { muniIssuesJsonSchema, muniIssuesIndexes } from "./muniIssues.schema";
|
|
11
|
+
export type {
|
|
12
|
+
MuniIssue,
|
|
13
|
+
MuniIssueContent,
|
|
14
|
+
MuniIssueJira,
|
|
15
|
+
MuniIssueSyncStatus,
|
|
16
|
+
CreateMuniIssueInput,
|
|
17
|
+
} from "./muniIssues.types";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Mongo collection name for reported municipal issues. */
|
|
2
|
+
export const MUNI_ISSUES_COLLECTION = "muniIssues";
|
|
3
|
+
|
|
4
|
+
/** Sync state of the issue's Jira ticket at write time. */
|
|
5
|
+
export const MUNI_ISSUE_SYNC_STATUSES = ["created", "failed"] as const;
|
|
6
|
+
|
|
7
|
+
/** Max length for the long free-text fields — well above the ~2000-char target. */
|
|
8
|
+
export const MUNI_ISSUE_TEXT_MAX_LENGTH = 5000;
|
|
9
|
+
|
|
10
|
+
/** Max length for the short identity fields (a person's name and role). */
|
|
11
|
+
export const MUNI_ISSUE_NAME_MAX_LENGTH = 200;
|
|
12
|
+
|
|
13
|
+
/** MongoDB error 48 (NamespaceExists): collection already exists, e.g. concurrent createCollection. */
|
|
14
|
+
export const NAMESPACE_EXISTS = 48;
|
|
15
|
+
|
|
16
|
+
/** Reject (not just flag) writes that fail the $jsonSchema validator. */
|
|
17
|
+
export const VALIDATION_LEVEL = "strict" as const;
|
|
18
|
+
export const VALIDATION_ACTION = "error" as const;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Collection, ObjectId } from "mongodb";
|
|
2
|
+
import { getDb } from "../index";
|
|
3
|
+
import { MuniIssue } from "./muniIssues.types";
|
|
4
|
+
import { MUNI_ISSUES_COLLECTION } from "./muniIssues.constants";
|
|
5
|
+
|
|
6
|
+
export const getMuniIssuesCollection = (): Collection<MuniIssue> => {
|
|
7
|
+
return getDb().collection<MuniIssue>(MUNI_ISSUES_COLLECTION);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const generateMuniIssueId = (): ObjectId => new ObjectId();
|
|
11
|
+
|
|
12
|
+
export const getMuniIssueById = async (
|
|
13
|
+
id: string,
|
|
14
|
+
): Promise<MuniIssue | null> => {
|
|
15
|
+
// A malformed id is treated as not-found rather than throwing on `new ObjectId`.
|
|
16
|
+
if (!ObjectId.isValid(id)) return null;
|
|
17
|
+
return getMuniIssuesCollection().findOne({ _id: new ObjectId(id) });
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const getMuniIssueByJiraKey = async (
|
|
21
|
+
jiraKey: string,
|
|
22
|
+
): Promise<MuniIssue | null> => {
|
|
23
|
+
return getMuniIssuesCollection().findOne({ "jira.key": jiraKey });
|
|
24
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Document, IndexDescription } from "mongodb";
|
|
2
|
+
import {
|
|
3
|
+
MUNI_ISSUE_SYNC_STATUSES,
|
|
4
|
+
MUNI_ISSUE_TEXT_MAX_LENGTH,
|
|
5
|
+
MUNI_ISSUE_NAME_MAX_LENGTH,
|
|
6
|
+
} from "./muniIssues.constants";
|
|
7
|
+
|
|
8
|
+
export const muniIssuesJsonSchema: Document = {
|
|
9
|
+
bsonType: "object",
|
|
10
|
+
required: [
|
|
11
|
+
"callSid",
|
|
12
|
+
"clientId",
|
|
13
|
+
"cityName",
|
|
14
|
+
"issueContent",
|
|
15
|
+
"jira",
|
|
16
|
+
"createdAt",
|
|
17
|
+
],
|
|
18
|
+
properties: {
|
|
19
|
+
callSid: { bsonType: "string" },
|
|
20
|
+
clientId: { bsonType: "string" },
|
|
21
|
+
cityName: { bsonType: "string" },
|
|
22
|
+
issueContent: {
|
|
23
|
+
bsonType: "object",
|
|
24
|
+
required: [
|
|
25
|
+
"issuerName",
|
|
26
|
+
"issuerRole",
|
|
27
|
+
"issueDescription",
|
|
28
|
+
"desiredBehavior",
|
|
29
|
+
],
|
|
30
|
+
properties: {
|
|
31
|
+
issuerName: {
|
|
32
|
+
bsonType: "string",
|
|
33
|
+
maxLength: MUNI_ISSUE_NAME_MAX_LENGTH,
|
|
34
|
+
},
|
|
35
|
+
issuerRole: {
|
|
36
|
+
bsonType: "string",
|
|
37
|
+
maxLength: MUNI_ISSUE_NAME_MAX_LENGTH,
|
|
38
|
+
},
|
|
39
|
+
issueDescription: {
|
|
40
|
+
bsonType: "string",
|
|
41
|
+
maxLength: MUNI_ISSUE_TEXT_MAX_LENGTH,
|
|
42
|
+
},
|
|
43
|
+
desiredBehavior: {
|
|
44
|
+
bsonType: "string",
|
|
45
|
+
maxLength: MUNI_ISSUE_TEXT_MAX_LENGTH,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
jira: {
|
|
50
|
+
bsonType: "object",
|
|
51
|
+
required: ["key", "syncStatus"],
|
|
52
|
+
properties: {
|
|
53
|
+
key: { bsonType: "string" },
|
|
54
|
+
syncStatus: { enum: [...MUNI_ISSUE_SYNC_STATUSES] },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
createdAt: { bsonType: "date" },
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const muniIssuesIndexes: IndexDescription[] = [
|
|
62
|
+
{ key: { callSid: 1 }, name: "callSid_1" },
|
|
63
|
+
{ key: { createdAt: -1 }, name: "createdAt_-1" },
|
|
64
|
+
// Unique: one stored document per Jira ticket, guarding against duplicate writes.
|
|
65
|
+
{ key: { "jira.key": 1 }, name: "jira_key_unique", unique: true },
|
|
66
|
+
];
|