Skip to content
Open
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
2 changes: 1 addition & 1 deletion app/StudyAI/DocumentLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ async function getPdfJs() {
const pdfjs = await import("pdfjs-dist");

pdfjs.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/build/pdf.worker.min.js",
"pdfjs-dist/build/pdf.worker.min.mjs",
import.meta.url
).toString();

Expand Down
1 change: 0 additions & 1 deletion app/api/StudyAI/embed/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { jsonSuccess, jsonError } from "@/lib/api-response";
import { withErrorHandler, parseJSON } from "@/lib/error-handler";
import { requireAuth } from "@/lib/rbac";
import { connectDb } from "@/lib/mongodb";
import { withErrorHandler, parseJSON } from "@/lib/error-handler";

export const dynamic = "force-dynamic";
export const runtime = "nodejs";
Expand Down
1 change: 0 additions & 1 deletion app/api/StudyAI/retrieve/route.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { jsonSuccess, jsonError } from "@/lib/api-response";
import { withErrorHandler, parseJSON } from "@/lib/error-handler";
import { requireAuth } from "@/lib/rbac";
import { withErrorHandler, parseJSON } from "@/lib/error-handler";
import { connectDb } from "@/lib/mongodb";

import { MemoryVectorStore } from "@langchain/classic/vectorstores/memory";
Expand Down
4 changes: 2 additions & 2 deletions app/api/activities/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { initFirebaseAdmin } from "@/lib/firebase-admin";
import { getFirestore, FieldValue } from "firebase-admin/firestore";
import { checkRateLimit } from "@/lib/rateLimit";

const ALLOWED_TYPES = ["course", "quiz", "assignment"];
const ALLOWED_TYPES = ["course", "quiz", "assignment", "login", "video"];

const activitySchema = z.object({
title: z
Expand Down Expand Up @@ -38,7 +38,7 @@ export const GET = withErrorHandler(async (request) => {
.collection("activities")
.where("userId", "==", decodedToken.uid)
.orderBy("timestamp", "desc")
.limit(100)
.limit(1000)
.get();

const activities = snapshot.docs.map((doc) => ({
Expand Down
6 changes: 3 additions & 3 deletions app/api/admin/data-retention/route.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { NextResponse } from "next/server";
import { getAuth } from "firebase-admin/auth";
import { getFirestore } from "firebase-admin/firestore";
import { initAdmin } from "@/lib/firebaseAdmin";
import { initFirebaseAdmin } from "@/lib/firebase-admin";

export async function GET(req) {
try {
initAdmin();
initFirebaseAdmin();
const authHeader = req.headers.get("Authorization");
if (!authHeader?.startsWith("Bearer ")) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
Expand Down Expand Up @@ -40,7 +40,7 @@ export async function GET(req) {

export async function POST(req) {
try {
initAdmin();
initFirebaseAdmin();
const authHeader = req.headers.get("Authorization");
if (!authHeader?.startsWith("Bearer ")) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
Expand Down
2 changes: 2 additions & 0 deletions app/api/admin/reconcile/__tests__/route.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ vi.mock("@/lib/mongodb", () => {
updateOne: mockUpdateOne,
findOne: mockFindOne,
deleteMany: vi.fn().mockResolvedValue({ deletedCount: 0 }),
insertOne: vi.fn().mockResolvedValue({}),
createIndex: vi.fn().mockResolvedValue({}),
};
});

Expand Down
27 changes: 16 additions & 11 deletions app/api/attendance/record/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,26 @@ export const POST = withErrorHandler(
userId,
studentName,
email,
confidenceScore: normalizedConfidence,
confidenceScore: parsedConfidence,
normalizedDate,
},
token
);

emitWebhookEvent("attendance.recorded", {
studentId: userId,
studentName,
email,
confidence: normalizedConfidence,
date: normalizedDate,
recordedBy: token.uid,
});
if (sagaResult.context?._alreadyRecorded) {
return jsonSuccess({ alreadyRecorded: true }, 200);
}

emitWebhookEvent("attendance.recorded", {
studentId: userId,
studentName,
email,
confidence: normalizedConfidence,
date: normalizedDate,
recordedBy: token.uid,
});

return jsonSuccess({ alreadyRecorded: false }, 201);
})
return jsonSuccess({ alreadyRecorded: false }, 201);
}
)
);
Binary file modified app/api/attendance/route.js
Binary file not shown.
13 changes: 8 additions & 5 deletions app/courses/[id]/route.js → app/api/courses/srs/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
import { calculateSRS } from "@/lib/srs/engine";

// In a real app, you would import your DB client here
// import { db } from "@/lib/db";
// import { db } from "@/lib/db";

export async function POST(req) {
try {
Expand All @@ -17,17 +17,20 @@ export async function POST(req) {
);

// Mock DB update
// await db.flashcard.update({
// await db.flashcard.update({\n
// where: { id: cardId },
// data: { ...updatedStats }
// });

return NextResponse.json({
success: true,
message: "Card scheduled for: " + updatedStats.nextReviewDate,
...updatedStats
...updatedStats,
});
} catch (error) {
return NextResponse.json({ error: "Failed to process review" }, { status: 500 });
return NextResponse.json(
{ error: "Failed to process review" },
{ status: 500 }
);
}
}
}
4 changes: 2 additions & 2 deletions app/api/cron/archive-data/route.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextResponse } from "next/server";
import { getFirestore } from "firebase-admin/firestore";
import { initAdmin } from "@/lib/firebaseAdmin";
import { initFirebaseAdmin } from "@/lib/firebase-admin";

export async function GET(req) {
try {
Expand All @@ -13,7 +13,7 @@ export async function GET(req) {
// Since we don't have CRON_SECRET configured for sure in this environment,
// we'll leave it open for demonstration/manual triggering or rely on Vercel's network layer.

initAdmin();
initFirebaseAdmin();
const db = getFirestore();

// Fetch config
Expand Down
2 changes: 1 addition & 1 deletion app/api/labels/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const GET = withErrorHandler(async (request) => {
const profile = await getUserProfile(decodedToken.uid);

// Search query — escape metacharacters to prevent ReDoS
const { searchParams } = new URL(request.url);
const { searchParams } = new URL(request.url, "http://localhost");
const rawSearch = searchParams.get("search") || "";
const search = escapeRegex(rawSearch);

Expand Down
6 changes: 1 addition & 5 deletions app/api/notifications/route.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,8 @@ vi.mock("../../../lib/mongodb", () => {
const mockDb = {
collection: vi.fn(() => mockCollection),
};
const mockClient = {
db: vi.fn(() => mockDb),
};
return {
__esModule: true,
default: Promise.resolve(mockClient),
connectDb: vi.fn().mockResolvedValue(mockDb),
};
});

Expand Down
4 changes: 2 additions & 2 deletions app/api/parent/dashboard/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export const GET = withErrorHandler(async (request) => {
.collection("attendance_records")
.where("userId", "==", studentId)
.get();

const studentRecords = [];
recordsQuery.docs.forEach((doc) => {
const data = doc.data();
Expand All @@ -118,7 +118,7 @@ export const GET = withErrorHandler(async (request) => {
});

const prediction = predictStudentAttendance(studentRecords);
if (prediction.riskLevel === "high") {
if (prediction.riskLevel === "high" && studentRecords.length >= 5) {
const oneDayAgo = new Date(
Date.now() - 24 * 60 * 60 * 1000
).toISOString();
Expand Down
37 changes: 23 additions & 14 deletions app/api/productivity/session/route.test.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import { describe, test, expect, vi, beforeEach } from "vitest";
import { POST, GET } from "./route";
import { connectDb } from "@/lib/mongodb";
import { requireRole } from "@/lib/rbac";
import { requireAuth } from "@/lib/rbac";
import { awardXp } from "@/lib/gamification-service";

jest.mock("@/lib/mongodb", () => ({
connectDb: jest.fn(),
vi.mock("@/lib/mongodb", () => ({
connectDb: vi.fn(),
}));

jest.mock("@/lib/rbac", () => ({
requireRole: jest.fn(),
vi.mock("@/lib/rbac", () => ({
requireAuth: vi.fn(),
}));

jest.mock("@/lib/error-handler", () => ({
vi.mock("@/lib/rateLimit", () => ({
checkRateLimit: vi.fn().mockResolvedValue({ allowed: true }),
}));

vi.mock("@/lib/error-handler", () => ({
withErrorHandler: (handler) => handler,
parseJSON: vi.fn().mockImplementation(async (req) => req.json()),
}));

jest.mock("@/lib/gamification-service", () => ({
awardXp: jest.fn(),
vi.mock("@/lib/gamification-service", () => ({
awardXp: vi.fn(),
}));

jest.mock("next/server", () => ({
vi.mock("next/server", () => ({
NextResponse: {
json: (body, init = {}) => ({
status: init.status ?? 200,
Expand All @@ -33,25 +39,26 @@ describe("POST /api/productivity/session", () => {
let mockCollection;

beforeEach(() => {
jest.clearAllMocks();
vi.clearAllMocks();

mockCollection = {
insertOne: jest.fn().mockResolvedValue({ insertedId: "session-123" }),
find: jest.fn(),
insertOne: vi.fn().mockResolvedValue({ insertedId: "session-123" }),
find: vi.fn(),
};

mockDb = {
collection: jest.fn(() => mockCollection),
collection: vi.fn(() => mockCollection),
};

connectDb.mockResolvedValue(mockDb);
requireRole.mockResolvedValue({ payload: { uid: "user-123" } });
requireAuth.mockResolvedValue({ uid: "user-123" });
});

test("successfully records a focus session and awards XP", async () => {
awardXp.mockResolvedValue({ xpAwarded: 15 });

const request = {
headers: { get: vi.fn().mockReturnValue("127.0.0.1") },
json: async () => ({
duration: 25,
completedAt: new Date().toISOString(),
Expand All @@ -71,6 +78,7 @@ describe("POST /api/productivity/session", () => {

test("records a break session and does not award XP", async () => {
const request = {
headers: { get: vi.fn().mockReturnValue("127.0.0.1") },
json: async () => ({
duration: 5,
completedAt: new Date().toISOString(),
Expand All @@ -90,6 +98,7 @@ describe("POST /api/productivity/session", () => {

test("rejects invalid request payload", async () => {
const request = {
headers: { get: vi.fn().mockReturnValue("127.0.0.1") },
json: async () => ({
duration: "invalid-duration",
completedAt: "invalid-date",
Expand Down
25 changes: 20 additions & 5 deletions app/api/register/__tests__/route.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,22 @@ describe("POST /api/register", () => {
});

test("includes Firestore profile write in the registration saga", async () => {
requireAuth.mockResolvedValue({ uid: "user-123", email: "test@example.com" });

executeSaga.mockResolvedValue({ success: true, context: { _insertedUser: { _id: "mongo-id", name: "Test User", rollNo: "ROLL-123", email: "test@example.com" } } });
requireAuth.mockResolvedValue({
uid: "user-123",
email: "test@example.com",
});

executeSaga.mockResolvedValue({
success: true,
context: {
_insertedUser: {
_id: "mongo-id",
name: "Test User",
rollNo: "ROLL-123",
email: "test@example.com",
},
},
});

const request = {
formData: async () => createFormData(),
Expand All @@ -126,7 +139,9 @@ describe("POST /api/register", () => {
expect(response.status).toBe(201);
expect(executeSaga).toHaveBeenCalled();
const sagaArgs = executeSaga.mock.calls[0][0];
expect(sagaArgs.steps.some((step) => step.name === "write_firestore_profile")).toBe(true);
expect(result.user.email).toBe("test@example.com");
expect(
sagaArgs.steps.some((step) => step.name === "write_firestore_profile")
).toBe(true);
expect(result.data.user.email).toBe("test@example.com");
});
});
24 changes: 16 additions & 8 deletions app/api/register/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
} from "@/lib/transactionCoordinator";
import { validateFaceDescriptor } from "@/lib/images/imagesService";
import { logAuditEvent } from "@/lib/auditLogger";
import { initializeFirebase } from "@/lib/firebase-admin";
import admin from "firebase-admin";

export const dynamic = "force-dynamic";

Expand Down Expand Up @@ -300,10 +302,13 @@ export const POST = withErrorHandler(async (req) => {
compensate: async (ctx) => {
if (ctx._insertedUserId) {
await users.deleteOne({ _id: ctx._insertedUserId }).catch((err) => {
logger.error("Registration rollback: failed to delete user document", {
userId: String(ctx._insertedUserId),
error: err.message,
});
logger.error(
"Registration rollback: failed to delete user document",
{
userId: String(ctx._insertedUserId),
error: err.message,
}
);
});
}
},
Expand All @@ -325,10 +330,13 @@ export const POST = withErrorHandler(async (req) => {
compensate: async (ctx) => {
if (ctx._blobUrl) {
await del(ctx._blobUrl).catch((err) => {
logger.error("Registration rollback: failed to delete orphaned blob", {
blobUrl: ctx._blobUrl,
error: err.message,
});
logger.error(
"Registration rollback: failed to delete orphaned blob",
{
blobUrl: ctx._blobUrl,
error: err.message,
}
);
});
}
},
Expand Down
Binary file modified app/attendance/page.jsx
Binary file not shown.
2 changes: 0 additions & 2 deletions app/calendar/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,6 @@ export default function CalendarPage() {
);
}

return ( ← this is the existing line 168, stays as-is

return (
<>
<div className="fixed inset-0 -z-10 bg-background">
Expand Down
Loading
Loading