jiraCloud/index.js
jmcqueen efc64a227c Add Webex inbound OAuth flow and room-to-Jira job request pipeline.
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>
2026-07-28 08:27:52 -04:00

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);
});