This commit is contained in:
2026-06-13 22:47:31 +02:00
parent add3da5177
commit d1446fc452
8032 changed files with 2650751 additions and 1551 deletions

View File

@@ -0,0 +1 @@
Lokale Diagnose-/Extraktions-Hilfsdateien. Nicht Teil des Release-Candidates.

View File

@@ -0,0 +1 @@
backup_doku_prompt_publish_ui_fix_20260610_225613

View File

@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
"""Erstellt AZA_PRE_RELEASE_BACKUP mit ZIP und SHA256."""
from __future__ import annotations
import hashlib
import os
import shutil
import subprocess
import zipfile
from datetime import datetime
ROOT = os.path.dirname(os.path.abspath(__file__))
TS = datetime.now().strftime("%Y%m%d_%H%M%S")
BAK = os.path.join(ROOT, f"AZA_PRE_RELEASE_BACKUP_{TS}")
ZIP_PATH = BAK + ".zip"
INCLUDE_DIRS = ("web", "installer", "deploy", "assets", "release")
INCLUDE_GLOBS = ("*.py", "*.spec", "build_test*.ps1", "start_doku_prompt_test.ps1", "_test_*.py")
SNAPSHOT_BUILDS = (
"test_translator_presentation_placeholder_v1",
"test_chat_only_host_update_v1",
"test_final_release_candidate_v1",
)
def _git_out(args: list[str]) -> str:
try:
return subprocess.check_output(["git"] + args, cwd=ROOT, text=True, stderr=subprocess.DEVNULL)
except Exception:
return ""
os.makedirs(BAK, exist_ok=True)
for sub in ("git_meta", "dist_snapshots", "release_meta"):
os.makedirs(os.path.join(BAK, sub), exist_ok=True)
(open(os.path.join(BAK, "README_RESTORE.txt"), "w", encoding="utf-8").write(
"AZA Pre-Release Backup. Restore: Dateien zurueckkopieren oder git checkout.\n"
))
for name, content in {
"git_branch.txt": _git_out(["rev-parse", "--abbrev-ref", "HEAD"]),
"git_commit.txt": _git_out(["rev-parse", "HEAD"]),
"git_status_short.txt": _git_out(["status", "--short"]),
"git_diff_stat.txt": _git_out(["diff", "--stat"]),
"git_untracked.txt": _git_out(["ls-files", "--others", "--exclude-standard"]),
}.items():
with open(os.path.join(BAK, "git_meta", name), "w", encoding="utf-8") as fh:
fh.write(content)
diff = _git_out(["diff"])
if diff:
with open(os.path.join(BAK, "git_meta", "git_diff.patch"), "w", encoding="utf-8") as fh:
fh.write(diff)
for fn in os.listdir(ROOT):
if fn.endswith(".py") or fn.endswith(".spec") or fn.startswith("build_test") or fn == "start_doku_prompt_test.ps1":
if os.path.isfile(os.path.join(ROOT, fn)):
shutil.copy2(os.path.join(ROOT, fn), os.path.join(BAK, fn))
for d in INCLUDE_DIRS:
src = os.path.join(ROOT, d)
if os.path.isdir(src):
shutil.copytree(src, os.path.join(BAK, d), dirs_exist_ok=True)
for tb in SNAPSHOT_BUILDS:
src = os.path.join(ROOT, "dist", tb)
if os.path.isdir(src):
shutil.copytree(src, os.path.join(BAK, "dist_snapshots", tb), dirs_exist_ok=True)
vj = os.path.join(ROOT, "release", "version.json")
if os.path.isfile(vj):
shutil.copy2(vj, os.path.join(BAK, "release_meta", "version.json"))
bi = os.path.join(ROOT, "_build_info.py")
if os.path.isfile(bi):
shutil.copy2(bi, os.path.join(BAK, "release_meta", "_build_info.py"))
with zipfile.ZipFile(ZIP_PATH, "w", zipfile.ZIP_DEFLATED) as zf:
for dirpath, _, files in os.walk(BAK):
for fn in files:
full = os.path.join(dirpath, fn)
arc = os.path.relpath(full, os.path.dirname(BAK))
zf.write(full, arc)
h = hashlib.sha256()
with open(ZIP_PATH, "rb") as fh:
for chunk in iter(lambda: fh.read(1024 * 1024), b""):
h.update(chunk)
print("BACKUP_DIR=", os.path.basename(BAK))
print("ZIP=", os.path.basename(ZIP_PATH))
print("SHA256=", h.hexdigest())
print("SIZE=", os.path.getsize(ZIP_PATH))

View File

@@ -0,0 +1,52 @@
# One-off extractor — not part of product
from pathlib import Path
src = Path("basis14.py").read_text(encoding="utf-8").splitlines()
ranges = [(2923, 3224), (14522, 15558)]
chunks: list[str] = []
for a, b in ranges:
chunks.extend(src[a - 1 : b])
header = '''# -*- coding: utf-8 -*-
"""Gemeinsame Empfang-/Chat-Desktop-Logik (Singleton Huelle + Kontaktpanel)."""
from __future__ import annotations
import hashlib
import os
import re
import subprocess
import sys
import threading
import time
import ctypes
from ctypes import wintypes
from pathlib import Path
from typing import Optional, Tuple
from urllib.parse import quote
import requests
import tkinter as tk
from tkinter import messagebox
from aza_empfang_identity import _empfang_identity_key
def init_empfang_desktop_core_state(host) -> None:
host._empfang_webview_proc = None
host._empfang_webview_last_child_pid = None
host._kontakt_panel_proc = None
host._kontakt_panel_last_child_pid = None
host._kontakt_panel_inflight_until = 0.0
host._kontakt_panel_launch_ts = 0.0
host._kontakt_panel_launch_lock = threading.Lock()
host._aza_tracked_external_pids = set()
host._empfang_webview_launch_lock = threading.Lock()
host._empfang_webview_launch_inflight_until = 0.0
host._empfang_webview_launch_ts = 0.0
class EmpfangDesktopCoreMixin:
'''
body = "\n".join(chunks)
Path("aza_empfang_desktop_core.py").write_text(header + body + "\n", encoding="utf-8")
print("written", len(body.splitlines()), "lines")

View File

@@ -0,0 +1,20 @@
# One-off extractor
from pathlib import Path
src = Path("basis14.py").read_text(encoding="utf-8").splitlines()
a, b = 10302, 10490
chunks = src[a - 1 : b]
header = '''# -*- coding: utf-8 -*-
"""Gemeinsame Nachrichten-Poll- und Popup-Helfer fuer Office- und Chat-Host."""
from __future__ import annotations
import threading
import time
import requests
class EmpfangHostPollMixin:
'''
Path("aza_empfang_host_poll.py").write_text(header + "\n".join(chunks) + "\n", encoding="utf-8")
print("poll mixin", len(chunks), "lines")

View File

@@ -0,0 +1,12 @@
# One-off: remove lines moved to aza_empfang_desktop_core.py
from pathlib import Path
path = Path("basis14.py")
lines = path.read_text(encoding="utf-8").splitlines(keepends=True)
remove = set()
for a, b in ((2923, 3224), (14522, 15558)):
for i in range(a - 1, b):
remove.add(i)
new_lines = [ln for i, ln in enumerate(lines) if i not in remove]
path.write_text("".join(new_lines), encoding="utf-8")
print("removed", len(remove), "lines; new total", len(new_lines))

View File

@@ -0,0 +1,9 @@
# One-off: remove poll mixin lines from basis14
from pathlib import Path
path = Path("basis14.py")
lines = path.read_text(encoding="utf-8").splitlines(keepends=True)
remove = set(range(10302 - 1, 10490))
new_lines = [ln for i, ln in enumerate(lines) if i not in remove]
path.write_text("".join(new_lines), encoding="utf-8")
print("removed poll lines")

View File

@@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
"""Einmaliger Backend-Deploy-Smoke-Test — gibt keine Secrets aus."""
from __future__ import annotations
import json
import os
import sys
import urllib.error
import urllib.parse
import urllib.request
BASE = "https://api.aza-medwork.ch"
ROOT = os.path.dirname(os.path.abspath(__file__))
TOKEN_PATH = os.path.join(ROOT, "backend_token.txt")
def _read_token() -> str:
with open(TOKEN_PATH, encoding="utf-8") as fh:
return fh.read().strip()
def _req(method: str, path: str, *, headers: dict | None = None, body: dict | None = None) -> tuple[int, dict | str]:
url = BASE + path
hdrs = dict(headers or {})
data = None
if body is not None:
data = json.dumps(body).encode("utf-8")
hdrs.setdefault("Content-Type", "application/json")
req = urllib.request.Request(url, data=data, headers=hdrs, method=method)
try:
with urllib.request.urlopen(req, timeout=20) as resp:
raw = resp.read().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().decode("utf-8", errors="replace")
try:
return exc.code, json.loads(raw)
except json.JSONDecodeError:
return exc.code, raw
def main() -> int:
results: list[str] = []
ok = True
code, _data = _req("GET", "/health")
results.append(f"health: {code}")
if code != 200:
ok = False
tok = _read_token()
auth = {"X-API-Token": tok}
pid = (os.environ.get("SMOKE_PRACTICE_ID") or "").strip()
email = (os.environ.get("SMOKE_LICENSE_EMAIL") or "").strip()
hdrs_base = {**auth, "X-User-Id": "smoke_test_user"}
if pid:
hdrs_base["X-Practice-Id"] = pid
code, data = _req("GET", "/v1/doku-prompts/public", headers=hdrs_base)
results.append(f"doku_public_list: {code}")
if code == 200 and isinstance(data, dict):
results.append(f" count={data.get('count', '?')}")
elif code not in (200, 403):
ok = False
code, data = _req("GET", "/v1/library/public", headers=hdrs_base)
results.append(f"library_public_list: {code}")
if code == 200 and isinstance(data, dict):
results.append(f" count={data.get('count', '?')}")
elif code not in (200, 403):
ok = False
pub_id = ""
if pid:
hdrs = {**auth, "X-Practice-Id": pid, "X-User-Id": "smoke_test_user"}
payload = {
"doc_type": "verlauf",
"title": "AZA Smoke Test Vorlage",
"description": "Smoke-Test, kann gelöscht werden.",
"content": "Dies ist eine technische Testvorlage ohne Patientendaten.",
"author_display": "AzA Smoke Test",
"city": "Winterthur",
"specialty": "Dermatologie",
"language": "de",
"visibility": "public",
}
code, data = _req("POST", "/v1/doku-prompts/publish", headers=hdrs, body=payload)
results.append(f"doku_publish: {code}")
if code == 200 and isinstance(data, dict) and data.get("ok"):
pub_id = str(data.get("id") or "")
results.append(f" published_id={pub_id[:24]}..." if len(pub_id) > 24 else f" published_id={pub_id}")
else:
detail = data.get("detail") if isinstance(data, dict) else str(data)[:120]
results.append(f" detail={detail}")
if code not in (200, 403):
ok = False
if pub_id:
code, data = _req("POST", f"/v1/doku-prompts/unpublish/{pub_id}", headers=hdrs, body={})
results.append(f"doku_unpublish: {code}")
if code != 200:
ok = False
else:
results.append("doku_publish: SKIPPED (SMOKE_PRACTICE_ID not set)")
results.append("doku_unpublish: SKIPPED")
lic_path = "/license/status"
if email:
lic_path += "?" + urllib.parse.urlencode({"email": email})
lic_hdrs = {**auth}
if pid:
lic_hdrs["X-Practice-Id"] = pid
lic_hdrs["X-Device-Id"] = "smoke-device-readonly"
code, data = _req("GET", lic_path, headers=lic_hdrs)
results.append(f"license_status: {code}")
if isinstance(data, dict):
for k in (
"chat_device_limit", "chat_devices_used", "contributing_office_licenses",
"chat_devices_per_license", "valid", "device_allowed", "reason",
):
if k in data:
results.append(f" {k}={data[k]}")
print("\n".join(results))
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,8 @@
import sqlite3
c = sqlite3.connect("/app/data/stripe_webhook.sqlite")
r = c.execute(
"SELECT customer_email FROM licenses WHERE customer_email IS NOT NULL "
"AND TRIM(customer_email) != '' LIMIT 1"
).fetchone()
if r:
print(r[0])

View File

@@ -0,0 +1,7 @@
import sqlite3
c = sqlite3.connect("/app/data/stripe_webhook.sqlite")
r = c.execute(
"SELECT customer_email FROM licenses WHERE lower(trim(coalesce(status,'')))='active' "
"AND trim(coalesce(practice_id,''))!='' LIMIT 1"
).fetchone()
print(r[0] if r and r[0] else "")

View File

@@ -0,0 +1,7 @@
import sqlite3
c = sqlite3.connect("/app/data/stripe_webhook.sqlite")
r = c.execute(
"SELECT practice_id FROM licenses WHERE lower(trim(coalesce(status,'')))='active' "
"AND trim(coalesce(practice_id,''))!='' LIMIT 1"
).fetchone()
print(r[0] if r else "NONE")

View File

@@ -0,0 +1,13 @@
import sqlite3
c = sqlite3.connect("/app/data/stripe_webhook.sqlite")
r = c.execute(
"SELECT practice_id, customer_email FROM licenses "
"WHERE lower(trim(coalesce(status,'')))='active' "
"AND trim(coalesce(practice_id,''))!='' LIMIT 1"
).fetchone()
if r:
print(r[0])
print("EMAIL_SET" if r[1] else "EMAIL_NONE")
else:
print("NONE")
print("EMAIL_NONE")

View File

@@ -0,0 +1 @@
{"ts": 1781301076.2451897, "source": "test2"}

View File

@@ -0,0 +1 @@
{"ts": 1, "mode": "direct", "peer_user_id": "old", "document_visible": true}