From 95229887028ad5fb17d917d6b2c67e60f0e1917c Mon Sep 17 00:00:00 2001 From: Jon Carifio Date: Thu, 2 Nov 2023 15:43:39 -0400 Subject: [PATCH 01/10] Update definitions for unique fields. --- src/models/student.ts | 1 + src/sql/create_educator_table.sql | 1 - src/sql/create_student_table.sql | 2 -- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/models/student.ts b/src/models/student.ts index 2e3e8ee..fcf5f9f 100644 --- a/src/models/student.ts +++ b/src/models/student.ts @@ -50,6 +50,7 @@ export function initializeStudentModel(sequelize: Sequelize) { username: { type: DataTypes.STRING, allowNull: false, + unique: true }, password: { type: DataTypes.STRING, diff --git a/src/sql/create_educator_table.sql b/src/sql/create_educator_table.sql index b5f4c4e..ba6b1b2 100644 --- a/src/sql/create_educator_table.sql +++ b/src/sql/create_educator_table.sql @@ -18,6 +18,5 @@ CREATE TABLE Educators ( last_visit_ip varchar(50) COLLATE utf8_unicode_ci, PRIMARY KEY(id), - INDEX(email), INDEX(last_name) ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci PACK_KEYS=0; diff --git a/src/sql/create_student_table.sql b/src/sql/create_student_table.sql index 000fb6d..0adea0c 100644 --- a/src/sql/create_student_table.sql +++ b/src/sql/create_student_table.sql @@ -20,6 +20,4 @@ CREATE TABLE Students ( dummy tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY(id), - INDEX(username), - INDEX(email) ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci PACK_KEYS=0; From 142c51cfa5e5ac1db9a22ea5aebd9af5717a99ab Mon Sep 17 00:00:00 2001 From: Jon Carifio Date: Thu, 2 Nov 2023 15:56:00 -0400 Subject: [PATCH 02/10] Start adding/tweaking endpoints. --- src/server.ts | 70 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/src/server.ts b/src/server.ts index d746e2d..dd0c9c0 100644 --- a/src/server.ts +++ b/src/server.ts @@ -404,6 +404,8 @@ app.get("/validate-classroom-code/:code", async (req, res) => { }); +/* Users (students and educators) */ + app.get("/students", async (_req, res) => { const queryResponse = await getAllStudents(); res.json(queryResponse); @@ -414,6 +416,58 @@ app.get("/educators", async (_req, res) => { res.json(queryResponse); }); +app.get("/users", async (_req, res) => { + const students = await getAllStudents(); + const educators = await getAllEducators(); + res.json({ students, educators }); +}); + +app.get("/students/:identifier", async (req, res) => { + const params = req.params; + const id = Number(params.identifier); + + let student; + if (isNaN(id)) { + student = await findStudentByUsername(params.identifier); + } else { + student = await findStudentById(id); + } + if (student == null) { + res.statusCode = 404; + } + res.json({ + student: student + }); +}); + +app.get("/students/:identifier/classes", async (req, res) => { + const id = Number(req.params.identifier); + + let student; + if (isNaN(id)) { + student = await findStudentByUsername(req.params.identifier); + } else { + student = await findStudentById(id); + } + + if (student === null) { + res.statusCode = 404; + res.json({ + student_id: null, + classes: [] + }); + return; + } + + const classes = await getClassesForStudent(student.id); + res.json({ + student_id: student.id, + classes: classes + }); + +}); + + app.get("/story-state/:studentID/:storyName", async (req, res) => { const params = req.params; const studentID = Number(params.studentID); @@ -483,23 +537,7 @@ app.get("/logout", (req, res) => { }); }); -app.get("/student/:identifier", async (req, res) => { - const params = req.params; - const id = Number(params.identifier); - let student; - if (isNaN(id)) { - student = await findStudentByUsername(params.identifier); - } else { - student = await findStudentById(id); - } - if (student == null) { - res.statusCode = 404; - } - res.json({ - student: student - }); -}); // Question information app.get("/question/:tag", async (req, res) => { From 585d378ef7972752cfd29f87ff8eced57f04f363 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Thu, 2 Nov 2023 19:19:20 -0400 Subject: [PATCH 03/10] More work on new endpoints for portal. --- src/database.ts | 53 ++++++++++++-------- src/models/dashboard_class_group.ts | 2 +- src/server.ts | 77 +++++++++++++++++++++++++++-- 3 files changed, 107 insertions(+), 25 deletions(-) diff --git a/src/database.ts b/src/database.ts index f2f99e6..54453c6 100644 --- a/src/database.ts +++ b/src/database.ts @@ -180,11 +180,19 @@ async function educatorVerificationCodeExists(code: string): Promise { return result.length > 0; } -export async function signUpEducator(firstName: string, lastName: string, - password: string, institution: string | null, - email: string, age: number | null, gender: string): Promise { +export interface SignUpEducatorOptions { + first_name: string; + last_name: string; + password: string; + email: string; + institution?: string; + age?: number; + gender?: string; +} + +export async function signUpEducator(options: SignUpEducatorOptions): Promise { - const encryptedPassword = encryptPassword(password); + const encryptedPassword = encryptPassword(options.password); let validCode; let verificationCode: string; @@ -195,15 +203,10 @@ export async function signUpEducator(firstName: string, lastName: string, let result = SignUpResult.Ok; await Educator.create({ - first_name: firstName, - last_name: lastName, + ...options, verified: 0, verification_code: verificationCode, password: encryptedPassword, - institution: institution, - email: email, - age: age, - gender: gender, }) .catch(error => { result = signupResultFromError(error); @@ -211,12 +214,20 @@ export async function signUpEducator(firstName: string, lastName: string, return result; } -export async function signUpStudent(username: string, - password: string, institution: string | null, - email: string, age: number, gender: string, - classroomCode: string | null): Promise { +export interface SignUpStudentOptions { + username: string; + password: string; + email: string; + age?: number; + gender?: string; + institution?: string; + classroom_code?: string; +} + + +export async function signUpStudent(options: SignUpStudentOptions): Promise { - const encryptedPassword = encryptPassword(password); + const encryptedPassword = encryptPassword(options.password); let validCode; let verificationCode: string; @@ -231,10 +242,10 @@ export async function signUpStudent(username: string, verified: 0, verification_code: verificationCode, password: encryptedPassword, - institution: institution, - email: email, - age: age, - gender: gender, + institution: options.institution, + email: options.email, + age: options.age, + gender: options.gender, }) .catch(error => { result = signupResultFromError(error); @@ -242,8 +253,8 @@ export async function signUpStudent(username: string, // If the student has a valid classroom code, // add them to the class - if (student && classroomCode) { - const cls = await findClassByCode(classroomCode); + if (student && options.classroom_code) { + const cls = await findClassByCode(options.classroom_code); if (cls !== null) { StudentsClasses.create({ student_id: student.id, diff --git a/src/models/dashboard_class_group.ts b/src/models/dashboard_class_group.ts index 42dd008..4e0e28d 100644 --- a/src/models/dashboard_class_group.ts +++ b/src/models/dashboard_class_group.ts @@ -1,4 +1,4 @@ -import { Sequelize, DataTypes, Model, InferAttributes, InferCreationAttributes, CreationOptional, DATE } from "sequelize"; +import { Sequelize, DataTypes, Model, InferAttributes, InferCreationAttributes, CreationOptional } from "sequelize"; export class DashboardClassGroup extends Model, InferCreationAttributes> { declare id: CreationOptional; diff --git a/src/server.ts b/src/server.ts index dd0c9c0..f5c03e7 100644 --- a/src/server.ts +++ b/src/server.ts @@ -40,7 +40,7 @@ import { VerificationResult, } from "./request_results"; -import { CosmicDSSession } from "./models"; +import { CosmicDSSession, StudentsClasses } from "./models"; import { ParsedQs } from "qs"; import express, { Request, Response as ExpressResponse, NextFunction } from "express"; @@ -255,12 +255,12 @@ app.post("/student-sign-up", async (req, res) => { typeof data.email === "string" && ((typeof data.age === "number") || (data.age == null)) && ((typeof data.gender === "string") || (data.gender == null)) && - ((typeof data.classroomCode === "string") || (data.classroomCode == null)) + ((typeof data.classroom_code === "string") || (data.classroom_code == null)) ); let result: SignUpResult; if (valid) { - result = await signUpStudent(data.username, data.password, data.institution, data.email, data.age, data.gender, data.classroomCode); + result = await signUpStudent(data); } else { result = SignUpResult.BadRequest; res.status(400); @@ -468,6 +468,77 @@ app.get("/students/:identifier/classes", async (req, res) => { }); +app.post("/classes/join", async (req, res) => { + const username = req.body.username as string; + const classCode = req.body.class_code as string; + const student = await findStudentByUsername(username); + const cls = await findClassByCode(classCode); + const isStudent = student !== null; + const isClass = cls !== null; + + if (!(isStudent && isClass)) { + let message = "The following were invalid:"; + const invalid: string[] = []; + if (!isStudent) { + invalid.push("username"); + } + if (!isClass) { + invalid.push("class_code"); + } + message += invalid.join(", "); + res.statusCode = 404; + res.json({ + success: false, + message: message + }); + return; + } + + const [join, created] = await StudentsClasses.upsert({ + class_id: cls.id, + student_id: student.id + }); + const success = join !== null; + res.statusCode = success ? 200 : 404; + let message: string; + if (!success) { + message = "Error adding student to class"; + } else if (!created) { + message = "Student was already enrolled in class"; + } else { + message = "Student added to class successfully"; + } + + res.json({ success, message }); +}); + +app.post("/educators/create", async (req, res) => { + const data = req.body; + const valid = ( + typeof data.first_name === "string" && + typeof data.last_name === "string" && + typeof data.password === "string" && + ((typeof data.institution === "string") || (data.institution == null)) && + typeof data.email === "string" && + ((typeof data.age === "number") || (data.age == null)) && + ((typeof data.gender === "string") || data.gender == null) + ); + + let result: SignUpResult; + if (valid) { + result = await signUpEducator(data.first_name, data.last_name, data.password, data.institution, data.email, data.age, data.gender); + } else { + result = SignUpResult.BadRequest; + res.status(400); + } + res.json({ + educator_info: data, + status: result, + success: SignUpResult.success(result) + }); +}); + + app.get("/story-state/:studentID/:storyName", async (req, res) => { const params = req.params; const studentID = Number(params.studentID); From 94cc0c528c4046d2296071d0e2597f1108103999 Mon Sep 17 00:00:00 2001 From: Jon Carifio Date: Wed, 8 Nov 2023 13:00:20 -0500 Subject: [PATCH 04/10] Add student creation endpoint. --- src/server.ts | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/server.ts b/src/server.ts index f5c03e7..0c68fed 100644 --- a/src/server.ts +++ b/src/server.ts @@ -233,7 +233,7 @@ app.post("/educator-sign-up", async (req, res) => { let result: SignUpResult; if (valid) { - result = await signUpEducator(data.firstName, data.lastName, data.password, data.institution, data.email, data.age, data.gender); + result = await signUpEducator(data); } else { result = SignUpResult.BadRequest; res.status(400); @@ -526,7 +526,7 @@ app.post("/educators/create", async (req, res) => { let result: SignUpResult; if (valid) { - result = await signUpEducator(data.first_name, data.last_name, data.password, data.institution, data.email, data.age, data.gender); + result = await signUpEducator(data); } else { result = SignUpResult.BadRequest; res.status(400); @@ -538,6 +538,31 @@ app.post("/educators/create", async (req, res) => { }); }); +app.post("/students/create", async (req, res) => { + const data = req.body; + const valid = ( + typeof data.username === "string" && + typeof data.password === "string" && + ((typeof data.institution === "string") || (data.institution == null)) && + typeof data.email === "string" && + ((typeof data.age === "number") || (data.age == null)) && + ((typeof data.gender === "string") || (data.gender == null)) && + ((typeof data.classroom_code === "string") || (data.classroom_code == null)) + ); + + let result: SignUpResult; + if (valid) { + result = await signUpStudent(data); + } else { + result = SignUpResult.BadRequest; + res.status(400); + } + res.json({ + student_info: data, + status: result, + success: SignUpResult.success(result) + }); +}); app.get("/story-state/:studentID/:storyName", async (req, res) => { const params = req.params; From 444c05a04aead95f6eddc80abfbc89f810de5365 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Thu, 7 Dec 2023 16:11:53 -0500 Subject: [PATCH 05/10] Add class creation and deletion endpoints. --- src/request_results.ts | 4 ++++ src/server.ts | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/request_results.ts b/src/request_results.ts index 7c288ea..28f4aae 100644 --- a/src/request_results.ts +++ b/src/request_results.ts @@ -22,6 +22,10 @@ export namespace CreateClassResult { return 200; } } + + export function success(result: CreateClassResult): boolean { + return result === CreateClassResult.Ok; + } } export enum LoginResult { diff --git a/src/server.ts b/src/server.ts index 0c68fed..d4ab9aa 100644 --- a/src/server.ts +++ b/src/server.ts @@ -29,6 +29,7 @@ import { currentVersionForQuestion, getQuestionsForStory, getDashboardGroupClasses, + CreateClassResponse, } from "./database"; import { getAPIKey, hasPermission } from "./authorization"; @@ -564,6 +565,45 @@ app.post("/students/create", async (req, res) => { }); }); +/* Classes */ +app.post("/classes/create", async (req, res) => { + const data = req.body; + const valid = ( + typeof data.username === "string" && + typeof data.educator_id === "string" + ); + let response: CreateClassResponse; + if (valid) { + response = await createClass(data.educator_id, data.username); + } else { + response = { + result: CreateClassResult.BadRequest, + }; + res.status(400); + } + res.json({ + class_info: response.class, + status: response.result, + success: CreateClassResult.success(response.result) + }); +}); + +app.delete("/classes/:code", async (req, res) => { + const cls = await findClassByCode(req.params.code); + const success = cls !== null; + if (!success) { + res.status(400); + } + cls?.destroy(); + const message = success ? + "Class deleted" : + "No class with the given code exists"; + res.json({ + success, + message + }); +}); + app.get("/story-state/:studentID/:storyName", async (req, res) => { const params = req.params; const studentID = Number(params.studentID); From 1e509189d855f8b0d3d148e9b9959986ee206cf7 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Thu, 7 Dec 2023 18:09:31 -0500 Subject: [PATCH 06/10] Make student email non-required. Some more refactoring. --- src/database.ts | 40 ++++++++++++++++------- src/models/student.ts | 2 +- src/server.ts | 74 ++++++++++++++++++++++++++----------------- src/user.ts | 2 +- 4 files changed, 76 insertions(+), 42 deletions(-) diff --git a/src/database.ts b/src/database.ts index 54453c6..455fa6a 100644 --- a/src/database.ts +++ b/src/database.ts @@ -39,8 +39,9 @@ import { logger } from "./logger"; type SequelizeError = { parent: { code: string } }; export type LoginResponse = { + type: "none" | "student" | "educator" | "admin", result: LoginResult; - id?: number; + user?: User; success: boolean; }; @@ -49,6 +50,13 @@ export type CreateClassResponse = { class?: object; } +export enum UserType { + None = 0, // Not logged in + Student, + Educator, + Admin +} + // Grab any environment variables // Currently, just the DB password dotenv.config(); @@ -217,7 +225,7 @@ export async function signUpEducator(options: SignUpEducatorOptions): Promise(email: string, password: string, emailFinder: (email: string) +async function checkLogin(identifier: string, password: string, identifierFinder: (identifier: string) => Promise): Promise { const encryptedPassword = encryptPassword(password); - const user = await emailFinder(email); + const user = await identifierFinder(identifier); let result: LoginResult; if (user === null) { result = LoginResult.EmailNotExist; @@ -322,23 +330,33 @@ async function checkLogin(email: string, password: strin where: { id: user.id } }); } - return { + + let type: LoginResponse["type"] = "none"; + if (user instanceof Student) { + type = "student"; + } else if (user instanceof Educator) { + type = "educator"; + } + + const response: LoginResponse = { result: result, - id: user?.id, - success: LoginResult.success(result) + success: LoginResult.success(result), + type }; + if (user) { + response.user = user; + } + return response; } -export async function checkStudentLogin(email: string, password: string): Promise { - return checkLogin(email, password, findStudentByEmail); +export async function checkStudentLogin(username: string, password: string): Promise { + return checkLogin(username, password, findStudentByUsername); } export async function checkEducatorLogin(email: string, password: string): Promise { return checkLogin(email, password, findEducatorByEmail); } - - export async function getAllStudents(): Promise { return Student.findAll(); } diff --git a/src/models/student.ts b/src/models/student.ts index fcf5f9f..9b4735e 100644 --- a/src/models/student.ts +++ b/src/models/student.ts @@ -5,7 +5,7 @@ export class Student extends Model, InferCreationAttrib declare id: CreationOptional; declare verified: number; declare verification_code: string; - declare email: string; + declare email: CreationOptional; declare username: string; declare password: string; declare institution: string | null; diff --git a/src/server.ts b/src/server.ts index d4ab9aa..bf05d1a 100644 --- a/src/server.ts +++ b/src/server.ts @@ -30,6 +30,7 @@ import { getQuestionsForStory, getDashboardGroupClasses, CreateClassResponse, + UserType } from "./database"; import { getAPIKey, hasPermission } from "./authorization"; @@ -71,12 +72,7 @@ type VerificationRequest = Request<{verificationCode: string}, any, any, ParsedQ type CDSSession = session.Session & Partial & CosmicDSSession; -export enum UserType { - None = 0, // Not logged in - Student, - Educator, - Admin -} + const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS ? process.env.ALLOWED_ORIGINS.split(",") : []; @@ -214,7 +210,7 @@ function _sendLoginCookie(userId: number, res: ExpressResponse): void { } // set port, listen for requests -const PORT = process.env.PORT || 8080; +const PORT = process.env.PORT || 8081; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}.`); }); @@ -273,38 +269,58 @@ app.post("/student-sign-up", async (req, res) => { }); }); -async function handleLogin(request: GenericRequest, checker: (email: string, pw: string) => Promise): Promise { +async function handleLogin(request: GenericRequest, identifierField: string, checker: (identifier: string, pw: string) => Promise): Promise { const data = request.body; - const valid = typeof data.email === "string" && typeof data.password === "string"; + const valid = typeof data[identifierField] === "string" && typeof data.password === "string"; let res: LoginResponse; if (valid) { - res = await checker(data.email, data.password); + res = await checker(data[identifierField], data.password); } else { - res = { result: LoginResult.BadRequest, success: false }; + res = { result: LoginResult.BadRequest, success: false, type: "none" }; } return res; } +// app.put("/login", async (req, res) => { +// const sess = req.session as CDSSession; +// let result = LoginResult.BadSession; +// res.status(401); +// if (sess.user_id && sess.user_type) { +// result = LoginResult.Ok; +// res.status(200); +// } +// res.json({ +// result: result, +// id: sess.user_id, +// success: LoginResult.success(result) +// }); +// }); + app.put("/login", async (req, res) => { - const sess = req.session as CDSSession; - let result = LoginResult.BadSession; - res.status(401); - if (sess.user_id && sess.user_type) { - result = LoginResult.Ok; - res.status(200); + let response = await handleLogin(req, "username", checkStudentLogin); + let type = UserType.Student; + if (!(response.success && response.user)) { + response = await handleLogin(req, "username", checkEducatorLogin); + type = UserType.Educator; } - res.json({ - result: result, - id: sess.user_id, - success: LoginResult.success(result) - }); + + if (response.success && response.user) { + const sess = req.session as CDSSession; + if (sess) { + sess.user_id = response.user.id; + sess.user_type = type; + } + } + + const status = response.success ? 200 : 401; + res.status(status).json(response); }); app.put("/student-login", async (req, res) => { - const loginResponse = await handleLogin(req, checkStudentLogin); - if (loginResponse.success && loginResponse.id) { + const loginResponse = await handleLogin(req, "username", checkStudentLogin); + if (loginResponse.success && loginResponse.user) { const sess = req.session as CDSSession; - sess.user_id = loginResponse.id; + sess.user_id = loginResponse.user.id; sess.user_type = UserType.Student; } const status = loginResponse.success ? 200 : 401; @@ -312,10 +328,10 @@ app.put("/student-login", async (req, res) => { }); app.put("/educator-login", async (req, res) => { - const loginResponse = await handleLogin(req, checkEducatorLogin); - if (loginResponse.success && loginResponse.id) { + const loginResponse = await handleLogin(req, "email", checkEducatorLogin); + if (loginResponse.success && loginResponse.user) { const sess = req.session as CDSSession; - sess.user_id = loginResponse.id; + sess.user_id = loginResponse.user.id; sess.user_type = UserType.Educator; } const status = loginResponse.success ? 200 : 401; @@ -545,7 +561,7 @@ app.post("/students/create", async (req, res) => { typeof data.username === "string" && typeof data.password === "string" && ((typeof data.institution === "string") || (data.institution == null)) && - typeof data.email === "string" && + ((typeof data.email === "string") || (data.email == null)) && ((typeof data.age === "number") || (data.age == null)) && ((typeof data.gender === "string") || (data.gender == null)) && ((typeof data.classroom_code === "string") || (data.classroom_code == null)) diff --git a/src/user.ts b/src/user.ts index 892a644..72f03d4 100644 --- a/src/user.ts +++ b/src/user.ts @@ -1,6 +1,6 @@ export interface User { id: number; - email: string; + email: string | null; password: string; verified: number; verification_code: string; From a3b98c77c5ac999d630220de1671ec8736b92a2f Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Wed, 4 Sep 2024 15:11:50 -0400 Subject: [PATCH 07/10] Retain old endpoint for now. --- src/server.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/server.ts b/src/server.ts index bf05d1a..f01cc46 100644 --- a/src/server.ts +++ b/src/server.ts @@ -439,7 +439,10 @@ app.get("/users", async (_req, res) => { res.json({ students, educators }); }); -app.get("/students/:identifier", async (req, res) => { +app.get([ + "/students/:identifier", + "/student/:identifier", // Backwards compatibility +], async (req, res) => { const params = req.params; const id = Number(params.identifier); From 37412b7a03818be1274f0db93c4eadb293656066 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Wed, 11 Sep 2024 12:34:07 -0400 Subject: [PATCH 08/10] Fix typo and a small typing issue. Update Student model to better reflect database schema. --- src/database.ts | 5 +++-- src/models/student.ts | 13 ++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/database.ts b/src/database.ts index 81b08a6..e69051e 100644 --- a/src/database.ts +++ b/src/database.ts @@ -45,6 +45,7 @@ export type LoginResponse = { type: "none" | "student" | "educator" | "admin", result: LoginResult; user?: User; + id?: number; success: boolean; }; @@ -115,7 +116,7 @@ async function findEducatorByEmail(email: string): Promise { }); } -async function findStudentByEmail(email: string): Promise { +async function _findStudentByEmail(email: string): Promise { return Student.findOne({ where: { email: { [Op.like] : email } } }); @@ -344,7 +345,7 @@ async function checkLogin(identifier: string, password: const response: LoginResponse = { result: result, success: LoginResult.success(result), - type + type, id: user?.id ?? 0, }; if (user) { diff --git a/src/models/student.ts b/src/models/student.ts index 9b4735e..c3a5ac4 100644 --- a/src/models/student.ts +++ b/src/models/student.ts @@ -8,9 +8,9 @@ export class Student extends Model, InferCreationAttrib declare email: CreationOptional; declare username: string; declare password: string; - declare institution: string | null; - declare age: number | null; - declare gender: string | null; + declare institution: CreationOptional; + declare age: CreationOptional; + declare gender: CreationOptional; declare ip: CreationOptional; declare lat: CreationOptional; declare lon: CreationOptional; @@ -57,13 +57,16 @@ export function initializeStudentModel(sequelize: Sequelize) { allowNull: false, }, institution: { - type: DataTypes.STRING + type: DataTypes.STRING, + defaultValue: null, }, age: { type: DataTypes.TINYINT, + defaultValue: null, }, gender: { - type: DataTypes.STRING + type: DataTypes.STRING, + defaultValue: null, }, ip: { type: DataTypes.STRING From 36b5373ddc95a736d3b2c4a2391e0df7aa36296c Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Wed, 11 Sep 2024 13:09:51 -0400 Subject: [PATCH 09/10] Update model and SQL for educator to account for presence of username field. --- src/models/educator.ts | 6 ++++++ src/sql/create_educator_table.sql | 1 + 2 files changed, 7 insertions(+) diff --git a/src/models/educator.ts b/src/models/educator.ts index 9934ac2..8d57ccd 100644 --- a/src/models/educator.ts +++ b/src/models/educator.ts @@ -6,6 +6,7 @@ export class Educator extends Model, InferCreationAttr declare verified: number; declare verification_code: string; declare email: string; + declare username: string; declare first_name: string; declare last_name: string; declare password: string; @@ -45,6 +46,11 @@ export function initializeEducatorModel(sequelize: Sequelize) { allowNull: false, unique: true }, + username: { + type: DataTypes.STRING, + allowNull: false, + unique: true + }, first_name: { type: DataTypes.STRING, allowNull: false, diff --git a/src/sql/create_educator_table.sql b/src/sql/create_educator_table.sql index ba6b1b2..da4e2f7 100644 --- a/src/sql/create_educator_table.sql +++ b/src/sql/create_educator_table.sql @@ -16,6 +16,7 @@ CREATE TABLE Educators ( visits int(11) NOT NULL DEFAULT 0, last_visit datetime NOT NULL, last_visit_ip varchar(50) COLLATE utf8_unicode_ci, + username varchar(64) COLLATE utf8_unicode_ci NOT NULL UNIQUE, PRIMARY KEY(id), INDEX(last_name) From 506808e2e7bd04b02bb54dd6ccfe59b5e1de5d0a Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Wed, 11 Sep 2024 13:10:36 -0400 Subject: [PATCH 10/10] Add endpoint for getting an educator. --- src/database.ts | 13 ++++++++++--- src/server.ts | 23 ++++++++++++++++++++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/database.ts b/src/database.ts index e69051e..6f408ff 100644 --- a/src/database.ts +++ b/src/database.ts @@ -124,19 +124,25 @@ async function _findStudentByEmail(email: string): Promise { export async function findStudentByUsername(username: string): Promise { return Student.findOne({ - where: { username: username } + where: { username } }); } export async function findStudentById(id: number): Promise { return Student.findOne({ - where: { id : id } + where: { id } }); } export async function findEducatorById(id: number): Promise { return Educator.findOne({ - where: { id: id } + where: { id } + }); +} + +export async function findEducatorByUsername(username: string): Promise { + return Educator.findOne({ + where: { username }, }); } @@ -197,6 +203,7 @@ export interface SignUpEducatorOptions { last_name: string; password: string; email: string; + username: string; institution?: string; age?: number; gender?: string; diff --git a/src/server.ts b/src/server.ts index 8959961..948e56b 100644 --- a/src/server.ts +++ b/src/server.ts @@ -39,6 +39,8 @@ import { StageStateQuery, CreateClassResponse, UserType, + findEducatorByUsername, + findEducatorById, } from "./database"; import { getAPIKey, hasPermission } from "./authorization"; @@ -232,6 +234,7 @@ app.post("/educator-sign-up", async (req, res) => { typeof data.password === "string" && ((typeof data.institution === "string") || (data.institution == null)) && typeof data.email === "string" && + typeof data.username === "string" && ((typeof data.age === "number") || (data.age == null)) && ((typeof data.gender === "string") || data.gender == null) ); @@ -464,7 +467,7 @@ app.get([ res.statusCode = 404; } res.json({ - student: student + student, }); }); @@ -495,6 +498,24 @@ app.get("/students/:identifier/classes", async (req, res) => { }); +app.get("/educators/:identifier", async (req, res) => { + const params = req.params; + const id = Number(params.identifier); + + let educator; + if (isNaN(id)) { + educator = await findEducatorByUsername(params.identifier); + } else { + educator = await findEducatorById(id); + } + if (educator == null) { + res.statusCode = 404; + } + res.json({ + educator, + }); +}); + app.post("/classes/join", async (req, res) => { const username = req.body.username as string; const classCode = req.body.class_code as string;