From a7eac7b81e38aec1d24693c8d6d92452821fe553 Mon Sep 17 00:00:00 2001 From: Aditya Thakral Date: Sun, 7 Aug 2022 16:35:56 -0700 Subject: [PATCH] Create members api --- .drone.yml | 8 ++ components/EventCard.tsx | 2 +- components/MiniEventCard.tsx | 2 +- components/WarningHeader.tsx | 3 +- lib/events.ts | 176 ++++++++----------------- lib/members.ts | 2 +- lib/news.ts | 39 ++++-- lib/team.ts | 89 +++++++------ package-lock.json | 70 ++++++++-- package.json | 6 +- pages/about/members.tsx | 7 +- pages/about/team.tsx | 16 +-- pages/events/[year]/[term]/[event].tsx | 8 +- pages/events/[year]/[term]/index.tsx | 25 ++-- pages/events/[year]/index.tsx | 4 +- pages/events/archive.tsx | 2 +- pages/events/index.tsx | 5 +- scripts/api/members.ts | 13 ++ scripts/change-dates.ts | 29 ++-- scripts/generate-calendar.ts | 2 +- utils.ts | 65 +++++++++ 21 files changed, 338 insertions(+), 235 deletions(-) create mode 100644 scripts/api/members.ts diff --git a/.drone.yml b/.drone.yml index e24c796b..4763b933 100644 --- a/.drone.yml +++ b/.drone.yml @@ -37,6 +37,13 @@ steps: commands: - npm run build:calendar +- name: generate-api + image: node:16 + depends_on: + - install-deps + commands: + - npm run build:api + - name: build image: node:16 depends_on: @@ -47,6 +54,7 @@ steps: - name: export image: node:16 depends_on: + - generate-api - generate-calendar - build commands: diff --git a/components/EventCard.tsx b/components/EventCard.tsx index 1cdaacef..0ee4e268 100644 --- a/components/EventCard.tsx +++ b/components/EventCard.tsx @@ -19,7 +19,7 @@ interface EventCardProps { permaLink: string; showDescription?: boolean; children: ReactNode; - year: string; + year: number; term: string; slug: string; titleLinked: boolean; diff --git a/components/MiniEventCard.tsx b/components/MiniEventCard.tsx index c589286e..1e8eb2e2 100644 --- a/components/MiniEventCard.tsx +++ b/components/MiniEventCard.tsx @@ -14,7 +14,7 @@ interface MiniEventCardProps { startDate: Date; endDate?: Date; background: "dark-bg" | "normal-bg"; - year: string; + year: number; term: string; slug: string; } diff --git a/components/WarningHeader.tsx b/components/WarningHeader.tsx index c8b0b632..ccd9382b 100644 --- a/components/WarningHeader.tsx +++ b/components/WarningHeader.tsx @@ -1,8 +1,9 @@ import { parse } from "date-fns"; import React from "react"; +import { DATE_FORMAT, getLocalDateFromEST } from "@/utils"; + import warnings from "../content/warnings/warnings.json"; -import { DATE_FORMAT, getLocalDateFromEST } from "../utils"; import styles from "./WarningHeader.module.css"; diff --git a/lib/events.ts b/lib/events.ts index 5b143de2..2266ae84 100644 --- a/lib/events.ts +++ b/lib/events.ts @@ -6,28 +6,33 @@ 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"; -// do not use alias "@/utils" as generate-calendar imports a function from this file and ts-node is not compatible import { Term, TERMS, isTerm, DATE_FORMAT, getLocalDateFromEST, -} from "../utils"; + 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 { +export async function getEventYears(): Promise { return (await fs.readdir(EVENTS_PATH, { withFileTypes: true })) .filter((dirent) => dirent.isDirectory()) - .map((dirent) => dirent.name) + .map((dirent) => parseInt(dirent.name)) .sort(); } -export async function getEventTermsByYear(year: string): Promise { +export async function getEventTermsByYear(year: number): Promise { return ( - await fs.readdir(path.join(EVENTS_PATH, year), { withFileTypes: true }) + await fs.readdir(path.join(EVENTS_PATH, year.toString()), { + withFileTypes: true, + }) ) .filter((dirent) => dirent.isDirectory() && isTerm(dirent.name)) .map((dirent) => dirent.name as Term) @@ -55,7 +60,7 @@ interface Metadata { location: string; permaLink: string; registerLink?: string; - year: string; + year: number; term: string; slug: string; } @@ -66,12 +71,12 @@ export interface Event { } export async function getEventBySlug( - year: string, + year: number, term: Term, slug: string ): Promise { const file = await fs.readFile( - path.join(EVENTS_PATH, year, term, `${slug}.md`), + path.join(EVENTS_PATH, year.toString(), term, `${slug}.md`), "utf-8" ); const { content, data } = matter(file); @@ -95,11 +100,11 @@ export async function getEventBySlug( } export async function getEventsByTerm( - year: string, + year: number, term: Term ): Promise { try { - return (await fs.readdir(path.join(EVENTS_PATH, year, term))) + return (await fs.readdir(path.join(EVENTS_PATH, year.toString(), term))) .filter((name) => name.endsWith(".md")) .map((name) => name.slice(0, -".md".length)); } catch { @@ -108,22 +113,24 @@ export async function getEventsByTerm( } 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 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( - [ - { year: currentYear.toString(), term: currentTerm }, - { year: nextYear.toString(), term: nextTerm }, - ].map(async ({ year, term }) => { + terms.map(async ({ year, term }) => { try { - const eventsInTerm = await getEventsByTerm(year, TERMS[term]); + const eventsInTerm = await getEventsByTerm(year, term); return await Promise.all( - eventsInTerm.map((slug) => getEventBySlug(year, TERMS[term], slug)) + eventsInTerm.map((slug) => getEventBySlug(year, term, slug)) ); } catch (error) { return []; @@ -162,12 +169,9 @@ export async function getAllEvents(): Promise { } export async function getEventsPageProps({ - year, term, -}: { - year: string; - term: Term; -}): Promise { + year, +}: TermYear): Promise { const eventNames = await getEventsByTerm(year, term); const events: Event[] = ( @@ -198,40 +202,43 @@ export async function getEventsPageProps({ currentDate ); - const current = getCurrentTerm(); - const eventYears = await getEventYears(); const minYear = eventYears[0]; - const pastTerms: { year: string; term: Term }[] = []; - 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); + 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: { year: string; term: Term }[] = []; - 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); + 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, @@ -242,70 +249,3 @@ export async function getEventsPageProps({ futureTerms: futureTerms, }; } - -export function getCurrentTerm(): { year: string; term: Term } { - const today = new Date().toLocaleDateString("en-CA", { - timeZone: "EST", - year: "numeric", - month: "numeric", - day: "numeric", - }); - - const [year] = today.split("-"); - - let term = ""; - - if (`${year}-01-01` <= today) { - term = "winter"; - } - - if (`${year}-05-01` <= today) { - term = "spring"; - } - - if (`${year}-09-01` <= today) { - term = "fall"; - } - - if (!isTerm(term)) { - throw new Error("Error setting the current term"); - } - - return { year, term }; -} - -function getPastTerm(year: string, term: Term): { year: string; term: Term } { - const index = TERMS.indexOf(term); - - if (index === -1) { - throw new Error(`[getPastTerm] Not a valid term: "${term}" "${year}"`); - } - - return index === 0 - ? { - year: (parseInt(year) - 1).toString(), - term: TERMS[TERMS.length - 1], - } - : { - year: year, - term: TERMS[index - 1], - }; -} - -function getFutureTerm(year: string, term: Term): { year: string; term: Term } { - const index = TERMS.indexOf(term); - - if (index === -1) { - throw new Error(`[getFutureTerm] Not a valid term: "${term}" "${year}"`); - } - - return index === TERMS.length - 1 - ? { - year: (parseInt(year) + 1).toString(), - term: TERMS[0], - } - : { - year: year, - term: TERMS[index + 1], - }; -} diff --git a/lib/members.ts b/lib/members.ts index 77fb9589..bb5b2584 100644 --- a/lib/members.ts +++ b/lib/members.ts @@ -8,7 +8,7 @@ export interface Member { program: string; } -export async function getMembers(year: string, term: Term): Promise { +export async function getMembers(year: number, term: Term): Promise { if (process.env.USE_LDAP?.toLowerCase() !== "true") { return dummyMembers; } diff --git a/lib/news.ts b/lib/news.ts index 9487ee4a..bba4d24f 100644 --- a/lib/news.ts +++ b/lib/news.ts @@ -7,9 +7,16 @@ import truncateMarkdown from "markdown-truncate"; import { MDXRemoteSerializeResult } from "next-mdx-remote"; import { serialize } from "next-mdx-remote/serialize"; -import { isTerm, Term, TERMS } from "@/utils"; - -import { DATE_FORMAT, getLocalDateFromEST } from "../utils"; +import { + isTerm, + Term, + TERMS, + DATE_FORMAT, + getLocalDateFromEST, + TermYear, + getTermYear, + getCurrentTermYear, +} from "@/utils"; export const NEWS_PATH = path.join("content", "news"); @@ -96,23 +103,27 @@ export async function getNewsBySlug( } export async function getRecentNews(): Promise { - const today = new Date(); - const currentYear = today.getFullYear(); - const currentTerm = Math.trunc(today.getMonth() / 4); - const prevYear = currentTerm > 0 ? currentYear : currentYear - 1; - const prevTerm = (currentTerm - 1 + 3) % 3; + 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( - [ - { year: currentYear.toString(), term: currentTerm }, - { year: prevYear.toString(), term: prevTerm }, - ].map(async ({ year, term }) => { + terms.map(async ({ year, term }) => { try { - const newsInTerm = await getNewsByTerm(year, TERMS[term]); + const newsInTerm = await getNewsByTerm(year.toString(), term); return await Promise.all( newsInTerm.map((slug) => { - return getNewsBySlug(year, TERMS[term], slug, true); + return getNewsBySlug(year.toString(), term, slug, true); }) ); } catch (error) { diff --git a/lib/team.ts b/lib/team.ts index 662f39a0..c4abc9c9 100644 --- a/lib/team.ts +++ b/lib/team.ts @@ -5,12 +5,10 @@ import matter from "gray-matter"; import { Client } from "ldapts"; import { serialize } from "next-mdx-remote/serialize"; -import { getCurrentTerm } from "@/lib/events"; -import { capitalize } from "@/utils"; +import { capitalize, TermYear } from "@/utils"; const EXECS_PATH = path.join("content", "team", "execs"); const FILETYPE = ".md"; -const { year, term } = getCurrentTerm(); const execPositions: { [position: string]: string } = { president: "President", @@ -34,7 +32,54 @@ export interface Metadata { image: string; } -export async function getExecNamePosPairs() { +export async function getExecs(termYear: TermYear) { + const execNamePosPairs = await getExecNamePosPairs(termYear); + + return await Promise.all( + execNamePosPairs.map((namePosPair) => + getExec(namePosPair[0], namePosPair[1]) + ) + ); +} + +async function getExec(name: string, pos: string) { + let content, metadata; + + try { + const raw = await readFile(path.join(EXECS_PATH, `${name}${FILETYPE}`)); + ({ content, data: metadata } = matter(raw)); + + const image = await getMemberImagePath(metadata.name as string); + + return { + content: await serialize(content), + metadata: { ...metadata, image } as Metadata, + }; + } catch (err) { + // Capitalize the first letter of the first name and last name + const firstName = capitalize(name.split("-")[0]); + const lastName = capitalize(name.split("-")[1]); + + const posName = execPositions[pos]; + content = "Coming Soon!"; + metadata = { + name: `${firstName} ${lastName}`, + role: `${posName}`, + }; + + const image = await getMemberImagePath(metadata.name); + + return { + content: await serialize(content), + metadata: { ...metadata, image } as Metadata, + }; + } +} + +async function getExecNamePosPairs({ + term, + year, +}: TermYear): Promise<[person: string, position: string][]> { if (process.env.USE_LDAP?.toLowerCase() !== "true") { return [["codey", "mascot"]]; } @@ -45,7 +90,7 @@ export async function getExecNamePosPairs() { // position: name const execMembers: { [position: string]: string } = {}; - let formattedExec: [string, string][] = []; + let formattedExec: [person: string, position: string][] = []; try { await client.bind("", ""); @@ -89,40 +134,6 @@ export async function getExecNamePosPairs() { return formattedExec; } -export async function getExec(name: string, pos: string, convert = true) { - let content, metadata; - - try { - const raw = await readFile(path.join(EXECS_PATH, `${name}${FILETYPE}`)); - ({ content, data: metadata } = matter(raw)); - - const image = await getMemberImagePath(metadata.name as string); - - return { - content: convert ? await serialize(content) : content, - metadata: { ...metadata, image } as Metadata, - }; - } catch (err) { - // Capitalize the first letter of the first name and last name - const firstName = capitalize(name.split("-")[0]); - const lastName = capitalize(name.split("-")[1]); - - const posName = execPositions[pos]; - content = "Coming Soon!"; - metadata = { - name: `${firstName} ${lastName}`, - role: `${posName}`, - }; - - const image = await getMemberImagePath(metadata.name); - - return { - content: convert ? await serialize(content) : content, - metadata: { ...metadata, image } as Metadata, - }; - } -} - async function getImage(imgPath: string) { try { await access(path.join("public", imgPath)); diff --git a/package-lock.json b/package-lock.json index 197aded2..f156be99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ "postcss-flexbugs-fixes": "^5.0.2", "postcss-preset-env": "^7.0.0", "ts-node": "^10.2.1", + "tsconfig-paths": "^4.1.0", "typescript": "4.6.4" }, "engines": { @@ -1075,7 +1076,7 @@ "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, "node_modules/@types/mdast": { @@ -2883,6 +2884,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/eslint-plugin-import/node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, "node_modules/eslint-plugin-prettier": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", @@ -7153,15 +7166,29 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz", + "integrity": "sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==", "dev": true, "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^2.2.1", "minimist": "^1.2.6", "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" } }, "node_modules/tslib": { @@ -8550,7 +8577,7 @@ "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, "@types/mdast": { @@ -10025,6 +10052,18 @@ "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } + }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } } } }, @@ -12975,15 +13014,22 @@ "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==" }, "tsconfig-paths": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", - "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz", + "integrity": "sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==", "dev": true, "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^2.2.1", "minimist": "^1.2.6", "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true + } } }, "tslib": { diff --git a/package.json b/package.json index 5ded9b68..8f25feed 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,10 @@ "scripts": { "dev": "next dev", "build": "npm run build:images && npm run build:web && npm run build:calendar", - "build:images": "ts-node ./scripts/optimize-images", "build:web": "next build", - "build:calendar": "ts-node ./scripts/generate-calendar", + "build:images": "ts-node -r tsconfig-paths/register ./scripts/optimize-images", + "build:calendar": "ts-node -r tsconfig-paths/register ./scripts/generate-calendar", + "build:api": "ts-node -r tsconfig-paths/register ./scripts/api/members", "start": "next start", "export": "next export", "lint": "eslint \"{pages,components,lib,hooks,scripts}/**/*.{js,ts,tsx,jsx}\" --quiet", @@ -56,6 +57,7 @@ "postcss-flexbugs-fixes": "^5.0.2", "postcss-preset-env": "^7.0.0", "ts-node": "^10.2.1", + "tsconfig-paths": "^4.1.0", "typescript": "4.6.4" } } diff --git a/pages/about/members.tsx b/pages/about/members.tsx index 78ec6cba..ef341bca 100644 --- a/pages/about/members.tsx +++ b/pages/about/members.tsx @@ -4,15 +4,14 @@ import React from "react"; import { Link } from "@/components/Link"; import { Table } from "@/components/Table"; import { Title } from "@/components/Title"; -import { getCurrentTerm } from "@/lib/events"; import { getMembers, Member } from "@/lib/members"; -import { Term, capitalize } from "@/utils"; +import { Term, capitalize, getCurrentTermYear } from "@/utils"; import styles from "./members.module.css"; interface Props { members: Member[]; - year: string; + year: number; term: Term; } @@ -56,7 +55,7 @@ export default function Members(props: Props) { } export const getStaticProps: GetStaticProps = async () => { - const curTerm = getCurrentTerm(); + const curTerm = getCurrentTermYear(); return { props: { year: curTerm.year, diff --git a/pages/about/team.tsx b/pages/about/team.tsx index 967b46f9..d7c7dd1e 100644 --- a/pages/about/team.tsx +++ b/pages/about/team.tsx @@ -9,12 +9,8 @@ import { Link } from "@/components/Link"; import { TeamMember } from "@/components/TeamMember"; import { TeamMemberCard } from "@/components/TeamMemberCard"; import { Title } from "@/components/Title"; -import { - getExec, - getExecNamePosPairs, - Metadata, - getMemberImagePath, -} from "@/lib/team"; +import { getExecs, Metadata, getMemberImagePath } from "@/lib/team"; +import { getCurrentTermYear } from "@/utils"; import designData from "../../content/team/design-team.json"; import discordData from "../../content/team/discord-team.json"; @@ -224,13 +220,7 @@ function sortTeam(team: Metadata[]): Metadata[] { } export const getStaticProps: GetStaticProps = async () => { - const execNamePosPairs = await getExecNamePosPairs(); - - const execs = (await Promise.all( - execNamePosPairs.map((namePosPair) => - getExec(namePosPair[0], namePosPair[1]) - ) - )) as SerializedExec[]; + const execs = await getExecs(getCurrentTermYear()); let [ design, diff --git a/pages/events/[year]/[term]/[event].tsx b/pages/events/[year]/[term]/[event].tsx index ffa4a6e4..1cbb4670 100644 --- a/pages/events/[year]/[term]/[event].tsx +++ b/pages/events/[year]/[term]/[event].tsx @@ -63,7 +63,11 @@ export const getStaticProps: GetStaticProps = async ( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const { year, term, event } = context.params!; return { - props: { year, term, event: await getEventBySlug(year, term, event) }, + props: { + year, + term, + event: await getEventBySlug(parseInt(year), term, event), + }, }; }; @@ -77,7 +81,7 @@ export const getStaticPaths: GetStaticPaths = async () => { termsInYear.map(async (term) => { const eventsInTerm = await getEventsByTerm(year, term); return eventsInTerm.map((event) => ({ - year, + year: year.toString(), term, event, })); diff --git a/pages/events/[year]/[term]/index.tsx b/pages/events/[year]/[term]/index.tsx index 04d8053e..46da54e1 100644 --- a/pages/events/[year]/[term]/index.tsx +++ b/pages/events/[year]/[term]/index.tsx @@ -10,22 +10,22 @@ import { MiniEventCard } from "@/components/MiniEventCard"; import { Title } from "@/components/Title"; import { Event, - getEventsPageProps, getEventYears, getEventTermsByYear, + getEventsPageProps, } from "@/lib/events"; -import { capitalize, Term } from "@/utils"; +import { capitalize, Term, TermYear } from "@/utils"; import styles from "./index.module.css"; export interface Props { - year: string; + year: number; term: Term; pastEvents: Event[]; futureEvents: Event[]; isCurrentTerm: boolean; - pastTerms: { year: string; term: Term }[]; - futureTerms: { year: string; term: Term }[]; + pastTerms: TermYear[]; + futureTerms: TermYear[]; } export default function TermPage(props: Props) { @@ -61,7 +61,7 @@ export default function TermPage(props: Props) { ))} Archive @@ -129,7 +129,7 @@ export default function TermPage(props: Props) { } function HeaderLink(props: { - year: string; + year: number; term: Term; isCurrentTerm?: boolean; }) { @@ -151,7 +151,14 @@ export const getStaticProps: GetStaticProps = async ( context ) => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return { props: await getEventsPageProps(context.params!) }; + const params = context.params!; + + return { + props: await getEventsPageProps({ + year: parseInt(params.year), + term: params.term, + }), + }; }; export const getStaticPaths: GetStaticPaths = async () => { @@ -161,7 +168,7 @@ export const getStaticPaths: GetStaticPaths = async () => { years.map(async (year) => { const terms = await getEventTermsByYear(year); return terms.map((curTerm) => ({ - params: { year: year, term: curTerm }, + params: { year: year.toString(), term: curTerm }, })); }) ) diff --git a/pages/events/[year]/index.tsx b/pages/events/[year]/index.tsx index 23df88bf..89fbbeaf 100644 --- a/pages/events/[year]/index.tsx +++ b/pages/events/[year]/index.tsx @@ -47,7 +47,7 @@ export const getStaticProps: GetStaticProps = async ( return { props: { year: year, - terms: await getEventTermsByYear(year), + terms: await getEventTermsByYear(parseInt(year)), }, }; }; @@ -55,7 +55,7 @@ export const getStaticProps: GetStaticProps = async ( export const getStaticPaths: GetStaticPaths = async () => { const years = await getEventYears(); const paths = years.map((curYear) => ({ - params: { year: curYear }, + params: { year: curYear.toString() }, })); return { paths: paths, diff --git a/pages/events/archive.tsx b/pages/events/archive.tsx index 9ae08a0d..b3c9e53a 100644 --- a/pages/events/archive.tsx +++ b/pages/events/archive.tsx @@ -9,7 +9,7 @@ export const getStaticProps: GetStaticProps = async () => { const years = (await getEventYears()).reverse(); const yearsWithTerms = await Promise.all( years.map(async (year) => ({ - year, + year: year.toString(), terms: (await getEventTermsByYear(year)).reverse(), })) ); diff --git a/pages/events/index.tsx b/pages/events/index.tsx index 7784304d..9d6b7466 100644 --- a/pages/events/index.tsx +++ b/pages/events/index.tsx @@ -1,11 +1,12 @@ import { GetStaticProps } from "next"; -import { getCurrentTerm, getEventsPageProps } from "@/lib/events"; +import { getEventsPageProps } from "@/lib/events"; +import { getCurrentTermYear } from "@/utils"; import TermPage, { Props } from "./[year]/[term]"; export default TermPage; export const getStaticProps: GetStaticProps = async () => { - return { props: await getEventsPageProps(getCurrentTerm()) }; + return { props: await getEventsPageProps(getCurrentTermYear()) }; }; diff --git a/scripts/api/members.ts b/scripts/api/members.ts new file mode 100644 index 00000000..63d7fc0c --- /dev/null +++ b/scripts/api/members.ts @@ -0,0 +1,13 @@ +import { writeFile } from "fs/promises"; + +import { getMembers } from "@/lib/members"; +import { getCurrentTermYear } from "@/utils"; + +async function createMembersApi() { + const { term, year } = getCurrentTermYear(); + const members = await getMembers(year, term); + + await writeFile("public/api/members.json", JSON.stringify(members)); +} + +void createMembersApi(); diff --git a/scripts/change-dates.ts b/scripts/change-dates.ts index 6d0a2ad8..98a91782 100644 --- a/scripts/change-dates.ts +++ b/scripts/change-dates.ts @@ -3,30 +3,35 @@ import path from "path"; import { format } from "date-fns"; +import { + getEventsByTerm, + getEventTermsByYear, + getEventYears, +} from "@/lib/events"; +import { + getNewsByTerm, + getNewsTermsByYear, + getNewsYears, + NEWS_PATH, +} from "@/lib/news"; import { DATE_FORMAT } from "@/utils"; /* Note: This script will not work for events by default anymore, since events now have startDate instead of endDate */ -import { - getEventsByTerm, - getEventTermsByYear, - getEventYears, -} from "../lib/events"; -import { - getNewsByTerm, - getNewsTermsByYear, - getNewsYears, - NEWS_PATH, -} from "../lib/news"; const EVENTS_PATH = path.join("content", "events"); export async function main() { for (const year of await getEventYears()) { for (const term of await getEventTermsByYear(year)) { for (const slug of await getEventsByTerm(year, term)) { - const filePath = path.join(EVENTS_PATH, year, term, `${slug}.md`); + const filePath = path.join( + EVENTS_PATH, + year.toString(), + term, + `${slug}.md` + ); const file = await fs.readFile(filePath, "utf-8"); await fs.writeFile(filePath, replaceDate(file)); diff --git a/scripts/generate-calendar.ts b/scripts/generate-calendar.ts index ed401c3d..1eba33d7 100644 --- a/scripts/generate-calendar.ts +++ b/scripts/generate-calendar.ts @@ -4,7 +4,7 @@ import path from "path"; import { addHours } from "date-fns"; import ical, { ICalCalendarMethod } from "ical-generator"; -import { getAllEvents } from "../lib/events"; +import { getAllEvents } from "@/lib/events"; export async function generateCalendar() { const events = await getAllEvents(); diff --git a/utils.ts b/utils.ts index 0df41524..978f9719 100644 --- a/utils.ts +++ b/utils.ts @@ -21,3 +21,68 @@ export function getLocalDateFromEST(date: Date): Date { Intl.DateTimeFormat().resolvedOptions().timeZone ); } + +export interface TermYear { + term: Term; + year: number; +} + +export interface GetTermYearOptions { + goBackwards?: boolean; + skipCurrent?: boolean; +} + +export function* getTermYear( + start?: number | TermYear, + { goBackwards = false, skipCurrent = false }: GetTermYearOptions = {} +) { + const today = new Date(); + const todayYear = today.getFullYear(); + const todayTerm = TERMS[Math.trunc(today.getMonth() / 4)]; + + start ??= { term: todayTerm, year: todayYear }; + + if (typeof start === "number") { + start = { term: TERMS[0], year: start }; + } + + const allTerms = [...TERMS]; + + if (goBackwards) { + allTerms.reverse(); + } + + let currentYear = start.year; + while (0 <= currentYear && currentYear <= Number.MAX_SAFE_INTEGER) { + for (const currentTerm of allTerms) { + if ( + currentYear === start.year && + allTerms.indexOf(currentTerm) < allTerms.indexOf(start.term) + ) { + continue; + } + + if ( + skipCurrent && + currentYear === start.year && + currentTerm === start.term + ) { + continue; + } + + yield { term: currentTerm, year: currentYear }; + } + + currentYear = currentYear + (goBackwards ? -1 : 1); + } +} + +export function getCurrentTermYear() { + const result = getTermYear().next(); + + if (result.done === true) { + throw new Error("Cannot get current term. Iterator is done."); + } + + return result.value; +}