Org blocks external Webex bots, so replaced the DM approach with an in-app notification bell. GitLab webhook still fires on issue close, but now writes to a notifications table instead of calling Webex API. - New: notifications table + migration - New: GET/PATCH/POST /api/notifications endpoints - New: NotificationBell component (bell icon + badge + dropdown) - Removed: backend/helpers/webexBot.js (org-blocked) - Removed: WEBEX_BOT_TOKEN from .env
94 lines
4.3 KiB
JavaScript
94 lines
4.3 KiB
JavaScript
// GitLab Webhook Routes — receives issue lifecycle events from GitLab
|
|
// Used to create in-app notifications when feedback issues are closed.
|
|
|
|
const express = require('express');
|
|
const pool = require('../db');
|
|
|
|
const GITLAB_WEBHOOK_SECRET = process.env.GITLAB_WEBHOOK_SECRET || '';
|
|
|
|
function createWebhooksRouter() {
|
|
const router = express.Router();
|
|
|
|
/**
|
|
* POST /api/webhooks/gitlab
|
|
*
|
|
* Receives GitLab issue webhook events. When an issue is closed, parses the
|
|
* submitter username from the issue description and creates an in-app notification.
|
|
*
|
|
* Always returns HTTP 200 to prevent GitLab from retrying on app-level failures.
|
|
*
|
|
* @header {string} x-gitlab-token - Webhook secret token (must match GITLAB_WEBHOOK_SECRET env var)
|
|
* @body {object} object_attributes - GitLab issue event payload
|
|
* @body {string} object_attributes.action - The issue action (only 'close' is processed)
|
|
* @body {string} object_attributes.title - The issue title
|
|
* @body {number} object_attributes.iid - The issue number
|
|
* @body {string} object_attributes.description - The issue description (parsed for "**Submitted by:** username")
|
|
* @returns {object} 200 - { status: 'ok', notified: username }
|
|
* @returns {object} 200 - { status: 'ignored', reason: 'invalid token' | 'not a close event' | 'no submitter in description' | 'user not found' }
|
|
* @returns {object} 200 - { status: 'error', message: string }
|
|
*/
|
|
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 notification');
|
|
return res.status(200).json({ status: 'ignored', reason: 'no submitter in description' });
|
|
}
|
|
|
|
const username = submitterMatch[1];
|
|
|
|
// Verify user exists in database
|
|
const { rows } = await pool.query(
|
|
'SELECT id FROM users WHERE username = $1',
|
|
[username]
|
|
);
|
|
|
|
if (!rows || rows.length === 0) {
|
|
console.log(`[Webhook] No user found for "${username}" — skipping notification`);
|
|
return res.status(200).json({ status: 'ignored', reason: 'user not found' });
|
|
}
|
|
|
|
const userId = rows[0].id;
|
|
|
|
// Insert in-app notification
|
|
const message = `Your bug report **${issueTitle}** (Issue #${issueNumber}) has been resolved and deployed.`;
|
|
await pool.query(
|
|
`INSERT INTO notifications (user_id, username, type, title, message, issue_number)
|
|
VALUES ($1, $2, 'issue_resolved', $3, $4, $5)`,
|
|
[userId, username, issueTitle, message, issueNumber]
|
|
);
|
|
|
|
console.log(`[Webhook] Issue #${issueNumber} closed — notification created for ${username}`);
|
|
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;
|