import logging from contextlib import asynccontextmanager from fastapi import FastAPI from database import engine, Base logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) from api import ( invoices, suppliers, reports, settings, field_mappings, newbook, sambapos, backup, search, resos, calendar_events, residents_table_chart, disputes, credit_notes, public, logbook, imap, support, budget, cover_overrides, purchase_orders, cost_distributions, ingredients, recipes, food_flags, event_orders, external, menus, reconciliation, ) from api.internal import router as internal_router from migrations.add_invoice_features import run_migration from migrations.add_newbook_tables import run_migration as run_newbook_migration from migrations.add_line_item_search import run_migration as run_search_migration from migrations.add_sambapos_settings import run_migration as run_sambapos_migration from migrations.add_sambapos_excluded_items import run_migration as run_sambapos_excluded_migration from migrations.add_admin_restricted_pages import run_migration as run_admin_pages_migration from migrations.add_nextcloud_backup import run_migration as run_nextcloud_backup_migration from migrations.add_price_settings import run_migration as run_price_settings_migration from migrations.add_resos_integration import run_migration as run_resos_migration from migrations.add_calendar_events import run_migration as run_calendar_events_migration from migrations.add_resos_upcoming_sync import run_migration as run_resos_upcoming_sync_migration from migrations.add_newbook_upcoming_sync import run_migration as run_newbook_upcoming_sync_migration from migrations.add_residents_table_chart import run_migration as run_residents_table_chart_migration from migrations.add_rooms_breakdown import run_migration as run_rooms_breakdown_migration from migrations.add_invoice_disputes import run_migration as run_disputes_migration from migrations.add_awaiting_replacement_status import run_migration as run_awaiting_replacement_migration from migrations.add_new_status import run_migration as run_new_status_migration from migrations.add_dispute_attachment_public_hash import run_migration as run_dispute_attachment_hash_migration from migrations.add_logbook import run_migration as run_logbook_migration from migrations.add_imap_integration import run_migration as run_imap_migration from migrations.add_support_request import run_migration as run_support_migration from migrations.add_pdf_annotation_settings import run_migration as run_pdf_annotation_settings_migration from migrations.add_linked_dispute import run_migration as run_linked_dispute_migration from migrations.add_ocr_post_processing import run_migration as run_ocr_post_processing_migration from migrations.add_ocr_weight_setting import run_migration as run_ocr_weight_setting_migration from migrations.add_budget_settings import migrate as run_budget_settings_migration from migrations.add_cover_overrides import migrate as run_cover_overrides_migration from migrations.add_purchase_orders import migrate as run_purchase_orders_migration from migrations.add_supplier_po_fields import migrate as run_supplier_po_fields_migration from migrations.add_kitchen_details import migrate as run_kitchen_details_migration from migrations.add_cost_distributions import migrate as run_cost_distributions_migration from migrations.add_recipe_system import migrate as run_recipe_system_migration from migrations.add_flag_assessment import migrate as run_flag_assessment_migration from migrations.add_dish_separation import migrate as run_dish_separation_migration from migrations.add_step_title import migrate as run_step_title_migration from migrations.add_gross_sell_price import migrate as run_gross_sell_price_migration from migrations.add_recipe_ingredient_yield import migrate as run_recipe_ingredient_yield_migration from migrations.add_recipe_batch_type import migrate as run_recipe_batch_type_migration from migrations.add_sub_recipe_unit import migrate as run_sub_recipe_unit_migration from migrations.add_recipe_ingredient_unit import migrate as run_recipe_ingredient_unit_migration from migrations.add_allergen_keywords import migrate as run_allergen_keywords_migration from migrations.add_brakes_cache import migrate as run_brakes_cache_migration from migrations.add_description_aliases import migrate as run_description_aliases_migration from migrations.add_brakes_dietary_info import migrate as run_brakes_dietary_info_migration from migrations.add_ingredient_flag_dismissals import migrate as run_ingredient_flag_dismissals_migration from migrations.add_menus import migrate as run_menus_migration from migrations.add_ingredient_is_free import migrate as run_ingredient_is_free_migration from migrations.add_recipe_text_flag_dismissals import migrate as run_recipe_text_flag_dismissals_migration from migrations.add_llm_infrastructure import migrate as run_llm_infrastructure_migration from migrations.add_changelog_invoice_link import migrate as run_changelog_invoice_link_migration from migrations.add_sambapos_portion_name import migrate as run_sambapos_portion_name_migration from scheduler import start_scheduler, stop_scheduler logger = logging.getLogger(__name__) async def _run(name, coro): try: await coro() logger.info(f"{name} migration completed") except Exception as e: logger.warning(f"{name} migration warning (may be expected): {e}") @asynccontextmanager async def lifespan(app: FastAPI): async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) await _run("Invoice features", run_migration) await _run("Newbook tables", run_newbook_migration) await _run("Line item search", run_search_migration) await _run("SambaPOS settings", run_sambapos_migration) await _run("SambaPOS excluded items", run_sambapos_excluded_migration) await _run("Admin restricted pages", run_admin_pages_migration) await _run("Nextcloud backup", run_nextcloud_backup_migration) await _run("Price settings", run_price_settings_migration) await _run("Resos integration", run_resos_migration) await _run("Calendar events", run_calendar_events_migration) await _run("Resos upcoming sync", run_resos_upcoming_sync_migration) await _run("Newbook upcoming sync", run_newbook_upcoming_sync_migration) await _run("Residents table chart", run_residents_table_chart_migration) await _run("Rooms breakdown", run_rooms_breakdown_migration) await _run("Invoice disputes", run_disputes_migration) await _run("Awaiting replacement status", run_awaiting_replacement_migration) await _run("NEW status", run_new_status_migration) await _run("Dispute attachment public hash", run_dispute_attachment_hash_migration) await _run("Logbook", run_logbook_migration) await _run("IMAP integration", run_imap_migration) await _run("Support request", run_support_migration) await _run("PDF annotation settings", run_pdf_annotation_settings_migration) await _run("Linked dispute", run_linked_dispute_migration) await _run("OCR post-processing", run_ocr_post_processing_migration) await _run("OCR weight setting", run_ocr_weight_setting_migration) await _run("Budget settings", run_budget_settings_migration) await _run("Cover overrides", run_cover_overrides_migration) await _run("Purchase orders", run_purchase_orders_migration) await _run("Supplier PO fields", run_supplier_po_fields_migration) await _run("Kitchen details", run_kitchen_details_migration) await _run("Cost distributions", run_cost_distributions_migration) await _run("Recipe system", run_recipe_system_migration) await _run("Flag assessment", run_flag_assessment_migration) await _run("Dish separation", run_dish_separation_migration) await _run("Step title", run_step_title_migration) await _run("Gross sell price", run_gross_sell_price_migration) await _run("Recipe ingredient yield", run_recipe_ingredient_yield_migration) await _run("Recipe batch type", run_recipe_batch_type_migration) await _run("Sub-recipe unit", run_sub_recipe_unit_migration) await _run("Recipe ingredient unit", run_recipe_ingredient_unit_migration) await _run("Allergen keywords", run_allergen_keywords_migration) await _run("Brakes product cache", run_brakes_cache_migration) await _run("Description aliases", run_description_aliases_migration) await _run("Brakes dietary info", run_brakes_dietary_info_migration) await _run("Ingredient flag dismissals", run_ingredient_flag_dismissals_migration) await _run("Menus", run_menus_migration) await _run("Ingredient is_free", run_ingredient_is_free_migration) await _run("Recipe text flag dismissals", run_recipe_text_flag_dismissals_migration) await _run("LLM infrastructure", run_llm_infrastructure_migration) await _run("Changelog invoice link", run_changelog_invoice_link_migration) await _run("SambaPOS portion name", run_sambapos_portion_name_migration) start_scheduler() yield stop_scheduler() await engine.dispose() app = FastAPI( title="Kitchen App", description="Invoice/GP, recipes, menus — Hotel Number Four kitchen management", version="1.0.0", lifespan=lifespan, ) # No CORS middleware — same-origin via nginx (A1) app.include_router(invoices.router, prefix="/api/invoices", tags=["Invoices"]) app.include_router(disputes.router, prefix="/api/disputes", tags=["Disputes"]) app.include_router(credit_notes.router, prefix="/api/credit-notes", tags=["Credit Notes"]) app.include_router(suppliers.router, prefix="/api/suppliers", tags=["Suppliers"]) app.include_router(reports.router, prefix="/api/reports", tags=["Reports"]) app.include_router(reconciliation.router, prefix="/api/reports", tags=["Reconciliation"]) app.include_router(settings.router, prefix="/api/settings", tags=["Settings"]) app.include_router(field_mappings.router, prefix="/api/field-mappings", tags=["Field Mappings"]) app.include_router(newbook.router, prefix="/api/newbook", tags=["Newbook"]) app.include_router(sambapos.router, prefix="/api/sambapos", tags=["SambaPOS"]) app.include_router(backup.router, prefix="/api/backup", tags=["Backup"]) app.include_router(search.router, tags=["Search"]) app.include_router(resos.router, prefix="/api/resos", tags=["Resos"]) app.include_router(calendar_events.router, prefix="/api/calendar-events", tags=["Calendar Events"]) app.include_router(residents_table_chart.router, prefix="/api", tags=["Residents Table Chart"]) app.include_router(public.router, prefix="/api/public", tags=["Public"]) app.include_router(logbook.router, prefix="/api", tags=["Logbook"]) app.include_router(imap.router, prefix="/api", tags=["IMAP"]) app.include_router(support.router, prefix="/api", tags=["Support"]) app.include_router(budget.router, prefix="/api/budget", tags=["Budget"]) app.include_router(cover_overrides.router, prefix="/api/cover-overrides", tags=["Cover Overrides"]) app.include_router(purchase_orders.router, prefix="/api/purchase-orders", tags=["Purchase Orders"]) app.include_router(cost_distributions.router, prefix="/api/cost-distributions", tags=["Cost Distributions"]) app.include_router(ingredients.router, prefix="/api/ingredients", tags=["Ingredients"]) app.include_router(recipes.router, prefix="/api/recipes", tags=["Recipes"]) app.include_router(food_flags.router, prefix="/api/food-flags", tags=["Food Flags"]) app.include_router(event_orders.router, prefix="/api/event-orders", tags=["Event Orders"]) app.include_router(menus.router, prefix="/api/menus", tags=["Menus"]) app.include_router(external.router, prefix="/api/external", tags=["External API"]) app.include_router(internal_router) @app.get("/health") async def health_check(): return {"status": "healthy"}