mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-23 12:43:48 -04:00
197 lines
6.2 KiB
TypeScript
197 lines
6.2 KiB
TypeScript
import * as Sentry from '@sentry/node';
|
|
import { Router } from 'express';
|
|
import type { Response } from 'express';
|
|
import { createEvents } from 'ics';
|
|
import SQL from 'sql-template-strings';
|
|
|
|
import type { LocaleDescription } from '../../locale/locales.ts';
|
|
import type { Translations } from '../../locale/translations.ts';
|
|
import { buildCalendar } from '../../src/calendar/calendar.ts';
|
|
import { Calendar, Day } from '../../src/calendar/helpers.ts';
|
|
import type { Event } from '../../src/calendar/helpers.ts';
|
|
import { handleErrorAsync, clearLinkedText } from '../../src/helpers.ts';
|
|
import { config } from '../globals.ts';
|
|
import { loadSuml, loadSumlFromBase } from '../loader.ts';
|
|
|
|
import { normalise } from './user.ts';
|
|
|
|
// TODO caching? // import { caches } from "../../src/cache";
|
|
|
|
const translations = loadSuml('translations') as Translations;
|
|
const fallbackTranslations = loadSumlFromBase('locale/_base/translations') as Translations;
|
|
|
|
const calendar = buildCalendar(process.env.BASE_URL!);
|
|
|
|
const renderEvents = (yearEvents: Record<number, Event[]>, res: Response, onlyFirstDays: boolean = false, calNameExtra: string = '') => {
|
|
const events = [];
|
|
let i = 1;
|
|
for (const year in yearEvents) {
|
|
if (!yearEvents.hasOwnProperty(year)) {
|
|
continue;
|
|
}
|
|
for (const event of yearEvents[year]) {
|
|
if (!event) {
|
|
continue;
|
|
}
|
|
const ics = event.toIcs(parseInt(year), translations, fallbackTranslations, clearLinkedText, i, onlyFirstDays, calNameExtra);
|
|
if (ics !== null) {
|
|
events.push(ics);
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (events.length === 0) {
|
|
return res.status(404).json({ error: 'Not found' });
|
|
}
|
|
|
|
createEvents(
|
|
events,
|
|
(error, value) => {
|
|
if (error) {
|
|
Sentry.captureException(error);
|
|
return res.status(500).json({ error: 'Unexpected server error' });
|
|
}
|
|
|
|
res.header('Content-type', 'text/calendar');
|
|
res.send(value);
|
|
},
|
|
);
|
|
};
|
|
|
|
const getEventName = (name: string): string => {
|
|
name = translations.calendar.events[name] || fallbackTranslations.calendar.events[name] || name;
|
|
name = name.replace(/{.*?=(.*?)}/g, '$1');
|
|
return name;
|
|
};
|
|
|
|
const buildMessage = (events: string[], locale: LocaleDescription, day: Day, link: string): string | null => {
|
|
if (events.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
let message = `[${locale.name}] ${day.toString()}\n\n${translations.calendar.banner}:\n`;
|
|
for (const event of events) {
|
|
message += ` - ${event}\n`;
|
|
}
|
|
message += `\n${link}`;
|
|
|
|
return message;
|
|
};
|
|
|
|
const eventsSummary = (day: Day, locale: LocaleDescription) => {
|
|
const eventsRaw = calendar.getCurrentYear()!.eventsByDate[day.toString()];
|
|
|
|
const link = `${locale.url}/${encodeURIComponent(config.calendar!.route!)}/${day}`;
|
|
const image = `${locale.url}/calendar/${day}.png`;
|
|
|
|
let message = null;
|
|
const events = [];
|
|
if (eventsRaw !== undefined && eventsRaw.length > 0) {
|
|
for (const event of eventsRaw) {
|
|
events.push(getEventName(event.name));
|
|
delete event.daysMemoise;
|
|
}
|
|
}
|
|
let eventsForMessage = [...events];
|
|
while (true) {
|
|
message = buildMessage(eventsForMessage, locale, day, link);
|
|
|
|
if (message === null || message.length <= 280) {
|
|
break;
|
|
} else {
|
|
eventsForMessage = eventsForMessage.slice(0, eventsForMessage.length - 1);
|
|
}
|
|
}
|
|
|
|
return {
|
|
day: day.toString(),
|
|
link,
|
|
image,
|
|
message,
|
|
events,
|
|
eventsRaw: eventsRaw || [],
|
|
};
|
|
};
|
|
|
|
const generateURIEncodedPathAlternative = (path: string): string[] => {
|
|
return [path, path.replace('@', encodeURIComponent('@'))];
|
|
};
|
|
|
|
interface ProfileRaw {
|
|
events: string;
|
|
customEvents: string;
|
|
}
|
|
|
|
const router = Router();
|
|
|
|
router.get('/calendar/today', handleErrorAsync(async (req, res) => {
|
|
return res.json(eventsSummary(
|
|
Day.today(),
|
|
req.locales[config.locale],
|
|
));
|
|
}));
|
|
|
|
router.get('/calendar/:year-:month-:day', handleErrorAsync(async (req, res) => {
|
|
return res.json(eventsSummary(
|
|
new Day(parseInt(req.params.year), parseInt(req.params.month), parseInt(req.params.day)),
|
|
req.locales[config.locale],
|
|
));
|
|
}));
|
|
|
|
const routeBase = `/queer-calendar-${config.locale}`;
|
|
|
|
router.get(`${routeBase}.ics`, handleErrorAsync(async (req, res) => {
|
|
const events: Record<number, Event[]> = {};
|
|
for (const year of calendar.getAllYears()) {
|
|
events[year.year] = year.events;
|
|
}
|
|
|
|
renderEvents(events, res, req.query.only_first_days !== undefined);
|
|
}));
|
|
|
|
router.get(generateURIEncodedPathAlternative(`${routeBase}-@:username.ics`), handleErrorAsync(async (req, res) => {
|
|
const profiles = await req.db.all<Pick<ProfileRaw, 'events' | 'customEvents'>>(SQL`
|
|
SELECT events, customEvents FROM profiles p
|
|
LEFT JOIN users u ON p.userId = u.id
|
|
WHERE u.usernameNorm = ${normalise(req.params.username)}
|
|
AND p.locale = ${config.locale}
|
|
`);
|
|
|
|
if (!profiles.length) {
|
|
return res.status(404).json({ error: 'Not found' });
|
|
}
|
|
|
|
const events = Calendar.generatePersonalCalendarEvents([
|
|
...JSON.parse(profiles[0].events),
|
|
...JSON.parse(profiles[0].customEvents),
|
|
], calendar.getCurrentYear()!);
|
|
|
|
const groupedEvents: Record<number, Event[]> = {};
|
|
for (const year of calendar.getAllYears()) {
|
|
groupedEvents[year.year] = events;
|
|
}
|
|
|
|
renderEvents(groupedEvents, res, req.query.only_first_days !== undefined, ` (@${req.params.username})`);
|
|
}));
|
|
|
|
router.get(`${routeBase}-:year-:uuid.ics`, handleErrorAsync(async (req, res) => {
|
|
const year = calendar.getYear(parseInt(req.params.year));
|
|
if (!year) {
|
|
return res.status(404).json({ error: 'Not found' });
|
|
}
|
|
|
|
renderEvents({ [year.year]: [year.eventsByUuid[req.params.uuid]] }, res);
|
|
}));
|
|
|
|
router.get(`${routeBase}-:year.ics`, handleErrorAsync(async (req, res) => {
|
|
const year = calendar.getYear(parseInt(req.params.year));
|
|
if (!year) {
|
|
return res.status(404).json({ error: 'Not found' });
|
|
}
|
|
|
|
renderEvents({ [year.year]: year.events }, res);
|
|
}));
|
|
|
|
export default router;
|