Update 31 files

This commit is contained in:
[dyad] committed 2026-08-02 15:47:01 +02:00
1 parent 93ad9aa1e7
commit 71b9ca0eb4
31 files changed
+480 -73

No files matched your search

+17
View File
@@ -1,6 +1,8 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessClass } from '@/lib/access-policy';
import { isAdministrator } from '@/lib/role-access';
import { addDays, addWeeks, addMonths, addYears, getDay } from 'date-fns';
interface RecurrencePattern {
@@ -124,6 +126,15 @@ export async function PUT(request: Request, { params }: { params: Promise<{ id:
if (!existing) {
return NextResponse.json({ error: 'Event not found' }, { status: 404 });
}
if (!session.user || (!isAdministrator(session.user.role) && existing.teacherId !== session.user.id)) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (session.user.role !== 'SUPER_ADMIN' && existing.schoolId !== session.user.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (classGroupId && !(await canAccessClass(session.user, classGroupId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// If editing a single instance of a recurring event
if (editMode === 'instance' && existing.parentEventId) {
@@ -237,6 +248,12 @@ export async function DELETE(request: Request, { params }: { params: Promise<{ i
if (!existing) {
return NextResponse.json({ error: 'Event not found' }, { status: 404 });
}
if (!session.user || (!isAdministrator(session.user.role) && existing.teacherId !== session.user.id)) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (session.user.role !== 'SUPER_ADMIN' && existing.schoolId !== session.user.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// If deleting a single instance, just delete that one
if (deleteMode === 'instance') {
+9 -2
View File
@@ -65,16 +65,23 @@ export async function POST(request: Request) {
const body = await request.json();
const { requestType, description, addToCalendar, isPrivate } = body;
const schoolId = body.schoolId || session.user?.schoolId;
const role = session.user?.role;
if (!schoolId) {
return NextResponse.json({ error: 'schoolId is required' }, { status: 400 });
}
if (role !== 'SUPER_ADMIN' && schoolId !== session.user?.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Only students can request counseling
const role = session.user?.role;
// Only students can request counseling.
if (role !== 'STUDENT' && role !== 'SCHOOL_ADMIN' && role !== 'SUPER_ADMIN') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (role === 'STUDENT') {
const student = await db.student.findFirst({ where: { userId: session.userId, schoolId, deletedAt: null }, select: { id: true } });
if (!student) return NextResponse.json({ error: 'Student profile not found' }, { status: 403 });
}
// Find a counselor teacher for this school
const counselor = await db.user.findFirst({
+19
View File
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
import { z } from 'zod';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessClass } from '@/lib/access-policy';
const updateDrawingSchema = z.object({
title: z.string().min(1).optional(),
@@ -31,11 +32,17 @@ export async function GET(
if (!drawing) {
return NextResponse.json({ error: 'Drawing not found' }, { status: 404 });
}
if (session.user?.role !== 'SUPER_ADMIN' && drawing.schoolId !== session.user?.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Only owner or public drawings
if (drawing.ownerId !== session.userId && !drawing.isPublic) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (drawing.isPublic && drawing.classGroupId && (!session.user || !(await canAccessClass(session.user, drawing.classGroupId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
return NextResponse.json(drawing);
} catch (error) {
@@ -67,6 +74,9 @@ export async function PUT(
if (existing.ownerId !== session.userId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (existing.schoolId !== session.user?.schoolId && session.user?.role !== 'SUPER_ADMIN') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const body = await request.json();
const parsed = updateDrawingSchema.safeParse(body);
@@ -76,6 +86,12 @@ export async function PUT(
{ status: 400 }
);
}
if (parsed.data.classGroupId && (!session.user || !(await canAccessClass(session.user, parsed.data.classGroupId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (session.user?.role === 'STUDENT' && parsed.data.isPublic) {
return NextResponse.json({ error: 'Students cannot publish drawings to a class' }, { status: 403 });
}
const drawing = await db.drawing.update({
where: { id },
@@ -112,6 +128,9 @@ export async function DELETE(
if (existing.ownerId !== session.userId && session.user?.role !== 'SUPER_ADMIN' && session.user?.role !== 'SCHOOL_ADMIN') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (existing.schoolId !== session.user?.schoolId && session.user?.role !== 'SUPER_ADMIN') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
await db.drawing.delete({ where: { id } });
+11
View File
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
import { z } from 'zod';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessClass } from '@/lib/access-policy';
const createDrawingSchema = z.object({
title: z.string().min(1).default('Untitled Drawing'),
@@ -71,6 +72,16 @@ export async function POST(request: Request) {
);
}
if (parsed.data.schoolId && session.user?.role !== 'SUPER_ADMIN' && parsed.data.schoolId !== session.user?.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (parsed.data.classGroupId && (!session.user || !(await canAccessClass(session.user, parsed.data.classGroupId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (session.user?.role === 'STUDENT' && parsed.data.isPublic) {
return NextResponse.json({ error: 'Students cannot publish drawings to a class' }, { status: 403 });
}
const drawing = await db.drawing.create({
data: {
schoolId: parsed.data.schoolId ?? session.user?.schoolId ?? 'default',
+11
View File
@@ -1,6 +1,8 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessStudent } from '@/lib/access-policy';
import { isAdministrator } from '@/lib/role-access';
export async function GET(request: Request) {
try {
@@ -20,12 +22,18 @@ export async function GET(request: Request) {
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!isAdministrator(session.user?.role)) {
return NextResponse.json({ error: 'Emergency contacts are restricted to administrators' }, { status: 403 });
}
const where: Record<string, unknown> = {
schoolId,
deletedAt: null,
};
if (studentId && (!session.user || !(await canAccessStudent(session.user, studentId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (studentId) where.studentId = studentId;
const contacts = await db.emergencyContact.findMany({
@@ -78,6 +86,9 @@ export async function POST(request: Request) {
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!isAdministrator(session.user?.role) || !session.user || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const contact = await db.emergencyContact.create({
data: {
+11
View File
@@ -1,6 +1,8 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessStudent } from '@/lib/access-policy';
import { isAdministrator } from '@/lib/role-access';
export async function GET(
_request: Request,
@@ -29,6 +31,9 @@ export async function GET(
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== record.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!isAdministrator(session.user?.role) || !session.user || !(await canAccessStudent(session.user, record.studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
return NextResponse.json(record);
} catch (error) {
@@ -59,6 +64,9 @@ export async function PUT(
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== existing.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!isAdministrator(session.user?.role) || !session.user || !(await canAccessStudent(session.user, existing.studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const body = await request.json();
const {
@@ -126,6 +134,9 @@ export async function DELETE(
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== existing.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!isAdministrator(session.user?.role) || !session.user || !(await canAccessStudent(session.user, existing.studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Soft delete
await db.healthRecord.update({
+11
View File
@@ -1,6 +1,8 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessStudent } from '@/lib/access-policy';
import { isAdministrator } from '@/lib/role-access';
export async function GET(request: Request) {
try {
@@ -20,12 +22,18 @@ export async function GET(request: Request) {
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!isAdministrator(session.user?.role)) {
return NextResponse.json({ error: 'Health records are restricted to administrators' }, { status: 403 });
}
const where: Record<string, unknown> = {
schoolId,
deletedAt: null,
};
if (studentId && (!session.user || !(await canAccessStudent(session.user, studentId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (studentId) where.studentId = studentId;
const records = await db.healthRecord.findMany({
@@ -80,6 +88,9 @@ export async function POST(request: Request) {
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!isAdministrator(session.user?.role) || !session.user || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const record = await db.healthRecord.create({
data: {
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
import { z } from 'zod';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessClass, canAccessStudent } from '@/lib/access-policy';
const gradeSchema = z.object({
score: z.number().min(0).optional().nullable(),
@@ -26,7 +27,7 @@ export async function PUT(
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const { submissionId } = await params;
const { id: homeworkId, submissionId } = await params;
const body = await request.json();
const parsed = gradeSchema.safeParse(body);
@@ -43,6 +44,10 @@ export async function PUT(
if (!existing) {
return NextResponse.json({ error: 'Submission not found' }, { status: 404 });
}
const homework = await db.homework.findUnique({ where: { id: homeworkId }, select: { classGroupId: true } });
if (!homework || !session.user || !(await canAccessClass(session.user, homework.classGroupId)) || !(await canAccessStudent(session.user, existing.studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const updateData: Record<string, unknown> = {};
if (parsed.data.score !== undefined) {
@@ -82,13 +87,17 @@ export async function DELETE(
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const { submissionId } = await params;
const { id: homeworkId, submissionId } = await params;
const existing = await db.homeworkSubmission.findUnique({
where: { id: submissionId, deletedAt: null },
});
if (!existing) {
return NextResponse.json({ error: 'Submission not found' }, { status: 404 });
}
const homework = await db.homework.findUnique({ where: { id: homeworkId }, select: { classGroupId: true } });
if (!homework || !session.user || !(await canAccessClass(session.user, homework.classGroupId)) || !(await canAccessStudent(session.user, existing.studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
await db.homeworkSubmission.update({
where: { id: submissionId },
+15
View File
@@ -1,6 +1,7 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessStudent } from '@/lib/access-policy';
export async function GET(request: Request) {
try {
@@ -28,7 +29,18 @@ export async function GET(request: Request) {
deletedAt: null,
};
if (studentId && (!session.user || !(await canAccessStudent(session.user, studentId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (studentId) where.studentId = studentId;
if (!studentId && session.user?.role === 'STUDENT') {
const student = await db.student.findFirst({ where: { userId: session.user.id }, select: { id: true } });
if (!student) return NextResponse.json([]);
where.studentId = student.id;
} else if (!studentId && session.user?.role === 'PARENT') {
const links = await db.parentStudentLink.findMany({ where: { parentId: session.user.id }, select: { studentId: true } });
where.studentId = { in: links.map((link) => link.studentId) };
}
if (competencyId) where.competencyId = competencyId;
if (status) where.status = status;
@@ -90,6 +102,9 @@ export async function POST(request: Request) {
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!session.user || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const goal = await db.learningGoal.create({
data: {
+16
View File
@@ -1,6 +1,7 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessStudent } from '@/lib/access-policy';
// ── GET: List checkouts ──────────────────────────────────────────────
export async function GET(request: Request) {
@@ -22,6 +23,12 @@ export async function GET(request: Request) {
if (!schoolId) {
return NextResponse.json({ error: 'schoolId is required' }, { status: 400 });
}
if (session.user?.role !== 'SUPER_ADMIN' && schoolId !== session.user?.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (studentId && (!session.user || !(await canAccessStudent(session.user, studentId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const where: Record<string, unknown> = { schoolId };
@@ -129,12 +136,21 @@ export async function POST(request: Request) {
{ status: 400 }
);
}
if (session.user?.role !== 'SUPER_ADMIN' && schoolId !== session.user?.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!session.user || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Check book availability
const book = await db.libraryBook.findUnique({ where: { id: bookId } });
if (!book) {
return NextResponse.json({ error: 'Book not found' }, { status: 404 });
}
if (book.schoolId !== schoolId) {
return NextResponse.json({ error: 'Book not found in this school' }, { status: 404 });
}
if (book.availableCopies <= 0) {
return NextResponse.json({ error: 'No copies available' }, { status: 400 });
+7
View File
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
import { z } from 'zod';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessClass } from '@/lib/access-policy';
// ── GET: List notebooks for the current user ──
export async function GET(request: Request) {
@@ -191,6 +192,12 @@ export async function POST(request: Request) {
{ status: 400 }
);
}
if (classGroupId && !(await canAccessClass(session.user, classGroupId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (session.user.role === 'STUDENT' && isPublic) {
return NextResponse.json({ error: 'Students cannot publish notebooks to a class' }, { status: 403 });
}
const ownerType = session.user.role === 'STUDENT' ? 'STUDENT' : 'TEACHER';
@@ -1,6 +1,7 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessClass } from '@/lib/access-policy';
export async function POST(
request: Request,
@@ -28,6 +29,9 @@ export async function POST(
if (!assessmentSession) {
return NextResponse.json({ error: 'Session not found' }, { status: 404 });
}
if (!session.user || !assessmentSession.classGroupId || !(await canAccessClass(session.user, assessmentSession.classGroupId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (assessmentSession.status === 'closed') {
return NextResponse.json({ error: 'Session is closed' }, { status: 400 });
@@ -55,6 +59,8 @@ export async function POST(
if (!student || student.id !== assessment.assessorId) {
return NextResponse.json({ error: 'You can only submit your own assessments' }, { status: 403 });
}
} else {
return NextResponse.json({ error: 'Only students can submit peer assessments' }, { status: 403 });
}
// Update the assessment
@@ -1,6 +1,8 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessClass, getTeacherClassIds } from '@/lib/access-policy';
import { isAdministrator } from '@/lib/role-access';
export async function GET(request: Request) {
try {
@@ -23,6 +25,9 @@ export async function GET(request: Request) {
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (classGroupId && (!session.user || !(await canAccessClass(session.user, classGroupId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const where: Record<string, unknown> = {
schoolId,
@@ -42,6 +47,8 @@ export async function GET(request: Request) {
if (student) {
where.assignedPairs = { contains: student.id };
}
} else if (session.user?.role === 'TEACHER') {
where.classGroupId = { in: await getTeacherClassIds(session.user.id) };
}
const sessions = await db.peerAssessmentSession.findMany({
@@ -126,6 +133,14 @@ export async function POST(request: Request) {
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (session.user?.role === 'VICE_PRINCIPAL') {
return NextResponse.json({ error: 'Vice principals cannot create peer assessments' }, { status: 403 });
}
if (session.user?.role === 'TEACHER') {
if ((teacherId && teacherId !== session.user.id) || (classGroupId && !(await canAccessClass(session.user, classGroupId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
}
const assessmentSession = await db.peerAssessmentSession.create({
data: {
+20
View File
@@ -1,6 +1,8 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessStudent } from '@/lib/access-policy';
import { isAdministrator } from '@/lib/role-access';
export async function GET(request: Request) {
try {
@@ -29,7 +31,22 @@ export async function GET(request: Request) {
deletedAt: null,
};
if (studentId && (!session.user || !(await canAccessStudent(session.user, studentId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (studentId) where.studentId = studentId;
if (!studentId && !isAdministrator(session.user?.role)) {
if (session.user?.role === 'STUDENT') {
const student = await db.student.findFirst({ where: { userId: session.user.id }, select: { id: true } });
if (!student) return NextResponse.json([]);
where.studentId = student.id;
} else if (session.user?.role === 'PARENT') {
const links = await db.parentStudentLink.findMany({ where: { parentId: session.user.id }, select: { studentId: true } });
where.studentId = { in: links.map((link) => link.studentId) };
} else {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
}
if (entryType) where.entryType = entryType;
if (competencyId) where.competencyId = competencyId;
if (isPublic !== null && isPublic !== undefined) where.isPublic = isPublic === 'true';
@@ -88,6 +105,9 @@ export async function POST(request: Request) {
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!session.user || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const entry = await db.portfolioEntry.create({
data: {
+11 -5
View File
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
import { z } from 'zod';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessClass, canAccessStudent, getTeacherClassIds } from '@/lib/access-policy';
const createReportCardSchema = z.object({
studentId: z.string().min(1),
@@ -40,6 +41,12 @@ export async function GET(request: Request) {
const templateId = searchParams.get('templateId');
const where: Record<string, unknown> = {};
if (studentId && (!session.user || !(await canAccessStudent(session.user, studentId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (classGroupId && (!session.user || !(await canAccessClass(session.user, classGroupId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (studentId) where.studentId = studentId;
if (classGroupId) where.classGroupId = classGroupId;
if (schoolYearId) where.schoolYearId = schoolYearId;
@@ -70,11 +77,7 @@ export async function GET(request: Request) {
} else if (session.user?.role === 'TEACHER') {
// Teachers see reports for their classes
if (!classGroupId) {
const teacherClasses = await db.classGroupTeacher.findMany({
where: { userId: session.userId },
});
const classIds = teacherClasses.map((c) => c.classGroupId);
where.classGroupId = { in: classIds };
where.classGroupId = { in: await getTeacherClassIds(session.userId) };
}
}
@@ -129,6 +132,9 @@ export async function POST(request: Request) {
}
const { sections, ...reportData } = parsed.data;
if (!session.user || !(await canAccessStudent(session.user, reportData.studentId)) || !(await canAccessClass(session.user, reportData.classGroupId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// If attendance summary not provided, try to compute it
let attendanceSummary = reportData.attendanceSummary;
+22
View File
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
import { z } from 'zod';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessClass, canAccessStudent, getTeacherClassIds } from '@/lib/access-policy';
const createReportSchema = z.object({
studentId: z.string().min(1),
@@ -34,8 +35,26 @@ export async function GET(request: Request) {
const status = searchParams.get('status');
const where: Record<string, unknown> = {};
if (studentId && (!session.user || !(await canAccessStudent(session.user, studentId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (classGroupId && (!session.user || !(await canAccessClass(session.user, classGroupId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (studentId) where.studentId = studentId;
if (classGroupId) where.classGroupId = classGroupId;
if (!studentId && session.user?.role === 'STUDENT') {
const student = await db.student.findFirst({ where: { userId: session.user.id }, select: { id: true } });
if (!student) return NextResponse.json([]);
where.studentId = student.id;
where.status = 'PUBLISHED';
} else if (!studentId && session.user?.role === 'PARENT') {
const links = await db.parentStudentLink.findMany({ where: { parentId: session.user.id }, select: { studentId: true } });
where.studentId = { in: links.map((link) => link.studentId) };
where.status = 'PUBLISHED';
} else if (!studentId && session.user?.role === 'TEACHER') {
where.classGroupId = { in: await getTeacherClassIds(session.user.id) };
}
if (schoolYearId) where.schoolYearId = schoolYearId;
if (status) where.status = status;
@@ -95,6 +114,9 @@ export async function POST(request: Request) {
}
const { sections, ...reportData } = parsed.data;
if (!session.user || !(await canAccessStudent(session.user, reportData.studentId)) || !(await canAccessClass(session.user, reportData.classGroupId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const report = await db.report.create({
data: {
+16
View File
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
import { z } from 'zod';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessClass } from '@/lib/access-policy';
const resourceTypeEnum = z.enum([
'document', 'worksheet', 'presentation', 'video_link', 'image', 'link', 'audio',
@@ -48,11 +49,17 @@ export async function GET(
if (!resource) {
return NextResponse.json({ error: 'Resource not found' }, { status: 404 });
}
if (session.user?.role !== 'SUPER_ADMIN' && resource.schoolId !== session.user?.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Students can only see public resources
if (session.user?.role === 'STUDENT' && !resource.isPublic) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (resource.classGroupId && (!session.user || !(await canAccessClass(session.user, resource.classGroupId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
return NextResponse.json(resource);
} catch (error) {
@@ -88,6 +95,9 @@ export async function PUT(
if (!existing) {
return NextResponse.json({ error: 'Resource not found' }, { status: 404 });
}
if (session.user?.role !== 'SUPER_ADMIN' && existing.schoolId !== session.user?.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Only the author or admins can edit
if (session.user?.role === 'TEACHER' && existing.authorId !== session.userId) {
@@ -111,6 +121,9 @@ export async function PUT(
if (parsed.data.content !== undefined) updateData.content = parsed.data.content;
if (parsed.data.subjectId !== undefined) updateData.subjectId = parsed.data.subjectId || null;
if (parsed.data.classGroupId !== undefined) updateData.classGroupId = parsed.data.classGroupId || null;
if (parsed.data.classGroupId && (!session.user || !(await canAccessClass(session.user, parsed.data.classGroupId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (parsed.data.gradeLevel !== undefined) updateData.gradeLevel = parsed.data.gradeLevel;
if (parsed.data.tags !== undefined) updateData.tags = parsed.data.tags ? JSON.stringify(parsed.data.tags) : null;
if (parsed.data.isPublic !== undefined) updateData.isPublic = parsed.data.isPublic;
@@ -150,6 +163,9 @@ export async function DELETE(
if (!existing) {
return NextResponse.json({ error: 'Resource not found' }, { status: 404 });
}
if (session.user?.role !== 'SUPER_ADMIN' && existing.schoolId !== session.user?.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Only the author or admins can delete
if (session.user?.role === 'TEACHER' && existing.authorId !== session.userId) {
+16
View File
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
import { z } from 'zod';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessClass } from '@/lib/access-policy';
const resourceTypeEnum = z.enum([
'document', 'worksheet', 'presentation', 'video_link', 'image', 'link', 'audio',
@@ -50,17 +51,26 @@ export async function GET(request: Request) {
if (!schoolId) {
return NextResponse.json([]);
}
if (session.user?.role !== 'SUPER_ADMIN' && schoolId !== session.user?.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const where: Record<string, unknown> = { schoolId, deletedAt: null };
// Students can only see public resources
if (session.user?.role === 'STUDENT') {
where.isPublic = true;
const student = await db.student.findFirst({ where: { userId: session.user.id, deletedAt: null }, select: { id: true } });
const enrollments = student ? await db.enrollment.findMany({ where: { studentId: student.id, endDate: null }, select: { classGroupId: true } }) : [];
where.OR = [{ classGroupId: null }, { classGroupId: { in: enrollments.map((enrollment) => enrollment.classGroupId) } }];
}
if (resourceType && resourceType !== 'all') where.resourceType = resourceType;
if (subjectId && subjectId !== 'all') where.subjectId = subjectId;
if (classGroupId && classGroupId !== 'all') where.classGroupId = classGroupId;
if (classGroupId && classGroupId !== 'all' && !(await canAccessClass(session.user!, classGroupId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (isPublic === 'true') where.isPublic = true;
if (isPublic === 'false') where.isPublic = false;
@@ -117,6 +127,9 @@ export async function POST(request: Request) {
if (session.user?.role === 'SCHOOL_ADMIN' && session.user.schoolId && school.id !== session.user.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (session.user?.role === 'TEACHER' && session.user.schoolId !== school.id) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Verify subject if provided
if (subjectId) {
@@ -132,6 +145,9 @@ export async function POST(request: Request) {
if (!classGroup || classGroup.schoolId !== schoolId) {
return NextResponse.json({ error: 'Class group not found in this school' }, { status: 404 });
}
if (!session.user || !(await canAccessClass(session.user, classGroupId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
}
const resource = await db.resource.create({
@@ -1,6 +1,8 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessClass } from '@/lib/access-policy';
import { isAdministrator } from '@/lib/role-access';
export async function POST(
request: Request,
@@ -20,6 +22,12 @@ export async function POST(
if (!event) {
return NextResponse.json({ error: 'Event not found' }, { status: 404 });
}
if (session.user?.role !== 'SUPER_ADMIN' && event.schoolId !== session.user?.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (event.classGroupId && (!session.user || !(await canAccessClass(session.user, event.classGroupId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!event.requiresRegistration) {
return NextResponse.json({ error: 'Event does not require registration' }, { status: 400 });
@@ -36,6 +44,9 @@ export async function POST(
if (!userId) {
return NextResponse.json({ error: 'userId is required' }, { status: 400 });
}
if (userId !== session.userId && !isAdministrator(session.user?.role)) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Check if already registered
const existing = await db.eventRegistration.findUnique({
@@ -110,6 +121,13 @@ export async function PUT(
if (!registration) {
return NextResponse.json({ error: 'Registration not found' }, { status: 404 });
}
const event = await db.schoolEvent.findFirst({ where: { id, deletedAt: null } });
if (!event || (session.user?.role !== 'SUPER_ADMIN' && event.schoolId !== session.user?.schoolId)) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (userId !== session.userId && !isAdministrator(session.user?.role)) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const updated = await db.eventRegistration.update({
where: { id: registration.id },
@@ -153,6 +171,13 @@ export async function DELETE(
if (!registration) {
return NextResponse.json({ error: 'Registration not found' }, { status: 404 });
}
const event = await db.schoolEvent.findFirst({ where: { id, deletedAt: null } });
if (!event || (session.user?.role !== 'SUPER_ADMIN' && event.schoolId !== session.user?.schoolId)) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (userId !== session.userId && !isAdministrator(session.user?.role)) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Cancel registration (soft delete via status)
const updated = await db.eventRegistration.update({
+16 -1
View File
@@ -1,6 +1,8 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessClass } from '@/lib/access-policy';
import { isAdministrator } from '@/lib/role-access';
export async function GET(request: Request) {
try {
@@ -27,11 +29,18 @@ export async function GET(request: Request) {
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (classGroupId && (!session.user || !(await canAccessClass(session.user, classGroupId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const where: Record<string, unknown> = {
schoolId,
deletedAt: null,
};
if (session.user?.role === 'STUDENT') {
const student = await db.student.findFirst({ where: { userId: session.user.id }, select: { id: true } });
const classIds = student ? (await db.enrollment.findMany({ where: { studentId: student.id, endDate: null }, select: { classGroupId: true } })).map((enrollment) => enrollment.classGroupId) : [];
where.OR = [{ isAllSchool: true }, { classGroupId: { in: classIds } }];
}
if (eventType) where.eventType = eventType;
if (classGroupId) where.classGroupId = classGroupId;
@@ -131,6 +140,12 @@ export async function POST(request: Request) {
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!isAdministrator(session.user?.role) && session.user?.role !== 'TEACHER') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (classGroupId && (!session.user || !(await canAccessClass(session.user, classGroupId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const event = await db.schoolEvent.create({
data: {
+20
View File
@@ -1,6 +1,8 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessStudent } from '@/lib/access-policy';
import { isAdministrator } from '@/lib/role-access';
export async function GET(request: Request) {
try {
@@ -114,6 +116,9 @@ export async function GET(request: Request) {
// Teacher/Admin view
if (studentIdParam) {
if (!session.user || !(await canAccessStudent(session.user, studentIdParam))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const studentBadges = await db.studentBadge.findMany({
where: { schoolId, studentId: studentIdParam },
orderBy: { awardedAt: 'desc' },
@@ -164,6 +169,12 @@ export async function POST(request: Request) {
if (!schoolId || !studentId || !badgeId) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}
if (!isAdministrator(session.user?.role) && session.user?.role !== 'TEACHER') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!session.user || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Check if already awarded
const existing = await db.studentBadge.findUnique({
@@ -197,6 +208,12 @@ export async function POST(request: Request) {
if (!schoolId || !studentId || xpAmount === undefined) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}
if (!isAdministrator(session.user?.role) && session.user?.role !== 'TEACHER') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!session.user || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const student = await db.student.findUnique({ where: { id: studentId }, select: { userId: true } });
if (!student?.userId) return NextResponse.json({ error: 'Student has no user account' }, { status: 400 });
@@ -209,6 +226,9 @@ export async function POST(request: Request) {
if (!characterData) {
return NextResponse.json({ error: 'Missing characterData' }, { status: 400 });
}
if (schoolId && session.user?.role !== 'SUPER_ADMIN' && schoolId !== session.user?.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const character = await db.virtualCharacter.upsert({
where: { userId: session.userId },
+13
View File
@@ -3,6 +3,7 @@ import { z } from 'zod';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { withRateLimit } from '@/lib/rate-limit';
import { getTeacherClassIds } from '@/lib/access-policy';
// ── GET: List student answers (for the current student) ──
async function getAnswers(request: Request) {
@@ -74,6 +75,10 @@ async function getAnswers(request: Request) {
return NextResponse.json(answers);
}
if (session.user.role === 'PARENT') {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Teachers / Admins: can see answers for their school
const schoolId = session.user.schoolId;
if (!schoolId) {
@@ -85,6 +90,11 @@ async function getAnswers(request: Request) {
if (questionId) where.questionId = questionId;
if (lessonId) {
if (session.user.role === 'TEACHER') {
const classIds = await getTeacherClassIds(session.user.id);
const lesson = await db.subjectLesson.findFirst({ where: { id: lessonId, topic: { schoolId } }, select: { id: true } });
if (!lesson || classIds.length === 0) return NextResponse.json([]);
}
const answers = await db.studentAnswer.findMany({
where: {
question: {
@@ -139,6 +149,9 @@ async function submitAnswer(request: Request) {
if (!session?.user) {
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
}
if (session.user.role !== 'STUDENT') {
return NextResponse.json({ error: 'Only students can submit answers' }, { status: 403 });
}
const body = await request.json();
const parsed = submitAnswerSchema.safeParse(body);
+5
View File
@@ -1,6 +1,7 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessStudent } from '@/lib/access-policy';
function isTeacherOrAdmin(role: string | undefined): boolean {
return role === 'TEACHER' || role === 'SCHOOL_ADMIN' || role === 'SUPER_ADMIN';
@@ -26,6 +27,8 @@ export async function GET(
});
if (!studentBadge) return NextResponse.json({ error: 'Student badge not found' }, { status: 404 });
if (session.user?.role !== 'SUPER_ADMIN' && studentBadge.schoolId !== session.user?.schoolId) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
if (!session.user || !(await canAccessStudent(session.user, studentBadge.studentId))) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
return NextResponse.json(studentBadge);
} catch (error) {
console.error('StudentBadge GET error:', error);
@@ -46,6 +49,8 @@ export async function DELETE(
const existing = await db.studentBadge.findUnique({ where: { id } });
if (!existing) return NextResponse.json({ error: 'Student badge not found' }, { status: 404 });
if (session.user?.role !== 'SUPER_ADMIN' && existing.schoolId !== session.user?.schoolId) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
if (!session.user || !(await canAccessStudent(session.user, existing.studentId))) return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
await db.studentBadge.delete({ where: { id } });
+12
View File
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
import { z } from 'zod';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessStudent } from '@/lib/access-policy';
const awardBadgeSchema = z.object({
schoolId: z.string().min(1),
@@ -35,6 +36,9 @@ export async function GET(request: Request) {
if (!schoolId) return NextResponse.json([]);
const where: Record<string, unknown> = { schoolId };
if (studentIdParam && (!session.user || !(await canAccessStudent(session.user, studentIdParam)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (studentIdParam) where.studentId = studentIdParam;
if (badgeIdParam) where.badgeId = badgeIdParam;
@@ -68,14 +72,22 @@ export async function POST(request: Request) {
}
const { schoolId, studentId, badgeId } = parsed.data;
if (session.user?.role !== 'SUPER_ADMIN' && schoolId !== session.user?.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!session.user || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Verify badge exists
const badge = await db.badge.findUnique({ where: { id: badgeId, deletedAt: null } });
if (!badge) return NextResponse.json({ error: 'Badge not found' }, { status: 404 });
if (badge.schoolId !== schoolId) return NextResponse.json({ error: 'Badge not found in this school' }, { status: 404 });
// Verify student exists
const student = await db.student.findUnique({ where: { id: studentId, deletedAt: null } });
if (!student) return NextResponse.json({ error: 'Student not found' }, { status: 404 });
if (student.schoolId !== schoolId) return NextResponse.json({ error: 'Student not found in this school' }, { status: 404 });
// Check if already awarded
const existing = await db.studentBadge.findUnique({
+5 -43
View File
@@ -1,6 +1,7 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessStudent } from '@/lib/access-policy';
export async function GET(request: Request) {
try {
@@ -17,23 +18,8 @@ export async function GET(request: Request) {
return NextResponse.json({ error: 'studentId is required' }, { status: 400 });
}
// Verify the student belongs to the same school or is the student themselves
if (session.user?.role === 'STUDENT') {
const student = await db.student.findUnique({
where: { id: studentId },
select: { userId: true, schoolId: true },
});
if (!student || student.userId !== session.user.id) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
} else if (session.user?.role !== 'SUPER_ADMIN') {
const student = await db.student.findUnique({
where: { id: studentId },
select: { schoolId: true },
});
if (!student || student.schoolId !== session.user?.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!session.user || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const where: Record<string, unknown> = { studentId };
@@ -68,32 +54,8 @@ export async function POST(request: Request) {
);
}
// Verify the student belongs to the same school or is the student themselves
if (session.user?.role === 'STUDENT') {
const student = await db.student.findUnique({
where: { id: studentId },
select: { userId: true, schoolId: true, firstName: true, lastName: true },
});
if (!student) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Check by userId (direct link) or by name matching (fallback for legacy data)
const isOwnStudent = student.userId === session.user.id ||
(student.userId === null &&
student.firstName === session.user.firstName &&
student.lastName === session.user.lastName &&
student.schoolId === session.user.schoolId);
if (!isOwnStudent) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
} else if (session.user?.role !== 'SUPER_ADMIN') {
const student = await db.student.findUnique({
where: { id: studentId },
select: { schoolId: true },
});
if (!student || student.schoolId !== session.user?.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!session.user || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const goal = await db.studentGoal.create({
+5 -18
View File
@@ -1,6 +1,7 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessStudent } from '@/lib/access-policy';
export async function GET(request: Request) {
try {
@@ -34,15 +35,8 @@ export async function GET(request: Request) {
if (!studentId) return NextResponse.json([]);
// Verify access
if (session.user?.role === 'STUDENT') {
const student = await db.student.findUnique({
where: { id: studentId },
select: { userId: true },
});
if (!student || student.userId !== session.userId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!session.user || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const studyPlans = await db.studyPlan.findMany({
@@ -125,15 +119,8 @@ export async function POST(request: Request) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}
// Verify access
if (session.user?.role === 'STUDENT') {
const student = await db.student.findUnique({
where: { id: studentId },
select: { userId: true, schoolId: true },
});
if (!student || student.userId !== session.userId || student.schoolId !== schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!session.user || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const plan = await db.studyPlan.create({
@@ -1,6 +1,8 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessStudent } from '@/lib/access-policy';
import { isAdministrator } from '@/lib/role-access';
export async function GET(
_request: Request,
@@ -32,6 +34,9 @@ export async function GET(
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== transport.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!isAdministrator(session.user?.role) || !session.user || !(await canAccessStudent(session.user, transport.studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
return NextResponse.json(transport);
} catch (error) {
@@ -62,6 +67,9 @@ export async function PUT(
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== existing.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!isAdministrator(session.user?.role) || !session.user || !(await canAccessStudent(session.user, existing.studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const body = await request.json();
const {
@@ -130,6 +138,9 @@ export async function DELETE(
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== existing.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!isAdministrator(session.user?.role) || !session.user || !(await canAccessStudent(session.user, existing.studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Soft delete
await db.studentTransport.update({
+11
View File
@@ -1,6 +1,8 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessStudent } from '@/lib/access-policy';
import { isAdministrator } from '@/lib/role-access';
export async function GET(request: Request) {
try {
@@ -20,12 +22,18 @@ export async function GET(request: Request) {
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!isAdministrator(session.user?.role)) {
return NextResponse.json({ error: 'Student transport is restricted to administrators' }, { status: 403 });
}
const where: Record<string, unknown> = {
schoolId,
deletedAt: null,
};
if (studentId && (!session.user || !(await canAccessStudent(session.user, studentId)))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (studentId) where.studentId = studentId;
const transports = await db.studentTransport.findMany({
@@ -82,6 +90,9 @@ export async function POST(request: Request) {
if (session.user?.role !== 'SUPER_ADMIN' && session.user?.schoolId !== schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (!isAdministrator(session.user?.role) || !session.user || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const transport = await db.studentTransport.create({
data: {
+7
View File
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server';
import { z } from 'zod';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessStudent } from '@/lib/access-policy';
const noteCategoryEnum = z.enum([
'GENERAL',
@@ -49,6 +50,9 @@ export async function GET(request: Request) {
if (!student) {
return NextResponse.json({ error: 'Student not found' }, { status: 404 });
}
if (!session.user || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (
session.user?.role === 'SCHOOL_ADMIN' &&
@@ -104,6 +108,9 @@ export async function POST(request: Request) {
if (!student) {
return NextResponse.json({ error: 'Student not found' }, { status: 404 });
}
if (!session.user || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (
session.user?.role === 'SCHOOL_ADMIN' &&
+21 -2
View File
@@ -1,22 +1,30 @@
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { getSession } from '@/lib/auth';
import { canAccessClass, canAccessStudent } from '@/lib/access-policy';
import { isAdministrator } from '@/lib/role-access';
// GET /api/wellness — Get wellness data for a student
export async function GET(req: NextRequest) {
try {
const session = await getSession();
if (!session?.user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const { searchParams } = new URL(req.url);
const schoolId = searchParams.get('schoolId');
const studentId = searchParams.get('studentId');
const role = searchParams.get('role') || 'STUDENT';
const role = session.user.role;
const classGroupId = searchParams.get('classGroupId');
const limit = parseInt(searchParams.get('limit') || '30');
if (!schoolId) {
return NextResponse.json({ error: 'schoolId is required' }, { status: 400 });
}
if (role !== 'SUPER_ADMIN' && schoolId !== session.user.schoolId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
// Admin/VP: school-wide overview
if ((role === 'SCHOOL_ADMIN' || role === 'VICE_PRINCIPAL') && !studentId) {
if (isAdministrator(role) && !studentId) {
const totalCheckins = await db.wellnessCheckin.count({
where: { schoolId },
});
@@ -55,6 +63,9 @@ export async function GET(req: NextRequest) {
// Teacher: class overview
if (role === 'TEACHER' && classGroupId && !studentId) {
if (!(await canAccessClass(session.user, classGroupId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const enrollments = await db.enrollment.findMany({
where: { classGroupId, endDate: null },
select: { studentId: true },
@@ -82,6 +93,9 @@ export async function GET(req: NextRequest) {
// Student or specific student view
if (studentId) {
if (!(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
const checkins = await db.wellnessCheckin.findMany({
where: { schoolId, studentId },
orderBy: { date: 'desc' },
@@ -112,12 +126,17 @@ export async function GET(req: NextRequest) {
// POST /api/wellness — Submit daily check-in
export async function POST(req: NextRequest) {
try {
const session = await getSession();
if (!session?.user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const body = await req.json();
const { schoolId, studentId, mood, sleepHours, sleepQuality, stressLevel, activityType, activityMinutes, mealsCount, waterGlasses, gratitudeEntry, notes } = body;
if (!schoolId || !studentId || mood === undefined) {
return NextResponse.json({ error: 'schoolId, studentId, and mood are required' }, { status: 400 });
}
if (session.user.role !== 'STUDENT' || schoolId !== session.user.schoolId || !(await canAccessStudent(session.user, studentId))) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
if (mood < 1 || mood > 5) {
return NextResponse.json({ error: 'Mood must be between 1 and 5' }, { status: 400 });