import express from 'express'; import { PrismaClient } from '@prisma/client'; // Import Prisma Client import { getMantisSettings, saveTicketToDatabase } from '../services/mantisDownloader.js'; import axios from 'axios'; import reader from '@kenjiuno/msgreader'; import { askGemini } from '../utils/gemini.js'; import { usernameMap } from '../services/mantisSummarizer.js'; const MsgReader = reader.default; const prisma = new PrismaClient(); // Instantiate Prisma Client const router = express.Router(); // GET /mantis - Fetch multiple Mantis issues with filtering and pagination router.get('/', async(req, res) => { const { page = 1, limit = 10, status, priority, severity, reporterUsername, search } = req.query; const pageNum = parseInt(page, 10); const limitNum = parseInt(limit, 10); const skip = (pageNum - 1) * limitNum; const where = {}; if (status) where.status = status; if (priority) where.priority = priority; if (severity) where.severity = severity; if (reporterUsername) where.reporterUsername = reporterUsername; if (search) { where.OR = [ { title: { contains: search, mode: 'insensitive' } }, { description: { contains: search, mode: 'insensitive' } }, ]; // If the search term is a number, treat it as an ID const searchNum = parseInt(search, 10); if (!isNaN(searchNum)) { where.OR.push({ id: searchNum }); } } try { let [issues, totalCount] = await prisma.$transaction([ prisma.mantisIssue.findMany({ where, skip, take: limitNum, orderBy: { updatedAt: 'desc', // Default sort order }, // You might want to include related data like comments count later // include: { _count: { select: { comments: true } } } }), prisma.mantisIssue.count({ where }), ]); if (!issues || issues.length === 0) { //If it's numeric, try to download the issue from Mantis const searchNum = parseInt(search, 10); if (!isNaN(searchNum)) { let data; try { data = await saveTicketToDatabase(searchNum); } catch (error) { console.error('Error saving ticket to database:', error.message); return res.status(404).json({ error: 'Mantis issue not found' }); } if (!data) { return res.status(404).json({ error: 'Mantis issue not found' }); } // Fetch the issue again from the database issues = await prisma.mantisIssue.findMany({ where, skip, take: limitNum, orderBy: { updatedAt: 'desc', // Default sort order }, }); if (issues.length === 0) { return res.status(404).json({ error: 'Mantis issue not found' }); } totalCount = await prisma.mantisIssue.count({ where }); } } res.json({ data: issues, pagination: { total: totalCount, page: pageNum, limit: limitNum, totalPages: Math.ceil(totalCount / limitNum), }, }); } catch (error) { console.error('Error fetching Mantis issues:', error.message); res.status(500).json({ error: 'Failed to fetch Mantis issues' }); } }); // GET /mantis/:id - Fetch a single Mantis issue by ID router.get('/:id', async(req, res) => { const { id } = req.params; const issueId = parseInt(id, 10); if (isNaN(issueId)) { return res.status(400).json({ error: 'Invalid issue ID format' }); } try { const issue = await prisma.mantisIssue.findUnique({ where: { id: issueId }, include: { comments: { // Include comments include: { attachments: true } // And include attachments for each comment } } }); if (!issue) { //Try to download the issue from Mantis const data = await saveTicketToDatabase(issueId); if (!data) { return res.status(404).json({ error: 'Mantis issue not found' }); } // Fetch the issue again from the database const issue = await prisma.mantisIssue.findUnique({ where: { id: issueId }, include: { comments: { // Include comments include: { attachments: true } // And include attachments for each comment } } }); } res.json(issue); } catch (error) { console.error(`Error fetching Mantis issue ${issueId}:`, error.message); res.status(500).json({ error: 'Failed to fetch Mantis issue' }); } }); router.get('/attachment/:ticketId/:attachmentId', async(req, res) => { const { url, headers } = await getMantisSettings(); const { ticketId, attachmentId } = req.params; const attachmentUrl = `${url}/issues/${ticketId}/files/${attachmentId}`; console.log('Fetching Mantis attachment from URL:', attachmentUrl); try { const response = await axios.get(attachmentUrl, { headers }); const attachment = response.data.files[0]; if (!attachment) { return res.status(404).json({ error: 'Attachment not found' }); } const buffer = Buffer.from(attachment.content, 'base64'); res.setHeader('Content-Type', attachment.content_type); res.setHeader('Content-Disposition', `attachment; filename="${attachment.filename}"`); res.setHeader('Content-Length', buffer.length); res.send(buffer); } catch (error) { console.error('Error fetching Mantis attachment:', error.message); res.status(500).json({ error: 'Failed to fetch Mantis attachment' }); } }); router.get('/msg-extract/:ticketId/:attachmentId', async(req, res) => { const { url, headers } = await getMantisSettings(); const { ticketId, attachmentId } = req.params; const attachmentUrl = `${url}/issues/${ticketId}/files/${attachmentId}`; console.log('Fetching Mantis attachment from URL:', attachmentUrl); try { const response = await axios.get(attachmentUrl, { headers }); const attachment = response.data.files[0]; if (!attachment) { return res.status(404).json({ error: 'Attachment not found' }); } const buffer = Buffer.from(attachment.content, 'base64'); console.log(MsgReader); const reader = new MsgReader(buffer); const msg = reader.getFileData(); res.status(200).json(msg); } catch (error) { console.error('Error fetching Mantis attachment:', error.message); res.status(500).json({ error: 'Failed to fetch Mantis attachment' }); } }); router.get('/msg-extract/:ticketId/:attachmentId/:innerAttachmentId', async(req, res) => { const { url, headers } = await getMantisSettings(); const { ticketId, attachmentId, innerAttachmentId } = req.params; const attachmentUrl = `${url}/issues/${ticketId}/files/${attachmentId}`; console.log('Fetching Mantis attachment from URL:', attachmentUrl); try { const response = await axios.get(attachmentUrl, { headers }); const attachment = response.data.files[0]; if (!attachment) { return res.status(404).json({ error: 'Attachment not found' }); } const buffer = Buffer.from(attachment.content, 'base64'); const reader = new MsgReader(buffer); const msg = reader.getFileData(); // Find the inner attachment const innerAttachment = msg.attachments[innerAttachmentId]; if (!innerAttachment) { return res.status(404).json({ error: 'Inner attachment not found' }); } const attachmentData = reader.getAttachment(innerAttachment); const innerBuffer = Buffer.from(attachmentData.content, 'base64'); res.setHeader('Content-Disposition', `attachment; filename="${innerAttachment.fileName}"`); res.status(200).send(innerBuffer); } catch (error) { console.error('Error fetching Mantis attachment:', error.message); res.status(500).json({ error: 'Failed to fetch Mantis attachment' }); } }); // GET /mantis/stats/issues - Get daily count of new Mantis issues (last 7 days) router.get('/stats/issues', async(req, res) => { try { // Calculate the date range (last 7 days) const endDate = new Date(); endDate.setHours(23, 59, 59, 999); // End of today const startDate = new Date(); startDate.setDate(startDate.getDate() - 6); // 7 days ago startDate.setHours(0, 0, 0, 0); // Start of the day // Query for daily counts of issues created in the last 7 days const dailyIssues = await prisma.$queryRaw` SELECT DATE(created_at) as date, COUNT(*) as count FROM "MantisIssue" WHERE created_at >= ${startDate} AND created_at <= ${endDate} GROUP BY DATE(created_at) ORDER BY date ASC `; res.status(200).json(dailyIssues); } catch (error) { console.error('Error fetching Mantis issue statistics:', error.message); res.status(500).json({ error: 'Failed to fetch Mantis issue statistics' }); } }); // GET /mantis/stats/comments - Get daily count of new Mantis comments (last 7 days) router.get('/stats/comments', async(req, res) => { try { // Calculate the date range (last 7 days) const endDate = new Date(); endDate.setHours(23, 59, 59, 999); // End of today const startDate = new Date(); startDate.setDate(startDate.getDate() - 6); // 7 days ago startDate.setHours(0, 0, 0, 0); // Start of the day // Query for daily counts of comments created in the last 7 days const dailyComments = await prisma.$queryRaw` SELECT DATE(created_at) as date, COUNT(*) as count FROM "MantisComment" WHERE created_at >= ${startDate} AND created_at <= ${endDate} GROUP BY DATE(created_at) ORDER BY date ASC `; res.status(200).json(dailyComments); } catch (error) { console.error('Error fetching Mantis comment statistics:', error.message); res.status(500).json({ error: 'Failed to fetch Mantis comment statistics' }); } }); router.get('/summary/:ticketId', async(req, res) => { const { ticketId } = req.params; try { const ticket = await prisma.mantisIssue.findUnique({ where: { id: parseInt(ticketId, 10) }, include: { comments: true, }, }); if (!ticket) { return res.status(404).json({ error: 'Mantis ticket not found' }); } //We need to change the usernames using the usernameMap ticket.reporterUsername = usernameMap[ticket.reporterUsername] || ticket.reporterUsername; ticket.comments = ticket.comments.map((comment) => { comment.senderUsername = usernameMap[comment.senderUsername] || comment.senderUsername; return comment; }); //Ask Gemini to summarize the ticket const summary = await askGemini(`Please summarize the following Mantis ticket in the form of a markdown list of bullet points formatted as "[Date] Point" (ensure a newline between each point, format the date as DD/MM/YYY and surround it with square brackets "[]"). Then after your summary, list any outstanding actions as a markdown list in the format "[Name] Action" (again surrounding the name with square brackets). Output a heading 6 "Summary:", a newline, the summary, then two newlines, a heading 6 "Actions:" then the actions. Do not wrap the output in a code block.\n\n### Ticket Data ###\n\n` + JSON.stringify(ticket, null, 2)); res.status(200).json({ summary }); } catch (error) { console.error('Error fetching Mantis summary:', error.message); res.status(500).json({ error: 'Failed to fetch Mantis summary' }); } }); export default router;