Files
HarborForge.Backend/app/main.py

121 lines
4.5 KiB
Python

"""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
from app.api.routers.milestones import router as milestones_router
from app.api.routers.roles import router as roles_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)
app.include_router(milestones_router)
app.include_router(roles_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)"))
# projects.owner_name
result = db.execute(text("SHOW COLUMNS FROM projects LIKE 'owner_name'")).fetchone()
if not result:
db.execute(text("ALTER TABLE projects ADD COLUMN owner_name VARCHAR(128) NOT NULL DEFAULT ''"))
# projects.sub_projects / related_projects
result = db.execute(text("SHOW COLUMNS FROM projects LIKE 'sub_projects'")).fetchone()
if not result:
db.execute(text("ALTER TABLE projects ADD COLUMN sub_projects VARCHAR(512) NULL"))
result = db.execute(text("SHOW COLUMNS FROM projects LIKE 'related_projects'")).fetchone()
if not result:
db.execute(text("ALTER TABLE projects ADD COLUMN related_projects VARCHAR(512) NULL"))
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, role_permission
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()