From c2c7e2a913b74f88f7da3dcfb1837754fbbcbde7 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Mon, 4 Nov 2024 14:04:12 -0500 Subject: [PATCH] Add endpoints for getting merged classes and adding a class to a merge group. --- src/stories/hubbles_law/database.ts | 70 +++------------------- src/stories/hubbles_law/router.ts | 91 ++++++++++++++++++----------- 2 files changed, 65 insertions(+), 96 deletions(-) diff --git a/src/stories/hubbles_law/database.ts b/src/stories/hubbles_law/database.ts index 86fc962..8051e6d 100644 --- a/src/stories/hubbles_law/database.ts +++ b/src/stories/hubbles_law/database.ts @@ -320,7 +320,7 @@ async function getClassIDsForSyncClass(classID: number): Promise { return classIDs; } -async function getMergedIDsForClass(classID: number): Promise { +export async function getMergedIDsForClass(classID: number): Promise { // TODO: Currently this uses two queries: // The first to get the merge group (if there is one) // Then a second to get all of the classes in the merge group @@ -758,35 +758,12 @@ export async function getMergeDataForClass(classID: number): Promise { - const size = await classSize(classID); - - // Running into the limits of the ORM a bit here - // Maybe there's a clever way to write this? - // But straight SQL gets the job done - return database.query( - `SELECT * FROM (SELECT - id, - test, - (SELECT - COUNT(*) - FROM - StudentsClasses - WHERE - StudentsClasses.class_id = id) AS size - FROM - Classes) q - WHERE - (size >= ${sizeThreshold - size} AND test = 0) - `, { type: QueryTypes.SELECT }) as Promise; -} - type GroupData = { unique_gid: string; is_group: boolean; merged_count: number; }; -export async function findClassForMerge(database: Sequelize): Promise { +export async function findClassForMerge(database: Sequelize, classID: number): Promise { // The SQL is complicated enough here; doing this with the ORM // will probably be unreadable const result = await database.query( @@ -807,6 +784,7 @@ export async function findClassForMerge(database: Sequelize): Promise= 12 ) C ON Classes.id = C.class_id + WHERE id != ${classID} GROUP BY unique_gid ORDER BY is_group ASC, group_count ASC, merged_count DESC LIMIT 1; @@ -822,7 +800,7 @@ async function nextMergeGroupID(): Promise { [Sequelize.fn("MAX", Sequelize.col("group_id")), "group_id"] ] })) as (HubbleClassMergeGroup & { group_id: number })[]; - return max[0].group_id as number; + return (max[0].group_id + 1) as number; } export async function addClassToMergeGroup(classID: number): Promise { @@ -838,50 +816,16 @@ export async function addClassToMergeGroup(classID: number): Promise { - const cls = await findClassById(classID); - if (cls === null) { - return { mergeData: null, message: "Invalid class ID!" }; - } - - let mergeData = await getMergeDataForClass(classID); - if (mergeData !== null) { - return { mergeData, message: "Class already merged" }; - } - - const eligibleClasses = await eligibleClassesForMerge(db, classID); - if (eligibleClasses.length > 0) { - const index = Math.floor(Math.random() * eligibleClasses.length); - const classToMerge = eligibleClasses[index]; - mergeData = await SyncMergedHubbleClasses.create({ class_id: classID, merged_class_id: classToMerge.id }); - if (mergeData === null) { - return { mergeData, message: "Error creating merge!" }; - } - return { mergeData, message: "New merge created" }; - } - - return { mergeData: null, message: "No eligible classes to merge" }; - -} diff --git a/src/stories/hubbles_law/router.ts b/src/stories/hubbles_law/router.ts index e721ac0..1443a62 100644 --- a/src/stories/hubbles_law/router.ts +++ b/src/stories/hubbles_law/router.ts @@ -1,3 +1,6 @@ +import * as S from "@effect/schema/Schema"; +import * as Either from "effect/Either"; + import { Galaxy } from "./models/galaxy"; import { @@ -32,9 +35,10 @@ import { getGalaxyById, removeSampleHubbleMeasurement, getAllNthSampleHubbleMeasurements, - tryToMergeClass, getClassMeasurementCount, - getStudentsWithCompleteMeasurementsCount + getStudentsWithCompleteMeasurementsCount, + getMergedIDsForClass, + addClassToMergeGroup } from "./database"; import { @@ -45,7 +49,7 @@ import { import { Express, Router } from "express"; import { Sequelize } from "sequelize"; import { classForStudentStory, findClassById, findStudentById } from "../../database"; -import { SyncMergedHubbleClasses, initializeModels } from "./models"; +import { initializeModels } from "./models"; import { setUpHubbleAssociations } from "./associations"; export const router = Router(); @@ -395,51 +399,72 @@ router.get(["/class-measurements/:studentID", "stage-3-measurements/:studentID"] }); }); -router.get("/all-data", async (req, res) => { - const minimal = (req.query?.minimal as string)?.toLowerCase() === "true"; - const beforeMs: number = parseInt(req.query.before as string); - const before = isNaN(beforeMs) ? null : new Date(beforeMs); - const [measurements, studentData, classData] = - await Promise.all([ - getAllHubbleMeasurements(before, minimal), - getAllHubbleStudentData(before, minimal), - getAllHubbleClassData(before, minimal) - ]); +router.get("/merged-classes/:classID", async (req, res) => { + const classID = Number(req.params.classID); + const cls = await findClassById(classID); + if (cls === null) { + res.status(404).json({ + message: `No class found with ID ${classID}`, + }); + return; + } + const classIDs = await getMergedIDsForClass(classID); res.json({ - measurements, - studentData, - classData + merged_class_ids: classIDs, }); }); -router.put("/sync-merged-class/:classID", async(req, res) => { - const classID = parseInt(req.params.classID); - if (isNaN(classID)) { - res.statusCode = 400; - res.json({ - error: "Class ID must be a number" +const MergeClassInfo = S.struct({ + class_id: S.number.pipe(S.int()), +}); +router.put("/merge-class", async (req, res) => { + const body = req.body; + const maybe = S.decodeUnknownEither(MergeClassInfo)(body); + + if (Either.isLeft(maybe)) { + res.status(400).json({ + message: `Expected class ID to be an integer, got ${body.class_id}`, }); return; } - const database = SyncMergedHubbleClasses.sequelize; - if (database === undefined) { - res.status(500).json({ - error: "Error connecting to database", + + const data = maybe.right; + const cls = await findClassById(data.class_id); + if (cls === null) { + res.status(404).json({ + message: `No class found with ID ${data.class_id}`, }); return; } - const data = await tryToMergeClass(database, classID); - if (data.mergeData === null) { - res.statusCode = 404; - res.json({ - error: data.message + + const groupID = await addClassToMergeGroup(data.class_id); + if (groupID === null) { + res.status(500).json({ + message: `There was an error while adding class ${data.class_id} to a merge group`, }); return; } res.json({ - merge_info: data.mergeData, - message: data.message + class_id: data.class_id, + group_id: groupID, + }); +}); + +router.get("/all-data", async (req, res) => { + const minimal = (req.query?.minimal as string)?.toLowerCase() === "true"; + const beforeMs: number = parseInt(req.query.before as string); + const before = isNaN(beforeMs) ? null : new Date(beforeMs); + const [measurements, studentData, classData] = + await Promise.all([ + getAllHubbleMeasurements(before, minimal), + getAllHubbleStudentData(before, minimal), + getAllHubbleClassData(before, minimal) + ]); + res.json({ + measurements, + studentData, + classData }); });