117 lines
4.3 KiB
Python
117 lines
4.3 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
|
|
|
|
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)"))
|
|
# 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
|
|
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()
|