diff --git a/app/api/projects/route.ts b/app/api/projects/route.ts index c26b533..89cea32 100644 --- a/app/api/projects/route.ts +++ b/app/api/projects/route.ts @@ -1,22 +1,25 @@ import { NextRequest, NextResponse } from 'next/server'; import { prisma } from '@/lib/prisma'; import { apiCache } from '@/lib/cache'; -import { requireSessionAuth, checkRateLimit, getRateLimitHeaders } from '@/lib/auth'; +import { requireSessionAuth, checkRateLimit, getRateLimitHeaders, getClientIp } from '@/lib/auth'; import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'; import { generateUniqueSlug } from '@/lib/slug'; 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 + const ip = getClientIp(request); + const rlKey = ip !== "unknown" ? ip : `dev_unknown:${request.headers.get("user-agent") || "ua"}`; + // In development we keep this very high to avoid breaking local navigation/HMR. + const max = process.env.NODE_ENV === "development" ? 300 : 60; + if (!checkRateLimit(rlKey, max, 60000)) { return new NextResponse( JSON.stringify({ error: 'Rate limit exceeded' }), { status: 429, headers: { 'Content-Type': 'application/json', - ...getRateLimitHeaders(ip, 10, 60000) + ...getRateLimitHeaders(rlKey, max, 60000) } } ); diff --git a/scripts/dev-minimal.js b/scripts/dev-minimal.js index ef78e2e..a597203 100644 --- a/scripts/dev-minimal.js +++ b/scripts/dev-minimal.js @@ -1,7 +1,7 @@ #!/usr/bin/env node /* eslint-disable @typescript-eslint/no-require-imports */ -const { spawn, exec } = require('child_process'); +const { spawn, exec } = require("child_process"); const os = require('os'); const isWindows = process.platform === 'win32'; @@ -15,9 +15,23 @@ console.log(`šŸ–„ļø Detected architecture: ${arch}`); console.log(`šŸŽ Apple Silicon: ${isAppleSilicon ? 'Yes' : 'No'}`); // Use minimal compose file (only PostgreSQL and Redis) -const composeFile = 'docker-compose.dev.minimal.yml'; +const composeFile = "docker-compose.dev.minimal.yml"; console.log(`šŸ“¦ Using minimal Docker Compose file: ${composeFile}`); +function runCommand(command, env) { + return new Promise((resolve, reject) => { + exec(command, { env: env ?? process.env }, (error, stdout, stderr) => { + if (stdout) process.stdout.write(stdout); + if (stderr) process.stderr.write(stderr); + if (error) { + reject(error); + return; + } + resolve(undefined); + }); + }); +} + // Check if docker-compose is available exec('docker-compose --version', (error) => { if (error) { @@ -43,40 +57,60 @@ exec('docker-compose --version', (error) => { // Wait a bit for services to be ready setTimeout(() => { - console.log('šŸš€ Starting Next.js development server...'); - - // Start Next.js dev server - const nextProcess = spawn('npm', ['run', 'dev:next'], { - stdio: 'inherit', - shell: isWindows, - env: { - ...process.env, - DATABASE_URL: process.env.DATABASE_URL || 'postgresql://portfolio_user:portfolio_dev_pass@localhost:5432/portfolio_dev?schema=public', - REDIS_URL: 'redis://localhost:6379', - NODE_ENV: 'development' + const devEnv = { + ...process.env, + DATABASE_URL: + process.env.DATABASE_URL || + "postgresql://portfolio_user:portfolio_dev_pass@localhost:5432/portfolio_dev?schema=public", + REDIS_URL: "redis://localhost:6379", + NODE_ENV: "development", + }; + + // Ensure DB schema exists before starting Next dev server. + // This prevents "table does not exist" errors for Projects/CMS/Analytics. + (async () => { + try { + console.log("šŸ—„ļø Applying Prisma migrations (dev)..."); + await runCommand("npx prisma generate", devEnv); + await runCommand("npx prisma migrate deploy", devEnv); + // seeding is optional; keep it non-blocking + await runCommand("npx prisma db seed", devEnv).catch(() => {}); + console.log("āœ… Database is ready"); + } catch (_e) { + console.warn("āš ļø Could not run Prisma migrations automatically."); + console.warn(" You can run: npm run db:setup"); } - }); - nextProcess.on('close', (code) => { - console.log(`Next.js dev server exited with code ${code}`); - }); + console.log("šŸš€ Starting Next.js development server..."); + + // Start Next.js dev server + const nextProcess = spawn("npm", ["run", "dev:next"], { + stdio: "inherit", + shell: isWindows, + env: devEnv, + }); - // Handle process signals - process.on('SIGINT', () => { - console.log('\nšŸ›‘ Stopping development environment...'); - nextProcess.kill('SIGTERM'); - - // Stop Docker services - const stopProcess = spawn('docker-compose', ['-f', composeFile, 'down'], { - stdio: 'inherit', - shell: isWindows + nextProcess.on("close", (code) => { + console.log(`Next.js dev server exited with code ${code}`); }); - - stopProcess.on('close', () => { - console.log('āœ… Development environment stopped'); - process.exit(0); + + // Handle process signals + process.on("SIGINT", () => { + console.log("\nšŸ›‘ Stopping development environment..."); + nextProcess.kill("SIGTERM"); + + // Stop Docker services + const stopProcess = spawn("docker-compose", ["-f", composeFile, "down"], { + stdio: "inherit", + shell: isWindows, + }); + + stopProcess.on("close", () => { + console.log("āœ… Development environment stopped"); + process.exit(0); + }); }); - }); + })(); }, 5000); // Wait 5 seconds for services to be ready diff --git a/scripts/setup-database.js b/scripts/setup-database.js index 3588f7f..fae6da7 100644 --- a/scripts/setup-database.js +++ b/scripts/setup-database.js @@ -32,8 +32,8 @@ async function setupDatabase() { console.log('šŸ“¦ Generating Prisma client...'); await runCommand('npx prisma generate'); - console.log('šŸ”„ Pushing database schema...'); - await runCommand('npx prisma db push'); + console.log('šŸ”„ Applying database migrations...'); + await runCommand('npx prisma migrate deploy'); console.log('🌱 Seeding database...'); await runCommand('npx prisma db seed');