2025-10-29 15:31:01 +08:00
|
|
|
|
"""V2 API routes - Enhanced implementation with structured results."""
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import time
|
|
|
|
|
|
from flask import Blueprint, jsonify, request
|
|
|
|
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
|
|
|
|
|
|
|
|
from lawrisk.services.lawrisk_v2_service import search_v2, list_regions
|
2025-10-30 08:52:48 +08:00
|
|
|
|
from lawrisk.services.licensing_repo import (
|
|
|
|
|
|
list_permits_for_region,
|
|
|
|
|
|
load_permits_and_risks,
|
|
|
|
|
|
list_region_theme_options,
|
|
|
|
|
|
load_theme_payload,
|
2025-10-30 10:33:35 +08:00
|
|
|
|
create_checkpoint,
|
|
|
|
|
|
list_checkpoints,
|
|
|
|
|
|
restore_checkpoint,
|
|
|
|
|
|
delete_checkpoint,
|
2025-10-30 08:52:48 +08:00
|
|
|
|
)
|
2025-10-29 15:31:01 +08:00
|
|
|
|
from lawrisk.services.lawrisk_service import suggest_questions_embed
|
|
|
|
|
|
|
|
|
|
|
|
v2_bp = Blueprint('lawrisk_v2', __name__, url_prefix='/fs-ai-asistant/api/workflow/lawrisk')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@v2_bp.route('/v2', methods=['POST', 'GET'])
|
|
|
|
|
|
def lawrisk_search_v2():
|
|
|
|
|
|
"""V2 search endpoint with structured results."""
|
|
|
|
|
|
query, debug_flag, top_k_int, _mode, region_filter = _extract_params()
|
|
|
|
|
|
|
|
|
|
|
|
if not query or not isinstance(query, str):
|
|
|
|
|
|
return jsonify({"error": "query is required"}), 400
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
t0 = time.time()
|
|
|
|
|
|
with ThreadPoolExecutor(max_workers=2) as ex:
|
|
|
|
|
|
fut_subject = ex.submit(search_v2, query, debug_flag, region_filter)
|
|
|
|
|
|
fut_questions = ex.submit(suggest_questions_embed, query, max(1, top_k_int))
|
|
|
|
|
|
|
|
|
|
|
|
result_v2 = fut_subject.result()
|
|
|
|
|
|
rec_questions = fut_questions.result() or []
|
|
|
|
|
|
|
|
|
|
|
|
risk_subject = result_v2.get("risk_subject", []) if isinstance(result_v2, dict) else []
|
|
|
|
|
|
found = bool(risk_subject)
|
|
|
|
|
|
exec_time = int((time.time() - t0) * 1000)
|
|
|
|
|
|
|
|
|
|
|
|
data = {
|
|
|
|
|
|
"llmRespond": "" if found else "抱歉,无法检索到相关答案",
|
|
|
|
|
|
"lawRisk": "",
|
|
|
|
|
|
"questionExtend": rec_questions,
|
|
|
|
|
|
"conversationId": "",
|
|
|
|
|
|
"messageId": "",
|
|
|
|
|
|
"roundNumber": 0,
|
|
|
|
|
|
"conversationInfo": {},
|
|
|
|
|
|
"knowledgeSources": [],
|
|
|
|
|
|
"totalKnowledgeSources": 0,
|
|
|
|
|
|
"executionTime": exec_time,
|
|
|
|
|
|
"workflowStatus": "ok" if found else "no_match",
|
|
|
|
|
|
"executionSteps": [],
|
|
|
|
|
|
"costStatistics": {},
|
|
|
|
|
|
"workflowTrackingId": "",
|
|
|
|
|
|
"risk_subject": risk_subject,
|
|
|
|
|
|
"debug": result_v2.get("debug", {}) if (debug_flag and isinstance(result_v2, dict)) else {},
|
|
|
|
|
|
}
|
|
|
|
|
|
resp = {"success": True, "message": "OK", "data": data}
|
|
|
|
|
|
return jsonify(resp)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"lawrisk_search_v2 error: {e}")
|
|
|
|
|
|
return jsonify({"success": False, "message": str(e), "data": {}}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-10-30 08:52:48 +08:00
|
|
|
|
@v2_bp.route('/v2/regions', methods=['GET'])
|
2025-10-29 15:31:01 +08:00
|
|
|
|
def lawrisk_regions():
|
|
|
|
|
|
"""Get list of available regions."""
|
|
|
|
|
|
try:
|
|
|
|
|
|
regions = list_regions()
|
|
|
|
|
|
return jsonify({"success": True, "data": {"regions": regions}})
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
print(f"lawrisk_regions error: {exc}")
|
|
|
|
|
|
return jsonify({"success": False, "message": str(exc)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@v2_bp.route('/getPermits', methods=['GET', 'POST'])
|
|
|
|
|
|
def lawrisk_get_permits():
|
|
|
|
|
|
"""Get permits for a specific region."""
|
|
|
|
|
|
if request.method == "GET":
|
|
|
|
|
|
region_value = request.args.get("region") or request.args.get("region_id")
|
|
|
|
|
|
else:
|
|
|
|
|
|
if request.is_json:
|
|
|
|
|
|
payload = request.get_json(silent=True) or {}
|
|
|
|
|
|
else:
|
|
|
|
|
|
payload = request.form.to_dict(flat=True) if request.form else {}
|
|
|
|
|
|
region_value = payload.get("region") or payload.get("region_id")
|
|
|
|
|
|
|
|
|
|
|
|
if not region_value or (isinstance(region_value, str) and not region_value.strip()):
|
|
|
|
|
|
return jsonify({"success": False, "message": "region is required", "data": {}}), 400
|
|
|
|
|
|
|
|
|
|
|
|
region_token = region_value.strip() if isinstance(region_value, str) else str(region_value)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
permits = list_permits_for_region(region_token)
|
|
|
|
|
|
return jsonify({"success": True, "data": {"region": region_token, "permits": permits}})
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
print(f"lawrisk_get_permits error: {exc}")
|
|
|
|
|
|
return jsonify({"success": False, "message": str(exc)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _extract_params():
|
|
|
|
|
|
"""Extract parameters from request."""
|
|
|
|
|
|
if request.method == "GET":
|
|
|
|
|
|
query = request.args.get("query") or request.args.get("q") or request.args.get("text")
|
|
|
|
|
|
debug_flag = request.args.get("debug") in {"1", "true", "yes", "on"}
|
|
|
|
|
|
top_k = request.args.get("top")
|
|
|
|
|
|
try:
|
|
|
|
|
|
top_k_int = int(top_k) if top_k else 5
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
top_k_int = 5
|
|
|
|
|
|
mode_value = (request.args.get("mode") or "llm").lower()
|
|
|
|
|
|
region_value = request.args.get("region") or request.args.get("region_id")
|
|
|
|
|
|
region_list = request.args.getlist("region")
|
|
|
|
|
|
if region_list and (not region_value or len(region_list) > 1):
|
|
|
|
|
|
region_value = region_list
|
|
|
|
|
|
else:
|
|
|
|
|
|
if request.is_json:
|
|
|
|
|
|
payload = request.get_json(silent=True) or {}
|
|
|
|
|
|
else:
|
|
|
|
|
|
payload = request.form.to_dict(flat=True) if request.form else {}
|
|
|
|
|
|
|
|
|
|
|
|
query = payload.get("query") or payload.get("q") or payload.get("text")
|
|
|
|
|
|
debug_flag = str(payload.get("debug", "")).strip().lower() in {"1", "true", "yes", "on"}
|
|
|
|
|
|
try:
|
|
|
|
|
|
top_k_int = int(payload.get("top", 5))
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
top_k_int = 5
|
|
|
|
|
|
mode_value = str(payload.get("mode", "llm")).lower()
|
|
|
|
|
|
region_value = payload.get("region") or payload.get("region_id")
|
|
|
|
|
|
|
|
|
|
|
|
return query, debug_flag, top_k_int, mode_value, region_value
|
2025-10-30 08:52:48 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@v2_bp.route('/admin/test', methods=['GET'])
|
|
|
|
|
|
def admin_test():
|
|
|
|
|
|
"""Simple test route."""
|
|
|
|
|
|
return jsonify({"success": True, "message": "Test route works!"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@v2_bp.route('/test-simple', methods=['GET'])
|
|
|
|
|
|
def test_simple():
|
|
|
|
|
|
"""Very simple test."""
|
|
|
|
|
|
return jsonify({"status": "ok"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@v2_bp.route('/admin/regions', methods=['GET'])
|
|
|
|
|
|
def admin_regions():
|
|
|
|
|
|
"""Get all regions for database maintenance."""
|
|
|
|
|
|
try:
|
|
|
|
|
|
regions = list_regions()
|
|
|
|
|
|
return jsonify({"success": True, "data": {"regions": regions}})
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
print(f"admin_regions error: {exc}")
|
|
|
|
|
|
return jsonify({"success": False, "message": str(exc)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@v2_bp.route('/admin/themes', methods=['GET'])
|
|
|
|
|
|
def admin_themes():
|
|
|
|
|
|
"""Get themes for a specific region."""
|
|
|
|
|
|
region_value = request.args.get("region") or request.args.get("region_id")
|
|
|
|
|
|
|
|
|
|
|
|
if not region_value or (isinstance(region_value, str) and not region_value.strip()):
|
|
|
|
|
|
return jsonify({"success": False, "message": "region is required", "data": {}}), 400
|
|
|
|
|
|
|
|
|
|
|
|
region_token = region_value.strip() if isinstance(region_value, str) else str(region_value)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
catalog = list_region_theme_options()
|
|
|
|
|
|
region_id_lower = region_token.lower()
|
|
|
|
|
|
|
|
|
|
|
|
themes = []
|
|
|
|
|
|
seen_theme_ids = set()
|
|
|
|
|
|
|
|
|
|
|
|
for item in catalog:
|
|
|
|
|
|
if (item["region_id"] == region_token or
|
|
|
|
|
|
item["region_id"].lower() == region_id_lower or
|
|
|
|
|
|
item["region_name"].lower() == region_id_lower):
|
|
|
|
|
|
theme_id = item["theme_id"]
|
|
|
|
|
|
if theme_id not in seen_theme_ids:
|
|
|
|
|
|
seen_theme_ids.add(theme_id)
|
|
|
|
|
|
themes.append({
|
|
|
|
|
|
"id": theme_id,
|
|
|
|
|
|
"name": item["theme_name"],
|
|
|
|
|
|
"option_id": item["option_id"]
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return jsonify({"success": True, "data": {"region": region_token, "themes": themes}})
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
print(f"admin_themes error: {exc}")
|
|
|
|
|
|
return jsonify({"success": False, "message": str(exc)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@v2_bp.route('/admin/permits', methods=['GET'])
|
|
|
|
|
|
def admin_permits():
|
|
|
|
|
|
"""Get permits for a specific region-theme combination."""
|
|
|
|
|
|
region_value = request.args.get("region") or request.args.get("region_id")
|
|
|
|
|
|
theme_value = request.args.get("theme") or request.args.get("theme_id")
|
|
|
|
|
|
|
|
|
|
|
|
if not region_value or not theme_value:
|
|
|
|
|
|
return jsonify({"success": False, "message": "region and theme are required", "data": {}}), 400
|
|
|
|
|
|
|
|
|
|
|
|
region_token = region_value.strip() if isinstance(region_value, str) else str(region_value)
|
|
|
|
|
|
theme_token = theme_value.strip() if isinstance(theme_value, str) else str(theme_value)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
permits = load_permits_and_risks(region_token, theme_token)
|
|
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"region": region_token,
|
|
|
|
|
|
"theme": theme_token,
|
|
|
|
|
|
"permits": permits
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
print(f"admin_permits error: {exc}")
|
|
|
|
|
|
return jsonify({"success": False, "message": str(exc)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@v2_bp.route('/admin/permit-details', methods=['GET'])
|
|
|
|
|
|
def admin_permit_details():
|
|
|
|
|
|
"""Get detailed information for a specific permit."""
|
|
|
|
|
|
region_value = request.args.get("region") or request.args.get("region_id")
|
|
|
|
|
|
theme_value = request.args.get("theme") or request.args.get("theme_id")
|
|
|
|
|
|
permit_value = request.args.get("permit") or request.args.get("permit_id")
|
|
|
|
|
|
|
|
|
|
|
|
if not region_value or not theme_value or not permit_value:
|
|
|
|
|
|
return jsonify({"success": False, "message": "region, theme, and permit are required", "data": {}}), 400
|
|
|
|
|
|
|
|
|
|
|
|
region_token = region_value.strip() if isinstance(region_value, str) else str(region_value)
|
|
|
|
|
|
theme_token = theme_value.strip() if isinstance(theme_value, str) else str(theme_value)
|
|
|
|
|
|
permit_token = permit_value.strip() if isinstance(permit_value, str) else str(permit_value)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
permits = load_permits_and_risks(region_token, theme_token, permit_token)
|
|
|
|
|
|
|
|
|
|
|
|
if not permits:
|
|
|
|
|
|
return jsonify({"success": False, "message": "Permit not found", "data": {}}), 404
|
|
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"region": region_token,
|
|
|
|
|
|
"theme": theme_token,
|
|
|
|
|
|
"permit": permits[0]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
print(f"admin_permit_details error: {exc}")
|
|
|
|
|
|
return jsonify({"success": False, "message": str(exc)}), 500
|
2025-10-30 10:33:35 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@v2_bp.route('/admin/checkpoints', methods=['GET'])
|
|
|
|
|
|
def admin_list_checkpoints():
|
|
|
|
|
|
"""List all available checkpoints."""
|
|
|
|
|
|
try:
|
|
|
|
|
|
checkpoints = list_checkpoints()
|
|
|
|
|
|
return jsonify({"success": True, "data": {"checkpoints": checkpoints}})
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
print(f"admin_list_checkpoints error: {exc}")
|
|
|
|
|
|
return jsonify({"success": False, "message": str(exc)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@v2_bp.route('/admin/checkpoints', methods=['POST'])
|
|
|
|
|
|
def admin_create_checkpoint():
|
|
|
|
|
|
"""Create a new checkpoint."""
|
|
|
|
|
|
if request.is_json:
|
|
|
|
|
|
payload = request.get_json(silent=True) or {}
|
|
|
|
|
|
else:
|
|
|
|
|
|
payload = request.form.to_dict(flat=True) if request.form else {}
|
|
|
|
|
|
|
|
|
|
|
|
description = payload.get("description", "")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
checkpoint = create_checkpoint(description)
|
|
|
|
|
|
return jsonify({"success": True, "data": checkpoint})
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
print(f"admin_create_checkpoint error: {exc}")
|
|
|
|
|
|
return jsonify({"success": False, "message": str(exc)}), 500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@v2_bp.route('/admin/checkpoints/<checkpoint_id>/restore', methods=['POST'])
|
|
|
|
|
|
def admin_restore_checkpoint(checkpoint_id):
|
2025-10-31 17:33:12 +08:00
|
|
|
|
"""
|
|
|
|
|
|
⚠️ DANGEROUS OPERATION ⚠️
|
|
|
|
|
|
|
|
|
|
|
|
恢复数据库从checkpoint。
|
|
|
|
|
|
|
|
|
|
|
|
此操作将会:
|
|
|
|
|
|
1. 永久删除当前数据库中的所有数据
|
|
|
|
|
|
2. 从指定的checkpoint恢复数据
|
|
|
|
|
|
3. 如果失败,可能导致数据丢失
|
|
|
|
|
|
|
|
|
|
|
|
建议在生产环境中:
|
|
|
|
|
|
- 确保已创建最新备份
|
|
|
|
|
|
- 在低峰期操作
|
|
|
|
|
|
- 启用自动备份功能 (create_auto_backup=true)
|
|
|
|
|
|
|
|
|
|
|
|
POST参数:
|
|
|
|
|
|
- create_auto_backup: 是否在恢复前自动备份当前状态 (默认: true)
|
|
|
|
|
|
- batch_size: 批量插入的批次大小,每批插入行数 (默认: 1000,范围: 100-10000)
|
|
|
|
|
|
"""
|
|
|
|
|
|
import logging
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
logger.info("=" * 80)
|
|
|
|
|
|
logger.info(f"[API] Received checkpoint restore request: {checkpoint_id}")
|
|
|
|
|
|
logger.info("=" * 80)
|
|
|
|
|
|
|
2025-10-30 10:33:35 +08:00
|
|
|
|
try:
|
2025-10-31 17:33:12 +08:00
|
|
|
|
# 获取请求参数
|
|
|
|
|
|
if request.is_json:
|
|
|
|
|
|
payload = request.get_json(silent=True) or {}
|
|
|
|
|
|
else:
|
|
|
|
|
|
payload = request.form.to_dict(flat=True) if request.form else {}
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(f"[API] Request parameters: {payload}")
|
|
|
|
|
|
|
|
|
|
|
|
# 默认启用自动备份
|
|
|
|
|
|
create_auto_backup = str(payload.get("create_auto_backup", "true")).lower() in {"1", "true", "yes", "on"}
|
|
|
|
|
|
logger.info(f"[API] Auto-backup enabled: {create_auto_backup}")
|
|
|
|
|
|
|
|
|
|
|
|
# 解析批量大小参数
|
|
|
|
|
|
try:
|
|
|
|
|
|
batch_size = int(payload.get("batch_size", "1000"))
|
|
|
|
|
|
if batch_size < 100 or batch_size > 10000:
|
|
|
|
|
|
raise ValueError(f"batch_size must be between 100 and 10000, got {batch_size}")
|
|
|
|
|
|
except (ValueError, TypeError) as e:
|
|
|
|
|
|
logger.warning(f"[API] Invalid batch_size: {e}, using default 1000")
|
|
|
|
|
|
batch_size = 1000
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(f"[API] Batch size: {batch_size} rows/batch")
|
|
|
|
|
|
|
|
|
|
|
|
# 执行恢复
|
|
|
|
|
|
logger.info(f"[API] Starting restore operation...")
|
|
|
|
|
|
restore_result = restore_checkpoint(checkpoint_id, create_auto_backup=create_auto_backup, batch_size=batch_size)
|
|
|
|
|
|
|
|
|
|
|
|
# 检查恢复状态
|
|
|
|
|
|
if restore_result.get("status") == "success":
|
|
|
|
|
|
logger.info(f"[API] Restore completed successfully")
|
|
|
|
|
|
logger.info(f"[API] Tables restored: {restore_result['summary']['tables_restored']}")
|
|
|
|
|
|
logger.info(f"[API] Total rows restored: {restore_result['summary']['total_rows_restored']}")
|
|
|
|
|
|
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"message": restore_result["message"],
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"checkpoint_id": restore_result["summary"]["checkpoint_id"],
|
|
|
|
|
|
"tables_restored": restore_result["summary"]["tables_restored"],
|
|
|
|
|
|
"total_rows": restore_result["summary"]["total_rows_restored"],
|
|
|
|
|
|
"auto_backup": restore_result["summary"].get("auto_backup"),
|
|
|
|
|
|
"table_details": restore_result["summary"]["table_details"]
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 恢复失败
|
|
|
|
|
|
logger.error(f"[API] Restore failed: {restore_result.get('message')}")
|
|
|
|
|
|
errors = restore_result["summary"].get("errors", [])
|
|
|
|
|
|
if errors:
|
|
|
|
|
|
logger.error(f"[API] Errors encountered: {errors}")
|
|
|
|
|
|
|
|
|
|
|
|
response_data = {
|
|
|
|
|
|
"success": False,
|
|
|
|
|
|
"message": restore_result["message"],
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"errors": errors,
|
|
|
|
|
|
"auto_backup_available": restore_result.get("auto_backup_available", False)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 如果有自动备份,提供恢复建议
|
|
|
|
|
|
if restore_result.get("recovery_suggestion"):
|
|
|
|
|
|
logger.warning(f"[API] Recovery suggestion: {restore_result['recovery_suggestion']}")
|
|
|
|
|
|
response_data["data"]["recovery_suggestion"] = restore_result["recovery_suggestion"]
|
|
|
|
|
|
|
|
|
|
|
|
return jsonify(response_data), 500
|
|
|
|
|
|
|
2025-10-30 10:33:35 +08:00
|
|
|
|
except Exception as exc:
|
2025-10-31 17:33:12 +08:00
|
|
|
|
logger.error(f"[API] Restore operation failed with exception: {str(exc)}", exc_info=True)
|
|
|
|
|
|
return jsonify({
|
|
|
|
|
|
"success": False,
|
|
|
|
|
|
"message": f"Restore failed: {str(exc)}",
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"error_type": type(exc).__name__,
|
|
|
|
|
|
"auto_backup_available": None # 未知,因为异常发生在备份之前
|
|
|
|
|
|
}
|
|
|
|
|
|
}), 500
|
2025-10-30 10:33:35 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@v2_bp.route('/admin/checkpoints/<checkpoint_id>', methods=['DELETE'])
|
|
|
|
|
|
def admin_delete_checkpoint(checkpoint_id):
|
|
|
|
|
|
"""Delete a checkpoint."""
|
|
|
|
|
|
try:
|
|
|
|
|
|
success = delete_checkpoint(checkpoint_id)
|
|
|
|
|
|
if success:
|
|
|
|
|
|
return jsonify({"success": True, "message": "Checkpoint deleted"})
|
|
|
|
|
|
else:
|
|
|
|
|
|
return jsonify({"success": False, "message": "Checkpoint not found"}), 404
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
|
print(f"admin_delete_checkpoint error: {exc}")
|
|
|
|
|
|
return jsonify({"success": False, "message": str(exc)}), 500
|