Files
padmaja/app/api/products/route.ts
2026-01-17 14:17:42 +05:30

214 lines
6.3 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { auth } from '@/auth'
import { prisma } from '@/lib/prisma'
import { productSchema } from '@/lib/validations/product'
import { DatabaseOptimizer } from '@/lib/database-optimizer'
export async function GET(request: NextRequest) {
const startTime = Date.now()
try {
const { searchParams } = new URL(request.url)
const category = searchParams.get('category')
const search = searchParams.get('search')
const page = parseInt(searchParams.get('page') || '1')
const limit = parseInt(searchParams.get('limit') || '12')
const isAdmin = searchParams.get('admin') === 'true'
const skip = (page - 1) * limit
// Create cache key for this specific query
const cacheKey = `products:${category || 'all'}:${search || 'none'}:${page}:${limit}:${isAdmin}`
// Try to get from cache first
const cached = await DatabaseOptimizer.getCachedData(cacheKey)
if (cached && !isAdmin) { // Don't cache admin requests
return NextResponse.json({
...cached,
_performance: {
responseTime: Date.now() - startTime,
cached: true
}
})
}
const where: any = {}
// Only filter by isActive if not admin request
if (!isAdmin) {
where.isActive = true
// Add stock filter for better SEO (only show available products)
where.stock = { gt: 0 }
}
if (category) {
where.categoryId = category
}
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
{ description: { contains: search, mode: 'insensitive' } },
{
category: {
name: { contains: search, mode: 'insensitive' }
}
},
]
}
// Optimize queries with parallel execution and selective field inclusion
const [products, total, categoryStats] = await Promise.all([
// Optimized products query with caching
DatabaseOptimizer.executeOptimizedQuery(
`products_list_${JSON.stringify(where)}_${skip}_${limit}`,
() => prisma.product.findMany({
where,
include: {
category: {
select: {
id: true,
name: true,
}
},
reviews: {
select: {
rating: true,
createdAt: true,
},
take: 5, // Limit reviews for performance
orderBy: {
createdAt: 'desc'
}
},
_count: {
select: {
reviews: true,
orderItems: true, // For popularity metrics
}
}
},
skip,
take: limit,
orderBy: [
{ isActive: 'desc' }, // Active products first
{ stock: 'desc' }, // In-stock products next
{ createdAt: 'desc' }, // Then by creation date
],
}),
300 // Cache for 5 minutes
),
// Optimized count query
DatabaseOptimizer.executeOptimizedQuery(
`products_count_${JSON.stringify(where)}`,
() => prisma.product.count({ where }),
600 // Cache for 10 minutes
),
// Optimized category stats
DatabaseOptimizer.executeOptimizedQuery(
`category_stats_${category || 'all'}`,
() => prisma.product.groupBy({
by: ['categoryId'],
where: { isActive: true, stock: { gt: 0 } },
_count: {
categoryId: true,
},
orderBy: {
_count: {
categoryId: 'desc'
}
}
}),
900 // Cache for 15 minutes
)
])
// Calculate advanced metrics for SEO and performance
const productsWithMetrics = products.map(product => ({
...product,
averageRating: product.reviews.length > 0
? Math.round((product.reviews.reduce((acc, review) => acc + review.rating, 0) / product.reviews.length) * 10) / 10
: null,
reviewCount: product._count.reviews,
popularityScore: product._count.orderItems,
hasRecentReviews: product.reviews.some(review =>
new Date(review.createdAt) > new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
),
isPopular: product._count.orderItems > 10,
inStock: product.stock > 0,
lowStock: product.stock > 0 && product.stock <= 10,
// Remove internal data from response
reviews: undefined,
_count: undefined,
}))
const responseData = {
products: productsWithMetrics,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit),
},
seo: {
totalProducts: total,
categoryDistribution: categoryStats,
hasProducts: total > 0
}
}
// Cache the response for non-admin requests
if (!isAdmin) {
await DatabaseOptimizer.setCachedData(cacheKey, responseData, 300)
}
return NextResponse.json({
...responseData,
_performance: {
responseTime: Date.now() - startTime,
cached: false
}
})
} catch (error) {
console.error('Products API error:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}
export async function POST(request: NextRequest) {
try {
const session = await auth()
if (!session?.user || session.user.role !== 'ADMIN') {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
const validatedData = productSchema.parse(body)
// Generate SKU and slug if not provided
const sku = validatedData.sku || `SKU-${Date.now()}`
const slug = validatedData.slug || validatedData.name.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '')
const product = await prisma.product.create({
data: {
...validatedData,
sku,
slug
},
include: {
category: true,
},
})
// Invalidate product caches after creation
DatabaseOptimizer.invalidateCache('products')
DatabaseOptimizer.invalidateCache('category_stats')
return NextResponse.json(product, { status: 201 })
} catch (error) {
console.error('Create product error:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}