Skip to content

Commit

Permalink
feat: student auth and temporary rejoin code generation
Browse files Browse the repository at this point in the history
  • Loading branch information
sgfost committed Jun 18, 2024
1 parent d36d8e3 commit c700395
Show file tree
Hide file tree
Showing 12 changed files with 138 additions and 26 deletions.
35 changes: 34 additions & 1 deletion client/src/api/educator/request.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { url } from "@port-of-mars/client/util";
import { TStore } from "@port-of-mars/client/plugins/tstore";
import { AjaxRequest } from "@port-of-mars/client/plugins/ajax";
import { StudentAuthData } from "@port-of-mars/shared/types";

export class EducatorAPI {
constructor(public store: TStore, public ajax: AjaxRequest) {}

async authenticateTeacher(): Promise<boolean> {
async authTeacher(): Promise<boolean> {
return true; //FIXME: needs to be implemented on the server
try {
return await this.ajax.get(url("/educator/authenticate-teacher"), ({ data }) => {
Expand All @@ -17,4 +18,36 @@ export class EducatorAPI {
throw e;
}
}

async authStudent(): Promise<StudentAuthData | void> {
try {
return await this.ajax.get(url("/educator/student"), ({ data }) => {
return data;
});
} catch (e) {
console.log("Unable to authenticate student");
console.log(e);
throw e;
}
}

async confirmStudent(formData: { name: string }) {
try {
await this.ajax.post(
url("/educator/confirm-student"),
({ data, status }) => {
if (status === 200) {
return data;
} else {
return data;
}
},
formData
);
} catch (e) {
console.log("Unable to confirm student");
console.log(e);
throw e;
}
}
}
6 changes: 3 additions & 3 deletions client/src/views/ClassroomLobby.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
<div class="mt-5 d-flex flex-grow-1 flex-column align-items-center justify-content-start pt-2">
<div class="">
<h1>WELCOME TO THE LOBBY</h1>
<div class="text-center" style="padding: 1rem;">
<div class="text-center" style="padding: 1rem">
<p>Total joined: {{ clients.length }}</p>
<p>Please wait patiently for your teacher to start game....</p>
<div v-if="isTeacher" class="start-game-button" style="padding: 1rem;">
<div v-if="isTeacher" class="start-game-button" style="padding: 1rem">
<b-button @click="startGame">Start Game</b-button>
</div>
</div>
Expand Down Expand Up @@ -95,7 +95,7 @@ export default class ClassroomLobby extends Vue {
//call to server to find out user's role
// then add button above for starting game if user is a teacher
this.educatorApi = new EducatorAPI(this.$store, this.$ajax);
this.isTeacher = await this.educatorApi.authenticateTeacher();
this.isTeacher = await this.educatorApi.authTeacher();
}
}
</script>
Expand Down
34 changes: 25 additions & 9 deletions client/src/views/StudentConfirm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@
"
class="rounded text-center"
>
PASSWORD: {{ password }}
PASSWORD: {{ rejoinCode }}
</div>
<div style="font-size: 1.2rem; font-weight: bold; margin-top: 0.5rem">
Write down this passcode to rejoin
</div>
</div>

<!-- Join Class Lobby Button -->
<b-button variant="primary" rounded @click="joinClassLobby" class="w-70" size="lg">
<b-button variant="primary" rounded @click="handleSubmit" class="w-70" size="lg">
<h4 class="mb-0">Join Class Lobby</h4>
</b-button>
</div>
Expand All @@ -68,23 +68,39 @@

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { EducatorAPI } from "@port-of-mars/client/api/educator/request";
import { CLASSROOM_LOBBY_PAGE, STUDENT_LOGIN_PAGE } from "@port-of-mars/shared/routes";
@Component
export default class StudentConfirm extends Vue {
educatorApi: EducatorAPI = new EducatorAPI(this.$store, this.$ajax);
firstName: string = "";
lastName: string = "";
password: string = "placeholder";
joinClassLobby() {
// FIXME: should send request to update user info and then redirect
this.$router.push("/classroom");
}
rejoinCode: string = "placeholder";
get username() {
return this.$tstore.state.user.username;
}
created() {}
async handleSubmit() {
try {
await this.educatorApi.confirmStudent({
name: `${this.firstName} ${this.lastName}`,
});
this.$router.push({ name: CLASSROOM_LOBBY_PAGE });
} catch (e) {
console.error(e);
}
}
async created() {
const data = await this.educatorApi.authStudent();
if (!data) {
this.$router.push({ name: STUDENT_LOGIN_PAGE });
} else {
this.rejoinCode = data.rejoinCode;
}
}
}
</script>

Expand Down
2 changes: 1 addition & 1 deletion client/src/views/TeacherDashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ export default class TeacherDashboard extends Vue {
//call to server to find out user's role
// then add button above for starting game if user is a teacher
this.educatorApi = new EducatorAPI(this.$store, this.$ajax);
this.isTeacher = await this.educatorApi.authenticateTeacher();
this.isTeacher = await this.educatorApi.authTeacher();
}
}
</script>
Expand Down
2 changes: 1 addition & 1 deletion server/src/entity/Classroom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class Classroom {
@Column()
teacherId!: number;

@Column()
@Column({ unique: true })
authToken!: string;

@Column()
Expand Down
4 changes: 2 additions & 2 deletions server/src/entity/Student.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ export class Student {
@Column()
classroomId!: number;

@Column()
password!: string;
@Column({ unique: true })
rejoinCode!: string;
}
4 changes: 4 additions & 0 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
tournamentRouter,
statusRouter,
statsRouter,
educatorRouter,
} from "@port-of-mars/server/routes";
import { ServerError } from "@port-of-mars/server/util";
import dataSource from "@port-of-mars/server/datasource";
Expand Down Expand Up @@ -225,6 +226,9 @@ async function createApp() {
app.use("/quiz", quizRouter);
app.use("/account", accountRouter);
app.use("/status", statusRouter);
if (isEducatorMode()) {
app.use("/educator", educatorRouter);
}

const server = http.createServer(app);
const gameServer = new Server({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {MigrationInterface, QueryRunner} from "typeorm";

export class MakeStudentCodeClassroomCodeUnique1715639656593 implements MigrationInterface {
name = 'MakeStudentCodeClassroomCodeUnique1715639656593'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "student" RENAME COLUMN "password" TO "rejoinCode"`);
await queryRunner.query(`ALTER TABLE "student" ADD CONSTRAINT "UQ_77adce5802e7f39e1fb53885c8f" UNIQUE ("rejoinCode")`);
await queryRunner.query(`ALTER TABLE "classroom" ADD CONSTRAINT "UQ_5001c4e5dbc1507f8ad6578f3e1" UNIQUE ("authToken")`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "classroom" DROP CONSTRAINT "UQ_5001c4e5dbc1507f8ad6578f3e1"`);
await queryRunner.query(`ALTER TABLE "student" DROP CONSTRAINT "UQ_77adce5802e7f39e1fb53885c8f"`);
await queryRunner.query(`ALTER TABLE "student" RENAME COLUMN "rejoinCode" TO "password"`);
}

}
21 changes: 18 additions & 3 deletions server/src/routes/educator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,30 @@ export const educatorRouter = Router();

educatorRouter.use(isAuthenticated);

educatorRouter.get("/student", async (req: Request, res: Response, next: NextFunction) => {
const user = req.user as User;
try {
const services = getServices();
const student = await services.educator.getStudentByUser(user.id, true);
if (!student) {
res.status(404).json({ message: "Student not found" });
return;
}
res.json(student);
return;
} catch (e) {
logger.warn("Unable to authorize student for user ID %d", user.id);
next(e);
}
});

educatorRouter.post("/confirm-student", async (req: Request, res: Response, next: NextFunction) => {
const user = req.user as User;
try {
const services = getServices();
const data = { ...req.body };
await services.account.setName(user.id, data.name);
// TODO: set verified, etc?
// generate password and return it
// res.json(password);
res.json(true);
} catch (e) {
if (e instanceof ValidationError) {
res.status(e.code).json(e.toDashboardMessage());
Expand Down
1 change: 1 addition & 0 deletions server/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from "./quiz";
export * from "./account";
export * from "./status";
export * from "./tournament";
export * from "./educator";
27 changes: 21 additions & 6 deletions server/src/services/educator.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { v4 as uuidv4 } from "uuid";
import { BaseService } from "@port-of-mars/server/services/db";
import { Teacher, Classroom, Student, User } from "@port-of-mars/server/entity";
import { ServerError, generateUsername } from "@port-of-mars/server/util";
Expand All @@ -7,13 +8,24 @@ import { getServices } from "@port-of-mars/server/services";
// const logger = settings.logging.getLogger(__filename);

export class EducatorService extends BaseService {
async getTeacherByUserId(userId: number) {
async getTeacherByUserId(userId: number, withUser = false) {
return this.em.getRepository(Teacher).findOne({
where: { userId },
relations: withUser ? ["user"] : [],
});
}

async generateStudentPassword() {}
async getStudentByUser(userId: number, withUser = false) {
return this.em.getRepository(Student).findOne({
where: { userId },
relations: withUser ? ["user"] : [],
});
}

async generateStudentRejoinCode(): Promise<string> {
// FIXME: generate a unique rejoin code
return uuidv4();
}

async createStudent(classroomAuthToken: string) {
/**
Expand All @@ -37,10 +49,13 @@ export class EducatorService extends BaseService {
user.username = await generateUsername();
await userRepo.save(user);

const student = new Student();
student.user = user;
student.classroom = classroom;
student.password = "something"; //FIXME
const student = studentRepo.create({
user,
userId: user.id,
classroom,
classroomId: classroom.id,
rejoinCode: await this.generateStudentRejoinCode(),
});
await studentRepo.save(student);

return user;
Expand Down
10 changes: 10 additions & 0 deletions shared/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,3 +522,13 @@ export interface DynamicSettingsData {
tournamentSignupsPopularityThreshold: number;
announcementBannerText: string;
}

// educator mode-specific types

export interface StudentAuthData {
id: number;
userId: number;
user?: ClientSafeUser;
classroomId: number;
rejoinCode: string;
}

0 comments on commit c700395

Please sign in to comment.