generated from nhcarrigan/template
135 lines
4.7 KiB
TypeScript
135 lines
4.7 KiB
TypeScript
/**
|
|
* @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,
|
|
});
|
|
},
|
|
);
|
|
};
|