import { NextRequest, NextResponse } from 'next/server'; import { prisma } from '@/lib/prisma'; import { apiCache } from '@/lib/cache'; import { requireSessionAuth, checkRateLimit, getRateLimitHeaders } from '@/lib/auth'; export async function GET(request: NextRequest) { try { // Rate limiting const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown'; if (!checkRateLimit(ip, 10, 60000)) { // 10 requests per minute return new NextResponse( JSON.stringify({ error: 'Rate limit exceeded' }), { status: 429, headers: { 'Content-Type': 'application/json', ...getRateLimitHeaders(ip, 10, 60000) } } ); } // Check session authentication for admin endpoints const url = new URL(request.url); if (url.pathname.includes('/manage') || request.headers.get('x-admin-request') === 'true') { const authError = requireSessionAuth(request); if (authError) { return authError; } } const { searchParams } = new URL(request.url); const page = parseInt(searchParams.get('page') || '1'); const limit = parseInt(searchParams.get('limit') || '50'); const category = searchParams.get('category'); const featured = searchParams.get('featured'); const published = searchParams.get('published'); const difficulty = searchParams.get('difficulty'); const search = searchParams.get('search'); // Create cache parameters object const cacheParams = { page: page.toString(), limit: limit.toString(), category, featured, published, difficulty, search }; // Check cache first const cached = await apiCache.getProjects(cacheParams); if (cached && !search) { // Don't cache search results return NextResponse.json(cached); } const skip = (page - 1) * limit; const where: Record = {}; if (category) where.category = category; if (featured !== null) where.featured = featured === 'true'; if (published !== null) where.published = published === 'true'; if (difficulty) where.difficulty = difficulty; if (search) { where.OR = [ { title: { contains: search, mode: 'insensitive' } }, { description: { contains: search, mode: 'insensitive' } }, { tags: { hasSome: [search] } }, { content: { contains: search, mode: 'insensitive' } } ]; } const [projects, total] = await Promise.all([ prisma.project.findMany({ where, orderBy: { createdAt: 'desc' }, skip, take: limit }), prisma.project.count({ where }) ]); const result = { projects, total, pages: Math.ceil(total / limit), currentPage: page }; // Cache the result (only for non-search queries) if (!search) { await apiCache.setProjects(cacheParams, result); } return NextResponse.json(result); } catch (error) { console.error('Error fetching projects:', error); return NextResponse.json( { error: 'Failed to fetch projects' }, { status: 500 } ); } } export async function POST(request: NextRequest) { try { // 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 data = await request.json(); // Remove difficulty field if it exists (since we're removing it) // eslint-disable-next-line @typescript-eslint/no-unused-vars const { difficulty, ...projectData } = data; const project = await prisma.project.create({ data: { ...projectData, // Set default difficulty since it's required in schema difficulty: 'INTERMEDIATE', performance: data.performance || { lighthouse: 0, bundleSize: '0KB', loadTime: '0s' }, analytics: data.analytics || { views: 0, likes: 0, shares: 0 } } }); // Invalidate cache await apiCache.invalidateAll(); return NextResponse.json(project); } catch (error) { console.error('Error creating project:', error); return NextResponse.json( { error: 'Failed to create project', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 } ); } }