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:
Mustafa Cetin committed 2026-07-29 15:05:07 +00:00
1 parent 8e7116e57d
commit 86e4b504e4
7 files changed
+338 -35

No files matched your search

+75 -3
View File
@@ -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);
+1 -3
View File
@@ -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');