Allowed admins and others to download reports related to other users
This commit is contained in:
@@ -1,15 +1,6 @@
|
|||||||
import type {NextApiRequest, NextApiResponse} from "next";
|
import type {NextApiRequest, NextApiResponse} from "next";
|
||||||
import {app, storage} from "@/firebase";
|
import {app, storage} from "@/firebase";
|
||||||
import {
|
import {getFirestore, doc, getDoc, updateDoc, getDocs, query, collection, where} from "firebase/firestore";
|
||||||
getFirestore,
|
|
||||||
doc,
|
|
||||||
getDoc,
|
|
||||||
updateDoc,
|
|
||||||
getDocs,
|
|
||||||
query,
|
|
||||||
collection,
|
|
||||||
where,
|
|
||||||
} from "firebase/firestore";
|
|
||||||
import {withIronSessionApiRoute} from "iron-session/next";
|
import {withIronSessionApiRoute} from "iron-session/next";
|
||||||
import {sessionOptions} from "@/lib/session";
|
import {sessionOptions} from "@/lib/session";
|
||||||
import ReactPDF from "@react-pdf/renderer";
|
import ReactPDF from "@react-pdf/renderer";
|
||||||
@@ -23,11 +14,7 @@ import { LevelExamDetails } from "@/exams/pdf/details/level.exam";
|
|||||||
import {calculateBandScore} from "@/utils/score";
|
import {calculateBandScore} from "@/utils/score";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import {moduleLabels} from "@/utils/moduleUtils";
|
import {moduleLabels} from "@/utils/moduleUtils";
|
||||||
import {
|
import {generateQRCode, getRadialProgressPNG, streamToBuffer} from "@/utils/pdf";
|
||||||
generateQRCode,
|
|
||||||
getRadialProgressPNG,
|
|
||||||
streamToBuffer,
|
|
||||||
} from "@/utils/pdf";
|
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
|
|
||||||
const db = getFirestore(app);
|
const db = getFirestore(app);
|
||||||
@@ -102,16 +89,14 @@ const getSkillsFeedback = async (sections: SkillsFeedbackRequest[]) => {
|
|||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${process.env.BACKEND_JWT}`,
|
Authorization: `Bearer ${process.env.BACKEND_JWT}`,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return backendRequest.data?.sections;
|
return backendRequest.data?.sections;
|
||||||
};
|
};
|
||||||
|
|
||||||
// perform the request with several retries if needed
|
// perform the request with several retries if needed
|
||||||
const handleSkillsFeedbackRequest = async (
|
const handleSkillsFeedbackRequest = async (sections: SkillsFeedbackRequest[]): Promise<SkillsFeedbackResponse[] | null> => {
|
||||||
sections: SkillsFeedbackRequest[]
|
|
||||||
): Promise<SkillsFeedbackResponse[] | null> => {
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
try {
|
try {
|
||||||
const data = await getSkillsFeedback(sections);
|
const data = await getSkillsFeedback(sections);
|
||||||
@@ -131,13 +116,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
if (req.session.user) {
|
if (req.session.user) {
|
||||||
const {id} = req.query as {id: string};
|
const {id} = req.query as {id: string};
|
||||||
// fetch stats entries for this particular user with the requested exam session
|
// fetch stats entries for this particular user with the requested exam session
|
||||||
const docsSnap = await getDocs(
|
const docsSnap = await getDocs(query(collection(db, "stats"), where("session", "==", id)));
|
||||||
query(
|
|
||||||
collection(db, "stats"),
|
|
||||||
where("session", "==", id),
|
|
||||||
where("user", "==", req.session.user.id)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (docsSnap.empty) {
|
if (docsSnap.empty) {
|
||||||
res.status(400).end();
|
res.status(400).end();
|
||||||
@@ -166,9 +145,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const user = docUser.data() as User;
|
const user = docUser.data() as User;
|
||||||
|
|
||||||
// generate the QR code for the report
|
// generate the QR code for the report
|
||||||
const qrcode = await generateQRCode(
|
const qrcode = await generateQRCode((req.headers.origin || "") + req.url);
|
||||||
(req.headers.origin || "") + req.url
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!qrcode) {
|
if (!qrcode) {
|
||||||
res.status(500).json({ok: false});
|
res.status(500).json({ok: false});
|
||||||
@@ -178,8 +155,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
// stats may contain multiple exams of the same type so we need to aggregate them
|
// stats may contain multiple exams of the same type so we need to aggregate them
|
||||||
const results = (
|
const results = (
|
||||||
stats.reduce((accm: ModuleScore[], {module, score}) => {
|
stats.reduce((accm: ModuleScore[], {module, score}) => {
|
||||||
const fixedModuleStr =
|
const fixedModuleStr = module[0].toUpperCase() + module.substring(1);
|
||||||
module[0].toUpperCase() + module.substring(1);
|
|
||||||
if (accm.find((e: ModuleScore) => e.module === fixedModuleStr)) {
|
if (accm.find((e: ModuleScore) => e.module === fixedModuleStr)) {
|
||||||
return accm.map((e: ModuleScore) => {
|
return accm.map((e: ModuleScore) => {
|
||||||
if (e.module === fixedModuleStr) {
|
if (e.module === fixedModuleStr) {
|
||||||
@@ -207,12 +183,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
).map((moduleScore) => {
|
).map((moduleScore) => {
|
||||||
const {score, total} = moduleScore;
|
const {score, total} = moduleScore;
|
||||||
// with all the scores aggreated we can calculate the band score for each module
|
// with all the scores aggreated we can calculate the band score for each module
|
||||||
const bandScore = calculateBandScore(
|
const bandScore = calculateBandScore(score, total, moduleScore.code as Module, user.focus);
|
||||||
score,
|
|
||||||
total,
|
|
||||||
moduleScore.code as Module,
|
|
||||||
user.focus
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...moduleScore,
|
...moduleScore,
|
||||||
@@ -228,7 +199,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
code,
|
code,
|
||||||
name: moduleLabels[code],
|
name: moduleLabels[code],
|
||||||
grade: bandScore,
|
grade: bandScore,
|
||||||
}))
|
})),
|
||||||
)) as SkillsFeedbackResponse[];
|
)) as SkillsFeedbackResponse[];
|
||||||
|
|
||||||
if (!skillsFeedback) {
|
if (!skillsFeedback) {
|
||||||
@@ -238,9 +209,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
// assign the feedback to the results
|
// assign the feedback to the results
|
||||||
const finalResults = results.map((result) => {
|
const finalResults = results.map((result) => {
|
||||||
const feedback = skillsFeedback.find(
|
const feedback = skillsFeedback.find((f: SkillsFeedbackResponse) => f.code === result.code);
|
||||||
(f: SkillsFeedbackResponse) => f.code === result.code
|
|
||||||
);
|
|
||||||
|
|
||||||
if (feedback) {
|
if (feedback) {
|
||||||
return {
|
return {
|
||||||
@@ -254,14 +223,8 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// calculate the overall score out of all the aggregated results
|
// calculate the overall score out of all the aggregated results
|
||||||
const overallScore = results.reduce(
|
const overallScore = results.reduce((accm, {score}) => accm + score, 0);
|
||||||
(accm, { score }) => accm + score,
|
const overallTotal = results.reduce((accm, {total}) => accm + total, 0);
|
||||||
0
|
|
||||||
);
|
|
||||||
const overallTotal = results.reduce(
|
|
||||||
(accm, { total }) => accm + total,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
const overallResult = overallScore / overallTotal;
|
const overallResult = overallScore / overallTotal;
|
||||||
|
|
||||||
const overallPNG = getRadialProgressPNG("laranja", overallScore, overallTotal);
|
const overallPNG = getRadialProgressPNG("laranja", overallScore, overallTotal);
|
||||||
@@ -278,22 +241,14 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const [stat] = stats;
|
const [stat] = stats;
|
||||||
|
|
||||||
// generate the performance summary based on the overall result
|
// generate the performance summary based on the overall result
|
||||||
const performanceSummary = getPerformanceSummary(
|
const performanceSummary = getPerformanceSummary(stat.module, overallResult);
|
||||||
stat.module,
|
|
||||||
overallResult
|
|
||||||
);
|
|
||||||
|
|
||||||
// level exams have a different report structure than the skill exams
|
// level exams have a different report structure than the skill exams
|
||||||
const getCustomData = () => {
|
const getCustomData = () => {
|
||||||
if (stat.module === "level") {
|
if (stat.module === "level") {
|
||||||
return {
|
return {
|
||||||
title: "ENGLISH LEVEL TEST RESULT REPORT ",
|
title: "ENGLISH LEVEL TEST RESULT REPORT ",
|
||||||
details: (
|
details: <LevelExamDetails detail={overallDetail} title="Level as per CEFR Levels" />,
|
||||||
<LevelExamDetails
|
|
||||||
detail={overallDetail}
|
|
||||||
title="Level as per CEFR Levels"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +264,9 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const pdfStream = await ReactPDF.renderToStream(
|
const pdfStream = await ReactPDF.renderToStream(
|
||||||
<TestReport
|
<TestReport
|
||||||
title={title}
|
title={title}
|
||||||
date={moment(stat.date).tz(user.demographicInformation?.timezone || 'UTC').format('ll HH:mm:ss')}
|
date={moment(stat.date)
|
||||||
|
.tz(user.demographicInformation?.timezone || "UTC")
|
||||||
|
.format("ll HH:mm:ss")}
|
||||||
name={user.name}
|
name={user.name}
|
||||||
email={user.email}
|
email={user.email}
|
||||||
id={user.id}
|
id={user.id}
|
||||||
@@ -322,7 +279,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
summaryPNG={overallPNG}
|
summaryPNG={overallPNG}
|
||||||
summaryScore={`${(overallResult * 100).toFixed(0)}%`}
|
summaryScore={`${(overallResult * 100).toFixed(0)}%`}
|
||||||
passportId={demographicInformation?.passport_id || ""}
|
passportId={demographicInformation?.passport_id || ""}
|
||||||
/>
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// generate the file ref for storage
|
// generate the file ref for storage
|
||||||
@@ -361,9 +318,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
|
|
||||||
async function get(req: NextApiRequest, res: NextApiResponse) {
|
async function get(req: NextApiRequest, res: NextApiResponse) {
|
||||||
const {id} = req.query as {id: string};
|
const {id} = req.query as {id: string};
|
||||||
const docsSnap = await getDocs(
|
const docsSnap = await getDocs(query(collection(db, "stats"), where("session", "==", id)));
|
||||||
query(collection(db, "stats"), where("session", "==", id))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (docsSnap.empty) {
|
if (docsSnap.empty) {
|
||||||
res.status(404).end();
|
res.status(404).end();
|
||||||
|
|||||||
Reference in New Issue
Block a user