From 8b1f1620dc92dbe70bd3a88c407554a4dda3581b Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Fri, 14 Jun 2024 14:27:21 -0400 Subject: [PATCH 1/2] Add SQL and model for stage states. --- src/models/stage_state.ts | 51 +++++++++++++++++++++++++++ src/sql/create_stage_states_table.sql | 21 +++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/models/stage_state.ts create mode 100644 src/sql/create_stage_states_table.sql diff --git a/src/models/stage_state.ts b/src/models/stage_state.ts new file mode 100644 index 0000000..7e6d857 --- /dev/null +++ b/src/models/stage_state.ts @@ -0,0 +1,51 @@ +import { Sequelize, DataTypes, Model, InferAttributes, InferCreationAttributes, CreationOptional } from "sequelize"; +import { Story } from "./story"; +import { Student } from "./student"; + +export class StageState extends Model, InferCreationAttributes> { + declare student_id: CreationOptional; + declare story_name: string; + declare stage_name: string; + declare state: JSON; + declare last_modified: CreationOptional; +} + +export function initializeStoryStateModel(sequelize: Sequelize) { + StageState.init({ + student_id: { + type: DataTypes.INTEGER.UNSIGNED, + allowNull: false, + primaryKey: true, + references: { + model: Student, + key: "id" + } + }, + story_name: { + type: DataTypes.STRING, + allowNull: false, + primaryKey: true, + references: { + model: Story, + key: "name" + } + }, + stage_name: { + type: DataTypes.STRING, + allowNull: false, + primaryKey: true, + }, + state: { + type: DataTypes.JSON, + allowNull: false + }, + last_modified: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: Sequelize.literal("CURRENT_TIMESTAMP") + } + }, { + sequelize, + engine: "InnoDB" + }); +} diff --git a/src/sql/create_stage_states_table.sql b/src/sql/create_stage_states_table.sql new file mode 100644 index 0000000..7dc0a11 --- /dev/null +++ b/src/sql/create_stage_states_table.sql @@ -0,0 +1,21 @@ +CREATE TABLE StageStates ( + student_id int(11) UNSIGNED NOT NULL, + story_name varchar(50) NOT NULL, + stage_name varchar(50) NOT NULL, + state JSON NOT NULL, + last_modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + ON UPDATE CURRENT_TIMESTAMP, + + PRIMARY KEY(student_id, story_name, stage_name), + INDEX(student_id), + INDEX(story_name), + INDEX(stage_name), + FOREIGN KEY(student_id) + REFERENCES Students(id) + ON UPDATE CASCADE + ON DELETE CASCADE, + FOREIGN KEY(story_name) + REFERENCES Stories(name) + ON UPDATE CASCADE + ON DELETE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci PACK_KEYS=0; From 2af8823ea06f8338828511474177c454188b771a Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Fri, 14 Jun 2024 15:19:19 -0400 Subject: [PATCH 2/2] Add endpoints and database functions for getting and creating/updating stage states. --- src/database.ts | 53 +++++++++++++++++++++++++++++++++------ src/models/index.ts | 3 +++ src/models/stage_state.ts | 2 +- src/server.ts | 37 +++++++++++++++++++++++++-- 4 files changed, 85 insertions(+), 10 deletions(-) diff --git a/src/database.ts b/src/database.ts index e5356f4..485f4b8 100644 --- a/src/database.ts +++ b/src/database.ts @@ -11,6 +11,7 @@ import { Student, DummyClass, DashboardClassGroup, + StageState, } from "./models"; import { @@ -347,35 +348,73 @@ export async function getStoryState(studentID: number, storyName: string): Promi console.log(error); return null; }); - return result?.story_state || null; + return result?.story_state ?? null; } export async function updateStoryState(studentID: number, storyName: string, newState: JSON): Promise { + const query = { + student_id: studentID, + story_name: storyName, + }; let result = await StoryState.findOne({ + where: query + }) + .catch(error => { + console.log(error); + return null; + }); + + const storyData = { ...query, story_state: newState }; + if (result !== null) { + result?.update(storyData); + } else { + result = await StoryState.create(storyData).catch(error => { + console.log(error); + return null; + }); + } + return result?.story_state ?? null; +} + +export async function getStageState(studentID: number, storyName: string, stageName: string): Promise { + const result = await StageState.findOne({ where: { student_id: studentID, - story_name: storyName + story_name: storyName, + stage_name: stageName, } }) .catch(error => { console.log(error); return null; }); + return result?.state ?? null; +} - const storyData = { +export async function updateStageState(studentID: number, storyName: string, stageName: string, newState: JSON): Promise { + const query = { student_id: studentID, story_name: storyName, - story_state: newState + stage_name: stageName, }; + let result = await StageState.findOne({ + where: query + }) + .catch(error => { + console.log(error); + return null; + }); + + const data = { ...query, state: newState }; if (result !== null) { - result?.update(storyData); + result?.update(data); } else { - result = await StoryState.create(storyData).catch(error => { + result = await StageState.create(data).catch(error => { console.log(error); return null; }); } - return result?.story_state || null; + return result?.state ?? null; } export async function getClassesForEducator(educatorID: number): Promise { diff --git a/src/models/index.ts b/src/models/index.ts index 457a688..d2396a2 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -5,6 +5,7 @@ import { Educator, initializeEducatorModel } from "./educator"; import { IgnoreStudent, initializeIgnoreStudentModel } from "./ignore_student"; import { ClassStories, initializeClassStoryModel } from "./story_class"; import { CosmicDSSession, initializeSessionModel } from "./session"; +import { StageState, initializeStageStateModel } from "./stage_state"; import { StoryState, initializeStoryStateModel } from "./story_state"; import { Story, initializeStoryModel } from "./story"; import { StudentsClasses, initializeStudentClassModel } from "./student_class"; @@ -22,6 +23,7 @@ export { DummyClass, Educator, IgnoreStudent, + StageState, Story, StoryState, Student, @@ -36,6 +38,7 @@ export function initializeModels(db: Sequelize) { initializeStoryModel(db); initializeClassStoryModel(db); initializeDummyClassModel(db); + initializeStageStateModel(db); initializeStoryStateModel(db); initializeStudentClassModel(db); initializeStudentOptionsModel(db); diff --git a/src/models/stage_state.ts b/src/models/stage_state.ts index 7e6d857..8e224a6 100644 --- a/src/models/stage_state.ts +++ b/src/models/stage_state.ts @@ -10,7 +10,7 @@ export class StageState extends Model, InferCreation declare last_modified: CreationOptional; } -export function initializeStoryStateModel(sequelize: Sequelize) { +export function initializeStageStateModel(sequelize: Sequelize) { StageState.init({ student_id: { type: DataTypes.INTEGER.UNSIGNED, diff --git a/src/server.ts b/src/server.ts index d746e2d..de4e5ad 100644 --- a/src/server.ts +++ b/src/server.ts @@ -29,6 +29,8 @@ import { currentVersionForQuestion, getQuestionsForStory, getDashboardGroupClasses, + getStageState, + updateStageState, } from "./database"; import { getAPIKey, hasPermission } from "./authorization"; @@ -423,7 +425,7 @@ app.get("/story-state/:studentID/:storyName", async (req, res) => { res.status(status).json({ student_id: studentID, story_name: storyName, - state: state + state }); }); @@ -437,7 +439,38 @@ app.put("/story-state/:studentID/:storyName", async (req, res) => { res.status(status).json({ student_id: studentID, story_name: storyName, - state: state + state + }); +}); + +app.get("/stage-state/:studentID/:storyName/:stageName", async (req, res) => { + const params = req.params; + const studentID = Number(params.studentID); + const storyName = params.storyName; + const stageName = params.stageName; + const state = await getStageState(studentID, storyName, stageName); + const status = state !== null ? 200 : 404; + res.status(status).json({ + student_id: studentID, + story_name: storyName, + stage_name: stageName, + state + }); +}); + +app.put("/stage-state/:studentID/:storyName/:stageName", async (req, res) => { + const params = req.params; + const studentID = Number(params.studentID); + const storyName = params.storyName; + const stageName = params.stageName; + const newState = req.body; + const state = await updateStageState(studentID, storyName, stageName, newState); + const status = state !== null ? 200 : 404; + res.status(status).json({ + student_id: studentID, + story_name: storyName, + stage_name: stageName, + state }); });