diff --git a/apps/api/src/controllers/webhooks.ts b/apps/api/src/controllers/webhooks.ts index 0a8590985..587b05bc5 100644 --- a/apps/api/src/controllers/webhooks.ts +++ b/apps/api/src/controllers/webhooks.ts @@ -1,78 +1,94 @@ -import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; -import { checkToken } from "../lib/jwt"; -import { prisma } from "../prisma"; +import { FastifyInstance } from "fastify"; import { track } from "../lib/hog"; +import { Hook, prisma } from "../prisma"; + +// Type definitions +interface WebhookPayload { + name: string; + url: string; + type: Hook; + active: boolean; + secret: string; +} export function webhookRoutes(fastify: FastifyInstance) { // Create a new webhook - fastify.post( + fastify.post<{ Body: WebhookPayload }>( "/api/v1/webhook/create", + async (request, reply) => { + try { + const { name, url, type, active, secret } = request.body; - async (request: FastifyRequest, reply: FastifyReply) => { - const bearer = request.headers.authorization!.split(" ")[1]; - const token = checkToken(bearer); - - if (token) { - const { name, url, type, active, secret }: any = request.body; - await prisma.webhooks.create({ + const webhook = await prisma.webhooks.create({ data: { name, url, type, active, secret, - createdBy: "375f7799-5485-40ff-ba8f-0a28e0855ecf", + createdBy: "375f7799-5485-40ff-ba8f-0a28e0855ecf", // TODO: Get from authenticated user }, }); const client = track(); - - client.capture({ + await client.capture({ event: "webhook_created", - distinctId: "uuid", + distinctId: webhook.id, // Use actual webhook ID }); + await client.shutdownAsync(); - client.shutdownAsync(); - - reply.status(200).send({ message: "Hook created!", success: true }); + return reply.status(201).send({ + data: webhook, + success: true, + }); + } catch (error) { + return reply.status(400).send({ + error: + error instanceof Error ? error.message : "Failed to create webhook", + success: false, + }); } } ); // Get all webhooks - fastify.get( - "/api/v1/webhooks/all", - - async (request: FastifyRequest, reply: FastifyReply) => { - const bearer = request.headers.authorization!.split(" ")[1]; - const token = checkToken(bearer); - - if (token) { - const webhooks = await prisma.webhooks.findMany({}); + fastify.get("/api/v1/webhooks/all", async (request, reply) => { + try { + const webhooks = await prisma.webhooks.findMany(); - reply.status(200).send({ webhooks: webhooks, success: true }); - } + return reply.status(200).send({ + data: webhooks, + success: true, + }); + } catch (error) { + return reply.status(400).send({ + error: + error instanceof Error ? error.message : "Failed to fetch webhooks", + success: false, + }); } - ); + }); // Delete a webhook - - fastify.delete( + fastify.delete<{ Params: { id: string } }>( "/api/v1/admin/webhook/:id/delete", + async (request, reply) => { + try { + const { id } = request.params; - async (request: FastifyRequest, reply: FastifyReply) => { - const bearer = request.headers.authorization!.split(" ")[1]; - const token = checkToken(bearer); - - if (token) { - const { id }: any = request.params; await prisma.webhooks.delete({ - where: { - id: id, - }, + where: { id }, }); - reply.status(200).send({ success: true }); + return reply.status(200).send({ + success: true, + }); + } catch (error) { + return reply.status(400).send({ + error: + error instanceof Error ? error.message : "Failed to delete webhook", + success: false, + }); } } ); diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index be66fe11b..b2257d439 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -7,14 +7,15 @@ import fs from "fs"; import { exec } from "child_process"; import { track } from "./lib/hog"; import { getEmails } from "./lib/imap"; +import { checkToken } from "./lib/jwt"; import { prisma } from "./prisma"; import { registerRoutes } from "./routes"; // Ensure the directory exists -const logFilePath = './logs.log'; // Update this path to a writable location +const logFilePath = "./logs.log"; // Update this path to a writable location // Create a writable stream -const logStream = fs.createWriteStream(logFilePath, { flags: 'a' }); +const logStream = fs.createWriteStream(logFilePath, { flags: "a" }); // Initialize Fastify with logger const server: FastifyInstance = Fastify({ @@ -26,62 +27,49 @@ const server: FastifyInstance = Fastify({ }); server.register(cors, { origin: "*", - + methods: ["GET", "POST", "PUT", "DELETE"], allowedHeaders: ["Content-Type", "Authorization", "Accept"], }); -server.register(require("@fastify/swagger"), { - swagger: { - info: { - title: "Peppermint API DOCS", - description: "Peppermint swagger API", - version: "0.1.0", - }, - externalDocs: { - url: "https://swagger.io", - description: "Find more info here", - }, - mode: "static", - host: "localhost", - schemes: ["http"], - consumes: ["application/json"], - produces: ["application/json"], - tags: [ - { name: "user", description: "User related end-points" }, - { name: "code", description: "Code related end-points" }, - ], - exposeRoute: true, - definitions: { - User: { - type: "object", - required: ["id", "email"], - properties: { - id: { type: "string", format: "uuid" }, - firstName: { type: "string" }, - lastName: { type: "string" }, - email: { type: "string", format: "email" }, - }, - }, - }, - securityDefinitions: { - apiKey: { - type: "apiKey", - name: "apiKey", - in: "header", - }, - }, - }, -}); - server.register(multer.contentParser); registerRoutes(server); -server.get("/", async function (request, response) { +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 }); }); +server.addHook("preHandler", async (request, reply) => { + if (request.url.startsWith("/api/public") || request.url.startsWith("/documentation")) { + return; + } + + try { + const bearer = request.headers.authorization?.split(" ")[1]; + if (!bearer) throw new Error("No authorization token provided"); + await checkToken(bearer); + } catch (error) { + reply.status(401).send({ + error: "Authentication failed", + success: false, + }); + } +}); + const start = async () => { try { // Run prisma generate and migrate commands before starting the server diff --git a/apps/api/src/prisma.ts b/apps/api/src/prisma.ts index 14e74f0a7..8f64c2626 100644 --- a/apps/api/src/prisma.ts +++ b/apps/api/src/prisma.ts @@ -1,2 +1,3 @@ import { PrismaClient } from "@prisma/client"; export const prisma: PrismaClient = new PrismaClient(); +export type Hook = "ticket_created" | "ticket_status_changed";