feat(settings): add PATCH env + Coolify redeploy endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
101
web/app.py
101
web/app.py
@@ -2672,6 +2672,107 @@ async def api_mcp_env():
|
||||
}
|
||||
|
||||
|
||||
class McpEnvUpdateRequest(BaseModel):
|
||||
value: Any
|
||||
|
||||
|
||||
@app.patch("/api/settings/mcp/env/{key}")
|
||||
async def api_mcp_env_update(key: str, req: McpEnvUpdateRequest):
|
||||
"""Update a non-secret env var in Infisical. Requires redeploy to take effect."""
|
||||
spec = ENV_CATALOG.get(key)
|
||||
if spec is None:
|
||||
raise HTTPException(404, f"Unknown env key: {key}")
|
||||
if spec.is_secret:
|
||||
raise HTTPException(400, f"Cannot edit secret: {key}")
|
||||
if not spec.is_editable:
|
||||
raise HTTPException(400, f"Read-only: {key}")
|
||||
try:
|
||||
coerced = coerce(spec, req.value)
|
||||
except ValueError as e:
|
||||
raise HTTPException(400, str(e))
|
||||
|
||||
client = _infisical_client()
|
||||
if client is None:
|
||||
raise HTTPException(503, "Infisical not configured")
|
||||
project_id, env, path = _infisical_ctx()
|
||||
str_value = "true" if coerced is True else (
|
||||
"false" if coerced is False else str(coerced)
|
||||
)
|
||||
try:
|
||||
# SDK pattern: try update, fall back to create if missing.
|
||||
# NOTE: exact method may vary by infisical-python version. The
|
||||
# canonical method is `update_secret_by_name`; if your version
|
||||
# uses `secrets.update`, replace accordingly.
|
||||
try:
|
||||
client.update_secret_by_name(
|
||||
project_id=project_id,
|
||||
environment_slug=env,
|
||||
secret_path=path,
|
||||
secret_name=key,
|
||||
secret_value=str_value,
|
||||
)
|
||||
except Exception:
|
||||
client.create_secret_by_name(
|
||||
project_id=project_id,
|
||||
environment_slug=env,
|
||||
secret_path=path,
|
||||
secret_name=key,
|
||||
secret_value=str_value,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("infisical_write_failed key=%s", key)
|
||||
raise HTTPException(502, f"Infisical write failed: {e}")
|
||||
|
||||
logger.info(
|
||||
"mcp_env_update key=%s value=%s",
|
||||
key,
|
||||
"[masked]" if spec.is_secret else str_value,
|
||||
)
|
||||
return {
|
||||
"ok": True,
|
||||
"key": key,
|
||||
"saved_value": str_value,
|
||||
"requires_redeploy": True,
|
||||
"message": "נשמר ב-Infisical. נדרש redeploy כדי שיכנס לתוקף בקונטיינר.",
|
||||
}
|
||||
|
||||
|
||||
@app.post("/api/settings/mcp/env/redeploy")
|
||||
async def api_mcp_env_redeploy():
|
||||
"""Trigger Coolify redeploy of the legal-ai app."""
|
||||
import httpx
|
||||
coolify_url = os.environ.get("COOLIFY_URL", "http://158.178.131.193:8000")
|
||||
coolify_token = os.environ.get("COOLIFY_API_TOKEN", "")
|
||||
app_uuid = os.environ.get("COOLIFY_APP_UUID", "gyjo0mtw2c42ej3xxvbz8zio")
|
||||
if not coolify_token:
|
||||
raise HTTPException(503, "COOLIFY_API_TOKEN not configured")
|
||||
|
||||
async with httpx.AsyncClient(timeout=30.0) as http:
|
||||
try:
|
||||
resp = await http.post(
|
||||
f"{coolify_url}/api/v1/deploy",
|
||||
params={"uuid": app_uuid, "force": "false"},
|
||||
headers={"Authorization": f"Bearer {coolify_token}"},
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(502, f"Coolify unreachable: {e}")
|
||||
if resp.status_code >= 400:
|
||||
raise HTTPException(
|
||||
502, f"Coolify deploy failed: {resp.status_code} {resp.text}"
|
||||
)
|
||||
data = resp.json() if resp.content else {}
|
||||
deployment_uuid = (
|
||||
data.get("deployment_uuid")
|
||||
or (data.get("deployments") or [{}])[0].get("deployment_uuid")
|
||||
)
|
||||
logger.info("mcp_env_redeploy triggered uuid=%s", deployment_uuid)
|
||||
return {
|
||||
"ok": True,
|
||||
"deployment_uuid": deployment_uuid,
|
||||
"message": "Redeploy הופעל. הקונטיינר יחזור תוך 2-4 דקות.",
|
||||
}
|
||||
|
||||
|
||||
# ── Settings: Tag → Company Mappings ──────────────────────────────
|
||||
|
||||
@app.get("/api/settings/paperclip-companies")
|
||||
|
||||
Reference in New Issue
Block a user