From 03015760374212c5dfb5a2522aee612e6ea71a0d Mon Sep 17 00:00:00 2001 From: Jack Andrews Date: Fri, 15 Nov 2024 19:59:34 +0000 Subject: [PATCH] patch: issue deletion --- apps/api/src/controllers/ticket.ts | 105 +++++++++++++++++- apps/api/src/main.ts | 40 ++++--- .../client/components/TicketDetails/index.tsx | 19 ++-- apps/client/pages/issues/closed.tsx | 77 +++++++------ apps/client/pages/issues/index.tsx | 81 ++++++++------ apps/client/pages/issues/open.tsx | 81 ++++++++------ apps/client/pages/submit.tsx | 2 +- 7 files changed, 280 insertions(+), 125 deletions(-) diff --git a/apps/api/src/controllers/ticket.ts b/apps/api/src/controllers/ticket.ts index 240548f80..f6ce5dfc1 100644 --- a/apps/api/src/controllers/ticket.ts +++ b/apps/api/src/controllers/ticket.ts @@ -25,7 +25,6 @@ const validateEmail = (email: string) => { }; export function ticketRoutes(fastify: FastifyInstance) { - // Create a new ticket - public endpoint, no preHandler needed fastify.post( "/api/v1/ticket/create", { @@ -133,6 +132,110 @@ export function ticketRoutes(fastify: FastifyInstance) { } ); + fastify.post( + "/api/v1/ticket/public/create", + async (request: FastifyRequest, reply: FastifyReply) => { + const { + name, + company, + detail, + title, + priority, + email, + engineer, + type, + createdBy, + }: any = request.body; + + const ticket: any = await prisma.ticket.create({ + data: { + name, + title, + detail: JSON.stringify(detail), + priority: priority ? priority : "low", + email, + type: type ? type.toLowerCase() : "support", + createdBy: createdBy + ? { + id: createdBy.id, + name: createdBy.name, + role: createdBy.role, + email: createdBy.email, + } + : undefined, + client: + company !== undefined + ? { + connect: { id: company.id || company }, + } + : undefined, + fromImap: false, + assignedTo: + engineer && engineer.name !== "Unassigned" + ? { + connect: { id: engineer.id }, + } + : undefined, + isComplete: Boolean(false), + }, + }); + + if (!email && !validateEmail(email)) { + await sendTicketCreate(ticket); + } + + if (engineer && engineer.name !== "Unassigned") { + const assgined = await prisma.user.findUnique({ + where: { + id: ticket.userId, + }, + }); + + await sendAssignedEmail(assgined!.email); + + await assignedNotification(engineer.id, ticket); + } + + const webhook = await prisma.webhooks.findMany({ + where: { + type: "ticket_created", + }, + }); + + for (let i = 0; i < webhook.length; i++) { + if (webhook[i].active === true) { + const message = { + event: "ticket_created", + id: ticket.id, + title: ticket.title, + priority: ticket.priority, + email: ticket.email, + name: ticket.name, + type: ticket.type, + createdBy: ticket.createdBy, + assignedTo: ticket.assignedTo, + client: ticket.client, + }; + + await sendWebhookNotification(webhook[i], message); + } + } + + const hog = track(); + + hog.capture({ + event: "ticket_created", + distinctId: ticket.id, + }); + + reply.status(200).send({ + message: "Ticket created correctly", + success: true, + id: ticket.id, + }); + } + ); + // Get a ticket by id - requires auth fastify.get( "/api/v1/ticket/:id", diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index b057ccc2b..22e14fc0b 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -36,22 +36,26 @@ server.register(multer.contentParser); registerRoutes(server); -server.get("/", { - schema: { - tags: ['health'], // This groups the endpoint under a category - description: 'Health check endpoint', - response: { - 200: { - type: 'object', - properties: { - healthy: { type: 'boolean' } - } - } - } +server.get( + "/", + { + schema: { + tags: ["health"], // This groups the endpoint under a category + description: "Health check endpoint", + response: { + 200: { + type: "object", + properties: { + healthy: { type: "boolean" }, + }, + }, + }, + }, + }, + async function (request, response) { + response.send({ healthy: true }); } -}, async function (request, response) { - response.send({ healthy: true }); -}); +); // JWT authentication hook server.addHook("preHandler", async function (request: any, reply: any) { @@ -59,6 +63,12 @@ server.addHook("preHandler", async function (request: any, reply: any) { if (request.url === "/api/v1/auth/login" && request.method === "POST") { return true; } + if ( + request.url === "/api/v1/ticket/public/create" && + request.method === "POST" + ) { + return true; + } const bearer = request.headers.authorization!.split(" ")[1]; checkToken(bearer); } catch (err) { diff --git a/apps/client/components/TicketDetails/index.tsx b/apps/client/components/TicketDetails/index.tsx index 615931e27..3a7a46497 100644 --- a/apps/client/components/TicketDetails/index.tsx +++ b/apps/client/components/TicketDetails/index.tsx @@ -1319,17 +1319,14 @@ export default function Ticket() { - { - e.preventDefault(); - if (confirm("Are you sure you want to delete this ticket?")) { - deleteIssue(data.ticket.id); - } - }} - > - Delete Ticket - + {user.isAdmin && ( + deleteIssue(data.ticket.id)} + > + Delete Ticket + + )} )} diff --git a/apps/client/pages/issues/closed.tsx b/apps/client/pages/issues/closed.tsx index 38f410f43..0ffb4b4e2 100644 --- a/apps/client/pages/issues/closed.tsx +++ b/apps/client/pages/issues/closed.tsx @@ -84,29 +84,38 @@ export default function Tickets() { const [filterSelected, setFilterSelected] = useState(); const [selectedPriorities, setSelectedPriorities] = useState(() => { - const saved = localStorage.getItem('closed_selectedPriorities'); + const saved = localStorage.getItem("closed_selectedPriorities"); return saved ? JSON.parse(saved) : []; }); const [selectedStatuses, setSelectedStatuses] = useState(() => { - const saved = localStorage.getItem('closed_selectedStatuses'); + const saved = localStorage.getItem("closed_selectedStatuses"); return saved ? JSON.parse(saved) : []; }); const [selectedAssignees, setSelectedAssignees] = useState(() => { - const saved = localStorage.getItem('closed_selectedAssignees'); + const saved = localStorage.getItem("closed_selectedAssignees"); return saved ? JSON.parse(saved) : []; }); const [users, setUsers] = useState([]); useEffect(() => { - localStorage.setItem('closed_selectedPriorities', JSON.stringify(selectedPriorities)); + localStorage.setItem( + "closed_selectedPriorities", + JSON.stringify(selectedPriorities) + ); }, [selectedPriorities]); useEffect(() => { - localStorage.setItem('closed_selectedStatuses', JSON.stringify(selectedStatuses)); + localStorage.setItem( + "closed_selectedStatuses", + JSON.stringify(selectedStatuses) + ); }, [selectedStatuses]); useEffect(() => { - localStorage.setItem('closed_selectedAssignees', JSON.stringify(selectedAssignees)); + localStorage.setItem( + "closed_selectedAssignees", + JSON.stringify(selectedAssignees) + ); }, [selectedAssignees]); const handlePriorityToggle = (priority: string) => { @@ -715,30 +724,36 @@ export default function Tickets() { Share Link - - - { - e.preventDefault(); - if ( - confirm( - "Are you sure you want to delete this ticket?" - ) - ) { - fetch(`/api/v1/ticket/delete`, { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ id: ticket.id }), - }); - } - }} - > - Delete Ticket - + {user.isAdmin && ( + <> + + + { + e.preventDefault(); + if ( + confirm( + "Are you sure you want to delete this ticket?" + ) + ) { + fetch(`/api/v1/ticket/delete`, { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ id: ticket.id }), + }).then(() => { + refetch(); + }); + } + }} + > + Delete Ticket + + + )} ); @@ -749,7 +764,7 @@ export default function Tickets() { type="button" className="relative block w-[400px] rounded-lg border-2 border-dashed border-gray-300 p-12 text-center hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" onClick={() => { - const event = new KeyboardEvent('keydown', { key: 'c' }); + const event = new KeyboardEvent("keydown", { key: "c" }); document.dispatchEvent(event); }} > diff --git a/apps/client/pages/issues/index.tsx b/apps/client/pages/issues/index.tsx index a487ba404..e84b4a3b2 100644 --- a/apps/client/pages/issues/index.tsx +++ b/apps/client/pages/issues/index.tsx @@ -83,25 +83,34 @@ export default function Tickets() { const normal = "bg-green-100 text-green-800"; const [selectedPriorities, setSelectedPriorities] = useState(() => { - const saved = localStorage.getItem('all_selectedPriorities'); + const saved = localStorage.getItem("all_selectedPriorities"); return saved ? JSON.parse(saved) : []; }); - + const [selectedStatuses, setSelectedStatuses] = useState(() => { - const saved = localStorage.getItem('all_selectedStatuses'); + const saved = localStorage.getItem("all_selectedStatuses"); return saved ? JSON.parse(saved) : []; }); - + const [selectedAssignees, setSelectedAssignees] = useState(() => { - const saved = localStorage.getItem('all_selectedAssignees'); + const saved = localStorage.getItem("all_selectedAssignees"); return saved ? JSON.parse(saved) : []; }); // Update local storage when filters change useEffect(() => { - localStorage.setItem('all_selectedPriorities', JSON.stringify(selectedPriorities)); - localStorage.setItem('all_selectedStatuses', JSON.stringify(selectedStatuses)); - localStorage.setItem('all_selectedAssignees', JSON.stringify(selectedAssignees)); + localStorage.setItem( + "all_selectedPriorities", + JSON.stringify(selectedPriorities) + ); + localStorage.setItem( + "all_selectedStatuses", + JSON.stringify(selectedStatuses) + ); + localStorage.setItem( + "all_selectedAssignees", + JSON.stringify(selectedAssignees) + ); }, [selectedPriorities, selectedStatuses, selectedAssignees]); const [users, setUsers] = useState([]); @@ -712,32 +721,36 @@ export default function Tickets() { Share Link - - - { - e.preventDefault(); - if ( - confirm( - "Are you sure you want to delete this ticket?" - ) - ) { - fetch(`/api/v1/ticket/delete`, { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ id: ticket.id }), - }).then(() => { - refetch(); - }); - } - }} - > - Delete Ticket - + {user.isAdmin && ( + <> + + + { + e.preventDefault(); + if ( + confirm( + "Are you sure you want to delete this ticket?" + ) + ) { + fetch(`/api/v1/ticket/delete`, { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ id: ticket.id }), + }).then(() => { + refetch(); + }); + } + }} + > + Delete Ticket + + + )} ); diff --git a/apps/client/pages/issues/open.tsx b/apps/client/pages/issues/open.tsx index 6604e0ab2..260776d25 100644 --- a/apps/client/pages/issues/open.tsx +++ b/apps/client/pages/issues/open.tsx @@ -84,38 +84,47 @@ export default function Tickets() { const [filterSelected, setFilterSelected] = useState(); const [selectedPriorities, setSelectedPriorities] = useState(() => { - const saved = localStorage.getItem('open_selectedPriorities'); + const saved = localStorage.getItem("open_selectedPriorities"); return saved ? JSON.parse(saved) : []; }); const [selectedStatuses, setSelectedStatuses] = useState(() => { - const saved = localStorage.getItem('open_selectedStatuses'); + const saved = localStorage.getItem("open_selectedStatuses"); return saved ? JSON.parse(saved) : []; }); const [selectedAssignees, setSelectedAssignees] = useState(() => { - const saved = localStorage.getItem('open_selectedAssignees'); + const saved = localStorage.getItem("open_selectedAssignees"); return saved ? JSON.parse(saved) : []; }); const [users, setUsers] = useState([]); useEffect(() => { - localStorage.setItem('open_selectedPriorities', JSON.stringify(selectedPriorities)); + localStorage.setItem( + "open_selectedPriorities", + JSON.stringify(selectedPriorities) + ); }, [selectedPriorities]); useEffect(() => { - localStorage.setItem('open_selectedStatuses', JSON.stringify(selectedStatuses)); + localStorage.setItem( + "open_selectedStatuses", + JSON.stringify(selectedStatuses) + ); }, [selectedStatuses]); useEffect(() => { - localStorage.setItem('open_selectedAssignees', JSON.stringify(selectedAssignees)); + localStorage.setItem( + "open_selectedAssignees", + JSON.stringify(selectedAssignees) + ); }, [selectedAssignees]); const clearAllFilters = () => { setSelectedPriorities([]); setSelectedStatuses([]); setSelectedAssignees([]); - localStorage.removeItem('open_selectedPriorities'); - localStorage.removeItem('open_selectedStatuses'); - localStorage.removeItem('open_selectedAssignees'); + localStorage.removeItem("open_selectedPriorities"); + localStorage.removeItem("open_selectedStatuses"); + localStorage.removeItem("open_selectedAssignees"); }; const handlePriorityToggle = (priority: string) => { @@ -722,28 +731,36 @@ export default function Tickets() { - { - e.preventDefault(); - if ( - confirm( - "Are you sure you want to delete this ticket?" - ) - ) { - fetch(`/api/v1/ticket/delete`, { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ id: ticket.id }), - }); - } - }} - > - Delete Ticket - + {user.isAdmin && ( + <> + + + { + e.preventDefault(); + if ( + confirm( + "Are you sure you want to delete this ticket?" + ) + ) { + fetch(`/api/v1/ticket/delete`, { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ id: ticket.id }), + }).then(() => { + refetch(); + }); + } + }} + > + Delete Ticket + + + )} ); @@ -754,7 +771,7 @@ export default function Tickets() { type="button" className="relative block w-[400px] rounded-lg border-2 border-dashed border-gray-300 p-12 text-center hover:border-gray-400 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" onClick={() => { - const event = new KeyboardEvent('keydown', { key: 'c' }); + const event = new KeyboardEvent("keydown", { key: "c" }); document.dispatchEvent(event); }} > diff --git a/apps/client/pages/submit.tsx b/apps/client/pages/submit.tsx index 2be97bfed..6cbbe0400 100644 --- a/apps/client/pages/submit.tsx +++ b/apps/client/pages/submit.tsx @@ -50,7 +50,7 @@ export default function ClientTicketNew() { async function submitTicket() { setIsLoading(true); - await fetch(`/api/v1/ticket/create`, { + await fetch(`/api/v1/ticket/public/create`, { method: "POST", headers: { "content-type": "application/json",