Initial commit: site monorepo with API, web, and infra.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
влад
2026-07-16 10:11:54 +03:00
co-authored by Cursor
commit 016910ffb7
447 changed files with 73972 additions and 0 deletions
+243
View File
@@ -0,0 +1,243 @@
#!/usr/bin/env python3
"""Pair local WESP hub with site orchestrator (Docker :8000) and write hub state."""
from __future__ import annotations
import json
import secrets
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path
LOG_PATH = Path("/Users/vlad/Documents/wesp new (1)/.cursor/debug-785e22.log")
SESSION = "785e22"
RUN_ID = "pair-v1"
ORCH_URL = "http://127.0.0.1:8000"
WESP_STATE = Path(__file__).resolve().parents[2] / "wesp/data/orchestrator_sync_state.json"
ADMIN_EMAIL = "admin@compton.example"
ADMIN_PASSWORD = "Admin1234"
def _log(hypothesis_id: str, location: str, message: str, data: dict) -> None:
payload = {
"sessionId": SESSION,
"runId": RUN_ID,
"hypothesisId": hypothesis_id,
"location": location,
"message": message,
"data": data,
"timestamp": int(time.time() * 1000),
}
LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
with LOG_PATH.open("a", encoding="utf-8") as fh:
fh.write(json.dumps(payload, ensure_ascii=False) + "\n")
def _request(method: str, url: str, body: dict | None = None, headers: dict | None = None) -> tuple[int, dict | list | str]:
data = None if body is None else json.dumps(body).encode("utf-8")
hdrs = {"Content-Type": "application/json", **(headers or {})}
req = urllib.request.Request(url, data=data, headers=hdrs, method=method)
try:
with urllib.request.urlopen(req, timeout=15) as resp:
raw = resp.read(4000).decode("utf-8", errors="replace")
try:
return resp.status, json.loads(raw)
except json.JSONDecodeError:
return resp.status, raw
except urllib.error.HTTPError as exc:
raw = exc.read(4000).decode("utf-8", errors="replace")
try:
return exc.code, json.loads(raw)
except json.JSONDecodeError:
return exc.code, raw
def _login() -> str:
code, body = _request(
"POST",
f"{ORCH_URL}/api/v1/auth/login",
{"email": ADMIN_EMAIL, "password": ADMIN_PASSWORD},
{"Origin": "http://localhost:5173"},
)
if code != 200 or not isinstance(body, dict) or not body.get("access_token"):
raise RuntimeError(f"login failed: {code} {body}")
return str(body["access_token"])
def _ensure_enterprise(token: str) -> str:
auth = {"Authorization": f"Bearer {token}"}
code, body = _request("GET", f"{ORCH_URL}/api/v1/enterprise/enterprises", headers=auth)
if code != 200:
raise RuntimeError(f"list enterprises failed: {code} {body}")
if isinstance(body, list) and body:
ent_id = str(body[0]["id"])
_log("H3", "pair:list-enterprises", "using existing enterprise", {"enterprise_id": ent_id, "name": body[0].get("name")})
return ent_id
slug = f"local-dev-{secrets.token_hex(4)}"
code, created = _request(
"POST",
f"{ORCH_URL}/api/v1/enterprise/enterprises",
{"name": "Local Dev Farm", "slug": slug},
auth,
)
if code not in (200, 201) or not isinstance(created, dict):
raise RuntimeError(f"create enterprise failed: {code} {created}")
ent_id = str(created["id"])
_log("H3", "pair:create-enterprise", "created enterprise", {"enterprise_id": ent_id, "slug": slug})
return ent_id
def _pair_hub(token: str, enterprise_id: str, hub_site_id: str, hub_name: str) -> dict:
auth = {"Authorization": f"Bearer {token}"}
code, start = _request(
"POST",
f"{ORCH_URL}/api/v1/enterprise/pair/start",
{"enterprise_id": enterprise_id, "farm_name": hub_name},
auth,
)
if code != 200 or not isinstance(start, dict) or not start.get("code"):
raise RuntimeError(f"pair start failed: {code} {start}")
pair_code = str(start["code"])
code, confirm = _request(
"POST",
f"{ORCH_URL}/api/v1/enterprise/pair/confirm",
{"code": pair_code, "hub_site_id": hub_site_id, "hub_name": hub_name, "hub_url": "http://127.0.0.1/"},
)
if code != 200 or not isinstance(confirm, dict) or not confirm.get("api_key"):
raise RuntimeError(f"pair confirm failed: {code} {confirm}")
_log(
"H3",
"pair:confirm",
"hub paired with orchestrator",
{
"enterprise_id": confirm.get("enterprise_id"),
"hub_site_id": confirm.get("hub_site_id"),
"farm_hub_id": confirm.get("farm_hub_id"),
},
)
return confirm
def _write_wesp_state(confirm: dict) -> None:
state = {
"upstream_url": ORCH_URL,
"api_key": confirm["api_key"],
"hub_site_id": confirm["hub_site_id"],
"enterprise_id": confirm["enterprise_id"],
"enabled": True,
"catalog_bootstrap_done": False,
"reports_bootstrap_done": False,
"pull_cursor": 0,
}
WESP_STATE.parent.mkdir(parents=True, exist_ok=True)
WESP_STATE.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8")
_log("H3", "pair:write-state", "wrote orchestrator_sync_state.json", {"path": str(WESP_STATE), "enabled": True})
def _verify_hub_api() -> None:
code, body = _request("GET", f"{ORCH_URL}/api/v1/health")
_log("H5", "pair:orch-health", "orchestrator health after pair", {"status": code})
state = json.loads(WESP_STATE.read_text(encoding="utf-8"))
hub_site_id = state["hub_site_id"]
api_key = state["api_key"]
auth = {"Authorization": f"Hub {hub_site_id}:{api_key}"}
code, conflicts = _request("GET", f"{ORCH_URL}/api/v1/sync/hub/conflicts", headers=auth)
_log(
"H5",
"pair:hub-conflicts",
"hub-auth conflicts list",
{"status": code, "count": len(conflicts) if isinstance(conflicts, list) else None, "body": conflicts if code != 200 else None},
)
def _run_hub_bootstrap() -> None:
import subprocess
import sys
wesp_root = WESP_STATE.parents[1]
script = """
from app import create_app
from app.services.orchestrator_sync.engine import OrchestratorSyncEngine
from config import Config
app = create_app(Config)
with app.app_context():
engine = OrchestratorSyncEngine()
catalog_created = engine.bootstrap_catalog_if_needed()
reports_created = engine.bootstrap_reports_if_needed()
print(f"bootstrap_catalog={catalog_created} bootstrap_reports={reports_created}")
for _ in range(30):
engine.run_cycle()
"""
proc = subprocess.run(
[sys.executable, "-c", script],
cwd=str(wesp_root),
capture_output=True,
text=True,
timeout=120,
)
_log(
"H1",
"pair:bootstrap",
"hub catalog bootstrap after pair",
{
"returncode": proc.returncode,
"stdout": (proc.stdout or "")[-500:],
"stderr": (proc.stderr or "")[-500:],
},
)
if proc.returncode != 0:
print(
"Warning: hub catalog bootstrap subprocess exited non-zero; "
"pending outbox rows will sync on the next hub run.py cycle.",
file=sys.stderr,
)
def _repair_orchestrator_report_payloads(enterprise_id: str) -> None:
import subprocess
script = f"""
from app.modules.zootech.report_apply import repair_report_payloads_from_event_log
repaired = repair_report_payloads_from_event_log({enterprise_id!r})
print(f"repair_report_payloads={{repaired}}")
"""
proc = subprocess.run(
["docker", "exec", "site-api-1", "python3", "-c", script],
capture_output=True,
text=True,
timeout=60,
)
_log(
"H2",
"pair:repair-reports",
"orchestrator report payload repair from event log",
{
"returncode": proc.returncode,
"stdout": (proc.stdout or "")[-300:],
"stderr": (proc.stderr or "")[-300:],
},
)
def main() -> None:
hub_site_id = f"local-wesp-{secrets.token_hex(4)}"
token = _login()
enterprise_id = _ensure_enterprise(token)
confirm = _pair_hub(token, enterprise_id, hub_site_id, "Local WESP Hub")
_write_wesp_state(confirm)
_run_hub_bootstrap()
_repair_orchestrator_report_payloads(confirm["enterprise_id"])
_verify_hub_api()
print("Paired local WESP hub with orchestrator.")
print(f" enterprise_id: {confirm['enterprise_id']}")
print(f" hub_site_id: {confirm['hub_site_id']}")
print(f" state file: {WESP_STATE}")
print("Restart WESP run.py if it was already running, then open Multiserver panel.")
if __name__ == "__main__":
main()
+154
View File
@@ -0,0 +1,154 @@
#!/usr/bin/env python3
"""Runtime smoke check: site orchestrator + WESP hub (debug session 785e22)."""
from __future__ import annotations
import json
import time
import urllib.error
import urllib.request
from pathlib import Path
LOG_PATH = Path("/Users/vlad/Documents/wesp new (1)/.cursor/debug-785e22.log")
SESSION = "785e22"
RUN_ID = "smoke-v1"
def _log(hypothesis_id: str, location: str, message: str, data: dict) -> None:
payload = {
"sessionId": SESSION,
"runId": RUN_ID,
"hypothesisId": hypothesis_id,
"location": location,
"message": message,
"data": data,
"timestamp": int(time.time() * 1000),
}
LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
with LOG_PATH.open("a", encoding="utf-8") as fh:
fh.write(json.dumps(payload, ensure_ascii=False) + "\n")
def _get(url: str, timeout: float = 3.0) -> tuple[int, str]:
req = urllib.request.Request(url, method="GET")
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return resp.status, resp.read(500).decode("utf-8", errors="replace")
except urllib.error.HTTPError as exc:
return exc.code, exc.read(500).decode("utf-8", errors="replace")
except Exception as exc:
return 0, str(exc)
def _post_json(url: str, body: dict, headers: dict | None = None, timeout: float = 5.0) -> tuple[int, str]:
data = json.dumps(body).encode("utf-8")
hdrs = {"Content-Type": "application/json", **(headers or {})}
req = urllib.request.Request(url, data=data, headers=hdrs, method="POST")
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return resp.status, resp.read(800).decode("utf-8", errors="replace")
except urllib.error.HTTPError as exc:
return exc.code, exc.read(800).decode("utf-8", errors="replace")
except Exception as exc:
return 0, str(exc)
def _get_with_cookies(url: str, cookie_header: str, timeout: float = 5.0) -> tuple[int, str]:
req = urllib.request.Request(url, method="GET", headers={"Cookie": cookie_header})
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return resp.status, resp.read(800).decode("utf-8", errors="replace")
except urllib.error.HTTPError as exc:
return exc.code, exc.read(800).decode("utf-8", errors="replace")
except Exception as exc:
return 0, str(exc)
def _wesp_session_cookie() -> str:
data = json.dumps({"login": "admin", "password": "admin"}).encode("utf-8")
req = urllib.request.Request(
"http://127.0.0.1/api/auth/login",
data=data,
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=5) as resp:
return resp.headers.get("Set-Cookie", "").split(";")[0]
except Exception:
return ""
def main() -> None:
install_env = Path(__file__).resolve().parents[1] / "apps/api/data/secrets/install.env"
wesp_state = Path(__file__).resolve().parents[2] / "wesp/data/orchestrator_sync_state.json"
db_scheme = "missing"
if install_env.is_file():
for line in install_env.read_text(encoding="utf-8").splitlines():
if line.startswith("DATABASE_URL="):
db_scheme = "postgresql" if "postgresql" in line else ("sqlite" if "sqlite" in line else "other")
break
_log("H1", "smoke:install.env", "install.env database scheme", {"scheme": db_scheme, "path": str(install_env)})
orch_cfg = {"exists": wesp_state.is_file(), "enabled": False, "has_upstream": False}
if wesp_state.is_file():
try:
state = json.loads(wesp_state.read_text(encoding="utf-8"))
orch_cfg["enabled"] = bool(state.get("enabled"))
orch_cfg["has_upstream"] = bool(state.get("upstream_url"))
except json.JSONDecodeError:
orch_cfg["parse_error"] = True
_log("H3", "smoke:wesp-state", "WESP orchestrator sync state", orch_cfg)
health_code, health_body = _get("http://127.0.0.1:8000/api/v1/health")
_log("H1", "smoke:site-health", "site API health", {"status": health_code, "body": health_body[:120]})
login_code, login_body = _post_json(
"http://127.0.0.1:8000/api/v1/auth/login",
{"email": "admin@compton.example", "password": "Admin1234"},
headers={"Origin": "http://localhost:5173"},
)
_log(
"H4",
"smoke:site-login",
"site API login",
{"status": login_code, "ok": login_code == 200, "has_token": "access_token" in login_body},
)
wesp_code, wesp_body = _get("http://127.0.0.1/")
_log("H2", "smoke:wesp-root", "WESP hub root", {"status": wesp_code, "body_prefix": wesp_body[:80]})
web_code, _ = _get("http://127.0.0.1:5173/")
_log("H1", "smoke:site-web", "site web dev server", {"status": web_code})
caps_code, caps_body = _get("http://127.0.0.1:8000/api/v1/sync/capabilities")
_log("H5", "smoke:sync-capabilities", "orchestrator sync API", {"status": caps_code, "body": caps_body[:120]})
cookie = _wesp_session_cookie()
if cookie:
ctx_code, ctx_body = _get_with_cookies("http://127.0.0.1/api/v1/orchestrator/context", cookie)
ctx = {}
try:
ctx = json.loads(ctx_body)
except json.JSONDecodeError:
pass
_log(
"H3",
"smoke:wesp-orchestrator-context",
"WESP orchestrator context",
{
"status": ctx_code,
"configured": ctx.get("configured"),
"reachable": ctx.get("reachable"),
"enterpriseId": ctx.get("enterpriseId"),
},
)
print("Smoke complete — see debug log:", LOG_PATH)
print(f" site health: {health_code}, login: {login_code}, web: {web_code}")
print(f" wesp hub: {wesp_code}, install.env db: {db_scheme}, orch configured: {orch_cfg.get('has_upstream')}")
if __name__ == "__main__":
main()
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Mirror WESP zootech + admin static UI into site web public/static (1:1 paths).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
WESP_STATIC="${WESP_STATIC:-$ROOT/../wesp/static}"
DEST="$ROOT/apps/web/public/static"
ORCH_BOOT="$ROOT/apps/web/public/wesp/js/wesp-orchestrator-boot.js"
AUTH_BRIDGE_SRC="$ROOT/apps/web/public/wesp/js/wesp-orchestrator-auth-bridge.js"
if [ ! -d "$WESP_STATIC" ]; then
echo "WESP static not found: $WESP_STATIC" >&2
exit 1
fi
mkdir -p "$DEST"
# Full mirror: same /static/* and page HTML paths as on the WESP hub.
rsync -a --delete \
--exclude '.DS_Store' \
"$WESP_STATIC/" "$DEST/"
# Orchestrator-only adapters (never overwritten by hub copy).
mkdir -p "$DEST/js"
if [ -f "$ORCH_BOOT" ]; then
cp "$ORCH_BOOT" "$DEST/js/wesp-orchestrator-boot.js"
fi
if [ -f "$AUTH_BRIDGE_SRC" ]; then
cp "$AUTH_BRIDGE_SRC" "$DEST/js/wesp-orchestrator-auth-bridge.js"
fi
LOGIN_ADAPTER_SRC="$ROOT/apps/web/public/wesp/js/wesp-orchestrator-login.js"
if [ -f "$LOGIN_ADAPTER_SRC" ]; then
cp "$LOGIN_ADAPTER_SRC" "$DEST/js/wesp-orchestrator-login.js"
fi
echo "Synced WESP static from $WESP_STATIC -> $DEST"