update: revision backend for assessment

This commit is contained in:
abiyasa05 2024-10-09 11:45:57 +07:00
parent 00c09c1a4e
commit 0f0f974c5e

View File

@ -1,4 +1,4 @@
import { and, eq, ilike, or, sql } from "drizzle-orm"; import { and, eq, ilike, isNull, inArray, or, sql } from "drizzle-orm";
import { Hono } from "hono"; import { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
import db from "../../drizzle"; import db from "../../drizzle";
@ -42,6 +42,133 @@ async function updateFilenameInDatabase(answerId: string, filename: string): Pro
const assessmentsRoute = new Hono<HonoEnv>() const assessmentsRoute = new Hono<HonoEnv>()
.use(authInfo) .use(authInfo)
// Get all aspects
.get(
"/aspect",
// checkPermission("managementAspect.readAll"),
requestValidator(
"query",
z.object({
includeTrashed: z
.string()
.optional()
.transform((v) => v?.toLowerCase() === "true"),
withMetadata: z
.string()
.optional()
.transform((v) => v?.toLowerCase() === "true"),
page: z.coerce.number().int().min(0).default(0),
limit: z.coerce.number().int().min(1).max(1000).default(10),
q: z.string().default(""),
})
),
async (c) => {
const { includeTrashed, page, limit, q } = c.req.valid("query");
const totalCountQuery = includeTrashed
? sql<number>`(SELECT count(DISTINCT ${aspects.id}) FROM ${aspects})`
: sql<number>`(SELECT count(DISTINCT ${aspects.id}) FROM ${aspects} WHERE ${aspects.deletedAt} IS NULL)`;
const aspectIdsQuery = await db
.select({
id: aspects.id,
})
.from(aspects)
.where(
and(
includeTrashed ? undefined : isNull(aspects.deletedAt),
q ? or(ilike(aspects.name, q), eq(aspects.id, q)) : undefined
)
)
.offset(page * limit)
.limit(limit);
const aspectIds = aspectIdsQuery.map(a => a.id);
if (aspectIds.length === 0) {
return c.json({
data: [],
_metadata: {
currentPage: page,
totalPages: 0,
totalItems: 0,
perPage: limit,
},
});
}
// Main query to get aspects, sub-aspects, and number of questions
const result = await db
.select({
id: aspects.id,
name: aspects.name,
createdAt: aspects.createdAt,
updatedAt: aspects.updatedAt,
...(includeTrashed ? { deletedAt: aspects.deletedAt } : {}),
subAspectId: subAspects.id,
subAspectName: subAspects.name,
// Increase the number of questions related to sub aspects
questionCount: sql<number>`(
SELECT count(*)
FROM ${questions}
WHERE ${questions.subAspectId} = ${subAspects.id}
)`.as('questionCount'),
fullCount: totalCountQuery,
})
.from(aspects)
.leftJoin(subAspects, eq(subAspects.aspectId, aspects.id))
.where(inArray(aspects.id, aspectIds));
// Grouping sub aspects by aspect ID
const groupedResult = result.reduce((acc, curr) => {
const aspectId = curr.id;
if (!acc[aspectId]) {
acc[aspectId] = {
id: curr.id,
name: curr.name,
createdAt: curr.createdAt ? new Date(curr.createdAt).toISOString() : null,
updatedAt: curr.updatedAt ? new Date(curr.updatedAt).toISOString() : null,
subAspects: curr.subAspectName
? [{ id: curr.subAspectId!, name: curr.subAspectName, questionCount: curr.questionCount }]
: [],
};
} else {
if (curr.subAspectName) {
const exists = acc[aspectId].subAspects.some(sub => sub.id === curr.subAspectId);
if (!exists) {
acc[aspectId].subAspects.push({
id: curr.subAspectId!,
name: curr.subAspectName,
questionCount: curr.questionCount,
});
}
}
}
return acc;
}, {} as Record<string, {
id: string;
name: string;
createdAt: string | null;
updatedAt: string | null;
subAspects: { id: string; name: string; questionCount: number }[];
}>);
const groupedArray = Object.values(groupedResult);
return c.json({
data: groupedArray,
_metadata: {
currentPage: page,
totalPages: Math.ceil((Number(result[0]?.fullCount) ?? 0) / limit),
totalItems: Number(result[0]?.fullCount) ?? 0,
perPage: limit,
},
});
}
)
// Get data for current Assessment Score from submitted options By Assessment Id // Get data for current Assessment Score from submitted options By Assessment Id
.get( .get(
"/getCurrentAssessmentScore", "/getCurrentAssessmentScore",
@ -77,6 +204,20 @@ const assessmentsRoute = new Hono<HonoEnv>()
"/getAllQuestions", "/getAllQuestions",
checkPermission("assessments.readAllQuestions"), checkPermission("assessments.readAllQuestions"),
async (c) => { async (c) => {
// Definisikan tipe untuk hasil query dan izinkan nilai null
type QuestionWithOptions = {
aspectsId: string | null;
aspectsName: string | null;
subAspectId: string | null;
subAspectName: string | null;
questionId: string | null;
questionText: string | null;
optionId: string;
optionText: string;
optionScore: number;
fullCount?: number;
};
const totalCountQuery = const totalCountQuery =
sql<number>`(SELECT count(*) sql<number>`(SELECT count(*)
FROM ${options} FROM ${options}
@ -86,15 +227,16 @@ const assessmentsRoute = new Hono<HonoEnv>()
WHERE ${questions.deletedAt} IS NULL WHERE ${questions.deletedAt} IS NULL
)`; )`;
const result = await db // Sesuaikan tipe hasil query
const result: QuestionWithOptions[] = await db
.select({ .select({
optionId: options.id,
aspectsId: aspects.id, aspectsId: aspects.id,
aspectsName: aspects.name, aspectsName: aspects.name,
subAspectId: subAspects.id, subAspectId: subAspects.id,
subAspectName: subAspects.name, subAspectName: subAspects.name,
questionId: questions.id, questionId: questions.id,
questionText: questions.question, questionText: questions.question,
optionId: options.id,
optionText: options.text, optionText: options.text,
optionScore: options.score, optionScore: options.score,
fullCount: totalCountQuery, fullCount: totalCountQuery,
@ -103,15 +245,61 @@ const assessmentsRoute = new Hono<HonoEnv>()
.leftJoin(questions, eq(options.questionId, questions.id)) .leftJoin(questions, eq(options.questionId, questions.id))
.leftJoin(subAspects, eq(questions.subAspectId, subAspects.id)) .leftJoin(subAspects, eq(questions.subAspectId, subAspects.id))
.leftJoin(aspects, eq(subAspects.aspectId, aspects.id)) .leftJoin(aspects, eq(subAspects.aspectId, aspects.id))
.where(sql`${questions.deletedAt} IS NULL`) .where(sql`${questions.deletedAt} IS NULL`);
// Definisikan tipe untuk hasil pengelompokan
type GroupedQuestion = {
questionId: string | null;
questionText: string | null;
aspectsId: string | null;
aspectsName: string | null;
subAspectId: string | null;
subAspectName: string | null;
options: {
optionId: string;
optionText: string;
optionScore: number;
}[];
};
// Mengelompokkan berdasarkan questionId
const groupedResult: GroupedQuestion[] = result.reduce((acc, current) => {
const { questionId, questionText, aspectsId, aspectsName, subAspectId, subAspectName, optionId, optionText, optionScore } = current;
// Cek apakah questionId sudah ada dalam accumulator
const existingQuestion = acc.find(q => q.questionId === questionId);
if (existingQuestion) {
// Tambahkan opsi baru ke array options dari pertanyaan yang ada
existingQuestion.options.push({
optionId,
optionText,
optionScore
});
} else {
// Jika pertanyaan belum ada, tambahkan objek baru
acc.push({
questionId,
questionText,
aspectsId,
aspectsName,
subAspectId,
subAspectName,
options: [
{
optionId,
optionText,
optionScore
}
]
});
}
return acc;
}, [] as GroupedQuestion[]); // Pastikan tipe untuk accumulator didefinisikan
return c.json({ return c.json({
data: result.map((d) => ( data: groupedResult,
{
...d,
fullCount: undefined
}
)),
}); });
} }
) )
@ -159,10 +347,10 @@ const assessmentsRoute = new Hono<HonoEnv>()
eq(answers.assessmentId, assessmentId), // Filter by assessmentId eq(answers.assessmentId, assessmentId), // Filter by assessmentId
q q
? or( ? or(
ilike(answers.filename, q), ilike(answers.filename, q),
ilike(answers.validationInformation, q), ilike(answers.validationInformation, q),
eq(answers.id, q) eq(answers.id, q)
) )
: undefined : undefined
) )
) )
@ -185,26 +373,26 @@ const assessmentsRoute = new Hono<HonoEnv>()
// Toggles the isFlagged field between true and false // Toggles the isFlagged field between true and false
.patch( .patch(
"/:id/toggleFlag", "/:questionId/toggleFlag",
checkPermission("assessments.toggleFlag"), checkPermission("assessments.toggleFlag"),
async (c) => { async (c) => {
const answerId = c.req.param("id"); const questionId = c.req.param("questionId");
// Retrieve the current state of isFlagged // Join answers and options to retrieve answer based on questionId
const currentAnswer = await db const currentAnswer = await db
.select({ .select({
isFlagged: answers.isFlagged, isFlagged: answers.isFlagged,
answerId: answers.id,
}) })
.from(answers) .from(answers)
.where(eq(answers.id, answerId)) .innerJoin(options, eq(answers.optionId, options.id))
.where(eq(options.questionId, questionId))
.limit(1); .limit(1);
if (!currentAnswer.length) { if (!currentAnswer.length) {
throw notFound( throw notFound({
{ message: "Answer not found",
message: "Answer not found", });
}
)
} }
// Toggle the isFlagged value // Toggle the isFlagged value
@ -216,15 +404,13 @@ const assessmentsRoute = new Hono<HonoEnv>()
.set({ .set({
isFlagged: newIsFlaggedValue, isFlagged: newIsFlaggedValue,
}) })
.where(eq(answers.id, answerId)) .where(eq(answers.id, currentAnswer[0].answerId))
.returning(); .returning();
if (!updatedAnswer.length) { if (!updatedAnswer.length) {
throw notFound( throw notFound({
{ message: "Failed to update answer",
message: "Failed to update answer", });
}
)
} }
return c.json( return c.json(
@ -409,7 +595,7 @@ const assessmentsRoute = new Hono<HonoEnv>()
// Get data for One Sub Aspect average score By Sub Aspect Id and Assessment Id // Get data for One Sub Aspect average score By Sub Aspect Id and Assessment Id
.get( .get(
'/average-score/sub-aspects/:subAspectId/assessments/:assessmentId', '/average-score/sub-aspects/:subAspectId/assessments/:assessmentId',
checkPermission("assessments.readAverageSubAspect"), // checkPermission("assessments.readAssessmentScore"),
async (c) => { async (c) => {
const { subAspectId, assessmentId } = c.req.param(); const { subAspectId, assessmentId } = c.req.param();
@ -437,10 +623,10 @@ const assessmentsRoute = new Hono<HonoEnv>()
} }
) )
// Get data for All Sub Aspects average score By Assessment Id // Get data for All Sub Aspects average score By Assessment Id
.get( .get(
'/average-score/sub-aspects/assessments/:assessmentId', '/average-score/sub-aspects/assessments/:assessmentId',
checkPermission("assessments.readAverageAllSubAspects"), // checkPermission("assessments.readAssessmentScore"),
async (c) => { async (c) => {
const { assessmentId } = c.req.param(); const { assessmentId } = c.req.param();
@ -472,7 +658,7 @@ const assessmentsRoute = new Hono<HonoEnv>()
// Get data for One Aspect average score By Aspect Id and Assessment Id // Get data for One Aspect average score By Aspect Id and Assessment Id
.get( .get(
"/average-score/aspects/:aspectId/assessments/:assessmentId", "/average-score/aspects/:aspectId/assessments/:assessmentId",
checkPermission("assessments.readAverageAspect"), // checkPermission("assessments.readAverageAspect"),
async (c) => { async (c) => {
const { aspectId, assessmentId } = c.req.param(); const { aspectId, assessmentId } = c.req.param();
@ -504,7 +690,7 @@ const assessmentsRoute = new Hono<HonoEnv>()
// Get data for All Aspects average score By Assessment Id // Get data for All Aspects average score By Assessment Id
.get( .get(
'/average-score/aspects/assessments/:assessmentId', '/average-score/aspects/assessments/:assessmentId',
checkPermission("assessments.readAverageAllAspects"), // checkPermission("assessments.readAssessmentScore"),
async (c) => { async (c) => {
const { assessmentId } = c.req.param(); const { assessmentId } = c.req.param();