Skip to main content
Glama

mcptix

by ownlytics
storage.js15.2 kB
/** * Storage module for mcptix * Handles saving and loading tickets from the server */ // Queue of changes to be applied let changeQueue = []; // Current tickets data let currentTickets = null; /** * Queue a change to be applied * @param {string} type - The type of change (add, update, delete, move) * @param {object} ticket - The ticket to be changed */ function queueChange(type, ticket) { changeQueue.push({ type, ticket }); } /** * Apply all queued changes * @returns {Promise} A promise that resolves when all changes are applied */ function applyChanges() { if (changeQueue.length === 0) { return Promise.resolve(); } // Keep track of the last ticket for returning let lastTicket = null; let lastOperation = null; // Process all changes locally first const promises = changeQueue.map(change => { const { type, ticket } = change; // Keep track of the last ticket for returning lastTicket = ticket; lastOperation = type; // Apply the change to the local data switch (type) { case 'add': addTicket(ticket); // Send to server return createTicketOnServer(ticket); case 'update': updateTicket(ticket); // Send to server return updateTicketOnServer(ticket); case 'delete': deleteTicket(ticket.id); // Send to server return deleteTicketOnServer(ticket.id); case 'move': updateTicket(ticket); // Move is handled the same as update // Send to server return updateTicketOnServer(ticket); case 'reorder': updateTicket(ticket); // Update the ticket with new order_value // Send to server return reorderTicketOnServer(ticket.id, ticket.order_value); } }); // Clear the queue changeQueue = []; // Wait for all server operations to complete return Promise.all(promises) .then(results => { // Save to localStorage as a backup saveToLocalStorage(); // Return the updated ticket data from the server if available if (results && results.length > 0 && lastOperation !== 'delete') { const lastResult = results[results.length - 1]; if (lastResult && lastResult.id) { // For add/update operations, the server returns the updated ticket return getTicketById(lastResult.id); } } return lastTicket; }) .catch(error => { console.error('Error applying changes:', error); // Still save to localStorage even if server operations fail saveToLocalStorage(); return lastTicket; }); } /** * Save the current tickets to localStorage */ function saveToLocalStorage() { localStorage.setItem('mcptix-tickets-backup', JSON.stringify(currentTickets)); } /** * Create a ticket on the server * @param {object} ticket - The ticket to create * @returns {Promise} A promise that resolves when the ticket is created */ function createTicketOnServer(ticket) { return fetch('/api/tickets', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ title: ticket.title, description: ticket.description || '', priority: ticket.priority || 'medium', status: ticket.status || 'backlog', complexity_metadata: ticket.complexity_metadata, }), }) .then(response => { if (!response.ok) { throw new Error('Failed to create ticket'); } return response.json(); }) .then(data => { // If the server returned the full ticket, update our local copy if (data && data.id && data.complexity_metadata) { // Find the ticket in our local data for (const column of currentTickets.columns) { const index = column.tickets.findIndex(t => t.id === data.id); if (index !== -1) { // Update the ticket with the server data column.tickets[index] = data; break; } } } return data; }); } /** * Update a ticket on the server * @param {object} ticket - The ticket to update * @returns {Promise} A promise that resolves when the ticket is updated */ function updateTicketOnServer(ticket) { return fetch(`/api/tickets/${ticket.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ title: ticket.title, description: ticket.description, priority: ticket.priority, status: ticket.status, complexity_metadata: ticket.complexity_metadata, }), }) .then(response => { if (!response.ok) { throw new Error('Failed to update ticket'); } return response.json(); }) .then(data => { // If the server returned the full ticket, update our local copy if (data && data.id && data.complexity_metadata) { // Find the ticket in our local data for (const column of currentTickets.columns) { const index = column.tickets.findIndex(t => t.id === data.id); if (index !== -1) { // Update the ticket with the server data column.tickets[index] = data; break; } } } return data; }); } /** * Delete a ticket on the server * @param {string} ticketId - The ID of the ticket to delete * @returns {Promise} A promise that resolves when the ticket is deleted */ function deleteTicketOnServer(ticketId) { return fetch(`/api/tickets/${ticketId}`, { method: 'DELETE', }).then(response => { if (!response.ok) { throw new Error('Failed to delete ticket'); } return response.json(); }); } /** * Add a ticket to the current tickets * @param {object} ticket - The ticket to add */ function addTicket(ticket) { // Find the column for this ticket const column = currentTickets.columns.find(col => col.id === ticket.status); if (column) { // Add the ticket to the column column.tickets.push(ticket); } } /** * Update a ticket in the current tickets * @param {object} ticket - The ticket to update */ function updateTicket(ticket) { // Find the ticket in all columns for (const column of currentTickets.columns) { const index = column.tickets.findIndex(t => t.id === ticket.id); if (index !== -1) { // Keep track of the original order_value if not specified in the update if (ticket.order_value === undefined && column.tickets[index].order_value !== undefined) { ticket.order_value = column.tickets[index].order_value; } // If the status has changed, move the ticket to the new column if (column.id !== ticket.status) { // Remove from current column column.tickets.splice(index, 1); // Add to new column const newColumn = currentTickets.columns.find(col => col.id === ticket.status); if (newColumn) { // If no order_value is specified for a moved ticket, assign a high value to place it at the top if (ticket.order_value === undefined) { // Find the highest order value in the new column const highestOrder = newColumn.tickets.length > 0 ? Math.max(...newColumn.tickets.map(t => t.order_value || 0)) : 0; ticket.order_value = highestOrder + 1000; } newColumn.tickets.push(ticket); } } else { // Update in place column.tickets[index] = ticket; } return; } } } /** * Delete a ticket from the current tickets * @param {string} ticketId - The ID of the ticket to delete */ function deleteTicket(ticketId) { // Find the ticket in all columns for (const column of currentTickets.columns) { const index = column.tickets.findIndex(t => t.id === ticketId); if (index !== -1) { // Remove from column column.tickets.splice(index, 1); return; } } } /** * Save tickets to the server * @param {object} tickets - The tickets to save * @returns {Promise} A promise that resolves when the tickets are saved */ function saveTickets(tickets) { // Save to localStorage as a backup localStorage.setItem('mcptix-tickets-backup', JSON.stringify(tickets)); // In the new API, we don't save all tickets at once // Instead, we use individual endpoints for each operation // This function is kept for compatibility with the original code return Promise.resolve(tickets); } /** * Load tickets from the server * @returns {Promise} A promise that resolves with the loaded tickets */ function loadTickets() { // If we already have tickets, return them if (currentTickets) { return Promise.resolve(currentTickets); } // Load from the server return fetch('/api/tickets') .then(response => { if (!response.ok) { throw new Error('Failed to load tickets'); } return response.json(); }) .then(data => { // Transform the API response to match the expected format const tickets = { columns: [ { id: 'backlog', name: 'Backlog', tickets: [], }, { id: 'up-next', name: 'Up Next', tickets: [], }, { id: 'in-progress', name: 'In Progress', tickets: [], }, { id: 'in-review', name: 'In Review', tickets: [], }, { id: 'completed', name: 'Completed', tickets: [], }, ], }; // Distribute tickets to their respective columns if (data.tickets && Array.isArray(data.tickets)) { data.tickets.forEach(ticket => { // Ensure complexity_metadata is properly initialized if missing if (!ticket.complexity_metadata) { ticket.complexity_metadata = {}; } const column = tickets.columns.find(col => col.id === ticket.status); if (column) { column.tickets.push(ticket); } }); } currentTickets = tickets; return tickets; }) .catch(error => { console.error('Error loading tickets:', error); // Try to load from localStorage as a fallback try { const ticketsJson = localStorage.getItem('mcptix-tickets-backup'); if (ticketsJson) { currentTickets = JSON.parse(ticketsJson); return currentTickets; } } catch (e) { console.error('Error loading tickets from localStorage:', e); } // Create empty tickets structure currentTickets = { columns: [ { id: 'backlog', name: 'Backlog', tickets: [], }, { id: 'up-next', name: 'Up Next', tickets: [], }, { id: 'in-progress', name: 'In Progress', tickets: [], }, { id: 'in-review', name: 'In Review', tickets: [], }, { id: 'completed', name: 'Completed', tickets: [], }, ], }; return currentTickets; }); } /** * Get a ticket by ID * @param {string} ticketId - The ID of the ticket to get * @returns {object|null} The ticket, or null if not found */ function getTicketById(ticketId) { if (!currentTickets) { return null; } // Find the ticket in all columns for (const column of currentTickets.columns) { const ticket = column.tickets.find(t => t.id === ticketId); if (ticket) { // Ensure complexity_metadata is properly initialized if missing if (!ticket.complexity_metadata) { ticket.complexity_metadata = {}; } return ticket; } } // If not found in local cache, try to fetch from server return fetch(`/api/tickets/${ticketId}`) .then(response => { if (!response.ok) { return null; } return response.json(); }) .then(ticket => { // Ensure complexity_metadata is properly initialized if missing if (ticket && !ticket.complexity_metadata) { ticket.complexity_metadata = {}; } return ticket; }) .catch(() => { return null; }); } /** * Add a comment to a ticket * @param {string} ticketId - The ID of the ticket to add the comment to * @param {object} comment - The comment to add * @returns {Promise} A promise that resolves when the comment is added */ function addComment(ticketId, comment) { return fetch(`/api/tickets/${ticketId}/comments`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ content: comment.content, author: comment.author || 'developer', }), }).then(response => { if (!response.ok) { throw new Error('Failed to add comment'); } return response.json(); }); } /** * Get comments for a ticket * @param {string} ticketId - The ID of the ticket to get comments for * @returns {Promise} A promise that resolves with the comments */ function getComments(ticketId) { return fetch(`/api/tickets/${ticketId}/comments`) .then(response => { if (!response.ok) { throw new Error('Failed to get comments'); } return response.json(); }) .then(data => { return data.comments || []; }); } /** * Reorder a ticket within its current column * @param {string} ticketId - The ID of the ticket to reorder * @param {number} newOrderValue - The new order value for the ticket * @returns {Promise} A promise that resolves when the ticket is reordered */ function reorderTicketOnServer(ticketId, newOrderValue) { return fetch(`/api/tickets/${ticketId}/reorder`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ order_value: newOrderValue, }), }).then(response => { if (!response.ok) { throw new Error('Failed to reorder ticket'); } return response.json(); }); } /** * Queue a reorder operation for a ticket * @param {string} ticketId - The ID of the ticket to reorder * @param {number} newOrderValue - The new order value for the ticket * @returns {Promise} A promise that resolves when the ticket is reordered */ function reorderTicket(ticketId, newOrderValue) { // Find the ticket in the cache let ticketToReorder = null; for (const column of currentTickets.columns) { const ticket = column.tickets.find(t => t.id === ticketId); if (ticket) { ticketToReorder = { ...ticket }; break; } } if (!ticketToReorder) return Promise.resolve(); // Update the ticket order value ticketToReorder.order_value = newOrderValue; // Queue the change queueChange('reorder', ticketToReorder); // Apply the changes and re-render return applyChanges(); } // Export the module functions export const Storage = { queueChange, applyChanges, saveTickets, loadTickets, getTicketById, addComment, getComments, reorderTicket, };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ownlytics/mcptix'

If you have feedback or need assistance with the MCP directory API, please join our Discord server