- 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 обновлены
82 lines
3.0 KiB
Bash
Executable File
82 lines
3.0 KiB
Bash
Executable File
#!/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
|