"""FastAPI application entry point.""" from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles import os # Absolute path for uploads directory (relative to this file: app/main.py -> backend/uploads) _BACKEND_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) UPLOAD_BASE = os.path.join(_BACKEND_DIR, "uploads") from app.core.config import settings from app.core.database import init_db, engine from sqlmodel import Session, select from app.models.taxi import Taxi from app.models.shuttle import Shuttle from app.models.business import Business from app.models.coupon import Coupon from app.api.routes import router as routes_router from app.api.bus_stops import router as bus_stops_router from app.api.schedules import router as schedules_router from app.api.coupons import router as coupons_router from app.api.taxis import router as taxis_router from app.api.auth import router as auth_router from app.api.users import router as users_router from app.api.favorites import router as favorites_router from app.api.telemetry import router as telemetry_router from app.api.businesses import router as businesses_router from app.api.analytics import router as analytics_router from app.api.reports import router as reports_router from app.api.shuttles import router as shuttles_router from contextlib import asynccontextmanager from alembic.config import Config from alembic import command import random from datetime import datetime, timedelta from app.services.image_handler import cleanup_expired_coupons @asynccontextmanager async def lifespan(app: FastAPI): # Run migrations try: alembic_cfg = Config("alembic.ini") command.upgrade(alembic_cfg, "head") print("DEBUG: Database migrations completed successfully") except Exception as e: print(f"WARNING: Database migrations failed: {e}") # Fallback to init_db if alembic fails or isn't configured try: init_db() except: pass # Run cleanup of expired coupons try: with Session(engine) as session: cleanup_expired_coupons(session) except Exception as e: print(f"WARNING: Initial cleanup failed: {e}") # Seed sample data if empty with Session(engine) as session: # 1. Taxis taxi_count = session.exec(select(Taxi)).first() if not taxi_count: sample_taxis = [ Taxi( owner_name="Don José (Sibu Demo)", phone_number="+507 6000-0001", license_plate="T-001-SIBU", corregimiento="Boquete", shift="dia", rating=4.9, english_speaking=True ), Taxi( owner_name="María C. (Sibu Demo)", phone_number="+507 6000-0002", license_plate="T-002-SIBU", corregimiento="Boquete", shift="tarde", rating=5.0, english_speaking=False ) ] session.add_all(sample_taxis) session.commit() # 2. Shuttles shuttle_count = session.exec(select(Shuttle)).first() if not shuttle_count: shuttles_data = [ {'route_name': 'Boquete > Santa Catalina', 'origin': 'Boquete', 'destination': 'Santa Catalina', 'vehicle_type': 'Mini Van', 'company_name': 'Chiriqui Transfers', 'trip_type': 'one_way', 'price_per_person': 35.0, 'estimated_duration': '4.5 horas', 'departure_times': '8:00 AM', 'contact_whatsapp': '+50760000000', 'is_active': True}, {'route_name': 'Boquete > Bocas del Toro', 'origin': 'Boquete', 'destination': 'Bocas del Toro', 'vehicle_type': 'Van + Bote', 'company_name': 'Hello Panama', 'trip_type': 'one_way', 'price_per_person': 30.0, 'estimated_duration': '3.5 horas', 'departure_times': '8:00 AM', 'contact_whatsapp': '+50760000000', 'is_active': True} ] for data in shuttles_data: session.add(Shuttle(**data)) session.commit() # 3. Businesses & Coupons (Tourist spots) biz_count = session.exec(select(Business)).first() if not biz_count: tourist_spots = [ {"name": "Finca El Explorador", "area": "Boquete", "lat": 8.7845, "lng": -82.4350}, {"name": "Cascada San Ramón", "area": "Boquete", "lat": 8.8120, "lng": -82.4650} ] for spot in tourist_spots: biz = Business(name=spot["name"], address=f"Sector {spot['area']}", phone="6000-0000", category="Area Turistica", area=spot["area"], latitude=spot["lat"], longitude=spot["lng"]) session.add(biz) session.flush() coupon = Coupon(title=f"Oferta en {biz.name}", description=f"Descuento especial en {biz.name}", business_id=biz.id, business_name=biz.name, business_address=biz.address, business_phone=biz.phone, category=biz.category, discount_percentage=15, valid_from=datetime.now(), valid_until=(datetime.now() + timedelta(days=30)), is_active=True) session.add(coupon) session.commit() yield app = FastAPI( title="SIBU Transportation API", description="API for SIBU public transportation system", version="1.0.0", debug=settings.debug, lifespan=lifespan ) # CORS middleware origins = [ "http://localhost:5173", "http://127.0.0.1:5173", "https://sibu-frontend.vercel.app", "https://sibu-transport.web.app", "https://sibu2-0-transport-2026.firebaseapp.com", "https://sibu2-0-transport-2026.web.app", ] app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Ensure upload directories exist for sub in ["profiles", "vehicles", "businesses", "coupons"]: os.makedirs(os.path.join(UPLOAD_BASE, sub), exist_ok=True) # Mount static files app.mount("/uploads", StaticFiles(directory=UPLOAD_BASE), name="uploads") # Include routers app.include_router(routes_router) app.include_router(bus_stops_router) app.include_router(schedules_router) app.include_router(coupons_router) app.include_router(taxis_router) app.include_router(auth_router) app.include_router(users_router) app.include_router(favorites_router) app.include_router(telemetry_router) app.include_router(businesses_router) app.include_router(analytics_router, prefix="/api/analytics", tags=["analytics"]) app.include_router(reports_router) app.include_router(shuttles_router) @app.get("/") async def root(): """Root endpoint.""" return {"message": "SIBU Transportation API", "version": "1.0.0"} @app.get("/health") async def health(): """Health check endpoint.""" return {"status": "healthy", "environment": settings.environment}