TKBK Studio
TKBK Studio
Курсы в markdown, оплата ЮKassa, вход по ссылке из письма или через Telegram-бота, тесты и PDF-сертификаты
Лёгкая онлайн-школа на своём сервере: без ежемесячной платы за платформу и без лишнего. Курсы — это папки с markdown-файлами: пишете уроки в любом редакторе, вставляете видео с Rutube, VK Видео или Kinescope, в конце урока добавляете тест.
Ученик входит без пароля — по одноразовой ссылке из письма или через Telegram-бота, покупает курс через ЮKassa (картой или по СБП, с чеком 54-ФЗ) и сразу получает доступ. Первые уроки можно открыть бесплатно как превью, остальные — после оплаты и по порядку.
Прогресс сохраняется, тесты проверяет сервер (правильные ответы не попадают в браузер), а после всех уроков ученик скачивает PDF-сертификат с номером и ссылкой для проверки подлинности. Бот присылает уведомление «курс открыт» и ссылку для входа.
Внутри — Node.js и Fastify, база SQLite, статический фронтенд без сборки. Запуск одной командой в Docker, ключевая логика покрыта тестами.
Для кого: Эксперты, авторы курсов, небольшие онлайн-школы и тренеры
Ключи и токены не вводятся на сайте — их кладут в файл .env на своём сервере.
APP_URLАдрес сайта — Например https://school.example.ru — без слеша в концеYOOKASSA_SHOP_IDshopId ЮKassa — Личный кабинет ЮKassa → Интеграция → Ключи APIYOOKASSA_SECRET_KEYСекретный ключ ЮKassa — Для тестового магазина начинается с test_YOOKASSA_RECEIPTSЧеки 54-ФЗ — true, если подключены «Чеки от ЮKassa» или онлайн-кассаYOOKASSA_VAT_CODEКод НДС в чеке — 1 — без НДС, 11 — НДС 22%TELEGRAM_BOT_TOKENТокен Telegram-бота — @BotFather → /newbot. Вход через бота и уведомленияSMTP_HOSTSMTP-сервер — Например smtp.yandex.ru. Пусто — ссылки входа пишутся в логSMTP_USERSMTP-логин — Обычно адрес ящикаSMTP_PASSWORDSMTP-пароль — Пароль приложения почтового сервиса# {{SCHOOL_NAME}}
Лёгкая онлайн-школа на своём сервере: курсы и уроки в markdown, оплата через ЮKassa, вход по ссылке из письма или через Telegram-бота, тесты в конце уроков, прогресс и PDF-сертификаты с проверкой подлинности.
## Что умеет
- **Курсы из файлов**: папка `courses/<курс>/` — `course.md` (название, описание, цена) и уроки `01-….md`, `02-….md`. Видео с Rutube, VK Видео, Kinescope, YouTube.
- **Превью и порядок**: уроки с `free: true` открыты всем; остальные — после оплаты, по порядку (`sequential: true`).
- **Оплата ЮKassa**: платёж с `Idempotence-Key`, страница оплаты (redirect), вебхук с перепроверкой статуса через `GET /v3/payments/{id}`, сверка суммы, чек 54-ФЗ с email покупателя. Доступ выдаётся ровно один раз.
- **Вход без пароля**: одноразовая ссылка на почту (30 минут) или ссылка от Telegram-бота. Вход подтверждается кнопкой — почтовые сканеры, которые сами открывают ссылки, её не «сжигают».
- **Тесты**: блок `quiz` в конце урока, один или несколько правильных ответов, проходной балл. Ответы проверяет сервер.
- **Сертификат PDF** после всех уроков: имя ученика, курс, дата, номер и страница проверки `cert.html`.
- **Telegram-бот**: ссылка для входа (`/start`, `/login`), список курсов (`/courses`), сообщение «курс открыт» после оплаты.
## Быстрый старт с Docker (10 минут)
1. Установите [Docker Desktop](https://www.docker.com/products/docker-desktop/) (или Docker Engine на сервере).
2. Создайте `.env` и впишите ключи **тестового** магазина ЮKassa (см. ниже):
```bash
cp .env.example .env
```
3. Запустите:
```bash
docker compose up -d --build
```
4. Откройте http://localhost:3000 → демо-курс → «Войти и купить». Пока SMTP не настроен, ссылку для входа смотрите в логах:
```bash
docker compose logs -f school
```
5. Оплатите тестовой картой `5555 5555 5555 4477` (срок — любая будущая дата, CVC — любые 3 цифры). Вернувшись на сайт, вы увидите «Оплата прошла» и открытые уроки.
База лежит в Docker-томе `school-data`. Папка `courses/` подключена с хоста: правите уроки → `docker compose restart school`.
## Запуск без Docker
Нужен Node.js 22.12+ (рекомендуется 24 LTS).
```bash
npm install
cp .env.example .env
npm run dev # разработка: курсы перечитываются при каждом запросе
npm start # продакшен (NODE_ENV=production): курсы читаются при запуске
```
SQLite ставится готовым бинарником — компилятор не нужен. Для сертификатов нужен шрифт с кириллицей: в Windows и macOS берётся Arial, в Linux — DejaVu Sans (`sudo apt install fonts-dejavu-core`), свой — через `PDF_FONT_REGULAR`.
## Как добавить курс
```
courses/
my-course/ ← адрес курса: /course.html?slug=my-course (латиница, цифры, «-»)
course.md ← шапка курса и описание
01-intro.md ← уроки идут в порядке имён файлов
02-practice.md
```
`course.md`:
```markdown
---
title: Основы фотографии
description: 5 уроков о свете, композиции и обработке
price: 2990
sequential: true
order: 1
---
Подробное описание курса в markdown.
```
- `price` — цена в рублях (`0` — бесплатный курс, доступен после входа);
- `sequential: false` — уроки открываются в любом порядке;
- `order` — порядок в каталоге.
Урок — шапка (`title`, по желанию `free: true` для бесплатного превью) и текст в markdown. Видео — код встраивания плеера (`<iframe …>`). Тест — блок в конце урока:
```quiz
? Что важнее всего в портрете?
- [x] Свет
- [ ] Дорогой объектив
? Отметьте тёплые цвета
- [x] Оранжевый
- [x] Жёлтый
- [ ] Синий
```
Если у вопроса несколько `[x]`, ученик должен отметить все. Проходной балл — `PASS_PERCENT` в `src/config.js`. Ошибку в курсе сервер покажет при запуске с указанием файла. Демо-курс `courses/demo` подробно показывает формат — удалите его, когда добавите свои.
## Настройка ЮKassa
1. Зарегистрируйтесь на [yookassa.ru](https://yookassa.ru) и создайте **тестовый магазин**.
2. «Интеграция → Ключи API»: `shopId` → `YOOKASSA_SHOP_ID`, «Выпустить секретный ключ» → `YOOKASSA_SECRET_KEY` (у тестового магазина начинается с `test_`).
3. «Интеграция → HTTP-уведомления»: URL `https://ваш-домен/yookassa/webhook`, события `payment.succeeded` и `payment.canceled`. Нужен публичный HTTPS-адрес; при локальной проверке уведомления не дойдут — статус всё равно уточнится, когда ученик вернётся на сайт.
4. Тестовая карта — `5555 5555 5555 4477`; другие сценарии — в [документации по тестированию](https://yookassa.ru/developers/payment-acceptance/testing-and-going-live/testing).
5. Чеки 54-ФЗ: при подключённых «Чеках от ЮKassa» или онлайн-кассе оставьте `YOOKASSA_RECEIPTS=true` (email для чека ученик вводит перед оплатой), код НДС — `YOOKASSA_VAT_CODE` (`1` — без НДС, `11` — НДС 22%).
6. Для боевого режима замените ключи на ключи боевого магазина.
Телу уведомления сервер не доверяет: статус перепроверяется запросом в API, сумма сверяется с заказом, повторные уведомления не выдают доступ второй раз.
## Telegram-бот
1. [@BotFather](https://t.me/BotFather) → `/newbot` → токен в `TELEGRAM_BOT_TOKEN`. Перезапустите сервер — бот заработает сам (long polling, публичный адрес не нужен).
2. На странице входа появится кнопка «Войти через Telegram»: бот пришлёт ссылку для входа.
3. В личном кабинете ученик может привязать Telegram к аккаунту с email — тогда бот пришлёт сообщение, когда откроется курс.
Сайт использует ссылки из бота, а не виджет Telegram Login, поэтому команда `/setdomain` у @BotFather не нужна. Если захотите кнопку Telegram Login прямо на сайте — привяжите домен через `/setdomain` (пример реализации — в шаблоне «SaaS-стартер с оплатой ЮKassa»).
## Почта
Любой SMTP: Яндекс 360 (`smtp.yandex.ru`, 465, пароль приложения), Mail.ru (`smtp.mail.ru`, 465), Unisender Go, SendPulse. Заполните `SMTP_HOST`, `SMTP_USER`, `SMTP_PASSWORD`, `MAIL_FROM`. Пока `SMTP_HOST` пуст, письма со ссылками пишутся в лог.
## Структура проекта
```
courses/ курсы и уроки (markdown)
src/
server.js app.js запуск, Fastify, безопасность (CSRF, CSP)
config.js env.js настройки школы (из мастера) и .env
routes/ вход, курсы/уроки/тесты/сертификаты, оплата
lib/ шапка markdown, тесты, загрузка курсов, доступ и прогресс, деньги, токены
payments/ клиент ЮKassa, заказы
services/ Telegram-бот, письма
pdf/ сертификат, поиск шрифта
repo/ auth/ db.js SQL, сессии и ссылки входа, миграции
public/ страницы сайта, app.js, style.css, оферта
test/ тесты node:test
```
## Тесты
```bash
npm test
```
Проверяются: разбор курсов и тестов (включая демо-курс), правила доступа и прогресс, вход по ссылке и одноразовость, CSRF, оплата с фейковым API ЮKassa (перепроверка статуса, сверка суммы, повторы), бот, полный путь ученика до сертификата.
## FAQ
**Оплатил, а доступа нет.** Обновите страницу курса через минуту. Проверьте URL уведомлений в ЮKassa и логи (`docker compose logs school`).
**Письмо со ссылкой не приходит.** Проверьте «Спам» и настройки SMTP. Без SMTP ссылка пишется в лог сервера.
**Как выдать курс вручную (подарок, оплата по счёту)?** Добавьте строку в таблицу `enrollments` (`user_id`, `course_slug`, `source`, `created_at`) — или допишите кнопку в будущей админке.
**Где хранятся видео?** На видеохостинге (Rutube, VK Видео, Kinescope и др.) — в урок вставляется плеер. Свой хостинг добавьте в `frame-src` в `src/app.js`.
**Бэкап.** `docker compose stop school && docker compose cp school:/app/data ./backup && docker compose start school`. Курсы — это ваши файлы в `courses/`.
## Что доработать
- Промокоды и скидки.
- Домашние задания с проверкой куратором.
- Открытие уроков по расписанию.
- Админка: продажи, ученики, ручная выдача доступа.
- Напоминания в Telegram тем, кто давно не заходил.
Перед запуском проверьте с юристом текст оферты (`public/offer.html`) и подайте уведомление об обработке персональных данных в Роскомнадзор.
Программа, тарифы, отзывы и запись на поток
Услуги, мастера, свободные окна на 14 дней, напоминания за сутки и за 2 часа, предоплата и отзывы
Оценка 0–10 после заказа: довольных — на Яндекс Карты, 2ГИС и WB, недовольных — сразу к менеджеру