www-new/lib/events.ts

252 lines
5.9 KiB
TypeScript

import fs from "fs/promises";
import path from "path";
import { parse } from "date-fns";
import matter from "gray-matter";
import { MDXRemoteSerializeResult } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize";
import {
Term,
TERMS,
isTerm,
DATE_FORMAT,
getLocalDateFromEST,
TermYear,
getTermYear,
getCurrentTermYear,
} from "@/utils";
import type { Props } from "../pages/events/[year]/[term]";
const EVENTS_PATH = path.join("content", "events");
export async function getEventYears(): Promise<number[]> {
return (await fs.readdir(EVENTS_PATH, { withFileTypes: true }))
.filter((dirent) => dirent.isDirectory())
.map((dirent) => parseInt(dirent.name))
.sort();
}
export async function getEventTermsByYear(year: number): Promise<Term[]> {
return (
await fs.readdir(path.join(EVENTS_PATH, year.toString()), {
withFileTypes: true,
})
)
.filter((dirent) => dirent.isDirectory() && isTerm(dirent.name))
.map((dirent) => dirent.name as Term)
.sort((a, b) => TERMS.indexOf(a) - TERMS.indexOf(b));
}
interface RawMetadata {
name: string;
poster?: string;
short: string;
startDate: string;
endDate?: string;
online?: boolean;
location: string;
registerLink?: string;
}
interface Metadata {
name: string;
poster?: string;
short: string;
startDate: string;
endDate?: string;
online: boolean;
location: string;
permaLink: string;
registerLink?: string;
year: number;
term: string;
slug: string;
}
export interface Event {
content: MDXRemoteSerializeResult<Record<string, unknown>>;
metadata: Metadata;
}
export async function getEventBySlug(
year: number,
term: Term,
slug: string
): Promise<Event> {
const file = await fs.readFile(
path.join(EVENTS_PATH, year.toString(), term, `${slug}.md`),
"utf-8"
);
const { content, data } = matter(file);
const raw = data as RawMetadata;
return {
content: await serialize(content),
metadata: {
...raw,
online: raw.online ?? false,
startDate: getLocalDateFromEST(
parse(raw.startDate, DATE_FORMAT, new Date())
).toString(),
// permaLink is based on the directory structure in /pages
permaLink: `/events/${year}/${term}/${slug}`,
year: year,
term: term,
slug: slug,
} as Metadata,
};
}
export async function getEventsByTerm(
year: number,
term: Term
): Promise<string[]> {
try {
return (await fs.readdir(path.join(EVENTS_PATH, year.toString(), term)))
.filter((name) => name.endsWith(".md"))
.map((name) => name.slice(0, -".md".length));
} catch {
return [];
}
}
export async function getUpcomingEvents(): Promise<Event[]> {
const terms: TermYear[] = [];
// Get events for the next two terms
for (const termYear of getTermYear()) {
if (terms.length >= 2) {
break;
}
terms.push(termYear);
}
const events: Event[] = (
await Promise.all(
terms.map(async ({ year, term }) => {
try {
const eventsInTerm = await getEventsByTerm(year, term);
return await Promise.all(
eventsInTerm.map((slug) => getEventBySlug(year, term, slug))
);
} catch (error) {
return [];
}
})
)
).flat();
return events
.filter(
(ev) =>
// use endDate if possible, else use startDate
new Date(ev.metadata.endDate ?? ev.metadata.startDate).getTime() >=
Date.now()
)
.sort((a, b) => {
return (
new Date(a.metadata.startDate).getTime() -
new Date(b.metadata.startDate).getTime()
);
});
}
export async function getAllEvents(): Promise<Event[]> {
const events: Event[] = [];
for (const year of await getEventYears()) {
for (const term of await getEventTermsByYear(year)) {
for (const slug of await getEventsByTerm(year, term)) {
events.push(await getEventBySlug(year, term, slug));
}
}
}
return events;
}
export async function getEventsPageProps({
term,
year,
}: TermYear): Promise<Props> {
const eventNames = await getEventsByTerm(year, term);
const events: Event[] = (
await Promise.all(
eventNames.map((file: string) => getEventBySlug(year, term, file))
)
).sort(
(a, b) =>
new Date(a.metadata.startDate).getTime() -
new Date(b.metadata.startDate).getTime()
);
const currentDate = Date.now();
const pastEvents = events
.filter(
(event) =>
// fallback to startDate if endDate is not set
new Date(event.metadata.endDate ?? event.metadata.startDate).getTime() <
currentDate
)
.reverse();
const futureEvents = events.filter(
// We display events that are currently going on as upcoming so they still show up homepage and other pages on the top
(event) =>
new Date(event.metadata.endDate ?? event.metadata.startDate).getTime() >=
currentDate
);
const eventYears = await getEventYears();
const minYear = eventYears[0];
const pastTerms: TermYear[] = [];
for (const current of getTermYear(
{ year, term },
{ goBackwards: true, skipCurrent: true }
)) {
if (pastTerms.length >= 2 || current.year < minYear) {
break;
}
if ((await getEventsByTerm(current.year, current.term)).length !== 0) {
pastTerms.push(current);
}
}
pastTerms.reverse();
const maxYear = eventYears[eventYears.length - 1];
const futureTerms: TermYear[] = [];
for (const current of getTermYear(
{ year, term },
{ goBackwards: false, skipCurrent: true }
)) {
if (futureTerms.length >= 2 || maxYear < current.year) {
break;
}
if ((await getEventsByTerm(current.year, current.term)).length !== 0) {
futureTerms.push(current);
}
}
const current = getCurrentTermYear();
return {
year: year,
term: term,
pastEvents: pastEvents,
futureEvents: futureEvents,
isCurrentTerm: term === current.term && year === current.year,
pastTerms: pastTerms,
futureTerms: futureTerms,
};
}