import fs from "fs/promises"; import path from "path"; import matter from "gray-matter"; import { MDXRemoteSerializeResult } from "next-mdx-remote"; import { serialize } from "next-mdx-remote/serialize"; import type { Props } from "../pages/events/[year]/[term]/index"; const EVENTS_PATH = path.join("content", "events"); export const TERMS = ["winter", "spring", "fall"]; export async function getEventYears(): Promise { return (await fs.readdir(EVENTS_PATH, { withFileTypes: true })) .filter((dirent) => dirent.isDirectory()) .map((dirent) => dirent.name) .sort(); } export async function getEventTermsByYear(year: string): Promise { return ( await fs.readdir(path.join(EVENTS_PATH, year), { withFileTypes: true }) ) .filter((dirent) => dirent.isDirectory() && TERMS.includes(dirent.name)) .map((dirent) => dirent.name) .sort((a, b) => TERMS.indexOf(a) - TERMS.indexOf(b)); } interface Metadata { name: string; short: string; date: string; online: boolean; location: string; } export interface Event { content: MDXRemoteSerializeResult>; metadata: Metadata; } export async function getEventBySlug( year: string, term: string, slug: string ): Promise { const raw = await fs.readFile( path.join(EVENTS_PATH, year, term, `${slug}.md`), "utf-8" ); const { content, data: metadata } = matter(raw); return { content: await serialize(content), metadata: metadata as Metadata, }; } export async function getEventsByTerm( year: string, term: string ): Promise { try { return (await fs.readdir(path.join(EVENTS_PATH, year, term))) .filter((name) => name.endsWith(".md")) .map((name) => name.slice(0, -".md".length)); } catch { return []; } } export async function getUpcomingEvents(): Promise { const today = new Date(); const currentYear = today.getFullYear(); const currentTerm = Math.trunc(today.getMonth() / 4); const nextYear = currentTerm < 2 ? currentYear : currentYear + 1; const nextTerm = (currentTerm + 1) % 3; const events: Event[] = ( await Promise.all( [ { year: currentYear.toString(), term: currentTerm }, { year: nextYear.toString(), term: nextTerm }, ].map(async ({ year, term }) => { try { const eventsInTerm = await getEventsByTerm(year, TERMS[term]); return await Promise.all( eventsInTerm.map((slug) => getEventBySlug(year, TERMS[term], slug)) ); } catch (error) { return []; } }) ) ).flat(); return events .filter((ev) => new Date(ev.metadata.date).getTime() >= Date.now()) .sort((a, b) => { return ( new Date(a.metadata.date).getTime() - new Date(b.metadata.date).getTime() ); }); } export async function getEventsPageProps({ year, term, }: { year: string; term: string; }): Promise { 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.date).getTime() - new Date(b.metadata.date).getTime() ); const pastEvents = events .filter((event) => new Date(event.metadata.date).getTime() < Date.now()) .reverse(); const futureEvents = events.filter( (event) => new Date(event.metadata.date).getTime() >= Date.now() ); const current = getCurrentTerm(); const eventYears = await getEventYears(); const minYear = eventYears[0]; const pastTerms: { year: string; term: string }[] = []; let curPastYear = year; let curPastTerm = term; while (parseInt(curPastYear) >= parseInt(minYear) && pastTerms.length < 2) { const pastTerm = getPastTerm(curPastYear, curPastTerm); curPastYear = pastTerm.year; curPastTerm = pastTerm.term; if ((await getEventsByTerm(curPastYear, curPastTerm)).length !== 0) { pastTerms.push(pastTerm); } } pastTerms.reverse(); const maxYear = eventYears[eventYears.length - 1]; const futureTerms: { year: string; term: string }[] = []; let curFutureYear = year; let curFutureTerm = term; while ( parseInt(curFutureYear) <= parseInt(maxYear) && futureTerms.length < 2 ) { const futureTerm = getFutureTerm(curFutureYear, curFutureTerm); curFutureYear = futureTerm.year; curFutureTerm = futureTerm.term; if ((await getEventsByTerm(curFutureYear, curFutureTerm)).length !== 0) { futureTerms.push(futureTerm); } } return { year: year, term: term, pastEvents: pastEvents, futureEvents: futureEvents, isCurrentTerm: term === current.term && year === current.year, pastTerms: pastTerms, futureTerms: futureTerms, }; } export function getCurrentTerm(): { year: string; term: string } { const date = new Date(); let term = ""; const year = date.getUTCFullYear().toString(); if ( new Date(`${year}-01-01 EST`).getTime() <= date.getTime() && date.getTime() <= new Date(`${year}-04-30 EST`).getTime() ) { term = "winter"; } else if ( new Date(`${year}-05-01 EST`).getTime() <= date.getTime() && date.getTime() <= new Date(`${year}-08-31 EST`).getTime() ) { term = "spring"; } else if ( new Date(`${year}-09-01 EST`).getTime() <= date.getTime() && date.getTime() <= new Date(`${year}-12-31 EST`).getTime() ) { term = "fall"; } return { year, term }; } function getPastTerm( year: string, term: string ): { year: string; term: string } { const index = TERMS.indexOf(term); if (index === -1) { throw new Error("Not a valid term"); } return index === 0 ? { year: (parseInt(year) - 1).toString(), term: TERMS[TERMS.length - 1], } : { year: year, term: TERMS[index - 1], }; } function getFutureTerm( year: string, term: string ): { year: string; term: string } { const index = TERMS.indexOf(term); if (index === -1) { throw new Error("Not a valid term"); } return index === TERMS.length - 1 ? { year: (parseInt(year) + 1).toString(), term: TERMS[0], } : { year: year, term: TERMS[index + 1], }; }