80 lines
2.7 KiB
Python
80 lines
2.7 KiB
Python
"""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)
|