turash/bugulma/frontend/services/discovery-api.ts

267 lines
6.7 KiB
TypeScript

/**
* 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?: any;
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<UniversalSearchResponse> {
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<SearchResponse> {
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<SearchResponse> {
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<SearchResponse> {
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<CommunityListing> {
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<CategoriesResponse> {
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();
}