From 7d04055f0a358221b974831a4543dc034813c698 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Mon, 30 Oct 2023 17:38:28 -0400 Subject: [PATCH 1/3] Update class model and SQL with test field. --- src/models/class.ts | 6 ++++++ src/sql/create_class_table.sql | 1 + 2 files changed, 7 insertions(+) diff --git a/src/models/class.ts b/src/models/class.ts index 0949088..0b6dd62 100644 --- a/src/models/class.ts +++ b/src/models/class.ts @@ -10,6 +10,7 @@ export class Class extends Model, InferCreationAttributes declare active: CreationOptional; declare code: string; declare asynchronous: CreationOptional; + declare test: CreationOptional; } export function initializeClassModel(sequelize: Sequelize) { @@ -55,6 +56,11 @@ export function initializeClassModel(sequelize: Sequelize) { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false + }, + test: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: 0 } }, { sequelize, diff --git a/src/sql/create_class_table.sql b/src/sql/create_class_table.sql index 3158d85..3c1a96b 100644 --- a/src/sql/create_class_table.sql +++ b/src/sql/create_class_table.sql @@ -6,6 +6,7 @@ CREATE TABLE Classes ( active tinyint(1) NOT NULL DEFAULT 0, code varchar(50) NOT NULL UNIQUE, asynchronous tinyint(1) NOT NULL DEFAULT 0, + test tinyint(1) NOT NULL DEFAULT 0, updated datetime DEFAULT NULL, PRIMARY KEY(id), From 14befaaa930eb79db9e907f4186c9c1cae8a7850 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Mon, 30 Oct 2023 17:38:37 -0400 Subject: [PATCH 2/3] Update gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 11f5d71..3045d73 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,8 @@ pnpm-debug.log* *.njsproj *.sln *.sw? + +# Elastic Beanstalk Files +.elasticbeanstalk/* +!.elasticbeanstalk/*.cfg.yml +!.elasticbeanstalk/*.global.yml From 86d4faf02e892da204097f7f4471c2e443ed5049 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Mon, 30 Oct 2023 17:45:22 -0400 Subject: [PATCH 3/3] Add endpoint for merging sync classes. --- src/database.ts | 1 - src/stories/hubbles_law/database.ts | 68 ++++++++++++++++++++++++++++- src/stories/hubbles_law/router.ts | 27 +++++++++++- 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/database.ts b/src/database.ts index 33bcd38..f2f99e6 100644 --- a/src/database.ts +++ b/src/database.ts @@ -664,7 +664,6 @@ export async function getQuestionsForStory(storyName: string, newestOnly=true): }); } - export async function getDashboardGroupClasses(code: string): Promise { const group = await DashboardClassGroup.findOne({ where: { code } }); if (group === null) { diff --git a/src/stories/hubbles_law/database.ts b/src/stories/hubbles_law/database.ts index 486b589..f6f4bd4 100644 --- a/src/stories/hubbles_law/database.ts +++ b/src/stories/hubbles_law/database.ts @@ -1,6 +1,6 @@ -import { Op, Sequelize, WhereOptions, col, fn, literal } from "sequelize"; +import { Op, QueryTypes, Sequelize, WhereOptions, col, fn, literal } from "sequelize"; import { AsyncMergedHubbleStudentClasses, Galaxy, HubbleMeasurement, SampleHubbleMeasurement, initializeModels, SyncMergedHubbleClasses } from "./models"; -import { cosmicdsDB, findClassById, findStudentById } from "../../database"; +import { classSize, cosmicdsDB, findClassById, findStudentById } from "../../database"; import { RemoveHubbleMeasurementResult, SubmitHubbleMeasurementResult } from "./request_results"; import { setUpHubbleAssociations } from "./associations"; import { Class, StoryState, Student, StudentsClasses } from "../../models"; @@ -662,3 +662,67 @@ export async function getGalaxiesForDataGeneration(types=["Sp"]): Promise { + return SyncMergedHubbleClasses.findOne({ where: { class_id: classID } }); +} + +export async function eligibleClassesForMerge(classID: number, sizeThreshold=20): 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 cosmicdsDB.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; +} + +// Try and merge the class with the given ID with another class such that the total size is above the threshold +// We say "try" because if a client doesn't know that the merge has already occurred, we may get +// multiple such requests from different student clients. +// If a merge has already been created, we don't make another one - we just return the existing one, with a +// message that indicates that this was the case. +export interface MergeAttemptData { + mergeData: SyncMergedHubbleClasses | null; + message: string; +} +export async function tryToMergeClass(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(classID); + if (eligibleClasses.length > 0) { + const index = Math.floor(Math.random() * eligibleClasses.length); + console.log(eligibleClasses); + console.log(index); + 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 05b1c7b..b19c01b 100644 --- a/src/stories/hubbles_law/router.ts +++ b/src/stories/hubbles_law/router.ts @@ -31,7 +31,8 @@ import { getSampleGalaxy, getGalaxyById, removeSampleHubbleMeasurement, - getAllNthSampleHubbleMeasurements + getAllNthSampleHubbleMeasurements, + tryToMergeClass } from "./database"; import { @@ -325,6 +326,30 @@ router.get("/all-data", async (req, res) => { }); }); +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" + }); + return; + } + const data = await tryToMergeClass(classID); + if (data.mergeData === null) { + res.statusCode = 404; + res.json({ + error: data.message + }); + return; + } + + res.json({ + merge_info: data.mergeData, + message: data.message + }); +}); + router.get("/galaxies", async (req, res) => { const types = req.query?.types ?? undefined; let galaxies: Galaxy[];