Подробный чеклист для разработчиков: 43 шага в 5 фаз (+ шаги 0-3 «до старта»). Каждый технический шаг раскрывается в блок «Реализация» с реальным кодом, путями к файлам, схемами БД, командами, проверками и граблями из боевых продуктов GG - Saxon, Italy, SAT, Web-Main-Site.
База: оригинал плейбука (levauth) дословно + правки продуктового мита 17.09.2026 + разбор четырёх боевых репозиториев. Внутренний документ.
После договора три потока сразу, у каждого владелец. Продажи и рост - позже, когда продукт выверен на аудитории партнёра.
Самое важное и первое. Пока не на бумаге - не начинать.
Доли и деньги, скоуп, сроки, владение доменом/данными/аккаунтами, условия выхода, юрисдикция оплат и юрлицо (РФ vs Казахстан), механика выплат, атрибуция каналов и SEO. Один документ, обе стороны подписали.
Почему первым: без письменной фиксации любой спор = потерянное время и продукт.
General + рабочий + продакшн-чаты, Notion-доступ, Джи-джи в чат (тикеты → дашборды → репорты), синки, ДР-ритуал (birthdays.*, AI-фото).
Монтажёр (~$1k) + ~$500-800 на разработку. Партнёр снимает контент и прогрев, не ждёт платформы.
Структура/навигация под направление (лучшее из порталов, лейаут главной свой), бренд/суббренд/палитра (не яркий), каналы и тест-рынок на аудитории партнёра, YouTube на вырост.
Почему до кода: структуру и бренд дешевле решить на берегу - переделывать после постройки дороже всего.
Каркас, на котором держится всё. Только Лев на шагах 4-9 (инфра), дальше пацаны.
Отдельное приватное репо, Next.js (App Router, TS, output:'standalone'), свой поддомен, никаких общих либ, свой OpenRouter-ключ в Secrets Manager, доступ прогерам в репо + чат.
output: 'standalone', // docker-friendly билд (.next/standalone)
outputFileTracingRoot: CONFIG_DIR, // монорепо: не ломать pooler-URL Neon
async redirects(){ return [{ source:'/', destination: LANDING, permanent:true }] }
async headers(){ /* X-Content-Type-Options nosniff, Referrer-Policy, no-store для /api/* */ }
// SAT: rewrites() проксирует /api/* на BACKEND_INTERNAL_URL (http://127.0.0.1:3001)
Эталоны: Product-SAT/frontend/next.config.ts (полный), Product-Saxon-Portal/next.config.ts (минимальный).
"strict": true, "moduleResolution": "bundler", "jsx": "preserve",
"paths": { "@/*": ["./src/*"] }, "exclude": ["node_modules","scripts"]
next, react, react-dom,
@neondatabase/serverless, // Neon HTTP-драйвер
drizzle-orm, // ORM (или сырой SQL)
bcryptjs, // хэш паролей
jose // JWT (если AKB-auth)
scripts: dev/build/start (+ --webpack если нестандартный конфиг, как Italy)
node_modules/ .next/ out/ build/ .env .env.* *.tsbuildinfo next-env.d.ts .vercel .claude/
npm run build ок, есть .next/standalone/server.jsnpx tsc --noEmit = 0 ошибокЛокальный запуск, отдельная dev-база (Neon-ветка от прода), идемпотентные сиды, тест-карты Т-Банка. Демо только на отдельном роуте/поддомене.
Почему рано: без дев-окружения всё тестируется на проде - тест-заказы в живой базе.
neon branches create dev_<user> --parent main
neon connection-string dev_<user> # → DATABASE_URL в .env.local (pooled, ?sslmode=require)
neon branches reset dev_<user> # сброс = свежая копия main
DATABASE_URL=postgresql://...neon.tech/db?sslmode=require
JWT_SECRET=... # общий с AKB-хабом если AKB-auth
TG_BOT_TOKEN=... TG_CHAT_ID=...
PRODAMUS_SECRET_KEY=... TBANK_TERMINAL_KEY=... TBANK_PASSWORD=... TBANK_TEST_MODE=true
BACKEND_INTERNAL_URL=http://127.0.0.1:3001 # только сервер, не NEXT_PUBLIC_
const hash = await bcrypt.hash(password, 10)
await sql`INSERT INTO users (email,password_hash,name,role)
VALUES (${email},${hash},${name},'admin')
ON CONFLICT (email) DO UPDATE SET role='admin', is_active=true`
// запуск: DATABASE_URL=$U ADMIN_EMAIL=a@b.c node scripts/seed-admin.mjs
4300 0000 0000 0777, отказ 5000 0000 0000 0009, любой будущий exp/CVC--dry не пишет в БДКак только создан репо - карточка в levauth (статус «в работе»): имя, репо, поддомен, стек. Точка сборки.
Свой проект Neon Postgres, индексы сразу. Домен и хостинг купить заранее (разные бизнесы = разные домены), SEO-фаундейшн под каждый.
import { neon } from '@neondatabase/serverless'
import { drizzle } from 'drizzle-orm/neon-http'
import * as schema from './schema'
export const db = drizzle(neon(process.env.DATABASE_URL!), { schema })
users(id, email UNIQUE, password_hash, name, role[student|admin],
is_active, access_until, tg_user_id UNIQUE, tg_username, created_at)
sessions(id, user_id FK cascade, token_hash UNIQUE, expires_at, created_at)
+ index(user_id), index(expires_at)
progress(user_id FK, page_slug, completed_at) PRIMARY KEY(user_id,page_slug)
orders(id, order_id UNIQUE, provider, status[pending|completed|failed], kind[course|service],
amount /*копейки*/, email, user_id FK, payment_id, webhook_payload JSONB, promo_code, paid_at)
promo_codes(id, code UNIQUE, kind[percent|fixed], value, max_uses, used_count,
is_active, expires_at, note)
tg_messages(chat_id, message_id, user_id, username, text, kind, sent_at, raw JSONB)
tg_invites(email, link UNIQUE, code, used_by_tg_id, used_at)
tg_allowed(tg_user_id UNIQUE, username, name, note)
SAT - Prisma (LearningTopic / QuestionAttempt / PaymentOrder, ELO abilityMath/RW, league, UTM-атрибуция). Индексы на FK и (user_id, created_at desc).
amount ВСЕГДА в копейках (×100 от рублей). Не хардкодить имена таблиц в сырой SQL на общих БД - public.table.
Бэкап-таблицы *_YYYYMMDD + Neon PITR перед любой миграцией. Миграции только скриптами, идемпотентно. Массовые правки - на копии.
Грабля кровью (SAT банк-аудит): без бэкапа одна кривая миграция = день восстановления.
# Prisma (SAT): npx prisma migrate dev --name x / migrate deploy (прод)
# Drizzle (Saxon/Italy): node scripts/migrate-*.mjs (ALTER TABLE IF NOT EXISTS)
# бэкап перед миграцией:
neon branches create backup_$(дата) --parent main
# откат по времени (PITR, retention 7-30 дней):
neon branch restore main --timestamp '2026-09-15T10:30:00Z'
Email+пароль (bcrypt, httpOnly-сессии) и/или Telegram Login. Middleware закрывает весь контент.
const token = randomBytes(32).toString('hex') // 64-hex
db.insert(sessions).values({ userId, tokenHash: sha256(token), expiresAt })
cookie.set('sess', token, { httpOnly:true, secure:prod, sameSite:'lax', expires })
// валидация КАЖDЫЙ запрос: join sessions+users where token_hash=sha256(token)
// and expires_at>now and users.is_active → деактивация блокирует сразу
Хранить SHA256(token), не сам токен. Никакого JWT-кэша - is_active проверяется в БД на каждом запросе.
await jwtVerify(token, secret, { algorithms:['HS256'], audience:'portal' })
// Bearer (импersonation в лабе) имеет приоритет над cookie gg_portal_session
PUBLIC = ['/', '/login', '/api/login', '/api/health', '/buy', '/api/tg', '/chat', '/expired']
matcher: ['/((?!_next/|api/|.*\\.(png|jpg|svg|webp|ico)$).*)']
// защищённый путь без cookie → redirect /login?next=<path>
// в middleware НЕ валидируем JWT (медленно) - только наличие cookie
createChatInviteLink(chat_id, member_limit:1, name:label) // одноразовый
getChatMember(chat_id, user_id) → status in [creator,administrator,member,restricted]
// вебхук /api/tg (secret_token) ловит join/leave → tg_allowed
Уроки/страницы/фичи, прогресс per-user. Админка контента (CRUD) вшита в модуль - владелец правит сам. Первый проход максимально тупой, ЧБ.
src/lib/courses.ts, 12 уроков, union CourseSection: video/pdf/switcher/callout/table/gallery/qa/prose). Быстро, но правки = деплой.course_lessons(slug,num,title,sections JSON,quiz JSON,published), админка правит без деплоя (src/server/courses.ts: listCourseLessons/getCourseLesson).LearningTopic (section CORE/RW/MATH, level, content JSON) → Material → Question, всё в БД (Prisma).Рекомендация: DB-driven как Italy (правки без деплоя), лекало компонентов как Saxon/Манускрипт.
Hero, видео-фасад без YouTube-UI, PDF-вьюер (зум 60-240%, «Стр X/Y», fullscreen), квиз по одному вопросу с мгновенным ответом, заметки в БД, Q&A, блок услуг. Минимум 3 варианта геометрии, ЧБ.
LessonVideo.tsx видео-фасад: youtube-nocookie/embed?rel=0&modestbranding=1, грузит по клику
LessonPdf.tsx зум ZOOM_MIN=.6 MAX=2.4 STEP=.2; lazy-render видимых страниц;
MAX_CANVAS_AREA=16M (лимит iOS Safari); 2 очереди (страницы+тумбы);
fullscreen через portal в document.body; PDF проксируется /api/pdf?url=
CourseQuiz.tsx 1 вопрос/слайд, мгновенный reveal (без «Проверить»); 100% → POST /api/events quiz_pass
LessonNotes.tsx автосейв debounce 800ms → PUT /api/lessons/{id}/note
LessonQA.tsx аккордеон из секций {kind:'qa'}
LessonServices.tsx апселл-грид → POST /api/services/buy → T-Bank paymentUrl
Манускрипт (frontend/src/components/learn/PdfViewer.tsx) - эталон PDF: те же зум-ступени [60..240], device-pixel-ratio cap 2.0.
PDF: рендерить все страницы разом = краш; извлечение YouTube-id ловит youtu.be/embed/v=; canvas >16.7MP роняет iOS.
Dockerfile (standalone, HOSTNAME=0.0.0.0) → ECR → App Runner. Route53 + SSL. Секреты gg/<product>/*. GH Actions по SHA-тегу. Live-смоук.
App Runner инжектит свой HOSTNAME - биндить 0.0.0.0 в CMD.
FROM node:22-slim
WORKDIR /srv
COPY package.json ./ && RUN npm install --omit=dev
COPY . .
# Т-Банк: COPY russiantrustedca.pem /app/ ; ENV NODE_EXTRA_CA_CERTS=/app/russiantrustedca.pem
ENV PORT=8080
CMD ["npm","start"]
# build --platform linux/amd64 → ECR → aws apprunner start-deployment (AutoDeploy OFF)
/api/health + аптайм-крон с EC2, алерт в General-топик. При ROLLBACK: остановиться, вытащить логи, воспроизвести локально, один диагноз - один фикс. Не итерировать фиксы редеплоями в прод.
Сайдбар: Ученики · Клиенты-воронка · Промокоды · Деньги · Чат. Создание/сброс доступа, фильтры, поиск. Сверху - мета-админка направления (все продукты), в конце.
src/app/admin/AdminNav.tsx): Ученики (email/name/role/lastSeen/passedCount, создать → модалка кредов), Клиенты (воронка), Промокоды, Деньги (orders: provider/status/amount/paidAt), Чат (tg_messages + tg_allowed).Events-трекинг (входы, открытые уроки, прогресс, отвал). Воронка оплата → регистрация → активность → чат. Дашборд: кто платит, кто активен, кто спит.
// Saxon: events(user_id, type, path, meta JSONB); клиент POST /api/events (вайтлист quiz_pass/fail)
// SAT: AnalyticsEvent(user_id, session_id, event, properties JSON, page_url, referrer) + очистка >90д кроном 3:00
// Никогда не доверять клиентскому correct - переоценивать на сервере (gradeAnswer, XP по difficulty 5/10/20)
Воронка/дашборд (SAT /admin/analytics): вкладки behavior (DAU/DAG/retention), activity, retention (когорты), traffic, utm, emails.
Форум-чат: топики «Оплаты» · «Аналитика» · «General». Бот роутит события по топикам. Сюда же вшиты комьюнити (22) и бот-помощник (23) - один большой модуль.
tgCall(method,payload) // таймаут 8с, все ошибки в console.error, НИКОГДА не throw
sendWorkMessage(text,'payments'|'analytics'|'security') // message_thread_id по топику, HTML, ≤4000
createCommunityInvite(label,accessUntil) // createChatInviteLink member_limit=1, лог в tg_invites
banFromCommunity / unbanFromCommunity // revoke_messages=false; бан по истечении, unban при продлении
rub(n) // 1234500 → "1 234 500 ₽"
Fail-soft: падение телеги НИКОГДА не ломает оплату/письмо.
за 24ч: analytics_events (sessions/events/top-6), orders (created/paid/revenue/failed),
tg_invites/tg_messages (вступили по инвайту/без, вышли, сообщений, спикеров)
крон EC2 18.211.149.64: 7 17 * * * curl -X POST .../internal/tg-daily -H "X-Admin-Token: ..." (20:07 МСК)
secret gg/<product>/telegram-bot-token
secret gg/<product>/telegram-chats = {work_chat_id, community_chat_id, topic_payments, topic_analytics}
env App Runner: TG_BOT_TOKEN(secret ARN), TG_WORK_CHAT_ID, TG_COMMUNITY_CHAT_ID, TG_TOPIC_PAYMENTS, TG_TOPIC_ANALYTICS
Всё, что превращает продукт в бизнес.
Т-Банк (54-ФЗ) + Prodamus (иностранные) + промокоды + рассрочка «Долями». Идемпотентный вебхук с подписью → автосоздание доступа. На уровне ядра - заглушка, эквайринги подключает Лев.
README.md карта кода + флоу MONEY.md все концы касс + диагностика + гочи
smoke.sh прод-смоук после каждого денежного деплоя test-prodamus-sign.mjs тест подписи vs Python-эталон
Init: { TerminalKey, Amount: rub*100 /*копейки*/, OrderId, Receipt(54-ФЗ),
NotificationURL, SuccessURL, FailURL, Token }
Token = SHA256( значения flat-полей + Password, отсортированные по ключу, конкатенация БЕЗ разделителей )
POST securepay.tinkoff.ru/v2/Init (test: rest-api-test.tinkoff.ru; TBANK_TEST_MODE=false в проде)
Вебхук ВСЕГДА 200 OK (иначе ретраит 7 дней). Боевой терминал на КАЖДЫЙ продукт (Italy 25984175).
russiantrustedca.pem (Минцифры) в Dockerfile + NODE_EXTRA_CA_CERTS - иначе Init 502. Реальные ссылки pay.tbank.ru (грепы «tinkoff» не ловят).
link: global-generations.payform.ru?do=link&products[0][name]=...&order_id=...&link_expired=<МСК+3д>
Подпись = HMAC-SHA256 от PHP-json_encode: вложить products[0][name] в nested, сортировка ключей,
экранировать слеши /→\/ (JSON.stringify(...).replace(/\//g,'\\/'))
Ключ ОБЩИЙ на все продукты payform. urlNotification ИГНОРИТСЯ - URL добавить в ЛК «Настройка уведомлений».
link_expired +3 дня МСК обязателен (иначе ссылка протухает после полуночи). Чужие заказы (общий payform) ACK-ать 200 ДО проверки подписи, фильтр по префиксу (sx-/it-/ggprod-).
const claimed = await db.update(orders).set({status:'completed',paidAt})
.where(and(eq(orders.id,id), ne(orders.status,'completed'))).returning()
if (!claimed.length) return // уже обработан - выходим
// новый юзер → креды-письмо (730д); существующий → продление от конца периода → письмо «продлён»
// затем: repoрт в топик «Оплаты» (сумма, способ РФ/иностранная/Долями, промокод), one-time TG-инвайт в письме
consumePromo(code) // used_count+1 атомарно, where used_count < max_uses
bash payment/smoke.sh зелёный (6+ проверок)node payment/test-prodamus-sign.mjs = эталонный хэшОферта на /buy, политика ПД, чек 54-ФЗ. Договор через GG-Legal, продукт в coverage «деньги без договора».
Продукт с первого платежа в финмодели GG (Sheets, доход по направлению). Иначе выручка мимо учёта.
Модуль finance: сводная картина оплат по всем продуктам в единой админке (кто платил), бухгалтерия, сверки, учёт рефералки. Сейчас сверка руками ~7 часов у Алисы.
ACCESS_DAYS в конфиге (Саксон 730 = 24 мес), /expired, продление идемпотентно от конца периода (без дублей), напоминания письмами.
Resend, noreply@global-generations-edu.com. Письмо с доступом + фирменный шаблон. Кнопка «Вступить в комьюнити» с одноразовым инвайтом (при продлении не слать).
Закрытый Telegram-чат учеников (топики Общение/Правила/Материалы/Вопросы), вход только по одноразовым инвайтам из письма после оплаты. Ценность сама по себе.
# Чаты создаёт юзер-сессия Telethon (Bot API не умеет), владелец = Лёв:
r = client(functions.channels.CreateChannelRequest(title='...', megagroup=True, forum=True))
chat_id = -1000000000000 - r.chats[0].id # формат Bot API
# бот → админ (manage_topics+invite_users+ban+delete+pin); топики создаёт бот (createForumTopic → message_thread_id)
# скилл gg-telegram-product-setup делает за ~15 минут
sqlite lock оригинала сессии - работать с копией (src.backup). BotFather с лагом (sleep ≥2.5с). Лимит 20 ботов/аккаунт. Одноразовые инвайты работают только пока чат закрыт.
Фейс-контроль (одноразовые инвайты + белый список), приветствия, кик левых, аналитика активности, репорты оплат в рабочий чат.
// /api/tg/webhook: при join сверяем cm.invite_link с tg_invites (UPDATE ... used_at IS NULL RETURNING email)
// совпало → grandfather в tg_allowed → приветствие; нет инвайта → alert в топик «security», проверка isAllowed
// SAT-вариант: creates_join_request=true + approveChatJoinRequest() (не member_limit=1)
Отдельная машина - борд Sales Machine (links.*). Дев делает только дэшборд (29) и ЧБ-лендинг (24).
Оффер/кейсы/цена/отзывы, «Купить» → /buy. Сначала ЧБ, красота - в фазе дизайна. SEO-фаундейшн из груминга; SEO-модуль собрать как архитектуру, а не текстовый файл.
Кастомные скрипты B2C/B2B, план A/B, метрика победителя.
Персональные питч-презы под сегмент (Saxon Sales Engine).
Квиз → скоринг → преза → промокод → CRM.
amoCRM: этапы, поля, источники, интеграции, автоматика, отчётность.
Реплика «Продажи GG» на amoCRM партнёра: чаты, скорость ответов, команда, источники, синк каждый час (saxon-sales).
Ролик-прохождение глазами клиента, в репо в demo-video/.
levauth «готово», живые ссылки, схема «как устроено».
PDF: путь клиента, оплаты, промокоды, FAQ. Без внутренних кредов.
Свой release-publisher (эталон Product-SAT): батч PR → main (squash --admin), semver-тег, верификация деплоя (App Runner RUNNING + cache-busted проверка на проде), GH Release + changelog, релиз-карточка в TG с бренд-картинкой, запись на /updates.
Чистка тестов, публичный доступ, трафик. Перед go-live Лев ок-ает дизайн с Яной, подключает эквайринги; Алина - готовность воронки.
Welcome, реактивация, напоминания о продлении, дайджесты. Resend, сегменты из воронки.
Только если есть старая база. Рассылка «переехали» после черновика на ОК.
A/B по метрикам, победителей в прод.
«Приведи друга» в продукте и воронке; финансовый учёт (30%) - модуль Финансы.
По фидбеку и аналитике. Retention как цель. Зарплата - от клиентов, которых дал продукт.
Роли, дежурства, регламенты.
Протокол «Маяк»: реактивация спящих. Развитие комьюнити. Потом, за деньги продукта.
Новые сегменты, апселлы, масштаб, цели по выручке.
Роль-гайды на онбординг-хабе (PIN по ролям). Дописать свой раздел.
gg/<product>/*), никогда в репо.robots.txt Disallow + noindex, если продукт закрытый.README.md карта продукта (что умеет / карта репо / стек / секреты именами / гочи, ≤80 строк)
Dockerfile Next standalone; CMD HOSTNAME=0.0.0.0
src/app/ (shell)/ за auth · login/ buy/ admin/ · api/<product>/[...path] (+OPTIONS) · api/tg/webhook
src/server/ <product>-router.ts · db.ts(Neon) · auth.ts(JWT) · tbank.ts · prodamus.ts ·
provision.ts(ИДЕМПОТЕНТНО) · email.ts(Resend) · telegram.ts(fail-soft) · amocrm.ts · promo.ts
src/components/ · middleware.ts (гейт + редиректы доменов)
public/landing/ ассеты: ГОДОВОЙ immutable-кэш, версия в ИМЕНИ файла
scripts/ сиды (.mjs, идемпотентные)
payment/ ОБЯЗАТЕЛЬНА денежным: README · MONEY.md · smoke.sh · тест подписи
docs/ BOT-INSTRUCTIONS.md (первым) · BILLING-LOGIC.md · LANDING-DEPLOY.md · legacy/
README - 5 секций в порядке: шапка · «Что умеет» · «Карта репо» (таблица) · «Стек и деплой» + секреты именами · «Гочи». Обновлять в ту же сессию, что и фичу.