"""PM — Project Management operational layer. FastAPI entrypoint.""" import asyncio import logging from contextlib import asynccontextmanager from fastapi import FastAPI from sqlalchemy import select from app.config import settings from app.db import SessionFactory, engine from app.dev_accounts import ensure_demo_accounts, ensure_demo_assignments from app.errors import install_error_handlers from app.models import Base, PMUser from app.routers import auth, dashboard, files, projects, qc, tasks, users from app.security import hash_password logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s — %(message)s") logging.getLogger("httpx").setLevel(logging.WARNING) async def _ensure_admin() -> None: """Seed the local admin user (role: project_manager) from env on first boot.""" async with SessionFactory() as db: existing = await db.scalar(select(PMUser).where(PMUser.username == settings.ADMIN_USER)) if existing is None: db.add( PMUser( username=settings.ADMIN_USER, password_hash=hash_password(settings.ADMIN_PASSWORD), display_name="مدیر سیستم", role="project_manager", ) ) await db.commit() logging.getLogger("pm.boot").info("seeded admin user %s", settings.ADMIN_USER) # dev role switcher: demo accounts for the dashboard picker await ensure_demo_accounts(db) @asynccontextmanager async def lifespan(_: FastAPI): if settings.DEV_ROLE_SWITCH: logging.getLogger("pm.boot").warning( "PM_DEV_ROLE_SWITCH is ON — /auth/dev-login issues tokens without a password. " "Set PM_DEV_ROLE_SWITCH=false for production." ) async with engine.begin() as conn: from app.schema_sync import drop_legacy, ensure_schema await drop_legacy(conn) await conn.run_sync(Base.metadata.create_all) await ensure_schema(conn) await _ensure_admin() asyncio.create_task(_init_demo_assignments()) yield async def _init_demo_assignments() -> None: await asyncio.sleep(1) try: async with SessionFactory() as db: await ensure_demo_assignments(db) except Exception: # noqa: BLE001 logging.getLogger("pm.boot").exception("demo assignment failed") app = FastAPI(title="PM — Construction Project Management Platform", version="1.0.0", lifespan=lifespan) API_V1_PREFIX = "/api/v1" for module in (auth, users, dashboard, projects, tasks, qc, files): app.include_router(module.router, prefix=API_V1_PREFIX) @app.get("/health") async def health(): return {"status": "ok", "service": "pm-api"} install_error_handlers(app)