#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ routes/code_review_routes.py Code Review 路由層 端點: POST /code-review/api/internal/trigger — CD Webhook(部署後觸發) GET /code-review/api/status — 前端 polling 即時狀態 GET /code-review/api/history — 歷史 review 清單 GET /code-review/ — 前端儀表板 """ import logging from flask import Blueprint, jsonify, render_template, request logger = logging.getLogger(__name__) code_review_bp = Blueprint("code_review", __name__, url_prefix="/code-review") # ══════════════════════════════════════════════════════════════════════════════ # CD Webhook — 部署成功後由 Gitea Action 呼叫 # ══════════════════════════════════════════════════════════════════════════════ @code_review_bp.route("/api/internal/trigger", methods=["POST"]) def trigger_review(): """ 接受 Gitea CD pipeline 觸發 Code Review 的 webhook。 Request body (JSON): commit_sha str 完整 commit SHA changed_files list 變更檔案路徑清單 branch str 分支名稱(預設 main) deploy_type str sync | rebuild Header: X-Internal-Token 與 INTERNAL_WEBHOOK_TOKEN env 比對 """ try: from services.code_review_pipeline_service import ( trigger_post_deploy_review, verify_internal_token, ) # 驗證 token token = request.headers.get("X-Internal-Token", "") if not verify_internal_token(token): logger.warning("[CodeReview] Webhook token 驗證失敗") return jsonify({"ok": False, "error": "Unauthorized"}), 401 body = request.get_json(silent=True) or {} commit_sha = body.get("commit_sha", "") changed_files = body.get("changed_files", []) branch = body.get("branch", "main") deploy_type = body.get("deploy_type", "sync") if not commit_sha: return jsonify({"ok": False, "error": "commit_sha 必填"}), 400 pipeline_id = trigger_post_deploy_review( commit_sha=commit_sha, changed_files=changed_files, branch=branch, deploy_type=deploy_type, ) logger.info("[CodeReview] Webhook 觸發成功 pipeline_id=%s", pipeline_id) return jsonify({"ok": True, "pipeline_id": pipeline_id}) except Exception as e: logger.error("[CodeReview] Webhook 處理失敗: %s", e, exc_info=True) return jsonify({"ok": False, "error": str(e)}), 500 # ══════════════════════════════════════════════════════════════════════════════ # 前端 API — 即時狀態 / 歷史 # ══════════════════════════════════════════════════════════════════════════════ @code_review_bp.route("/api/status", methods=["GET"]) def api_status(): """即時 Pipeline 狀態(前端每 3 秒 polling)""" try: from services.code_review_pipeline_service import get_current_state return jsonify(get_current_state()) except Exception as e: return jsonify({"error": str(e)}), 500 @code_review_bp.route("/api/history", methods=["GET"]) def api_history(): """歷史 Code Review 結果清單""" try: limit = min(int(request.args.get("limit", 20)), 50) from services.code_review_pipeline_service import get_history return jsonify(get_history(limit=limit)) except Exception as e: return jsonify({"error": str(e)}), 500 # ══════════════════════════════════════════════════════════════════════════════ # 前端頁面 # ══════════════════════════════════════════════════════════════════════════════ @code_review_bp.route("/", methods=["GET"]) def dashboard(): return render_template("code_review.html", active_page="code_review")