Skip to content

Commit b58266a

Browse files
authored
PROD (#49)
This pull request introduces new modules and endpoints to support academic and "academic chaos" question grading and retrieval for staff, along with corresponding schema and database updates. It also refactors and expands the grading logic for both academic and academic chaos questions, ensuring staff can grade answers, retrieve scores, and update application status accordingly. Key changes include: **New Features: Academic and Academic Chaos Grading Modules** - Added `StaffAcademicGradingModule`, `StaffAcademicChaosGradingModule`, `StaffAcademicQuestionModule`, and `StaffAcademicChaosQuestionModule`, including their controllers, services, and DTOs, to handle grading and question retrieval for both academic and academic chaos sections. These modules provide endpoints for listing, retrieving by application ID, and grading student answers, with appropriate guards for access control. [[1]](diffhunk://#diff-089f4f2474b64391c42b6e66aed33977e132058d92108f0a63234a7862e1f8b8R26-R29) [[2]](diffhunk://#diff-089f4f2474b64391c42b6e66aed33977e132058d92108f0a63234a7862e1f8b8R80-R82) [[3]](diffhunk://#diff-39fa606a587c8875669aa333f9833aadfd157e0bb0c43a7274cffb2453db2515R1-R21) [[4]](diffhunk://#diff-d85b80b1391c1e79cc9921615f6f57106699caf5b4e27b62015e5ff8cccc7763L1-R26) [[5]](diffhunk://#diff-f653883d2992110b94e450feebf920cd1c3f0f6e48c0c969cdd01f9bdfa40550L1-R111) [[6]](diffhunk://#diff-875ea014b8cf2e9788c4f51c7a4024baf2b79d29a191155d01e4a324055b0786R1-R21) [[7]](diffhunk://#diff-8d493d5216d89fe155e5a73cb27fb1c008afa59b40a8ab883231fb79ea75d369R1-R9) [[8]](diffhunk://#diff-8bf04ccbe253ebb8e455397b08807b6296c430691c9d3e73949b98c994386c16R1-R77) [[9]](diffhunk://#diff-7e92598208c33c21a1aa7d3ca70517806d959bb48aed427e1344508545967022R1-R21) [[10]](diffhunk://#diff-335212c3435deccac0b550f33d8fe1edec6b105812a2d25e00e270dd35846821R1-R28) [[11]](diffhunk://#diff-07333057a1e896b3c57905c76daf2102dfc918438e2630329d4d6488d7e513ecR1-R9) [[12]](diffhunk://#diff-df805b2b8a2f3be495261efa9baf5668f11d4a1bf7e0527f464fd72d99772f81R1-R113) [[13]](diffhunk://#diff-34345011061309e1b53b5eedd4cd7547573b6151242b3a8d3c6d0e7015fa07faR1-R20) [[14]](diffhunk://#diff-b14fb1d48d797b19001dfb5b92d9e6df826752ad341acf99c8877a720269e8c8R1-R9) [[15]](diffhunk://#diff-926c2215ab17b6ceef68010c7c97c849edc366950b4fadcb3cb19bdf453d5210R1-R77) **Database and Schema Updates** - Updated the Prisma schema and created a migration to add the `stf_academic_chaos_question_checked` boolean field to the `ApplicationStatus` table, allowing the system to track whether academic chaos questions have been checked for each application. [[1]](diffhunk://#diff-aaee65c56c24c6524c6d8956c9fb32019cf3a23df95566391981faa6c197c0edR1-R2) [[2]](diffhunk://#diff-5b443964f4f3a611682db8f7e02177b0a8c632b2039e2bd5e4dd7347815c565cR216-R217) **Grading Logic Enhancements** - Refactored grading services to: - Delete existing scores by the same staff before creating new ones. - Aggregate and return scores, including staff user info. - Update application status after grading is completed. - Add error handling and logging for robustness. [[1]](diffhunk://#diff-f653883d2992110b94e450feebf920cd1c3f0f6e48c0c969cdd01f9bdfa40550L1-R111) [[2]](diffhunk://#diff-df805b2b8a2f3be495261efa9baf5668f11d4a1bf7e0527f464fd72d99772f81R1-R113) **Endpoints for Score Aggregation and Retrieval** - Implemented endpoints and logic to aggregate total scores for each application and retrieve answers (with scores) by application ID, for both academic and academic chaos questions. [[1]](diffhunk://#diff-8bf04ccbe253ebb8e455397b08807b6296c430691c9d3e73949b98c994386c16R1-R77) [[2]](diffhunk://#diff-926c2215ab17b6ceef68010c7c97c849edc366950b4fadcb3cb19bdf453d5210R1-R77) **Minor Improvements** - Improved error messages for incomplete grading in the status updater service. <img width="640" height="640" alt=" asd " src="https://github.com/user-attachments/assets/0d985af6-cf82-4be1-bd3a-67a1e478dccb" />
2 parents 5356246 + 7b0c5f9 commit b58266a

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)