/** * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import fastify from "fastify"; import fastifyRawBody from "fastify-raw-body"; import { applicationData } from "../config/applicationData.js"; import { auth } from "../modules/auth.js"; import { sendDiscord } from "../modules/discord.js"; import { sendMail } from "../modules/sendMail.js"; import { validateWebhook } from "../modules/validateWebhook.js"; import { errorSchema } from "../schemas/errorSchema.js"; import { logSchema } from "../schemas/logSchema.js"; import { uptimeSchema } from "../schemas/uptimeSchema.js"; import type { Entitlement } from "../interfaces/entitlement.js"; import type { Error } from "../interfaces/error.js"; import type { Log } from "../interfaces/log.js"; import type { Uptime } from "../interfaces/uptime.js"; const html = ` Rosalia Nightsong

Rosalia Nightsong

Rosalia

A basic web server that allows us to pipe logs and errors from our applications into our inbox.

Links

Source Code

Documentation

Support

`; /** * Starts up the server to receive events. */ // eslint-disable-next-line max-lines-per-function -- This function is long because it is setting up a server. export const instantiateServer = (): void => { try { const server = fastify({ logger: false, }); server.register(fastifyRawBody, { encoding: "utf8", field: "rawBody", global: false, runFirst: true, }); server.get("/", (_request, response) => { response.header("Content-Type", "text/html"); response.send(html); }); // eslint-disable-next-line @typescript-eslint/naming-convention -- Body must be capitalised for Fastify. server.post<{ Body: Log }>("/log", logSchema, async(request, response) => { if (!auth(request)) { await response.status(401).send({ success: false }); return; } const { application, level, message } = request.body; await sendMail(`[${level}]: ${application}`, message); await sendDiscord(`[${level}]: ${application}`, message); await response.status(200).send({ success: true }); }); // eslint-disable-next-line @typescript-eslint/naming-convention -- Body must be capitalised for Fastify. server.post<{ Body: Error }>( "/error", errorSchema, async(request, response) => { if (!auth(request)) { await response.status(401).send({ success: false }); return; } const { application, context, stack, message } = request.body; await sendMail( `[ERROR]: ${context} - ${application}`, `${message}\n\n${stack}`, ); await sendDiscord( `[ERROR]: ${context} - ${application}`, `${message}\n\n\`\`\`\n${stack}\n\`\`\``, ); await response.status(200).send({ success: true }); }, ); // eslint-disable-next-line @typescript-eslint/naming-convention -- Body must be capitalised for Fastify. server.post<{ Body: Uptime }>( "/uptime", uptimeSchema, async(request, response) => { if (!auth(request)) { await response.status(401).send({ success: false }); return; } const { application, message } = request.body; await sendMail(`[UPTIME]: ${application}`, message); await sendDiscord(`[UPTIME]: ${application}`, message); await response.status(200).send({ success: true }); }, ); // eslint-disable-next-line @typescript-eslint/naming-convention -- Body must be capitalised for Fastify. server.post<{ Body: Entitlement }>( "/entitlement", { config: { rawBody: true } }, async(request, response) => { if (!validateWebhook(request)) { await response.status(401).send({ success: false }); void sendDiscord( "[NOTIFICATION]: Entitlement Event", "An invalid webhook signature was received.", ); void sendMail( "[NOTIFICATION]: Entitlement Event", "An invalid webhook signature was received.", ); return; } const { type } = request.body; if (type === 0) { await response.status(204).send(); void sendDiscord( "[NOTIFICATION]: Entitlement Event", "Received a ping from Discord.", ); void sendMail( "[NOTIFICATION]: Entitlement Event", "Received a ping from Discord.", ); return; } await response.status(204).send(); const { application_id: applicationId, event } = request.body; const { user_id: userId, guild_id: guildId, ends_at: endsAt, } = event.data; const appInfo = applicationData[applicationId]; await sendDiscord( `[ENTITLEMENT]: ${appInfo?.name ?? applicationId}`, `Entitlement purchased!\n- **User ID**: ${userId}\n- **Guild ID**: ${guildId}\n- **Ends At**: ${endsAt}`, ); await sendMail( `[ENTITLEMENT]: ${appInfo?.name ?? applicationId}`, `Entitlement purchased!\n- **User ID**: ${userId}\n- **Guild ID**: ${guildId}\n- **Ends At**: ${endsAt}`, ); }, ); server.listen({ port: 5003 }, (error) => { const application = "Alert Server"; if (error) { const { message, stack } = error; const context = "Server Startup"; void sendMail( `[ERROR]: ${context} - ${application}`, `${message}\n\n${String(stack)}`, ); void sendDiscord( `[ERROR]: ${context} - ${application}`, `${message}\n\n\`\`\`\n${String(stack)}\n\`\`\``, ); return; } const level = "debug"; const message = `Server listening on port 5003.`; void sendMail(`[${level}]: ${application}`, message); void sendDiscord(`[${level}]: ${application}`, message); }); } catch (error) { const application = "Alert Server"; const context = "Server Startup"; // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Totally being lazy. const { message, stack } = error as Error; void sendMail( `[ERROR]: ${context} - ${application}`, `${message}\n\n${stack}`, ); void sendDiscord( `[ERROR]: ${context} - ${application}`, `${message}\n\n\`\`\`\n${stack}\n\`\`\``, ); } };