From 47e301a5b81612b8743957a89ea7d86ae0296d50 Mon Sep 17 00:00:00 2001 From: dmitriylaukhin Date: Sat, 27 Jun 2026 19:27:22 +0500 Subject: [PATCH] =?UTF-8?q?feat(ws6):=20SQL-=D1=81=D0=BA=D1=80=D0=B8=D0=BF?= =?UTF-8?q?=D1=82=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BA=D1=83=D1=80=D1=81=D0=BE=D0=B2-=D0=BD=D0=BE=D1=81=D0=B8?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D0=B5=D0=B9=20biz-brain=20+=20verify-=D1=81?= =?UTF-8?q?=D0=BA=D1=80=D0=B8=D0=BF=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scripts/create_biz_brain_courses.sql: идемпотентное создание 5 курсов (biz-brain-base, biz-brain-premium, module-caldav, module-ai-pdf, module-legacy) - scripts/verify_biz_brain_grant.sh: проверка /api/internal/grant (Нужен Дмитрий для запуска на проде) Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/create_biz_brain_courses.sql | 128 +++++++++++++++++++++++++++ scripts/verify_biz_brain_grant.sh | 108 ++++++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 scripts/create_biz_brain_courses.sql create mode 100755 scripts/verify_biz_brain_grant.sh diff --git a/scripts/create_biz_brain_courses.sql b/scripts/create_biz_brain_courses.sql new file mode 100644 index 0000000..125d3be --- /dev/null +++ b/scripts/create_biz_brain_courses.sql @@ -0,0 +1,128 @@ +-- ============================================================ +-- WS6: Создание курсов-носителей «Второй мозг для бизнеса» +-- ============================================================ +-- ВАЖНО: Выполнять НА ПРОДЕ только после проверки на staging. +-- Запускать через SSH: psql -f create_biz_brain_courses.sql +-- Идемпотентно: INSERT IGNORE по slug (уникальный ключ). +-- +-- Курсы-носители (5 штук): +-- biz-brain-base — основной курс (ZIP хранилища + видео-онбординг) +-- biz-brain-premium — премиум-раздел (консультации, инструкции по расширениям) +-- module-caldav — расширение CalDAV-календарь +-- module-ai-pdf — расширение AI-разбор PDF +-- module-legacy — расширение Legacy-миграция +-- ============================================================ + +BEGIN; + +-- Функция для генерации cuid-подобного ID (через gen_random_uuid как fallback) +-- В реальности Prisma генерирует cuid автоматически — используем это свойство. +-- Если у вас Postgres 14+: gen_random_uuid() дает UUID, но Prisma ожидает cuid. +-- Безопаснее создать через Prisma API или LMS-Admin. + +-- Проверяем что slug ещё не существует перед вставкой +DO $$ +BEGIN + -- biz-brain-base + IF NOT EXISTS (SELECT 1 FROM "Course" WHERE slug = 'biz-brain-base') THEN + INSERT INTO "Course" (id, slug, title, description, published, "allowAudio", "order", "createdAt", "updatedAt") + VALUES ( + 'clbizbrainbase0000001', + 'biz-brain-base', + 'Второй мозг для бизнеса — Базовый', + 'Готовое хранилище Obsidian для собственника: CRM-воронка, финансовый кокпит, журнал решений, витрина команды. Разовый платёж, бессрочный доступ.', + false, -- опубликовать вручную после наполнения контентом + false, + 100, + NOW(), + NOW() + ); + RAISE NOTICE 'Создан курс biz-brain-base'; + ELSE + RAISE NOTICE 'Курс biz-brain-base уже существует — пропуск'; + END IF; + + -- biz-brain-premium + IF NOT EXISTS (SELECT 1 FROM "Course" WHERE slug = 'biz-brain-premium') THEN + INSERT INTO "Course" (id, slug, title, description, published, "allowAudio", "order", "createdAt", "updatedAt") + VALUES ( + 'clbizbrainprem0000002', + 'biz-brain-premium', + 'Второй мозг для бизнеса — Премиум (инструкции и консультации)', + 'Раздел для премиум-покупателей: инструкции по установке расширений, запись на 2 персональные консультации, ответы на частые вопросы.', + false, + false, + 101, + NOW(), + NOW() + ); + RAISE NOTICE 'Создан курс biz-brain-premium'; + ELSE + RAISE NOTICE 'Курс biz-brain-premium уже существует — пропуск'; + END IF; + + -- module-caldav + IF NOT EXISTS (SELECT 1 FROM "Course" WHERE slug = 'module-caldav') THEN + INSERT INTO "Course" (id, slug, title, description, published, "allowAudio", "order", "createdAt", "updatedAt") + VALUES ( + 'clmodulecaldav00000003', + 'module-caldav', + 'Расширение: CalDAV-календарь', + 'Двусторонняя синхронизация задач и событий Obsidian с Google Calendar / Apple Calendar через CalDAV. Плагин Full Calendar Remastered v0.13.2.', + false, + false, + 200, + NOW(), + NOW() + ); + RAISE NOTICE 'Создан курс module-caldav'; + ELSE + RAISE NOTICE 'Курс module-caldav уже существует — пропуск'; + END IF; + + -- module-ai-pdf + IF NOT EXISTS (SELECT 1 FROM "Course" WHERE slug = 'module-ai-pdf') THEN + INSERT INTO "Course" (id, slug, title, description, published, "allowAudio", "order", "createdAt", "updatedAt") + VALUES ( + 'clmoduleaipdf000000004', + 'module-ai-pdf', + 'Расширение: AI-разбор PDF и исследований', + 'Промпты А1/А2/А3 для структурированного разбора PDF-документов, статей и исследований с помощью AI. Включает эталонный прогон и демо-PDF.', + false, + false, + 201, + NOW(), + NOW() + ); + RAISE NOTICE 'Создан курс module-ai-pdf'; + ELSE + RAISE NOTICE 'Курс module-ai-pdf уже существует — пропуск'; + END IF; + + -- module-legacy + IF NOT EXISTS (SELECT 1 FROM "Course" WHERE slug = 'module-legacy') THEN + INSERT INTO "Course" (id, slug, title, description, published, "allowAudio", "order", "createdAt", "updatedAt") + VALUES ( + 'clmodulelegacy00000005', + 'module-legacy', + 'Расширение: Legacy-миграция из Evernote / Notion', + 'Пошаговые инструкции и чеклист пост-обработки для переноса данных из Evernote и Notion в хранилище «Второй мозг для бизнеса».', + false, + false, + 202, + NOW(), + NOW() + ); + RAISE NOTICE 'Создан курс module-legacy'; + ELSE + RAISE NOTICE 'Курс module-legacy уже существует — пропуск'; + END IF; +END +$$; + +COMMIT; + +-- Проверка результата +SELECT slug, title, published, "order" FROM "Course" +WHERE slug IN ('biz-brain-base', 'biz-brain-premium', 'module-caldav', 'module-ai-pdf', 'module-legacy') +ORDER BY "order"; diff --git a/scripts/verify_biz_brain_grant.sh b/scripts/verify_biz_brain_grant.sh new file mode 100755 index 0000000..08cd37e --- /dev/null +++ b/scripts/verify_biz_brain_grant.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# ============================================================ +# WS6: Проверка /api/internal/grant для biz-brain-* курсов +# ============================================================ +# Использование: +# LMS_URL=https://school.second-brain.ru \ +# LMS_SECRET= \ +# TEST_EMAIL=laukhin@dementiy.com \ +# bash verify_biz_brain_grant.sh +# +# Проверяет: +# 1. Grant base-курса (biz-brain-base) +# 2. Grant premium-бандла (все 5 курсов) +# 3. Идемпотентность (повтор того же order_id) +# ============================================================ + +set -euo pipefail + +LMS_URL="${LMS_URL:-https://school.second-brain.ru}" +LMS_SECRET="${LMS_SECRET:?Задай переменную LMS_SECRET}" +TEST_EMAIL="${TEST_EMAIL:-laukhin@dementiy.com}" + +echo "=== WS6: Проверка grant-эндпоинта LMS ===" +echo " LMS: $LMS_URL" +echo " Email: $TEST_EMAIL" +echo "" + +# ── Тест 1: Базовый бандл ──────────────────────────────────────────────────── +echo "[1] Grant biz-brain-base..." +RESPONSE=$(curl -sf -X POST "$LMS_URL/api/internal/grant" \ + -H "Content-Type: application/json" \ + -H "x-internal-secret: $LMS_SECRET" \ + -d '{ + "email": "'"$TEST_EMAIL"'", + "name": "WS6 Test", + "course_slugs": ["biz-brain-base"], + "order_id": "ws6-test-base-001", + "payment_system": "robokassa" + }') + +echo " Ответ: $RESPONSE" + +GRANTED=$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('granted',''))") +if [[ "$GRANTED" == *"biz-brain-base"* ]]; then + echo " ✅ biz-brain-base выдан" +else + IDEMPOTENT=$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('idempotent',''))") + if [[ "$IDEMPOTENT" == "True" ]]; then + echo " ℹ️ Идемпотентный ответ (уже выдан ранее)" + else + echo " ❌ ОШИБКА: biz-brain-base не выдан. Ответ: $RESPONSE" + echo " Убедись что курс biz-brain-base создан в LMS!" + exit 1 + fi +fi + +echo "" + +# ── Тест 2: Премиум-бандл ──────────────────────────────────────────────────── +echo "[2] Grant biz-brain-premium (5 курсов)..." +RESPONSE=$(curl -sf -X POST "$LMS_URL/api/internal/grant" \ + -H "Content-Type: application/json" \ + -H "x-internal-secret: $LMS_SECRET" \ + -d '{ + "email": "'"$TEST_EMAIL"'", + "name": "WS6 Test", + "course_slugs": ["biz-brain-base", "biz-brain-premium", "module-caldav", "module-ai-pdf", "module-legacy"], + "order_id": "ws6-test-premium-001", + "payment_system": "robokassa" + }') + +echo " Ответ: $RESPONSE" + +UNRESOLVED=$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('unresolved',[]))") +if [[ "$UNRESOLVED" == "[]" ]]; then + echo " ✅ Все 5 курсов разрешены и выданы" +else + echo " ⚠️ Не разрешены: $UNRESOLVED" + echo " Создай недостающие курсы в LMS (slug'и должны точно совпадать)" +fi + +echo "" + +# ── Тест 3: Идемпотентность ────────────────────────────────────────────────── +echo "[3] Повторный grant того же order_id (должен вернуть idempotent=true)..." +RESPONSE=$(curl -sf -X POST "$LMS_URL/api/internal/grant" \ + -H "Content-Type: application/json" \ + -H "x-internal-secret: $LMS_SECRET" \ + -d '{ + "email": "'"$TEST_EMAIL"'", + "name": "WS6 Test", + "course_slugs": ["biz-brain-base"], + "order_id": "ws6-test-base-001", + "payment_system": "robokassa" + }') + +IDEMPOTENT=$(echo "$RESPONSE" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('idempotent',''))" 2>/dev/null || echo "") +if [[ "$IDEMPOTENT" == "True" ]]; then + echo " ✅ Идемпотентность работает (повторный вызов вернул idempotent=true)" +else + echo " ⚠️ Ожидался idempotent=true, получено: $RESPONSE" +fi + +echo "" +echo "=== Проверка завершена ===" +echo "" +echo "Следующий шаг: опубликовать курсы в admin (published=true) после наполнения контентом." +echo " school.second-brain.ru/admin/courses"