Skip to content

Commit 7b0c5f9

Browse files
authored
Feat/staff academic question (#48)
This pull request introduces new modules and endpoints for handling both academic and academic "chaos" question grading and retrieval by staff, and refactors the grading logic to support these new workflows. It also extends the application status model to track the completion of academic chaos question checks. **New modules and endpoints for academic and academic chaos question grading:** * Added `StaffAcademicChaosGradingModule`, `StaffAcademicChaosQuestionModule`, and `StaffAcademicQuestionModule`, each with their own controller and service, to support grading and retrieval of academic and academic chaos questions and answers. These modules expose endpoints for listing all answers, retrieving answers by application ID, and submitting grades, all protected by the `AcademicGuard`. [[1]](diffhunk://#diff-089f4f2474b64391c42b6e66aed33977e132058d92108f0a63234a7862e1f8b8R26-R29) [[2]](diffhunk://#diff-089f4f2474b64391c42b6e66aed33977e132058d92108f0a63234a7862e1f8b8R80-R82) [[3]](diffhunk://#diff-07333057a1e896b3c57905c76daf2102dfc918438e2630329d4d6488d7e513ecR1-R9) [[4]](diffhunk://#diff-b14fb1d48d797b19001dfb5b92d9e6df826752ad341acf99c8877a720269e8c8R1-R9) [[5]](diffhunk://#diff-8d493d5216d89fe155e5a73cb27fb1c008afa59b40a8ab883231fb79ea75d369R1-R9) [[6]](diffhunk://#diff-335212c3435deccac0b550f33d8fe1edec6b105812a2d25e00e270dd35846821R1-R28) [[7]](diffhunk://#diff-34345011061309e1b53b5eedd4cd7547573b6151242b3a8d3c6d0e7015fa07faR1-R20) [[8]](diffhunk://#diff-875ea014b8cf2e9788c4f51c7a4024baf2b79d29a191155d01e4a324055b0786R1-R21) * Implemented service logic for these modules to fetch relevant answers, calculate total scores, and handle grading, with error handling and logging. [[1]](diffhunk://#diff-df805b2b8a2f3be495261efa9baf5668f11d4a1bf7e0527f464fd72d99772f81R1-R113) [[2]](diffhunk://#diff-926c2215ab17b6ceef68010c7c97c849edc366950b4fadcb3cb19bdf453d5210R1-R77) [[3]](diffhunk://#diff-8bf04ccbe253ebb8e455397b08807b6296c430691c9d3e73949b98c994386c16R1-R77) [[4]](diffhunk://#diff-f653883d2992110b94e450feebf920cd1c3f0f6e48c0c969cdd01f9bdfa40550L1-R111) **Database and schema changes:** * Added a new boolean column `stf_academic_chaos_question_checked` to the `ApplicationStatus` table and updated the Prisma schema to track whether chaos academic questions have been checked for an application. [[1]](diffhunk://#diff-aaee65c56c24c6524c6d8956c9fb32019cf3a23df95566391981faa6c197c0edR1-R2) [[2]](diffhunk://#diff-5b443964f4f3a611682db8f7e02177b0a8c632b2039e2bd5e4dd7347815c565cR216-R217) **DTOs and validation:** * Introduced DTOs for grading endpoints (`StaffAcademicGradingDto` and `StaffAcademicChaosGradingDto`) with validation decorators for incoming grading data. [[1]](diffhunk://#diff-39fa606a587c8875669aa333f9833aadfd157e0bb0c43a7274cffb2453db2515R1-R21) [[2]](diffhunk://#diff-7e92598208c33c21a1aa7d3ca70517806d959bb48aed427e1344508545967022R1-R21) **Other improvements:** * Updated error messages in the staff status updater service for clarity. These changes collectively provide a robust backend structure for staff to manage, grade, and review both standard and chaos academic questions, with clear status tracking and validation throughout the process.
2 parents 5b6a9ee + ba65f48 commit 7b0c5f9

17 files changed

Lines changed: 615 additions & 8 deletions
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "ApplicationStatus" ADD COLUMN "stf_academic_chaos_question_checked" BOOLEAN DEFAULT false;

prisma/schema.prisma

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@ model ApplicationStatus {
213213
214214
stf_regis_question_checked Boolean? @default(false)
215215
stf_academic_question_checked Boolean? @default(false)
216+
stf_academic_chaos_question_checked Boolean? @default(false)
217+
216218
217219
std_info_note String? @db.Text
218220

src/app.module.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ import { ApplicationQuestionModule } from "./modules/application-question/applic
2323
import { ApplicationStatusModule } from "./modules/application-status/application-status.module";
2424
import { ApplicationSubmitModule } from "./modules/application-submit/application-submit.module";
2525
import { EmailNotificationModule } from "./modules/email-notification/email-notification.module";
26+
import { StaffAcademicChaosGradingModule } from "./modules/staff-academic-chaos-grading/staff-academic-chaos-grading.module";
27+
import { StaffAcademicChaosQuestionModule } from "./modules/staff-academic-chaos-question/staff-academic-chaos-question.module";
2628
import { StaffAcademicGradingModule } from "./modules/staff-academic-grading/staff-academic-grading.module";
29+
import { StaffAcademicQuestionModule } from "./modules/staff-academic-question/staff-academic-question.module";
2730
import { StaffAccountModule } from "./modules/staff-account/staff-account.module";
2831
import { StaffApplicationModule } from "./modules/staff-application/staff-application.module";
2932
import { StaffFileModule } from "./modules/staff-file/staff-file.module";
@@ -74,6 +77,9 @@ import { UtilModule } from "./modules/util/util.module";
7477
LoggerModule,
7578
StaffStatusUpdaterModule,
7679
StaffFileModule,
80+
StaffAcademicQuestionModule,
81+
StaffAcademicChaosQuestionModule,
82+
StaffAcademicChaosGradingModule,
7783
],
7884

7985
controllers: [AppController],
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { IsNumber, IsOptional, IsString, IsUUID, Max, Min } from "class-validator";
2+
3+
export class StaffAcademicChaosGradingDto {
4+
@IsUUID()
5+
readonly application_id: string;
6+
7+
@IsNumber()
8+
readonly answer_id: number;
9+
10+
// @IsNumber()
11+
// @Max(2)
12+
// @Min(1)
13+
// readonly staff_count: number;
14+
15+
@IsNumber()
16+
readonly score: number;
17+
18+
// @IsString()
19+
// @IsOptional()
20+
// readonly comment?: string;
21+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Body, Controller, Get, Param, Post, UseGuards } from "@nestjs/common";
2+
import { Session, type UserSession } from "@thallesp/nestjs-better-auth";
3+
import { AcademicGuard } from "src/common/guards/academic.guard";
4+
import { StaffAcademicChaosGradingDto } from "./dto/staff-academic-chaos-grading.dto";
5+
import { StaffAcademicChaosGradingService } from "./staff-academic-chaos-grading.service";
6+
7+
@Controller("/api/staff/academic-chaos/answer")
8+
export class StaffAcademicChaosGradingController {
9+
constructor(private readonly staffAcademicChaosGradingService: StaffAcademicChaosGradingService) {}
10+
11+
@Get("/")
12+
@UseGuards(AcademicGuard)
13+
getAll() {
14+
return this.staffAcademicChaosGradingService.getAll();
15+
}
16+
17+
@Get("/:id")
18+
@UseGuards(AcademicGuard)
19+
getAnswerByAppId(@Param("id") appId: string) {
20+
return this.staffAcademicChaosGradingService.getAnswerByAppId(appId);
21+
}
22+
23+
@Post("/grading")
24+
@UseGuards(AcademicGuard)
25+
answerGrading(@Session() session: UserSession, @Body() staffAcademicChaosGradingDto: StaffAcademicChaosGradingDto) {
26+
return this.staffAcademicChaosGradingService.answerGrading(session.user.id, staffAcademicChaosGradingDto);
27+
}
28+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Module } from "@nestjs/common";
2+
import { StaffAcademicChaosGradingController } from "./staff-academic-chaos-grading.controller";
3+
import { StaffAcademicChaosGradingService } from "./staff-academic-chaos-grading.service";
4+
5+
@Module({
6+
controllers: [StaffAcademicChaosGradingController],
7+
providers: [StaffAcademicChaosGradingService],
8+
})
9+
export class StaffAcademicChaosGradingModule {}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { Injectable, InternalServerErrorException, NotFoundException } from "@nestjs/common";
2+
import { LoggerService } from "src/core/logger/logger.service";
3+
import { PrismaService } from "src/core/prisma/prisma.service";
4+
import { StaffStatusUpdaterService } from "../staff-status-updater/staff-status-updater.service";
5+
import { StaffAcademicChaosGradingDto } from "./dto/staff-academic-chaos-grading.dto";
6+
7+
@Injectable()
8+
export class StaffAcademicChaosGradingService {
9+
constructor(
10+
private readonly prisma: PrismaService,
11+
private readonly logger: LoggerService,
12+
private readonly staffStatusUpdaterService: StaffStatusUpdaterService,
13+
) {}
14+
15+
async getAll() {
16+
try {
17+
const allAnswers = await this.prisma.studentApplication.findMany({
18+
where: {
19+
std_application_submit: true,
20+
},
21+
include: {
22+
std_status: true,
23+
std_academic_chaos_question: {
24+
include: {
25+
stf_academic_chaos_question_score: {
26+
include: {
27+
stf_user: true,
28+
},
29+
},
30+
},
31+
},
32+
},
33+
});
34+
35+
if (!allAnswers) {
36+
throw new NotFoundException();
37+
}
38+
39+
return allAnswers;
40+
} catch (e) {
41+
this.logger.error(e);
42+
throw new InternalServerErrorException(e);
43+
}
44+
}
45+
46+
async getAnswerByAppId(appId: string) {
47+
try {
48+
const allAnswers = await this.prisma.applicationAcademicChaosQuestionAnswer.findMany({
49+
where: {
50+
std_application_id: appId,
51+
},
52+
include: {
53+
stf_academic_chaos_question_score: {
54+
include: {
55+
stf_user: true,
56+
},
57+
},
58+
},
59+
});
60+
61+
if (!allAnswers) {
62+
throw new NotFoundException();
63+
}
64+
65+
return allAnswers;
66+
} catch (e) {
67+
this.logger.error(e);
68+
throw new InternalServerErrorException(e);
69+
}
70+
}
71+
72+
async answerGrading(userId: string, staffAcademicChaosGradingDto: StaffAcademicChaosGradingDto) {
73+
try {
74+
// Delete existing score by the same staff before creating a new one
75+
await this.prisma.applicationAcademicChaosQuestionScore.deleteMany({
76+
where: {
77+
std_academic_chaos_answer: {
78+
std_application_id: staffAcademicChaosGradingDto.application_id,
79+
},
80+
std_academic_chaos_answer_id: staffAcademicChaosGradingDto.answer_id,
81+
stf_count: 1,
82+
},
83+
});
84+
85+
const createScore = await this.prisma.applicationAcademicChaosQuestionScore.create({
86+
data: {
87+
std_academic_chaos_answer_id: staffAcademicChaosGradingDto.answer_id,
88+
stf_count: 1,
89+
stf_score: staffAcademicChaosGradingDto.score,
90+
stf_user_id: userId,
91+
},
92+
});
93+
94+
const answerScore = await this.prisma.applicationAcademicChaosQuestionScore.findMany({
95+
where: {
96+
std_academic_chaos_answer: {
97+
std_application_id: staffAcademicChaosGradingDto.application_id,
98+
},
99+
std_academic_chaos_answer_id: staffAcademicChaosGradingDto.answer_id,
100+
},
101+
include: {
102+
stf_user: true,
103+
},
104+
});
105+
106+
await this.staffStatusUpdaterService.updateAcademicChaosQuestionCheckedStatus(staffAcademicChaosGradingDto.application_id);
107+
return answerScore;
108+
} catch (e) {
109+
this.logger.error(e);
110+
throw new InternalServerErrorException(e);
111+
}
112+
}
113+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Controller, Get, Param, UseGuards } from "@nestjs/common";
2+
import { AcademicGuard } from "src/common/guards/academic.guard";
3+
import { StaffAcademicChaosQuestionService } from "./staff-academic-chaos-question.service";
4+
5+
@Controller("/api/staff/academic-chaos/question")
6+
export class StaffAcademicChaosQuestionController {
7+
constructor(private readonly staffAcademicChaosQuestionService: StaffAcademicChaosQuestionService) {}
8+
9+
@Get("/")
10+
@UseGuards(AcademicGuard)
11+
getAllRegisAnswer() {
12+
return this.staffAcademicChaosQuestionService.getAllAcademicChaosAnswer();
13+
}
14+
15+
@Get("/:id")
16+
@UseGuards(AcademicGuard)
17+
getRegisAnswerByAppId(@Param("id") appId: string) {
18+
return this.staffAcademicChaosQuestionService.getAcademicChaosAnswerByAppId(appId);
19+
}
20+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Module } from "@nestjs/common";
2+
import { StaffAcademicChaosQuestionController } from "./staff-academic-chaos-question.controller";
3+
import { StaffAcademicChaosQuestionService } from "./staff-academic-chaos-question.service";
4+
5+
@Module({
6+
controllers: [StaffAcademicChaosQuestionController],
7+
providers: [StaffAcademicChaosQuestionService],
8+
})
9+
export class StaffAcademicChaosQuestionModule {}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { Injectable, InternalServerErrorException, NotFoundException } from "@nestjs/common";
2+
import { LoggerService } from "src/core/logger/logger.service";
3+
import { PrismaService } from "src/core/prisma/prisma.service";
4+
5+
@Injectable()
6+
export class StaffAcademicChaosQuestionService {
7+
constructor(
8+
private readonly prisma: PrismaService,
9+
private readonly logger: LoggerService,
10+
) {}
11+
12+
async getAllAcademicChaosAnswer() {
13+
try {
14+
const studentAcademicChaosAnswer = await this.prisma.studentApplication.findMany({
15+
include: {
16+
std_academic_chaos_question: {
17+
include: {
18+
stf_academic_chaos_question_score: true,
19+
},
20+
},
21+
std_status: true,
22+
},
23+
});
24+
25+
return studentAcademicChaosAnswer.map((stdAns) => {
26+
let score = 0;
27+
for (const { stf_academic_chaos_question_score } of stdAns.std_academic_chaos_question) {
28+
for (const { stf_score } of stf_academic_chaos_question_score) {
29+
score += stf_score;
30+
}
31+
}
32+
return {
33+
...stdAns,
34+
total_score: score,
35+
};
36+
});
37+
} catch (e) {
38+
this.logger.error(e);
39+
throw new InternalServerErrorException();
40+
}
41+
}
42+
43+
async getAcademicChaosAnswerByAppId(appId: string) {
44+
try {
45+
const studentAcademicChaosAnswer = await this.prisma.studentApplication.findUnique({
46+
where: {
47+
std_application_id: appId,
48+
},
49+
include: {
50+
std_academic_chaos_question: {
51+
include: {
52+
stf_academic_chaos_question_score: true,
53+
},
54+
},
55+
std_status: true,
56+
},
57+
});
58+
59+
if (!studentAcademicChaosAnswer) return new NotFoundException();
60+
61+
let score = 0;
62+
for (const { stf_academic_chaos_question_score } of studentAcademicChaosAnswer.std_academic_chaos_question) {
63+
for (const { stf_score } of stf_academic_chaos_question_score) {
64+
score += stf_score;
65+
}
66+
}
67+
68+
return {
69+
...studentAcademicChaosAnswer,
70+
total_score: score,
71+
};
72+
} catch (e) {
73+
this.logger.error(e);
74+
throw new InternalServerErrorException();
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)