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