Skip to content

Архитектура

Общая экосистема

Платформа интеграций состоит из 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 WorkerCloudflare Workersinteg.happ.tools
integ-apiAPI прокси для авторизации, MCP протокол, оркестрацияDigitalOcean VPSapi.integ.happ.tools
integ-adminAdmin UI для управления интеграциями и credentialsDigitalOcean VPSadmin.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_TOKENDoppler → CF WorkersАвторизация между integ-api и Gateway
CRYPTO_KEYDoppler → CF WorkersКлюч шифрования секретов в D1
CRYPTO_SALTDoppler → CF WorkersСоль для PBKDF2 key derivation
Глобальные API ключиDoppler → CF WorkersOpenAI, Qdrant и т.д.
Уникальные секретыD1 integ-db (зашифрованы)API ключи для конкретных интеграций

integ-core

integ-core — монорепозиторий с пакетами, интеграциями клиентов и Gateway Worker, работающий на Cloudflare Workers.

Технологический стек

КомпонентТехнологияНазначение
RuntimeCloudflare WorkersServerless compute на edge
HTTP FrameworkHonoМинималистичный роутинг
OrchestrationCustom (built-in)Step functions, retry, scheduling
DatabaseCloudflare D1 / Neon PostgreSQLХранение данных
CacheCloudflare KVКэширование
MonorepoTurborepo + pnpmУправление пакетами
BuildtsupБыстрая сборка в 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/nethuntNethunt CRMsearchRecords, getRecord, updateRecord
@happ-integ/keycrmKeyCRMsearchContacts, getContact, createContact
@happ-integ/bitrixBitrix24searchContacts, getContact, createContact
@happ-integ/salesdriveSalesDrivesearchContacts, getContact, updateContact

Внешние сервисы

ПакетНазначениеОсновные методы
@happ-integ/google-driveGoogle Drive APIlistFiles, getFile, downloadTextFile, downloadSpreadsheet
@happ-integ/happ-voiceVoice AssistantinitiateCall, getCallStatus, endCall, getTranscript
@happ-integ/integ-apiInteg API управлениеupsertIntegration, getCredentials, setCredentials
@happ-integ/saas-apiSaaS APIcreateAssistant, getAssistants, updateAssistant

LLM пакет (@happ-integ/llm)

Провайдеры: OpenAI, Claude, Gemini, Groq

Особенность: Автоматический fallback + автоматическое получение ключей из env

Primary (OpenAI) → error → Secondary (Claude) → error → throw

Методы: chat(), embedding(), embeddings()

typescript
// Автоматически берёт 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()

typescript
// Автоматически берёт 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()

typescript
// Автоматически берёт 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

typescript
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

typescript
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.

typescript
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 в конфиге.

typescript
// Автоматически — достаточно указать scheduled в createIntegration()
// POST /my-crm/cron/trigger будет создан автоматически

// Ручное использование (если нужно кастомизировать)
import { createCronTriggerHandler } from "@happ-integ/cron";

Credentials (@happ-integ/creds)

Хранилище: D1 с AES-256-GCM шифрованием

typescript
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

typescript
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.

typescript
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

HandlerEndpointНазначениеRetries
webinar-call-originatePOST /webhook/webinar-call-originateИнициация исходящего звонка3
webinar-agent-initPOST /webhook/webinar-agent-initИнициализация голосового агента (CRM)0
webinar-call-eventsPOST /webhook/webinar-call-eventsОбработка media_start / media_end3
webinar-agent-post-callPOST /webhook/webinar-agent-post-callPost-call AI-анализ + CRM update0
retrycron 0 * * * *Повтор неуспешных звонков3

Cloudflare Bindings

BindingТипНазначение
INTEG_DBD1 DatabaseCredentials + данные
INTEG_KVKV NamespaceКэширование
SOFAService BindingGateway → Integration routing

Схема БД для Credentials

sql
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_KEYOpenAI API
CLAUDE_API_KEYClaude API
GROQ_API_KEYGroq API

Per-integration (D1 Creds)

Хранятся зашифрованными в D1:

  • NETHUNT_EMAIL, NETHUNT_API_KEY
  • VOICE_ASSISTANT_URL
  • и т.д.

Паттерны кода

Entry Point (createIntegration)

typescript
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)

typescript
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
Free3
Paid (Workers Paid)5

Мы на Paid Plan — максимум 5 cron задач на один worker. Поскольку каждая интеграция — отдельный worker, лимит считается per integration.

Как это работает

  1. В createIntegration() передаётся массив scheduled
  2. Core использует @happ-integ/cron для автоматического создания POST /cron/trigger endpoint
  3. Core генерирует scheduled worker handler для Cloudflare Cron Triggers
  4. В wrangler.toml (или через Cloudflare Dashboard) регистрируются [triggers] с cron expressions
  5. Cloudflare вызывает scheduled event 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

Конфигурация в коде

typescript
// 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):

ПолеТипОписание
cronstringCron expression (Cloudflare формат)
handlerNamestringИмя handler, должно совпадать с name в defineHandler()
payloadRecord<string, unknown>?Данные, передаваемые в handler как payload

Конфигурация в wrangler.toml

Cron triggers нужно зарегистрировать в wrangler.toml (или через Cloudflare Dashboard):

toml
[triggers]
crons = ["0 * * * *"]

Шаблон templates/wrangler.integration.toml.tpl не содержит [triggers] — их нужно добавлять вручную для интеграций, которые используют scheduled.

Где мы используем cron

ИнтеграцияCronHandlerНазначение
sofa0 * * * * (каждый час)retryОбработка очереди звонков и повторы неуспешных

Cron handler = обычный handler

Handler, вызываемый по cron, ничем не отличается от обычного. Он может одновременно иметь HTTP endpoint (для ручного вызова) и быть привязанным к cron:

typescript
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:

bash
# Все 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, вызывай его:

bash
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'а:

bash
curl "http://localhost:8787/__scheduled"

Cron expressions

ExpressionОписание
* * * * *Каждую минуту
*/5 * * * *Каждые 5 минут
0 * * * *Каждый час
0 0 * * *Каждый день в полночь
0 8 * * 1Каждый понедельник в 08:00

Время — UTC. Подробнее: Cloudflare Cron Triggers docs.

Файлы конфигурации

ФайлНазначение
turbo.jsonTurborepo tasks (build, dev, deploy)
pnpm-workspace.yamlWorkspace packages
wrangler.tomlCloudflare Worker config
tsup.config.tsBuild config для пакетов
doppler.yamlDoppler project config

Связанные документы

Интеграции

ИнтеграцияCRMОписание
SofaNetHuntVoice AI + CRM интеграция