← back to Wine Finder Next

app/api/membership/bottles/route.ts

124 lines

import { NextRequest, NextResponse } from 'next/server';
import { bottleDb } from '@/lib/membershipDatabase';
import type { ApiResponse } from '@/src/types/membership';
import { checkRateLimit, getRateLimitConfig, getClientIdentifier } from '@/lib/rateLimit';
import { logAudit } from '@/lib/auth';
import { sanitizeString, validateBottleId } from '@/lib/inputValidation';
import { analyzeRequest } from '@/lib/securityMonitoring';

export async function GET(request: NextRequest) {
  try {
    // Rate limiting
    const identifier = getClientIdentifier(request);
    const rateLimit = checkRateLimit(identifier, getRateLimitConfig('/api/membership/bottles'));

    if (!rateLimit.success) {
      logAudit({
        action: 'RATE_LIMIT_EXCEEDED',
        resource: 'bottles/list',
        ipAddress: identifier,
        userAgent: request.headers.get('user-agent') || 'unknown',
        success: false
      });

      return NextResponse.json({
        success: false,
        error: 'Rate limit exceeded. Please try again later.'
      } as ApiResponse<null>, {
        status: 429,
        headers: {
          'X-RateLimit-Limit': rateLimit.limit.toString(),
          'X-RateLimit-Remaining': '0',
          'X-RateLimit-Reset': new Date(rateLimit.reset).toISOString()
        }
      });
    }

    const bottles = bottleDb.getAll();

    logAudit({
      action: 'BOTTLES_LIST',
      resource: 'bottles',
      ipAddress: identifier,
      userAgent: request.headers.get('user-agent') || 'unknown',
      success: true,
      metadata: { count: bottles.length }
    });

    return NextResponse.json({
      success: true,
      data: bottles
    } as ApiResponse<typeof bottles>, {
      headers: {
        'X-RateLimit-Limit': rateLimit.limit.toString(),
        'X-RateLimit-Remaining': rateLimit.remaining.toString(),
        'X-RateLimit-Reset': new Date(rateLimit.reset).toISOString()
      }
    });
  } catch (error) {
    return NextResponse.json({
      success: false,
      error: 'Failed to fetch bottles'
    } as ApiResponse<null>, { status: 500 });
  }
}

export async function POST(request: NextRequest) {
  try {
    // Rate limiting
    const identifier = getClientIdentifier(request);
    const rateLimit = checkRateLimit(identifier, getRateLimitConfig('/api/membership/bottles'));

    if (!rateLimit.success) {
      return NextResponse.json({
        success: false,
        error: 'Rate limit exceeded. Please try again later.'
      } as ApiResponse<null>, { status: 429 });
    }

    // Threat detection
    const analysis = analyzeRequest({
      body: await request.json(),
      headers: Object.fromEntries(request.headers),
      ip: identifier
    });

    if (!analysis.safe) {
      return NextResponse.json({
        success: false,
        error: 'Security threat detected'
      } as ApiResponse<null>, { status: 403 });
    }

    const body = await request.json();

    // Input validation and sanitization
    const sanitizedName = sanitizeString(body.name, 200);

    const newBottle = bottleDb.create({
      ...body,
      name: sanitizedName
    });

    logAudit({
      action: 'BOTTLE_CREATE',
      resource: `bottle/${newBottle.id}`,
      ipAddress: identifier,
      userAgent: request.headers.get('user-agent') || 'unknown',
      success: true,
      metadata: { bottleId: newBottle.id, name: sanitizedName }
    });

    return NextResponse.json({
      success: true,
      data: newBottle,
      message: 'Bottle created successfully'
    } as ApiResponse<typeof newBottle>);
  } catch (error) {
    return NextResponse.json({
      success: false,
      error: 'Failed to create bottle'
    } as ApiResponse<null>, { status: 500 });
  }
}