from fastapi import FastAPI, Depends, Query, Request from fastapi.templating import Jinja2Templates import httpx from sqlalchemy.future import select from sqlalchemy.ext.asyncio import AsyncSession from db import get_session, engine, Base from models import WeatherRecord from datetime import datetime app = FastAPI() templates = Jinja2Templates(directory="templates") CITIES = { "warszawa": (52.23, 21.01), "krakow": (50.06, 19.94), "gdansk": (54.35, 18.65), } @app.on_event("startup") async def startup(): async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) @app.get("/weather") async def get_weather(city: str = Query("warszawa"), session: AsyncSession = Depends(get_session)): coords = CITIES.get(city.lower()) if not coords: return {"error": "Miasto nieobsługiwane"} url = f"https://api.open-meteo.com/v1/forecast?latitude={coords[0]}&longitude={coords[1]}¤t_weather=true" async with httpx.AsyncClient() as client: res = await client.get(url) data = res.json() current = data.get("current_weather", {}) record = WeatherRecord( city=city.capitalize(), temperature=current.get("temperature"), windspeed=current.get("windspeed"), time=datetime.fromisoformat(current.get("time")), ) session.add(record) await session.commit() return { "city": record.city, "temperature": record.temperature, "windspeed": record.windspeed, "time": record.time } @app.get("/history") async def get_history(city: str = Query("warszawa"), session: AsyncSession = Depends(get_session)): stmt = select(WeatherRecord).where(WeatherRecord.city.ilike(city)).order_by(WeatherRecord.time.desc()).limit(10) result = await session.execute(stmt) records = result.scalars().all() return [ { "city": r.city, "temperature": r.temperature, "windspeed": r.windspeed, "time": r.time.isoformat() } for r in records ] @app.get("/view") async def view_history(request: Request, city: str = "warszawa", session: AsyncSession = Depends(get_session)): stmt = select(WeatherRecord).where(WeatherRecord.city.ilike(city)).order_by(WeatherRecord.time.desc()).limit(10) result = await session.execute(stmt) records = result.scalars().all() return templates.TemplateResponse("history.html", { "request": request, "city": city.capitalize(), "records": records }) @app.get("/ping") async def ping(): return {"status": "ok"} @app.get("/weather/geo") async def get_weather_by_coords( latitude: float = Query(..., ge=-90, le=90), longitude: float = Query(..., ge=-180, le=180), session: AsyncSession = Depends(get_session) ): url = ( f"https://api.open-meteo.com/v1/forecast?" f"latitude={latitude}&longitude={longitude}¤t_weather=true" ) async with httpx.AsyncClient() as client: try: res = await client.get(url, timeout=10.0) res.raise_for_status() data = res.json() except httpx.RequestError as e: raise HTTPException(status_code=502, detail=f"Błąd połączenia: {e}") except httpx.HTTPStatusError as e: raise HTTPException(status_code=502, detail=f"Błąd API pogodowego: {e}") current = data.get("current_weather", {}) record = WeatherRecord( city="Custom Location", temperature=current.get("temperature"), windspeed=current.get("windspeed"), time=datetime.fromisoformat(current.get("time")), ) session.add(record) await session.commit() return { "city": record.city, "latitude": latitude, "longitude": longitude, "temperature": record.temperature, "windspeed": record.windspeed, "time": record.time } @app.get("/forecast") async def get_forecast( city: str = Query("warszawa"), days: int = Query(3, ge=1, le=16) ): coords = CITIES.get(city.lower()) if not coords: return {"error": "Miasto nieobsługiwane"} url = ( f"https://api.open-meteo.com/v1/forecast?" f"latitude={coords[0]}&longitude={coords[1]}" f"&daily=temperature_2m_max,temperature_2m_min,precipitation_sum" f"&timezone=Europe/Warsaw" f"&forecast_days={days}" ) async with httpx.AsyncClient() as client: res = await client.get(url) data = res.json() daily = data.get("daily", {}) return { "city": city.capitalize(), "forecast": daily } @app.get("/forecast/geo") async def get_forecast_geo( latitude: float = Query(..., ge=-90, le=90), longitude: float = Query(..., ge=-180, le=180), days: int = Query(3, ge=1, le=16) ): url = ( f"https://api.open-meteo.com/v1/forecast?" f"latitude={latitude}&longitude={longitude}" f"&daily=temperature_2m_max,temperature_2m_min,precipitation_sum" f"&timezone=Europe/Warsaw" f"&forecast_days={days}" ) async with httpx.AsyncClient() as client: res = await client.get(url) data = res.json() daily = data.get("daily", {}) return { "latitude": latitude, "longitude": longitude, "forecast": daily } @app.get("/forecast/hourly/geo") async def get_hourly_forecast_geo( latitude: float = Query(..., ge=-90, le=90), longitude: float = Query(..., ge=-180, le=180), hours: int = Query(12, ge=1, le=48) ): url = ( f"https://api.open-meteo.com/v1/forecast?" f"latitude={latitude}&longitude={longitude}" f"&hourly=temperature_2m,windspeed_10m,precipitation" f"&timezone=Europe/Warsaw" ) async with httpx.AsyncClient() as client: res = await client.get(url) data = res.json() hourly_data = {} for key, values in data.get("hourly", {}).items(): hourly_data[key] = values[:hours] return { "latitude": latitude, "longitude": longitude, "hourly_forecast": hourly_data } @app.get("/forecast/hourly") async def get_hourly_forecast_city( city: str = Query(...), hours: int = Query(12, ge=1, le=48) ): coords = CITIES.get(city.lower()) if not coords: raise HTTPException(status_code=404, detail="Miasto nieobsługiwane") latitude, longitude = coords url = ( f"https://api.open-meteo.com/v1/forecast?" f"latitude={latitude}&longitude={longitude}" f"&hourly=temperature_2m,windspeed_10m,precipitation" f"&timezone=Europe/Warsaw" ) async with httpx.AsyncClient() as client: res = await client.get(url) data = res.json() hourly_data = {} for key, values in data.get("hourly", {}).items(): hourly_data[key] = values[:hours] return { "city": city.capitalize(), "hourly_forecast": hourly_data } @app.get("/history-range") async def get_weather_history_range( city: str = Query(..., description="Nazwa miasta, np. warszawa"), start_date: str = Query(..., description="Początkowa data w formacie YYYY-MM-DD"), end_date: str = Query(..., description="Końcowa data w formacie YYYY-MM-DD") ): coords = CITIES.get(city.lower()) if not coords: raise HTTPException(status_code=404, detail="Miasto nieobsługiwane") latitude, longitude = coords url = ( f"https://archive-api.open-meteo.com/v1/archive?" f"latitude={latitude}&longitude={longitude}" f"&daily=temperature_2m_max,temperature_2m_min,precipitation_sum" f"&start_date={start_date}&end_date={end_date}" f"&timezone=Europe/Warsaw" ) async with httpx.AsyncClient() as client: res = await client.get(url) data = res.json() daily = data.get("daily", {}) return { "city": city.capitalize(), "start_date": start_date, "end_date": end_date, "history": daily } @app.get("/history-range/geo") async def get_weather_history_range_geo( latitude: float = Query(..., ge=-90, le=90, description="Szerokość geograficzna"), longitude: float = Query(..., ge=-180, le=180, description="Długość geograficzna"), start_date: str = Query(..., description="Początkowa data w formacie YYYY-MM-DD"), end_date: str = Query(..., description="Końcowa data w formacie YYYY-MM-DD") ): url = ( f"https://archive-api.open-meteo.com/v1/archive?" f"latitude={latitude}&longitude={longitude}" f"&daily=temperature_2m_max,temperature_2m_min,precipitation_sum" f"&start_date={start_date}&end_date={end_date}" f"&timezone=Europe/Warsaw" ) async with httpx.AsyncClient() as client: res = await client.get(url) data = res.json() daily = data.get("daily", {}) return { "latitude": latitude, "longitude": longitude, "start_date": start_date, "end_date": end_date, "history": daily }