Add comprehensive plant trading marketplace with: - Prisma schema with marketplace models (Listing, Offer, SellerProfile, WishlistItem) - Service layer for listings, offers, search, and matching - API endpoints for CRUD operations, search, and recommendations - Marketplace pages: home, listing detail, create, my-listings, my-offers - Reusable UI components: ListingCard, ListingGrid, OfferForm, SearchFilters, etc. Features: - Browse and search listings by category, price, tags - Create and manage listings (draft, active, sold, cancelled) - Make and manage offers on listings - Seller and buyer views with statistics - Featured and recommended listings - In-memory store (ready for database migration via Agent 2)
102 lines
2.5 KiB
TypeScript
102 lines
2.5 KiB
TypeScript
// API: Marketplace Search
|
|
// GET /api/marketplace/search - Search listings with filters
|
|
|
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
import { searchService } from '@/lib/marketplace';
|
|
import type { SearchFilters, ListingCategory } from '@/lib/marketplace/types';
|
|
|
|
export default async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse
|
|
) {
|
|
if (req.method !== 'GET') {
|
|
res.setHeader('Allow', ['GET']);
|
|
return res.status(405).json({ error: `Method ${req.method} Not Allowed` });
|
|
}
|
|
|
|
try {
|
|
const {
|
|
q,
|
|
category,
|
|
minPrice,
|
|
maxPrice,
|
|
lat,
|
|
lng,
|
|
radius,
|
|
tags,
|
|
sort,
|
|
page,
|
|
limit,
|
|
} = req.query;
|
|
|
|
const filters: SearchFilters = {};
|
|
|
|
// Text search
|
|
if (q && typeof q === 'string') {
|
|
filters.query = q;
|
|
}
|
|
|
|
// Category
|
|
if (category && typeof category === 'string') {
|
|
filters.category = category as ListingCategory;
|
|
}
|
|
|
|
// Price range
|
|
if (minPrice && typeof minPrice === 'string') {
|
|
filters.minPrice = parseFloat(minPrice);
|
|
}
|
|
if (maxPrice && typeof maxPrice === 'string') {
|
|
filters.maxPrice = parseFloat(maxPrice);
|
|
}
|
|
|
|
// Location
|
|
if (lat && lng && radius) {
|
|
filters.location = {
|
|
lat: parseFloat(lat as string),
|
|
lng: parseFloat(lng as string),
|
|
radiusKm: parseFloat(radius as string),
|
|
};
|
|
}
|
|
|
|
// Tags
|
|
if (tags) {
|
|
const tagArray = Array.isArray(tags) ? tags : (tags as string).split(',');
|
|
filters.tags = tagArray.filter((t): t is string => typeof t === 'string');
|
|
}
|
|
|
|
// Sorting
|
|
if (sort && typeof sort === 'string') {
|
|
filters.sortBy = sort as SearchFilters['sortBy'];
|
|
}
|
|
|
|
// Pagination
|
|
if (page && typeof page === 'string') {
|
|
filters.page = parseInt(page, 10);
|
|
}
|
|
if (limit && typeof limit === 'string') {
|
|
filters.limit = Math.min(parseInt(limit, 10), 100); // Max 100 per page
|
|
}
|
|
|
|
const results = await searchService.searchListings(filters);
|
|
|
|
// Get additional data for the response
|
|
const [popularTags, stats] = await Promise.all([
|
|
searchService.getPopularTags(10),
|
|
searchService.getMarketplaceStats(),
|
|
]);
|
|
|
|
return res.status(200).json({
|
|
...results,
|
|
meta: {
|
|
popularTags,
|
|
totalActive: stats.activeListings,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error('Marketplace search API error:', error);
|
|
return res.status(500).json({
|
|
error: 'Internal server error',
|
|
message: error instanceof Error ? error.message : 'Unknown error',
|
|
});
|
|
}
|
|
}
|