← back to Handbag Auth Nextjs
src/app/api/price-alerts/route.ts
168 lines
import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { priceAlertGetSchema, priceAlertCreateSchema, priceAlertDeleteSchema, validateInput } from '@/lib/validation'
import { rateLimit, getClientIp } from '@/lib/rateLimit'
import { addSecurityHeaders } from '@/lib/securityHeaders'
export async function GET(request: NextRequest) {
// Apply rate limiting
const clientIp = getClientIp(request)
const rateLimitResult = rateLimit(clientIp)
if (!rateLimitResult.allowed) {
const response = NextResponse.json(
{ success: false, error: 'Rate limit exceeded' },
{ status: 429 }
)
addSecurityHeaders(response)
return response
}
try {
// Validate input
const searchParams = Object.fromEntries(request.nextUrl.searchParams.entries())
const validation = validateInput(priceAlertGetSchema, searchParams)
if (!validation.success) {
return NextResponse.json(
{ success: false, error: 'Invalid parameters', details: validation.errors.format() },
{ status: 400 }
)
}
const { listingId, externalId, email } = validation.data
const where: any = {}
if (listingId) where.listingId = listingId
if (externalId) where.externalId = externalId
if (email) where.userEmail = email
const alerts = await prisma.priceAlert.findMany({
where,
orderBy: { createdAt: 'desc' },
})
return NextResponse.json({
success: true,
count: alerts.length,
alerts,
})
} catch (error) {
console.error('Price alerts GET error:', error)
return NextResponse.json(
{ success: false, error: 'Failed to fetch price alerts' },
{ status: 500 }
)
}
}
export async function POST(request: NextRequest) {
// Apply rate limiting
const clientIp = getClientIp(request)
const rateLimitResult = rateLimit(clientIp, {
windowMs: 60 * 1000,
maxRequests: 20, // Limit alert creation
})
if (!rateLimitResult.allowed) {
const response = NextResponse.json(
{ success: false, error: 'Rate limit exceeded' },
{ status: 429 }
)
addSecurityHeaders(response)
return response
}
try {
const body = await request.json()
// Validate input
const validation = validateInput(priceAlertCreateSchema, body)
if (!validation.success) {
return NextResponse.json(
{ success: false, error: 'Invalid input', details: validation.errors.format() },
{ status: 400 }
)
}
const {
listingId,
externalId,
userEmail,
targetPrice,
alertType,
} = validation.data
const now = new Date().toISOString().replace('T', ' ').split('.')[0]
const alert = await prisma.priceAlert.create({
data: {
listingId,
externalId,
userEmail,
targetPrice,
alertType,
isActive: 1,
createdAt: now,
notified: 0,
},
})
return NextResponse.json({
success: true,
alert,
})
} catch (error) {
console.error('Price alert POST error:', error)
return NextResponse.json(
{ success: false, error: 'Failed to create price alert' },
{ status: 500 }
)
}
}
export async function DELETE(request: NextRequest) {
// Apply rate limiting
const clientIp = getClientIp(request)
const rateLimitResult = rateLimit(clientIp)
if (!rateLimitResult.allowed) {
const response = NextResponse.json(
{ success: false, error: 'Rate limit exceeded' },
{ status: 429 }
)
addSecurityHeaders(response)
return response
}
try {
// Validate input
const searchParams = Object.fromEntries(request.nextUrl.searchParams.entries())
const validation = validateInput(priceAlertDeleteSchema, searchParams)
if (!validation.success) {
return NextResponse.json(
{ success: false, error: 'Invalid parameters', details: validation.errors.format() },
{ status: 400 }
)
}
const { id } = validation.data
await prisma.priceAlert.delete({
where: { id },
})
return NextResponse.json({
success: true,
message: 'Price alert deleted',
})
} catch (error) {
console.error('Price alert DELETE error:', error)
return NextResponse.json(
{ success: false, error: 'Failed to delete price alert' },
{ status: 500 }
)
}
}