- 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 обновлены
208 lines
10 KiB
Bash
Executable File
208 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# LDD migration evidence: master-схема + данные -> миграции ветки.
|
||
# Одноразовая БД, рабочие данные не затрагиваются.
|
||
set -euo pipefail
|
||
|
||
cd "$(dirname "$0")/.."
|
||
|
||
DB="lms_ldd_mig_$(date -u +%H%M%S)_$$"
|
||
EVIDENCE="build/evidence/migration.json"
|
||
POSTGRES_WAS_RUNNING=0
|
||
FAILED=0
|
||
|
||
log() { echo ">> $*"; }
|
||
|
||
cleanup() {
|
||
local code=$?
|
||
docker exec -i lms-postgres psql -U lms -d postgres -v ON_ERROR_STOP=1 \
|
||
-c "DROP DATABASE IF EXISTS $DB;" >/dev/null 2>&1 || true
|
||
if ((POSTGRES_WAS_RUNNING == 0)); then
|
||
docker compose stop postgres >/dev/null 2>&1 || true
|
||
fi
|
||
exit $code
|
||
}
|
||
trap cleanup EXIT INT TERM
|
||
|
||
command -v docker >/dev/null 2>&1 || { echo "Нужен docker" >&2; exit 1; }
|
||
git rev-parse --verify master >/dev/null 2>&1 || { echo "Нужна ветка master в git" >&2; exit 1; }
|
||
|
||
if docker compose ps --status running --services 2>/dev/null | grep -qx postgres; then
|
||
POSTGRES_WAS_RUNNING=1
|
||
fi
|
||
docker compose up -d postgres >/dev/null
|
||
for _ in $(seq 1 40); do
|
||
docker exec lms-postgres pg_isready -U lms -d postgres >/dev/null 2>&1 && break
|
||
sleep 1
|
||
done
|
||
docker exec lms-postgres pg_isready -U lms -d postgres >/dev/null
|
||
docker exec -i lms-postgres psql -U lms -d postgres -v ON_ERROR_STOP=1 \
|
||
-c "CREATE DATABASE $DB;" >/dev/null
|
||
|
||
mkdir -p build/evidence
|
||
|
||
apply() {
|
||
docker exec -i lms-postgres psql -U lms -d "$DB" -v ON_ERROR_STOP=1 >/tmp/lms-ldd-migration.log
|
||
}
|
||
|
||
log "Разворачиваю master-схему и синтетические данные..."
|
||
{
|
||
git show master:services/identity-service/src/main/resources/db/migration/V1__init.sql
|
||
echo 'SET search_path TO identity;'
|
||
git show master:services/identity-service/src/main/resources/db/migration/V2__accounts.sql
|
||
git show master:services/identity-service/src/main/resources/db/migration/V3__login_attempts.sql
|
||
cat <<'SQL'
|
||
INSERT INTO accounts (id, login, password_hash, role, must_change_password) VALUES
|
||
('00000000-0000-7000-8000-000000001001','admin','x','ADMIN',false),
|
||
('00000000-0000-7000-8000-000000001002','s1','x','STUDENT',false);
|
||
INSERT INTO students (account_id, student_no, last_name, first_name) VALUES
|
||
('00000000-0000-7000-8000-000000001002','S1','Иванов','Иван');
|
||
INSERT INTO groups (id, name) VALUES ('00000000-0000-7000-8000-000000002001','ПИ-11БО');
|
||
INSERT INTO enrollments (group_id, student_id) VALUES
|
||
('00000000-0000-7000-8000-000000002001','00000000-0000-7000-8000-000000001002');
|
||
SQL
|
||
echo 'SET search_path TO academic;'
|
||
git show master:services/academic-service/src/main/resources/db/migration/V1__init.sql
|
||
git show master:services/academic-service/src/main/resources/db/migration/V2__schema.sql
|
||
git show master:services/academic-service/src/main/resources/db/migration/V3__seed.sql
|
||
git show master:services/academic-service/src/main/resources/db/migration/V4__content.sql
|
||
git show master:services/academic-service/src/main/resources/db/migration/V5__content_published.sql
|
||
cat <<'SQL'
|
||
INSERT INTO labs (id, number, title, semester_id) VALUES
|
||
('00000000-0000-7000-8000-000000003001',1,'ЛР 1','00000000-0000-7000-8000-000000000001');
|
||
INSERT INTO chapters (id, title, slug) VALUES
|
||
('00000000-0000-7000-8000-000000003002','Глава','osnovy-c');
|
||
SQL
|
||
echo 'SET search_path TO progress;'
|
||
git show master:services/progress-service/src/main/resources/db/migration/V1__init.sql
|
||
git show master:services/progress-service/src/main/resources/db/migration/V2__schema.sql
|
||
git show master:services/progress-service/src/main/resources/db/migration/V3__grades.sql
|
||
git show master:services/progress-service/src/main/resources/db/migration/V4__submissions.sql
|
||
git show master:services/progress-service/src/main/resources/db/migration/V5__defense_sessions.sql
|
||
git show master:services/progress-service/src/main/resources/db/migration/V6__grade_comments.sql
|
||
git show master:services/progress-service/src/main/resources/db/migration/V7__grade_publications.sql
|
||
git show master:services/progress-service/src/main/resources/db/migration/V8__grade_publication_students.sql
|
||
git show master:services/progress-service/src/main/resources/db/migration/V9__grade_general_comment.sql
|
||
git show master:services/progress-service/src/main/resources/db/migration/V11__attestations.sql
|
||
git show master:services/progress-service/src/main/resources/db/migration/V12__attestation_semester_and_publication.sql
|
||
cat <<'SQL'
|
||
INSERT INTO lab_grades (student_id, lab_id, score) VALUES
|
||
('00000000-0000-7000-8000-000000001002','00000000-0000-7000-8000-000000003001',4.5);
|
||
INSERT INTO grade_comments (student_id, lab_id, text) VALUES
|
||
('00000000-0000-7000-8000-000000001002','00000000-0000-7000-8000-000000003001','ok');
|
||
SQL
|
||
} | apply
|
||
|
||
log "Применяю миграции ветки..."
|
||
{
|
||
echo 'SET search_path TO identity;'
|
||
cat services/identity-service/src/main/resources/db/migration/V4__roles.sql
|
||
cat services/identity-service/src/main/resources/db/migration/V5__groups_cohorts.sql
|
||
cat services/identity-service/src/main/resources/db/migration/V6__student_statuses.sql
|
||
cat services/identity-service/src/main/resources/db/migration/V7__promotion.sql
|
||
cat services/identity-service/src/main/resources/db/migration/V8__membership_history.sql
|
||
cat services/identity-service/src/main/resources/db/migration/V9__account_statuses.sql
|
||
cat services/identity-service/src/main/resources/db/migration/V10__audit_events.sql
|
||
cat services/identity-service/src/main/resources/db/migration/V11__refresh_tokens.sql
|
||
cat services/identity-service/src/main/resources/db/migration/V12__refresh_absolute.sql
|
||
cat services/identity-service/src/main/resources/db/migration/V13__audit_keyset_indexes.sql
|
||
echo 'SET search_path TO academic;'
|
||
cat services/academic-service/src/main/resources/db/migration/V6__courses.sql
|
||
cat services/academic-service/src/main/resources/db/migration/V7__labs_course.sql
|
||
cat services/academic-service/src/main/resources/db/migration/V8__chapters_course.sql
|
||
cat services/academic-service/src/main/resources/db/migration/V9__article_assets_indexes.sql
|
||
cat services/academic-service/src/main/resources/db/migration/V10__course_title_unique.sql
|
||
cat services/academic-service/src/main/resources/db/migration/V11__global_audience_all.sql
|
||
cat services/academic-service/src/main/resources/db/migration/V12__asset_links_backfill.sql
|
||
echo 'SET search_path TO progress;'
|
||
cat services/progress-service/src/main/resources/db/migration/V13__profile_authors.sql
|
||
} | apply
|
||
|
||
log "Проверяю postflight..."
|
||
CHECK_SQL=$(cat <<'SQL'
|
||
SET search_path TO identity;
|
||
SELECT 'audit_events=' || count(*) FROM information_schema.tables
|
||
WHERE table_schema='identity' AND table_name='audit_events';
|
||
SELECT 'refresh_tokens=' || count(*) FROM information_schema.tables
|
||
WHERE table_schema='identity' AND table_name='refresh_tokens';
|
||
SELECT 'refresh_absolute=' || count(*) FROM information_schema.columns
|
||
WHERE table_schema='identity' AND table_name='refresh_tokens' AND column_name='absolute_expires_at';
|
||
SELECT 'audit_keyset_idx=' || count(*) FROM pg_indexes
|
||
WHERE schemaname='identity' AND indexname='audit_events_occurred_id_idx';
|
||
SELECT 'students=' || count(*) FROM students;
|
||
SELECT 'groups=' || count(*) FROM groups;
|
||
SELECT 'history=' || count(*) FROM membership_history;
|
||
SET search_path TO academic;
|
||
SELECT 'labs_course_id=' || count(*) FROM information_schema.columns
|
||
WHERE table_schema='academic' AND table_name='labs' AND column_name='course_id';
|
||
SELECT 'chapters_course_id=' || count(*) FROM information_schema.columns
|
||
WHERE table_schema='academic' AND table_name='chapters' AND column_name='course_id';
|
||
SELECT 'article_assets=' || count(*) FROM information_schema.tables
|
||
WHERE table_schema='academic' AND table_name='article_assets';
|
||
SELECT 'labs=' || count(*) FROM labs;
|
||
SELECT 'chapters=' || count(*) FROM chapters;
|
||
SET search_path TO progress;
|
||
SELECT 'grade_author=' || count(*) FROM information_schema.columns
|
||
WHERE table_schema='progress' AND table_name='grade_comments' AND column_name='author_account_id';
|
||
SELECT 'grade_updated_by=' || count(*) FROM information_schema.columns
|
||
WHERE table_schema='progress' AND table_name='lab_grades' AND column_name='updated_by_account_id';
|
||
SELECT 'grades=' || count(*) FROM lab_grades;
|
||
SELECT 'comments=' || count(*) FROM grade_comments;
|
||
SQL
|
||
)
|
||
RESULT=$(docker exec -i lms-postgres psql -U lms -d "$DB" -At -v ON_ERROR_STOP=1 -c "$CHECK_SQL")
|
||
|
||
# V13 повторно: идемпотентность.
|
||
if { echo 'SET search_path TO progress;'; cat services/progress-service/src/main/resources/db/migration/V13__profile_authors.sql; } | apply; then
|
||
V13_TWICE="passed"
|
||
else
|
||
V13_TWICE="failed"
|
||
FAILED=1
|
||
fi
|
||
|
||
printf '%s\n' "$RESULT" | sed 's/^/ /'
|
||
|
||
python3 - "$EVIDENCE" "$RESULT" "$V13_TWICE" <<'PY'
|
||
import json
|
||
import sys
|
||
|
||
out, result, v13 = sys.argv[1], sys.argv[2], sys.argv[3]
|
||
required = {
|
||
"audit_events=1",
|
||
"refresh_tokens=1",
|
||
"refresh_absolute=1",
|
||
"audit_keyset_idx=1",
|
||
"students=1",
|
||
"groups=1",
|
||
"history=1",
|
||
"labs_course_id=1",
|
||
"chapters_course_id=1",
|
||
"article_assets=1",
|
||
"labs=1",
|
||
"chapters=1",
|
||
"grade_author=1",
|
||
"grade_updated_by=1",
|
||
"grades=1",
|
||
"comments=1",
|
||
}
|
||
lines = {line.strip() for line in result.splitlines() if line.strip()}
|
||
missing = sorted(required - lines)
|
||
failed = bool(missing) or v13 != "passed"
|
||
if missing:
|
||
status = "failed"
|
||
detail = "postflight не подтвердил: " + ", ".join(missing)
|
||
elif v13 != "passed":
|
||
status = "failed"
|
||
detail = "V13 не применилась повторно"
|
||
else:
|
||
status = "passed"
|
||
detail = "master->branch: все postflight-проверки и повторная V13 успешны"
|
||
checks = [{"id": "migration.master_upgrade", "status": status, "detail": detail}]
|
||
with open(out, "w", encoding="utf-8") as fh:
|
||
now = __import__("datetime").datetime.now(__import__("datetime").UTC).isoformat()
|
||
json.dump({"generatedAt": now, "checks": checks}, fh, ensure_ascii=False, indent=2)
|
||
print(("PASS " if not failed else "FAIL ") + "migration.master_upgrade: " + detail)
|
||
sys.exit(1 if failed else 0)
|
||
PY
|
||
|
||
log "Migration evidence: $EVIDENCE"
|