@app-connect/core 1.7.36 → 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} +548 -450
- 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/dist/lib/migrateCallLogsSchema.js +339 -0
- 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 +13 -1
- 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/migrateCallLogsSchema.js +0 -237
- 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 };
|
|
@@ -0,0 +1,339 @@
|
|
|
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;
|
|
11
|
+
const Sequelize = require('sequelize');
|
|
12
|
+
const CALL_LOGS_TABLE = 'callLogs';
|
|
13
|
+
const HASHED_EXTENSION_ID_COLUMN = 'hashedExtensionId';
|
|
14
|
+
const CALL_LOG_IDENTITY_PK_COLUMNS = ['id', 'sessionId', 'extensionNumber', HASHED_EXTENSION_ID_COLUMN];
|
|
15
|
+
function findColumnKey(tableDescription, name) {
|
|
16
|
+
const lower = name.toLowerCase();
|
|
17
|
+
return Object.keys(tableDescription).find((k) => k.toLowerCase() === lower);
|
|
18
|
+
}
|
|
19
|
+
async function fetchCallLogsCreateSqlSqlite(sequelize, options = {}) {
|
|
20
|
+
const rows = await sequelize.query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'callLogs'", { type: Sequelize.QueryTypes.SELECT, ...options });
|
|
21
|
+
return rows[0]?.sql ?? null;
|
|
22
|
+
}
|
|
23
|
+
function callLogsCreateSqlClaimsPkColumns(createSql, columns) {
|
|
24
|
+
if (!createSql || typeof createSql !== 'string') {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const pkRegex = /PRIMARY\s+KEY\s*\(([^)]+)\)/gi;
|
|
28
|
+
let m;
|
|
29
|
+
while ((m = pkRegex.exec(createSql)) !== null) {
|
|
30
|
+
const pkColumns = m[1]
|
|
31
|
+
.split(',')
|
|
32
|
+
.map((column) => column.replace(/["'`\[\]\s]/g, '').toLowerCase());
|
|
33
|
+
if (columns.every((column) => pkColumns.includes(column.toLowerCase()))) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
function callLogsCreateSqlClaimsExtensionPk(createSql) {
|
|
40
|
+
return callLogsCreateSqlClaimsPkColumns(createSql, ['extensionNumber']);
|
|
41
|
+
}
|
|
42
|
+
async function sqliteCallLogsPkIncludesExtension(sequelize, options = {}) {
|
|
43
|
+
const sql = await fetchCallLogsCreateSqlSqlite(sequelize, options);
|
|
44
|
+
return callLogsCreateSqlClaimsExtensionPk(sql);
|
|
45
|
+
}
|
|
46
|
+
async function sqliteCallLogsPkIncludesHashedExtension(sequelize, options = {}) {
|
|
47
|
+
const sql = await fetchCallLogsCreateSqlSqlite(sequelize, options);
|
|
48
|
+
return callLogsCreateSqlClaimsPkColumns(sql, CALL_LOG_IDENTITY_PK_COLUMNS);
|
|
49
|
+
}
|
|
50
|
+
async function describeCallLogsTable(sequelize, options = {}) {
|
|
51
|
+
return sequelize.getQueryInterface().describeTable(CALL_LOGS_TABLE, options);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* SQLite cannot change composite PK via Sequelize addConstraint (it appends a second PRIMARY KEY).
|
|
55
|
+
* Rebuild the table with the current call-log identity primary key and copy rows.
|
|
56
|
+
*/
|
|
57
|
+
async function migrateCallLogsIdentitySqlite(sequelize, transaction) {
|
|
58
|
+
const qg = sequelize.getQueryInterface().queryGenerator;
|
|
59
|
+
const qi = sequelize.getQueryInterface();
|
|
60
|
+
const q = (id) => qg.quoteIdentifier(id);
|
|
61
|
+
const tmpName = `callLogs_mig_legacy_${Date.now()}`;
|
|
62
|
+
const mainTableSql = qg.quoteTable({ tableName: CALL_LOGS_TABLE });
|
|
63
|
+
const tmpTableSql = qg.quoteTable({ tableName: tmpName });
|
|
64
|
+
await sequelize.query(`ALTER TABLE ${mainTableSql} RENAME TO ${qg.quoteIdentifier(tmpName)};`, { transaction });
|
|
65
|
+
await sequelize.query(`
|
|
66
|
+
CREATE TABLE ${mainTableSql} (
|
|
67
|
+
${q('id')} VARCHAR(255) NOT NULL,
|
|
68
|
+
${q('sessionId')} VARCHAR(255) NOT NULL,
|
|
69
|
+
${q('extensionNumber')} VARCHAR(255) NOT NULL DEFAULT '',
|
|
70
|
+
${q(HASHED_EXTENSION_ID_COLUMN)} VARCHAR(255) NOT NULL DEFAULT '',
|
|
71
|
+
${q('platform')} VARCHAR(255),
|
|
72
|
+
${q('thirdPartyLogId')} VARCHAR(255),
|
|
73
|
+
${q('userId')} VARCHAR(255),
|
|
74
|
+
${q('contactId')} VARCHAR(255),
|
|
75
|
+
${q('createdAt')} DATETIME NOT NULL,
|
|
76
|
+
${q('updatedAt')} DATETIME NOT NULL,
|
|
77
|
+
PRIMARY KEY (${CALL_LOG_IDENTITY_PK_COLUMNS.map(q).join(', ')})
|
|
78
|
+
);
|
|
79
|
+
`, { transaction });
|
|
80
|
+
const oldDesc = await qi.describeTable(tmpName, { transaction });
|
|
81
|
+
const targetCols = [
|
|
82
|
+
'id',
|
|
83
|
+
'sessionId',
|
|
84
|
+
'extensionNumber',
|
|
85
|
+
HASHED_EXTENSION_ID_COLUMN,
|
|
86
|
+
'platform',
|
|
87
|
+
'thirdPartyLogId',
|
|
88
|
+
'userId',
|
|
89
|
+
'contactId',
|
|
90
|
+
'createdAt',
|
|
91
|
+
'updatedAt',
|
|
92
|
+
];
|
|
93
|
+
const missingRequired = targetCols.filter((c) => findColumnKey(oldDesc, c) == null && c !== 'extensionNumber' && c !== HASHED_EXTENSION_ID_COLUMN);
|
|
94
|
+
if (missingRequired.length) {
|
|
95
|
+
throw new Error(`unexpected callLogs schema before migration (missing columns: ${missingRequired.join(', ')})`);
|
|
96
|
+
}
|
|
97
|
+
const insertCols = targetCols.map(q).join(', ');
|
|
98
|
+
const selectExprs = targetCols
|
|
99
|
+
.map((col) => {
|
|
100
|
+
const key = findColumnKey(oldDesc, col);
|
|
101
|
+
if (key != null) {
|
|
102
|
+
return q(key);
|
|
103
|
+
}
|
|
104
|
+
return `CAST('' AS VARCHAR(255))`;
|
|
105
|
+
})
|
|
106
|
+
.join(', ');
|
|
107
|
+
await sequelize.query(`INSERT INTO ${mainTableSql} (${insertCols}) SELECT ${selectExprs} FROM ${tmpTableSql};`, { transaction });
|
|
108
|
+
await sequelize.query(`DROP TABLE ${tmpTableSql};`, { transaction });
|
|
109
|
+
}
|
|
110
|
+
async function migrateCallLogsExtensionNumberSqlite(sequelize, transaction) {
|
|
111
|
+
return migrateCallLogsIdentitySqlite(sequelize, transaction);
|
|
112
|
+
}
|
|
113
|
+
async function getPostgresPrimaryKeyConstraint(sequelize, options = {}) {
|
|
114
|
+
const constraints = await sequelize.query(`
|
|
115
|
+
SELECT tc.constraint_name AS "constraintName"
|
|
116
|
+
FROM information_schema.table_constraints tc
|
|
117
|
+
WHERE tc.table_name = :tableName
|
|
118
|
+
AND tc.constraint_type = 'PRIMARY KEY'
|
|
119
|
+
LIMIT 1
|
|
120
|
+
`, {
|
|
121
|
+
type: Sequelize.QueryTypes.SELECT,
|
|
122
|
+
replacements: { tableName: CALL_LOGS_TABLE },
|
|
123
|
+
...options,
|
|
124
|
+
});
|
|
125
|
+
return constraints[0]?.constraintName ?? null;
|
|
126
|
+
}
|
|
127
|
+
async function postgresCallLogsPkIncludesHashedExtension(sequelize, options = {}) {
|
|
128
|
+
const pkConstraintName = await getPostgresPrimaryKeyConstraint(sequelize, options);
|
|
129
|
+
if (!pkConstraintName) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
const rows = await sequelize.query(`
|
|
133
|
+
SELECT kcu.column_name AS "columnName"
|
|
134
|
+
FROM information_schema.key_column_usage kcu
|
|
135
|
+
WHERE kcu.table_name = :tableName
|
|
136
|
+
AND kcu.constraint_name = :constraintName
|
|
137
|
+
ORDER BY kcu.ordinal_position
|
|
138
|
+
`, {
|
|
139
|
+
type: Sequelize.QueryTypes.SELECT,
|
|
140
|
+
replacements: { tableName: CALL_LOGS_TABLE, constraintName: pkConstraintName },
|
|
141
|
+
...options,
|
|
142
|
+
});
|
|
143
|
+
const pkColumns = rows.map((row) => row.columnName.toLowerCase());
|
|
144
|
+
return CALL_LOG_IDENTITY_PK_COLUMNS.every((column) => pkColumns.includes(column.toLowerCase()));
|
|
145
|
+
}
|
|
146
|
+
async function migrateCallLogsIdentityPostgres(sequelize, transaction) {
|
|
147
|
+
const qi = sequelize.getQueryInterface();
|
|
148
|
+
const qg = qi.queryGenerator;
|
|
149
|
+
const table = qg.quoteTable({ tableName: CALL_LOGS_TABLE });
|
|
150
|
+
const pkConstraintName = await getPostgresPrimaryKeyConstraint(sequelize, { transaction });
|
|
151
|
+
if (pkConstraintName) {
|
|
152
|
+
await sequelize.query(`ALTER TABLE ${table} DROP CONSTRAINT ${qg.quoteIdentifier(pkConstraintName)};`, { transaction });
|
|
153
|
+
}
|
|
154
|
+
await sequelize.query(`ALTER TABLE ${table} ADD PRIMARY KEY (${CALL_LOG_IDENTITY_PK_COLUMNS.map((column) => qg.quoteIdentifier(column)).join(', ')});`, { transaction });
|
|
155
|
+
}
|
|
156
|
+
async function ensureCallLogsHashedExtensionIdSchema(sequelize) {
|
|
157
|
+
const qi = sequelize.getQueryInterface();
|
|
158
|
+
const dialect = sequelize.getDialect();
|
|
159
|
+
let desc = await describeCallLogsTable(sequelize);
|
|
160
|
+
if (findColumnKey(desc, HASHED_EXTENSION_ID_COLUMN) == null) {
|
|
161
|
+
await qi.addColumn(CALL_LOGS_TABLE, HASHED_EXTENSION_ID_COLUMN, {
|
|
162
|
+
type: Sequelize.STRING,
|
|
163
|
+
allowNull: false,
|
|
164
|
+
defaultValue: '',
|
|
165
|
+
});
|
|
166
|
+
desc = await describeCallLogsTable(sequelize);
|
|
167
|
+
}
|
|
168
|
+
if (dialect === 'sqlite') {
|
|
169
|
+
const hasIdentityPk = await sqliteCallLogsPkIncludesHashedExtension(sequelize);
|
|
170
|
+
if (!hasIdentityPk) {
|
|
171
|
+
await sequelize.transaction(async (transaction) => {
|
|
172
|
+
await migrateCallLogsIdentitySqlite(sequelize, transaction);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (dialect === 'postgres') {
|
|
178
|
+
const hasIdentityPk = await postgresCallLogsPkIncludesHashedExtension(sequelize);
|
|
179
|
+
if (!hasIdentityPk) {
|
|
180
|
+
await sequelize.transaction(async (transaction) => {
|
|
181
|
+
await migrateCallLogsIdentityPostgres(sequelize, transaction);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Dedicated name for the online-built unique index that becomes the new PK.
|
|
187
|
+
const IDENTITY_INDEX_NAME = 'callLogs_identity_pk';
|
|
188
|
+
// Stable application-wide key for the Postgres advisory lock that serializes the
|
|
189
|
+
// migration across concurrently-booting instances. Any fixed integer works as long
|
|
190
|
+
// as it is unique within the app; keep it constant so every instance agrees on it.
|
|
191
|
+
const CALL_LOGS_MIGRATION_LOCK_KEY = 4972001;
|
|
192
|
+
async function postgresPrimaryKeyNameOnConnection(client) {
|
|
193
|
+
const { rows } = await client.query(`SELECT conname FROM pg_constraint
|
|
194
|
+
WHERE conrelid = '"${CALL_LOGS_TABLE}"'::regclass AND contype = 'p' LIMIT 1`);
|
|
195
|
+
return rows[0]?.conname ?? null;
|
|
196
|
+
}
|
|
197
|
+
async function postgresPkIncludesIdentityOnConnection(client) {
|
|
198
|
+
const { rows } = await client.query(`SELECT kcu.column_name AS col
|
|
199
|
+
FROM information_schema.table_constraints tc
|
|
200
|
+
JOIN information_schema.key_column_usage kcu
|
|
201
|
+
ON kcu.constraint_name = tc.constraint_name
|
|
202
|
+
AND kcu.table_name = tc.table_name
|
|
203
|
+
WHERE tc.table_name = $1 AND tc.constraint_type = 'PRIMARY KEY'`, [CALL_LOGS_TABLE]);
|
|
204
|
+
const cols = rows.map((r) => String(r.col).toLowerCase());
|
|
205
|
+
return CALL_LOG_IDENTITY_PK_COLUMNS.every((c) => cols.includes(c.toLowerCase()));
|
|
206
|
+
}
|
|
207
|
+
async function postgresColumnIsNullableOnConnection(client, column) {
|
|
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]);
|
|
210
|
+
return rows[0]?.is_nullable === 'YES';
|
|
211
|
+
}
|
|
212
|
+
// 'absent' | 'valid' | 'invalid' - an INVALID index is the residue of a
|
|
213
|
+
// CONCURRENTLY build that was interrupted (process killed, connection dropped).
|
|
214
|
+
async function postgresIdentityIndexState(client) {
|
|
215
|
+
const { rows } = await client.query(`SELECT (i.indisvalid AND i.indisready) AS ok
|
|
216
|
+
FROM pg_class c
|
|
217
|
+
JOIN pg_index i ON i.indexrelid = c.oid
|
|
218
|
+
WHERE c.relname = $1`, [IDENTITY_INDEX_NAME]);
|
|
219
|
+
if (rows.length === 0)
|
|
220
|
+
return 'absent';
|
|
221
|
+
return rows[0].ok ? 'valid' : 'invalid';
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Online (non-blocking) Postgres migration.
|
|
225
|
+
*
|
|
226
|
+
* Adds the hashedExtensionId column and rebuilds the primary key to the call-log
|
|
227
|
+
* identity WITHOUT holding an ACCESS EXCLUSIVE lock for the duration of the index
|
|
228
|
+
* build. This is the safe path for large production tables:
|
|
229
|
+
* - ADD COLUMN ... NOT NULL DEFAULT '' is metadata-only on PG 11+.
|
|
230
|
+
* - CREATE UNIQUE INDEX CONCURRENTLY builds the index while reads/writes continue.
|
|
231
|
+
* - ADD PRIMARY KEY USING INDEX attaches the prebuilt index with only a brief lock.
|
|
232
|
+
*
|
|
233
|
+
* Runs on a single dedicated pooled connection so the advisory lock and the
|
|
234
|
+
* (necessarily non-transactional) CONCURRENTLY statements share one backend session.
|
|
235
|
+
*/
|
|
236
|
+
async function ensureCallLogsHashedExtensionIdSchemaPostgresOnline(sequelize, logger) {
|
|
237
|
+
// Cheap pre-check on a pooled connection; skip entirely once migrated.
|
|
238
|
+
if (await postgresCallLogsPkIncludesHashedExtension(sequelize)) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const connectionManager = sequelize.connectionManager;
|
|
242
|
+
const client = await connectionManager.getConnection({ type: 'write' });
|
|
243
|
+
let lockAcquired = false;
|
|
244
|
+
try {
|
|
245
|
+
const lockRes = await client.query(`SELECT pg_try_advisory_lock(${CALL_LOGS_MIGRATION_LOCK_KEY}) AS locked`);
|
|
246
|
+
lockAcquired = lockRes.rows[0]?.locked === true;
|
|
247
|
+
if (!lockAcquired) {
|
|
248
|
+
logger?.info?.('[callLogs migration] another instance is migrating; skipping this run');
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// Re-check under the lock - another instance may have finished between the
|
|
252
|
+
// pre-check and acquiring the lock.
|
|
253
|
+
if (await postgresPkIncludesIdentityOnConnection(client)) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
logger?.info?.('[callLogs migration] starting online migration (add column + rebuild primary key)');
|
|
257
|
+
// A server-side statement_timeout (common in RDS parameter groups) would abort
|
|
258
|
+
// the long CONCURRENTLY build and leave an INVALID index. Disable it for this
|
|
259
|
+
// session only; it is reset before the connection returns to the pool.
|
|
260
|
+
await client.query(`SET statement_timeout = 0`);
|
|
261
|
+
// 1. Add the column (metadata-only on PG 11+, safe to repeat).
|
|
262
|
+
await client.query(`ALTER TABLE "${CALL_LOGS_TABLE}" ADD COLUMN IF NOT EXISTS "${HASHED_EXTENSION_ID_COLUMN}" varchar(255) NOT NULL DEFAULT ''`);
|
|
263
|
+
// 2. Primary-key columns must be NOT NULL. Only touch extensionNumber if needed.
|
|
264
|
+
if (await postgresColumnIsNullableOnConnection(client, 'extensionNumber')) {
|
|
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`);
|
|
267
|
+
}
|
|
268
|
+
// 3. Build the unique index without blocking traffic (cannot run in a transaction).
|
|
269
|
+
// Reuse an already-valid index from a prior run so a failed *attach* (step 4)
|
|
270
|
+
// never forces us to redo the expensive build; only rebuild if it is missing
|
|
271
|
+
// or was left INVALID by an interrupted run.
|
|
272
|
+
const indexState = await postgresIdentityIndexState(client);
|
|
273
|
+
if (indexState === 'invalid') {
|
|
274
|
+
await client.query(`DROP INDEX CONCURRENTLY IF EXISTS "${IDENTITY_INDEX_NAME}"`);
|
|
275
|
+
}
|
|
276
|
+
if (indexState !== 'valid') {
|
|
277
|
+
await client.query(`CREATE UNIQUE INDEX CONCURRENTLY "${IDENTITY_INDEX_NAME}" ON "${CALL_LOGS_TABLE}" (${CALL_LOG_IDENTITY_PK_COLUMNS.map((c) => `"${c}"`).join(', ')})`);
|
|
278
|
+
}
|
|
279
|
+
// 4. Swap the primary key onto the prebuilt index (brief ACCESS EXCLUSIVE lock,
|
|
280
|
+
// no rebuild). Bound the lock wait so that if a long-running query holds the
|
|
281
|
+
// table we fail fast and retry next boot, instead of parking an exclusive-lock
|
|
282
|
+
// request at the head of the queue and stalling all traffic behind it.
|
|
283
|
+
await client.query(`SET lock_timeout = '5s'`);
|
|
284
|
+
const pkName = await postgresPrimaryKeyNameOnConnection(client);
|
|
285
|
+
if (pkName) {
|
|
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}"`);
|
|
290
|
+
}
|
|
291
|
+
logger?.info?.('[callLogs migration] completed');
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
// Restore session defaults before the connection goes back to the pool, so no
|
|
295
|
+
// other query inherits statement_timeout=0 / the short lock_timeout.
|
|
296
|
+
try {
|
|
297
|
+
await client.query('RESET statement_timeout');
|
|
298
|
+
await client.query('RESET lock_timeout');
|
|
299
|
+
}
|
|
300
|
+
catch (e) {
|
|
301
|
+
const error = e;
|
|
302
|
+
logger?.warn?.('[callLogs migration] failed to reset session settings', { message: error?.message });
|
|
303
|
+
}
|
|
304
|
+
if (lockAcquired) {
|
|
305
|
+
try {
|
|
306
|
+
await client.query(`SELECT pg_advisory_unlock(${CALL_LOGS_MIGRATION_LOCK_KEY})`);
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
const error = e;
|
|
310
|
+
logger?.warn?.('[callLogs migration] failed to release advisory lock', { message: error?.message });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
connectionManager.releaseConnection(client);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Automatic entry point. Safe to call fire-and-forget off the startup critical path:
|
|
318
|
+
* it never throws (failures are logged and retried on the next boot) and, on Postgres,
|
|
319
|
+
* never blocks readiness or live traffic.
|
|
320
|
+
*/
|
|
321
|
+
async function runCallLogsSchemaMigration(sequelize, logger) {
|
|
322
|
+
try {
|
|
323
|
+
const dialect = sequelize.getDialect();
|
|
324
|
+
if (dialect === 'postgres') {
|
|
325
|
+
await ensureCallLogsHashedExtensionIdSchemaPostgresOnline(sequelize, logger);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
// sqlite / local / tests: tables are tiny, the transactional rebuild is instant.
|
|
329
|
+
await ensureCallLogsHashedExtensionIdSchema(sequelize);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
catch (e) {
|
|
333
|
+
const error = e;
|
|
334
|
+
logger?.error?.('[callLogs migration] failed; will retry on next start', {
|
|
335
|
+
message: error?.message,
|
|
336
|
+
stack: error?.stack,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
@@ -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 };
|