74 lines
3.1 KiB
JavaScript
74 lines
3.1 KiB
JavaScript
|
|
// GitLab Webhook Routes — receives issue lifecycle events from GitLab
|
||
|
|
// Used to notify users via Webex when their feedback issues are closed.
|
||
|
|
|
||
|
|
const express = require('express');
|
||
|
|
const pool = require('../db');
|
||
|
|
const { sendDirectMessage } = require('../helpers/webexBot');
|
||
|
|
|
||
|
|
const GITLAB_WEBHOOK_SECRET = process.env.GITLAB_WEBHOOK_SECRET || '';
|
||
|
|
|
||
|
|
function createWebhooksRouter() {
|
||
|
|
const router = express.Router();
|
||
|
|
|
||
|
|
// POST /api/webhooks/gitlab — GitLab issue webhook receiver
|
||
|
|
router.post('/gitlab', express.json(), async (req, res) => {
|
||
|
|
// Always return 200 — webhooks should not retry on app-level failures
|
||
|
|
try {
|
||
|
|
// Validate webhook secret token
|
||
|
|
const token = req.headers['x-gitlab-token'];
|
||
|
|
if (!GITLAB_WEBHOOK_SECRET || token !== GITLAB_WEBHOOK_SECRET) {
|
||
|
|
console.warn('[Webhook] Invalid or missing X-Gitlab-Token');
|
||
|
|
return res.status(200).json({ status: 'ignored', reason: 'invalid token' });
|
||
|
|
}
|
||
|
|
|
||
|
|
const { object_attributes } = req.body || {};
|
||
|
|
|
||
|
|
// Only process issue close events
|
||
|
|
if (!object_attributes || object_attributes.action !== 'close') {
|
||
|
|
return res.status(200).json({ status: 'ignored', reason: 'not a close event' });
|
||
|
|
}
|
||
|
|
|
||
|
|
const issueTitle = object_attributes.title || 'Untitled';
|
||
|
|
const issueNumber = object_attributes.iid;
|
||
|
|
const description = object_attributes.description || '';
|
||
|
|
|
||
|
|
// Parse submitter username from issue description
|
||
|
|
// Format: **Submitted by:** username
|
||
|
|
const submitterMatch = description.match(/\*\*Submitted by:\*\*\s*(\S+)/);
|
||
|
|
if (!submitterMatch) {
|
||
|
|
console.log('[Webhook] No submitter found in issue description — skipping DM');
|
||
|
|
return res.status(200).json({ status: 'ignored', reason: 'no submitter in description' });
|
||
|
|
}
|
||
|
|
|
||
|
|
const username = submitterMatch[1];
|
||
|
|
|
||
|
|
// Look up user email in database
|
||
|
|
const { rows } = await pool.query(
|
||
|
|
'SELECT email FROM users WHERE username = $1',
|
||
|
|
[username]
|
||
|
|
);
|
||
|
|
|
||
|
|
if (!rows || rows.length === 0 || !rows[0].email) {
|
||
|
|
console.log(`[Webhook] No email found for user "${username}" — skipping DM`);
|
||
|
|
return res.status(200).json({ status: 'ignored', reason: 'user email not found' });
|
||
|
|
}
|
||
|
|
|
||
|
|
const email = rows[0].email;
|
||
|
|
|
||
|
|
// Send Webex DM notification
|
||
|
|
const message = `Hey! Your bug report **${issueTitle}** (Issue #${issueNumber}) has been resolved and deployed. — Patches O'Houlihan`;
|
||
|
|
sendDirectMessage(email, message);
|
||
|
|
|
||
|
|
console.log(`[Webhook] Issue #${issueNumber} closed — notified ${username} (${email})`);
|
||
|
|
return res.status(200).json({ status: 'ok', notified: username });
|
||
|
|
} catch (err) {
|
||
|
|
console.error('[Webhook] Error processing GitLab webhook:', err.message);
|
||
|
|
return res.status(200).json({ status: 'error', message: err.message });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return router;
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = createWebhooksRouter;
|