From e8d5c8ee4329799b19dff4423edf39c4a064b4e1 Mon Sep 17 00:00:00 2001 From: MinhxNguyen7 <64875104+MinhxNguyen7@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:01:41 -0800 Subject: [PATCH] feat(api): read from RDS --- apps/backend/src/lib/rds.ts | 123 +++++++++++++++++++++++++++++- apps/backend/src/routers/users.ts | 9 ++- 2 files changed, 128 insertions(+), 4 deletions(-) diff --git a/apps/backend/src/lib/rds.ts b/apps/backend/src/lib/rds.ts index 868398903..355dd59f2 100644 --- a/apps/backend/src/lib/rds.ts +++ b/apps/backend/src/lib/rds.ts @@ -2,7 +2,11 @@ import { ShortCourse, ShortCourseSchedule, User, RepeatingCustomEvent } from '@p import { and, eq } from 'drizzle-orm'; import type { Database } from '$db/index'; -import { schedules, users, accounts, coursesInSchedule, customEvents } from '$db/schema'; +import { + schedules, users, accounts, coursesInSchedule, customEvents, + Schedule, CourseInSchedule, CustomEvent, + AccountType +} from '$db/schema'; type DatabaseOrTransaction = Omit; @@ -158,6 +162,123 @@ export class RDS { ); } + static async getGuestUserData( + db: DatabaseOrTransaction, guestId: string + ): Promise { + const userAndAccount = await RDS.getUserAndAccount(db, 'GUEST', guestId); + if (!userAndAccount) { + return null; + } + + const userId = userAndAccount.user.id; + + const sectionResults = await db + .select() + .from(schedules) + .where(eq(schedules.userId, userId)) + .leftJoin(coursesInSchedule, eq(schedules.id, coursesInSchedule.scheduleId)) + + const customEventResults = await db + .select() + .from(schedules) + .where(eq(schedules.userId, userId)) + .leftJoin(customEvents, eq(schedules.id, customEvents.scheduleId)); + + const userSchedules = RDS.aggregateUserData(sectionResults, customEventResults); + + const scheduleIndex = userAndAccount.user.currentScheduleId + ? userSchedules.findIndex((schedule) => schedule.id === userAndAccount.user.currentScheduleId) + : userSchedules.length; + + return { + id: guestId, + userData: { + schedules: userSchedules, + scheduleIndex, + }, + } + } + + private static async getUserAndAccount( + db: DatabaseOrTransaction, accountType: AccountType, providerAccountId: string + ) { + const res = await db + .select() + .from(accounts) + .where(and(eq(accounts.accountType, accountType), eq(accounts.providerAccountId, providerAccountId))) + .leftJoin(users, eq(accounts.userId, users.id)) + .limit(1); + + if (res.length === 0 || res[0].users === null || res[0].accounts === null) { + return null; + } + + return { user: res[0].users, account: res[0].accounts }; + } + + /** + * Aggregates the user's schedule data from the results of two queries. + */ + private static aggregateUserData( + sectionResults: {schedules: Schedule, coursesInSchedule: CourseInSchedule | null}[], + customEventResults: {schedules: Schedule, customEvents: CustomEvent | null}[] + ): (ShortCourseSchedule & { id: string, index: number })[] { + // Map from schedule ID to schedule data + const schedulesMapping: Record = {}; + + // Add courses to schedules + sectionResults.forEach(({ schedules: schedule, coursesInSchedule: course }) => { + const scheduleId = schedule.id; + + const scheduleAggregate = schedulesMapping[scheduleId] || { + id: scheduleId, + scheduleName: schedule.name, + scheduleNote: schedule.notes, + courses: [], + customEvents: [], + index: schedule.index, + }; + + if (course) { + scheduleAggregate.courses.push({ + sectionCode: course.sectionCode.toString(), + term: course.term, + color: course.color, + }); + } + + schedulesMapping[scheduleId] = scheduleAggregate; + }); + + // Add custom events to schedules + customEventResults.forEach(({ schedules: schedule, customEvents: customEvent }) => { + const scheduleId = schedule.id; + const scheduleAggregate = schedulesMapping[scheduleId] || { + scheduleName: schedule.name, + scheduleNote: schedule.notes, + courses: [], + customEvents: [], + }; + + if (customEvent) { + scheduleAggregate.customEvents.push({ + customEventID: customEvent.id, + title: customEvent.title, + start: customEvent.start, + end: customEvent.end, + days: customEvent.days.split('').map((day) => day === '1'), + color: customEvent.color ?? undefined, + building: customEvent.building ?? undefined, + }); + } + + schedulesMapping[scheduleId] = scheduleAggregate; + }); + + // Sort schedules by index + return Object.values(schedulesMapping).sort((a, b) => a.index - b.index); + } + /** * Drops all courses in the schedule and re-add them, * deduplicating by section code and term. diff --git a/apps/backend/src/routers/users.ts b/apps/backend/src/routers/users.ts index dbe33791f..75aad0e45 100644 --- a/apps/backend/src/routers/users.ts +++ b/apps/backend/src/routers/users.ts @@ -3,9 +3,9 @@ import { type } from 'arktype'; import { UserSchema } from '@packages/antalmanac-types'; import { db } from 'src/db'; -import { ddbClient } from 'src/db/ddb'; import { mangleDupliateScheduleNames } from 'src/lib/formatting'; import { RDS } from 'src/lib/rds'; +import { TRPCError } from '@trpc/server'; import { procedure, router } from '../trpc'; const userInputSchema = type([{ userId: 'string' }, '|', { googleId: 'string' }]); @@ -43,9 +43,12 @@ const usersRouter = router({ */ getUserData: procedure.input(userInputSchema.assert).query(async ({ input }) => { if ('googleId' in input) { - return await ddbClient.getGoogleUserData(input.googleId); + throw new TRPCError({ + code: 'NOT_IMPLEMENTED', + message: 'Google login not implemented', + }) } - return await ddbClient.getUserData(input.userId); + return await RDS.getGuestUserData(db, input.userId); }), /**