refactor: replace issues backend with milestone tasks

This commit is contained in:
zhi
2026-03-16 13:22:14 +00:00
parent dc5d06489d
commit 214a9b109d
20 changed files with 836 additions and 1066 deletions

View File

@@ -5,7 +5,7 @@ from fastapi.middleware.cors import CORSMiddleware
app = FastAPI(
title="HarborForge API",
description="Agent/人类协同任务管理平台 API",
version="0.2.0"
version="0.3.0"
)
# CORS
@@ -24,11 +24,11 @@ def health_check():
@app.get("/version", tags=["System"])
def version():
return {"name": "HarborForge", "version": "0.2.0", "description": "Agent/人类协同任务管理平台"}
return {"name": "HarborForge", "version": "0.3.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.tasks import router as tasks_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
@@ -39,7 +39,7 @@ 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(tasks_router)
app.include_router(projects_router)
app.include_router(users_router)
app.include_router(comments_router)
@@ -54,33 +54,113 @@ app.include_router(roles_router)
def _migrate_schema():
from sqlalchemy import text
from app.core.config import SessionLocal
def _has_table(db, table_name: str) -> bool:
return db.execute(text("SHOW TABLES LIKE :table_name"), {"table_name": table_name}).fetchone() is not None
def _has_column(db, table_name: str, column_name: str) -> bool:
return db.execute(
text(f"SHOW COLUMNS FROM {table_name} LIKE :column_name"),
{"column_name": column_name},
).fetchone() is not None
def _drop_fk_constraints(db, table_name: str, referenced_table: str):
rows = db.execute(text(
"""
SELECT CONSTRAINT_NAME
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = :table_name
AND REFERENCED_TABLE_NAME = :referenced_table
AND CONSTRAINT_NAME <> 'PRIMARY'
"""
), {"table_name": table_name, "referenced_table": referenced_table}).fetchall()
for (constraint_name,) in rows:
db.execute(text(f"ALTER TABLE {table_name} DROP FOREIGN KEY `{constraint_name}`"))
def _ensure_fk(db, table_name: str, column_name: str, referenced_table: str, referenced_column: str, constraint_name: str):
exists = db.execute(text(
"""
SELECT 1
FROM information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = :table_name
AND COLUMN_NAME = :column_name
AND REFERENCED_TABLE_NAME = :referenced_table
AND REFERENCED_COLUMN_NAME = :referenced_column
LIMIT 1
"""
), {
"table_name": table_name,
"column_name": column_name,
"referenced_table": referenced_table,
"referenced_column": referenced_column,
}).fetchone()
if not exists:
db.execute(text(
f"ALTER TABLE {table_name} ADD CONSTRAINT `{constraint_name}` FOREIGN KEY ({column_name}) REFERENCES {referenced_table}({referenced_column})"
))
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:
result = db.execute(text("SHOW COLUMNS FROM projects LIKE 'project_code'"))
if not result.fetchone():
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:
result = db.execute(text("SHOW COLUMNS FROM projects LIKE 'owner_name'"))
if not result.fetchone():
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:
result = db.execute(text("SHOW COLUMNS FROM projects LIKE 'sub_projects'"))
if not result.fetchone():
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:
result = db.execute(text("SHOW COLUMNS FROM projects LIKE 'related_projects'"))
if not result.fetchone():
db.execute(text("ALTER TABLE projects ADD COLUMN related_projects VARCHAR(512) NULL"))
# tasks extra fields
result = db.execute(text("SHOW COLUMNS FROM tasks LIKE 'task_type'"))
if not result.fetchone():
db.execute(text("ALTER TABLE tasks ADD COLUMN task_type VARCHAR(32) DEFAULT 'task'"))
result = db.execute(text("SHOW COLUMNS FROM tasks LIKE 'task_subtype'"))
if not result.fetchone():
db.execute(text("ALTER TABLE tasks ADD COLUMN task_subtype VARCHAR(64) NULL"))
result = db.execute(text("SHOW COLUMNS FROM tasks LIKE 'tags'"))
if not result.fetchone():
db.execute(text("ALTER TABLE tasks ADD COLUMN tags VARCHAR(500) NULL"))
result = db.execute(text("SHOW COLUMNS FROM tasks LIKE 'resolution_summary'"))
if not result.fetchone():
db.execute(text("ALTER TABLE tasks ADD COLUMN resolution_summary TEXT NULL"))
db.execute(text("ALTER TABLE tasks ADD COLUMN positions TEXT NULL"))
db.execute(text("ALTER TABLE tasks ADD COLUMN pending_matters TEXT NULL"))
# comments: issue_id -> task_id
if _has_table(db, "comments"):
_drop_fk_constraints(db, "comments", "issues")
if _has_column(db, "comments", "issue_id") and not _has_column(db, "comments", "task_id"):
db.execute(text("ALTER TABLE comments CHANGE COLUMN issue_id task_id INTEGER NOT NULL"))
if _has_column(db, "comments", "task_id"):
_ensure_fk(db, "comments", "task_id", "tasks", "id", "fk_comments_task_id")
# work_logs: issue_id -> task_id
if _has_table(db, "work_logs"):
_drop_fk_constraints(db, "work_logs", "issues")
if _has_column(db, "work_logs", "issue_id") and not _has_column(db, "work_logs", "task_id"):
db.execute(text("ALTER TABLE work_logs CHANGE COLUMN issue_id task_id INTEGER NOT NULL"))
if _has_column(db, "work_logs", "task_id"):
_ensure_fk(db, "work_logs", "task_id", "tasks", "id", "fk_work_logs_task_id")
# Drop issues table if it exists (no longer used anywhere)
if _has_table(db, "issues"):
db.execute(text("DROP TABLE issues"))
db.commit()
except Exception as e:
db.rollback()
print(f"Migration warning: {e}")
finally:
db.close()
@@ -89,7 +169,7 @@ def _migrate_schema():
@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
from app.models import models, webhook, apikey, activity, milestone, notification, worklog, monitor, role_permission, task, support, meeting
Base.metadata.create_all(bind=engine)
_migrate_schema()