import { NextRequest, NextResponse } from 'next/server'; import { prisma } from '@/lib/prisma'; import { apiCache } from '@/lib/cache'; import { checkRateLimit, getRateLimitHeaders, requireSessionAuth } from '@/lib/auth'; import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'; export async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { const { id: idParam } = await params; const id = parseInt(idParam); if (!Number.isFinite(id)) { return NextResponse.json({ error: 'Invalid project id' }, { status: 400 }); } const project = await prisma.project.findUnique({ where: { id } }); if (!project) { return NextResponse.json( { error: 'Project not found' }, { status: 404 } ); } return NextResponse.json(project); } catch (error) { // Handle missing database table gracefully if (error instanceof PrismaClientKnownRequestError && error.code === 'P2021') { if (process.env.NODE_ENV === 'development') { console.warn('Project table does not exist. Returning 404.'); } return NextResponse.json( { error: 'Project not found' }, { status: 404 } ); } if (process.env.NODE_ENV === 'development') { console.error('Error fetching project:', error); } return NextResponse.json( { error: 'Failed to fetch project' }, { status: 500 } ); } } export async function PUT( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { // Rate limiting for PUT requests const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown'; if (!checkRateLimit(ip, 5, 60000)) { // 5 requests per minute for PUT return new NextResponse( JSON.stringify({ error: 'Rate limit exceeded' }), { status: 429, headers: { 'Content-Type': 'application/json', ...getRateLimitHeaders(ip, 5, 60000) } } ); } // Check if this is an admin request const isAdminRequest = request.headers.get('x-admin-request') === 'true'; if (!isAdminRequest) { return NextResponse.json( { error: 'Admin access required' }, { status: 403 } ); } const authError = requireSessionAuth(request); if (authError) return authError; const { id: idParam } = await params; const id = parseInt(idParam); if (!Number.isFinite(id)) { return NextResponse.json({ error: 'Invalid project id' }, { status: 400 }); } const data = await request.json(); // Remove difficulty field if it exists (since we're removing it) const { difficulty, ...projectData } = data; const project = await prisma.project.update({ where: { id }, data: { ...projectData, updatedAt: new Date(), // Keep existing difficulty if not provided ...(difficulty ? { difficulty } : {}) } }); // Invalidate cache after successful update await apiCache.invalidateProject(id); await apiCache.invalidateAll(); return NextResponse.json(project); } catch (error) { // Handle missing database table gracefully if (error instanceof PrismaClientKnownRequestError && error.code === 'P2021') { if (process.env.NODE_ENV === 'development') { console.warn('Project table does not exist.'); } return NextResponse.json( { error: 'Database table not found. Please run migrations.' }, { status: 503 } ); } if (process.env.NODE_ENV === 'development') { console.error('Error updating project:', error); } return NextResponse.json( { error: 'Failed to update project', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 } ); } } export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { try { // Rate limiting for DELETE requests const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown'; if (!checkRateLimit(ip, 3, 60000)) { // 3 requests per minute for DELETE (more restrictive) return new NextResponse( JSON.stringify({ error: 'Rate limit exceeded' }), { status: 429, headers: { 'Content-Type': 'application/json', ...getRateLimitHeaders(ip, 3, 60000) } } ); } // Check if this is an admin request const isAdminRequest = request.headers.get('x-admin-request') === 'true'; if (!isAdminRequest) { return NextResponse.json( { error: 'Admin access required' }, { status: 403 } ); } const authError = requireSessionAuth(request); if (authError) return authError; const { id: idParam } = await params; const id = parseInt(idParam); if (!Number.isFinite(id)) { return NextResponse.json({ error: 'Invalid project id' }, { status: 400 }); } await prisma.project.delete({ where: { id } }); // Invalidate cache after successful deletion await apiCache.invalidateProject(id); await apiCache.invalidateAll(); return NextResponse.json({ success: true }); } catch (error) { // Handle missing database table gracefully if (error instanceof PrismaClientKnownRequestError && error.code === 'P2021') { if (process.env.NODE_ENV === 'development') { console.warn('Project table does not exist.'); } return NextResponse.json( { error: 'Database table not found. Please run migrations.' }, { status: 503 } ); } if (process.env.NODE_ENV === 'development') { console.error('Error deleting project:', error); } return NextResponse.json( { error: 'Failed to delete project' }, { status: 500 } ); } }