← back to Wine Finder Next

app/api/membership/bottles/[id]/route.ts

164 lines

import { NextRequest, NextResponse } from 'next/server';
import { bottleDb, membershipUnitDb, governanceVoteDb } 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,
  { params }: { params: Promise<{ id: string }> }
) {
  try {
    const { id } = await params;

    // 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 });
    }

    // Validate bottle ID
    const sanitizedId = sanitizeString(id, 100);
    if (!validateBottleId(sanitizedId)) {
      return NextResponse.json({
        success: false,
        error: 'Invalid bottle ID format'
      } as ApiResponse<null>, { status: 400 });
    }

    const bottle = bottleDb.getById(sanitizedId);

    if (!bottle) {
      logAudit({
        action: 'BOTTLE_VIEW_NOT_FOUND',
        resource: `bottle/${sanitizedId}`,
        ipAddress: identifier,
        userAgent: request.headers.get('user-agent') || 'unknown',
        success: false
      });

      return NextResponse.json({
        success: false,
        error: 'Bottle not found'
      } as ApiResponse<null>, { status: 404 });
    }

    const membershipUnits = membershipUnitDb.getByBottleId(sanitizedId);
    const votes = governanceVoteDb.getByBottleId(sanitizedId);

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

    return NextResponse.json({
      success: true,
      data: {
        bottle,
        membershipUnits,
        votes
      }
    });
  } catch (error) {
    return NextResponse.json({
      success: false,
      error: 'Failed to fetch bottle details'
    } as ApiResponse<null>, { status: 500 });
  }
}

export async function PATCH(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  try {
    const { id } = await params;

    // 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 body = await request.json();
    const analysis = analyzeRequest({
      body,
      params: { id },
      headers: Object.fromEntries(request.headers),
      ip: identifier
    });

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

    // Validate and sanitize
    const sanitizedId = sanitizeString(id, 100);
    if (!validateBottleId(sanitizedId)) {
      return NextResponse.json({
        success: false,
        error: 'Invalid bottle ID format'
      } as ApiResponse<null>, { status: 400 });
    }

    const sanitizedBody = body.name ? { ...body, name: sanitizeString(body.name, 200) } : body;

    const updated = bottleDb.update(sanitizedId, sanitizedBody);

    if (!updated) {
      logAudit({
        action: 'BOTTLE_UPDATE_NOT_FOUND',
        resource: `bottle/${sanitizedId}`,
        ipAddress: identifier,
        userAgent: request.headers.get('user-agent') || 'unknown',
        success: false
      });

      return NextResponse.json({
        success: false,
        error: 'Bottle not found'
      } as ApiResponse<null>, { status: 404 });
    }

    logAudit({
      action: 'BOTTLE_UPDATE',
      resource: `bottle/${sanitizedId}`,
      ipAddress: identifier,
      userAgent: request.headers.get('user-agent') || 'unknown',
      success: true,
      metadata: { bottleId: sanitizedId, changes: Object.keys(sanitizedBody) }
    });

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