/** * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import { blockedIps } from "../cache/blockedIps.js"; import { database } from "../db/database.js"; import { announceOnBluesky } from "../modules/announceOnBluesky.js"; import { announceOnDiscord } from "../modules/announceOnDiscord.js"; import { announceOnFacebook } from "../modules/announceOnFacebook.js"; import { announceOnMastodon } from "../modules/announceOnMastodon.js"; import { announceOnReddit } from "../modules/announceOnReddit.js"; import { announceOnThreads } from "../modules/announceOnThreads.js"; import { announceOnTwitter } from "../modules/announceOnTwitter.js"; import { generateAnnouncements } from "../modules/generateAnnouncements.js"; import { getIpFromRequest } from "../modules/getIpFromRequest.js"; import { isAnnouncementType, isValidString } from "../utils/typeguards.js"; import type { FastifyPluginAsync } from "fastify"; const oneDay = 24 * 60 * 60 * 1000; /** * Mounts the entry routes for the application. These routes * should not require CORS, as they are used by external services * such as our uptime monitor. * @param server - The Fastify server instance. */ export const announcementRoutes: FastifyPluginAsync = async(server) => { server.get("/announcements", async(_request, reply) => { const announcements = await database.getInstance().announcements.findMany({ orderBy: { createdAt: "desc", }, take: 10, }); return await reply. status(200). type("application/json"). send( announcements.map((announcement) => { return { content: announcement.content, createdAt: announcement.createdAt, title: announcement.title, type: announcement.type, }; }), ); }); // eslint-disable-next-line @typescript-eslint/naming-convention -- Fastify requires Body instead of body. server.post<{ Body: { content: string; type: string } }>( "/announcement", // eslint-disable-next-line max-statements -- This is a long function. async(request, reply) => { const token = request.headers.authorization; if (token === undefined || token !== process.env.ANNOUNCEMENT_TOKEN) { blockedIps.push({ ip: getIpFromRequest(request), ttl: new Date(Date.now() + oneDay), }); return await reply.status(401).send({ error: // eslint-disable-next-line stylistic/max-len -- Big boi string. "This endpoint requires a special auth token. If you believe you should have access, please contact Naomi. To protect our services, your IP has been blocked from all routes for 24 hours.", }); } const { content, type } = request.body; if (!isValidString(content) || !isValidString(type)) { return await reply.status(400).send({ error: "Missing required fields.", }); } if (!isAnnouncementType(type)) { return await reply.status(400).send({ error: `Invalid announcement type. Available types: products, community, company.`, }); } const announcement = await generateAnnouncements(content); if (announcement === null) { return await reply.status(201).send({ message: `Failed to generate announcements.`, }); } const { bluesky, discord, facebook, mastodon, reddit, threads, twitter, } = announcement.response; const { title: discordTitle, content: discordContent } = discord; const { title: redditTitle, content: redditContent } = reddit; await database.getInstance().announcements.create({ data: { content: discordContent, title: discordTitle, type: type, }, }); const discordPost = await announceOnDiscord( discordTitle, discordContent, type, ); const redditPost = await announceOnReddit( redditTitle, redditContent, type, ); const blueskyPost = await announceOnBluesky(bluesky); const twitterPost = await announceOnTwitter(twitter); const facebookPost = await announceOnFacebook(facebook); const mastodonPost = await announceOnMastodon(mastodon); const threadsPost = await announceOnThreads(threads); return await reply.status(201).send({ cost: announcement.cost, message: `Announcement processed. Discord: ${discordPost}, Reddit: ${redditPost}, Bluesky: ${blueskyPost}, Twitter: ${twitterPost}, Facebook: ${facebookPost}, Mastodon: ${mastodonPost}, Threads: ${threadsPost}`, rawPost: announcement.response, }); }, ); };