359 lines
No EOL
9.7 KiB
JavaScript
359 lines
No EOL
9.7 KiB
JavaScript
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 SuperJSON from 'superjson';
|
|
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' });
|
|
}
|
|
});
|
|
|
|
export default router; |