Introduces integration-based webhook registration, message parsing, dry-run monitoring, JSM ticket creation, and OAuth token refresh for DC Ops spaces. Co-authored-by: Cursor <cursoragent@cursor.com>
143 lines
5.4 KiB
JavaScript
143 lines
5.4 KiB
JavaScript
import fs from 'fs';
|
|
import { loadConfig, loadRequests, loadWebexRooms, getMissingSecrets, describeJiraIdentity } from './lib/config.js';
|
|
import { createLogger } from './lib/logger.js';
|
|
import { createJiraClient } from './services/jiraClient.js';
|
|
import { createJiraService } from './services/jira.js';
|
|
import { createWebexService } from './services/webex.js';
|
|
import { createJiraProcessor } from './services/jiraProcessor.js';
|
|
import { createPendingApprovalsJob, cleanupOldLogs } from './jobs/pendingApprovals.js';
|
|
import { scheduleJobs } from './jobs/cron.js';
|
|
import { createJiraTicketsService } from './services/jiraTickets.js';
|
|
import { createWebexOAuthService } from './services/webexOAuth.js';
|
|
import { createWebexWebhookManager } from './services/webexWebhookManager.js';
|
|
import { createWebhookRoutes } from './routes/webhooks.js';
|
|
import { createWebexWebhookRoutes } from './routes/webexWebhooks.js';
|
|
import { createWebexOAuthRoutes } from './routes/webexOAuth.js';
|
|
import { createApp } from './app.js';
|
|
|
|
const OAUTH_START_URL = 'https://bot.joesjavajoint.com/jiracloud/webex/oauth/start';
|
|
|
|
const config = loadConfig();
|
|
const requests = loadRequests();
|
|
const webexRooms = loadWebexRooms();
|
|
const log = createLogger(config);
|
|
|
|
if (!fs.existsSync('./logs')) {
|
|
fs.mkdirSync('./logs', { recursive: true });
|
|
}
|
|
|
|
const missingSecrets = getMissingSecrets(config);
|
|
if (missingSecrets.length > 0) {
|
|
log.writeLog('warn', 'startup', `Missing configuration — set env vars or config.json: ${missingSecrets.join(', ')}`);
|
|
}
|
|
if (process.env.NODE_TLS_REJECT_UNAUTHORIZED === '0') {
|
|
log.writeLog('warn', 'startup', 'NODE_TLS_REJECT_UNAUTHORIZED=0 is set — TLS certificate verification is disabled');
|
|
}
|
|
log.logger('startup', `Jira identity: ${describeJiraIdentity(config)}`);
|
|
if (config.webex.inboundEnabled) {
|
|
log.logger(
|
|
'startup',
|
|
`Webex inbound enabled (dryRun=${config.webex.inboundDryRun}, rooms=${Object.keys(webexRooms).length})`
|
|
);
|
|
if (!config.webex.webhookSecret) {
|
|
log.writeLog('warn', 'startup', 'WEBEX_WEBHOOK_SECRET is not set — inbound Webex webhooks will be rejected');
|
|
}
|
|
if (!config.webex.integration?.clientId || !config.webex.integration?.clientSecret) {
|
|
log.writeLog('warn', 'startup', 'WEBEX_INTEGRATION_CLIENT_ID/SECRET not set — OAuth flow unavailable');
|
|
}
|
|
}
|
|
|
|
const jiraClient = createJiraClient(config);
|
|
const jiraService = createJiraService(jiraClient, log);
|
|
const jiraTicketsService = createJiraTicketsService(config, log);
|
|
const webexService = createWebexService(config, log);
|
|
const webexOAuth = createWebexOAuthService(config, log);
|
|
const webexWebhookManager = createWebexWebhookManager({ webexOAuth, log });
|
|
const jiraProcessor = createJiraProcessor({ config, requests, jiraService, webexService, log });
|
|
const pendingApprovalsJob = createPendingApprovalsJob({ jiraService, webexService, log });
|
|
|
|
scheduleJobs({
|
|
runPendingApprovals: pendingApprovalsJob.runPendingApprovals,
|
|
cleanupOldLogs: () => cleanupOldLogs(log),
|
|
runWebexOAuthMaintenance: async () => {
|
|
if (!config.webex.inboundEnabled || !webexOAuth.isAuthenticated()) {
|
|
return;
|
|
}
|
|
await webexOAuth.refreshIfNeeded();
|
|
const accessToken = await webexOAuth.getAccessToken();
|
|
if (accessToken) {
|
|
await webexWebhookManager.ensureWebhooks({ accessToken, webexRooms, config });
|
|
}
|
|
},
|
|
log,
|
|
});
|
|
|
|
async function initializeWebexInbound() {
|
|
if (!config.webex.inboundEnabled) {
|
|
return;
|
|
}
|
|
|
|
if (!webexOAuth.isAuthenticated()) {
|
|
log.writeLog('warn', 'startup', `Webex integration not authenticated — visit ${OAUTH_START_URL}`);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await webexOAuth.refreshIfNeeded();
|
|
const accessToken = await webexOAuth.getAccessToken();
|
|
if (!accessToken) {
|
|
log.writeLog('warn', 'startup', `Webex integration token unavailable — visit ${OAUTH_START_URL}`);
|
|
return;
|
|
}
|
|
const results = await webexWebhookManager.ensureWebhooks({ accessToken, webexRooms, config });
|
|
log.logger('startup', `Webex webhooks ensured for ${results.length} room(s)`);
|
|
} catch (error) {
|
|
log.logError('startup', 'Failed to initialize Webex inbound OAuth/webhooks', error);
|
|
}
|
|
}
|
|
|
|
const app = createApp();
|
|
const webhookRoutes = createWebhookRoutes({ config, jiraProcessor, webexService, log });
|
|
const webexWebhookRoutes = createWebexWebhookRoutes({
|
|
config,
|
|
webexRooms,
|
|
jiraTicketsService,
|
|
webexService,
|
|
webexOAuth,
|
|
log,
|
|
});
|
|
const webexOAuthRoutes = createWebexOAuthRoutes({
|
|
config,
|
|
webexOAuth,
|
|
webexWebhookManager,
|
|
webexRooms,
|
|
log,
|
|
});
|
|
webhookRoutes.registerRoutes(app);
|
|
webexWebhookRoutes.registerRoutes(app);
|
|
webexOAuthRoutes.registerRoutes(app);
|
|
webhookRoutes.registerErrorHandler(app);
|
|
|
|
initializeWebexInbound().catch(error => {
|
|
log.logError('startup', 'Webex inbound initialization failed', error);
|
|
});
|
|
|
|
const server = app.listen(config.server.port, () => {
|
|
log.logger('startup', `${config.server.name} running on port ${config.server.port}.`);
|
|
});
|
|
|
|
process.on('SIGINT', () => {
|
|
server.close(() => {
|
|
log.logger('shutdown', `${config.server.name} stopped!`);
|
|
process.exit();
|
|
});
|
|
});
|
|
|
|
process.on('unhandledRejection', (reason) => {
|
|
log.logError('process', 'Unhandled promise rejection', reason);
|
|
});
|
|
|
|
process.on('uncaughtException', (err) => {
|
|
log.logError('process', 'Uncaught exception — shutting down', err);
|
|
process.exit(1);
|
|
});
|