from fastapi import FastAPI, Request, Depends, HTTPException, status from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from fastapi.middleware.cors import CORSMiddleware from sqlalchemy.orm import Session import os import time import psutil from datetime import datetime, timedelta # 导入数据库模型 from app.models.database import create_tables, get_db, APIKey, ProjectMapping, TriggerLog from app.auth.middleware import auth_middleware, get_current_user from app.config import settings # 创建 FastAPI 应用 app = FastAPI( title="Gitea Webhook Ambassador", description="高性能的 Gitea 到 Jenkins 的 Webhook 服务", version="2.0.0" ) # 添加 CORS 中间件 app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 创建数据库表 create_tables() # 挂载静态文件 app.mount("/static", StaticFiles(directory="app/static"), name="static") # 设置模板 templates = Jinja2Templates(directory="app/templates") # 启动时间 start_time = datetime.now() @app.get("/", response_class=HTMLResponse) async def root(request: Request): """根路径 - 重定向到登录页""" return RedirectResponse(url="/login") @app.get("/login", response_class=HTMLResponse) async def login_page(request: Request): """登录页面""" return templates.TemplateResponse("login.html", {"request": request}) @app.get("/dashboard", response_class=HTMLResponse) async def dashboard_page(request: Request): """仪表板页面""" return templates.TemplateResponse("dashboard.html", {"request": request}) @app.post("/api/auth/login") async def login(request: dict): """管理员登录""" admin_key = os.getenv("ADMIN_SECRET_KEY", "admin-secret-key-change-in-production") if request.get("secret_key") != admin_key: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid secret key" ) # 生成 JWT 令牌 token = auth_middleware.create_access_token( data={"sub": "admin", "role": "admin"} ) return {"token": token} @app.get("/api/stats") async def get_stats(db: Session = Depends(get_db), current_user: dict = Depends(get_current_user)): """获取统计信息""" try: # 获取项目总数 total_projects = db.query(ProjectMapping).count() # 获取 API 密钥总数 total_api_keys = db.query(APIKey).count() # 获取今日触发次数 today = datetime.now().date() today_triggers = db.query(TriggerLog).filter( TriggerLog.created_at >= today ).count() # 获取成功触发次数 successful_triggers = db.query(TriggerLog).filter( TriggerLog.status == "success" ).count() return { "total_projects": total_projects, "total_api_keys": total_api_keys, "today_triggers": today_triggers, "successful_triggers": successful_triggers } except Exception as e: raise HTTPException(status_code=500, detail=f"获取统计信息失败: {str(e)}") @app.get("/api/keys", response_model=dict) async def list_api_keys(db: Session = Depends(get_db), current_user: dict = Depends(get_current_user)): """获取所有 API 密钥(兼容前端)""" try: keys = db.query(APIKey).order_by(APIKey.created_at.desc()).all() return { "keys": [ { "id": key.id, "key": key.key, "description": key.description, "created_at": key.created_at.isoformat() } for key in keys ] } except Exception as e: raise HTTPException(status_code=500, detail=f"获取 API 密钥失败: {str(e)}") @app.post("/api/keys", response_model=dict) async def create_api_key( request: dict, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user) ): """创建新的 API 密钥(兼容前端)""" try: # 生成新的 API 密钥 api_key_value = auth_middleware.generate_api_key() # 保存到数据库 db_key = APIKey( key=api_key_value, description=request.get("description", "") ) db.add(db_key) db.commit() db.refresh(db_key) return { "id": db_key.id, "key": db_key.key, "description": db_key.description, "created_at": db_key.created_at.isoformat() } except Exception as e: raise HTTPException(status_code=500, detail=f"创建 API 密钥失败: {str(e)}") @app.delete("/api/keys/{key_id}") async def delete_api_key( key_id: int, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user) ): """删除 API 密钥(兼容前端)""" try: key = db.query(APIKey).filter(APIKey.id == key_id).first() if not key: raise HTTPException(status_code=404, detail="API 密钥不存在") db.delete(key) db.commit() return {"message": "API 密钥删除成功"} except HTTPException: raise except Exception as e: raise HTTPException(status_code=500, detail=f"删除 API 密钥失败: {str(e)}") @app.get("/api/projects/", response_model=dict) async def list_projects(db: Session = Depends(get_db), current_user: dict = Depends(get_current_user)): """获取所有项目(兼容前端)""" try: projects = db.query(ProjectMapping).order_by(ProjectMapping.created_at.desc()).all() return { "projects": [ { "id": project.id, "name": project.repository_name.split('/')[-1], "jenkinsJob": project.default_job, "giteaRepo": project.repository_name, "created_at": project.created_at.isoformat() } for project in projects ] } except Exception as e: raise HTTPException(status_code=500, detail=f"获取项目列表失败: {str(e)}") @app.post("/api/projects/", response_model=dict) async def create_project( request: dict, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user) ): """创建新项目(兼容前端)""" try: # 检查项目是否已存在 existing_project = db.query(ProjectMapping).filter( ProjectMapping.repository_name == request["giteaRepo"] ).first() if existing_project: raise HTTPException(status_code=400, detail="项目已存在") # 创建新项目 project = ProjectMapping( repository_name=request["giteaRepo"], default_job=request["jenkinsJob"] ) db.add(project) db.commit() db.refresh(project) return { "id": project.id, "name": request["name"], "jenkinsJob": project.default_job, "giteaRepo": project.repository_name, "created_at": project.created_at.isoformat() } except HTTPException: raise except Exception as e: raise HTTPException(status_code=500, detail=f"创建项目失败: {str(e)}") @app.delete("/api/projects/{project_id}") async def delete_project( project_id: int, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user) ): """删除项目(兼容前端)""" try: project = db.query(ProjectMapping).filter(ProjectMapping.id == project_id).first() if not project: raise HTTPException(status_code=404, detail="项目不存在") db.delete(project) db.commit() return {"message": "项目删除成功"} except HTTPException: raise except Exception as e: raise HTTPException(status_code=500, detail=f"删除项目失败: {str(e)}") @app.get("/health") async def health_check(): """健康检查端点""" try: # 计算运行时间 uptime = datetime.now() - start_time uptime_str = str(uptime).split('.')[0] # 移除微秒 # 获取内存使用情况 process = psutil.Process() memory_info = process.memory_info() memory_mb = memory_info.rss / 1024 / 1024 return { "status": "healthy", "version": "2.0.0", "uptime": uptime_str, "memory": f"{memory_mb:.1f} MB", "timestamp": datetime.now().isoformat() } except Exception as e: return { "status": "unhealthy", "error": str(e), "timestamp": datetime.now().isoformat() } @app.get("/api/logs") async def get_logs( startTime: str = None, endTime: str = None, level: str = None, query: str = None, db: Session = Depends(get_db), current_user: dict = Depends(get_current_user) ): """获取日志(简化版本)""" try: # 这里应该实现真正的日志查询逻辑 # 目前返回模拟数据 logs = [ { "timestamp": datetime.now().isoformat(), "level": "info", "message": "系统运行正常" } ] return {"logs": logs} except Exception as e: raise HTTPException(status_code=500, detail=f"获取日志失败: {str(e)}") if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)