Add browserless service to prod compose and document clean-pdf

This commit is contained in:
2026-07-06 13:30:24 +05:00
parent fd40b1e394
commit f143f58cea
3 changed files with 48 additions and 0 deletions
+2
View File
@@ -48,4 +48,6 @@ TOOLBOX_VISIBLE=""
# Чистый PDF (browserless на staging/prod; локально — SSH-туннель на staging) # Чистый PDF (browserless на staging/prod; локально — SSH-туннель на staging)
BROWSER_WS_URL="ws://localhost:3333" BROWSER_WS_URL="ws://localhost:3333"
# Секрет browserless (TOKEN в его env) — на прод/staging генерировать: openssl rand -hex 24
BROWSERLESS_TOKEN=""
PDF_MONTHLY_LIMIT="100" PDF_MONTHLY_LIMIT="100"
+31
View File
@@ -171,6 +171,37 @@ CSS-классы: `.card-aubade`, `.btn-aubade`, `.btn-aubade-accent`, `.tag-aub
|---|---|---|---| |---|---|---|---|
| `POST` | `/api/auth/[...all]` | Better Auth handler | Все | | `POST` | `/api/auth/[...all]` | Better Auth handler | Все |
| `POST` | `/api/admin/upload` | Загрузка файла в S3, возвращает `{ url, key }` | admin | | `POST` | `/api/admin/upload` | Загрузка файла в S3, возвращает `{ url, key }` | admin |
| `GET` | `/api/pdf` | Чистый PDF из URL (Bearer-ключ или сессия) — см. раздел ниже | платный студент, admin, curator |
---
## Чистый PDF (`/tools/clean-pdf`, `/api/pdf`)
Инструмент Obsidian Toolbox: превращает произвольный URL в чистый PDF (без рекламы и меню, с типографикой, оглавлением, A4/Letter, light/dark). Полный дизайн-документ: [`docs/specs/20260706-clean-pdf-design.md`](docs/specs/20260706-clean-pdf-design.md).
**Пайплайн генерации:**
```
URL студента
→ browserless (Chromium по WebSocket, playwright-core) загружает страницу
→ Next.js забирает итоговый HTML
→ Defuddle (JSDOM) выделяет основной контент
→ HTML-шаблон (типографика, A4/Letter, light/dark, оглавление)
→ Playwright page.pdf() в том же browserless
→ application/pdf в ответе
```
Доступ: любой платный студент (есть `CourseEnrollment` вне `FREE_COURSE_SLUG`), admin/curator — без ограничений. Видимость страницы гейтится флагом `TOOLBOX_VISIBLE`, но сам `/api/pdf` работает независимо от него (доступ проверяется отдельно). Ключ для внешнего API — модель `PdfApiKey` (`sbpdf_<random>`, ленивая генерация, регенерация инвалидирует старый).
**Env-переменные:**
| Переменная | Назначение |
|---|---|
| `BROWSER_WS_URL` | WebSocket-адрес browserless (`ws://browserless:3000` в compose, `ws://localhost:3333` при туннеле локально) |
| `BROWSERLESS_TOKEN` | Секрет browserless (`TOKEN` в его env) — общий и для сервиса, и для клиента в LMS |
| `PDF_MONTHLY_LIMIT` | Лимит генераций в месяц на студента (по умолчанию `100`), без пересборки |
Контейнер `browserless` (`ghcr.io/browserless/chromium`) — внутренний, порт наружу не публикуется ни на staging, ни на проде.
--- ---
+15
View File
@@ -13,9 +13,14 @@ services:
NEXT_PUBLIC_APP_URL: "https://school.second-brain.ru" NEXT_PUBLIC_APP_URL: "https://school.second-brain.ru"
RESEND_API_KEY: "${RESEND_API_KEY}" RESEND_API_KEY: "${RESEND_API_KEY}"
EMAIL_FROM: "${EMAIL_FROM}" EMAIL_FROM: "${EMAIL_FROM}"
BROWSER_WS_URL: "ws://browserless:3000"
BROWSERLESS_TOKEN: "${BROWSERLESS_TOKEN}"
PDF_MONTHLY_LIMIT: "${PDF_MONTHLY_LIMIT:-100}"
depends_on: depends_on:
db: db:
condition: service_healthy condition: service_healthy
browserless:
condition: service_started
db: db:
image: postgres:16-alpine image: postgres:16-alpine
@@ -32,5 +37,15 @@ services:
timeout: 5s timeout: 5s
retries: 10 retries: 10
browserless:
image: ghcr.io/browserless/chromium
restart: unless-stopped
environment:
TOKEN: "${BROWSERLESS_TOKEN}"
CONCURRENT: "2"
QUEUED: "10"
TIMEOUT: "120000"
mem_limit: 1g
volumes: volumes:
postgres_data: postgres_data: