From bb7bc999420ebce55e995532ff131b2f55b96236 Mon Sep 17 00:00:00 2001 From: Roger Fan Date: Wed, 10 Aug 2022 21:55:59 -0700 Subject: [PATCH 1/5] Update sgyfetch function on firebase functions --- .../src/components/classes/ClassesLayout.tsx | 2 +- client/src/contexts/UserDataContext.ts | 4 +- functions/src/sgyfetch.ts | 46 ++++++++++++------- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/client/src/components/classes/ClassesLayout.tsx b/client/src/components/classes/ClassesLayout.tsx index 598d6840..1fab280a 100644 --- a/client/src/components/classes/ClassesLayout.tsx +++ b/client/src/components/classes/ClassesLayout.tsx @@ -75,7 +75,7 @@ export default function ClassesLayout() { let needToReset = false; let classes: {[key:string]: any} = {}; - const periods: SgyPeriod[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', 'S']; + const periods: SgyPeriod[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', 'S', 'P', 'H']; for (const p of periods) { const course = userData.classes[p]; diff --git a/client/src/contexts/UserDataContext.ts b/client/src/contexts/UserDataContext.ts index 0ead76e0..dbf5fbe3 100644 --- a/client/src/contexts/UserDataContext.ts +++ b/client/src/contexts/UserDataContext.ts @@ -52,9 +52,9 @@ export type SgyData = { grades: SectionGrade[]; 1?: SgyCourseData, 2?: SgyCourseData, 3?: SgyCourseData, 4?: SgyCourseData, 5?: SgyCourseData, 6?: SgyCourseData, 7?: SgyCourseData, S?: SgyCourseData, - 0?: SgyCourseData, 8?: SgyCourseData + 0?: SgyCourseData, 8?: SgyCourseData, P?: SgyCourseData, H?: SgyCourseData }; -export type SgyPeriod = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '0' | 'S'; +export type SgyPeriod = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '0' | 'S' | 'P' | 'H'; export const defaultUserData: UserData = { clubs: [], diff --git a/functions/src/sgyfetch.ts b/functions/src/sgyfetch.ts index a4ef73ca..3f4e266e 100644 --- a/functions/src/sgyfetch.ts +++ b/functions/src/sgyfetch.ts @@ -3,9 +3,12 @@ import admin from './util/adminInit'; import {get} from './util/sgyOAuth'; const SEMESTER = 1; // We are in semester 1 +const CURRENT_YEAR = 2022; // will work for 2022-2023 school year + +const YEAR_STR = `${CURRENT_YEAR}-${CURRENT_YEAR+1}`; const firestore = admin.firestore(); -const periods = ['0', '1', '2', '3', '4', '5', '6', '7', '8', 'SELF']; +const periods = ['0', '1', '2', '3', '4', '5', '6', '7', '8', 'S', 'P', 'H']; // Get a user's stored firestore sgy info @@ -18,16 +21,9 @@ async function getSgyInfo(uid: string) { } function getClassInfo(info: string) { - const words = info.split(' '); - const pRegex = info.match(/\((.+)\)/); - let term = null; - if(pRegex) { - const parenblock = pRegex[1]; // 2696 1 FY - const items = parenblock.split(' '); // [2696, 1, FY] - term = items[items.length - 1]; // FY - } - - return { pName: words[0], pTeacher: words[1], term }; + const match = info.match(/(.+?) (\w+) \(\d+ \d+ (\w+)\)/); + if (!match) return { pName: '', pTeacher: '', term: null }; + return { pName: match[1], pTeacher: match[2], term: match[3] }; } type SgyPeriodData = {n: string, c: string, l: string, o: string, s: string}; @@ -51,16 +47,19 @@ export const init = functions.https.onCall(async (data, context) => { const classes: {[key: string]: SgyPeriodData} = {}; for (const p in periods) { - classes[p[0]] = { n: '', c: '', l: '', o: '', s: '' }; + classes[p] = { n: '', c: '', l: '', o: '', s: '' }; } const teachers: {[key: string]: [string, string]} = {}; for (const element of sgyClasses) { let {pName, pTeacher, term} = getClassInfo(element['section_title']); if (term === `S${3-SEMESTER}`) continue; // 3 - SEMESTER will rule out S1 courses if SEMESTER is 2, and S2 courses if SEMESTER is 1 + + if (pName === 'SELF') pName = 'S' + if (pName === 'PRIME') pName = 'P' + if (pName === 'Study Hall') pName = 'H' + if (periods.includes(pName)) { - if (pName === 'SELF') pName = 'S' - if (pName === 'PRIME') pName = 'P' classes[pName] = { n: `${element.course_title} · ${pTeacher}`, c: sgyInfo.classes[pName].c, @@ -132,10 +131,22 @@ export const fetchMaterials = functions.https.onCall(async (data, context) => { // Fetch courses, then kick out yucky ones // TODO: type this better - let courses = (await get(`users/${sgyInfo.uid}/sections`, sgyInfo.key, sgyInfo.sec)).section + const courses_unfiltered = await get(`users/${sgyInfo.uid}/sections`, sgyInfo.key, sgyInfo.sec); + const gunn_student_course = courses_unfiltered.section.find((sec: {section_title: string}) => { + const title = sec.section_title.toLowerCase(); + return (title.endsWith(YEAR_STR) && title !== YEAR_STR); + }) + const grad_year = ['se', 'ju', 'so', 'fr'].indexOf(gunn_student_course.section_title.slice(0,2)) + CURRENT_YEAR + 1; + + let courses = courses_unfiltered.section .filter((sec: {section_title: string}) => { - const {pName, term} = getClassInfo(sec.section_title); + let {pName, term} = getClassInfo(sec.section_title); if (term === `S${3 - SEMESTER}`) return false; // 3 - SEMESTER will rule out S1 courses if SEMESTER is 2, and S2 courses if SEMESTER is 1 + + if (pName === 'SELF') pName = 'S' + if (pName === 'PRIME') pName = 'P' + if (pName === 'Study Hall') pName = 'H' + return periods.includes(pName); }); @@ -159,7 +170,7 @@ export const fetchMaterials = functions.https.onCall(async (data, context) => { const responses = await Promise.all(promises); // TODO: type this better - const sections: {[key: string]: {info: any, documents: any, assignments: any, pages: any, events: any}} = {}; + const sections: {[key: string]: {info: any, documents: any, assignments: any, pages: any, events: any}} & { grad_year?: number } = {}; // Unflattening smh my head my head my head for (let i = 0; i < courses.length; i++) { @@ -181,6 +192,7 @@ export const fetchMaterials = functions.https.onCall(async (data, context) => { } sections.grades = (await grades).section; + sections.grad_year = grad_year; return sections; From 0725e3ffbdfd49419787c653e565914504ff8d91 Mon Sep 17 00:00:00 2001 From: Kevin Yu Date: Wed, 10 Aug 2022 22:59:17 -0700 Subject: [PATCH 2/5] Use client types for better type safety --- client/src/contexts/UserDataContext.ts | 10 +++++----- functions/src/sgyfetch.ts | 26 ++++++++++++++------------ 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/client/src/contexts/UserDataContext.ts b/client/src/contexts/UserDataContext.ts index dbf5fbe3..3a194989 100644 --- a/client/src/contexts/UserDataContext.ts +++ b/client/src/contexts/UserDataContext.ts @@ -49,12 +49,12 @@ type SgyCourseData = { events: Event[]; } export type SgyData = { - grades: SectionGrade[]; - 1?: SgyCourseData, 2?: SgyCourseData, 3?: SgyCourseData, 4?: SgyCourseData, - 5?: SgyCourseData, 6?: SgyCourseData, 7?: SgyCourseData, S?: SgyCourseData, - 0?: SgyCourseData, 8?: SgyCourseData, P?: SgyCourseData, H?: SgyCourseData + grades: SectionGrade[], + gradYear: number +} & { + [P in SgyPeriod]?: SgyCourseData }; -export type SgyPeriod = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '0' | 'S' | 'P' | 'H'; +export type SgyPeriod = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | 'S' | 'P' | 'H'; export const defaultUserData: UserData = { clubs: [], diff --git a/functions/src/sgyfetch.ts b/functions/src/sgyfetch.ts index 3f4e266e..1350cfcb 100644 --- a/functions/src/sgyfetch.ts +++ b/functions/src/sgyfetch.ts @@ -1,6 +1,7 @@ import * as functions from 'firebase-functions'; import admin from './util/adminInit'; import {get} from './util/sgyOAuth'; +import {SgyData, SgyPeriod, SgyPeriodData} from '@watt/client/src/contexts/UserDataContext'; const SEMESTER = 1; // We are in semester 1 const CURRENT_YEAR = 2022; // will work for 2022-2023 school year @@ -20,14 +21,15 @@ async function getSgyInfo(uid: string) { return {uid: creds.sgy.uid, key: creds.sgy.key, sec: creds.sgy.sec, classes: creds.classes}; } +// Parses info about the given schoology info string. Ex. +// `"Study Hall Kaneko (9813 43 FY)"` -> `{ pName: "Study Hall", pTeacher: "Kaneko", term: "FY" }` +// `"2 Liberatore (9813 43 FY)"` -> `{ pName: "2", pTeacher: "Liberatore", term: "FY" }` function getClassInfo(info: string) { const match = info.match(/(.+?) (\w+) \(\d+ \d+ (\w+)\)/); if (!match) return { pName: '', pTeacher: '', term: null }; return { pName: match[1], pTeacher: match[2], term: match[3] }; } -type SgyPeriodData = {n: string, c: string, l: string, o: string, s: string}; - // sgyfetch-init // Populate the user's period customization with fetched class names from schoology, @@ -131,14 +133,14 @@ export const fetchMaterials = functions.https.onCall(async (data, context) => { // Fetch courses, then kick out yucky ones // TODO: type this better - const courses_unfiltered = await get(`users/${sgyInfo.uid}/sections`, sgyInfo.key, sgyInfo.sec); - const gunn_student_course = courses_unfiltered.section.find((sec: {section_title: string}) => { + const unfiltered = await get(`users/${sgyInfo.uid}/sections`, sgyInfo.key, sgyInfo.sec); + const gunnStudentCourse = unfiltered.section.find((sec: {section_title: string}) => { const title = sec.section_title.toLowerCase(); return (title.endsWith(YEAR_STR) && title !== YEAR_STR); }) - const grad_year = ['se', 'ju', 'so', 'fr'].indexOf(gunn_student_course.section_title.slice(0,2)) + CURRENT_YEAR + 1; + const grad_year = ['se', 'ju', 'so', 'fr'].indexOf(gunnStudentCourse.section_title.slice(0,2)) + CURRENT_YEAR + 1; - let courses = courses_unfiltered.section + let courses = unfiltered.section .filter((sec: {section_title: string}) => { let {pName, term} = getClassInfo(sec.section_title); if (term === `S${3 - SEMESTER}`) return false; // 3 - SEMESTER will rule out S1 courses if SEMESTER is 2, and S2 courses if SEMESTER is 1 @@ -169,8 +171,11 @@ export const fetchMaterials = functions.https.onCall(async (data, context) => { // Rip we have to flatten promises const responses = await Promise.all(promises); - // TODO: type this better - const sections: {[key: string]: {info: any, documents: any, assignments: any, pages: any, events: any}} & { grad_year?: number } = {}; + // Return an object of type `SgyData` containing relevant data. + const sections: SgyData = { + grades: (await grades).section, + gradYear: grad_year + } // Unflattening smh my head my head my head for (let i = 0; i < courses.length; i++) { @@ -182,7 +187,7 @@ export const fetchMaterials = functions.https.onCall(async (data, context) => { const pages = responses[4 * i + 2].page; const events = responses[4 * i + 3].event; - sections[course.section_title.split(' ')[0][0]] = { + sections[course.section_title.split(' ')[0][0] as SgyPeriod] = { info: course, documents, assignments, @@ -191,9 +196,6 @@ export const fetchMaterials = functions.https.onCall(async (data, context) => { }; } - sections.grades = (await grades).section; - sections.grad_year = grad_year; - return sections; } catch (e) { From a84ad50af8f236fcc74e9724e4f1ae05e3d26466 Mon Sep 17 00:00:00 2001 From: Kevin Yu Date: Wed, 10 Aug 2022 23:11:38 -0700 Subject: [PATCH 3/5] Fix type error --- client/src/components/classes/ClassesLayout.tsx | 4 ++-- client/src/contexts/SgyDataContext.ts | 2 +- functions/src/sgyfetch.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/components/classes/ClassesLayout.tsx b/client/src/components/classes/ClassesLayout.tsx index 1fab280a..d6c3b9c7 100644 --- a/client/src/components/classes/ClassesLayout.tsx +++ b/client/src/components/classes/ClassesLayout.tsx @@ -15,7 +15,7 @@ import { updateUserData } from '../../util/firestore'; // Contexts import CurrentTimeContext from '../../contexts/CurrentTimeContext'; -import UserDataContext, { SgyPeriod, SgyData, UserData } from '../../contexts/UserDataContext'; +import UserDataContext, {SgyPeriod, SgyData, UserData, SgyFetchResponse} from '../../contexts/UserDataContext'; import { SgyDataProvider } from '../../contexts/SgyDataContext'; // Utilities @@ -28,7 +28,7 @@ export async function fetchSgyMaterials(functions: Functions) { localStorage.setItem('sgy-last-attempted-fetch', '' + Date.now()); // HttpsCallable where T is the type of the arguments to the callable and K is the return type - const fetchMaterials = httpsCallable(functions, 'sgyfetch-fetchMaterials'); + const fetchMaterials = httpsCallable(functions, 'sgyfetch-fetchMaterials'); const res = await fetchMaterials(); localStorage.setItem('sgy-data', JSON.stringify(res.data)); diff --git a/client/src/contexts/SgyDataContext.ts b/client/src/contexts/SgyDataContext.ts index 6bc8d935..2f76c9e2 100644 --- a/client/src/contexts/SgyDataContext.ts +++ b/client/src/contexts/SgyDataContext.ts @@ -10,7 +10,7 @@ export type SgyContext = { updateSgy: () => Promise, }; -const DefaultSgyContext:SgyContext = { +const DefaultSgyContext: SgyContext = { sgyData: { grades: [] }, diff --git a/functions/src/sgyfetch.ts b/functions/src/sgyfetch.ts index 1350cfcb..30928255 100644 --- a/functions/src/sgyfetch.ts +++ b/functions/src/sgyfetch.ts @@ -1,7 +1,7 @@ import * as functions from 'firebase-functions'; import admin from './util/adminInit'; import {get} from './util/sgyOAuth'; -import {SgyData, SgyPeriod, SgyPeriodData} from '@watt/client/src/contexts/UserDataContext'; +import {SgyData, SgyFetchResponse, SgyPeriod, SgyPeriodData} from '@watt/client/src/contexts/UserDataContext'; const SEMESTER = 1; // We are in semester 1 const CURRENT_YEAR = 2022; // will work for 2022-2023 school year @@ -172,7 +172,7 @@ export const fetchMaterials = functions.https.onCall(async (data, context) => { const responses = await Promise.all(promises); // Return an object of type `SgyData` containing relevant data. - const sections: SgyData = { + const sections: SgyFetchResponse = { grades: (await grades).section, gradYear: grad_year } From fc6b7a824ac2d94ea62a5b37ee58ce4e7ccbafc0 Mon Sep 17 00:00:00 2001 From: Kevin Yu Date: Wed, 10 Aug 2022 23:13:41 -0700 Subject: [PATCH 4/5] Remember to commit this file --- client/src/contexts/UserDataContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/contexts/UserDataContext.ts b/client/src/contexts/UserDataContext.ts index 3a194989..a751863a 100644 --- a/client/src/contexts/UserDataContext.ts +++ b/client/src/contexts/UserDataContext.ts @@ -50,10 +50,10 @@ type SgyCourseData = { } export type SgyData = { grades: SectionGrade[], - gradYear: number } & { [P in SgyPeriod]?: SgyCourseData }; +export type SgyFetchResponse = SgyData & {gradYear: number}; export type SgyPeriod = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | 'S' | 'P' | 'H'; export const defaultUserData: UserData = { From af50813cfd3f04a04fefc878983b7cf246e7f3b1 Mon Sep 17 00:00:00 2001 From: Roger Fan Date: Thu, 11 Aug 2022 07:17:36 -0700 Subject: [PATCH 5/5] Fix a minor bug Kevin found --- functions/src/sgyfetch.ts | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/functions/src/sgyfetch.ts b/functions/src/sgyfetch.ts index 30928255..5e58ac9e 100644 --- a/functions/src/sgyfetch.ts +++ b/functions/src/sgyfetch.ts @@ -22,12 +22,21 @@ async function getSgyInfo(uid: string) { } // Parses info about the given schoology info string. Ex. -// `"Study Hall Kaneko (9813 43 FY)"` -> `{ pName: "Study Hall", pTeacher: "Kaneko", term: "FY" }` +// `"Study Hall Kaneko (9813 43 FY)"` -> `{ pName: "H", pTeacher: "Kaneko", term: "FY" }` // `"2 Liberatore (9813 43 FY)"` -> `{ pName: "2", pTeacher: "Liberatore", term: "FY" }` +// Note: regarding pName, it changes "Study Hall" into "H", "SELF" into "S", and "PRIME" into "P" function getClassInfo(info: string) { const match = info.match(/(.+?) (\w+) \(\d+ \d+ (\w+)\)/); if (!match) return { pName: '', pTeacher: '', term: null }; - return { pName: match[1], pTeacher: match[2], term: match[3] }; + return { pName: pNameToLetter(match[1]), pTeacher: match[2], term: match[3] }; +} + +// Turns "Study Hall" into "H" +function pNameToLetter(pName: string) { + if (pName === 'SELF') return 'S' + if (pName === 'PRIME') return 'P' + if (pName === 'Study Hall') return 'H' + return pName; } @@ -57,10 +66,6 @@ export const init = functions.https.onCall(async (data, context) => { let {pName, pTeacher, term} = getClassInfo(element['section_title']); if (term === `S${3-SEMESTER}`) continue; // 3 - SEMESTER will rule out S1 courses if SEMESTER is 2, and S2 courses if SEMESTER is 1 - if (pName === 'SELF') pName = 'S' - if (pName === 'PRIME') pName = 'P' - if (pName === 'Study Hall') pName = 'H' - if (periods.includes(pName)) { classes[pName] = { n: `${element.course_title} · ${pTeacher}`, @@ -145,10 +150,6 @@ export const fetchMaterials = functions.https.onCall(async (data, context) => { let {pName, term} = getClassInfo(sec.section_title); if (term === `S${3 - SEMESTER}`) return false; // 3 - SEMESTER will rule out S1 courses if SEMESTER is 2, and S2 courses if SEMESTER is 1 - if (pName === 'SELF') pName = 'S' - if (pName === 'PRIME') pName = 'P' - if (pName === 'Study Hall') pName = 'H' - return periods.includes(pName); }); @@ -187,7 +188,9 @@ export const fetchMaterials = functions.https.onCall(async (data, context) => { const pages = responses[4 * i + 2].page; const events = responses[4 * i + 3].event; - sections[course.section_title.split(' ')[0][0] as SgyPeriod] = { + const { pName } = getClassInfo(course.section_title) as { pName: SgyPeriod }; + + sections[pName] = { info: course, documents, assignments,