from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from sqlalchemy import select from app.config import settings from app.database import engine, Base, async_session from app.api import router as api_router from app.api import auth, users, customers, visits, work_plans, mini_business, key_visits from app.api import dashboard, upload, export, import_data, wecom, daily_notes, ai_summary, system_config from app.models.system_config import SystemConfig from app.services.scheduler_manager import start_scheduler, shutdown_scheduler @asynccontextmanager async def lifespan(app: FastAPI): # Startup: create tables if not exists (for dev convenience) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) # Add columns that may be missing from older tables await conn.run_sync(lambda c: c.exec_driver_sql( "ALTER TABLE customers ADD COLUMN IF NOT EXISTS remarks TEXT DEFAULT ''" )) await conn.run_sync(lambda c: c.exec_driver_sql( "ALTER TABLE visits ADD COLUMN IF NOT EXISTS visitor_name VARCHAR(50) DEFAULT ''" )) await conn.run_sync(lambda c: c.exec_driver_sql( "ALTER TABLE visits ADD COLUMN IF NOT EXISTS visitor_phone VARCHAR(20) DEFAULT ''" )) # edit_log columns for change tracking for tbl in ["visits", "daily_notes", "work_plans", "mini_business", "key_visits"]: await conn.run_sync(lambda c, t=tbl: c.exec_driver_sql( f"ALTER TABLE {t} ADD COLUMN IF NOT EXISTS edit_log JSONB DEFAULT '[]'" )) # New columns for v0.3 await conn.run_sync(lambda c: c.exec_driver_sql( "ALTER TABLE users ADD COLUMN IF NOT EXISTS require_report BOOLEAN DEFAULT TRUE" )) await conn.run_sync(lambda c: c.exec_driver_sql( "ALTER TABLE customers ADD COLUMN IF NOT EXISTS last_visit_date DATE" )) await conn.run_sync(lambda c: c.exec_driver_sql( "ALTER TABLE customers ADD COLUMN IF NOT EXISTS last_visit_manager_id UUID" )) # New columns for v0.4 await conn.run_sync(lambda c: c.exec_driver_sql( "ALTER TABLE visits ADD COLUMN IF NOT EXISTS companion_names TEXT[] DEFAULT '{}'" )) # v0.6 — manager color tag await conn.run_sync(lambda c: c.exec_driver_sql( "ALTER TABLE users ADD COLUMN IF NOT EXISTS color VARCHAR(7)" )) # system_config table for v0.5 await conn.run_sync(lambda c: c.exec_driver_sql( "CREATE TABLE IF NOT EXISTS system_config (key VARCHAR(64) PRIMARY KEY, value TEXT DEFAULT '')" )) # Read notification_time from DB (or use default 17:30) notification_hour, notification_minute = 17, 30 async with async_session() as db: row = await db.get(SystemConfig, "notification_time") if row and row.value: try: parts = row.value.strip().split(":") notification_hour, notification_minute = int(parts[0]), int(parts[1]) except (ValueError, IndexError): pass # Start daily reporting scheduler start_scheduler(notification_hour, notification_minute) yield # Shutdown shutdown_scheduler() await engine.dispose() app = FastAPI( title=settings.APP_NAME, version="0.1.0", lifespan=lifespan, ) # CORS app.add_middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Mount all routers app.include_router(auth.router, prefix="/api") app.include_router(users.router, prefix="/api") app.include_router(customers.router, prefix="/api") app.include_router(visits.router, prefix="/api") app.include_router(work_plans.router, prefix="/api") app.include_router(mini_business.router, prefix="/api") app.include_router(key_visits.router, prefix="/api") app.include_router(dashboard.router, prefix="/api") app.include_router(upload.router, prefix="/api") app.include_router(export.router, prefix="/api") app.include_router(import_data.router, prefix="/api") app.include_router(wecom.router, prefix="/api") app.include_router(daily_notes.router, prefix="/api") app.include_router(ai_summary.router, prefix="/api") app.include_router(system_config.router, prefix="/api") @app.get("/health") async def health_check(): return {"status": "ok", "app": settings.APP_NAME}