feat: закрыть остаточный backlog
- keyset-пагинация аудита с курсором (occurred_at, id) и UI «Показать ещё» - пакетный /api/v1/labs/details и переход progress на один запрос - абсолютный потолок сессии 30 дней (identity V12 + web), очистка refresh - админская инвентаризация legacy-активов (/admin/legacy-assets, скрипт) - E2E в Chromium/Firefox/WebKit (18/18), CI preflight - LDD: 51 claim, 346 тестов, docs и handoff обновлены
This commit is contained in:
81
scripts/legacy-assets.sh
Executable file
81
scripts/legacy-assets.sh
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
# Инвентаризация legacy-активов (файлов без связи со статьёй) на работающем стенде.
|
||||
# Пример:
|
||||
# LMS_BASE_URL=https://lms.example.ru LMS_ADMIN_LOGIN=admin LMS_ADMIN_PASSWORD=... \
|
||||
# ./scripts/legacy-assets.sh
|
||||
set -euo pipefail
|
||||
|
||||
base_url="${LMS_BASE_URL:-http://localhost:8080}"
|
||||
admin_login="${LMS_ADMIN_LOGIN:-}"
|
||||
admin_password="${LMS_ADMIN_PASSWORD:-}"
|
||||
|
||||
if [[ -z "$admin_login" || -z "$admin_password" ]]; then
|
||||
echo "Задайте LMS_ADMIN_LOGIN и LMS_ADMIN_PASSWORD." >&2
|
||||
echo "Пример: LMS_BASE_URL=https://lms.example.ru LMS_ADMIN_LOGIN=admin LMS_ADMIN_PASSWORD=... $0" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
LMS_BASE_URL="${base_url%/}" \
|
||||
LMS_ADMIN_LOGIN="$admin_login" \
|
||||
LMS_ADMIN_PASSWORD="$admin_password" \
|
||||
python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
base_url = os.environ["LMS_BASE_URL"]
|
||||
admin_login = os.environ["LMS_ADMIN_LOGIN"]
|
||||
admin_password = os.environ["LMS_ADMIN_PASSWORD"]
|
||||
|
||||
|
||||
def request(method, path, payload=None, token=None):
|
||||
body = None if payload is None else json.dumps(payload).encode("utf-8")
|
||||
headers = {"Accept": "application/json"}
|
||||
if body is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
if token:
|
||||
headers["Authorization"] = "Bearer " + token
|
||||
req = urllib.request.Request(base_url + path, data=body, headers=headers, method=method)
|
||||
try:
|
||||
return urllib.request.urlopen(req, timeout=30)
|
||||
except urllib.error.HTTPError as exc:
|
||||
detail = exc.read().decode("utf-8", "replace").strip()
|
||||
message = f"{method} {path}: HTTP {exc.code}"
|
||||
if detail:
|
||||
message += f": {detail}"
|
||||
print(message, file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
except urllib.error.URLError as exc:
|
||||
print(f"Не удалось подключиться к {base_url}: {exc.reason}", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
with request("POST", "/api/v1/auth/login", {"login": admin_login, "password": admin_password}) as response:
|
||||
login_body = response.read().decode("utf-8")
|
||||
|
||||
try:
|
||||
token = json.loads(login_body)["token"]
|
||||
except (ValueError, KeyError, TypeError):
|
||||
print("Ответ /api/v1/auth/login не содержит token.", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
with request("GET", "/api/v1/content/legacy-assets", token=token) as response:
|
||||
try:
|
||||
assets = json.load(response)
|
||||
except ValueError:
|
||||
print("Ответ /api/v1/content/legacy-assets не является JSON.", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
if not isinstance(assets, list):
|
||||
print("Неожиданный формат /api/v1/content/legacy-assets: ожидался список.", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
for asset in assets:
|
||||
if not isinstance(asset, str):
|
||||
print(f"Пропущен нестроковый элемент: {asset!r}", file=sys.stderr)
|
||||
continue
|
||||
print(asset)
|
||||
print(f"Всего legacy-активов: {len(assets)}", file=sys.stderr)
|
||||
PY
|
||||
Reference in New Issue
Block a user