diff --git a/src/stories/hubbles_law/database.ts b/src/stories/hubbles_law/database.ts index 8e8c1a7..65fad45 100644 --- a/src/stories/hubbles_law/database.ts +++ b/src/stories/hubbles_law/database.ts @@ -1,5 +1,5 @@ import { Attributes, FindOptions, Op, QueryTypes, Sequelize, WhereAttributeHash, WhereOptions, col, fn, literal } from "sequelize"; -import { AsyncMergedHubbleStudentClasses, Galaxy, HubbleMeasurement, SampleHubbleMeasurement, SyncMergedHubbleClasses } from "./models"; +import { AsyncMergedHubbleStudentClasses, Galaxy, HubbleMeasurement, HubbleWaitingRoomOverride, SampleHubbleMeasurement, SyncMergedHubbleClasses } from "./models"; import { classSize, findClassById, findStudentById } from "../../database"; import { RemoveHubbleMeasurementResult, SubmitHubbleMeasurementResult } from "./request_results"; import { Class, StoryState, Student, StudentsClasses } from "../../models"; @@ -848,3 +848,23 @@ export async function addClassToMergeGroup(classID: number): Promise { + return HubbleWaitingRoomOverride.findOrCreate({ + where: { + class_id: classID, + } + }) + .then(result => result[1]) + .catch((error: Error) => error); +} + +export async function removeWaitingRoomOverride(classID: number): Promise { + return HubbleWaitingRoomOverride.destroy({ + where: { + class_id: classID, + } + }) + .then(_result => true) + .catch(_error => false); +} diff --git a/src/stories/hubbles_law/models/hubble_waiting_room_override.ts b/src/stories/hubbles_law/models/hubble_waiting_room_override.ts new file mode 100644 index 0000000..c3399b2 --- /dev/null +++ b/src/stories/hubbles_law/models/hubble_waiting_room_override.ts @@ -0,0 +1,28 @@ +import { Class } from "../../../models"; +import { Sequelize, DataTypes, Model, InferAttributes, InferCreationAttributes, CreationOptional } from "sequelize"; + +export class HubbleWaitingRoomOverride extends Model, InferCreationAttributes> { + declare class_id: number; + declare timestamp: CreationOptional; +} + +export function initializeHubbleWaitingRoomOverrideModel(sequelize: Sequelize) { + HubbleWaitingRoomOverride.init({ + class_id: { + type: DataTypes.INTEGER.UNSIGNED, + allowNull: false, + primaryKey: true, + references: { + model: Class, + key: "id", + } + }, + timestamp: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: Sequelize.literal("CURRENT_TIMESTAMP"), + } + }, { + sequelize, + }); +} diff --git a/src/stories/hubbles_law/models/index.ts b/src/stories/hubbles_law/models/index.ts index 976c691..bd1fc12 100644 --- a/src/stories/hubbles_law/models/index.ts +++ b/src/stories/hubbles_law/models/index.ts @@ -7,13 +7,15 @@ import { Sequelize } from "sequelize"; import { initializeHubbleStudentDataModel } from "./hubble_student_data"; import { initializeHubbleClassDataModel } from "./hubble_class_data"; import { initializeHubbleClassMergeGroupModel } from "./hubble_class_merge_group"; +import { initializeHubbleWaitingRoomOverrideModel, HubbleWaitingRoomOverride } from "./hubble_waiting_room_override"; export { Galaxy, HubbleMeasurement, SampleHubbleMeasurement, AsyncMergedHubbleStudentClasses, - SyncMergedHubbleClasses + SyncMergedHubbleClasses, + HubbleWaitingRoomOverride }; export function initializeModels(db: Sequelize) { @@ -25,4 +27,5 @@ export function initializeModels(db: Sequelize) { initializeHubbleStudentDataModel(db); initializeHubbleClassDataModel(db); initializeHubbleClassMergeGroupModel(db); + initializeHubbleWaitingRoomOverrideModel(db); } diff --git a/src/stories/hubbles_law/router.ts b/src/stories/hubbles_law/router.ts index 0f4acc9..01db3ba 100644 --- a/src/stories/hubbles_law/router.ts +++ b/src/stories/hubbles_law/router.ts @@ -38,7 +38,9 @@ import { getClassMeasurementCount, getStudentsWithCompleteMeasurementsCount, getMergedIDsForClass, - addClassToMergeGroup + addClassToMergeGroup, + setWaitingRoomOverride, + removeWaitingRoomOverride } from "./database"; import { @@ -47,7 +49,7 @@ import { } from "./request_results"; import { Express, Router } from "express"; -import { Sequelize } from "sequelize"; +import { Sequelize, ForeignKeyConstraintError, UniqueConstraintError } from "sequelize"; import { classForStudentStory, findClassById, findStudentById } from "../../database"; import { initializeModels } from "./models"; import { setUpHubbleAssociations } from "./associations"; @@ -553,6 +555,94 @@ router.get("/spectra/:type/:name", async (req, res) => { }); +const WaitingRoomOverrideSchema = S.struct({ + class_id: S.number.pipe(S.int()), +}); + +router.put("/waiting-room-override", async (req, res) => { + const body = req.body; + const maybe = S.decodeUnknownEither(WaitingRoomOverrideSchema)(body); + if (Either.isLeft(maybe)) { + res.status(400).json({ + error: "Invalid format. Request body should have the form { class_id: }", + }); + return; + } + + const right = maybe.right; + const result = await setWaitingRoomOverride(right.class_id); + const success = !(result instanceof Error); + const responseData = { + success, + class_id: right.class_id, + }; + if (!success) { + if (result instanceof ForeignKeyConstraintError) { + res.status(404).json({ + ...responseData, + error: `No class found with ID ${right.class_id}`, + }); + + // It's fine if the override already exists + } else if (result instanceof UniqueConstraintError) { + res.status(200).json({ + class_id: right.class_id, + success: true, + message: `The waiting room override for class ${right.class_id} was already set`, + }); + } else { + res.status(500).json({ + ...responseData, + error: `An error occurred while setting the waiting room override for class ${right.class_id}`, + }); + } + return; + } + + if (result) { + res.status(201).json({ + ...responseData, + message: `Successfully set waiting room override for class ${right.class_id}`, + }); + } else { + res.status(200).json({ + message: `The waiting room override for class ${right.class_id} was already set`, + }); + } +}); + + +router.delete("/waiting-room-override", async (req, res) => { + const body = req.body; + const maybe = S.decodeUnknownEither(WaitingRoomOverrideSchema)(body); + if (Either.isLeft(maybe)) { + res.status(400).json({ + error: "Invalid format. Request body should have the form { class_id: }", + }); + return; + } + + const right = maybe.right; + const success = await removeWaitingRoomOverride(right.class_id); + const responseData = { + success, + class_id: right.class_id, + }; + if (!success) { + res.status(500).json({ + ...responseData, + error: `An error occurred while removing the waiting room override for class ${right.class_id}`, + }); + return; + } + + res.json({ + ...responseData, + message: `The waiting room override for class ${right.class_id} was removed, if one existed.`, + }); +}); + + /** These endpoints are specifically for the spectrum-checking branch */ router.get("/unchecked-galaxies", async (_req, res) => { diff --git a/src/stories/hubbles_law/sql/create_hubble_waiting_room_overrides.sql b/src/stories/hubbles_law/sql/create_hubble_waiting_room_overrides.sql new file mode 100644 index 0000000..d7321ae --- /dev/null +++ b/src/stories/hubbles_law/sql/create_hubble_waiting_room_overrides.sql @@ -0,0 +1,11 @@ +CREATE TABLE HubbleWaitingRoomOverrides ( + class_id int(11) UNSIGNED NOT NULL, + timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + ON UPDATE CURRENT_TIMESTAMP, + + PRIMARY KEY(class_id), + FOREIGN KEY(class_id) + REFERENCES Classes(id) + ON UPDATE CASCADE + ON DELETE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci PACK_KEYS=0;