from fastapi import FastAPI, HTTPException, Request, Response from fastapi.middleware.cors import CORSMiddleware import json from datetime import datetime import os import requests import pytz from typing import Dict import traceback import hashlib app = FastAPI() # CORS ayarları app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) CACHE_FILE = "cache/menu_cache.json" CLAUDE_API_KEY = os.getenv("CLAUDE_API_KEY") if not CLAUDE_API_KEY: raise Exception("CLAUDE_API_KEY environment variable is not set") def calculate_etag(data: Dict) -> str: """Verilen data için ETag hesaplar""" # Sadece menu kısmını hash'le, date değişse bile aynı menu için aynı ETag dönsün menu_data = data.get("menu", {}) data_str = json.dumps(menu_data, sort_keys=True) return hashlib.md5(data_str.encode()).hexdigest() def get_current_date() -> str: """Günün tarihini YYYY-MM-DD formatında döndürür""" tz = pytz.timezone('Europe/Istanbul') return datetime.now(tz).strftime("%Y-%m-%d") def get_current_date_and_season() -> tuple: """Günün tarihini ve mevsimini döndürür""" # Türkiye saat dilimini ayarla tz = pytz.timezone('Europe/Istanbul') current_date = datetime.now(tz) # Türkçe ay isimleri months = { 1: "Ocak", 2: "Şubat", 3: "Mart", 4: "Nisan", 5: "Mayıs", 6: "Haziran", 7: "Temmuz", 8: "Ağustos", 9: "Eylül", 10: "Ekim", 11: "Kasım", 12: "Aralık" } # Mevsimi belirle month = current_date.month if month in [12, 1, 2]: season = "Kış" elif month in [3, 4, 5]: season = "İlkbahar" elif month in [6, 7, 8]: season = "Yaz" else: season = "Sonbahar" # Tarih stringini formatla date_str = f"{current_date.day} {months[current_date.month]} {current_date.year}" return date_str, season def load_cache() -> Dict: """Cache dosyasını okur""" try: if os.path.exists(CACHE_FILE): with open(CACHE_FILE, "r", encoding="utf-8") as f: cache_data = json.load(f) current_date = get_current_date() # Sadece bugünün cache'ini döndür if cache_data.get("date") == current_date: return cache_data except Exception as e: print(f"Cache okuma hatası: {str(e)}\nTrace: {traceback.format_exc()}") return {} def save_cache(cache_data: Dict) -> None: """Cache'i dosyaya kaydeder""" try: # date field'ının olduğundan emin ol if "date" not in cache_data: cache_data["date"] = get_current_date() with open(CACHE_FILE, "w", encoding="utf-8") as f: json.dump(cache_data, f, ensure_ascii=False, indent=2) except Exception as e: error_detail = f"Cache yazma hatası: {str(e)}\nTrace: {traceback.format_exc()}" print(error_detail) raise Exception(error_detail) async def generate_menu() -> Dict: """Claude API kullanarak menü oluşturur""" try: API_URL = "https://api.anthropic.com/v1/messages" headers = { "Content-Type": "application/json", "anthropic-version": "2023-06-01", "x-api-key": CLAUDE_API_KEY } current_date, current_season = get_current_date_and_season() data = { "model": "claude-3-opus-20240229", "max_tokens": 1024, "messages": [{ "role": "user", "content": f"""Günlük türk yemeği menüsü yaz JSON formatında, 3 öğün olsun. Bugün {current_date} ({current_season} mevsimi), dolayısıyla mevsime dikkat et. Sadece JSON ı yaz. Lütfen mevsime uygun ve sağlıklı seçimler yap. Sadece menüyü liste olarak ver ve menüyü Türkçe hazırla. ÖRNEK: {{ "kahvalti": [ "Menemen", "Beyaz Peynir", "Zeytin", "Domates", "Taze Ekmek", "Çay" ] , "ogle": [ "Mercimek Çorbası", "Tavuk Sote", "Pilav", "Mevsim Salatası", "Ayran" ] , "aksam": [ "Yayla Çorbası", "Etli Kuru Fasulye", "Bulgur Pilavı", "Cacık", "Baklava" ] }}""" }] } print("API isteği gönderiliyor...") response = requests.post(API_URL, json=data, headers=headers) print(f"API yanıtı status: {response.status_code}") if response.status_code == 200: menu_text = response.json()['content'][0]['text'] # JSON string'i parse et menu_json = json.loads(menu_text) return { "date": get_current_date(), "menu": menu_json } else: error_msg = f"API hata kodu: {response.status_code}, Yanıt: {response.text}" print(error_msg) raise Exception(error_msg) except Exception as e: error_detail = f"Menü oluşturma hatası: {str(e)}\nTrace: {traceback.format_exc()}" print(error_detail) raise Exception(error_detail) @app.get("/menu") async def get_menu(request: Request, response: Response): """Günün menüsünü getirir, yoksa oluşturur""" try: cache = load_cache() current_date = get_current_date() # Cache varsa ve bugüne aitse if cache and cache.get("date") == current_date: # ETag hesapla ve header'a ekle etag = f'W/"{calculate_etag(cache)}"' # Weak ETag formatı response.headers["ETag"] = etag response.headers["Cache-Control"] = "public, must-revalidate" # If-None-Match header'ı kontrol et if_none_match = request.headers.get("if-none-match") if if_none_match and if_none_match == etag: return Response(status_code=304) # Not Modified return cache # Cache yoksa veya eski ise yeni menü oluştur print("Yeni menü oluşturuluyor...") menu_data = await generate_menu() try: print("Cache'e kaydediliyor...") save_cache(menu_data) print("Cache güncellendi") # Yeni ETag hesapla ve header'a ekle etag = f'W/"{calculate_etag(menu_data)}"' response.headers["ETag"] = etag response.headers["Cache-Control"] = "public, must-revalidate" except Exception as e: print(f"Cache kaydetme hatası: {str(e)}\nTrace: {traceback.format_exc()}") return menu_data except Exception as e: error_detail = f"Genel hata: {str(e)}\nTrace: {traceback.format_exc()}" print(error_detail) raise HTTPException(status_code=500, detail=error_detail) @app.post("/reset") async def reset_cache(): """Cache'i temizler""" try: if os.path.exists(CACHE_FILE): os.remove(CACHE_FILE) print("Cache dosyası silindi") return {"message": "Cache temizlendi"} except Exception as e: error_detail = f"Cache temizleme hatası: {str(e)}\nTrace: {traceback.format_exc()}" print(error_detail) raise HTTPException(status_code=500, detail=error_detail) @app.get("/health") async def health_check(): """API durumunu kontrol eder""" return {"status": "healthy"}