feat: Fix Forbidden 403 errors, add notebook split view (text+handwriting), German curriculum templates, role-based navigation
- Fix attendance API: students/parents can now view their own attendance records - Fix competitions API: all authenticated users can view competitions - Add notebook split view: text editor + drawing canvas side by side - Add resizable divider for split view with mouse drag support - Add 3 new German curriculum templates: Grundschule, Geschichte, Religion/Ethik - Enhance existing templates with more detailed content - Fix role comparisons in competitions-view (uppercase role names) - Add Resources and Settings to student navigation - Add Grading, Attendance, Competitions to parent navigation - Add i18n keys for new features (DE + EN)
This commit is contained in:
1 parent
8e7116e57d
commit
86e4b504e4
7 files changed
+338
-35
No files matched your search
@@ -30,9 +30,8 @@ export async function GET(request: Request) {
|
||||
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
if (session.user?.role !== 'TEACHER' && session.user?.role !== 'SCHOOL_ADMIN' && session.user?.role !== 'SUPER_ADMIN') {
|
||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
}
|
||||
const role = session.user?.role;
|
||||
const isTeacherOrAdmin = role === 'TEACHER' || role === 'SCHOOL_ADMIN' || role === 'SUPER_ADMIN';
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const classGroupId = searchParams.get('classGroupId');
|
||||
@@ -62,6 +61,47 @@ export async function GET(request: Request) {
|
||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
}
|
||||
|
||||
// Students can only view their own attendance records
|
||||
if (role === 'STUDENT') {
|
||||
// Find the student record matching this user (by name + school)
|
||||
const studentRecord = await db.student.findFirst({
|
||||
where: {
|
||||
schoolId: session.user.schoolId ?? undefined,
|
||||
firstName: session.user.firstName,
|
||||
lastName: session.user.lastName,
|
||||
deletedAt: null,
|
||||
},
|
||||
});
|
||||
if (!studentRecord) {
|
||||
// No student record found for this user - return empty
|
||||
return NextResponse.json([]);
|
||||
}
|
||||
// Verify the student is enrolled in this class
|
||||
const enrollment = await db.enrollment.findFirst({
|
||||
where: { classGroupId, studentId: studentRecord.id, endDate: null },
|
||||
});
|
||||
if (!enrollment) {
|
||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
}
|
||||
}
|
||||
|
||||
// Parents can view attendance if they have a child in the class
|
||||
if (role === 'PARENT') {
|
||||
const childEnrollment = await db.enrollment.findFirst({
|
||||
where: { classGroupId, endDate: null },
|
||||
include: {
|
||||
student: {
|
||||
include: {
|
||||
parentLinks: { where: { parentId: session.userId } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!childEnrollment || childEnrollment.student.parentLinks.length === 0) {
|
||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
}
|
||||
}
|
||||
|
||||
const where: Record<string, unknown> = { classGroupId };
|
||||
if (dateFrom || dateTo) {
|
||||
const dateFilter: Record<string, unknown> = {};
|
||||
@@ -88,6 +128,38 @@ export async function GET(request: Request) {
|
||||
},
|
||||
});
|
||||
|
||||
// For students, filter records to only show their own
|
||||
if (role === 'STUDENT') {
|
||||
const studentRecord = await db.student.findFirst({
|
||||
where: {
|
||||
schoolId: session.user.schoolId ?? undefined,
|
||||
firstName: session.user.firstName,
|
||||
lastName: session.user.lastName,
|
||||
deletedAt: null,
|
||||
},
|
||||
});
|
||||
const studentId = studentRecord?.id ?? '';
|
||||
const filteredSessions = sessions.map((s) => ({
|
||||
...s,
|
||||
records: s.records.filter((r) => r.studentId === studentId),
|
||||
}));
|
||||
return NextResponse.json(filteredSessions);
|
||||
}
|
||||
|
||||
// For parents, filter records to only show their children's
|
||||
if (role === 'PARENT') {
|
||||
const childIds = await db.parentStudentLink.findMany({
|
||||
where: { parentId: session.userId },
|
||||
select: { studentId: true },
|
||||
});
|
||||
const childIdSet = new Set(childIds.map((c) => c.studentId));
|
||||
const filteredSessions = sessions.map((s) => ({
|
||||
...s,
|
||||
records: s.records.filter((r) => childIdSet.has(r.studentId)),
|
||||
}));
|
||||
return NextResponse.json(filteredSessions);
|
||||
}
|
||||
|
||||
return NextResponse.json(sessions);
|
||||
} catch (error) {
|
||||
console.error('Attendance GET error:', error);
|
||||
|
||||
@@ -42,9 +42,7 @@ async function getCompetitions(request: Request): Promise<NextResponse> {
|
||||
try {
|
||||
const session = await getSession();
|
||||
if (!session) return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
||||
if (!isTeacherOrAdmin(session.user?.role)) {
|
||||
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
||||
}
|
||||
// All authenticated users can view competitions (students, parents, teachers, admins)
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const schoolIdParam = searchParams.get('schoolId');
|
||||
|
||||
Reference in new issue
Block a user