- Schlaukopf.de clone: Subject Content Management System (7 categories, 416 topics) - AI Integration: Pollination AI, OpenAI, Anthropic (chat, image/video generation) - Virtual Character System: 5 animated characters (Owl, Dragon, Robot, Cat, Wizard) - Illness Reporting with Parent Approval: privacy-first, student reports need parent approval - Student-Teacher Communication Rooms: real-time chat, voice, file sharing - Counseling/Guidance System: 4 types, calendar integration - Disciplinary Committee System: case management, severity levels - AI Test Generation: auto-generate practice tests before exams - Teacher Grading Review: AI-powered grading audit - VICE_PRINCIPAL role with same privileges as SCHOOL_ADMIN - Class responsible teacher (Klassenlehrer) field - 15 new Prisma models (80+ total) - 30+ new API route files - 8 new frontend components - 400+ i18n keys - Professional styling with gradient headers, animated counters, skeleton loading
78 lines
2.5 KiB
TypeScript
78 lines
2.5 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { z } from 'zod';
|
|
import { db } from '@/lib/db';
|
|
import { getSession } from '@/lib/auth';
|
|
import { withRateLimit } from '@/lib/rate-limit';
|
|
|
|
// ── PUT: Approve or reject a change request ──
|
|
const updateChangeRequestSchema = z.object({
|
|
status: z.enum(['approved', 'rejected']),
|
|
reviewNote: z.string().max(2000).optional().nullable(),
|
|
});
|
|
|
|
async function updateChangeRequest(
|
|
request: Request,
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
) {
|
|
try {
|
|
const session = await getSession();
|
|
if (!session?.user) {
|
|
return NextResponse.json({ error: 'Not authenticated' }, { status: 401 });
|
|
}
|
|
|
|
if (
|
|
session.user.role !== 'SCHOOL_ADMIN' &&
|
|
session.user.role !== 'VICE_PRINCIPAL' &&
|
|
session.user.role !== 'SUPER_ADMIN'
|
|
) {
|
|
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
|
|
}
|
|
|
|
const { id } = await params;
|
|
const body = await request.json();
|
|
const parsed = updateChangeRequestSchema.safeParse(body);
|
|
if (!parsed.success) {
|
|
return NextResponse.json(
|
|
{ error: 'Validation failed', details: parsed.error.issues },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const changeRequest = await db.contentChangeRequest.update({
|
|
where: { id },
|
|
data: {
|
|
status: parsed.data.status,
|
|
reviewedBy: session.userId,
|
|
reviewNote: parsed.data.reviewNote,
|
|
},
|
|
include: {
|
|
content: { select: { id: true, title: true } },
|
|
requester: { select: { id: true, firstName: true, lastName: true } },
|
|
reviewer: { select: { id: true, firstName: true, lastName: true } },
|
|
},
|
|
});
|
|
|
|
// If approved and it's an edit request, apply the proposed changes to the content
|
|
if (parsed.data.status === 'approved' && changeRequest.proposedChanges) {
|
|
try {
|
|
const proposed = JSON.parse(changeRequest.proposedChanges);
|
|
if (changeRequest.content?.id && proposed) {
|
|
await db.subjectContent.update({
|
|
where: { id: changeRequest.contentId },
|
|
data: proposed,
|
|
});
|
|
}
|
|
} catch {
|
|
// If proposed changes are not valid JSON, skip auto-apply
|
|
}
|
|
}
|
|
|
|
return NextResponse.json(changeRequest);
|
|
} catch (error) {
|
|
console.error('ContentChangeRequest PUT [id] error:', error);
|
|
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
}
|
|
}
|
|
|
|
export const PUT = withRateLimit(updateChangeRequest, 'dataWrite');
|