//This is a service which will download data for the latest updated Mantis tickets and store them in the database. //It will also download all the notes and attachments for each ticket. import axios from 'axios'; import { getSetting } from '../utils/settings.js'; import prisma from '../database.js'; import { logger } from '../utils/logging.js'; export async function getMantisSettings() { const MANTIS_API_KEY = await getSetting('MANTIS_API_KEY'); const MANTIS_API_ENDPOINT = await getSetting('MANTIS_API_ENDPOINT'); if (!MANTIS_API_ENDPOINT || !MANTIS_API_KEY) { throw new Error('Mantis API endpoint or key not configured in environment variables.'); } const headers = { Authorization: `${MANTIS_API_KEY}`, Accept: 'application/json', 'Content-Type': 'application/json', }; return { url: MANTIS_API_ENDPOINT, headers }; } export async function getLatestMantisTickets() { const { url, headers } = await getMantisSettings(); const ticketUrl = `${url}/issues?project_id=1&page_size=50&select=id,updated_at`; try { const response = await axios.get(ticketUrl, { headers }); return response.data.issues; } catch (error) { logger.error('Error fetching tickets data:', error); return []; } } export async function getDataForMantisTicket(ticketId) { const { url, headers } = await getMantisSettings(); // Removed notes from select, as they are fetched separately with attachments const ticketUrl = `${url}/issues/${ticketId}?select=id,summary,description,created_at,updated_at,reporter,status,severity,priority,notes`; try { const response = await axios.get(ticketUrl, { headers }); // Assuming response.data contains the issue object directly return response.data.issues && response.data.issues.length > 0 ? response.data.issues[0] : null; } catch (error) { logger.error(`Error fetching ticket data for ID ${ticketId}:`, error); throw new Error(`Failed to fetch ticket data for ID ${ticketId} from Mantis.`); } } export async function saveTicketToDatabase(ticketId) { const ticketData = await getDataForMantisTicket(ticketId); if (!ticketData) { logger.warn(`No ticket data found for ID ${ticketId}. Skipping save.`); return null; } const ticketInDb = await prisma.$transaction(async(tx) => { const reporterUsername = ticketData.reporter?.name; const ticket = await tx.mantisIssue.upsert({ where: { id: ticketId }, update: { title: ticketData.summary, description: ticketData.description, reporterUsername, status: ticketData.status.name, priority: ticketData.priority.name, severity: ticketData.severity.name, updatedAt: new Date(ticketData.updated_at), }, create: { id: ticketId, title: ticketData.summary, description: ticketData.description, reporterUsername, status: ticketData.status.name, priority: ticketData.priority.name, severity: ticketData.severity.name, createdAt: new Date(ticketData.created_at), updatedAt: new Date(ticketData.updated_at), }, }); logger.info(`Ticket ${ticketId} saved to database.`); // Process notes if (ticketData.notes && ticketData.notes.length > 0) { for (const note of ticketData.notes) { const noteReporter = note.reporter?.name || 'Unknown Reporter'; const comment = await tx.mantisComment.create({ data: { mantisIssueId: ticketId, senderUsername: noteReporter, comment: note.text, createdAt: new Date(note.created_at), }, }); // Process attachments for the note if (note.attachments && note.attachments.length > 0) { for (const attachment of note.attachments) { const attachmentData = { commentId: comment.id, filename: attachment.filename, url: '/mantis/attachment/' + ticketId + '/' + attachment.id, mimeType: attachment.content_type, size: attachment.size, uploadedAt: new Date(attachment.created_at), }; await tx.mantisAttachment.create({ data: attachmentData, }); logger.info(`Attachment ${attachment.filename} for ticket ${ticketId} saved to database.`); } } } } return ticket; }); return ticketInDb; } async function processNewMantisTickets() { logger.info('Checking for new Mantis tickets...'); const issues = await getLatestMantisTickets(); if (!issues) { logger.warn('No issues returned from getLatestMantisTickets.'); return; } //Check if the tickets exist, and if not, or if they have a newer updated_at date, add them to the download queue for (const issue of issues) { const ticketId = issue.id; const existingTicket = await prisma.mantisIssue.findUnique({ // Changed from prisma.ticket to prisma.mantisIssue where: { id: ticketId }, select: { updatedAt: true } // Only select needed field }); if (!existingTicket || new Date(issue.updated_at) > new Date(existingTicket.updatedAt)) // Changed existingTicket.updated_at to existingTicket.updatedAt { // Avoid adding duplicates to the queue if (!downloadQueue.includes(ticketId)) { downloadQueue.push(ticketId); logger.info(`Queueing ticket ${ticketId} for processing.`); } } } } async function processTicketsInQueue() { if (downloadQueue.length === 0) { logger.info('No tickets to process.'); return; } logger.info(`Processing tickets in queue: ${downloadQueue.length} tickets remaining.`); // const ticketId = downloadQueue.shift(); //Pick a random ticket from the queue const randomIndex = Math.floor(Math.random() * downloadQueue.length); const ticketId = downloadQueue[randomIndex]; downloadQueue.splice(randomIndex, 1); try { logger.info(`Processing ticket ${ticketId}...`); await saveTicketToDatabase(ticketId); logger.info(`Ticket ${ticketId} processed and saved to database.`); } catch (error) { console.log(error); logger.error(`Error processing ticket ${ticketId}:`, error); // Optionally, you can re-add the ticket to the queue for retrying later downloadQueue.push(ticketId); } } const downloadQueue = []; export async function setup() { // Initialize the download queue downloadQueue.length = 0; // Start the process of checking for new tickets processNewMantisTickets(); setInterval(processNewMantisTickets, 5 * 60 * 1000); // Check for new tickets every 5 minutes setInterval(processTicketsInQueue, 1 * 1000); // Process the queue every 10 seconds if(process.env.LOAD_ALL_MANTISES == 'true') { for (let i = 3000; i <= 5100; i++) { //Check if the ticket already exists in the database const existingTicket = await prisma.mantisIssue.findUnique({ where: { id: i }, select: { updatedAt: true } // Only select needed field }); if (!existingTicket) { downloadQueue.push(i); } } } }