Архитектура
Общая экосистема
Платформа интеграций состоит из 3 проектов:
┌─────────────────────────────────────────────────────────────────────────────┐
│ ИНТЕГРАЦИОННАЯ ПЛАТФОРМА │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │
│ │ integ-admin │ │ integ-api │ │ integ-core │ │
│ │ │ │ │ │ │ │
│ │ Admin UI │◄──►│ API Gateway │◄──►│ Workers + Packages │ │
│ │ (Nuxt.js) │ │ (Hono/VPS) │ │ (Cloudflare) │ │
│ │ │ │ │ │ │ │
│ │ DigitalOcean │ │ DigitalOcean │ │ Cloudflare Edge │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘Проекты
| Проект | Описание | Хостинг | URL |
|---|---|---|---|
| integ-core | Монорепозиторий с пакетами, интеграциями и Gateway Worker | Cloudflare Workers | integ.happ.tools |
| integ-api | API прокси для авторизации, MCP протокол, оркестрация | DigitalOcean VPS | api.integ.happ.tools |
| integ-admin | Admin UI для управления интеграциями и credentials | DigitalOcean VPS | admin.integ.happ.tools |
Взаимодействие
┌─────────────┐
│ Client │ (AI Agent / MCP / External System)
└──────┬──────┘
│
▼
┌─────────────────┐
│ integ-api │ Authorization + MCP Protocol
│ (VPS) │
│ │ • Bearer token validation
│ │ • Request routing
│ │ • MCP tools orchestration
└────────┬────────┘
│ x-access-token
▼
┌─────────────────┐
│ integ-core │ Gateway Worker (Cloudflare)
│ Gateway │
│ │ • Token validation
│ │ • Rate limiting
│ │ • Service Bindings routing
└────────┬────────┘
│ Service Bindings
▼
┌─────────────────┐
│ Integration │ Individual Workers
│ Workers │
│ │ • sofa, prom, booking...
│ │ • Business logic
└─────────────────┘Секреты и авторизация
| Секрет | Где хранится | Назначение |
|---|---|---|
ACCESS_TOKEN | Doppler → CF Workers | Авторизация между integ-api и Gateway |
CRYPTO_KEY | Doppler → CF Workers | Ключ шифрования секретов в D1 |
CRYPTO_SALT | Doppler → CF Workers | Соль для PBKDF2 key derivation |
| Глобальные API ключи | Doppler → CF Workers | OpenAI, Qdrant и т.д. |
| Уникальные секреты | D1 integ-db (зашифрованы) | API ключи для конкретных интеграций |
integ-core
integ-core — монорепозиторий с пакетами, интеграциями клиентов и Gateway Worker, работающий на Cloudflare Workers.
Технологический стек
| Компонент | Технология | Назначение |
|---|---|---|
| Runtime | Cloudflare Workers | Serverless compute на edge |
| HTTP Framework | Hono | Минималистичный роутинг |
| Orchestration | Custom (built-in) | Step functions, retry, scheduling |
| Database | Cloudflare D1 / Neon PostgreSQL | Хранение данных |
| Cache | Cloudflare KV | Кэширование |
| Monorepo | Turborepo + pnpm | Управление пакетами |
| Build | tsup | Быстрая сборка в ESM |
Структура проекта
integ-core/
├── workers/ # Gateway (точка входа)
│ └── gateway/
│ ├── src/
│ │ ├── index.ts # Auth, routing, rate limiting
│ │ ├── types.ts # TypeScript типы
│ │ └── utils/ # Утилиты
│ │ ├── rate-limiter.ts
│ │ └── service-fetcher.ts
│ └── wrangler.toml.tpl
├── integrations/ # Конкретные интеграции
│ └── sofa/
│ ├── migrations/ # SQL миграции
│ ├── src/
│ │ ├── index.ts # createIntegration() entry point
│ │ ├── types.ts
│ │ ├── config/ # Конфигурация (types, defaults)
│ │ ├── handlers/ # Бизнес-логика
│ │ ├── migrations/ # Код миграций
│ │ └── utils/ # Утилиты (clients, logger)
│ └── wrangler.toml.tpl
├── packages/ # Переиспользуемые пакеты
│ ├── CRM клиенты
│ │ ├── nethunt/
│ │ ├── keycrm/
│ │ ├── bitrix/
│ │ └── salesdrive/
│ ├── AI/LLM
│ │ ├── llm/ # LLM с fallback
│ │ ├── openai/
│ │ ├── qdrant/ # Qdrant Vector DB клиент
│ │ └── vector/ # Vector Store с fallback
│ ├── Внешние сервисы
│ │ ├── google-drive/ # Google Drive API клиент
│ │ ├── happ-voice/ # Happ Voice Assistant клиент
│ │ ├── integ-api/ # Integ API клиент
│ │ └── saas-api/ # SaaS API клиент
│ ├── Бизнес-логика
│ │ ├── call-queue/ # Очередь исходящих звонков
│ │ └── cron/ # Scheduled tasks (cron trigger endpoint)
│ └── Инфраструктура
│ ├── db/ # DB abstraction (D1/Neon)
│ ├── cache/ # Cache abstraction (KV/Redis)
│ ├── creds/ # Encrypted credentials
│ ├── d1/ # D1 низкоуровневый клиент
│ ├── drizzle/ # Drizzle ORM для Neon
│ ├── kv/ # KV низкоуровневый клиент
│ ├── logger/ # Структурированное логирование
│ ├── neon/ # PostgreSQL клиент
│ ├── config/ # Конфигурация интеграций
├── templates/ # Шаблоны для генерации
├── scripts/ # CLI утилиты
└── docs/ # ДокументацияКомпоненты
1. Gateway Worker
Назначение: Единая точка входа для всех интеграций
Функции:
- Авторизация через
x-access-token - Rate limiting (100 req/min per IP)
- Генерация и проброс
trace_id - Роутинг через Service Bindings
URL маршрутизация:
integ.happ.tools/sofa/* → SOFA Worker
integ.happ.tools/health → Gateway health
integ.happ.tools/reference → Scalar UI (все интеграции)2. Integration Workers
Назначение: Отдельный Cloudflare Worker для каждой интеграции
Компоненты интеграции:
- Hono App — HTTP endpoints
- Handlers — бизнес-логика
Endpoints:
POST /setup— инициализация БД и секретовGET /health— проверка состояния (D1, KV, Creds)GET /reference— Scalar UI API документацияGET /openapi.json— OpenAPI 3.0 спецификацияPOST /webhook/:action— приём webhook-ов
3. Пакеты (Packages)
CRM пакеты (прямые клиенты)
| Пакет | Назначение | Основные методы |
|---|---|---|
@happ-integ/nethunt | Nethunt CRM | searchRecords, getRecord, updateRecord |
@happ-integ/keycrm | KeyCRM | searchContacts, getContact, createContact |
@happ-integ/bitrix | Bitrix24 | searchContacts, getContact, createContact |
@happ-integ/salesdrive | SalesDrive | searchContacts, getContact, updateContact |
Внешние сервисы
| Пакет | Назначение | Основные методы |
|---|---|---|
@happ-integ/google-drive | Google Drive API | listFiles, getFile, downloadTextFile, downloadSpreadsheet |
@happ-integ/happ-voice | Voice Assistant | initiateCall, getCallStatus, endCall, getTranscript |
@happ-integ/integ-api | Integ API управление | upsertIntegration, getCredentials, setCredentials |
@happ-integ/saas-api | SaaS API | createAssistant, getAssistants, updateAssistant |
LLM пакет (@happ-integ/llm)
Провайдеры: OpenAI, Claude, Gemini, Groq
Особенность: Автоматический fallback + автоматическое получение ключей из env
Primary (OpenAI) → error → Secondary (Claude) → error → throwМетоды: chat(), embedding(), embeddings()
// Автоматически берёт GROQ_API_KEY, OPENAI_API_KEY, CLAUDE_API_KEY из env
const llm = new LLMService({
primary: "groq",
secondary: "claude",
env, // ← передаём env binding
});
const response = await llm.chat({ messages }); // fallback автоматическийVector пакет (@happ-integ/vector)
Провайдеры: Qdrant, Pinecone, Weaviate
Особенность: Автоматическое получение ключей из env
Методы: upsert(), query(), deleteByIds(), deleteByFilter()
// Автоматически берёт QDRANT_URL, QDRANT_API_KEY, PINECONE_API_KEY и т.д. из env
const vector = new VectorService({
primary: "qdrant",
env, // ← передаём env binding
});Qdrant (@happ-integ/qdrant)
Назначение: Прямой клиент для Qdrant Vector Database
Методы: createCollection(), upsert(), search(), deleteByIds(), deleteByFilter()
// Автоматически берёт QDRANT_URL, QDRANT_API_KEY из env
const qdrant = new QdrantClient({ env });
const results = await qdrant.search({
collectionName: "documents",
vector: queryEmbedding,
limit: 10,
});Database (@happ-integ/db)
Провайдеры: Cloudflare D1 (default), Neon PostgreSQL
const db = new DB({ provider: "d1", d1: env.DB, project: "sofa" });
await db.insert("calls", { id: "123", status: "pending" });Cache (@happ-integ/cache)
Провайдеры: Cloudflare KV
const cache = new Cache({ provider: "kv", kv: env.KV, project: "sofa" });
const data = await cache.cacheAside("key", 3600, async () => heavyOperation());Call Queue (@happ-integ/call-queue)
Назначение: Очередь исходящих звонков с concurrency-контролем, retry-окнами и управлением жизненным циклом звонка.
Используй когда: интеграция делает исходящие звонки через Voice Assistant.
import { CallQueue, DEFAULT_RETRY, generateCallsMigration } from "@happ-integ/call-queue";
const queue = new CallQueue({
db, tableName: "calls", integrationPrefix: "my-crm",
queueConfig: { concurrency_limit: 3, failed_call_duration_threshold: 5 },
retryConfig: DEFAULT_RETRY,
originate: async (phone) => {
const result = await saasClient.originateCall(assistantId, phone);
return { vaCallId: result.callId! };
},
log,
});
await queue.enqueue(phone); // Поставить в очередь / инициировать
await queue.handleCallStarted(id); // media_start
await queue.handleCallEnded(id, duration, phone); // media_end + auto-retry
await queue.processQueue(); // Обработать очередь (cron)Cron (@happ-integ/cron)
Назначение: Автоматический POST /cron/trigger endpoint для ручного вызова scheduled handlers. Используется core автоматически при наличии scheduled в конфиге.
// Автоматически — достаточно указать scheduled в createIntegration()
// POST /my-crm/cron/trigger будет создан автоматически
// Ручное использование (если нужно кастомизировать)
import { createCronTriggerHandler } from "@happ-integ/cron";Credentials (@happ-integ/creds)
Хранилище: D1 с AES-256-GCM шифрованием
const creds = new Creds({ d1: env.DB, masterKey: env.CRYPTO_KEY, salt: env.CRYPTO_SALT });
const secrets = await creds.get<{ API_KEY: string }>("sofa");Drizzle (@happ-integ/drizzle)
Назначение: Type-safe ORM для Neon PostgreSQL
import { createDrizzleClient, users } from "@happ-integ/drizzle";
import { eq } from "drizzle-orm";
const db = createDrizzleClient();
const activeUsers = await db.select().from(users).where(eq(users.status, "active"));Logger (@happ-integ/logger)
Назначение: Централизованное логирование для packages и gateway. Поддерживает JSON (prod) и pretty console (local) режимы, фильтрацию по LOG_LEVEL.
import { Logger } from "@happ-integ/logger";
const logger = new Logger("my-service");
logger.error("Operation failed", error);
logger.warn("Fallback activated");
logger.info("Event processed", { action });
logger.debug("Detailed data", { payload });
// Child logger для подсервисов
const child = logger.child("sub-module");
child.error("Sub-module failed", error);Режимы вывода:
json: false(local) —[HH:MM:SS] LEVEL [service] message {...data}json: true(prod) —{ level, service, message, timestamp, ...data }
LOG_LEVEL настраивается в wrangler.toml ([vars]) и через pnpm debug (устанавливает LOG_LEVEL=debug).
4. Handlers интеграции sofa
| Handler | Endpoint | Назначение | Retries |
|---|---|---|---|
webinar-call-originate | POST /webhook/webinar-call-originate | Инициация исходящего звонка | 3 |
webinar-agent-init | POST /webhook/webinar-agent-init | Инициализация голосового агента (CRM) | 0 |
webinar-call-events | POST /webhook/webinar-call-events | Обработка media_start / media_end | 3 |
webinar-agent-post-call | POST /webhook/webinar-agent-post-call | Post-call AI-анализ + CRM update | 0 |
retry | cron 0 * * * * | Повтор неуспешных звонков | 3 |
Cloudflare Bindings
| Binding | Тип | Назначение |
|---|---|---|
INTEG_DB | D1 Database | Credentials + данные |
INTEG_KV | KV Namespace | Кэширование |
SOFA | Service Binding | Gateway → Integration routing |
Схема БД для Credentials
CREATE TABLE creds (
id TEXT PRIMARY KEY,
integration_name TEXT NOT NULL,
key TEXT NOT NULL,
encrypted_value TEXT NOT NULL,
created_at TEXT,
updated_at TEXT,
UNIQUE(integration_name, key)
);Диаграмма потока данных
┌─────────────────┐
│ External │
│ (CRM webhook) │
└────────┬────────┘
│ HTTP POST
▼
┌─────────────────┐
│ Gateway │
│ Worker │
│ │
│ • Auth check │
│ • Rate limit │
│ • trace_id │
└────────┬────────┘
│ Service Binding
▼
┌─────────────────┐
│ Integration │
│ Worker │
│ │
│ /webhook/:action│
└────────┬────────┘
│
▼
┌─────────────────┐
│ Handler │
│ │
│ • CRM API │
│ • LLM │
│ • DB/Cache │
└─────────────────┘Environment Variables
Глобальные (Doppler → Cloudflare Secrets)
| Переменная | Назначение |
|---|---|
CRYPTO_KEY | Ключ шифрования credentials (min 32 chars) |
CRYPTO_SALT | Соль для шифрования (min 16 chars) |
OPENAI_API_KEY | OpenAI API |
CLAUDE_API_KEY | Claude API |
GROQ_API_KEY | Groq API |
Per-integration (D1 Creds)
Хранятся зашифрованными в D1:
NETHUNT_EMAIL,NETHUNT_API_KEYVOICE_ASSISTANT_URL- и т.д.
Паттерны кода
Entry Point (createIntegration)
import { createIntegration } from "@happ-integ/core";
import * as handlers from "./handlers";
import { DEFAULT_CONFIGS } from "./config";
export default createIntegration({
name: "sofa",
handlers: Object.values(handlers),
migrations: SOFA_MIGRATIONS,
secrets: ["NETHUNT_EMAIL", "NETHUNT_API_KEY", "NETHUNT_FOLDER_ID", "SAAS_ASSISTANT_ID"],
defaultConfigs: DEFAULT_CONFIGS,
scheduled: [{ cron: "0 * * * *", handlerName: "retry", payload: { triggered_by: "cron" } }],
});defaultConfigs — объект с дефолтными значениями конфигурации, которые записываются в KV при вызове /setup. Все переменные бизнес-логики (названия полей CRM, пороги, лимиты, параметры LLM) хранятся в конфиге и читаются handlers через config.get() с fallback на дефолт.
Handler (defineHandler)
import { defineHandler } from "@happ-integ/core";
export const webinarCallOriginateHandler = defineHandler({
name: "webinar-call-originate",
retries: 3,
endpoint: "POST /webhook/webinar-call-originate",
concurrency: { limit: 3 },
async handler({ payload, env, step, ctx, creds, integrationName }) {
// ... logic
return { success: true };
},
});Scheduled Tasks (Cron)
Cloudflare Workers поддерживает Cron Triggers — периодический запуск worker по расписанию.
Лимиты Cloudflare
| План | Максимум cron triggers на worker |
|---|---|
| Free | 3 |
| Paid (Workers Paid) | 5 |
Мы на Paid Plan — максимум 5 cron задач на один worker. Поскольку каждая интеграция — отдельный worker, лимит считается per integration.
Как это работает
- В
createIntegration()передаётся массивscheduled - Core использует
@happ-integ/cronдля автоматического созданияPOST /cron/triggerendpoint - Core генерирует
scheduledworker handler для Cloudflare Cron Triggers - В
wrangler.toml(или через Cloudflare Dashboard) регистрируются[triggers]с cron expressions - Cloudflare вызывает
scheduledevent handler worker'а по расписанию
Cloudflare Cron Trigger (dev/prod) HTTP POST /cron/trigger (local/manual)
↓ ↓
ScheduledEvent @happ-integ/cron
↓ ↓
Iterate config.scheduled[] Iterate config.scheduled[]
↓ ↓
Find handler by name → execute Find handler by name → executeКонфигурация в коде
// integrations/{name}/src/index.ts
export default createIntegration({
name: "my-integration",
handlers: Object.values(handlers),
migrations: MY_MIGRATIONS,
scheduled: [
{
cron: "0 * * * *", // Каждый час
handlerName: "retry", // Имя handler из handlers[]
payload: { triggered_by: "cron" }, // Передаётся как payload
},
],
});Интерфейс конфигурации (IScheduledHandlerConfig):
| Поле | Тип | Описание |
|---|---|---|
cron | string | Cron expression (Cloudflare формат) |
handlerName | string | Имя handler, должно совпадать с name в defineHandler() |
payload | Record<string, unknown>? | Данные, передаваемые в handler как payload |
Конфигурация в wrangler.toml
Cron triggers нужно зарегистрировать в wrangler.toml (или через Cloudflare Dashboard):
[triggers]
crons = ["0 * * * *"]Шаблон templates/wrangler.integration.toml.tpl не содержит [triggers] — их нужно добавлять вручную для интеграций, которые используют scheduled.
Где мы используем cron
| Интеграция | Cron | Handler | Назначение |
|---|---|---|---|
| sofa | 0 * * * * (каждый час) | retry | Обработка очереди звонков и повторы неуспешных |
Cron handler = обычный handler
Handler, вызываемый по cron, ничем не отличается от обычного. Он может одновременно иметь HTTP endpoint (для ручного вызова) и быть привязанным к cron:
export const retryHandler = defineHandler({
name: "retry",
retries: 3,
endpoint: "POST /webhook/retry-calls", // Ручной вызов через HTTP
async handler({ db, config, creds, env, log, integrationName }) {
const queue = await createCallQueue({ ... });
return { success: true, ...await queue.processQueue() };
},
});Локальное тестирование
Cron triggers не работают локально в wrangler dev. Автоматический запуск по расписанию доступен только в dev/prod окружениях (deployed workers).
Для локального тестирования есть три способа:
1. POST /cron/trigger через gateway (рекомендуется)
Автоматически создаётся пакетом @happ-integ/cron при наличии scheduled в конфиге. Работает через gateway:
# Все scheduled handlers
curl -X POST http://localhost:3001/sofa/cron/trigger
# Конкретный handler
curl -X POST http://localhost:3001/sofa/cron/trigger \
-H "Content-Type: application/json" \
-d '{"handlerName": "retry"}'2. HTTP endpoint handler напрямую
Если handler имеет endpoint, вызывай его:
curl -X POST http://localhost:3001/sofa/webhook/retry-calls \
-H "Content-Type: application/json" \
-d '{"triggered_by": "manual"}'3. /__scheduled endpoint (wrangler dev)
Специальный URL wrangler dev для эмуляции cron trigger. Вызывается моментально. Не проксируется через gateway — только напрямую на порт worker'а:
curl "http://localhost:8787/__scheduled"Cron expressions
| Expression | Описание |
|---|---|
* * * * * | Каждую минуту |
*/5 * * * * | Каждые 5 минут |
0 * * * * | Каждый час |
0 0 * * * | Каждый день в полночь |
0 8 * * 1 | Каждый понедельник в 08:00 |
Время — UTC. Подробнее: Cloudflare Cron Triggers docs.
Файлы конфигурации
| Файл | Назначение |
|---|---|
turbo.json | Turborepo tasks (build, dev, deploy) |
pnpm-workspace.yaml | Workspace packages |
wrangler.toml | Cloudflare Worker config |
tsup.config.ts | Build config для пакетов |
doppler.yaml | Doppler project config |
Связанные документы
- QUICK_START.md — быстрый старт (5 минут)
- INTEGRATION_CHECKLIST.md — чеклист создания интеграции
- ENVIRONMENTS.md — окружения (local/dev/prod)
- SECRETS.md — управление секретами
- DEVELOPMENT.md — локальная разработка
- PRODUCTION.md — production workflow
- CODE_RULES.md — правила написания кода
- MONITORING.md — мониторинг и логирование
Интеграции
| Интеграция | CRM | Описание |
|---|---|---|
| Sofa | NetHunt | Voice AI + CRM интеграция |