import fs from "fs/promises"; import path from "path"; import { parse } from "date-fns"; import matter from "gray-matter"; import truncateMarkdown from "markdown-truncate"; import { MDXRemoteSerializeResult } from "next-mdx-remote"; import { serialize } from "next-mdx-remote/serialize"; import { isTerm, Term, TERMS, DATE_FORMAT, getLocalDateFromEST, TermYear, getTermYear, getCurrentTermYear, } from "@/utils"; export const NEWS_PATH = path.join("content", "news"); export interface Metadata { author: string; date: string; permalink: string; } export interface News { content: MDXRemoteSerializeResult; metadata: Metadata; } export async function getNewsYears(): Promise { return (await fs.readdir(NEWS_PATH, { withFileTypes: true })) .filter((dirent) => dirent.isDirectory()) .map((dirent) => dirent.name) .sort(); } export async function getNewsTermsByYear(year: string): Promise { return ( await fs.readdir(path.join(NEWS_PATH, year), { withFileTypes: true, }) ) .filter((dirent) => dirent.isDirectory() && isTerm(dirent.name)) .map((dirent) => dirent.name as Term) .sort((a, b) => TERMS.indexOf(a) - TERMS.indexOf(b)); } export async function getNewsDateByTerm( year: string, term: Term ): Promise { return (await getNewsByTerm(year, term)).map( (news) => news.split("-").slice(0, 3).join("-") // retrieves date from filename ); } export async function getNewsByTerm( year: string, term: Term ): Promise { return ( await fs.readdir(path.join(NEWS_PATH, year, term), { withFileTypes: true, }) ) .filter((dirent) => dirent.isFile() && dirent.name.endsWith(".md")) .map((dirent) => dirent.name.slice(0, -".md".length)); } export async function getNewsBySlug( year: string, term: Term, slug: string, shortened = false ): Promise { const raw = await fs.readFile( path.join(NEWS_PATH, year, term, `${slug}.md`), "utf-8" ); const { content: rawContent, data: metadata } = matter(raw); const slugDate = slug.split("-").slice(0, 3).join("-"); const content: string = shortened ? truncateMarkdown(rawContent, { limit: 150, ellipsis: true, }) : rawContent; return { content: await serialize(content), metadata: { ...metadata, date: getLocalDateFromEST( parse(metadata.date as string, DATE_FORMAT, new Date()) ).toString(), permalink: `/news/${year}/${term}/${slugDate}`, } as Metadata, }; } export async function getRecentNews(): Promise { const terms: TermYear[] = []; // Get news for the last two terms for (const termYear of getTermYear(getCurrentTermYear(), { goBackwards: true, })) { if (terms.length >= 2) { break; } terms.push(termYear); } const news: News[] = ( await Promise.all( terms.map(async ({ year, term }) => { try { const newsInTerm = await getNewsByTerm(year.toString(), term); return await Promise.all( newsInTerm.map((slug) => { return getNewsBySlug(year.toString(), term, slug, true); }) ); } catch (error) { return []; } }) ) ).flat(); return news.sort((a, b) => { return ( new Date(b.metadata.date).getTime() - new Date(a.metadata.date).getTime() ); }); }