Skip to content

Commit

Permalink
Merge branch 'main' into api-refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Carifio24 authored Sep 11, 2024
2 parents a3b98c7 + 9989062 commit 3a247ea
Show file tree
Hide file tree
Showing 24 changed files with 1,798 additions and 789 deletions.
2 changes: 1 addition & 1 deletion .ebextensions/ts_compile.config
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ container_commands:
compile:
command: "./node_modules/.bin/tsc -p tsconfig.json"
env:
PATH: /opt/elasticbeanstalk/node-install/node-v16.20.2-linux-x64/bin/
PATH: /usr/bin/
1,564 changes: 839 additions & 725 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"start": "npm run serve"
},
"dependencies": {
"@effect/schema": "^0.63.2",
"@types/body-parser": "^1.19.2",
"@types/cookie-parser": "^1.4.2",
"@types/cors": "^2.8.12",
Expand All @@ -14,6 +15,8 @@
"@types/express-session": "^1.17.4",
"@types/jsonwebtoken": "^8.5.8",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1",
"body-parser": "^1.19.1",
"connect-session-sequelize": "^7.1.3",
"cookie-parser": "^1.4.6",
Expand All @@ -27,21 +30,19 @@
"mysql2": "^2.3.3",
"nanoid": "^3.3.2",
"path": "^0.12.7",
"sequelize": "^6.21.3",
"sequelize": "^6.37.1",
"sha3": "^2.1.4",
"typescript": "^4.6.3",
"typescript": "^5.4.2",
"uuid": "^8.3.2",
"winston": "^3.10.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.19.0",
"@typescript-eslint/parser": "^5.19.0",
"eslint": "^8.13.0",
"eslint-plugin-vue": "^8.6.0",
"sass": "~1.32.0",
"sass-loader": "^10.0.0"
},
"engines": {
"node": "16.20.2"
"node": "18.18.0"
}
}
9 changes: 9 additions & 0 deletions src/authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,20 @@ import { APIKey } from "./models/api_key";

const HASHER = new SHA3(256);

const validKeys = new Map<string, APIKey>();

export async function getAPIKey(key: string): Promise<APIKey | null> {
const cachedKey = validKeys.get(key);
if (cachedKey !== undefined) {
return cachedKey;
}
HASHER.update(key);
const hashedKey = HASHER.digest("hex");
const apiKey = await APIKey.findOne({ where: { hashed_key: hashedKey } });
HASHER.reset();
if (apiKey !== null) {
validKeys.set(key, apiKey);
}
return apiKey;
}

Expand Down
122 changes: 113 additions & 9 deletions src/database.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Model, Op, QueryTypes, Sequelize } from "sequelize";
import { Model, Op, QueryTypes, Sequelize, WhereOptions } from "sequelize";
import dotenv from "dotenv";

import {
Expand All @@ -11,13 +11,15 @@ import {
Student,
DummyClass,
DashboardClassGroup,
StageState,
} from "./models";

import {
createClassCode,
createVerificationCode,
encryptPassword,
isNumberArray,
Either,
} from "./utils";


Expand All @@ -35,6 +37,7 @@ import { initializeModels } from "./models";
import { StudentOption, StudentOptions } from "./models/student_options";
import { Question } from "./models/question";
import { logger } from "./logger";
import { Stage } from "./models/stage";

type SequelizeError = { parent: { code: string } };

Expand All @@ -47,7 +50,7 @@ export type LoginResponse = {

export type CreateClassResponse = {
result: CreateClassResult;
class?: object;
class?: object | undefined;
}

export enum UserType {
Expand Down Expand Up @@ -342,6 +345,7 @@ async function checkLogin<T extends Model & User>(identifier: string, password:
result: result,
success: LoginResult.success(result),
type
id: user?.id ?? 0,
};
if (user) {
response.user = user;
Expand All @@ -365,6 +369,19 @@ export async function getAllEducators(): Promise<Educator[]> {
return Educator.findAll();
}

export async function getStory(storyName: string): Promise<Story | null> {
return Story.findOne({ where: { name: storyName } });
}

export async function getStages(storyName: string): Promise<Stage[]> {
return Stage.findAll({
where: {
story_name: storyName,
},
order: [["stage_index", "ASC"]],
});
}

export async function getStoryState(studentID: number, storyName: string): Promise<JSON | null> {
const result = await StoryState.findOne({
where: {
Expand All @@ -376,35 +393,122 @@ 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<JSON | null> {
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 getStudentStageState(studentID: number, storyName: string, stageName: string): Promise<JSON | null> {
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 type StageStateQuery = { storyName: string, stageName?: string } & Either<{studentID: number}, {classID: number}>;

export async function getStageStates(query: StageStateQuery): Promise<Record<string, JSON[]>> {
const where: WhereOptions = { story_name: query.storyName };
if (query.stageName != undefined) {
where.stage_name = query.stageName;
}

if (query.classID != undefined) {
const students = await StudentsClasses.findAll({
where: { class_id: query.classID }
});
const studentIDs = students.map(sc => sc.student_id);
where.student_id = {
[Op.in]: studentIDs
};
} else {
where.student_id = query.studentID;
}

const results = await StageState.findAll({ where })
.catch(error => {
console.log(error);
return null;
});

const stageStates: Record<string, JSON[]> = {};
if (results !== null) {
results.forEach(result => {
const states = stageStates[result.stage_name] ?? [];
states.push(result.state);
stageStates[result.stage_name] = states;
});
}

return stageStates;

}

export async function updateStageState(studentID: number, storyName: string, stageName: string, newState: JSON): Promise<JSON | null> {
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 deleteStageState(studentID: number, storyName: string, stageName: string): Promise<number> {
return StageState.destroy({
where: {
student_id: studentID,
story_name: storyName,
stage_name: stageName,
}
});
}

export async function getClassesForEducator(educatorID: number): Promise<Class[]> {
Expand Down
6 changes: 6 additions & 0 deletions src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Educator, initializeEducatorModel } from "./educator";
import { IgnoreStudent, initializeIgnoreStudentModel } from "./ignore_student";
import { ClassStories, initializeClassStoryModel } from "./story_class";
import { CosmicDSSession, initializeSessionModel } from "./session";
import { Stage, initializeStageModel } from "./stage";
import { StageState, initializeStageStateModel } from "./stage_state";
import { StoryState, initializeStoryStateModel } from "./story_state";
import { Story, initializeStoryModel } from "./story";
import { StudentsClasses, initializeStudentClassModel } from "./student_class";
Expand All @@ -22,6 +24,8 @@ export {
DummyClass,
Educator,
IgnoreStudent,
Stage,
StageState,
Story,
StoryState,
Student,
Expand All @@ -36,6 +40,8 @@ export function initializeModels(db: Sequelize) {
initializeStoryModel(db);
initializeClassStoryModel(db);
initializeDummyClassModel(db);
initializeStageModel(db);
initializeStageStateModel(db);
initializeStoryStateModel(db);
initializeStudentClassModel(db);
initializeStudentOptionsModel(db);
Expand Down
52 changes: 52 additions & 0 deletions src/models/stage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Sequelize, DataTypes, Model, InferAttributes, InferCreationAttributes, CreationOptional } from "sequelize";
import { Story } from "./story";


export class Stage extends Model<InferAttributes<Stage>, InferCreationAttributes<Stage>> {
declare story_name: string;
declare stage_name: string;
declare stage_index: CreationOptional<number | null>;
}

export function initializeStageModel(sequelize: Sequelize) {
Stage.init({
story_name: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
primaryKey: true,
},
stage_name: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
primaryKey: true,
references: {
model: Story,
key: "name",
}
},
stage_index: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: true,
defaultValue: null,
}
}, {
sequelize,
engine: "InnoDB",
indexes: [
{
unique: true,
fields: ["story_name", "stage_name"],
},
{
unique: true,
fields: ["story_name"],
},
{
unique: true,
fields: ["stage_name"],
},
]
});
}
51 changes: 51 additions & 0 deletions src/models/stage_state.ts
Original file line number Diff line number Diff line change
@@ -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<InferAttributes<StageState>, InferCreationAttributes<StageState>> {
declare student_id: CreationOptional<number>;
declare story_name: string;
declare stage_name: string;
declare state: JSON;
declare last_modified: CreationOptional<Date>;
}

export function initializeStageStateModel(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"
});
}
Loading

0 comments on commit 3a247ea

Please sign in to comment.