2026-03-04 18:25:09 +00:00

200 lines
6.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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' }
}
);
}
});