17 KiB
Kapsamlı Kod Analizi Raporu
Tarih: 5 Şubat 2026
Proje: Wanderlog-Style Travel Planning Application
📋 Genel Bakış
Bu rapor, mevcut kod tabanının (frontend, backend, veritabanı) kapsamlı bir analizini içermektedir. Mantık hataları, eksik fonksiyonlar ve kullanıcı deneyimini olumsuz etkileyebilecek durumlar tespit edilmiştir.
✅ Güçlü Yönler
1. İyi Yapılandırılmış Mimari
- ✅ React + TypeScript + shadcn/ui modern stack
- ✅ Supabase backend ile temiz ayrım
- ✅ Edge Functions ile AI entegrasyonu
- ✅ Context API ile state management
- ✅ Modüler component yapısı
2. Kapsamlı Özellikler
- ✅ Trip planning ve management
- ✅ Provider/Lead sistemi
- ✅ Admin dashboard
- ✅ Public trip sharing
- ✅ Daily tours sistemi
- ✅ Google Maps entegrasyonu
- ✅ AI-powered suggestions
3. Güvenlik
- ✅ RLS policies mevcut
- ✅ Edge Functions ile API key koruması
- ✅ Anonymous trip access düzeltilmiş (migration 00040)
🚨 Kritik Sorunlar
1. Race Condition - Balloon Constraint Violation
Sorun: Hem TripPlanner.tsx hem de api.ts'de balon ekleme sırasında race condition var.
Etkilenen Dosyalar:
src/pages/TripPlanner.tsx:599-607(handleAddPlaceToDay)src/db/api.ts:413-432(generateAutoSeedItinerary)
Kod (TripPlanner.tsx):
// ❌ YANLIŞ SIRA: Önce place ekle, sonra trip güncelle
await tripPlacesApi.addToDay(placeData); // 1. Place eklendi
// Eğer balon eklendiyse, trip'i güncelle
if (isBalloon && trip?.id) {
await tripsApi.update(trip.id, { // 2. Trip güncellendi
has_balloon: true,
balloon_day_id: activeDayId,
});
}
Sorun: generateItinerary fonksiyonunda balon ekleme sırasında race condition riski var.
Kod: src/db/api.ts:413-432
if (shouldAddBalloon(dayIndex, existingDays.length, interests, balloonAdded)) {
const balloonPlace = scoredPlaces.find((p: any) => p.type === BALLOON_PLACE_TYPE);
if (balloonPlace) {
// ... balon ekleme
balloonAdded = true;
// ⚠️ RACE CONDITION: Bu update başarısız olursa ne olur?
await supabase
.from('trips')
.update({ has_balloon: true, balloon_day_id: day.id })
.eq('id', tripId);
}
}
Risk:
- Eğer trip update başarısız olursa,
trip_places'e balon eklenir amatrip.has_balloonfalse kalır - Kullanıcı ikinci bir balon ekleyebilir (constraint ihlali)
Çözüm:
// Trip update'i ÖNCE yap, başarılı olursa place ekle
const { error: tripUpdateError } = await supabase
.from('trips')
.update({ has_balloon: true, balloon_day_id: day.id })
.eq('id', tripId);
if (tripUpdateError) {
console.error('Trip update hatası:', tripUpdateError);
continue; // Bu günü atla, balon ekleme
}
// Şimdi güvenle place ekle
const { error: placeError } = await supabase
.from('trip_places')
.insert([{
trip_day_id: day.id,
place_id: balloonPlace.id,
order_index: 0,
duration: getTypicalDuration(BALLOON_PLACE_TYPE),
}]);
Çözüm:
// ✅ DOĞRU SIRA: Önce trip güncelle, başarılı olursa place ekle
// 1. Trip'i güncelle (constraint'i ayarla)
if (isBalloon && trip?.id) {
const { error: tripUpdateError } = await tripsApi.update(trip.id, {
has_balloon: true,
balloon_day_id: activeDayId,
});
if (tripUpdateError) {
toast({
title: 'Hata',
description: 'Balon uçuşu eklenirken bir hata oluştu.',
variant: 'destructive',
});
return; // Hata varsa place ekleme
}
}
// 2. Place'i ekle (trip constraint'i zaten ayarlandı)
await tripPlacesApi.addToDay(placeData);
Aynı sorun api.ts'de de var:
// src/db/api.ts:426-432
// ❌ YANLIŞ: Place ekle, sonra trip güncelle
await supabase.from('trip_places').insert([...]);
await supabase
.from('trips')
.update({ has_balloon: true, balloon_day_id: day.id })
.eq('id', tripId);
// ✅ DOĞRU: Trip güncelle, sonra place ekle
const { error: tripError } = await supabase
.from('trips')
.update({ has_balloon: true, balloon_day_id: day.id })
.eq('id', tripId);
if (!tripError) {
await supabase.from('trip_places').insert([...]);
}
Öncelik: 🔴 Yüksek
2. Frontend - Missing Error Boundary
Sorun: Uygulama genelinde error boundary yok. Bir component crash olursa tüm uygulama çöker.
Risk:
- Kullanıcı white screen görür
- Hata mesajı gösterilmez
- Debugging zorlaşır
Çözüm:
// src/components/ErrorBoundary.tsx
import React from 'react';
import { AlertCircle } from 'lucide-react';
import { Button } from '@/components/ui/button';
class ErrorBoundary extends React.Component<
{ children: React.ReactNode },
{ hasError: boolean; error: Error | null }
> {
constructor(props: any) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('ErrorBoundary caught:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="flex flex-col items-center justify-center min-h-screen p-4">
<AlertCircle className="w-16 h-16 text-destructive mb-4" />
<h1 className="text-2xl font-bold mb-2">Bir şeyler yanlış gitti</h1>
<p className="text-muted-foreground mb-4">
{this.state.error?.message || 'Bilinmeyen hata'}
</p>
<Button onClick={() => window.location.reload()}>
Sayfayı Yenile
</Button>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
App.tsx'e ekle:
import ErrorBoundary from '@/components/ErrorBoundary';
function App() {
return (
<ErrorBoundary>
{/* ... mevcut kod */}
</ErrorBoundary>
);
}
Öncelik: 🟡 Orta
3. UX - No Loading State for AI Suggestions
Sorun: TripPlanner.tsx'de AI suggestions çağrılırken loading state yok.
Risk:
- Kullanıcı butona tıkladıktan sonra ne olduğunu bilmiyor
- Birden fazla tıklama yapabilir (duplicate requests)
Kod: src/pages/TripPlanner.tsx (AI suggestion handler)
Çözüm:
const [isLoadingAI, setIsLoadingAI] = useState(false);
const handleGetAISuggestions = async () => {
if (isLoadingAI) return; // Prevent duplicate calls
setIsLoadingAI(true);
try {
// ... AI call
} catch (error) {
// ... error handling
} finally {
setIsLoadingAI(false);
}
};
// Button'da:
<Button
onClick={handleGetAISuggestions}
disabled={isLoadingAI}
>
{isLoadingAI ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Öneriler yükleniyor...
</>
) : (
<>
<Sparkles className="mr-2 h-4 w-4" />
AI Önerileri Al
</>
)}
</Button>
Öncelik: 🟡 Orta
4. Edge Function - No Timeout Handling
Sorun: Edge Functions'da AI API çağrıları için timeout yok.
Risk:
- AI API yanıt vermezse function sonsuza kadar bekler
- Kullanıcı stuck kalır
Kod: supabase/functions/suggest-places/index.ts:155-172
Çözüm:
// Timeout wrapper
const fetchWithTimeout = async (url: string, options: any, timeout = 30000) => {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
});
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error('AI API timeout (30s)');
}
throw error;
}
};
// Kullanım:
const aiResponse = await fetchWithTimeout(
'https://app-9fepb4t1z6dc-api-zYm4ze3j7XvL.gateway.appmedo.com/...',
{
method: 'POST',
headers: { ... },
body: JSON.stringify({ ... }),
},
30000 // 30 saniye timeout
);
Öncelik: 🟡 Orta
⚠️ Orta Öncelikli Sorunlar
5. Missing Validation - Place Duration
Sorun: trip_places.duration string olarak saklanıyor ("2 saat", "3 hours", vb.) ama validation yok.
Risk:
- Tutarsız format ("2 saat", "120 dakika", "2h")
- Zaman hesaplamaları hatalı olabilir
Çözüm:
// src/lib/duration-utils.ts
export const parseDuration = (duration: string): number => {
// "2 saat" -> 120 (dakika)
// "3 hours" -> 180
// "90 dakika" -> 90
const match = duration.match(/(\d+)\s*(saat|hour|dakika|minute|min)/i);
if (!match) return 120; // default 2 saat
const value = parseInt(match[1]);
const unit = match[2].toLowerCase();
if (unit.includes('saat') || unit.includes('hour')) {
return value * 60;
}
return value;
};
export const formatDuration = (minutes: number): string => {
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
if (hours === 0) return `${mins} dakika`;
if (mins === 0) return `${hours} saat`;
return `${hours} saat ${mins} dakika`;
};
// Veritabanında duration_minutes column ekle
// Migration:
ALTER TABLE trip_places ADD COLUMN duration_minutes INTEGER;
UPDATE trip_places SET duration_minutes = 120 WHERE duration_minutes IS NULL;
Öncelik: 🟡 Orta
6. UX - No Undo/Redo Implementation
Sorun: TripPlanner.tsx'de Undo/Redo butonları var ama fonksiyon yok.
Kod: src/pages/TripPlanner.tsx:15-16
import { Undo2, Redo2 } from 'lucide-react';
// ... ama handleUndo/handleRedo yok
Risk:
- Kullanıcı yanlışlıkla yer silerse geri alamaz
- Kötü UX
Çözüm:
// History stack
const [history, setHistory] = useState<any[]>([]);
const [historyIndex, setHistoryIndex] = useState(-1);
// Save state to history
const saveToHistory = (state: any) => {
const newHistory = history.slice(0, historyIndex + 1);
newHistory.push(state);
setHistory(newHistory);
setHistoryIndex(newHistory.length - 1);
};
// Undo
const handleUndo = () => {
if (historyIndex > 0) {
setHistoryIndex(historyIndex - 1);
// Restore state from history[historyIndex - 1]
}
};
// Redo
const handleRedo = () => {
if (historyIndex < history.length - 1) {
setHistoryIndex(historyIndex + 1);
// Restore state from history[historyIndex + 1]
}
};
Öncelik: 🟢 Düşük (Nice to have)
7. Performance - No Pagination in Places List
Sorun: placesApi.getAll() tüm yerleri getiriyor, pagination yok.
Kod: src/db/api.ts:6-14
async getAll() {
const { data, error } = await supabase
.from('places')
.select('*')
.order('created_at', { ascending: false });
// ⚠️ Limit yok, 1000+ yer olursa yavaşlar
}
Risk:
- Yavaş yükleme
- Gereksiz network trafiği
Çözüm:
async getAll(page = 1, limit = 50) {
const from = (page - 1) * limit;
const to = from + limit - 1;
const { data, error, count } = await supabase
.from('places')
.select('*', { count: 'exact' })
.order('created_at', { ascending: false })
.range(from, to);
if (error) throw error;
return {
places: Array.isArray(data) ? data : [],
total: count || 0,
page,
totalPages: Math.ceil((count || 0) / limit),
};
}
Öncelik: 🟡 Orta
8. Security - No Rate Limiting on Edge Functions
Sorun: Edge Functions'da rate limiting yok.
Risk:
- Abuse edilebilir (spam requests)
- AI API maliyeti artar
Çözüm:
// supabase/functions/_shared/rate-limit.ts
const rateLimitMap = new Map<string, { count: number; resetAt: number }>();
export const checkRateLimit = (userId: string, maxRequests = 10, windowMs = 60000): boolean => {
const now = Date.now();
const userLimit = rateLimitMap.get(userId);
if (!userLimit || now > userLimit.resetAt) {
rateLimitMap.set(userId, { count: 1, resetAt: now + windowMs });
return true;
}
if (userLimit.count >= maxRequests) {
return false; // Rate limit exceeded
}
userLimit.count++;
return true;
};
// Edge Function'da kullan:
const userId = req.headers.get('x-user-id') || 'anonymous';
if (!checkRateLimit(userId, 10, 60000)) {
return new Response(
JSON.stringify({ error: 'Rate limit exceeded. Try again later.' }),
{ status: 429, headers: corsHeaders }
);
}
Öncelik: 🟡 Orta
9. UX - No Offline Support
Sorun: Uygulama offline çalışmıyor.
Risk:
- Kullanıcı internet bağlantısı kesilirse hiçbir şey yapamaz
- Kötü UX (özellikle seyahat sırasında)
Çözüm:
// Service Worker + IndexedDB
// src/service-worker.ts
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('trip-planner-v1').then((cache) => {
return cache.addAll([
'/',
'/planner',
'/journal',
// ... static assets
]);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
// IndexedDB for offline data
import { openDB } from 'idb';
const db = await openDB('trip-planner-db', 1, {
upgrade(db) {
db.createObjectStore('trips', { keyPath: 'id' });
db.createObjectStore('places', { keyPath: 'id' });
},
});
// Sync when online
window.addEventListener('online', () => {
// Sync offline changes to Supabase
});
Öncelik: 🟢 Düşük (Future enhancement)
🐛 Küçük Hatalar
10. Typo in Console Log
Kod: src/db/api.ts:323
console.log('generateItinerary çağrıldı:', { tripId, interests, startDate, endDate, destination, mode });
Sorun: Production'da console.log olmamalı.
Çözüm: Tüm console.log'ları kaldır veya debug mode'da çalıştır.
11. Unused Import
Kod: src/pages/TripPlanner.tsx:70
import { sampleTrips } from '@/data/sampleData';
// ⚠️ Kullanılmıyor
Çözüm: Kaldır.
12. Magic Numbers
Kod: src/db/api.ts:441-444
const targetPlaces = Math.min(
MAX_PLACES_PER_DAY - dayPlaces.length,
Math.max(MIN_PLACES_PER_DAY - dayPlaces.length, 0)
);
Sorun: MAX_PLACES_PER_DAY ve MIN_PLACES_PER_DAY config'den geliyor ama değerleri belli değil.
Çözüm: Config dosyasında açıklama ekle.
📊 Eksik Özellikler
13. No Trip Collaboration
Durum: Kullanıcılar trip'i paylaşabilir ama birlikte düzenleyemez.
Öneri: Real-time collaboration ekle (Supabase Realtime kullanarak).
14. No Budget Tracking
Durum: Kullanıcılar bütçe takibi yapamıyor.
Öneri:
tripstablosunabudgetvespentcolumn'ları ekle- Her place için tahmini maliyet ekle
- Budget progress bar göster
15. No Weather Integration
Durum: Hava durumu bilgisi yok.
Öneri: Weather API entegrasyonu (OpenWeatherMap, WeatherAPI).
16. No Notification System
Durum: Kullanıcılar bildirim almıyor (trip reminder, provider lead, vb.).
Öneri:
- Email notifications (Supabase Auth)
- Push notifications (Web Push API)
- In-app notifications
🎯 Öncelik Sıralaması
🔴 Kritik (Hemen Düzelt)
- Race Condition in Balloon Constraint (#1)
🟡 Orta (Yakında Düzelt)
- Missing Error Boundary (#2)
- No Loading State for AI (#3)
- No Timeout in Edge Functions (#4)
- Missing Duration Validation (#5)
- No Pagination in Places (#7)
- No Rate Limiting (#8)
🟢 Düşük (Nice to Have)
- No Undo/Redo (#6)
- No Offline Support (#9)
- Console Logs (#10)
- Unused Imports (#11)
🛠️ Önerilen Düzeltme Planı
Faz 1: Kritik Düzeltmeler (1 gün)
- Race condition düzelt - TripPlanner.tsx (#1)
- Race condition düzelt - api.ts generateItinerary (#1)
- Error boundary ekle (#2)
Faz 2: UX İyileştirmeleri (2-3 gün)
- AI loading states ekle (#3)
- Edge function timeout ekle (#4)
- Duration validation ekle (#5)
- Pagination ekle (#7)
Faz 3: Güvenlik ve Performance (1 hafta)
- Rate limiting ekle (#8)
- Console logs temizle (#10)
- Unused imports temizle (#11)
Faz 4: Yeni Özellikler (Gelecek)
- Undo/Redo (#6)
- Offline support (#9)
- Collaboration (#13)
- Budget tracking (#14)
- Weather integration (#15)
- Notifications (#16)
📝 Sonuç
Genel Durum: 🟢 İyi
Uygulama genel olarak iyi yapılandırılmış ve çalışır durumda. Ancak:
- 1 kritik sorun var (race condition - 2 yerde)
- 7 orta öncelikli iyileştirme gerekiyor
- 3 küçük hata var
- 4 eksik özellik eklenebilir
Not: Database schema'da orphaned records sorunu YOK - foreign key constraints zaten ON DELETE CASCADE olarak ayarlanmış ✅
Tavsiye: Önce kritik race condition sorununu düzelt, sonra UX iyileştirmelerine geç.
🤝 Sonraki Adımlar
- Bu raporu incele
- Hangi sorunları düzeltmek istediğine karar ver
- Öncelik sırasına göre düzeltmelere başla
- Her düzeltme sonrası test et
Soru: Hangi sorunları önce düzeltmek istersin? Hepsini mi yoksa belirli birkaç tanesini mi?