200 lines
6.7 KiB
TypeScript
200 lines
6.7 KiB
TypeScript
import { createClient } from 'jsr:@supabase/supabase-js@2';
|
||
import { requireAuth, checkRateLimit } from '../_shared/auth.ts';
|
||
|
||
const corsHeaders = {
|
||
'Access-Control-Allow-Origin': '*',
|
||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
||
};
|
||
|
||
interface SearchToursRequest {
|
||
destination: string;
|
||
recommended_type: string;
|
||
place_types?: string[];
|
||
travelers?: number;
|
||
daily_tour_slug?: string; // Preferred tour slug from AI analysis
|
||
}
|
||
|
||
Deno.serve(async (req) => {
|
||
if (req.method === 'OPTIONS') {
|
||
return new Response(null, { headers: corsHeaders });
|
||
}
|
||
|
||
try {
|
||
// Auth check
|
||
const auth = await requireAuth(req);
|
||
if (auth.error) return auth.error;
|
||
|
||
// Rate limit check (20 AI suggestions per hour)
|
||
const supabaseService = createClient(
|
||
Deno.env.get('SUPABASE_URL')!,
|
||
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
|
||
);
|
||
const rateLimitResponse = await checkRateLimit(auth.userId, 'ai_suggest', supabaseService);
|
||
if (rateLimitResponse) return rateLimitResponse;
|
||
|
||
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
|
||
const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
|
||
const supabase = createClient(supabaseUrl, supabaseKey);
|
||
|
||
const body: SearchToursRequest = await req.json();
|
||
const { destination, recommended_type, place_types = [], travelers = 2, daily_tour_slug } = body;
|
||
|
||
console.log('🔍 Search tours request:', { destination, recommended_type, daily_tour_slug, travelers });
|
||
|
||
// GOLDEN RULE: Match providers by daily_tour_slug in services array
|
||
// Query provider_services where daily_tour_services contains the slug
|
||
let query = supabase
|
||
.from('provider_services')
|
||
.select(`
|
||
*,
|
||
provider:profiles!provider_services_provider_id_fkey (
|
||
id,
|
||
full_name,
|
||
role,
|
||
is_active
|
||
)
|
||
`)
|
||
.eq('is_active', true);
|
||
|
||
// Filter by daily_tour_slug if provided
|
||
// BUT: If no providers have this slug, fall back to showing all providers
|
||
if (daily_tour_slug) {
|
||
console.log(' Filtering by daily_tour_slug:', daily_tour_slug);
|
||
query = query.contains('daily_tour_services', [daily_tour_slug]);
|
||
}
|
||
|
||
// Order by rating and lead_price
|
||
query = query
|
||
.order('rating', { ascending: false })
|
||
.order('lead_price', { ascending: true })
|
||
.limit(10);
|
||
|
||
const { data: providerServices, error } = await query;
|
||
|
||
if (error) {
|
||
console.error('❌ Database query error:', error);
|
||
throw error;
|
||
}
|
||
|
||
console.log(' Found provider services:', providerServices?.length || 0);
|
||
|
||
// FALLBACK: If no providers found with specific slug, try without filter
|
||
let finalProviderServices = providerServices;
|
||
if ((!providerServices || providerServices.length === 0) && daily_tour_slug) {
|
||
console.log(' No providers found with slug, trying without filter...');
|
||
|
||
const fallbackQuery = supabase
|
||
.from('provider_services')
|
||
.select(`
|
||
*,
|
||
provider:profiles!provider_services_provider_id_fkey (
|
||
id,
|
||
full_name,
|
||
role,
|
||
is_active
|
||
)
|
||
`)
|
||
.eq('is_active', true)
|
||
.order('rating', { ascending: false })
|
||
.order('lead_price', { ascending: true })
|
||
.limit(10);
|
||
|
||
const { data: fallbackData, error: fallbackError } = await fallbackQuery;
|
||
|
||
if (!fallbackError && fallbackData) {
|
||
finalProviderServices = fallbackData;
|
||
console.log(' Fallback found:', fallbackData.length, 'providers');
|
||
}
|
||
}
|
||
|
||
// Transform to tour-like format for backward compatibility
|
||
const tours = (finalProviderServices || []).map((ps: any) => {
|
||
const provider = ps.provider;
|
||
|
||
return {
|
||
id: ps.id,
|
||
provider_id: ps.provider_id,
|
||
name: `${daily_tour_slug || 'Özel Tur'} - ${ps.business_name || provider?.full_name || 'Yerel Rehber'}`,
|
||
destination: destination,
|
||
type: recommended_type,
|
||
description: ps.description || `${ps.business_name || provider?.full_name} ile profesyonel tur hizmeti`,
|
||
duration_hours: ps.duration_hours || 6,
|
||
price_from: ps.price_per_person || 0,
|
||
price_currency: ps.currency || 'TRY',
|
||
max_participants: ps.max_group_size || 15,
|
||
rating: parseFloat(ps.rating) || 0,
|
||
review_count: ps.total_reviews || 0,
|
||
covers: ps.daily_tour_services || [],
|
||
includes: ps.includes || [],
|
||
excludes: ps.excludes || [],
|
||
commission_rate: 0.15, // Default 15% commission
|
||
lead_price: ps.lead_price || 20,
|
||
provider_name: ps.business_name || provider?.full_name || 'Yerel Rehber',
|
||
provider_contact: ps.contact_phone || ps.contact_email || '',
|
||
is_active: ps.is_active && provider?.is_active,
|
||
daily_tour_slug: daily_tour_slug,
|
||
languages: ps.languages || ['Türkçe'],
|
||
vehicle_types: ps.vehicle_types || [],
|
||
created_at: ps.created_at || new Date().toISOString(),
|
||
updated_at: ps.updated_at || new Date().toISOString()
|
||
};
|
||
});
|
||
|
||
// Filter only active providers
|
||
const activeTours = tours.filter((t: any) => t.is_active);
|
||
|
||
// Score tours based on relevance
|
||
const scoredTours = activeTours.map((tour: any) => {
|
||
let score = 0;
|
||
|
||
// Rating weight (highest priority)
|
||
score += (tour.rating || 0) * 20;
|
||
|
||
// Lead price weight (lower is better)
|
||
score += Math.max(0, 100 - (tour.lead_price || 20));
|
||
|
||
// Coverage match weight
|
||
if (daily_tour_slug && tour.covers.includes(daily_tour_slug)) {
|
||
score += 50; // Exact match bonus
|
||
}
|
||
|
||
// Participant capacity weight
|
||
if (tour.max_participants && tour.max_participants >= travelers) {
|
||
score += 10;
|
||
}
|
||
|
||
return { ...tour, relevance_score: score };
|
||
});
|
||
|
||
// Sort by relevance score
|
||
scoredTours.sort((a: any, b: any) => b.relevance_score - a.relevance_score);
|
||
|
||
// FALLBACK: If no providers found, suggest private_guide
|
||
if (scoredTours.length === 0) {
|
||
return new Response(
|
||
JSON.stringify({
|
||
tours: [],
|
||
message: 'Uygun tur sağlayıcısı bulunamadı. Özel rehber öneriyoruz.',
|
||
fallback_suggestion: 'private_guide'
|
||
}),
|
||
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||
);
|
||
}
|
||
|
||
return new Response(
|
||
JSON.stringify({ tours: scoredTours }),
|
||
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||
);
|
||
|
||
} catch (error) {
|
||
console.error('Error in search-tours:', error);
|
||
return new Response(
|
||
JSON.stringify({ error: error.message, tours: [] }),
|
||
{
|
||
status: 500,
|
||
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
||
}
|
||
);
|
||
}
|
||
});
|