Add Term Type (#361)
continuous-integration/drone/push Build is passing Details

Co-authored-by: Jared He <66887902+jaredjhe@users.noreply.github.com>
Reviewed-on: #361
Reviewed-by: Amy <a258wang@csclub.uwaterloo.ca>
Reviewed-by: n3parikh <n3parikh@csclub.uwaterloo.ca>
Co-authored-by: j285he <j285he@localhost>
Co-committed-by: j285he <j285he@localhost>
This commit is contained in:
Jared He 2022-01-19 20:19:13 -05:00
parent ff0594eac7
commit 2264e60852
11 changed files with 58 additions and 56 deletions

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { capitalize } from "@/utils"; import { capitalize, Term } from "@/utils";
import { Link } from "./Link"; import { Link } from "./Link";
import { import {
@ -16,7 +16,7 @@ export interface Props {
type: "news" | "events"; type: "news" | "events";
items: { items: {
year: string; year: string;
terms: string[]; terms: Term[];
}[]; }[];
} }

View File

@ -8,9 +8,10 @@ import { MDXRemoteSerializeResult } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize"; import { serialize } from "next-mdx-remote/serialize";
import type { Props } from "../pages/events/[year]/[term]/index"; 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"); const EVENTS_PATH = path.join("content", "events");
export const TERMS = ["winter", "spring", "fall"];
export async function getEventYears(): Promise<string[]> { export async function getEventYears(): Promise<string[]> {
return (await fs.readdir(EVENTS_PATH, { withFileTypes: true })) return (await fs.readdir(EVENTS_PATH, { withFileTypes: true }))
@ -19,12 +20,12 @@ export async function getEventYears(): Promise<string[]> {
.sort(); .sort();
} }
export async function getEventTermsByYear(year: string): Promise<string[]> { export async function getEventTermsByYear(year: string): Promise<Term[]> {
return ( return (
await fs.readdir(path.join(EVENTS_PATH, year), { withFileTypes: true }) await fs.readdir(path.join(EVENTS_PATH, year), { withFileTypes: true })
) )
.filter((dirent) => dirent.isDirectory() && TERMS.includes(dirent.name)) .filter((dirent) => dirent.isDirectory() && isTerm(dirent.name))
.map((dirent) => dirent.name) .map((dirent) => dirent.name as Term)
.sort((a, b) => TERMS.indexOf(a) - TERMS.indexOf(b)); .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( export async function getEventBySlug(
year: string, year: string,
term: string, term: Term,
slug: string slug: string
): Promise<Event> { ): Promise<Event> {
const file = await fs.readFile( const file = await fs.readFile(
@ -84,7 +85,7 @@ export async function getEventBySlug(
export async function getEventsByTerm( export async function getEventsByTerm(
year: string, year: string,
term: string term: Term
): Promise<string[]> { ): Promise<string[]> {
try { try {
return (await fs.readdir(path.join(EVENTS_PATH, year, term))) return (await fs.readdir(path.join(EVENTS_PATH, year, term)))
@ -130,7 +131,7 @@ export async function getUpcomingEvents(): Promise<Event[]> {
}); });
} }
export async function getAllEvents() { export async function getAllEvents(): Promise<Event[]> {
const events: Event[] = []; const events: Event[] = [];
for (const year of await getEventYears()) { for (const year of await getEventYears()) {
@ -149,7 +150,7 @@ export async function getEventsPageProps({
term, term,
}: { }: {
year: string; year: string;
term: string; term: Term;
}): Promise<Props> { }): Promise<Props> {
const eventNames = await getEventsByTerm(year, term); const eventNames = await getEventsByTerm(year, term);
@ -177,7 +178,7 @@ export async function getEventsPageProps({
const eventYears = await getEventYears(); const eventYears = await getEventYears();
const minYear = eventYears[0]; const minYear = eventYears[0];
const pastTerms: { year: string; term: string }[] = []; const pastTerms: { year: string; term: Term }[] = [];
let curPastYear = year; let curPastYear = year;
let curPastTerm = term; let curPastTerm = term;
while (parseInt(curPastYear) >= parseInt(minYear) && pastTerms.length < 2) { while (parseInt(curPastYear) >= parseInt(minYear) && pastTerms.length < 2) {
@ -191,7 +192,7 @@ export async function getEventsPageProps({
pastTerms.reverse(); pastTerms.reverse();
const maxYear = eventYears[eventYears.length - 1]; const maxYear = eventYears[eventYears.length - 1];
const futureTerms: { year: string; term: string }[] = []; const futureTerms: { year: string; term: Term }[] = [];
let curFutureYear = year; let curFutureYear = year;
let curFutureTerm = term; let curFutureTerm = term;
while ( 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", { const today = new Date().toLocaleDateString("en-CA", {
timeZone: "EST", timeZone: "EST",
year: "numeric", year: "numeric",
@ -241,17 +242,14 @@ export function getCurrentTerm() {
term = "fall"; term = "fall";
} }
if (term === "") { if (!isTerm(term)) {
throw new Error("Error setting the current term"); throw new Error("Error setting the current term");
} }
return { year, term }; return { year, term };
} }
function getPastTerm( function getPastTerm(year: string, term: Term): { year: string; term: Term } {
year: string,
term: string
): { year: string; term: string } {
const index = TERMS.indexOf(term); const index = TERMS.indexOf(term);
if (index === -1) { if (index === -1) {
@ -269,10 +267,7 @@ function getPastTerm(
}; };
} }
function getFutureTerm( function getFutureTerm(year: string, term: Term): { year: string; term: Term } {
year: string,
term: string
): { year: string; term: string } {
const index = TERMS.indexOf(term); const index = TERMS.indexOf(term);
if (index === -1) { if (index === -1) {

View File

@ -1,23 +1,17 @@
import { Client } from "ldapts"; import { Client } from "ldapts";
import { Term } from "@/utils";
export interface Member { export interface Member {
name: string; name: string;
id: string; id: string;
program: string; program: string;
} }
export async function getMembers( export async function getMembers(year: string, term: Term): Promise<Member[]> {
year: string,
term: string
): Promise<Member[]> {
if (term !== "winter" && term !== "spring" && term !== "fall") {
throw new Error(`[getMembers] Not a valid term: "${term}"`);
}
if (process.env.USE_LDAP?.toLowerCase() !== "true") { if (process.env.USE_LDAP?.toLowerCase() !== "true") {
return dummyMembers; return dummyMembers;
} }
let members: Member[] = []; let members: Member[] = [];
const url = "ldap://ldap1.csclub.uwaterloo.ca"; const url = "ldap://ldap1.csclub.uwaterloo.ca";
@ -28,7 +22,10 @@ export async function getMembers(
await client.bind("", ""); await client.bind("", "");
const { searchEntries } = await client.search(searchDN, { const { searchEntries } = await client.search(searchDN, {
scope: "sub", scope: "sub",
filter: `(&(objectClass=member)(term=${term.slice(0, 1)}${year}))`, filter: `(&(objectClass=member)(term=${(term as string).slice(
0,
1
)}${year}))`,
}); });
members = searchEntries members = searchEntries

View File

@ -6,10 +6,11 @@ import matter from "gray-matter";
import { MDXRemoteSerializeResult } from "next-mdx-remote"; import { MDXRemoteSerializeResult } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize"; import { serialize } from "next-mdx-remote/serialize";
import { isTerm, Term, TERMS } from "@/utils";
import { DATE_FORMAT, getLocalDateFromEST } from "./events"; import { DATE_FORMAT, getLocalDateFromEST } from "./events";
export const NEWS_PATH = path.join("content", "news"); export const NEWS_PATH = path.join("content", "news");
const TERMS = ["winter", "spring", "fall"];
export interface Metadata { export interface Metadata {
author: string; author: string;
@ -28,20 +29,20 @@ export async function getNewsYears(): Promise<string[]> {
.sort(); .sort();
} }
export async function getNewsTermsByYear(year: string): Promise<string[]> { export async function getNewsTermsByYear(year: string): Promise<Term[]> {
return ( return (
await fs.readdir(path.join(NEWS_PATH, year), { await fs.readdir(path.join(NEWS_PATH, year), {
withFileTypes: true, withFileTypes: true,
}) })
) )
.filter((dirent) => dirent.isDirectory() && TERMS.includes(dirent.name)) .filter((dirent) => dirent.isDirectory() && isTerm(dirent.name))
.map((dirent) => dirent.name) .map((dirent) => dirent.name as Term)
.sort((a, b) => TERMS.indexOf(a) - TERMS.indexOf(b)); .sort((a, b) => TERMS.indexOf(a) - TERMS.indexOf(b));
} }
export async function getNewsByTerm( export async function getNewsByTerm(
year: string, year: string,
term: string term: Term
): Promise<string[]> { ): Promise<string[]> {
return ( return (
await fs.readdir(path.join(NEWS_PATH, year, term), { await fs.readdir(path.join(NEWS_PATH, year, term), {
@ -54,7 +55,7 @@ export async function getNewsByTerm(
export async function getNewsBySlug( export async function getNewsBySlug(
year: string, year: string,
term: string, term: Term,
slug: string slug: string
): Promise<News> { ): Promise<News> {
const raw = await fs.readFile( const raw = await fs.readFile(

View File

@ -6,14 +6,14 @@ import { Table } from "@/components/Table";
import { Title } from "@/components/Title"; import { Title } from "@/components/Title";
import { getCurrentTerm } from "@/lib/events"; import { getCurrentTerm } from "@/lib/events";
import { getMembers, Member } from "@/lib/members"; import { getMembers, Member } from "@/lib/members";
import { capitalize } from "@/utils"; import { Term, capitalize } from "@/utils";
import styles from "./members.module.css"; import styles from "./members.module.css";
interface Props { interface Props {
members: Member[]; members: Member[];
year: string; year: string;
term: string; term: Term;
} }
export default function Members(props: Props) { export default function Members(props: Props) {

View File

@ -18,7 +18,7 @@ import {
getEventsByTerm, getEventsByTerm,
getEventBySlug, getEventBySlug,
} from "@/lib/events"; } from "@/lib/events";
import { capitalize } from "@/utils"; import { capitalize, Term } from "@/utils";
export default function EventInfoPage({ year, term, event }: Props) { export default function EventInfoPage({ year, term, event }: Props) {
return ( return (
@ -43,13 +43,13 @@ EventInfoPage.getShapesConfig = ((width, height) => {
interface Props { interface Props {
year: string; year: string;
term: string; term: Term;
event: Event; event: Event;
} }
interface Params extends ParsedUrlQuery { interface Params extends ParsedUrlQuery {
year: string; year: string;
term: string; term: Term;
event: string; event: string;
} }

View File

@ -14,21 +14,21 @@ import {
getEventYears, getEventYears,
getEventTermsByYear, getEventTermsByYear,
} from "@/lib/events"; } from "@/lib/events";
import { capitalize } from "@/utils"; import { capitalize, Term } from "@/utils";
import styles from "./index.module.css"; import styles from "./index.module.css";
export interface Props { export interface Props {
year: string; year: string;
term: string; term: Term;
pastEvents: Event[]; pastEvents: Event[];
futureEvents: Event[]; futureEvents: Event[];
isCurrentTerm: boolean; isCurrentTerm: boolean;
pastTerms: { year: string; term: string }[]; pastTerms: { year: string; term: Term }[];
futureTerms: { year: string; term: string }[]; 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 }]; let headerTerms = [{ year: props.year, term: props.term }];
// p, Current, f // p, Current, f
@ -120,7 +120,7 @@ export default function Term(props: Props) {
function HeaderLink(props: { function HeaderLink(props: {
year: string; year: string;
term: string; term: Term;
isCurrentTerm?: boolean; isCurrentTerm?: boolean;
}) { }) {
return ( return (
@ -134,7 +134,7 @@ function HeaderLink(props: {
interface Params extends ParsedUrlQuery { interface Params extends ParsedUrlQuery {
year: string; year: string;
term: string; term: Term;
} }
export const getStaticProps: GetStaticProps<Props, Params> = async ( export const getStaticProps: GetStaticProps<Props, Params> = async (

View File

@ -6,12 +6,13 @@ import React from "react";
import { Link } from "@/components/Link"; import { Link } from "@/components/Link";
import { Title } from "@/components/Title"; import { Title } from "@/components/Title";
import { getEventYears, getEventTermsByYear } from "@/lib/events"; import { getEventYears, getEventTermsByYear } from "@/lib/events";
import { Term } from "@/utils";
import styles from "./index.module.css"; import styles from "./index.module.css";
interface Props { interface Props {
year: string; year: string;
terms: string[]; terms: Term[];
} }
export default function Year(props: Props) { export default function Year(props: Props) {

View File

@ -2,9 +2,9 @@ import { GetStaticProps } from "next";
import { getCurrentTerm, getEventsPageProps } from "@/lib/events"; 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<Props> = async () => { export const getStaticProps: GetStaticProps<Props> = async () => {
return { props: await getEventsPageProps(getCurrentTerm()) }; return { props: await getEventsPageProps(getCurrentTerm()) };

View File

@ -18,13 +18,13 @@ import {
getNewsYears, getNewsYears,
News, News,
} from "@/lib/news"; } from "@/lib/news";
import { capitalize } from "@/utils"; import { capitalize, Term } from "@/utils";
import styles from "./[term].module.css"; import styles from "./[term].module.css";
interface Props { interface Props {
year: string; year: string;
term: string; term: Term;
news: News[]; news: News[];
} }
@ -75,7 +75,7 @@ export const getStaticProps: GetStaticProps<Props, Params> = async (
interface Params extends ParsedUrlQuery { interface Params extends ParsedUrlQuery {
year: string; year: string;
term: string; term: Term;
} }
export const getStaticPaths: GetStaticPaths<Params> = async () => { export const getStaticPaths: GetStaticPaths<Params> = async () => {

View File

@ -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) { export function capitalize(str: string) {
return str.slice(0, 1).toUpperCase() + str.slice(1); return str.slice(0, 1).toUpperCase() + str.slice(1);
} }