From 2264e60852b03b9236075217937457a8fec2bb84 Mon Sep 17 00:00:00 2001 From: j285he Date: Wed, 19 Jan 2022 20:19:13 -0500 Subject: [PATCH] Add Term Type (#361) Co-authored-by: Jared He <66887902+jaredjhe@users.noreply.github.com> Reviewed-on: https://git.csclub.uwaterloo.ca/www/www-new/pulls/361 Reviewed-by: Amy Reviewed-by: n3parikh Co-authored-by: j285he Co-committed-by: j285he --- components/ArchivePage.tsx | 4 +-- lib/events.ts | 35 +++++++++++--------------- lib/members.ts | 17 ++++++------- lib/news.ts | 13 +++++----- pages/about/members.tsx | 4 +-- pages/events/[year]/[term]/[event].tsx | 6 ++--- pages/events/[year]/[term]/index.tsx | 14 +++++------ pages/events/[year]/index.tsx | 3 ++- pages/events/index.tsx | 4 +-- pages/news/[year]/[term].tsx | 6 ++--- utils.ts | 8 ++++++ 11 files changed, 58 insertions(+), 56 deletions(-) diff --git a/components/ArchivePage.tsx b/components/ArchivePage.tsx index 522578a2..ed714184 100644 --- a/components/ArchivePage.tsx +++ b/components/ArchivePage.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { capitalize } from "@/utils"; +import { capitalize, Term } from "@/utils"; import { Link } from "./Link"; import { @@ -16,7 +16,7 @@ export interface Props { type: "news" | "events"; items: { year: string; - terms: string[]; + terms: Term[]; }[]; } diff --git a/lib/events.ts b/lib/events.ts index 8df6e449..06952eb3 100644 --- a/lib/events.ts +++ b/lib/events.ts @@ -8,9 +8,10 @@ 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 } from "../utils"; 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 })) @@ -19,12 +20,12 @@ export async function getEventYears(): Promise { .sort(); } -export async function getEventTermsByYear(year: string): Promise { +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) + .filter((dirent) => dirent.isDirectory() && isTerm(dirent.name)) + .map((dirent) => dirent.name as Term) .sort((a, b) => TERMS.indexOf(a) - TERMS.indexOf(b)); } @@ -58,7 +59,7 @@ export const DATE_FORMAT = "MMMM dd yyyy HH:mm"; export async function getEventBySlug( year: string, - term: string, + term: Term, slug: string ): Promise { const file = await fs.readFile( @@ -84,7 +85,7 @@ export async function getEventBySlug( export async function getEventsByTerm( year: string, - term: string + term: Term ): Promise { try { return (await fs.readdir(path.join(EVENTS_PATH, year, term))) @@ -130,7 +131,7 @@ export async function getUpcomingEvents(): Promise { }); } -export async function getAllEvents() { +export async function getAllEvents(): Promise { const events: Event[] = []; for (const year of await getEventYears()) { @@ -149,7 +150,7 @@ export async function getEventsPageProps({ term, }: { year: string; - term: string; + term: Term; }): Promise { const eventNames = await getEventsByTerm(year, term); @@ -177,7 +178,7 @@ export async function getEventsPageProps({ const eventYears = await getEventYears(); const minYear = eventYears[0]; - const pastTerms: { year: string; term: string }[] = []; + const pastTerms: { year: string; term: Term }[] = []; let curPastYear = year; let curPastTerm = term; while (parseInt(curPastYear) >= parseInt(minYear) && pastTerms.length < 2) { @@ -191,7 +192,7 @@ export async function getEventsPageProps({ pastTerms.reverse(); const maxYear = eventYears[eventYears.length - 1]; - const futureTerms: { year: string; term: string }[] = []; + const futureTerms: { year: string; term: Term }[] = []; let curFutureYear = year; let curFutureTerm = term; while ( @@ -217,7 +218,7 @@ export async function getEventsPageProps({ }; } -export function getCurrentTerm() { +export function getCurrentTerm(): { year: string; term: Term } { const today = new Date().toLocaleDateString("en-CA", { timeZone: "EST", year: "numeric", @@ -241,17 +242,14 @@ export function getCurrentTerm() { term = "fall"; } - if (term === "") { + if (!isTerm(term)) { throw new Error("Error setting the current term"); } return { year, term }; } -function getPastTerm( - year: string, - term: string -): { year: string; term: string } { +function getPastTerm(year: string, term: Term): { year: string; term: Term } { const index = TERMS.indexOf(term); if (index === -1) { @@ -269,10 +267,7 @@ function getPastTerm( }; } -function getFutureTerm( - year: string, - term: string -): { year: string; term: string } { +function getFutureTerm(year: string, term: Term): { year: string; term: Term } { const index = TERMS.indexOf(term); if (index === -1) { diff --git a/lib/members.ts b/lib/members.ts index 5a7e7da8..77fb9589 100644 --- a/lib/members.ts +++ b/lib/members.ts @@ -1,23 +1,17 @@ import { Client } from "ldapts"; +import { Term } from "@/utils"; + export interface Member { name: string; id: string; program: string; } -export async function getMembers( - year: string, - term: string -): Promise { - if (term !== "winter" && term !== "spring" && term !== "fall") { - throw new Error(`[getMembers] Not a valid term: "${term}"`); - } - +export async function getMembers(year: string, term: Term): Promise { if (process.env.USE_LDAP?.toLowerCase() !== "true") { return dummyMembers; } - let members: Member[] = []; const url = "ldap://ldap1.csclub.uwaterloo.ca"; @@ -28,7 +22,10 @@ export async function getMembers( await client.bind("", ""); const { searchEntries } = await client.search(searchDN, { scope: "sub", - filter: `(&(objectClass=member)(term=${term.slice(0, 1)}${year}))`, + filter: `(&(objectClass=member)(term=${(term as string).slice( + 0, + 1 + )}${year}))`, }); members = searchEntries diff --git a/lib/news.ts b/lib/news.ts index 0ab30257..6abd9547 100644 --- a/lib/news.ts +++ b/lib/news.ts @@ -6,10 +6,11 @@ import matter from "gray-matter"; import { MDXRemoteSerializeResult } from "next-mdx-remote"; import { serialize } from "next-mdx-remote/serialize"; +import { isTerm, Term, TERMS } from "@/utils"; + import { DATE_FORMAT, getLocalDateFromEST } from "./events"; export const NEWS_PATH = path.join("content", "news"); -const TERMS = ["winter", "spring", "fall"]; export interface Metadata { author: string; @@ -28,20 +29,20 @@ export async function getNewsYears(): Promise { .sort(); } -export async function getNewsTermsByYear(year: string): Promise { +export async function getNewsTermsByYear(year: string): Promise { return ( await fs.readdir(path.join(NEWS_PATH, year), { withFileTypes: true, }) ) - .filter((dirent) => dirent.isDirectory() && TERMS.includes(dirent.name)) - .map((dirent) => dirent.name) + .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 getNewsByTerm( year: string, - term: string + term: Term ): Promise { return ( await fs.readdir(path.join(NEWS_PATH, year, term), { @@ -54,7 +55,7 @@ export async function getNewsByTerm( export async function getNewsBySlug( year: string, - term: string, + term: Term, slug: string ): Promise { const raw = await fs.readFile( diff --git a/pages/about/members.tsx b/pages/about/members.tsx index 32992271..9407d804 100644 --- a/pages/about/members.tsx +++ b/pages/about/members.tsx @@ -6,14 +6,14 @@ import { Table } from "@/components/Table"; import { Title } from "@/components/Title"; import { getCurrentTerm } from "@/lib/events"; import { getMembers, Member } from "@/lib/members"; -import { capitalize } from "@/utils"; +import { Term, capitalize } from "@/utils"; import styles from "./members.module.css"; interface Props { members: Member[]; year: string; - term: string; + term: Term; } export default function Members(props: Props) { diff --git a/pages/events/[year]/[term]/[event].tsx b/pages/events/[year]/[term]/[event].tsx index 4ab46a09..ef30a6a5 100644 --- a/pages/events/[year]/[term]/[event].tsx +++ b/pages/events/[year]/[term]/[event].tsx @@ -18,7 +18,7 @@ import { getEventsByTerm, getEventBySlug, } from "@/lib/events"; -import { capitalize } from "@/utils"; +import { capitalize, Term } from "@/utils"; export default function EventInfoPage({ year, term, event }: Props) { return ( @@ -43,13 +43,13 @@ EventInfoPage.getShapesConfig = ((width, height) => { interface Props { year: string; - term: string; + term: Term; event: Event; } interface Params extends ParsedUrlQuery { year: string; - term: string; + term: Term; event: string; } diff --git a/pages/events/[year]/[term]/index.tsx b/pages/events/[year]/[term]/index.tsx index bb7cc93f..8830d624 100644 --- a/pages/events/[year]/[term]/index.tsx +++ b/pages/events/[year]/[term]/index.tsx @@ -14,21 +14,21 @@ import { getEventYears, getEventTermsByYear, } from "@/lib/events"; -import { capitalize } from "@/utils"; +import { capitalize, Term } from "@/utils"; import styles from "./index.module.css"; export interface Props { year: string; - term: string; + term: Term; pastEvents: Event[]; futureEvents: Event[]; isCurrentTerm: boolean; - pastTerms: { year: string; term: string }[]; - futureTerms: { year: string; term: string }[]; + pastTerms: { year: string; term: Term }[]; + futureTerms: { year: string; term: Term }[]; } -export default function Term(props: Props) { +export default function TermPage(props: Props) { let headerTerms = [{ year: props.year, term: props.term }]; // p, Current, f @@ -120,7 +120,7 @@ export default function Term(props: Props) { function HeaderLink(props: { year: string; - term: string; + term: Term; isCurrentTerm?: boolean; }) { return ( @@ -134,7 +134,7 @@ function HeaderLink(props: { interface Params extends ParsedUrlQuery { year: string; - term: string; + term: Term; } export const getStaticProps: GetStaticProps = async ( diff --git a/pages/events/[year]/index.tsx b/pages/events/[year]/index.tsx index 07e29e05..23df88bf 100644 --- a/pages/events/[year]/index.tsx +++ b/pages/events/[year]/index.tsx @@ -6,12 +6,13 @@ import React from "react"; import { Link } from "@/components/Link"; import { Title } from "@/components/Title"; import { getEventYears, getEventTermsByYear } from "@/lib/events"; +import { Term } from "@/utils"; import styles from "./index.module.css"; interface Props { year: string; - terms: string[]; + terms: Term[]; } export default function Year(props: Props) { diff --git a/pages/events/index.tsx b/pages/events/index.tsx index 9d5b1796..7784304d 100644 --- a/pages/events/index.tsx +++ b/pages/events/index.tsx @@ -2,9 +2,9 @@ import { GetStaticProps } from "next"; import { getCurrentTerm, getEventsPageProps } from "@/lib/events"; -import Term, { Props } from "./[year]/[term]"; +import TermPage, { Props } from "./[year]/[term]"; -export default Term; +export default TermPage; export const getStaticProps: GetStaticProps = async () => { return { props: await getEventsPageProps(getCurrentTerm()) }; diff --git a/pages/news/[year]/[term].tsx b/pages/news/[year]/[term].tsx index 06640a7e..e28fa908 100644 --- a/pages/news/[year]/[term].tsx +++ b/pages/news/[year]/[term].tsx @@ -18,13 +18,13 @@ import { getNewsYears, News, } from "@/lib/news"; -import { capitalize } from "@/utils"; +import { capitalize, Term } from "@/utils"; import styles from "./[term].module.css"; interface Props { year: string; - term: string; + term: Term; news: News[]; } @@ -75,7 +75,7 @@ export const getStaticProps: GetStaticProps = async ( interface Params extends ParsedUrlQuery { year: string; - term: string; + term: Term; } export const getStaticPaths: GetStaticPaths = async () => { diff --git a/utils.ts b/utils.ts index 40ef05aa..fc08499a 100644 --- a/utils.ts +++ b/utils.ts @@ -1,3 +1,11 @@ +export const TERMS = ["winter", "spring", "fall"] as const; +export type Term = typeof TERMS[number]; + +// https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates +export function isTerm(x: string): x is Term { + return TERMS.some((term) => x === term); +} + export function capitalize(str: string) { return str.slice(0, 1).toUpperCase() + str.slice(1); }