@app-connect/core 1.7.37 → 1.7.38
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 +17 -16
- package/dist/connector/developerPortal.d.ts +6 -0
- package/{connector → dist/connector}/developerPortal.js +29 -12
- package/dist/connector/mock.d.ts +1 -0
- package/{connector → dist/connector}/mock.js +33 -17
- package/dist/connector/proxy/engine.d.ts +10 -0
- package/dist/connector/proxy/engine.js +222 -0
- package/dist/connector/proxy/index.d.ts +5 -0
- package/dist/connector/proxy/index.js +511 -0
- package/dist/connector/registry.d.ts +1 -0
- package/dist/connector/registry.js +243 -0
- package/dist/handlers/admin.d.ts +1 -0
- package/{handlers → dist/handlers}/admin.js +137 -78
- package/dist/handlers/appointment.d.ts +9 -0
- package/{handlers → dist/handlers}/appointment.js +58 -29
- package/dist/handlers/auth.d.ts +9 -0
- package/{handlers → dist/handlers}/auth.js +68 -40
- package/dist/handlers/calldown.d.ts +10 -0
- package/dist/handlers/calldown.js +134 -0
- package/dist/handlers/contact.d.ts +1 -0
- package/{handlers → dist/handlers}/contact.js +61 -39
- package/dist/handlers/disposition.d.ts +1 -0
- package/{handlers → dist/handlers}/disposition.js +20 -10
- package/dist/handlers/log.d.ts +1 -0
- package/{handlers → dist/handlers}/log.js +91 -124
- package/dist/handlers/managedAuth.d.ts +1 -0
- package/{handlers → dist/handlers}/managedAuth.js +134 -61
- package/dist/handlers/managedOAuth.d.ts +13 -0
- package/{handlers → dist/handlers}/managedOAuth.js +102 -30
- package/dist/handlers/plugin.d.ts +1 -0
- package/{handlers → dist/handlers}/plugin.js +59 -27
- package/dist/handlers/user.d.ts +1 -0
- package/{handlers → dist/handlers}/user.js +45 -27
- package/dist/index.d.ts +1 -0
- package/{index.js → dist/index.js} +538 -449
- package/dist/jest.config.d.ts +1 -0
- package/dist/jest.config.js +65 -0
- package/dist/lib/analytics.d.ts +4 -0
- package/{lib → dist/lib}/analytics.js +8 -6
- package/dist/lib/authSession.d.ts +5 -0
- package/{lib → dist/lib}/authSession.js +15 -43
- package/dist/lib/cacheCleanup.d.ts +4 -0
- package/{lib → dist/lib}/cacheCleanup.js +5 -17
- package/dist/lib/callLogComposer.d.ts +109 -0
- package/{lib → dist/lib}/callLogComposer.js +157 -197
- package/dist/lib/callLogLookup.d.ts +6 -0
- package/{lib → dist/lib}/callLogLookup.js +12 -34
- package/dist/lib/constants.d.ts +6 -0
- package/dist/lib/constants.js +9 -0
- package/dist/lib/debugTracer.d.ts +17 -0
- package/{lib → dist/lib}/debugTracer.js +7 -83
- package/dist/lib/encode.d.ts +3 -0
- package/dist/lib/encode.js +50 -0
- package/dist/lib/errorHandler.d.ts +7 -0
- package/dist/lib/errorHandler.js +161 -0
- package/dist/lib/generalErrorMessage.d.ts +7 -0
- package/{lib → dist/lib}/generalErrorMessage.js +6 -6
- package/dist/lib/jwt.d.ts +4 -0
- package/dist/lib/jwt.js +18 -0
- package/dist/lib/logger.d.ts +24 -0
- package/dist/lib/logger.js +126 -0
- package/dist/lib/migrateCallLogsSchema.d.ts +32 -0
- package/{lib → dist/lib}/migrateCallLogsSchema.js +75 -160
- package/dist/lib/oauth.d.ts +4 -0
- package/{lib → dist/lib}/oauth.js +157 -160
- package/dist/lib/ringcentral.d.ts +24 -0
- package/dist/lib/ringcentral.js +226 -0
- package/dist/lib/s3ErrorLogReport.d.ts +4 -0
- package/{lib → dist/lib}/s3ErrorLogReport.js +11 -23
- package/dist/lib/sharedSMSComposer.d.ts +7 -0
- package/{lib → dist/lib}/sharedSMSComposer.js +21 -107
- package/dist/lib/util.d.ts +7 -0
- package/{lib → dist/lib}/util.js +9 -20
- package/dist/mcp/lib/oauthError.d.ts +1 -0
- package/dist/mcp/lib/oauthError.js +25 -0
- package/dist/mcp/lib/validator.d.ts +1 -0
- package/{mcp → dist/mcp}/lib/validator.js +16 -20
- package/dist/mcp/lib/widgetSessionToken.d.ts +1 -0
- package/dist/mcp/lib/widgetSessionToken.js +46 -0
- package/dist/mcp/mcpHandler.d.ts +8 -0
- package/{mcp → dist/mcp}/mcpHandler.js +98 -88
- package/dist/mcp/tools/cancelAppointment.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/cancelAppointment.js +7 -16
- package/dist/mcp/tools/checkAuthStatus.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/checkAuthStatus.js +21 -14
- package/dist/mcp/tools/confirmAppointment.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/confirmAppointment.js +7 -16
- package/dist/mcp/tools/createAppointment.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/createAppointment.js +7 -19
- package/dist/mcp/tools/createCallLog.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/createCallLog.js +18 -29
- package/dist/mcp/tools/createContact.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/createContact.js +8 -20
- package/dist/mcp/tools/createMessageLog.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/createMessageLog.js +14 -26
- package/dist/mcp/tools/doAuth.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/doAuth.js +17 -13
- package/dist/mcp/tools/findContactByName.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/findContactByName.js +9 -15
- package/dist/mcp/tools/findContactByPhone.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/findContactByPhone.js +8 -15
- package/dist/mcp/tools/getCallLog.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/getCallLog.js +12 -21
- package/dist/mcp/tools/getGoogleFilePicker.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/getGoogleFilePicker.js +9 -18
- package/dist/mcp/tools/getHelp.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/getHelp.js +5 -6
- package/dist/mcp/tools/getPublicConnectors.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/getPublicConnectors.js +19 -18
- package/dist/mcp/tools/getSessionInfo.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/getSessionInfo.js +10 -16
- package/dist/mcp/tools/index.d.ts +8 -0
- package/dist/mcp/tools/index.js +50 -0
- package/dist/mcp/tools/listAppointments.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/listAppointments.js +7 -19
- package/dist/mcp/tools/logout.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/logout.js +16 -14
- package/dist/mcp/tools/rcGetCallLogs.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/rcGetCallLogs.js +10 -8
- package/dist/mcp/tools/updateAppointment.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/updateAppointment.js +17 -22
- package/dist/mcp/tools/updateCallLog.d.ts +1 -0
- package/{mcp → dist/mcp}/tools/updateCallLog.js +16 -26
- package/dist/models/accountDataModel.d.ts +3 -0
- package/dist/models/accountDataModel.js +39 -0
- package/dist/models/adminConfigModel.d.ts +2 -0
- package/dist/models/adminConfigModel.js +39 -0
- package/dist/models/cacheModel.d.ts +2 -0
- package/{models → dist/models}/cacheModel.js +7 -3
- package/dist/models/callDownListModel.d.ts +2 -0
- package/{models → dist/models}/callDownListModel.js +8 -4
- package/dist/models/callLogModel.d.ts +2 -0
- package/dist/models/callLogModel.js +43 -0
- package/dist/models/dynamo/connectorSchema.d.ts +2 -0
- package/dist/models/dynamo/connectorSchema.js +141 -0
- package/dist/models/dynamo/lockSchema.d.ts +2 -0
- package/{models → dist/models}/dynamo/lockSchema.js +5 -7
- package/dist/models/dynamo/noteCacheSchema.d.ts +2 -0
- package/{models → dist/models}/dynamo/noteCacheSchema.js +4 -6
- package/dist/models/llmSessionModel.d.ts +2 -0
- package/dist/models/llmSessionModel.js +21 -0
- package/dist/models/messageLogModel.d.ts +2 -0
- package/{models → dist/models}/messageLogModel.js +8 -4
- package/dist/models/sequelize.d.ts +2 -0
- package/dist/models/sequelize.js +68 -0
- package/dist/models/userModel.d.ts +2 -0
- package/dist/models/userModel.js +49 -0
- package/dist/package.json +100 -0
- package/dist/releaseNotes.json +1222 -0
- package/dist/tsconfig.json +14 -0
- package/dist/types/admin.d.ts +141 -0
- package/dist/types/analytics.d.ts +14 -0
- package/dist/types/appointment.d.ts +50 -0
- package/dist/types/auth.d.ts +137 -0
- package/dist/types/authSession.d.ts +35 -0
- package/dist/types/cacheCleanup.d.ts +12 -0
- package/dist/types/calldown.d.ts +54 -0
- package/dist/types/common.d.ts +46 -0
- package/dist/types/connector.d.ts +165 -0
- package/dist/types/contact.d.ts +125 -0
- package/dist/types/errorHandler.d.ts +60 -0
- package/dist/types/index.d.ts +76 -0
- package/dist/types/json.d.ts +9 -0
- package/dist/types/jwt.d.ts +8 -0
- package/dist/types/logging.d.ts +224 -0
- package/dist/types/managedAuth.d.ts +114 -0
- package/dist/types/managedOAuth.d.ts +59 -0
- package/dist/types/manifest.d.ts +120 -0
- package/dist/types/migration.d.ts +15 -0
- package/dist/types/plugin.d.ts +74 -0
- package/dist/types/ringcentral.d.ts +61 -0
- package/dist/types/s3ErrorLogReport.d.ts +16 -0
- package/dist/types/sharedSMSComposer.d.ts +105 -0
- package/dist/types/user.d.ts +46 -0
- package/dist/types/util.d.ts +7 -0
- package/package.json +34 -6
- package/releaseNotes.json +8 -0
- package/.env.test +0 -5
- package/connector/proxy/engine.js +0 -165
- package/connector/proxy/index.js +0 -500
- package/connector/registry.js +0 -252
- package/docs/README.md +0 -50
- package/docs/architecture.md +0 -94
- package/docs/connectors.md +0 -116
- package/docs/handlers.md +0 -128
- package/docs/libraries.md +0 -98
- package/docs/models.md +0 -146
- package/docs/routes.md +0 -119
- package/docs/tests.md +0 -73
- package/handlers/calldown.js +0 -99
- package/jest.config.js +0 -57
- package/lib/constants.js +0 -9
- package/lib/encode.js +0 -30
- package/lib/errorHandler.js +0 -218
- package/lib/jwt.js +0 -18
- package/lib/logger.js +0 -190
- package/lib/ringcentral.js +0 -267
- package/mcp/README.md +0 -420
- package/mcp/tools/index.js +0 -51
- package/mcp/ui/App/App.tsx +0 -358
- package/mcp/ui/App/components/AuthInfoForm.tsx +0 -113
- package/mcp/ui/App/components/AuthSuccess.tsx +0 -22
- package/mcp/ui/App/components/ConnectorList.tsx +0 -82
- package/mcp/ui/App/components/DebugPanel.tsx +0 -43
- package/mcp/ui/App/components/OAuthConnect.tsx +0 -270
- package/mcp/ui/App/lib/callTool.ts +0 -130
- package/mcp/ui/App/lib/debugLog.ts +0 -41
- package/mcp/ui/App/lib/developerPortal.ts +0 -111
- package/mcp/ui/App/main.css +0 -6
- package/mcp/ui/App/root.tsx +0 -13
- package/mcp/ui/dist/index.html +0 -53
- package/mcp/ui/vite.config.ts +0 -16
- package/models/accountDataModel.js +0 -34
- package/models/adminConfigModel.js +0 -35
- package/models/callLogModel.js +0 -39
- package/models/dynamo/connectorSchema.js +0 -146
- package/models/llmSessionModel.js +0 -17
- package/models/sequelize.js +0 -17
- package/models/userModel.js +0 -45
- package/test/connector/developerPortal.test.js +0 -166
- package/test/connector/mock.test.js +0 -131
- package/test/connector/proxy/engine.test.js +0 -211
- package/test/connector/proxy/index.test.js +0 -525
- package/test/connector/proxy/sample.json +0 -162
- package/test/connector/registry.test.js +0 -416
- package/test/handlers/admin.test.js +0 -960
- package/test/handlers/appointment.test.js +0 -260
- package/test/handlers/auth.test.js +0 -1096
- package/test/handlers/calldown.test.js +0 -310
- package/test/handlers/contact.test.js +0 -1014
- package/test/handlers/disposition.test.js +0 -396
- package/test/handlers/log.test.js +0 -1847
- package/test/handlers/managedAuth.test.js +0 -458
- package/test/handlers/managedOAuth.test.js +0 -262
- package/test/handlers/plugin.test.js +0 -429
- package/test/handlers/user.test.js +0 -381
- package/test/index.test.js +0 -301
- package/test/lib/analytics.test.js +0 -146
- package/test/lib/authSession.test.js +0 -173
- package/test/lib/cacheCleanup.test.js +0 -127
- package/test/lib/callLogComposer.test.js +0 -1231
- package/test/lib/debugTracer.test.js +0 -328
- package/test/lib/encode.test.js +0 -59
- package/test/lib/errorHandler.test.js +0 -246
- package/test/lib/generalErrorMessage.test.js +0 -82
- package/test/lib/jwt.test.js +0 -176
- package/test/lib/logger.test.js +0 -206
- package/test/lib/oauth.test.js +0 -359
- package/test/lib/ringcentral.test.js +0 -467
- package/test/lib/s3ErrorLogReport.test.js +0 -187
- package/test/lib/sharedSMSComposer.test.js +0 -1084
- package/test/lib/util.test.js +0 -329
- package/test/mcp/mcpHandler.test.js +0 -73
- package/test/mcp/mcpHandlerMore.test.js +0 -384
- package/test/mcp/tools/appointmentTools.test.js +0 -362
- package/test/mcp/tools/checkAuthStatus.test.js +0 -83
- package/test/mcp/tools/createCallLog.test.js +0 -436
- package/test/mcp/tools/createContact.test.js +0 -58
- package/test/mcp/tools/createMessageLog.test.js +0 -595
- package/test/mcp/tools/doAuth.test.js +0 -113
- package/test/mcp/tools/findContactByName.test.js +0 -275
- package/test/mcp/tools/findContactByPhone.test.js +0 -296
- package/test/mcp/tools/getCallLog.test.js +0 -298
- package/test/mcp/tools/getGoogleFilePicker.test.js +0 -281
- package/test/mcp/tools/getPublicConnectors.test.js +0 -107
- package/test/mcp/tools/getSessionInfo.test.js +0 -127
- package/test/mcp/tools/logout.test.js +0 -233
- package/test/mcp/tools/rcGetCallLogs.test.js +0 -56
- package/test/mcp/tools/updateCallLog.test.js +0 -360
- package/test/models/accountDataModel.test.js +0 -98
- package/test/models/callDownListModel.test.js +0 -125
- package/test/models/dynamo/connectorSchema.test.js +0 -189
- package/test/models/dynamo/lockSchema.test.js +0 -37
- package/test/models/dynamo/noteCacheSchema.test.js +0 -45
- package/test/models/llmSessionModel.test.js +0 -91
- package/test/models/models.test.js +0 -660
- package/test/routes/calldownRoutes.test.js +0 -224
- package/test/routes/coreRouterBroadRoutes.test.js +0 -855
- package/test/routes/dispositionRoutes.test.js +0 -192
- package/test/routes/managedAuthRoutes.test.js +0 -386
- package/test/routes/pluginRoutes.test.js +0 -262
- package/test/setup.js +0 -178
- /package/{mcp → dist/mcp}/ui/index.html +0 -0
- /package/{mcp → dist/mcp}/ui/package-lock.json +0 -0
- /package/{mcp → dist/mcp}/ui/package.json +0 -0
- /package/{mcp → dist/mcp}/ui/tsconfig.json +0 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const util = require('util');
|
|
3
|
+
const LOG_LEVELS = {
|
|
4
|
+
ERROR: 0,
|
|
5
|
+
WARN: 1,
|
|
6
|
+
INFO: 2,
|
|
7
|
+
DEBUG: 3
|
|
8
|
+
};
|
|
9
|
+
const COLORS = {
|
|
10
|
+
ERROR: '\x1b[31m',
|
|
11
|
+
WARN: '\x1b[33m',
|
|
12
|
+
INFO: '\x1b[36m',
|
|
13
|
+
DEBUG: '\x1b[90m',
|
|
14
|
+
RESET: '\x1b[0m'
|
|
15
|
+
};
|
|
16
|
+
class Logger {
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.level = this._getLogLevel(options.level || process.env.LOG_LEVEL || 'INFO');
|
|
19
|
+
this.isProd = process.env.NODE_ENV === 'production';
|
|
20
|
+
this.enableColors = !this.isProd && Boolean(process.stdout.isTTY);
|
|
21
|
+
}
|
|
22
|
+
_getLogLevel(levelName) {
|
|
23
|
+
const upperLevel = levelName.toUpperCase();
|
|
24
|
+
return LOG_LEVELS[upperLevel] !== undefined ? LOG_LEVELS[upperLevel] : LOG_LEVELS.INFO;
|
|
25
|
+
}
|
|
26
|
+
_shouldLog(level) {
|
|
27
|
+
return LOG_LEVELS[level] <= this.level;
|
|
28
|
+
}
|
|
29
|
+
_formatMessage(level, message, context = {}) {
|
|
30
|
+
const timestamp = new Date().toISOString();
|
|
31
|
+
if (this.isProd) {
|
|
32
|
+
return JSON.stringify({
|
|
33
|
+
timestamp,
|
|
34
|
+
level,
|
|
35
|
+
message,
|
|
36
|
+
...context
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const color = this.enableColors ? COLORS[level] : '';
|
|
40
|
+
const reset = this.enableColors ? COLORS.RESET : '';
|
|
41
|
+
const contextStr = Object.keys(context).length > 0
|
|
42
|
+
? '\n' + util.inspect(context, { depth: 4, colors: this.enableColors })
|
|
43
|
+
: '';
|
|
44
|
+
return `${color}[${timestamp}] [${level}]${reset} ${message}${contextStr}`;
|
|
45
|
+
}
|
|
46
|
+
_log(level, message, context = {}) {
|
|
47
|
+
if (!this._shouldLog(level)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const formattedMessage = this._formatMessage(level, message, context);
|
|
51
|
+
if (level === 'ERROR' || level === 'WARN') {
|
|
52
|
+
console.error(formattedMessage);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.log(formattedMessage);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
error(message, context = {}) {
|
|
59
|
+
let enrichedContext = context;
|
|
60
|
+
if (context.error instanceof Error) {
|
|
61
|
+
const { error, ...rest } = context;
|
|
62
|
+
const response = error.response;
|
|
63
|
+
enrichedContext = {
|
|
64
|
+
...rest,
|
|
65
|
+
errorMessage: error.message,
|
|
66
|
+
errorStack: error.stack,
|
|
67
|
+
errorResponse: response?.data,
|
|
68
|
+
errorStatus: response?.status
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
this._log('ERROR', message, enrichedContext);
|
|
72
|
+
}
|
|
73
|
+
warn(message, context = {}) {
|
|
74
|
+
this._log('WARN', message, context);
|
|
75
|
+
}
|
|
76
|
+
info(message, context = {}) {
|
|
77
|
+
this._log('INFO', message, context);
|
|
78
|
+
}
|
|
79
|
+
debug(message, context = {}) {
|
|
80
|
+
this._log('DEBUG', message, context);
|
|
81
|
+
}
|
|
82
|
+
child(defaultContext = {}) {
|
|
83
|
+
return {
|
|
84
|
+
error: (message, context = {}) => this.error(message, { ...defaultContext, ...context }),
|
|
85
|
+
warn: (message, context = {}) => this.warn(message, { ...defaultContext, ...context }),
|
|
86
|
+
info: (message, context = {}) => this.info(message, { ...defaultContext, ...context }),
|
|
87
|
+
debug: (message, context = {}) => this.debug(message, { ...defaultContext, ...context }),
|
|
88
|
+
child: (additionalContext = {}) => this.child({ ...defaultContext, ...additionalContext })
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
logApiRequest({ method, url, status, duration, platform, error }) {
|
|
92
|
+
const context = {
|
|
93
|
+
method,
|
|
94
|
+
url,
|
|
95
|
+
status,
|
|
96
|
+
duration: duration ? `${duration}ms` : undefined,
|
|
97
|
+
platform
|
|
98
|
+
};
|
|
99
|
+
if (error) {
|
|
100
|
+
this.error('API request failed', { ...context, error });
|
|
101
|
+
}
|
|
102
|
+
else if (status !== undefined && status >= 400) {
|
|
103
|
+
this.warn('API request returned error status', context);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
this.debug('API request completed', context);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
logDatabaseQuery({ operation, table, duration, error }) {
|
|
110
|
+
const context = {
|
|
111
|
+
operation,
|
|
112
|
+
table,
|
|
113
|
+
duration: duration ? `${duration}ms` : undefined
|
|
114
|
+
};
|
|
115
|
+
if (error) {
|
|
116
|
+
this.error('Database query failed', { ...context, error });
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
this.debug('Database query completed', context);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const logger = new Logger();
|
|
124
|
+
logger.Logger = Logger;
|
|
125
|
+
logger.LOG_LEVELS = LOG_LEVELS;
|
|
126
|
+
module.exports = logger;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { MigrationLogger, SequelizeLike, SequelizeTransactionLike, TableDescription } from '../types';
|
|
2
|
+
declare function findColumnKey(tableDescription: TableDescription, name: string): string | undefined;
|
|
3
|
+
declare function sqliteCallLogsPkIncludesExtension(sequelize: SequelizeLike, options?: Record<string, unknown>): Promise<boolean>;
|
|
4
|
+
declare function sqliteCallLogsPkIncludesHashedExtension(sequelize: SequelizeLike, options?: Record<string, unknown>): Promise<boolean>;
|
|
5
|
+
/**
|
|
6
|
+
* SQLite cannot change composite PK via Sequelize addConstraint (it appends a second PRIMARY KEY).
|
|
7
|
+
* Rebuild the table with the current call-log identity primary key and copy rows.
|
|
8
|
+
*/
|
|
9
|
+
declare function migrateCallLogsIdentitySqlite(sequelize: SequelizeLike, transaction: SequelizeTransactionLike): Promise<void>;
|
|
10
|
+
declare function migrateCallLogsExtensionNumberSqlite(sequelize: SequelizeLike, transaction: SequelizeTransactionLike): Promise<void>;
|
|
11
|
+
declare function ensureCallLogsHashedExtensionIdSchema(sequelize: SequelizeLike): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Online (non-blocking) Postgres migration.
|
|
14
|
+
*
|
|
15
|
+
* Adds the hashedExtensionId column and rebuilds the primary key to the call-log
|
|
16
|
+
* identity WITHOUT holding an ACCESS EXCLUSIVE lock for the duration of the index
|
|
17
|
+
* build. This is the safe path for large production tables:
|
|
18
|
+
* - ADD COLUMN ... NOT NULL DEFAULT '' is metadata-only on PG 11+.
|
|
19
|
+
* - CREATE UNIQUE INDEX CONCURRENTLY builds the index while reads/writes continue.
|
|
20
|
+
* - ADD PRIMARY KEY USING INDEX attaches the prebuilt index with only a brief lock.
|
|
21
|
+
*
|
|
22
|
+
* Runs on a single dedicated pooled connection so the advisory lock and the
|
|
23
|
+
* (necessarily non-transactional) CONCURRENTLY statements share one backend session.
|
|
24
|
+
*/
|
|
25
|
+
declare function ensureCallLogsHashedExtensionIdSchemaPostgresOnline(sequelize: SequelizeLike, logger?: MigrationLogger): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Automatic entry point. Safe to call fire-and-forget off the startup critical path:
|
|
28
|
+
* it never throws (failures are logged and retried on the next boot) and, on Postgres,
|
|
29
|
+
* never blocks readiness or live traffic.
|
|
30
|
+
*/
|
|
31
|
+
declare function runCallLogsSchemaMigration(sequelize: SequelizeLike, logger?: MigrationLogger): Promise<void>;
|
|
32
|
+
export { findColumnKey, migrateCallLogsExtensionNumberSqlite, migrateCallLogsIdentitySqlite, sqliteCallLogsPkIncludesExtension, sqliteCallLogsPkIncludesHashedExtension, ensureCallLogsHashedExtensionIdSchema, ensureCallLogsHashedExtensionIdSchemaPostgresOnline, runCallLogsSchemaMigration };
|
|
@@ -1,22 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findColumnKey = findColumnKey;
|
|
4
|
+
exports.migrateCallLogsExtensionNumberSqlite = migrateCallLogsExtensionNumberSqlite;
|
|
5
|
+
exports.migrateCallLogsIdentitySqlite = migrateCallLogsIdentitySqlite;
|
|
6
|
+
exports.sqliteCallLogsPkIncludesExtension = sqliteCallLogsPkIncludesExtension;
|
|
7
|
+
exports.sqliteCallLogsPkIncludesHashedExtension = sqliteCallLogsPkIncludesHashedExtension;
|
|
8
|
+
exports.ensureCallLogsHashedExtensionIdSchema = ensureCallLogsHashedExtensionIdSchema;
|
|
9
|
+
exports.ensureCallLogsHashedExtensionIdSchemaPostgresOnline = ensureCallLogsHashedExtensionIdSchemaPostgresOnline;
|
|
10
|
+
exports.runCallLogsSchemaMigration = runCallLogsSchemaMigration;
|
|
1
11
|
const Sequelize = require('sequelize');
|
|
2
|
-
|
|
3
12
|
const CALL_LOGS_TABLE = 'callLogs';
|
|
4
13
|
const HASHED_EXTENSION_ID_COLUMN = 'hashedExtensionId';
|
|
5
14
|
const CALL_LOG_IDENTITY_PK_COLUMNS = ['id', 'sessionId', 'extensionNumber', HASHED_EXTENSION_ID_COLUMN];
|
|
6
|
-
|
|
7
15
|
function findColumnKey(tableDescription, name) {
|
|
8
16
|
const lower = name.toLowerCase();
|
|
9
17
|
return Object.keys(tableDescription).find((k) => k.toLowerCase() === lower);
|
|
10
18
|
}
|
|
11
|
-
|
|
12
19
|
async function fetchCallLogsCreateSqlSqlite(sequelize, options = {}) {
|
|
13
|
-
const rows = await sequelize.query(
|
|
14
|
-
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'callLogs'",
|
|
15
|
-
{ type: Sequelize.QueryTypes.SELECT, ...options },
|
|
16
|
-
);
|
|
20
|
+
const rows = await sequelize.query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'callLogs'", { type: Sequelize.QueryTypes.SELECT, ...options });
|
|
17
21
|
return rows[0]?.sql ?? null;
|
|
18
22
|
}
|
|
19
|
-
|
|
20
23
|
function callLogsCreateSqlClaimsPkColumns(createSql, columns) {
|
|
21
24
|
if (!createSql || typeof createSql !== 'string') {
|
|
22
25
|
return false;
|
|
@@ -33,25 +36,20 @@ function callLogsCreateSqlClaimsPkColumns(createSql, columns) {
|
|
|
33
36
|
}
|
|
34
37
|
return false;
|
|
35
38
|
}
|
|
36
|
-
|
|
37
39
|
function callLogsCreateSqlClaimsExtensionPk(createSql) {
|
|
38
40
|
return callLogsCreateSqlClaimsPkColumns(createSql, ['extensionNumber']);
|
|
39
41
|
}
|
|
40
|
-
|
|
41
42
|
async function sqliteCallLogsPkIncludesExtension(sequelize, options = {}) {
|
|
42
43
|
const sql = await fetchCallLogsCreateSqlSqlite(sequelize, options);
|
|
43
44
|
return callLogsCreateSqlClaimsExtensionPk(sql);
|
|
44
45
|
}
|
|
45
|
-
|
|
46
46
|
async function sqliteCallLogsPkIncludesHashedExtension(sequelize, options = {}) {
|
|
47
47
|
const sql = await fetchCallLogsCreateSqlSqlite(sequelize, options);
|
|
48
48
|
return callLogsCreateSqlClaimsPkColumns(sql, CALL_LOG_IDENTITY_PK_COLUMNS);
|
|
49
49
|
}
|
|
50
|
-
|
|
51
50
|
async function describeCallLogsTable(sequelize, options = {}) {
|
|
52
51
|
return sequelize.getQueryInterface().describeTable(CALL_LOGS_TABLE, options);
|
|
53
52
|
}
|
|
54
|
-
|
|
55
53
|
/**
|
|
56
54
|
* SQLite cannot change composite PK via Sequelize addConstraint (it appends a second PRIMARY KEY).
|
|
57
55
|
* Rebuild the table with the current call-log identity primary key and copy rows.
|
|
@@ -63,14 +61,8 @@ async function migrateCallLogsIdentitySqlite(sequelize, transaction) {
|
|
|
63
61
|
const tmpName = `callLogs_mig_legacy_${Date.now()}`;
|
|
64
62
|
const mainTableSql = qg.quoteTable({ tableName: CALL_LOGS_TABLE });
|
|
65
63
|
const tmpTableSql = qg.quoteTable({ tableName: tmpName });
|
|
66
|
-
|
|
67
|
-
await sequelize.query(
|
|
68
|
-
`ALTER TABLE ${mainTableSql} RENAME TO ${qg.quoteIdentifier(tmpName)};`,
|
|
69
|
-
{ transaction },
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
await sequelize.query(
|
|
73
|
-
`
|
|
64
|
+
await sequelize.query(`ALTER TABLE ${mainTableSql} RENAME TO ${qg.quoteIdentifier(tmpName)};`, { transaction });
|
|
65
|
+
await sequelize.query(`
|
|
74
66
|
CREATE TABLE ${mainTableSql} (
|
|
75
67
|
${q('id')} VARCHAR(255) NOT NULL,
|
|
76
68
|
${q('sessionId')} VARCHAR(255) NOT NULL,
|
|
@@ -84,12 +76,8 @@ async function migrateCallLogsIdentitySqlite(sequelize, transaction) {
|
|
|
84
76
|
${q('updatedAt')} DATETIME NOT NULL,
|
|
85
77
|
PRIMARY KEY (${CALL_LOG_IDENTITY_PK_COLUMNS.map(q).join(', ')})
|
|
86
78
|
);
|
|
87
|
-
`,
|
|
88
|
-
{ transaction },
|
|
89
|
-
);
|
|
90
|
-
|
|
79
|
+
`, { transaction });
|
|
91
80
|
const oldDesc = await qi.describeTable(tmpName, { transaction });
|
|
92
|
-
|
|
93
81
|
const targetCols = [
|
|
94
82
|
'id',
|
|
95
83
|
'sessionId',
|
|
@@ -102,102 +90,73 @@ async function migrateCallLogsIdentitySqlite(sequelize, transaction) {
|
|
|
102
90
|
'createdAt',
|
|
103
91
|
'updatedAt',
|
|
104
92
|
];
|
|
105
|
-
|
|
106
|
-
const missingRequired = targetCols.filter(
|
|
107
|
-
(c) => findColumnKey(oldDesc, c) == null && c !== 'extensionNumber' && c !== HASHED_EXTENSION_ID_COLUMN,
|
|
108
|
-
);
|
|
93
|
+
const missingRequired = targetCols.filter((c) => findColumnKey(oldDesc, c) == null && c !== 'extensionNumber' && c !== HASHED_EXTENSION_ID_COLUMN);
|
|
109
94
|
if (missingRequired.length) {
|
|
110
|
-
throw new Error(
|
|
111
|
-
`unexpected callLogs schema before migration (missing columns: ${missingRequired.join(', ')})`,
|
|
112
|
-
);
|
|
95
|
+
throw new Error(`unexpected callLogs schema before migration (missing columns: ${missingRequired.join(', ')})`);
|
|
113
96
|
}
|
|
114
|
-
|
|
115
97
|
const insertCols = targetCols.map(q).join(', ');
|
|
116
98
|
const selectExprs = targetCols
|
|
117
99
|
.map((col) => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
100
|
+
const key = findColumnKey(oldDesc, col);
|
|
101
|
+
if (key != null) {
|
|
102
|
+
return q(key);
|
|
103
|
+
}
|
|
104
|
+
return `CAST('' AS VARCHAR(255))`;
|
|
105
|
+
})
|
|
124
106
|
.join(', ');
|
|
125
|
-
|
|
126
|
-
await sequelize.query(
|
|
127
|
-
`INSERT INTO ${mainTableSql} (${insertCols}) SELECT ${selectExprs} FROM ${tmpTableSql};`,
|
|
128
|
-
{ transaction },
|
|
129
|
-
);
|
|
130
|
-
|
|
107
|
+
await sequelize.query(`INSERT INTO ${mainTableSql} (${insertCols}) SELECT ${selectExprs} FROM ${tmpTableSql};`, { transaction });
|
|
131
108
|
await sequelize.query(`DROP TABLE ${tmpTableSql};`, { transaction });
|
|
132
109
|
}
|
|
133
|
-
|
|
134
110
|
async function migrateCallLogsExtensionNumberSqlite(sequelize, transaction) {
|
|
135
111
|
return migrateCallLogsIdentitySqlite(sequelize, transaction);
|
|
136
112
|
}
|
|
137
|
-
|
|
138
113
|
async function getPostgresPrimaryKeyConstraint(sequelize, options = {}) {
|
|
139
|
-
const constraints = await sequelize.query(
|
|
140
|
-
`
|
|
114
|
+
const constraints = await sequelize.query(`
|
|
141
115
|
SELECT tc.constraint_name AS "constraintName"
|
|
142
116
|
FROM information_schema.table_constraints tc
|
|
143
117
|
WHERE tc.table_name = :tableName
|
|
144
118
|
AND tc.constraint_type = 'PRIMARY KEY'
|
|
145
119
|
LIMIT 1
|
|
146
|
-
`,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
},
|
|
152
|
-
);
|
|
120
|
+
`, {
|
|
121
|
+
type: Sequelize.QueryTypes.SELECT,
|
|
122
|
+
replacements: { tableName: CALL_LOGS_TABLE },
|
|
123
|
+
...options,
|
|
124
|
+
});
|
|
153
125
|
return constraints[0]?.constraintName ?? null;
|
|
154
126
|
}
|
|
155
|
-
|
|
156
127
|
async function postgresCallLogsPkIncludesHashedExtension(sequelize, options = {}) {
|
|
157
128
|
const pkConstraintName = await getPostgresPrimaryKeyConstraint(sequelize, options);
|
|
158
129
|
if (!pkConstraintName) {
|
|
159
130
|
return false;
|
|
160
131
|
}
|
|
161
|
-
const rows = await sequelize.query(
|
|
162
|
-
`
|
|
132
|
+
const rows = await sequelize.query(`
|
|
163
133
|
SELECT kcu.column_name AS "columnName"
|
|
164
134
|
FROM information_schema.key_column_usage kcu
|
|
165
135
|
WHERE kcu.table_name = :tableName
|
|
166
136
|
AND kcu.constraint_name = :constraintName
|
|
167
137
|
ORDER BY kcu.ordinal_position
|
|
168
|
-
`,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
},
|
|
174
|
-
);
|
|
138
|
+
`, {
|
|
139
|
+
type: Sequelize.QueryTypes.SELECT,
|
|
140
|
+
replacements: { tableName: CALL_LOGS_TABLE, constraintName: pkConstraintName },
|
|
141
|
+
...options,
|
|
142
|
+
});
|
|
175
143
|
const pkColumns = rows.map((row) => row.columnName.toLowerCase());
|
|
176
144
|
return CALL_LOG_IDENTITY_PK_COLUMNS.every((column) => pkColumns.includes(column.toLowerCase()));
|
|
177
145
|
}
|
|
178
|
-
|
|
179
146
|
async function migrateCallLogsIdentityPostgres(sequelize, transaction) {
|
|
180
147
|
const qi = sequelize.getQueryInterface();
|
|
181
148
|
const qg = qi.queryGenerator;
|
|
182
149
|
const table = qg.quoteTable({ tableName: CALL_LOGS_TABLE });
|
|
183
150
|
const pkConstraintName = await getPostgresPrimaryKeyConstraint(sequelize, { transaction });
|
|
184
151
|
if (pkConstraintName) {
|
|
185
|
-
await sequelize.query(
|
|
186
|
-
`ALTER TABLE ${table} DROP CONSTRAINT ${qg.quoteIdentifier(pkConstraintName)};`,
|
|
187
|
-
{ transaction },
|
|
188
|
-
);
|
|
152
|
+
await sequelize.query(`ALTER TABLE ${table} DROP CONSTRAINT ${qg.quoteIdentifier(pkConstraintName)};`, { transaction });
|
|
189
153
|
}
|
|
190
|
-
await sequelize.query(
|
|
191
|
-
`ALTER TABLE ${table} ADD PRIMARY KEY (${CALL_LOG_IDENTITY_PK_COLUMNS.map((column) => qg.quoteIdentifier(column)).join(', ')});`,
|
|
192
|
-
{ transaction },
|
|
193
|
-
);
|
|
154
|
+
await sequelize.query(`ALTER TABLE ${table} ADD PRIMARY KEY (${CALL_LOG_IDENTITY_PK_COLUMNS.map((column) => qg.quoteIdentifier(column)).join(', ')});`, { transaction });
|
|
194
155
|
}
|
|
195
|
-
|
|
196
156
|
async function ensureCallLogsHashedExtensionIdSchema(sequelize) {
|
|
197
157
|
const qi = sequelize.getQueryInterface();
|
|
198
158
|
const dialect = sequelize.getDialect();
|
|
199
159
|
let desc = await describeCallLogsTable(sequelize);
|
|
200
|
-
|
|
201
160
|
if (findColumnKey(desc, HASHED_EXTENSION_ID_COLUMN) == null) {
|
|
202
161
|
await qi.addColumn(CALL_LOGS_TABLE, HASHED_EXTENSION_ID_COLUMN, {
|
|
203
162
|
type: Sequelize.STRING,
|
|
@@ -206,7 +165,6 @@ async function ensureCallLogsHashedExtensionIdSchema(sequelize) {
|
|
|
206
165
|
});
|
|
207
166
|
desc = await describeCallLogsTable(sequelize);
|
|
208
167
|
}
|
|
209
|
-
|
|
210
168
|
if (dialect === 'sqlite') {
|
|
211
169
|
const hasIdentityPk = await sqliteCallLogsPkIncludesHashedExtension(sequelize);
|
|
212
170
|
if (!hasIdentityPk) {
|
|
@@ -216,7 +174,6 @@ async function ensureCallLogsHashedExtensionIdSchema(sequelize) {
|
|
|
216
174
|
}
|
|
217
175
|
return;
|
|
218
176
|
}
|
|
219
|
-
|
|
220
177
|
if (dialect === 'postgres') {
|
|
221
178
|
const hasIdentityPk = await postgresCallLogsPkIncludesHashedExtension(sequelize);
|
|
222
179
|
if (!hasIdentityPk) {
|
|
@@ -226,59 +183,43 @@ async function ensureCallLogsHashedExtensionIdSchema(sequelize) {
|
|
|
226
183
|
}
|
|
227
184
|
}
|
|
228
185
|
}
|
|
229
|
-
|
|
230
186
|
// Dedicated name for the online-built unique index that becomes the new PK.
|
|
231
187
|
const IDENTITY_INDEX_NAME = 'callLogs_identity_pk';
|
|
232
188
|
// Stable application-wide key for the Postgres advisory lock that serializes the
|
|
233
189
|
// migration across concurrently-booting instances. Any fixed integer works as long
|
|
234
190
|
// as it is unique within the app; keep it constant so every instance agrees on it.
|
|
235
191
|
const CALL_LOGS_MIGRATION_LOCK_KEY = 4972001;
|
|
236
|
-
|
|
237
192
|
async function postgresPrimaryKeyNameOnConnection(client) {
|
|
238
|
-
const { rows } = await client.query(
|
|
239
|
-
|
|
240
|
-
WHERE conrelid = '"${CALL_LOGS_TABLE}"'::regclass AND contype = 'p' LIMIT 1`,
|
|
241
|
-
);
|
|
193
|
+
const { rows } = await client.query(`SELECT conname FROM pg_constraint
|
|
194
|
+
WHERE conrelid = '"${CALL_LOGS_TABLE}"'::regclass AND contype = 'p' LIMIT 1`);
|
|
242
195
|
return rows[0]?.conname ?? null;
|
|
243
196
|
}
|
|
244
|
-
|
|
245
197
|
async function postgresPkIncludesIdentityOnConnection(client) {
|
|
246
|
-
const { rows } = await client.query(
|
|
247
|
-
`SELECT kcu.column_name AS col
|
|
198
|
+
const { rows } = await client.query(`SELECT kcu.column_name AS col
|
|
248
199
|
FROM information_schema.table_constraints tc
|
|
249
200
|
JOIN information_schema.key_column_usage kcu
|
|
250
201
|
ON kcu.constraint_name = tc.constraint_name
|
|
251
202
|
AND kcu.table_name = tc.table_name
|
|
252
|
-
WHERE tc.table_name = $1 AND tc.constraint_type = 'PRIMARY KEY'`,
|
|
253
|
-
[CALL_LOGS_TABLE],
|
|
254
|
-
);
|
|
203
|
+
WHERE tc.table_name = $1 AND tc.constraint_type = 'PRIMARY KEY'`, [CALL_LOGS_TABLE]);
|
|
255
204
|
const cols = rows.map((r) => String(r.col).toLowerCase());
|
|
256
205
|
return CALL_LOG_IDENTITY_PK_COLUMNS.every((c) => cols.includes(c.toLowerCase()));
|
|
257
206
|
}
|
|
258
|
-
|
|
259
207
|
async function postgresColumnIsNullableOnConnection(client, column) {
|
|
260
|
-
const { rows } = await client.query(
|
|
261
|
-
|
|
262
|
-
WHERE table_name = $1 AND column_name = $2`,
|
|
263
|
-
[CALL_LOGS_TABLE, column],
|
|
264
|
-
);
|
|
208
|
+
const { rows } = await client.query(`SELECT is_nullable FROM information_schema.columns
|
|
209
|
+
WHERE table_name = $1 AND column_name = $2`, [CALL_LOGS_TABLE, column]);
|
|
265
210
|
return rows[0]?.is_nullable === 'YES';
|
|
266
211
|
}
|
|
267
|
-
|
|
268
|
-
// 'absent' | 'valid' | 'invalid' — an INVALID index is the residue of a
|
|
212
|
+
// 'absent' | 'valid' | 'invalid' - an INVALID index is the residue of a
|
|
269
213
|
// CONCURRENTLY build that was interrupted (process killed, connection dropped).
|
|
270
214
|
async function postgresIdentityIndexState(client) {
|
|
271
|
-
const { rows } = await client.query(
|
|
272
|
-
`SELECT (i.indisvalid AND i.indisready) AS ok
|
|
215
|
+
const { rows } = await client.query(`SELECT (i.indisvalid AND i.indisready) AS ok
|
|
273
216
|
FROM pg_class c
|
|
274
217
|
JOIN pg_index i ON i.indexrelid = c.oid
|
|
275
|
-
WHERE c.relname = $1`,
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (rows.length === 0) return 'absent';
|
|
218
|
+
WHERE c.relname = $1`, [IDENTITY_INDEX_NAME]);
|
|
219
|
+
if (rows.length === 0)
|
|
220
|
+
return 'absent';
|
|
279
221
|
return rows[0].ok ? 'valid' : 'invalid';
|
|
280
222
|
}
|
|
281
|
-
|
|
282
223
|
/**
|
|
283
224
|
* Online (non-blocking) Postgres migration.
|
|
284
225
|
*
|
|
@@ -297,48 +238,33 @@ async function ensureCallLogsHashedExtensionIdSchemaPostgresOnline(sequelize, lo
|
|
|
297
238
|
if (await postgresCallLogsPkIncludesHashedExtension(sequelize)) {
|
|
298
239
|
return;
|
|
299
240
|
}
|
|
300
|
-
|
|
301
241
|
const connectionManager = sequelize.connectionManager;
|
|
302
242
|
const client = await connectionManager.getConnection({ type: 'write' });
|
|
303
243
|
let lockAcquired = false;
|
|
304
244
|
try {
|
|
305
|
-
const lockRes = await client.query(
|
|
306
|
-
`SELECT pg_try_advisory_lock(${CALL_LOGS_MIGRATION_LOCK_KEY}) AS locked`,
|
|
307
|
-
);
|
|
245
|
+
const lockRes = await client.query(`SELECT pg_try_advisory_lock(${CALL_LOGS_MIGRATION_LOCK_KEY}) AS locked`);
|
|
308
246
|
lockAcquired = lockRes.rows[0]?.locked === true;
|
|
309
247
|
if (!lockAcquired) {
|
|
310
248
|
logger?.info?.('[callLogs migration] another instance is migrating; skipping this run');
|
|
311
249
|
return;
|
|
312
250
|
}
|
|
313
|
-
|
|
314
|
-
// Re-check under the lock — another instance may have finished between the
|
|
251
|
+
// Re-check under the lock - another instance may have finished between the
|
|
315
252
|
// pre-check and acquiring the lock.
|
|
316
253
|
if (await postgresPkIncludesIdentityOnConnection(client)) {
|
|
317
254
|
return;
|
|
318
255
|
}
|
|
319
|
-
|
|
320
256
|
logger?.info?.('[callLogs migration] starting online migration (add column + rebuild primary key)');
|
|
321
|
-
|
|
322
257
|
// A server-side statement_timeout (common in RDS parameter groups) would abort
|
|
323
258
|
// the long CONCURRENTLY build and leave an INVALID index. Disable it for this
|
|
324
259
|
// session only; it is reset before the connection returns to the pool.
|
|
325
260
|
await client.query(`SET statement_timeout = 0`);
|
|
326
|
-
|
|
327
261
|
// 1. Add the column (metadata-only on PG 11+, safe to repeat).
|
|
328
|
-
await client.query(
|
|
329
|
-
`ALTER TABLE "${CALL_LOGS_TABLE}" ADD COLUMN IF NOT EXISTS "${HASHED_EXTENSION_ID_COLUMN}" varchar(255) NOT NULL DEFAULT ''`,
|
|
330
|
-
);
|
|
331
|
-
|
|
262
|
+
await client.query(`ALTER TABLE "${CALL_LOGS_TABLE}" ADD COLUMN IF NOT EXISTS "${HASHED_EXTENSION_ID_COLUMN}" varchar(255) NOT NULL DEFAULT ''`);
|
|
332
263
|
// 2. Primary-key columns must be NOT NULL. Only touch extensionNumber if needed.
|
|
333
264
|
if (await postgresColumnIsNullableOnConnection(client, 'extensionNumber')) {
|
|
334
|
-
await client.query(
|
|
335
|
-
|
|
336
|
-
);
|
|
337
|
-
await client.query(
|
|
338
|
-
`ALTER TABLE "${CALL_LOGS_TABLE}" ALTER COLUMN "extensionNumber" SET NOT NULL`,
|
|
339
|
-
);
|
|
265
|
+
await client.query(`UPDATE "${CALL_LOGS_TABLE}" SET "extensionNumber" = '' WHERE "extensionNumber" IS NULL`);
|
|
266
|
+
await client.query(`ALTER TABLE "${CALL_LOGS_TABLE}" ALTER COLUMN "extensionNumber" SET NOT NULL`);
|
|
340
267
|
}
|
|
341
|
-
|
|
342
268
|
// 3. Build the unique index without blocking traffic (cannot run in a transaction).
|
|
343
269
|
// Reuse an already-valid index from a prior run so a failed *attach* (step 4)
|
|
344
270
|
// never forces us to redo the expensive build; only rebuild if it is missing
|
|
@@ -348,11 +274,8 @@ async function ensureCallLogsHashedExtensionIdSchemaPostgresOnline(sequelize, lo
|
|
|
348
274
|
await client.query(`DROP INDEX CONCURRENTLY IF EXISTS "${IDENTITY_INDEX_NAME}"`);
|
|
349
275
|
}
|
|
350
276
|
if (indexState !== 'valid') {
|
|
351
|
-
await client.query(
|
|
352
|
-
`CREATE UNIQUE INDEX CONCURRENTLY "${IDENTITY_INDEX_NAME}" ON "${CALL_LOGS_TABLE}" (${CALL_LOG_IDENTITY_PK_COLUMNS.map((c) => `"${c}"`).join(', ')})`,
|
|
353
|
-
);
|
|
277
|
+
await client.query(`CREATE UNIQUE INDEX CONCURRENTLY "${IDENTITY_INDEX_NAME}" ON "${CALL_LOGS_TABLE}" (${CALL_LOG_IDENTITY_PK_COLUMNS.map((c) => `"${c}"`).join(', ')})`);
|
|
354
278
|
}
|
|
355
|
-
|
|
356
279
|
// 4. Swap the primary key onto the prebuilt index (brief ACCESS EXCLUSIVE lock,
|
|
357
280
|
// no rebuild). Bound the lock wait so that if a long-running query holds the
|
|
358
281
|
// table we fail fast and retry next boot, instead of parking an exclusive-lock
|
|
@@ -360,36 +283,36 @@ async function ensureCallLogsHashedExtensionIdSchemaPostgresOnline(sequelize, lo
|
|
|
360
283
|
await client.query(`SET lock_timeout = '5s'`);
|
|
361
284
|
const pkName = await postgresPrimaryKeyNameOnConnection(client);
|
|
362
285
|
if (pkName) {
|
|
363
|
-
await client.query(
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
await client.query(
|
|
368
|
-
`ALTER TABLE "${CALL_LOGS_TABLE}" ADD PRIMARY KEY USING INDEX "${IDENTITY_INDEX_NAME}"`,
|
|
369
|
-
);
|
|
286
|
+
await client.query(`ALTER TABLE "${CALL_LOGS_TABLE}" DROP CONSTRAINT "${pkName}", ADD PRIMARY KEY USING INDEX "${IDENTITY_INDEX_NAME}"`);
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
await client.query(`ALTER TABLE "${CALL_LOGS_TABLE}" ADD PRIMARY KEY USING INDEX "${IDENTITY_INDEX_NAME}"`);
|
|
370
290
|
}
|
|
371
|
-
|
|
372
291
|
logger?.info?.('[callLogs migration] completed');
|
|
373
|
-
}
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
374
294
|
// Restore session defaults before the connection goes back to the pool, so no
|
|
375
295
|
// other query inherits statement_timeout=0 / the short lock_timeout.
|
|
376
296
|
try {
|
|
377
297
|
await client.query('RESET statement_timeout');
|
|
378
298
|
await client.query('RESET lock_timeout');
|
|
379
|
-
}
|
|
380
|
-
|
|
299
|
+
}
|
|
300
|
+
catch (e) {
|
|
301
|
+
const error = e;
|
|
302
|
+
logger?.warn?.('[callLogs migration] failed to reset session settings', { message: error?.message });
|
|
381
303
|
}
|
|
382
304
|
if (lockAcquired) {
|
|
383
305
|
try {
|
|
384
306
|
await client.query(`SELECT pg_advisory_unlock(${CALL_LOGS_MIGRATION_LOCK_KEY})`);
|
|
385
|
-
}
|
|
386
|
-
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
const error = e;
|
|
310
|
+
logger?.warn?.('[callLogs migration] failed to release advisory lock', { message: error?.message });
|
|
387
311
|
}
|
|
388
312
|
}
|
|
389
313
|
connectionManager.releaseConnection(client);
|
|
390
314
|
}
|
|
391
315
|
}
|
|
392
|
-
|
|
393
316
|
/**
|
|
394
317
|
* Automatic entry point. Safe to call fire-and-forget off the startup critical path:
|
|
395
318
|
* it never throws (failures are logged and retried on the next boot) and, on Postgres,
|
|
@@ -400,25 +323,17 @@ async function runCallLogsSchemaMigration(sequelize, logger) {
|
|
|
400
323
|
const dialect = sequelize.getDialect();
|
|
401
324
|
if (dialect === 'postgres') {
|
|
402
325
|
await ensureCallLogsHashedExtensionIdSchemaPostgresOnline(sequelize, logger);
|
|
403
|
-
}
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
404
328
|
// sqlite / local / tests: tables are tiny, the transactional rebuild is instant.
|
|
405
329
|
await ensureCallLogsHashedExtensionIdSchema(sequelize);
|
|
406
330
|
}
|
|
407
|
-
}
|
|
331
|
+
}
|
|
332
|
+
catch (e) {
|
|
333
|
+
const error = e;
|
|
408
334
|
logger?.error?.('[callLogs migration] failed; will retry on next start', {
|
|
409
|
-
message:
|
|
410
|
-
stack:
|
|
335
|
+
message: error?.message,
|
|
336
|
+
stack: error?.stack,
|
|
411
337
|
});
|
|
412
338
|
}
|
|
413
339
|
}
|
|
414
|
-
|
|
415
|
-
module.exports = {
|
|
416
|
-
findColumnKey,
|
|
417
|
-
migrateCallLogsExtensionNumberSqlite,
|
|
418
|
-
migrateCallLogsIdentitySqlite,
|
|
419
|
-
sqliteCallLogsPkIncludesExtension,
|
|
420
|
-
sqliteCallLogsPkIncludesHashedExtension,
|
|
421
|
-
ensureCallLogsHashedExtensionIdSchema,
|
|
422
|
-
ensureCallLogsHashedExtensionIdSchemaPostgresOnline,
|
|
423
|
-
runCallLogsSchemaMigration,
|
|
424
|
-
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { OAuthAppLike, OAuthInfo, OperationFailureResult, RefreshableOAuthUser } from '../types';
|
|
2
|
+
declare function getOAuthApp({ clientId, clientSecret, accessTokenUri, authorizationUri, redirectUri, scopes }: OAuthInfo): OAuthAppLike;
|
|
3
|
+
declare function checkAndRefreshAccessToken(oauthApp: OAuthAppLike, user: RefreshableOAuthUser, tokenLockTimeout?: number): Promise<RefreshableOAuthUser | OperationFailureResult | null>;
|
|
4
|
+
export { checkAndRefreshAccessToken, getOAuthApp };
|