TaskPulse/backend/app/main.py
Steven cd41e59afc feat: add user authentication system
- User model (users table) with bcrypt password hashing
- JWT-based login API (POST /api/auth/login, GET /api/auth/me)
- Default admin account (admin/admin123) auto-created on startup
- Login page with centered dark-themed login card
- Route guards + Axios interceptors for token management
- Login page renders without sidebar (standalone layout)
- Gunicorn --preload to avoid worker DDL race condition
2026-06-14 23:22:53 +08:00

87 lines
2.9 KiB
Python

"""FastAPI application entry point."""
import asyncio
import logging
from pathlib import Path
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from app.api.agents import router as agents_router
from app.api.auth import router as auth_router
from app.api.tasks import router as tasks_router
from app.api.executions import router as executions_router
from app.api.notifications import router as notifications_router
from app.api.dashboard import router as dashboard_router
from app.api.system import router as system_router
from app.config import settings
from app.database import engine, Base, async_session_factory
from app.services.scheduler import SchedulerService
from app.services.auth import AuthService
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s")
logger = logging.getLogger("taskpulse")
scheduler = SchedulerService()
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Startup: create tables + start background scheduler."""
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
logger.info("Database tables ensured")
# Ensure default admin
async with async_session_factory() as db:
await AuthService.ensure_default_admin(db)
# Start background scheduler
task = asyncio.create_task(scheduler.start())
yield
# Shutdown
await scheduler.stop()
task.cancel()
await engine.dispose()
app = FastAPI(
title="TaskPulse — AI Agent Task Monitor",
description="Unified dashboard for tracking, monitoring and alerting on AI agent scheduled tasks.",
version="1.0.0",
lifespan=lifespan,
)
# API routers
app.include_router(agents_router)
app.include_router(auth_router)
app.include_router(tasks_router)
app.include_router(executions_router)
app.include_router(notifications_router)
app.include_router(dashboard_router)
app.include_router(system_router)
# Serve Vue3 static files (SPA — monolithic deployment)
FRONTEND_DIST = Path(__file__).resolve().parent.parent.parent / "frontend" / "dist"
if FRONTEND_DIST.is_dir():
# Mount assets directory for JS/CSS/fonts
app.mount("/assets", StaticFiles(directory=str(FRONTEND_DIST / "assets")), name="assets")
@app.exception_handler(404)
async def spa_fallback(request: Request, exc):
"""Return index.html for any non-API route (Vue Router SPA)."""
if not request.url.path.startswith("/api"):
content = (FRONTEND_DIST / "index.html").read_text(encoding="utf-8")
return HTMLResponse(content=content, status_code=200)
raise exc
logger.info("Frontend SPA mounted from %s", FRONTEND_DIST)
else:
logger.info("Frontend dist not found at %s — API-only mode", FRONTEND_DIST)
@app.get("/api/health")
async def health():
return {"status": "ok", "service": "taskpulse"}