/** * Discovery API Service * Handles product, service, and community listing discovery/search */ const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api/v1'; export interface SearchQuery { query?: string; categories?: string[]; latitude?: number; longitude?: number; radius_km?: number; max_price?: number; min_price?: number; availability_status?: string; tags?: string[]; limit?: number; offset?: number; } export interface DiscoveryMatch { product?: Product; service?: Service; community_listing?: CommunityListing; match_type: 'product' | 'service' | 'community'; relevance_score: number; text_match_score: number; category_match_score: number; distance_score: number; price_match_score: number; availability_score: number; distance_km: number; organization?: Organization; site?: Site; } export interface Product { ID: string; Name: string; Category: string; Description: string; UnitPrice: number; MOQ: number; AvailabilityStatus: string; Images: string[]; Tags: string[]; OrganizationID: string; SiteID?: string; Location?: { Latitude: number; Longitude: number; }; } export interface Service { ID: string; Type: string; Domain: string; Description: string; HourlyRate: number; ServiceAreaKm: number; AvailabilityStatus: string; AvailabilitySchedule?: { [day: string]: { startTime: string; endTime: string; isAvailable: boolean; }; }; Tags: string[]; OrganizationID: string; SiteID?: string; ServiceLocation?: { Latitude: number; Longitude: number; }; } export interface CommunityListing { id: string; user_id: string; title: string; description: string; listing_type: 'product' | 'service' | 'tool' | 'skill' | 'need'; category: string; price?: number; price_type?: 'free' | 'sale' | 'rent' | 'trade' | 'borrow'; availability_status: string; location?: { latitude: number; longitude: number; }; images: string[]; tags: string[]; } export interface Organization { id: string; name: string; } export interface Site { id: string; name: string; latitude: number; longitude: number; } export interface UniversalSearchResponse { query: SearchQuery; product_matches: DiscoveryMatch[]; service_matches: DiscoveryMatch[]; community_matches: DiscoveryMatch[]; total: number; } export interface SearchResponse { matches: DiscoveryMatch[]; total: number; } /** * Build query string from SearchQuery object */ function buildQueryString(params: SearchQuery): string { const searchParams = new URLSearchParams(); if (params.query) searchParams.append('query', params.query); if (params.categories) { params.categories.forEach((cat) => searchParams.append('categories', cat)); } if (params.latitude !== undefined) searchParams.append('latitude', params.latitude.toString()); if (params.longitude !== undefined) searchParams.append('longitude', params.longitude.toString()); if (params.radius_km !== undefined) searchParams.append('radius_km', params.radius_km.toString()); if (params.max_price !== undefined) searchParams.append('max_price', params.max_price.toString()); if (params.min_price !== undefined) searchParams.append('min_price', params.min_price.toString()); if (params.availability_status) searchParams.append('availability_status', params.availability_status); if (params.tags) { params.tags.forEach((tag) => searchParams.append('tags', tag)); } if (params.limit !== undefined) searchParams.append('limit', params.limit.toString()); if (params.offset !== undefined) searchParams.append('offset', params.offset.toString()); return searchParams.toString(); } /** * Universal search across products, services, and community listings */ export async function universalSearch(query: SearchQuery): Promise { const queryString = buildQueryString(query); const response = await fetch(`${API_BASE_URL}/discovery/search?${queryString}`); if (!response.ok) { throw new Error(`Search failed: ${response.statusText}`); } return response.json(); } /** * Search products */ export async function searchProducts(query: SearchQuery): Promise { const queryString = buildQueryString(query); const response = await fetch(`${API_BASE_URL}/discovery/products?${queryString}`); if (!response.ok) { throw new Error(`Product search failed: ${response.statusText}`); } return response.json(); } /** * Search services */ export async function searchServices(query: SearchQuery): Promise { const queryString = buildQueryString(query); const response = await fetch(`${API_BASE_URL}/discovery/services?${queryString}`); if (!response.ok) { throw new Error(`Service search failed: ${response.statusText}`); } return response.json(); } /** * Search community listings */ export async function searchCommunity(query: SearchQuery): Promise { const queryString = buildQueryString(query); const response = await fetch(`${API_BASE_URL}/discovery/community?${queryString}`); if (!response.ok) { throw new Error(`Community search failed: ${response.statusText}`); } return response.json(); } /** * Create community listing request/response types */ export interface CreateCommunityListingRequest { title: string; description?: string; listing_type: 'product' | 'service' | 'tool' | 'skill' | 'need'; category: string; subcategory?: string; condition?: 'new' | 'like_new' | 'good' | 'fair' | 'needs_repair'; price?: number; price_type?: 'free' | 'sale' | 'rent' | 'trade' | 'borrow'; quantity?: number; service_type?: string; rate?: number; rate_type?: string; latitude?: number; longitude?: number; pickup_available?: boolean; delivery_available?: boolean; delivery_radius_km?: number; images?: string[]; tags?: string[]; } /** * Create a community listing */ export async function createCommunityListing( listing: CreateCommunityListingRequest ): Promise { const response = await fetch(`${API_BASE_URL}/discovery/community`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', // Include auth cookies body: JSON.stringify(listing), }); if (!response.ok) { throw new Error(`Failed to create community listing: ${response.statusText}`); } return response.json(); } /** * Get available categories for discovery search */ export interface CategoriesResponse { products: string[]; services: string[]; community: string[]; } export async function getCategories(): Promise { const response = await fetch(`${API_BASE_URL}/discovery/categories`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); if (!response.ok) { throw new Error(`Failed to fetch categories: ${response.statusText}`); } return response.json(); }