Create members api

This commit is contained in:
Aditya Thakral 2022-08-07 16:35:56 -07:00
parent ef9050357f
commit a7eac7b81e
21 changed files with 338 additions and 235 deletions

View File

@ -37,6 +37,13 @@ steps:
commands: commands:
- npm run build:calendar - npm run build:calendar
- name: generate-api
image: node:16
depends_on:
- install-deps
commands:
- npm run build:api
- name: build - name: build
image: node:16 image: node:16
depends_on: depends_on:
@ -47,6 +54,7 @@ steps:
- name: export - name: export
image: node:16 image: node:16
depends_on: depends_on:
- generate-api
- generate-calendar - generate-calendar
- build - build
commands: commands:

View File

@ -19,7 +19,7 @@ interface EventCardProps {
permaLink: string; permaLink: string;
showDescription?: boolean; showDescription?: boolean;
children: ReactNode; children: ReactNode;
year: string; year: number;
term: string; term: string;
slug: string; slug: string;
titleLinked: boolean; titleLinked: boolean;

View File

@ -14,7 +14,7 @@ interface MiniEventCardProps {
startDate: Date; startDate: Date;
endDate?: Date; endDate?: Date;
background: "dark-bg" | "normal-bg"; background: "dark-bg" | "normal-bg";
year: string; year: number;
term: string; term: string;
slug: string; slug: string;
} }

View File

@ -1,8 +1,9 @@
import { parse } from "date-fns"; import { parse } from "date-fns";
import React from "react"; import React from "react";
import { DATE_FORMAT, getLocalDateFromEST } from "@/utils";
import warnings from "../content/warnings/warnings.json"; import warnings from "../content/warnings/warnings.json";
import { DATE_FORMAT, getLocalDateFromEST } from "../utils";
import styles from "./WarningHeader.module.css"; import styles from "./WarningHeader.module.css";

View File

@ -6,28 +6,33 @@ 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 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 { import {
Term, Term,
TERMS, TERMS,
isTerm, isTerm,
DATE_FORMAT, DATE_FORMAT,
getLocalDateFromEST, getLocalDateFromEST,
} from "../utils"; TermYear,
getTermYear,
getCurrentTermYear,
} from "@/utils";
import type { Props } from "../pages/events/[year]/[term]";
const EVENTS_PATH = path.join("content", "events"); const EVENTS_PATH = path.join("content", "events");
export async function getEventYears(): Promise<string[]> { export async function getEventYears(): Promise<number[]> {
return (await fs.readdir(EVENTS_PATH, { withFileTypes: true })) return (await fs.readdir(EVENTS_PATH, { withFileTypes: true }))
.filter((dirent) => dirent.isDirectory()) .filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name) .map((dirent) => parseInt(dirent.name))
.sort(); .sort();
} }
export async function getEventTermsByYear(year: string): Promise<Term[]> { export async function getEventTermsByYear(year: number): Promise<Term[]> {
return ( 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)) .filter((dirent) => dirent.isDirectory() && isTerm(dirent.name))
.map((dirent) => dirent.name as Term) .map((dirent) => dirent.name as Term)
@ -55,7 +60,7 @@ interface Metadata {
location: string; location: string;
permaLink: string; permaLink: string;
registerLink?: string; registerLink?: string;
year: string; year: number;
term: string; term: string;
slug: string; slug: string;
} }
@ -66,12 +71,12 @@ export interface Event {
} }
export async function getEventBySlug( export async function getEventBySlug(
year: string, year: number,
term: Term, term: Term,
slug: string slug: string
): Promise<Event> { ): Promise<Event> {
const file = await fs.readFile( const file = await fs.readFile(
path.join(EVENTS_PATH, year, term, `${slug}.md`), path.join(EVENTS_PATH, year.toString(), term, `${slug}.md`),
"utf-8" "utf-8"
); );
const { content, data } = matter(file); const { content, data } = matter(file);
@ -95,11 +100,11 @@ export async function getEventBySlug(
} }
export async function getEventsByTerm( export async function getEventsByTerm(
year: string, year: number,
term: Term 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.toString(), term)))
.filter((name) => name.endsWith(".md")) .filter((name) => name.endsWith(".md"))
.map((name) => name.slice(0, -".md".length)); .map((name) => name.slice(0, -".md".length));
} catch { } catch {
@ -108,22 +113,24 @@ export async function getEventsByTerm(
} }
export async function getUpcomingEvents(): Promise<Event[]> { export async function getUpcomingEvents(): Promise<Event[]> {
const today = new Date(); const terms: TermYear[] = [];
const currentYear = today.getFullYear();
const currentTerm = Math.trunc(today.getMonth() / 4); // Get events for the next two terms
const nextYear = currentTerm < 2 ? currentYear : currentYear + 1; for (const termYear of getTermYear()) {
const nextTerm = (currentTerm + 1) % 3; if (terms.length >= 2) {
break;
}
terms.push(termYear);
}
const events: Event[] = ( const events: Event[] = (
await Promise.all( await Promise.all(
[ terms.map(async ({ year, term }) => {
{ year: currentYear.toString(), term: currentTerm },
{ year: nextYear.toString(), term: nextTerm },
].map(async ({ year, term }) => {
try { try {
const eventsInTerm = await getEventsByTerm(year, TERMS[term]); const eventsInTerm = await getEventsByTerm(year, term);
return await Promise.all( return await Promise.all(
eventsInTerm.map((slug) => getEventBySlug(year, TERMS[term], slug)) eventsInTerm.map((slug) => getEventBySlug(year, term, slug))
); );
} catch (error) { } catch (error) {
return []; return [];
@ -162,12 +169,9 @@ export async function getAllEvents(): Promise<Event[]> {
} }
export async function getEventsPageProps({ export async function getEventsPageProps({
year,
term, term,
}: { year,
year: string; }: TermYear): Promise<Props> {
term: Term;
}): Promise<Props> {
const eventNames = await getEventsByTerm(year, term); const eventNames = await getEventsByTerm(year, term);
const events: Event[] = ( const events: Event[] = (
@ -198,40 +202,43 @@ export async function getEventsPageProps({
currentDate currentDate
); );
const current = getCurrentTerm();
const eventYears = await getEventYears(); const eventYears = await getEventYears();
const minYear = eventYears[0]; const minYear = eventYears[0];
const pastTerms: { year: string; term: Term }[] = []; const pastTerms: TermYear[] = [];
let curPastYear = year;
let curPastTerm = term; for (const current of getTermYear(
while (parseInt(curPastYear) >= parseInt(minYear) && pastTerms.length < 2) { { year, term },
const pastTerm = getPastTerm(curPastYear, curPastTerm); { goBackwards: true, skipCurrent: true }
curPastYear = pastTerm.year; )) {
curPastTerm = pastTerm.term; if (pastTerms.length >= 2 || current.year < minYear) {
if ((await getEventsByTerm(curPastYear, curPastTerm)).length !== 0) { break;
pastTerms.push(pastTerm); }
if ((await getEventsByTerm(current.year, current.term)).length !== 0) {
pastTerms.push(current);
} }
} }
pastTerms.reverse(); pastTerms.reverse();
const maxYear = eventYears[eventYears.length - 1]; const maxYear = eventYears[eventYears.length - 1];
const futureTerms: { year: string; term: Term }[] = []; const futureTerms: TermYear[] = [];
let curFutureYear = year;
let curFutureTerm = term; for (const current of getTermYear(
while ( { year, term },
parseInt(curFutureYear) <= parseInt(maxYear) && { goBackwards: false, skipCurrent: true }
futureTerms.length < 2 )) {
) { if (futureTerms.length >= 2 || maxYear < current.year) {
const futureTerm = getFutureTerm(curFutureYear, curFutureTerm); break;
curFutureYear = futureTerm.year; }
curFutureTerm = futureTerm.term;
if ((await getEventsByTerm(curFutureYear, curFutureTerm)).length !== 0) { if ((await getEventsByTerm(current.year, current.term)).length !== 0) {
futureTerms.push(futureTerm); futureTerms.push(current);
} }
} }
const current = getCurrentTermYear();
return { return {
year: year, year: year,
term: term, term: term,
@ -242,70 +249,3 @@ export async function getEventsPageProps({
futureTerms: futureTerms, 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],
};
}

View File

@ -8,7 +8,7 @@ export interface Member {
program: string; program: string;
} }
export async function getMembers(year: string, term: Term): Promise<Member[]> { export async function getMembers(year: number, term: Term): Promise<Member[]> {
if (process.env.USE_LDAP?.toLowerCase() !== "true") { if (process.env.USE_LDAP?.toLowerCase() !== "true") {
return dummyMembers; return dummyMembers;
} }

View File

@ -7,9 +7,16 @@ import truncateMarkdown from "markdown-truncate";
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 {
isTerm,
import { DATE_FORMAT, getLocalDateFromEST } from "../utils"; Term,
TERMS,
DATE_FORMAT,
getLocalDateFromEST,
TermYear,
getTermYear,
getCurrentTermYear,
} from "@/utils";
export const NEWS_PATH = path.join("content", "news"); export const NEWS_PATH = path.join("content", "news");
@ -96,23 +103,27 @@ export async function getNewsBySlug(
} }
export async function getRecentNews(): Promise<News[]> { export async function getRecentNews(): Promise<News[]> {
const today = new Date(); const terms: TermYear[] = [];
const currentYear = today.getFullYear();
const currentTerm = Math.trunc(today.getMonth() / 4); // Get news for the last two terms
const prevYear = currentTerm > 0 ? currentYear : currentYear - 1; for (const termYear of getTermYear(getCurrentTermYear(), {
const prevTerm = (currentTerm - 1 + 3) % 3; goBackwards: true,
})) {
if (terms.length >= 2) {
break;
}
terms.push(termYear);
}
const news: News[] = ( const news: News[] = (
await Promise.all( await Promise.all(
[ terms.map(async ({ year, term }) => {
{ year: currentYear.toString(), term: currentTerm },
{ year: prevYear.toString(), term: prevTerm },
].map(async ({ year, term }) => {
try { try {
const newsInTerm = await getNewsByTerm(year, TERMS[term]); const newsInTerm = await getNewsByTerm(year.toString(), term);
return await Promise.all( return await Promise.all(
newsInTerm.map((slug) => { newsInTerm.map((slug) => {
return getNewsBySlug(year, TERMS[term], slug, true); return getNewsBySlug(year.toString(), term, slug, true);
}) })
); );
} catch (error) { } catch (error) {

View File

@ -5,12 +5,10 @@ import matter from "gray-matter";
import { Client } from "ldapts"; import { Client } from "ldapts";
import { serialize } from "next-mdx-remote/serialize"; import { serialize } from "next-mdx-remote/serialize";
import { getCurrentTerm } from "@/lib/events"; import { capitalize, TermYear } from "@/utils";
import { capitalize } from "@/utils";
const EXECS_PATH = path.join("content", "team", "execs"); const EXECS_PATH = path.join("content", "team", "execs");
const FILETYPE = ".md"; const FILETYPE = ".md";
const { year, term } = getCurrentTerm();
const execPositions: { [position: string]: string } = { const execPositions: { [position: string]: string } = {
president: "President", president: "President",
@ -34,7 +32,54 @@ export interface Metadata {
image: string; 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") { if (process.env.USE_LDAP?.toLowerCase() !== "true") {
return [["codey", "mascot"]]; return [["codey", "mascot"]];
} }
@ -45,7 +90,7 @@ export async function getExecNamePosPairs() {
// position: name // position: name
const execMembers: { [position: string]: string } = {}; const execMembers: { [position: string]: string } = {};
let formattedExec: [string, string][] = []; let formattedExec: [person: string, position: string][] = [];
try { try {
await client.bind("", ""); await client.bind("", "");
@ -89,40 +134,6 @@ export async function getExecNamePosPairs() {
return formattedExec; 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) { async function getImage(imgPath: string) {
try { try {
await access(path.join("public", imgPath)); await access(path.join("public", imgPath));

70
package-lock.json generated
View File

@ -45,6 +45,7 @@
"postcss-flexbugs-fixes": "^5.0.2", "postcss-flexbugs-fixes": "^5.0.2",
"postcss-preset-env": "^7.0.0", "postcss-preset-env": "^7.0.0",
"ts-node": "^10.2.1", "ts-node": "^10.2.1",
"tsconfig-paths": "^4.1.0",
"typescript": "4.6.4" "typescript": "4.6.4"
}, },
"engines": { "engines": {
@ -1075,7 +1076,7 @@
"node_modules/@types/json5": { "node_modules/@types/json5": {
"version": "0.0.29", "version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true "dev": true
}, },
"node_modules/@types/mdast": { "node_modules/@types/mdast": {
@ -2883,6 +2884,18 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/eslint-plugin-prettier": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz",
@ -7153,15 +7166,29 @@
} }
}, },
"node_modules/tsconfig-paths": { "node_modules/tsconfig-paths": {
"version": "3.14.1", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz",
"integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", "integrity": "sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/json5": "^0.0.29", "json5": "^2.2.1",
"json5": "^1.0.1",
"minimist": "^1.2.6", "minimist": "^1.2.6",
"strip-bom": "^3.0.0" "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": { "node_modules/tslib": {
@ -8550,7 +8577,7 @@
"@types/json5": { "@types/json5": {
"version": "0.0.29", "version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true "dev": true
}, },
"@types/mdast": { "@types/mdast": {
@ -10025,6 +10052,18 @@
"path-parse": "^1.0.7", "path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0" "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==" "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw=="
}, },
"tsconfig-paths": { "tsconfig-paths": {
"version": "3.14.1", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz",
"integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", "integrity": "sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/json5": "^0.0.29", "json5": "^2.2.1",
"json5": "^1.0.1",
"minimist": "^1.2.6", "minimist": "^1.2.6",
"strip-bom": "^3.0.0" "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": { "tslib": {

View File

@ -8,9 +8,10 @@
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "npm run build:images && npm run build:web && npm run build:calendar", "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: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", "start": "next start",
"export": "next export", "export": "next export",
"lint": "eslint \"{pages,components,lib,hooks,scripts}/**/*.{js,ts,tsx,jsx}\" --quiet", "lint": "eslint \"{pages,components,lib,hooks,scripts}/**/*.{js,ts,tsx,jsx}\" --quiet",
@ -56,6 +57,7 @@
"postcss-flexbugs-fixes": "^5.0.2", "postcss-flexbugs-fixes": "^5.0.2",
"postcss-preset-env": "^7.0.0", "postcss-preset-env": "^7.0.0",
"ts-node": "^10.2.1", "ts-node": "^10.2.1",
"tsconfig-paths": "^4.1.0",
"typescript": "4.6.4" "typescript": "4.6.4"
} }
} }

View File

@ -4,15 +4,14 @@ import React from "react";
import { Link } from "@/components/Link"; import { Link } from "@/components/Link";
import { Table } from "@/components/Table"; import { Table } from "@/components/Table";
import { Title } from "@/components/Title"; import { Title } from "@/components/Title";
import { getCurrentTerm } from "@/lib/events";
import { getMembers, Member } from "@/lib/members"; import { getMembers, Member } from "@/lib/members";
import { Term, capitalize } from "@/utils"; import { Term, capitalize, getCurrentTermYear } 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: number;
term: Term; term: Term;
} }
@ -56,7 +55,7 @@ export default function Members(props: Props) {
} }
export const getStaticProps: GetStaticProps<Props> = async () => { export const getStaticProps: GetStaticProps<Props> = async () => {
const curTerm = getCurrentTerm(); const curTerm = getCurrentTermYear();
return { return {
props: { props: {
year: curTerm.year, year: curTerm.year,

View File

@ -9,12 +9,8 @@ import { Link } from "@/components/Link";
import { TeamMember } from "@/components/TeamMember"; import { TeamMember } from "@/components/TeamMember";
import { TeamMemberCard } from "@/components/TeamMemberCard"; import { TeamMemberCard } from "@/components/TeamMemberCard";
import { Title } from "@/components/Title"; import { Title } from "@/components/Title";
import { import { getExecs, Metadata, getMemberImagePath } from "@/lib/team";
getExec, import { getCurrentTermYear } from "@/utils";
getExecNamePosPairs,
Metadata,
getMemberImagePath,
} from "@/lib/team";
import designData from "../../content/team/design-team.json"; import designData from "../../content/team/design-team.json";
import discordData from "../../content/team/discord-team.json"; import discordData from "../../content/team/discord-team.json";
@ -224,13 +220,7 @@ function sortTeam(team: Metadata[]): Metadata[] {
} }
export const getStaticProps: GetStaticProps<Props> = async () => { export const getStaticProps: GetStaticProps<Props> = async () => {
const execNamePosPairs = await getExecNamePosPairs(); const execs = await getExecs(getCurrentTermYear());
const execs = (await Promise.all(
execNamePosPairs.map((namePosPair) =>
getExec(namePosPair[0], namePosPair[1])
)
)) as SerializedExec[];
let [ let [
design, design,

View File

@ -63,7 +63,11 @@ export const getStaticProps: GetStaticProps<Props, Params> = async (
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const { year, term, event } = context.params!; const { year, term, event } = context.params!;
return { 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<Params> = async () => {
termsInYear.map(async (term) => { termsInYear.map(async (term) => {
const eventsInTerm = await getEventsByTerm(year, term); const eventsInTerm = await getEventsByTerm(year, term);
return eventsInTerm.map((event) => ({ return eventsInTerm.map((event) => ({
year, year: year.toString(),
term, term,
event, event,
})); }));

View File

@ -10,22 +10,22 @@ import { MiniEventCard } from "@/components/MiniEventCard";
import { Title } from "@/components/Title"; import { Title } from "@/components/Title";
import { import {
Event, Event,
getEventsPageProps,
getEventYears, getEventYears,
getEventTermsByYear, getEventTermsByYear,
getEventsPageProps,
} from "@/lib/events"; } from "@/lib/events";
import { capitalize, Term } from "@/utils"; import { capitalize, Term, TermYear } from "@/utils";
import styles from "./index.module.css"; import styles from "./index.module.css";
export interface Props { export interface Props {
year: string; year: number;
term: Term; term: Term;
pastEvents: Event[]; pastEvents: Event[];
futureEvents: Event[]; futureEvents: Event[];
isCurrentTerm: boolean; isCurrentTerm: boolean;
pastTerms: { year: string; term: Term }[]; pastTerms: TermYear[];
futureTerms: { year: string; term: Term }[]; futureTerms: TermYear[];
} }
export default function TermPage(props: Props) { export default function TermPage(props: Props) {
@ -61,7 +61,7 @@ export default function TermPage(props: Props) {
<HeaderLink <HeaderLink
{...link} {...link}
isCurrentTerm={link.year === props.year && link.term === props.term} isCurrentTerm={link.year === props.year && link.term === props.term}
key={link.year + link.term} key={`${link.year}${link.term}`}
/> />
))} ))}
<Link href="/events/archive">Archive</Link> <Link href="/events/archive">Archive</Link>
@ -129,7 +129,7 @@ export default function TermPage(props: Props) {
} }
function HeaderLink(props: { function HeaderLink(props: {
year: string; year: number;
term: Term; term: Term;
isCurrentTerm?: boolean; isCurrentTerm?: boolean;
}) { }) {
@ -151,7 +151,14 @@ export const getStaticProps: GetStaticProps<Props, Params> = async (
context context
) => { ) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // 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<Params> = async () => { export const getStaticPaths: GetStaticPaths<Params> = async () => {
@ -161,7 +168,7 @@ export const getStaticPaths: GetStaticPaths<Params> = async () => {
years.map(async (year) => { years.map(async (year) => {
const terms = await getEventTermsByYear(year); const terms = await getEventTermsByYear(year);
return terms.map((curTerm) => ({ return terms.map((curTerm) => ({
params: { year: year, term: curTerm }, params: { year: year.toString(), term: curTerm },
})); }));
}) })
) )

View File

@ -47,7 +47,7 @@ export const getStaticProps: GetStaticProps<Props, Params> = async (
return { return {
props: { props: {
year: year, year: year,
terms: await getEventTermsByYear(year), terms: await getEventTermsByYear(parseInt(year)),
}, },
}; };
}; };
@ -55,7 +55,7 @@ export const getStaticProps: GetStaticProps<Props, Params> = async (
export const getStaticPaths: GetStaticPaths<Params> = async () => { export const getStaticPaths: GetStaticPaths<Params> = async () => {
const years = await getEventYears(); const years = await getEventYears();
const paths = years.map((curYear) => ({ const paths = years.map((curYear) => ({
params: { year: curYear }, params: { year: curYear.toString() },
})); }));
return { return {
paths: paths, paths: paths,

View File

@ -9,7 +9,7 @@ export const getStaticProps: GetStaticProps<Props> = async () => {
const years = (await getEventYears()).reverse(); const years = (await getEventYears()).reverse();
const yearsWithTerms = await Promise.all( const yearsWithTerms = await Promise.all(
years.map(async (year) => ({ years.map(async (year) => ({
year, year: year.toString(),
terms: (await getEventTermsByYear(year)).reverse(), terms: (await getEventTermsByYear(year)).reverse(),
})) }))
); );

View File

@ -1,11 +1,12 @@
import { GetStaticProps } from "next"; 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]"; import TermPage, { Props } from "./[year]/[term]";
export default TermPage; export default TermPage;
export const getStaticProps: GetStaticProps<Props> = async () => { export const getStaticProps: GetStaticProps<Props> = async () => {
return { props: await getEventsPageProps(getCurrentTerm()) }; return { props: await getEventsPageProps(getCurrentTermYear()) };
}; };

13
scripts/api/members.ts Normal file
View File

@ -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();

View File

@ -3,30 +3,35 @@ import path from "path";
import { format } from "date-fns"; 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"; import { DATE_FORMAT } from "@/utils";
/* /*
Note: Note:
This script will not work for events by default anymore, since events now have startDate instead of endDate 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"); const EVENTS_PATH = path.join("content", "events");
export async function main() { export async function main() {
for (const year of await getEventYears()) { for (const year of await getEventYears()) {
for (const term of await getEventTermsByYear(year)) { for (const term of await getEventTermsByYear(year)) {
for (const slug of await getEventsByTerm(year, term)) { 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"); const file = await fs.readFile(filePath, "utf-8");
await fs.writeFile(filePath, replaceDate(file)); await fs.writeFile(filePath, replaceDate(file));

View File

@ -4,7 +4,7 @@ import path from "path";
import { addHours } from "date-fns"; import { addHours } from "date-fns";
import ical, { ICalCalendarMethod } from "ical-generator"; import ical, { ICalCalendarMethod } from "ical-generator";
import { getAllEvents } from "../lib/events"; import { getAllEvents } from "@/lib/events";
export async function generateCalendar() { export async function generateCalendar() {
const events = await getAllEvents(); const events = await getAllEvents();

View File

@ -21,3 +21,68 @@ export function getLocalDateFromEST(date: Date): Date {
Intl.DateTimeFormat().resolvedOptions().timeZone 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;
}