"""HarborForge API — Agent/人类协同任务管理平台""" from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI( title="HarborForge API", description="Agent/人类协同任务管理平台 API", version="0.2.0" ) # CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Health & version (kept at top level) @app.get("/health", tags=["System"]) def health_check(): return {"status": "healthy"} @app.get("/version", tags=["System"]) def version(): return {"name": "HarborForge", "version": "0.2.0", "description": "Agent/人类协同任务管理平台"} # Register routers from app.api.routers.auth import router as auth_router from app.api.routers.issues import router as issues_router from app.api.routers.projects import router as projects_router from app.api.routers.users import router as users_router from app.api.routers.comments import router as comments_router from app.api.routers.webhooks import router as webhooks_router from app.api.routers.misc import router as misc_router from app.api.routers.monitor import router as monitor_router app.include_router(auth_router) app.include_router(issues_router) app.include_router(projects_router) app.include_router(users_router) app.include_router(comments_router) app.include_router(webhooks_router) app.include_router(misc_router) app.include_router(monitor_router) # Auto schema migration for lightweight deployments def _migrate_schema(): from sqlalchemy import text from app.core.config import SessionLocal db = SessionLocal() try: # issues.issue_subtype result = db.execute(text("SHOW COLUMNS FROM issues LIKE 'issue_subtype'")).fetchone() if not result: db.execute(text("ALTER TABLE issues ADD COLUMN issue_subtype VARCHAR(64) NULL")) # issues.issue_type enum -> varchar result = db.execute(text("SHOW COLUMNS FROM issues WHERE Field='issue_type'")).fetchone() if result and 'enum' in result[1].lower(): db.execute(text("ALTER TABLE issues MODIFY issue_type VARCHAR(32) DEFAULT 'issue'")) # projects.project_code result = db.execute(text("SHOW COLUMNS FROM projects LIKE 'project_code'")).fetchone() if not result: db.execute(text("ALTER TABLE projects ADD COLUMN project_code VARCHAR(16) NULL")) db.execute(text("CREATE UNIQUE INDEX idx_projects_project_code ON projects (project_code)")) except Exception as e: print(f"Migration warning: {e}") finally: db.close() # Run database migration on startup @app.on_event("startup") def startup(): from app.core.config import Base, engine, SessionLocal from app.models import models, webhook, apikey, activity, milestone, notification, worklog, monitor Base.metadata.create_all(bind=engine) _migrate_schema() # Initialize from AbstractWizard (admin user, default project, etc.) from app.init_wizard import run_init db = SessionLocal() try: run_init(db) finally: db.close() # Start lightweight monitor polling thread (every 10 minutes) import threading, time from app.services.monitoring import refresh_provider_usage_once def _monitor_poll_loop(): while True: db2 = SessionLocal() try: refresh_provider_usage_once(db2) except Exception: pass finally: db2.close() time.sleep(600) t = threading.Thread(target=_monitor_poll_loop, daemon=True) t.start()