import os from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from database import engine, Base from middleware.license_check import LicenseCheckMiddleware from services.cloud_sync import start_cloud_sync # Import all models so SQLAlchemy can create their tables import models.user # noqa: F401 import models.table # noqa: F401 import models.printer # noqa: F401 import models.product # noqa: F401 import models.order # noqa: F401 from routers import auth, tables, products, orders, waiters, reports, system def _run_migrations(): """Apply additive schema changes that create_all won't handle. Each migration gets its own connection so a no-op (column already exists) doesn't leave a dirty transaction that blocks subsequent migrations.""" from sqlalchemy import text migrations = [ "ALTER TABLE product_ingredients ADD COLUMN extra_cost REAL NOT NULL DEFAULT 0.0", "ALTER TABLE products ADD COLUMN image_url VARCHAR", "ALTER TABLE tables ADD COLUMN group_id INTEGER REFERENCES table_groups(id)", "ALTER TABLE table_groups ADD COLUMN prefix VARCHAR", "ALTER TABLE table_groups ADD COLUMN color VARCHAR", "ALTER TABLE products ADD COLUMN sort_order INTEGER NOT NULL DEFAULT 0", "ALTER TABLE product_preference_sets ADD COLUMN default_choice_id INTEGER", "ALTER TABLE product_preference_choices ADD COLUMN sub_choices TEXT", "ALTER TABLE product_preference_choices ADD COLUMN disables_subset INTEGER NOT NULL DEFAULT 0", "ALTER TABLE product_preference_sets ADD COLUMN shared_subset TEXT", "ALTER TABLE product_options ADD COLUMN sub_choices TEXT", ] for sql in migrations: try: with engine.connect() as conn: conn.execute(text(sql)) conn.commit() except Exception: pass @asynccontextmanager async def lifespan(app: FastAPI): Base.metadata.create_all(bind=engine) _run_migrations() sync_task = await start_cloud_sync() yield sync_task.cancel() app = FastAPI(title="POS Local Backend", lifespan=lifespan) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], ) app.add_middleware(LicenseCheckMiddleware) # Serve product images as static files IMAGE_DIR = "/app/data/product_images" os.makedirs(IMAGE_DIR, exist_ok=True) app.mount("/static/product_images", StaticFiles(directory=IMAGE_DIR), name="product_images") app.include_router(auth.router, prefix="/api/auth", tags=["auth"]) app.include_router(tables.router, prefix="/api/tables", tags=["tables"]) app.include_router(products.router, prefix="/api/products", tags=["products"]) app.include_router(orders.router, prefix="/api/orders", tags=["orders"]) app.include_router(waiters.router, prefix="/api/waiters", tags=["waiters"]) app.include_router(reports.router, prefix="/api/reports", tags=["reports"]) app.include_router(system.router, prefix="/api/system", tags=["system"])