← back to Wine Finder Next

app/api/membership/fulfillment/route.ts

186 lines

import { NextRequest, NextResponse } from 'next/server';
import { fulfillmentDb, sampleRightDb, partnerDb } from '@/lib/membershipDatabase';
import type { ApiResponse, ComplianceCheck } from '@/src/types/membership';
import { checkRateLimit, getRateLimitConfig, getClientIdentifier } from '@/lib/rateLimit';
import { logAudit } from '@/lib/auth';
import { sanitizeString, validateUserId } from '@/lib/inputValidation';

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

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

    const { searchParams } = new URL(request.url);
    const userId = searchParams.get('userId');

    // Validate userId if provided
    if (userId) {
      const sanitizedUserId = sanitizeString(userId, 100);
      if (!validateUserId(sanitizedUserId)) {
        return NextResponse.json({
          success: false,
          error: 'Invalid user ID format'
        } as ApiResponse<null>, { status: 400 });
      }
    }

    const requests = userId
      ? fulfillmentDb.getByUserId(sanitizeString(userId, 100))
      : fulfillmentDb.getAll();

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

    return NextResponse.json({
      success: true,
      data: requests
    } as ApiResponse<typeof requests>);
  } catch (error) {
    return NextResponse.json({
      success: false,
      error: 'Failed to fetch fulfillment requests'
    } 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/fulfillment'));

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

    const body = await request.json();
    const { sampleRightId, userId, userEmail, shippingAddress } = body;

    // Validate and sanitize inputs
    const sanitizedUserId = sanitizeString(userId, 100);
    const sanitizedUserEmail = sanitizeString(userEmail, 200);
    const sanitizedSampleRightId = sanitizeString(sampleRightId, 100);

    if (!validateUserId(sanitizedUserId)) {
      return NextResponse.json({
        success: false,
        error: 'Invalid user ID format'
      } as ApiResponse<null>, { status: 400 });
    }

    // Verify user owns the sample right
    const sampleRight = sampleRightDb.getById(sanitizedSampleRightId);
    if (!sampleRight || sampleRight.ownerId !== sanitizedUserId) {
      logAudit({
        action: 'FULFILLMENT_UNAUTHORIZED',
        resource: `sample/${sanitizedSampleRightId}`,
        ipAddress: identifier,
        userAgent: request.headers.get('user-agent') || 'unknown',
        success: false,
        metadata: { userId: sanitizedUserId }
      });

      return NextResponse.json({
        success: false,
        error: 'Invalid sample right or not owned by user'
      } as ApiResponse<null>, { status: 400 });
    }

    if (!sampleRight.canRedeem) {
      return NextResponse.json({
        success: false,
        error: 'Sample right cannot be redeemed at this time'
      } as ApiResponse<null>, { status: 400 });
    }

    // Sanitize shipping address
    const sanitizedAddress = {
      name: sanitizeString(shippingAddress.name || '', 200),
      addressLine1: sanitizeString(shippingAddress.addressLine1 || shippingAddress.street || '', 200),
      addressLine2: shippingAddress.addressLine2 ? sanitizeString(shippingAddress.addressLine2, 200) : undefined,
      city: sanitizeString(shippingAddress.city, 100),
      state: sanitizeString(shippingAddress.state, 50),
      zipCode: sanitizeString(shippingAddress.zipCode, 20),
      country: sanitizeString(shippingAddress.country, 100),
      phone: sanitizeString(shippingAddress.phone || '', 50)
    };

    // Check if partner available for state
    const partners = partnerDb.getByState(sanitizedAddress.state);
    if (partners.length === 0) {
      return NextResponse.json({
        success: false,
        error: `No fulfillment partner available for ${sanitizedAddress.state}. Shipping not allowed in this state.`
      } as ApiResponse<null>, { status: 400 });
    }

    // Create compliance check (simplified version)
    const complianceCheck: ComplianceCheck = {
      age21Plus: true, // Would verify with ID verification service
      stateAllowed: partners.length > 0,
      licensedDistributorAvailable: true,
      specialPermitsRequired: false,
      notes: `Partner: ${partners[0].name}`,
      checkedAt: new Date(),
      checkedBy: 'system'
    };

    const newRequest = fulfillmentDb.create({
      sampleRightId: sanitizedSampleRightId,
      sampleUnitId: sampleRight.sampleUnitId,
      userId: sanitizedUserId,
      userEmail: sanitizedUserEmail,
      shippingAddress: sanitizedAddress,
      status: 'compliance_check',
      complianceCheckResults: complianceCheck
    });

    // Update sample right status
    sampleRightDb.create({
      ...sampleRight,
      status: 'redeemed'
    });

    logAudit({
      action: 'FULFILLMENT_REQUEST',
      resource: `fulfillment/${newRequest.id}`,
      ipAddress: identifier,
      userAgent: request.headers.get('user-agent') || 'unknown',
      success: true,
      metadata: {
        fulfillmentId: newRequest.id,
        userId: sanitizedUserId,
        state: sanitizedAddress.state
      }
    });

    return NextResponse.json({
      success: true,
      data: newRequest,
      message: 'Fulfillment request created. Pending compliance approval.'
    });
  } catch (error) {
    return NextResponse.json({
      success: false,
      error: 'Failed to create fulfillment request'
    } as ApiResponse<null>, { status: 500 });
  }
}