Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions app/api/courses/[courseId]/past-questions/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import { authOptions } from "@/lib/auth";
import prisma from "@/lib/prisma";
import { validateUser } from "@/services/userCourse";

export async function GET(request: NextRequest, context: { params: Promise<{ courseId: string }> }) {
export async function GET(
request: NextRequest,
context: { params: Promise<{ courseId: string }> },
) {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
Expand All @@ -24,7 +27,7 @@ export async function GET(request: NextRequest, context: { params: Promise<{ cou
}

if (!(await validateUser(session.user.id, courseId, Role.LECTURER))) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
return NextResponse.json({ error: "Questions not Found" }, { status: 404 });
}

const pastQuestions = await prisma.question.findMany({
Expand Down
25 changes: 8 additions & 17 deletions app/api/export/[courseId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import { csvAdvancedFieldNames, csvBasicFieldNames } from "@/lib/constants";
import prisma from "@/lib/prisma";
import { ExportCSVType } from "@/types/ExportCSVType";
import { ExportCSVType } from "@/models/ExportCSVType";
import { validateUser } from "@/services/userCourse";

export async function GET(req: NextRequest, context: { params: Promise<{ courseId: string }> }) {
const session = await getServerSession(authOptions);
Expand All @@ -15,22 +16,12 @@ export async function GET(req: NextRequest, context: { params: Promise<{ courseI

const { courseId } = await context.params;

if (!courseId || Number.isNaN(+courseId)) {
return NextResponse.json({ error: "Course Id is required" }, { status: 403 });
}

const courseLecturers = await prisma.userCourse.findMany({
where: {
courseId: +courseId,
role: Role.LECTURER,
},
select: {
userId: true,
},
});

if (!courseLecturers.find((lecturer) => lecturer.userId === session.user.id)) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
if (
!courseId ||
Number.isNaN(+courseId) ||
!(await validateUser(session.user.id, +courseId, Role.LECTURER))
) {
return NextResponse.json({ error: "Not Found" }, { status: 404 });
}

const url = new URL(req.url);
Expand Down
3 changes: 2 additions & 1 deletion app/api/fetchCourseSessionQuestion/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/lib/auth";
import prisma from "@/lib/prisma";
import { validateUser } from "@/services/userCourse";

export async function GET(request: NextRequest) {
try {
Expand Down Expand Up @@ -48,7 +49,7 @@ export async function GET(request: NextRequest) {
},
});

if (!courseSession) {
if (!courseSession || !(await validateUser(session.user.id, courseSession.courseId))) {
return NextResponse.json({ error: "Course session not found" }, { status: 404 });
}

Expand Down
18 changes: 18 additions & 0 deletions app/api/fetchQuestionById/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/lib/auth";
import prisma from "@/lib/prisma";
import { validateUser } from "@/services/userCourse";

// Make sure we're using the correct export format for Next.js App Router
export async function GET(request: NextRequest) {
Expand All @@ -24,6 +25,23 @@ export async function GET(request: NextRequest) {
);
}

const course = await prisma.question.findFirst({
where: {
id: +questionId,
},
select: {
session: {
select: {
courseId: true,
},
},
},
});

if (!course || !(await validateUser(session.user.id, course.session.courseId))) {
return NextResponse.json({ error: "Question not found" }, { status: 404 });
}

// Fetch the question with its options
const question = await prisma.question.findUnique({
where: {
Expand Down
23 changes: 23 additions & 0 deletions app/api/getResponseCounts/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Role } from "@prisma/client";
import { NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/lib/auth";
import prisma from "@/lib/prisma";
import { validateUser } from "@/services/userCourse";

export async function GET(request: NextRequest) {
try {
Expand All @@ -20,6 +22,27 @@ export async function GET(request: NextRequest) {
);
}

const courseId = await prisma.course.findFirst({
where: {
sessions: {
some: {
questions: {
some: {
id: +questionId,
},
},
},
},
},
select: {
id: true,
},
});

if (!courseId || !(await validateUser(session.user.id, courseId.id, Role.LECTURER))) {
return NextResponse.json({ error: "Responses not found" }, { status: 404 });
}

const groups = await prisma.response.groupBy({
by: ["optionId"],
where: { questionId: Number(questionId) },
Expand Down
33 changes: 29 additions & 4 deletions app/api/session/[sessionId]/activeQuestion/route.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,42 @@
import { Role } from "@prisma/client";
import { NextResponse } from "next/server";
import { ActiveQuestionPayload } from "../../../../../models/CourseSession";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import prisma from "@/lib/prisma";
import { ActiveQuestionPayload } from "@/models/CourseSession";
import { validateUser } from "@/services/userCourse";

export async function PATCH(
request: Request,
{ params }: { params: Promise<{ sessionId: string }> },
) {
try {
const resolvedParams = await params;
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

const { sessionId } = await params;

const courseSession = await prisma.courseSession.findFirst({
where: {
id: +sessionId,
},
select: {
courseId: true,
},
});

if (
!courseSession?.courseId ||
!(await validateUser(session.user.id, courseSession.courseId, Role.LECTURER))
) {
return NextResponse.json({ error: "Responses not found" }, { status: 404 });
}

const { activeQuestionId } = (await request.json()) as ActiveQuestionPayload;
const sessionId = parseInt(resolvedParams.sessionId, 10);
const updatedSession = await prisma.courseSession.update({
where: { id: sessionId },
where: { id: +sessionId },
data: { activeQuestionId },
});
return NextResponse.json(updatedSession);
Expand Down
31 changes: 28 additions & 3 deletions app/api/session/[sessionId]/wildcard/route.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,41 @@
import { Role } from "@prisma/client";
import { NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import prisma from "@/lib/prisma";
import { WildcardPayload } from "@/models/CourseSession";
import { createWildcardQuestion } from "@/services/session";
import { validateUser } from "@/services/userCourse";

export async function POST(
request: Request,
{ params }: { params: Promise<{ sessionId: string }> },
) {
try {
const resolvedParams = await params;
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

const { sessionId } = await params;

const courseSession = await prisma.courseSession.findFirst({
where: {
id: +sessionId,
},
select: {
courseId: true,
},
});

if (
!courseSession?.courseId ||
!(await validateUser(session.user.id, courseSession.courseId, Role.LECTURER))
) {
return NextResponse.json({ error: "Responses not found" }, { status: 404 });
}
const { position, questionType } = (await request.json()) as WildcardPayload;
const sessionId = parseInt(resolvedParams.sessionId, 10);
const wildcardQuestion = await createWildcardQuestion(sessionId, position, questionType);
const wildcardQuestion = await createWildcardQuestion(+sessionId, position, questionType);
return NextResponse.json(wildcardQuestion);
} catch (error) {
console.error(error);
Expand Down
15 changes: 4 additions & 11 deletions app/api/updateCourse/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Role } from "@prisma/client";
import { NextResponse } from "next/server";
import { getServerSession } from "next-auth/next";
import { z } from "zod";
import { authOptions } from "@/lib/auth";
import prisma from "@/lib/prisma";
import { validateUser } from "@/services/userCourse";

const updateSchema = z.object({
title: z.string().min(2),
Expand Down Expand Up @@ -34,17 +36,8 @@ export async function PUT(request: Request) {
const courseId = getCourseId(request);

// Verify user has permission
const userCourse = await prisma.userCourse.findUnique({
where: {
userId_courseId: {
userId: session.user.id,
courseId,
},
},
});

if (!userCourse || userCourse.role !== "LECTURER") {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
if (!courseId || !(await validateUser(session.user.id, courseId, Role.LECTURER))) {
return NextResponse.json({ error: "Course not found" }, { status: 404 });
}

// Validate request body
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AddInstructorForm } from "@/components/AddInstuctorForm";

const CourseAdminPage = () => {
return <AddInstructorForm />;
};

export default CourseAdminPage;
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import PerformanceData from "@/components/ui/PerformanceData";
import StudentTable from "@/components/ui/StudentTable";
import { toast } from "@/hooks/use-toast";
import { analyticsPages } from "@/lib/constants";
import { ExportCSVType } from "@/types/ExportCSVType";
import { ExportCSVType } from "@/models/ExportCSVType";

export default function Page() {
const params = useParams();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,7 @@ export default function CourseInfoLayout({
<button
key={tab}
onClick={() => {
if (tab === "Analytics") {
router.push(`/dashboard/course/${courseId}/analytics`);
} else {
router.push(`/dashboard/course/${courseId}/questionnaire`);
}
router.push(encodeURIComponent(tab.toLowerCase()));
}}
className={`pb-2 text-base font-medium ${
path.includes(tab.toLowerCase()) ? "text-[#1441DB]" : "text-slate-600"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { ArrowLeft } from "lucide-react";
import Link from "next/link";
import { notFound } from "next/navigation";
import React from "react";
import { CircularProgress } from "@/components/ui/circular-progress";
import DonutChart from "@/components/ui/DonutChart";
import { StringTooltipContainer, Tooltip, TooltipTrigger } from "@/components/ui/tooltip";
import { studentAnalyticsScoreChartConfig } from "@/lib/charts";
import prisma from "@/lib/prisma";

interface Props {
Expand Down Expand Up @@ -250,13 +251,26 @@ export default async function QuestionResponsesPage({ params }: Props) {

{/* Circular Progress */}
<div className="flex justify-center order-2 md:order-none w-full">
{/* Mobile View */}
<div className="lg:hidden">
<CircularProgress value={correctPercentage} size={180} thickness={18} />
</div>
{/* Desktop View */}
<div className="hidden lg:flex justify-center items-center">
<CircularProgress value={correctPercentage} size={240} thickness={24} />
<div className="w-[320px] h-[320px]">
<DonutChart
chartData={[
{
name: studentAnalyticsScoreChartConfig.Correct.label,
value: correctPercentage ?? 0,
fill: studentAnalyticsScoreChartConfig.Correct.color,
},
{
name: studentAnalyticsScoreChartConfig.Incorrect.label,
value: 100 - (correctPercentage ?? 0),
fill: studentAnalyticsScoreChartConfig.Incorrect.color,
},
]}
chartConfig={studentAnalyticsScoreChartConfig}
dataKey="value"
nameKey="name"
description="Class Average"
descriptionStatistic={correctPercentage ?? 0}
/>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import Link from "next/link";
import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { AddEditQuestionForm } from "@/components/AddEditQuestionForm";
import { AddInstructorForm } from "@/components/AddInstuctorForm";
import BeginPollDialog from "@/components/BeginPollDialog";
import PastQuestions from "@/components/ui/PastQuestions";
import SlidingCalendar from "@/components/ui/SlidingCalendar";
Expand Down Expand Up @@ -87,7 +86,6 @@ export default function Page() {
refreshTrigger={refreshCalendar}
/>
<PastQuestions courseId={courseId} />
<AddInstructorForm />
</div>
);
}
Loading