feat: add TeleCartPulse telemetry system and ETL endpoints

- Add TeleCartPulse service for event tracking and analytics
  - Implement PayloadSigner for secure payload signing/verification
  - Add StartParamSerializer for campaign parameter handling
  - Create TeleCartPulseServiceProvider for dependency injection
  - Add PulseEvents constants and exception classes

- Add TelemetryHandler for ingesting client-side events
  - Implement /ingest endpoint for receiving webapp events
  - Support WEBAPP_OPEN event tracking with campaign metadata

- Add ETLHandler for customer data export
  - Implement /customers endpoint for ETL processes
  - Add /customers/meta endpoint for pagination metadata
  - Support filtering by updated_at timestamp
  - Include customer metrics: orders count, total spent, etc.

- Add InvalidApiTokenException for API key validation
- Update Request class to support API key extraction
- Add Utils helper methods for domain extraction
- Integrate telemetry in frontend SPA (webapp open event)
- Add TeleCartPulseView in admin panel for API key configuration
- Update routes to include new telemetry and ETL endpoints
This commit is contained in:
2025-11-30 11:49:55 +03:00
committed by Nikita Kiselev
parent 2743b83a2c
commit e8d0f8a819
26 changed files with 607 additions and 18 deletions

View File

@@ -38,6 +38,10 @@
<RouterLink :to="{name: 'customers'}">Telegram Покупатели</RouterLink>
</li>
<li :class="{active: route.name === 'pulse'}">
<RouterLink :to="{name: 'pulse'}">TeleCart Pulse</RouterLink>
</li>
<li :class="{active: route.name === 'logs'}">
<RouterLink :to="{name: 'logs'}">Журнал событий</RouterLink>
</li>

View File

@@ -9,6 +9,7 @@ import MainPageView from "@/views/MainPageView.vue";
import LogsView from "@/views/LogsView.vue";
import FormBuilderView from "@/views/FormBuilderView.vue";
import CustomersView from "@/views/CustomersView.vue";
import TeleCartPulseView from "@/views/TeleCartPulseView.vue";
const router = createRouter({
history: createMemoryHistory(),
@@ -20,6 +21,7 @@ const router = createRouter({
{path: '/mainpage', name: 'mainpage', component: MainPageView},
{path: '/metrics', name: 'metrics', component: MetricsView},
{path: '/orders', name: 'orders', component: OrdersView},
{path: '/pulse', name: 'pulse', component: TeleCartPulseView},
{path: '/store', name: 'store', component: StoreView},
{path: '/telegram', name: 'telegram', component: TelegramView},
{path: '/texts', name: 'texts', component: TextsView},

View File

@@ -73,6 +73,10 @@ export const useSettingsStore = defineStore('settings', {
schema: [],
}
},
pulse: {
api_key: '',
},
},
}),

View File

@@ -0,0 +1,15 @@
<template>
<ItemInput label="API ключ"
v-model="settings.items.pulse.api_key"
placeholder="AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE"
>
Используется для обмена информацией по кампаниям, рассылкам, сбору метрик.
</ItemInput>
</template>
<script setup>
import {useSettingsStore} from "@/stores/settings.js";
import ItemInput from "@/components/Settings/ItemInput.vue";
const settings = useSettingsStore();
</script>

View File

@@ -0,0 +1,3 @@
export const TC_PULSE_EVENTS = {
WEBAPP_OPEN: 'WEBAPP_OPEN',
};

View File

@@ -8,18 +8,18 @@ import {useSettingsStore} from "@/stores/SettingsStore.js";
import ApplicationError from "@/ApplicationError.vue";
import AppMetaInitializer from "@/utils/AppMetaInitializer.ts";
import {injectYaMetrika} from "@/utils/yaMetrika.js";
import {checkIsUserPrivacyConsented, saveTelegramCustomer} from "@/utils/ftch.js";
import {checkIsUserPrivacyConsented, ingest, saveTelegramCustomer} from "@/utils/ftch.js";
import {register} from 'swiper/element/bundle';
import 'swiper/element/bundle';
import 'swiper/css/bundle';
import AppLoading from "@/AppLoading.vue";
import {useProductFiltersStore} from "@/stores/ProductFiltersStore.js";
import {useBlocksStore} from "@/stores/BlocksStore.js";
import {getCssVarOklchRgb} from "@/helpers.js";
import {defaultConfig, plugin} from '@formkit/vue';
import config from './formkit.config.js';
import {TC_PULSE_EVENTS} from "@/constants/tPulseEvents.js";
register();
@@ -51,18 +51,22 @@ settings.load()
throw new Error('App disabled (maintenance mode)');
}
})
.then(async () => {
.then(() => {
const webapp = window.Telegram.WebApp;
ingest({
event: TC_PULSE_EVENTS.WEBAPP_OPEN,
webapp,
})
.catch(err => console.error('Ingest failed:', err));
})
.then(() => {
// Сохраняем данные Telegram-пользователя в базу данных
const userData = window.Telegram?.WebApp?.initDataUnsafe?.user;
if (userData) {
try {
console.debug('[Init] Saving Telegram customer data');
await saveTelegramCustomer(userData);
console.debug('[Init] Telegram customer data saved successfully');
} catch (error) {
// Не прерываем загрузку приложения, если не удалось сохранить пользователя
console.warn('[Init] Failed to save Telegram customer data:', error);
}
saveTelegramCustomer(userData)
.then(() => console.debug('[Init] Telegram customer data saved successfully'))
.catch(() => console.warn('[Init] Failed to save Telegram customer data:', error));
}
})
.then(() => {
@@ -82,11 +86,11 @@ settings.load()
})();
})
.then(() => blocks.processBlocks(settings.mainpage_blocks))
.then(async () => {
console.debug('Load default filters for the main page');
const filtersStore = useProductFiltersStore();
filtersStore.applied = await filtersStore.fetchFiltersForMainPage();
})
// .then(async () => {
// console.debug('Load default filters for the main page');
// const filtersStore = useProductFiltersStore();
// filtersStore.applied = await filtersStore.fetchFiltersForMainPage();
// })
.then(() => {
console.debug('[Init] Set theme attributes');
document.documentElement.setAttribute('data-theme', settings.theme[window.Telegram.WebApp.colorScheme]);

View File

@@ -1 +1,2 @@
APP_DEBUG=true
PULSE_API_HOST=http://host.docker.internal:8086/api/

View File

@@ -11,6 +11,7 @@ use Openguru\OpenCartFramework\Cache\CacheServiceProvider;
use Openguru\OpenCartFramework\QueryBuilder\QueryBuilderServiceProvider;
use Openguru\OpenCartFramework\Router\RouteServiceProvider;
use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\TeleCartPulse\TeleCartPulseServiceProvider;
use Openguru\OpenCartFramework\Telegram\TelegramServiceProvider;
class ApplicationFactory
@@ -31,6 +32,7 @@ class ApplicationFactory
AppServiceProvider::class,
CacheServiceProvider::class,
TelegramServiceProvider::class,
TeleCartPulseServiceProvider::class,
]);
}
}

View File

@@ -68,6 +68,7 @@ class SettingsHandler
'texts',
'sliders',
'mainpage_blocks',
'pulse',
]);
$data['forms'] = [];
@@ -107,6 +108,7 @@ class SettingsHandler
'texts',
'sliders',
'mainpage_blocks',
'pulse',
]),
);

View File

@@ -5,6 +5,7 @@ namespace Openguru\OpenCartFramework;
use ErrorException;
use Openguru\OpenCartFramework\Contracts\ExceptionHandlerInterface;
use Openguru\OpenCartFramework\Exceptions\ActionNotFoundException;
use Openguru\OpenCartFramework\Exceptions\InvalidApiTokenException;
use Openguru\OpenCartFramework\Exceptions\NonLoggableExceptionInterface;
use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Response;
@@ -71,6 +72,13 @@ class ErrorHandler
exit(1);
}
if ($exception instanceof InvalidApiTokenException) {
(new JsonResponse([
'message' => $exception->getMessage(),
], $exception->getCode()))->send();
exit(1);
}
if (PHP_SAPI === 'cli') {
echo $exception->getMessage() . PHP_EOL;
} else {

View File

@@ -0,0 +1,14 @@
<?php
namespace Openguru\OpenCartFramework\Exceptions;
use Exception;
use Throwable;
class InvalidApiTokenException extends Exception
{
public function __construct($message = "Token is invalid", $code = 401, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@@ -41,7 +41,7 @@ final class Request
public function getContent(): string
{
if ($this->content === null || $this->content === '') {
$this->content = (string) file_get_contents('php://input');
$this->content = (string)file_get_contents('php://input');
}
return $this->content;
@@ -115,4 +115,14 @@ final class Request
return $this->json($key) !== null;
}
public function getApiKey(): ?string
{
$header =
$_SERVER['HTTP_X_API_KEY']
?? $this->header('X-API-KEY')
?? null;
return $header ? trim($header) : null;
}
}

View File

@@ -71,4 +71,31 @@ class Utils
{
return $value === true ? 1 : 0;
}
public static function getCurrentDomain(): string
{
$scheme = 'http';
if (
(! empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
(! empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
) {
$scheme = 'https';
}
// Определяем хост
$host = $_SERVER['HTTP_X_FORWARDED_HOST']
?? $_SERVER['HTTP_HOST']
?? $_SERVER['SERVER_NAME']
?? 'localhost';
// Порт
$port = $_SERVER['SERVER_PORT'] ?? null;
$defaultPort = ($scheme === 'https') ? 443 : 80;
if ($port && $port != $defaultPort && strpos($host, ':') === false) {
$host .= ':' . $port;
}
return $scheme . '://' . $host;
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Openguru\OpenCartFramework\TeleCartPulse;
use Exception;
class PayloadSignException extends Exception
{
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Openguru\OpenCartFramework\TeleCartPulse;
use JsonException;
class PayloadSigner
{
private string $secret;
public function __construct(string $secret)
{
$this->secret = $secret;
}
/**
* @throws PayloadSignException
*/
public function sign(array $payload): string
{
$encoded = $this->encodeJson($payload);
return hash_hmac('sha256', $encoded, $this->secret);
}
/**
* @throws PayloadSignException
*/
public function verify(string $signature, array $payload): bool
{
$encoded = $this->encodeJson($payload);
$expected = hash_hmac('sha256', $encoded, $this->secret);
return hash_equals($signature, $expected);
}
private function encodeJson(array $payload): string
{
try {
return json_encode($payload, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} catch (JsonException $e) {
throw new PayloadSignException('Could not encode JSON: ' . $e->getMessage(), 0, $e);
}
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Openguru\OpenCartFramework\TeleCartPulse;
final class PulseEvents
{
public const WEBAPP_OPEN = 'WEBAPP_OPEN';
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Openguru\OpenCartFramework\TeleCartPulse;
use Exception;
class PulseIngestException extends Exception
{
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Openguru\OpenCartFramework\TeleCartPulse;
class StartParamSerializer
{
/**
* Сериализовать массив параметров в строку для передачи в startapp
*
* Используется base64 кодирование JSON для безопасной передачи данных в URL
*
* @param array<string, mixed> $parameters Массив параметров для передачи
* @return string Сериализованная строка параметров
* @throws \InvalidArgumentException Если не удалось закодировать параметры
*/
public static function serialize(array $parameters): string
{
if (empty($parameters)) {
return '';
}
$json = json_encode($parameters, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($json === false) {
throw new \InvalidArgumentException('Failed to encode parameters to JSON: ' . json_last_error_msg());
}
// Используем base64 для безопасной передачи в URL
$encoded = base64_encode($json);
// Заменяем символы, которые могут вызвать проблемы в URL
// + на -, / на _, убираем = в конце (padding)
return rtrim(strtr($encoded, '+/', '-_'), '=');
}
/**
* Десериализовать строку параметров обратно в массив
*
* @param string $serialized Сериализованная строка параметров
* @return array<string, mixed> Массив параметров
* @throws \InvalidArgumentException Если строка не может быть десериализована
*/
public static function deserialize(string $serialized): array
{
if (empty($serialized)) {
return [];
}
// Восстанавливаем base64 символы
$encoded = strtr($serialized, '-_', '+/');
// Добавляем padding если нужно
$padding = strlen($encoded) % 4;
if ($padding !== 0) {
$encoded .= str_repeat('=', 4 - $padding);
}
$json = base64_decode($encoded, true);
if ($json === false) {
throw new \InvalidArgumentException('Failed to decode base64 string');
}
$parameters = json_decode($json, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \InvalidArgumentException('Failed to decode JSON: ' . json_last_error_msg());
}
if (! is_array($parameters)) {
throw new \InvalidArgumentException('Decoded value is not an array');
}
return $parameters;
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace Openguru\OpenCartFramework\TeleCartPulse;
use Carbon\Carbon;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\GuzzleException;
use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\Support\Utils;
use Openguru\OpenCartFramework\Telegram\TelegramInitDataDecoder;
use Throwable;
class TeleCartPulseService
{
private TelegramInitDataDecoder $initDataDecoder;
private PayloadSigner $payloadSigner;
private string $apiKey;
public function __construct(TelegramInitDataDecoder $initDataDecoder, PayloadSigner $payloadSigner, string $apiKey)
{
$this->initDataDecoder = $initDataDecoder;
$this->payloadSigner = $payloadSigner;
$this->apiKey = $apiKey;
}
/**
* @throws PulseIngestException
*/
public function handleIngest(array $data): void
{
if (! $this->apiKey) {
return;
}
$initData = Arr::get($data, 'webapp.initData');
if (! $initData) {
return;
}
$event = Arr::get($data, 'event');
if (! $event) {
return;
}
try {
$decoded = $this->initDataDecoder->parseInitDataStringToArray($initData);
$startParam = Arr::get($decoded, 'start_param');
$deserialized = StartParamSerializer::deserialize($startParam);
if ($event === PulseEvents::WEBAPP_OPEN) {
$this->handleWebAppInit($data, $deserialized);
}
} catch (ClientException $exception) {
$contents = (string)$exception->getResponse()->getBody();
$decoded = json_decode($contents, true);
throw new PulseIngestException('TeleCart Pulse API error: ' . $decoded['error'] ?? '', 0, $exception);
} catch (Throwable $exception) {
throw new PulseIngestException('Could not handle ingest: ' . $exception->getMessage(), 0, $exception);
}
}
/**
* @throws PayloadSignException
* @throws GuzzleException
*/
private function handleWebAppInit(array $data, array $deserialized): void
{
// Campaign Event
if (isset($deserialized['campaign_id'], $deserialized['tracking_id'])) {
$payload = [
'event' => PulseEvents::WEBAPP_OPEN,
'campaign_id' => $deserialized['campaign_id'],
'tracking_id' => $deserialized['tracking_id'],
'meta' => [
'domain' => Utils::getCurrentDomain(),
'version' => Arr::get($data, 'webapp.version'),
'platform' => Arr::get($data, 'webapp.platform'),
],
'timestamp' => Carbon::now('UTC')->toJSON(),
];
$dataToSend = [
'payload' => $payload,
'signature' => $this->payloadSigner->sign($payload),
];
$this->pushEvent($dataToSend);
}
}
/**
* @throws GuzzleException
*/
private function pushEvent(array $json): void
{
$baseUri = rtrim(env('PULSE_API_HOST', 'http://localhost'), '/') . '/';
$client = new Client([
'base_uri' => $baseUri,
'timeout' => env('PULSE_TIMEOUT', 5.0),
'headers' => [
'Authorization' => 'Bearer ' . $this->apiKey,
'X-TELECART-VERSION' => '2.0.0',
],
]);
$client->post('events', compact('json'));
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Openguru\OpenCartFramework\TeleCartPulse;
use Openguru\OpenCartFramework\Container\Container;
use Openguru\OpenCartFramework\Container\ServiceProvider;
use Openguru\OpenCartFramework\Telegram\TelegramInitDataDecoder;
class TeleCartPulseServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->container->singleton(PayloadSigner::class, function (Container $app) {
return new PayloadSigner(
$app->getConfigValue('pulse.api_key'),
);
});
$this->container->singleton(TeleCartPulseService::class, function (Container $app) {
return new TeleCartPulseService(
$app->get(TelegramInitDataDecoder::class),
$app->get(PayloadSigner::class),
$app->getConfigValue('pulse.api_key'),
);
});
}
}

View File

@@ -32,7 +32,7 @@ class TelegramInitDataDecoder
/**
* @throws JsonException
*/
private function parseInitDataStringToArray(string $initData): array
public function parseInitDataStringToArray(string $initData): array
{
parse_str($initData, $parsed);

View File

@@ -13,6 +13,8 @@ class TelegramValidateInitDataMiddleware
'manifest',
'webhook',
'health',
'etlCustomers',
'etlCustomersMeta',
];
public function __construct(SignatureValidator $signatureValidator)

View File

@@ -9,6 +9,7 @@ use Openguru\OpenCartFramework\Cache\CacheServiceProvider;
use Openguru\OpenCartFramework\QueryBuilder\QueryBuilderServiceProvider;
use Openguru\OpenCartFramework\Router\RouteServiceProvider;
use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\TeleCartPulse\TeleCartPulseServiceProvider;
use Openguru\OpenCartFramework\Telegram\TelegramServiceProvider;
use Openguru\OpenCartFramework\Telegram\TelegramValidateInitDataMiddleware;
use Openguru\OpenCartFramework\Validator\ValidatorServiceProvider;
@@ -30,6 +31,7 @@ class ApplicationFactory
AppServiceProvider::class,
TelegramServiceProvider::class,
ValidatorServiceProvider::class,
TeleCartPulseServiceProvider::class,
])
->withMiddlewares([
TelegramValidateInitDataMiddleware::class,

View File

@@ -0,0 +1,168 @@
<?php
namespace App\Handlers;
use Carbon\Carbon;
use Openguru\OpenCartFramework\Config\Settings;
use Openguru\OpenCartFramework\Exceptions\InvalidApiTokenException;
use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\QueryBuilder\Builder;
use Openguru\OpenCartFramework\QueryBuilder\RawExpression;
use Psr\Log\LoggerInterface;
class ETLHandler
{
private Builder $builder;
private Settings $settings;
private LoggerInterface $logger;
public function __construct(Builder $builder, Settings $settings, LoggerInterface $logger)
{
$this->builder = $builder;
$this->settings = $settings;
$this->logger = $logger;
}
private function getLastUpdatedAtSql(): string
{
return '
GREATEST(
COALESCE((
SELECT MAX(date_modified)
FROM oc_order as o
where o.customer_id = telecart_customers.oc_customer_id
), 0),
telecart_customers.updated_at
)
';
}
private function getCustomerQuery(?Carbon $updatedAt = null): Builder
{
$lastUpdatedAtSql = $this->getLastUpdatedAtSql();
return $this->builder->newQuery()
->from('telecart_customers')
->where('allows_write_to_pm', '=', 1)
->when(! empty($updatedAt), function (Builder $builder) use ($lastUpdatedAtSql, $updatedAt) {
$builder->where(new RawExpression($lastUpdatedAtSql), '>=', $updatedAt);
});
}
/**
* @throws InvalidApiTokenException
*/
public function getCustomersMeta(Request $request): JsonResponse
{
$this->validateApiKey($request);
$updatedAt = $request->get('updated_at');
if ($updatedAt) {
$updatedAt = Carbon::parse($updatedAt);
}
$query = $this->getCustomerQuery($updatedAt);
$total = $query->count();
return new JsonResponse([
'data' => [
'total' => $total,
],
]);
}
/**
* @throws InvalidApiTokenException
*/
public function customers(Request $request): JsonResponse
{
$this->validateApiKey($request);
$this->logger->debug('Get customers for ETL');
$page = (int)$request->get('page', 1);
$perPage = (int)$request->get('perPage', 10000);
$successOrderStatusIds = '5,3';
$updatedAt = $request->get('updated_at');
if ($updatedAt) {
$updatedAt = Carbon::parse($updatedAt);
}
$lastUpdatedAtSql = $this->getLastUpdatedAtSql();
$query = $this->getCustomerQuery($updatedAt);
$query->orderBy('telegram_user_id');
$query->forPage($page, $perPage);
$query
->select([
new RawExpression('md5(telegram_user_id) AS tracking_id'),
'telegram_user_id' => 'tg_user_id',
'telecart_customers.oc_customer_id',
'is_premium',
'last_seen_at',
'orders_count' => 'orders_count_total',
'created_at' => 'registered_at',
new RawExpression(
'(SELECT MIN(date_added) FROM oc_order WHERE oc_order.customer_id = telecart_customers.oc_customer_id) AS first_order_date'
),
new RawExpression(
'(SELECT MAX(date_added) FROM oc_order WHERE oc_order.customer_id = telecart_customers.oc_customer_id) AS last_order_date'
),
new RawExpression(
"COALESCE((
SELECT
SUM(total)
FROM
oc_order
WHERE
oc_order.customer_id = telecart_customers.oc_customer_id
AND oc_order.order_status_id IN ($successOrderStatusIds)
), 0) AS total_spent"
),
new RawExpression(
"COALESCE((
SELECT
COUNT(*)
FROM
oc_order
WHERE
oc_order.customer_id = telecart_customers.oc_customer_id
AND oc_order.order_status_id IN ($successOrderStatusIds)
), 0) AS orders_count_success"
),
new RawExpression("$lastUpdatedAtSql AS updated_at"),
]);
$items = $query->get();
return new JsonResponse([
'data' => array_map(static function ($item) {
$item['is_premium'] = filter_var($item['is_premium'], FILTER_VALIDATE_BOOLEAN);
$item['orders_count_total'] = filter_var($item['orders_count_total'], FILTER_VALIDATE_INT);
$item['oc_customer_id'] = filter_var($item['oc_customer_id'], FILTER_VALIDATE_INT);
$item['tg_user_id'] = filter_var($item['tg_user_id'], FILTER_VALIDATE_INT);
$item['orders_count_success'] = filter_var($item['orders_count_success'], FILTER_VALIDATE_INT);
$item['total_spent'] = (float)$item['total_spent'];
return $item;
}, $items),
]);
}
/**
* @throws InvalidApiTokenException
*/
private function validateApiKey(Request $request): void
{
$token = $request->getApiKey();
if (empty($token)) {
throw new InvalidApiTokenException('Invalid API Key.');
}
if (strcmp($token, $this->settings->get('pulse.api_key')) !== 0) {
throw new InvalidApiTokenException('Invalid API Key');
}
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\Handlers;
use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\Http\Response;
use Openguru\OpenCartFramework\TeleCartPulse\PulseIngestException;
use Openguru\OpenCartFramework\TeleCartPulse\TeleCartPulseService;
class TelemetryHandler
{
private TeleCartPulseService $teleCartPulseService;
public function __construct(TeleCartPulseService $teleCartPulseService)
{
$this->teleCartPulseService = $teleCartPulseService;
}
/**
* @throws PulseIngestException
*/
public function ingest(Request $request): JsonResponse
{
$this->teleCartPulseService->handleIngest($request->json());
return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
}

View File

@@ -3,6 +3,7 @@
use App\Handlers\BlocksHandler;
use App\Handlers\CartHandler;
use App\Handlers\CategoriesHandler;
use App\Handlers\ETLHandler;
use App\Handlers\FiltersHandler;
use App\Handlers\FormsHandler;
use App\Handlers\HealthCheckHandler;
@@ -12,6 +13,7 @@ use App\Handlers\ProductsHandler;
use App\Handlers\SettingsHandler;
use App\Handlers\TelegramCustomerHandler;
use App\Handlers\TelegramHandler;
use App\Handlers\TelemetryHandler;
return [
'categoriesList' => [CategoriesHandler::class, 'index'],
@@ -21,6 +23,7 @@ return [
'getCart' => [CartHandler::class, 'index'],
'getForm' => [FormsHandler::class, 'getForm'],
'health' => [HealthCheckHandler::class, 'handle'],
'ingest' => [TelemetryHandler::class, 'ingest'],
'manifest' => [SettingsHandler::class, 'manifest'],
'processBlock' => [BlocksHandler::class, 'processBlock'],
'product_show' => [ProductsHandler::class, 'show'],
@@ -31,4 +34,6 @@ return [
'testTgMessage' => [SettingsHandler::class, 'testTgMessage'],
'userPrivacyConsent' => [PrivacyPolicyHandler::class, 'userPrivacyConsent'],
'webhook' => [TelegramHandler::class, 'webhook'],
'etlCustomers' => [ETLHandler::class, 'customers'],
'etlCustomersMeta' => [ETLHandler::class, 'getCustomersMeta'],
];