Compare commits
19 Commits
87eb66f06b
...
docs/featu
| Author | SHA1 | Date | |
|---|---|---|---|
| 24075dceb7 | |||
| d621baae45 | |||
| ed9d2c1b1f | |||
| 7f73343f3f | |||
| 0d4ffa7411 | |||
| c24e30f14c | |||
| d43564481d | |||
|
|
41b17925df | ||
|
|
caf862cfed | ||
|
|
eed36fe00e | ||
| 28c8ce3b68 | |||
| 80c2467b4a | |||
| a3f54a87dc | |||
| 554822720b | |||
| 67f2fe6af6 | |||
| 0b786c832f | |||
| f894d33c46 | |||
| 3e6fed813d | |||
|
|
16c60e0e12 |
BIN
.cache/v3.0.3.7.zip
Normal file
BIN
.cache/v3.0.3.7.zip
Normal file
Binary file not shown.
@@ -1,64 +0,0 @@
|
|||||||
# Cursor AI Agents Configuration
|
|
||||||
|
|
||||||
## Роли и правила поведения ИИ
|
|
||||||
|
|
||||||
### Основная роль: Senior Full-Stack Developer
|
|
||||||
|
|
||||||
Вы - опытный full-stack разработчик, специализирующийся на:
|
|
||||||
|
|
||||||
- OpenCart модульной разработке
|
|
||||||
- Кастомных фреймворках (OpenCart Framework)
|
|
||||||
- PHP 7.4+ с современными практиками
|
|
||||||
- Vue.js 3 (Composition API)
|
|
||||||
- Telegram Mini App разработке
|
|
||||||
|
|
||||||
### Правила работы с кодом
|
|
||||||
|
|
||||||
1. **Всегда используй существующие паттерны проекта**
|
|
||||||
2. **Не создавай дубликаты - используй существующие утилиты**
|
|
||||||
3. **Следуй соглашениям именования проекта**
|
|
||||||
4. **Тестируй изменения перед коммитом**
|
|
||||||
5. **Документируй публичные API**
|
|
||||||
6. **Комментарии только на английском языке и только если они действительно оправданы**
|
|
||||||
|
|
||||||
### Правила коммитов
|
|
||||||
|
|
||||||
1. **Следование Conventional Commits**
|
|
||||||
- Используй префиксы: `feat:`, `fix:`, `chore:`, `refactor:`, `style:`, `test:`, `docs:`
|
|
||||||
- Формат: `<type>: <subject>` (первая строка до 72 символов)
|
|
||||||
- После пустой строки - подробное описание изменений
|
|
||||||
|
|
||||||
2. **Язык коммитов**
|
|
||||||
- Все коммиты на **английском языке**
|
|
||||||
- Подробное описание изменений в теле коммита
|
|
||||||
- Перечисляй все измененные файлы и ключевые изменения
|
|
||||||
|
|
||||||
3. **Примеры правильных коммитов**
|
|
||||||
```
|
|
||||||
feat: add setting to control category products button visibility
|
|
||||||
|
|
||||||
- Add show_category_products_button field to StoreDTO
|
|
||||||
- Update SettingsSerializerService to support new field
|
|
||||||
- Add setting in admin panel on 'Store' tab with toggle
|
|
||||||
- Pass setting to SPA through SettingsHandler
|
|
||||||
- Button displays only for categories with child categories
|
|
||||||
- Add default value true to configuration
|
|
||||||
```
|
|
||||||
|
|
||||||
### Запрещено
|
|
||||||
|
|
||||||
- Хардкод значений (используй конфиги/настройки)
|
|
||||||
- Игнорирование обработки ошибок
|
|
||||||
- Создание циклических зависимостей
|
|
||||||
|
|
||||||
Для разработки FrontEnd используй:
|
|
||||||
|
|
||||||
- Vue.js 3 (Composition API)
|
|
||||||
- Старайся избегать функций watch там, где это возможно и где можно сделать более красиво.
|
|
||||||
- Для frontend/admin используй Tailwind 4 с префиксом `tw:`.
|
|
||||||
- Для frontend/spa используй Tailwind 4 без префикса.
|
|
||||||
- Для frontend/admin используй иконки от FontAwesome 4, потому что это уже встроено в OpenCart 3.
|
|
||||||
- Для frontend/admin используй компоненты VuePrime 4.
|
|
||||||
- Для frontend/spa используй Daisy UI.
|
|
||||||
- Чтобы получить название стандартной таблицы OpenCart, используй хелпер `db_table`, либо добавляй константу DB_PREFIX перед названием таблицы. Так ты получишь название таблицы с префиксом.
|
|
||||||
- Все таблицы моего модуля MegaPay начинаются с префикса `megapay_`. Примеры миграций лежат в `module/oc_telegram_shop/upload/oc_telegram_shop/database/migrations`
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"rules": {
|
|
||||||
"preferCompositionAPI": true,
|
|
||||||
"strictTypes": true,
|
|
||||||
"noHardcodedValues": true,
|
|
||||||
"useDependencyInjection": true
|
|
||||||
},
|
|
||||||
"paths": {
|
|
||||||
"megapay_module": "module/oc_telegram_shop/upload/oc_telegram_shop",
|
|
||||||
"frontendAdmin": "frontend/admin",
|
|
||||||
"telegramShopSpa": "frontend/spa",
|
|
||||||
"migrations": "module/oc_telegram_shop/upload/oc_telegram_shop/database/migrations",
|
|
||||||
"megapayHandlers": "module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers",
|
|
||||||
"adminHandlers": "module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers",
|
|
||||||
"models": "module/oc_telegram_shop/upload/oc_telegram_shop/src/Models",
|
|
||||||
"framework": "module/oc_telegram_shop/upload/oc_telegram_shop/framework"
|
|
||||||
},
|
|
||||||
"naming": {
|
|
||||||
"classes": "PascalCase",
|
|
||||||
"methods": "camelCase",
|
|
||||||
"variables": "camelCase",
|
|
||||||
"constants": "UPPER_SNAKE_CASE",
|
|
||||||
"files": "PascalCase for classes, kebab-case for others",
|
|
||||||
"tables": "snake_case with megapay_ prefix"
|
|
||||||
},
|
|
||||||
"php": {
|
|
||||||
"version": "7.4+",
|
|
||||||
"preferVersion": "7.4+",
|
|
||||||
"psr12": true
|
|
||||||
},
|
|
||||||
"javascript": {
|
|
||||||
"version": "ES2020+",
|
|
||||||
"framework": "Vue 3 Composition API",
|
|
||||||
"stateManagement": "Pinia",
|
|
||||||
"uiLibrary": "PrimeVue (admin), Tailwind (spa)"
|
|
||||||
},
|
|
||||||
"database": {
|
|
||||||
"queryBuilder": true,
|
|
||||||
"migrations": true,
|
|
||||||
"tablePrefix": "megapay_",
|
|
||||||
"noForeignKeys": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
## MegaPay Pulse Heartbeat Telemetry
|
|
||||||
|
|
||||||
### Цель
|
|
||||||
Раз в час отправлять телеметрию (heartbeat) на MegaPay Pulse, чтобы фиксировать состояние магазина и версии окружения без участия пользователя.
|
|
||||||
|
|
||||||
### Backend (`module/oc_telegram_shop/upload/oc_telegram_shop`)
|
|
||||||
- `framework/MegaPayPulse/MegaPayPulseService.php`
|
|
||||||
- Новый метод `handleHeartbeat()` собирает данные: домен (через `Utils::getCurrentDomain()`), username бота (через `TelegramService::getMe()`), версии PHP, модуля (из `composer.json`), OpenCart (`VERSION` и `VERSION_CORE`), текущий UTC timestamp.
|
|
||||||
- Последний успешный пинг кешируется (ключ `megapay_pulse_heartbeat`, TTL 1 час) через существующий `CacheInterface`.
|
|
||||||
- Подпись heartbeat выполняется через отдельный `PayloadSigner`, который использует секрет `pulse.heartbeat_secret`/`PULSE_HEARTBEAT_SECRET`. Логируются предупреждения при ошибках кеша/бота/подписи.
|
|
||||||
- Отправка идет на эндпоинт `heartbeat` с таймаутом 2 секунды и заголовком `X-MEGAPAY-VERSION`, взятым из `composer.json`.
|
|
||||||
- `framework/MegaPayPulse/MegaPayPulseServiceProvider.php`
|
|
||||||
- Регистрирует основной `PayloadSigner` (по `pulse.api_key`) и отдельный heartbeat signer (по `pulse.heartbeat_secret` или `PULSE_HEARTBEAT_SECRET`), инжектит `LoggerInterface`.
|
|
||||||
- `src/Handlers/TelemetryHandler.php` + `src/routes.php`
|
|
||||||
- Добавлен маршрут `heartbeat`, который вызывает `handleHeartbeat()` и возвращает `{ status: "ok" }`. Логгер пишет warning при проблемах.
|
|
||||||
|
|
||||||
### Frontend (`frontend/spa`)
|
|
||||||
- `src/utils/ftch.js`: новая функция `heartbeat()` вызывает `api_action=heartbeat`.
|
|
||||||
- `src/stores/Pulse.js`: добавлен action `heartbeat`, использующий новую API-функцию и логирующий результат.
|
|
||||||
- `src/main.js`: после `pulse.ingest(...)` вызывается `pulse.heartbeat()` без блокировки цепочки.
|
|
||||||
|
|
||||||
### Конфигурация / ENV
|
|
||||||
- `PULSE_API_HOST` — базовый URL MegaPay Pulse (используется и для events, и для heartbeat).
|
|
||||||
- `PULSE_TIMEOUT` — общий таймаут HTTP (для heartbeat принудительно 2 секунды).
|
|
||||||
- `PULSE_HEARTBEAT_SECRET` (или `pulse.heartbeat_secret` в настройках) — общий секрет для подписания heartbeat. Обязателен, иначе heartbeat не будет отправляться.
|
|
||||||
- `pulse.api_key` — прежний API ключ, используется только для event-инджеста.
|
|
||||||
|
|
||||||
### Поведение
|
|
||||||
1. Frontend (SPA) вызывает `heartbeat` при инициализации приложения (fire-and-forget).
|
|
||||||
2. Backend проверяет кеш. Если часа еще не прошло, `handleHeartbeat()` возвращает без запросов.
|
|
||||||
3. При необходимости собираются данные, подписываются через heartbeat signer и отправляются POST-запросом на `/heartbeat`.
|
|
||||||
4. Любые сбои (bot info, подпись, HTTP) логируются как warning, чтобы не тревожить пользователей.
|
|
||||||
|
|
||||||
### TODO / Возможные улучшения
|
|
||||||
- При необходимости вынести heartbeat запуск в крон/CLI, чтобы не зависеть от фронтенда.
|
|
||||||
- Добавить метрики успешности heartbeat в админку.
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
# Промпты для генерации API
|
|
||||||
|
|
||||||
## Создание нового API endpoint
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай новый API endpoint [ENDPOINT_NAME] для [DESCRIPTION]:
|
|
||||||
|
|
||||||
1. Handler в [HANDLER_PATH]:
|
|
||||||
- Метод handle() принимает Request
|
|
||||||
- Валидация входных данных
|
|
||||||
- Использование Service для бизнес-логики
|
|
||||||
- Возврат JsonResponse с правильной структурой
|
|
||||||
- Обработка ошибок с логированием
|
|
||||||
|
|
||||||
2. Service в [SERVICE_PATH]:
|
|
||||||
- Бизнес-логика
|
|
||||||
- Работа с Model
|
|
||||||
- Валидация данных
|
|
||||||
- Обработка исключений
|
|
||||||
|
|
||||||
3. Model в [MODEL_PATH] (если нужен):
|
|
||||||
- Методы для работы с БД
|
|
||||||
- Использование Query Builder
|
|
||||||
- Типизация методов
|
|
||||||
|
|
||||||
4. Route в routes.php:
|
|
||||||
- Добавь маршрут с правильным именем
|
|
||||||
|
|
||||||
5. Миграция (если нужна новая таблица):
|
|
||||||
- Создай миграцию в database/migrations/
|
|
||||||
- Используй фиксированный префикс megapay_
|
|
||||||
- Добавь индексы где необходимо
|
|
||||||
|
|
||||||
Следуй архитектуре MVC-L проекта и используй существующие паттерны.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание CRUD API
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай полный CRUD API для сущности [ENTITY_NAME]:
|
|
||||||
|
|
||||||
1. Handler с методами:
|
|
||||||
- list() - список с пагинацией и фильтрацией
|
|
||||||
- get() - получение одной записи
|
|
||||||
- create() - создание
|
|
||||||
- update() - обновление
|
|
||||||
- delete() - удаление
|
|
||||||
|
|
||||||
2. Service с бизнес-логикой для всех операций
|
|
||||||
|
|
||||||
3. Model с методами:
|
|
||||||
- findAll() - список
|
|
||||||
- findById() - по ID
|
|
||||||
- create() - создание
|
|
||||||
- update() - обновление
|
|
||||||
- delete() - удаление
|
|
||||||
|
|
||||||
4. DTO для валидации данных
|
|
||||||
|
|
||||||
5. Миграция для таблицы [TABLE_NAME]
|
|
||||||
|
|
||||||
6. Routes для всех endpoints
|
|
||||||
|
|
||||||
Используй серверную пагинацию, фильтрацию и сортировку для list().
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание Admin API endpoint
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай Admin API endpoint [ENDPOINT_NAME] в bastion/Handlers/:
|
|
||||||
|
|
||||||
1. Handler в bastion/Handlers/[HANDLER_NAME].php:
|
|
||||||
- Используй Request для получения параметров
|
|
||||||
- Валидация данных
|
|
||||||
- Работа через Service
|
|
||||||
- Возврат JsonResponse с структурой { data: { data: [...], totalRecords: ... } }
|
|
||||||
- Обработка ошибок
|
|
||||||
|
|
||||||
2. Service в bastion/Services/ (если нужен):
|
|
||||||
- Бизнес-логика для админки
|
|
||||||
- Работа с Models
|
|
||||||
|
|
||||||
3. Route в bastion/routes.php
|
|
||||||
|
|
||||||
4. Frontend компонент (если нужен UI):
|
|
||||||
- Vue компонент в frontend/admin/src/views/
|
|
||||||
- Используй PrimeVue компоненты
|
|
||||||
- Серверная пагинация/фильтрация
|
|
||||||
- Обработка ошибок с toast уведомлениями
|
|
||||||
|
|
||||||
Следуй существующим паттернам проекта.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание Frontend API клиента
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай функцию для работы с API endpoint [ENDPOINT_NAME]:
|
|
||||||
|
|
||||||
1. В frontend/[admin|spa]/src/utils/http.js:
|
|
||||||
- Функция api[Method] для вызова endpoint
|
|
||||||
- Правильная обработка ошибок
|
|
||||||
- Возврат структурированного ответа
|
|
||||||
|
|
||||||
2. Использование:
|
|
||||||
- В компонентах через import
|
|
||||||
- Обработка loading states
|
|
||||||
- Toast уведомления для ошибок
|
|
||||||
|
|
||||||
Следуй существующим паттернам в http.js.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание миграции
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай миграцию для таблицы [TABLE_NAME]:
|
|
||||||
|
|
||||||
1. Файл: database/migrations/[TIMESTAMP]_[DESCRIPTION].php
|
|
||||||
2. Используй фиксированный префикс megapay_ для таблицы
|
|
||||||
3. Добавь все необходимые поля с правильными типами
|
|
||||||
4. Добавь индексы для часто используемых полей
|
|
||||||
5. Используй utf8mb4_unicode_ci collation
|
|
||||||
6. Используй InnoDB engine
|
|
||||||
7. Добавь created_at и updated_at timestamps
|
|
||||||
8. Не создавай foreign keys (используй только индексы)
|
|
||||||
|
|
||||||
Следуй структуре существующих миграций.
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
# Правила оформления Changelog для документации
|
|
||||||
|
|
||||||
## Общие требования
|
|
||||||
|
|
||||||
- **Формат**: Markdown
|
|
||||||
- **Структура**: Одна версия = одна страница
|
|
||||||
- **Стиль**: Профессиональный, лаконичный, без маркетинговых лозунгов
|
|
||||||
- **Язык**: Русский
|
|
||||||
- **Целевая аудитория**: Разработчики и владельцы магазинов
|
|
||||||
- **Содержимое**: Только ключевые изменения, без лишних технических деталей
|
|
||||||
|
|
||||||
## Структура страницы
|
|
||||||
|
|
||||||
### Вводный абзац
|
|
||||||
- Краткое описание релиза (1-2 предложения)
|
|
||||||
- Дополнить 1-2 предложениями о ключевых изменениях
|
|
||||||
- Упоминать основные фичи и улучшения
|
|
||||||
|
|
||||||
### Разделы (строго в этом порядке)
|
|
||||||
|
|
||||||
1. **🚀 Добавлено** - новые функции и возможности
|
|
||||||
2. **✨ Улучшено** - улучшения существующих функций
|
|
||||||
3. **🐞 Исправлено** - исправленные ошибки
|
|
||||||
4. **⚠️ Несовместимые изменения** - изменения без обратной совместимости
|
|
||||||
5. **🔧 Технические изменения** - технические изменения (отдельный раздел)
|
|
||||||
|
|
||||||
### Правила для разделов
|
|
||||||
|
|
||||||
- Раздел **НЕ** добавлять, если в нём нет пунктов
|
|
||||||
- Использовать маркдаун-списки, без нумерации
|
|
||||||
- Не добавлять ссылок, если они явно не указаны
|
|
||||||
- Не добавлять лишних пояснений, выводов и заключений
|
|
||||||
- Выводить только содержимое Markdown-файла, без комментариев
|
|
||||||
|
|
||||||
## Разделение изменений
|
|
||||||
|
|
||||||
### Бизнес-логика и процессы
|
|
||||||
Разделы "Добавлено", "Улучшено", "Исправлено", "Несовместимые изменения" содержат только изменения, связанные с:
|
|
||||||
- Бизнес-процессами магазина
|
|
||||||
- Пользовательским опытом
|
|
||||||
- Функциональностью для владельцев магазинов
|
|
||||||
- Продающими фичами
|
|
||||||
|
|
||||||
### Технические изменения
|
|
||||||
Все технические изменения выносятся в отдельный раздел **🔧 Технические изменения**:
|
|
||||||
- Без подразделов, всё в одном списке
|
|
||||||
- Только самые ключевые технические изменения
|
|
||||||
- Не расписывать детали, которые не интересны пользователю
|
|
||||||
|
|
||||||
## Стиль написания
|
|
||||||
|
|
||||||
### Терминология
|
|
||||||
- Английские термины писать по-английски (например: "navbar", а не "навбар")
|
|
||||||
- Избегать технических терминов в бизнес-разделах (например: "customer_id" → "автоматическое связывание покупателей")
|
|
||||||
|
|
||||||
### Описания
|
|
||||||
|
|
||||||
**Для ключевых продающих фич:**
|
|
||||||
- Давать подробное описание с деталями
|
|
||||||
- Указывать возможности и преимущества
|
|
||||||
- Подчеркивать ценность для пользователя
|
|
||||||
|
|
||||||
**Для технических изменений:**
|
|
||||||
- Кратко, только ключевое
|
|
||||||
- Без лишних деталей
|
|
||||||
|
|
||||||
**Для обычных функций:**
|
|
||||||
- Лаконично, но информативно
|
|
||||||
- Фокус на пользе для пользователя
|
|
||||||
|
|
||||||
## Порядок размещения
|
|
||||||
|
|
||||||
### В разделе "Добавлено"
|
|
||||||
Ключевые продающие фичи размещать **первыми**:
|
|
||||||
1. Система конфигурации главной страницы через блоки
|
|
||||||
2. Конструктор форм на базе FormKit
|
|
||||||
3. Интеграция с Яндекс.Метрикой
|
|
||||||
4. Политика конфиденциальности
|
|
||||||
5. Поддержка купонов и подарочных сертификатов
|
|
||||||
6. Остальные функции
|
|
||||||
|
|
||||||
## Примеры правильных формулировок
|
|
||||||
|
|
||||||
### ✅ Хорошо
|
|
||||||
- "Автоматическое связывание покупателей Telegram с покупателями OpenCart: система автоматически находит и связывает покупателей из Telegram-магазина с существующими покупателями в OpenCart по email или номеру телефона, создавая единую базу клиентов"
|
|
||||||
|
|
||||||
### ❌ Плохо
|
|
||||||
- "Сохранение customer_id вместе с заказом для связи с клиентами OpenCart: автоматическая связь заказов из Telegram с покупателями в OpenCart, единая база клиентов"
|
|
||||||
|
|
||||||
### ✅ Хорошо
|
|
||||||
- "Navbar с логотипом и названием приложения"
|
|
||||||
|
|
||||||
### ❌ Плохо
|
|
||||||
- "Навбар с логотипом и названием приложения"
|
|
||||||
|
|
||||||
## Что не включать
|
|
||||||
|
|
||||||
- Внутренние детали разработки (тесты, статический анализ, обфускация)
|
|
||||||
- Технические детали, не интересные пользователям
|
|
||||||
- Избыточные технические описания
|
|
||||||
- Маркетинговые лозунги и призывы
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
# Промпты для документирования
|
|
||||||
|
|
||||||
## Документирование класса
|
|
||||||
|
|
||||||
```
|
|
||||||
Добавь PHPDoc документацию для класса [CLASS_NAME]:
|
|
||||||
1. Описание класса и его назначения
|
|
||||||
2. @package тег
|
|
||||||
3. @author тег
|
|
||||||
4. Документация для всех публичных методов
|
|
||||||
5. Документация для публичных свойств
|
|
||||||
6. Примеры использования где уместно
|
|
||||||
```
|
|
||||||
|
|
||||||
## Документирование метода
|
|
||||||
|
|
||||||
```
|
|
||||||
Добавь PHPDoc для метода [METHOD_NAME]:
|
|
||||||
1. Описание метода
|
|
||||||
2. @param для всех параметров с типами
|
|
||||||
3. @return с типом возвращаемого значения
|
|
||||||
4. @throws для всех исключений
|
|
||||||
5. Примеры использования если сложная логика
|
|
||||||
```
|
|
||||||
|
|
||||||
## Документирование API endpoint
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай документацию для API endpoint [ENDPOINT_NAME]:
|
|
||||||
1. Описание назначения
|
|
||||||
2. HTTP метод и путь
|
|
||||||
3. Параметры запроса (query/body)
|
|
||||||
4. Формат ответа (JSON структура)
|
|
||||||
5. Коды ошибок
|
|
||||||
6. Примеры запросов/ответов
|
|
||||||
7. Требования к авторизации
|
|
||||||
```
|
|
||||||
|
|
||||||
## Документирование Vue компонента
|
|
||||||
|
|
||||||
```
|
|
||||||
Добавь документацию для Vue компонента [COMPONENT_NAME]:
|
|
||||||
1. Описание компонента
|
|
||||||
2. Props с типами и описаниями
|
|
||||||
3. Emits с описаниями
|
|
||||||
4. Slots если есть
|
|
||||||
5. Примеры использования
|
|
||||||
6. Зависимости от других компонентов
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание README
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай README.md для [MODULE/COMPONENT]:
|
|
||||||
1. Описание назначения
|
|
||||||
2. Установка/настройка
|
|
||||||
3. Использование с примерами
|
|
||||||
4. API документация
|
|
||||||
5. Конфигурация
|
|
||||||
6. Troubleshooting
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
# Промпты для рефакторинга
|
|
||||||
|
|
||||||
## Общий рефакторинг
|
|
||||||
|
|
||||||
```
|
|
||||||
Проанализируй код в файле [FILE_PATH] и выполни рефакторинг:
|
|
||||||
1. Убери дублирование кода
|
|
||||||
2. Улучши читаемость
|
|
||||||
3. Примени принципы SOLID
|
|
||||||
4. Добавь обработку ошибок где необходимо
|
|
||||||
5. Улучши типизацию
|
|
||||||
6. Добавь документацию для публичных методов
|
|
||||||
7. Убедись что код следует архитектуре MVC-L проекта
|
|
||||||
8. Используй существующие утилиты и сервисы проекта вместо создания новых
|
|
||||||
```
|
|
||||||
|
|
||||||
## Рефакторинг Handler
|
|
||||||
|
|
||||||
```
|
|
||||||
Рефакторинг Handler [HANDLER_NAME]:
|
|
||||||
1. Вынеси бизнес-логику в отдельный Service
|
|
||||||
2. Добавь валидацию входных данных
|
|
||||||
3. Улучши обработку ошибок с логированием
|
|
||||||
4. Используй DTO для передачи данных
|
|
||||||
5. Добавь PHPDoc комментарии
|
|
||||||
6. Убедись что используется Dependency Injection
|
|
||||||
7. Оптимизируй запросы к БД если необходимо
|
|
||||||
```
|
|
||||||
|
|
||||||
## Рефакторинг Model
|
|
||||||
|
|
||||||
```
|
|
||||||
Рефакторинг Model [MODEL_NAME]:
|
|
||||||
1. Убедись что все запросы используют Query Builder
|
|
||||||
2. Добавь методы для частых операций (findBy, findAll, create, update)
|
|
||||||
3. Добавь валидацию данных перед сохранением
|
|
||||||
4. Улучши типизацию методов
|
|
||||||
5. Добавь PHPDoc комментарии
|
|
||||||
6. Используй транзакции для сложных операций
|
|
||||||
```
|
|
||||||
|
|
||||||
## Рефакторинг Vue компонента
|
|
||||||
|
|
||||||
```
|
|
||||||
Рефакторинг Vue компонента [COMPONENT_NAME]:
|
|
||||||
1. Вынеси логику в composable функции
|
|
||||||
2. Улучши типизацию props и emits
|
|
||||||
3. Оптимизируй computed properties
|
|
||||||
4. Добавь обработку ошибок
|
|
||||||
5. Улучши структуру template
|
|
||||||
6. Добавь loading states
|
|
||||||
7. Используй существующие утилиты проекта
|
|
||||||
```
|
|
||||||
|
|
||||||
## Удаление дублирования
|
|
||||||
|
|
||||||
```
|
|
||||||
Найди и устрани дублирование кода в:
|
|
||||||
- [FILE_PATH_1]
|
|
||||||
- [FILE_PATH_2]
|
|
||||||
- [FILE_PATH_3]
|
|
||||||
|
|
||||||
Создай общие утилиты/сервисы где необходимо, следуя архитектуре проекта.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Улучшение производительности
|
|
||||||
|
|
||||||
```
|
|
||||||
Проанализируй производительность кода в [FILE_PATH]:
|
|
||||||
1. Оптимизируй запросы к БД (используй индексы, избегай N+1)
|
|
||||||
2. Добавь кэширование где уместно
|
|
||||||
3. Оптимизируй алгоритмы
|
|
||||||
4. Уменьши количество запросов к API
|
|
||||||
5. Используй ленивую загрузку на фронтенде
|
|
||||||
```
|
|
||||||
|
|
||||||
## Улучшение безопасности
|
|
||||||
|
|
||||||
```
|
|
||||||
Улучши безопасность кода в [FILE_PATH]:
|
|
||||||
1. Добавь валидацию всех входных данных
|
|
||||||
2. Используй prepared statements (Query Builder)
|
|
||||||
3. Добавь CSRF защиту где необходимо
|
|
||||||
4. Валидируй права доступа
|
|
||||||
5. Санитизируй выходные данные
|
|
||||||
6. Добавь rate limiting где необходимо
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
# Промпты для тестирования
|
|
||||||
|
|
||||||
## Создание unit теста
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай unit тест для [CLASS_NAME] в tests/Unit/:
|
|
||||||
|
|
||||||
1. Используй PHPUnit
|
|
||||||
2. Покрой все публичные методы
|
|
||||||
3. Тестируй успешные сценарии
|
|
||||||
4. Тестируй обработку ошибок
|
|
||||||
5. Используй моки для зависимостей
|
|
||||||
6. Следуй структуре существующих тестов
|
|
||||||
7. Используй TestCase базовый класс проекта
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание integration теста
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай integration тест для [FEATURE_NAME] в tests/Integration/:
|
|
||||||
|
|
||||||
1. Тестируй полный flow от запроса до ответа
|
|
||||||
2. Используй тестовую БД
|
|
||||||
3. Очищай данные после тестов
|
|
||||||
4. Тестируй реальные сценарии использования
|
|
||||||
5. Проверяй валидацию данных
|
|
||||||
6. Проверяй обработку ошибок
|
|
||||||
```
|
|
||||||
|
|
||||||
## Создание Vue компонент теста
|
|
||||||
|
|
||||||
```
|
|
||||||
Создай тест для Vue компонента [COMPONENT_NAME] в frontend/[admin|spa]/tests/:
|
|
||||||
|
|
||||||
1. Используй Vitest
|
|
||||||
2. Тестируй рендеринг компонента
|
|
||||||
3. Тестируй props
|
|
||||||
4. Тестируй события (emits)
|
|
||||||
5. Тестируй пользовательские взаимодействия
|
|
||||||
6. Используй моки для API вызовов
|
|
||||||
7. Следуй структуре существующих тестов
|
|
||||||
```
|
|
||||||
|
|
||||||
## Покрытие тестами
|
|
||||||
|
|
||||||
```
|
|
||||||
Проанализируй покрытие тестами для [FILE_PATH]:
|
|
||||||
1. Определи какие методы не покрыты тестами
|
|
||||||
2. Создай тесты для критичных методов
|
|
||||||
3. Убедись что тестируются граничные случаи
|
|
||||||
4. Добавь тесты для обработки ошибок
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
# Архитектурные правила
|
|
||||||
|
|
||||||
## OpenCart Framework Architecture
|
|
||||||
|
|
||||||
### MVC-L Pattern
|
|
||||||
|
|
||||||
Проект использует модифицированный паттерн MVC-L (Model-View-Controller-Language):
|
|
||||||
|
|
||||||
- **Model**: Классы в `src/Models/` - работа с данными, доступ к БД
|
|
||||||
- **View**: Vue компоненты на фронтенде, JSON ответы на бэкенде
|
|
||||||
- **Controller**: Handlers в `src/Handlers/` и `bastion/Handlers/`
|
|
||||||
- **Language**: Переводчик в `framework/Translator/`
|
|
||||||
|
|
||||||
### Dependency Injection
|
|
||||||
|
|
||||||
Все зависимости внедряются через Container:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Правильно
|
|
||||||
public function __construct(
|
|
||||||
private Builder $builder,
|
|
||||||
private TelegramCustomer $telegramCustomerModel
|
|
||||||
) {}
|
|
||||||
|
|
||||||
// ❌ Неправильно
|
|
||||||
public function __construct() {
|
|
||||||
$this->builder = new Builder(...);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Service Providers
|
|
||||||
|
|
||||||
Регистрация сервисов через Service Providers:
|
|
||||||
|
|
||||||
```php
|
|
||||||
class MyServiceProvider extends ServiceProvider
|
|
||||||
{
|
|
||||||
public function register(): void
|
|
||||||
{
|
|
||||||
$this->app->singleton(MyService::class, function ($app) {
|
|
||||||
return new MyService($app->get(Dependency::class));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Routes
|
|
||||||
|
|
||||||
Маршруты определяются в `routes.php`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
return [
|
|
||||||
'actionName' => [HandlerClass::class, 'methodName'],
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
### Handlers (Controllers)
|
|
||||||
|
|
||||||
Handlers обрабатывают HTTP запросы:
|
|
||||||
|
|
||||||
```php
|
|
||||||
class MyHandler
|
|
||||||
{
|
|
||||||
public function handle(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
// Валидация
|
|
||||||
// Бизнес-логика через Services
|
|
||||||
// Возврат JsonResponse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Models
|
|
||||||
|
|
||||||
Models работают с данными:
|
|
||||||
|
|
||||||
```php
|
|
||||||
class MyModel
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private ConnectionInterface $database,
|
|
||||||
private Builder $builder
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function findById(int $id): ?array
|
|
||||||
{
|
|
||||||
return $this->builder->newQuery()
|
|
||||||
->from($this->tableName)
|
|
||||||
->where('id', '=', $id)
|
|
||||||
->firstOrNull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Services
|
|
||||||
|
|
||||||
Services содержат бизнес-логику:
|
|
||||||
|
|
||||||
```php
|
|
||||||
class MyService
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private MyModel $model
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function doSomething(array $data): array
|
|
||||||
{
|
|
||||||
// Бизнес-логика
|
|
||||||
return $this->model->create($data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Migrations
|
|
||||||
|
|
||||||
Миграции в `database/migrations/`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
return new class extends Migration {
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->database->statement('CREATE TABLE ...');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Query Builder
|
|
||||||
|
|
||||||
Всегда используй Query Builder вместо прямых SQL:
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Правильно
|
|
||||||
$query = $this->builder->newQuery()
|
|
||||||
->select(['id', 'name'])
|
|
||||||
->from('table_name')
|
|
||||||
->where('status', '=', 'active')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
// ❌ Неправильно
|
|
||||||
$result = $this->database->query("SELECT * FROM table_name WHERE status = 'active'");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Frontend Architecture
|
|
||||||
|
|
||||||
#### Admin Panel (Vue 3)
|
|
||||||
|
|
||||||
- Composition API
|
|
||||||
- Pinia для state management
|
|
||||||
- PrimeVue для UI компонентов
|
|
||||||
- Axios для HTTP запросов
|
|
||||||
- Vue Router для навигации
|
|
||||||
|
|
||||||
#### SPA (Telegram Mini App)
|
|
||||||
|
|
||||||
- Composition API
|
|
||||||
- Pinia stores
|
|
||||||
- Tailwind CSS для стилей
|
|
||||||
- Telegram WebApp API
|
|
||||||
- Vue Router
|
|
||||||
|
|
||||||
### Naming Conventions
|
|
||||||
|
|
||||||
- **Classes**: PascalCase (`TelegramCustomerService`)
|
|
||||||
- **Methods**: camelCase (`getCustomers`)
|
|
||||||
- **Variables**: camelCase (`$customerData`)
|
|
||||||
- **Constants**: UPPER_SNAKE_CASE (`MAX_RETRIES`)
|
|
||||||
- **Files**: PascalCase для классов, kebab-case для остального
|
|
||||||
- **Tables**: snake_case с префиксом `megapay_`
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
|
|
||||||
Всегда обрабатывай ошибки:
|
|
||||||
|
|
||||||
```php
|
|
||||||
try {
|
|
||||||
$result = $this->service->doSomething();
|
|
||||||
} catch (SpecificException $e) {
|
|
||||||
$this->logger->error('Error message', ['exception' => $e]);
|
|
||||||
throw new UserFriendlyException('User message');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
Используй конфигурационные файлы в `configs/`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$config = $this->app->getConfigValue('app.setting_name');
|
|
||||||
```
|
|
||||||
|
|
||||||
### Caching
|
|
||||||
|
|
||||||
Используй Cache Service для кэширования:
|
|
||||||
|
|
||||||
```php
|
|
||||||
$cache = $this->app->get(CacheInterface::class);
|
|
||||||
$value = $cache->get('key', function() {
|
|
||||||
return expensiveOperation();
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
# FormBuilder System Context
|
|
||||||
|
|
||||||
## Architectural Overview
|
|
||||||
The FormBuilder ecosystem is a strictly typed Vue 3 application module designed to generate standard FormKit Schema JSON. It eschews internal DTOs in favor of direct schema manipulation.
|
|
||||||
|
|
||||||
### Core Components
|
|
||||||
1. **FormBuilderView (`views/FormBuilderView.vue`)**:
|
|
||||||
* **Role**: Smart container / Data fetcher.
|
|
||||||
* **Responsibility**: Fetches form data from API (`GET /api/admin/forms/{alias}`), handles loading states, and passes data to `FormBuilder`.
|
|
||||||
* **Contract**: Expects API response `{ data: { schema: Array, is_custom: Boolean, ... } }`.
|
|
||||||
|
|
||||||
2. **FormBuilder (`components/FormBuilder/FormBuilder.vue`)**:
|
|
||||||
* **Role**: Main Orchestrator / State Owner.
|
|
||||||
* **Responsibility**: Manages `v-model` (schema), mode switching (Visual/Code/Preview), and provides state to children.
|
|
||||||
* **State Management**: Uses `defineModel` for `formFields` (schema) and `isCustom` (mode flag). Uses `provide('formFields')` and `provide('selectedFieldId')` for deep dependency injection.
|
|
||||||
* **Modes**:
|
|
||||||
* **Visual**: Drag-and-drop interface using `vuedraggable`.
|
|
||||||
* **Code**: Direct JSON editing of the FormKit schema. Sets `isCustom = true`.
|
|
||||||
* **Preview**: Renders the current schema using `FormKit`.
|
|
||||||
|
|
||||||
3. **FormCanvas (`components/FormBuilder/FormCanvas.vue`)**:
|
|
||||||
* **Role**: Visual Editor Surface.
|
|
||||||
* **Responsibility**: Renders the draggable list of fields.
|
|
||||||
* **Implementation**: Uses `vuedraggable` bound to `formFields`.
|
|
||||||
* **UX**: Implements "Ghost" and "Drag" classes for visual feedback. Handles selection logic.
|
|
||||||
|
|
||||||
4. **FieldsPanel (`components/FormBuilder/FieldsPanel.vue`)**:
|
|
||||||
* **Role**: Component Palette.
|
|
||||||
* **Responsibility**: Source of truth for available field types.
|
|
||||||
* **Implementation**: Uses `vuedraggable` with `pull: 'clone', put: false` to spawn new fields.
|
|
||||||
|
|
||||||
5. **FieldSettings (`components/FormBuilder/FieldSettings.vue`)**:
|
|
||||||
* **Role**: Property Editor.
|
|
||||||
* **Responsibility**: Edits properties of the `selectedFieldId`.
|
|
||||||
* **Constraint**: Must use PrimeVue components for all inputs.
|
|
||||||
|
|
||||||
## Data Flow & Invariants
|
|
||||||
1. **Schema Authority**: The FormKit Schema JSON is the single source of truth. There is no "internal model" separate from the schema.
|
|
||||||
2. **Reactivity**:
|
|
||||||
* `formFields` is an Array of Objects.
|
|
||||||
* Mutations must preserve reactivity. When using `v-model` or `provide/inject`, ensure array methods (splice, push, filter) are used correctly or replace the entire array reference if needed to trigger watchers.
|
|
||||||
* **Immutability**: `useFormFields` composable uses immutable patterns (returning new array references) to ensure `defineModel` in parent detects changes.
|
|
||||||
3. **Mode Logic**:
|
|
||||||
* Switching to **Code** mode sets `isCustom = true`.
|
|
||||||
* Switching to **Visual** mode sets `isCustom = false`.
|
|
||||||
* **Safety**: Switching modes triggers JSON validation. Invalid JSON prevents mode switch.
|
|
||||||
4. **Drag and Drop**:
|
|
||||||
* Powered by `vuedraggable` (Sortable.js).
|
|
||||||
* **Clone Logic**: `FieldsPanel` clones from `availableFields`. `FormCanvas` receives the clone.
|
|
||||||
* **ID Generation**: Unique IDs are generated upon cloning/addition to ensure key stability.
|
|
||||||
|
|
||||||
## Naming & Conventions
|
|
||||||
* **Tailwind**: Use `tw:` prefix for all utility classes (e.g., `tw:flex`, `tw:p-4`).
|
|
||||||
* **Components**: PrimeVue components are the standard UI kit (Button, Panel, InputText, etc.).
|
|
||||||
* **Icons**: FontAwesome (`fa fa-*`).
|
|
||||||
* **Files**: PascalCase for components (`FormBuilder.vue`), camelCase for logic (`useFormFields.js`).
|
|
||||||
|
|
||||||
## Integration Rules
|
|
||||||
* **Backend**: The backend stores the JSON blob directly. `FormBuilder` does not transform data before save; it emits the raw schema.
|
|
||||||
* **API**: `useFormsStore` handles API communication.
|
|
||||||
|
|
||||||
## Pitfalls & Warnings
|
|
||||||
* **vuedraggable vs @formkit/drag-and-drop**: We strictly use `vuedraggable`. Do not re-introduce `@formkit/drag-and-drop`.
|
|
||||||
* **Watchers**: Avoid `watch` where `computed` or event handlers suffice, to prevent infinite loops in bidirectional data flow.
|
|
||||||
* **Tailwind Config**: Do not use `@apply` with `tw:` prefixed classes in `<style>` blocks; standard CSS properties should be used if custom classes are needed.
|
|
||||||
|
|
||||||
## Future Modifications
|
|
||||||
* **Adding Fields**: Update `constants/availableFields.js` and ensure `utils/fieldHelpers.js` supports the new type.
|
|
||||||
* **Validation**: FormKit validation rules string (e.g., "required|email") is edited as a raw string in `FieldSettings`. Complex validation builders would require a new UI component.
|
|
||||||
|
|
||||||
@@ -1,332 +0,0 @@
|
|||||||
# JavaScript/TypeScript Code Style Rules
|
|
||||||
|
|
||||||
## JavaScript Version
|
|
||||||
|
|
||||||
- ES2020+ features
|
|
||||||
- Modern async/await
|
|
||||||
- Optional chaining (`?.`)
|
|
||||||
- Nullish coalescing (`??`)
|
|
||||||
- Template literals
|
|
||||||
|
|
||||||
## Code Style
|
|
||||||
|
|
||||||
### Variable Declarations
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй const по умолчанию
|
|
||||||
const customers = [];
|
|
||||||
const totalRecords = 0;
|
|
||||||
|
|
||||||
// ✅ let только когда нужно переназначение
|
|
||||||
let currentPage = 1;
|
|
||||||
currentPage = 2;
|
|
||||||
|
|
||||||
// ❌ Не используй var
|
|
||||||
var oldVariable = 'bad';
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arrow Functions
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Предпочтительно для коротких функций
|
|
||||||
const filtered = items.filter(item => item.isActive);
|
|
||||||
|
|
||||||
// ✅ Для методов объектов
|
|
||||||
const api = {
|
|
||||||
get: async (url) => {
|
|
||||||
return await fetch(url);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ✅ Для сложной логики - обычные функции
|
|
||||||
function complexCalculation(data) {
|
|
||||||
// много строк кода
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Template Literals
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Предпочтительно
|
|
||||||
const message = `User ${userId} not found`;
|
|
||||||
const url = `${baseUrl}/api/${endpoint}`;
|
|
||||||
|
|
||||||
// ❌ Не используй конкатенацию
|
|
||||||
const message = 'User ' + userId + ' not found';
|
|
||||||
```
|
|
||||||
|
|
||||||
### Optional Chaining
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй optional chaining
|
|
||||||
const name = user?.profile?.name;
|
|
||||||
const count = data?.items?.length ?? 0;
|
|
||||||
|
|
||||||
// ❌ Избегай длинных проверок
|
|
||||||
const name = user && user.profile && user.profile.name;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nullish Coalescing
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй ?? для значений по умолчанию
|
|
||||||
const page = params.page ?? 1;
|
|
||||||
const name = user.name ?? 'Unknown';
|
|
||||||
|
|
||||||
// ❌ Не используй || для чисел/булевых
|
|
||||||
const page = params.page || 1; // 0 будет заменено на 1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Destructuring
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй деструктуризацию
|
|
||||||
const { data, totalRecords } = response.data;
|
|
||||||
const [first, second] = items;
|
|
||||||
|
|
||||||
// ✅ В параметрах функций
|
|
||||||
function processUser({ id, name, email }) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ С значениями по умолчанию
|
|
||||||
const { page = 1, limit = 20 } = params;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Async/Await
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Предпочтительно
|
|
||||||
async function loadCustomers() {
|
|
||||||
try {
|
|
||||||
const response = await apiGet('getCustomers', params);
|
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ❌ Избегай .then() цепочек
|
|
||||||
function loadCustomers() {
|
|
||||||
return apiGet('getCustomers', params)
|
|
||||||
.then(response => response.data)
|
|
||||||
.catch(error => console.error(error));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Vue.js 3 Composition API
|
|
||||||
|
|
||||||
### Script Setup
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Используй <script setup>
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
import { apiGet } from '@/utils/http.js';
|
|
||||||
|
|
||||||
const customers = ref([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
const totalRecords = computed(() => customers.value.length);
|
|
||||||
|
|
||||||
async function loadCustomers() {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const result = await apiGet('getCustomers');
|
|
||||||
customers.value = result.data || [];
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadCustomers();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Reactive State
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй ref для примитивов
|
|
||||||
const count = ref(0);
|
|
||||||
const name = ref('');
|
|
||||||
|
|
||||||
// ✅ Используй reactive для объектов
|
|
||||||
import { reactive } from 'vue';
|
|
||||||
const state = reactive({
|
|
||||||
customers: [],
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// ✅ Или ref для объектов (предпочтительно)
|
|
||||||
const state = ref({
|
|
||||||
customers: [],
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Computed Properties
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй computed для производных значений
|
|
||||||
const filteredCustomers = computed(() => {
|
|
||||||
return customers.value.filter(c => c.isActive);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ❌ Не используй методы для вычислений
|
|
||||||
function filteredCustomers() {
|
|
||||||
return customers.value.filter(c => c.isActive);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Props
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Определяй props с типами
|
|
||||||
const props = defineProps({
|
|
||||||
customerId: {
|
|
||||||
type: Number,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
showDetails: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Emits
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Определяй emits
|
|
||||||
const emit = defineEmits(['update', 'delete']);
|
|
||||||
|
|
||||||
function handleUpdate() {
|
|
||||||
emit('update', data);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Pinia Stores
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Используй setup stores
|
|
||||||
import { defineStore } from 'pinia';
|
|
||||||
import { ref, computed } from 'vue';
|
|
||||||
|
|
||||||
export const useCustomersStore = defineStore('customers', () => {
|
|
||||||
const customers = ref([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
const totalRecords = computed(() => customers.value.length);
|
|
||||||
|
|
||||||
async function loadCustomers() {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const result = await apiGet('getCustomers');
|
|
||||||
customers.value = result.data || [];
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
customers,
|
|
||||||
loading,
|
|
||||||
totalRecords,
|
|
||||||
loadCustomers
|
|
||||||
};
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Всегда обрабатывай ошибки
|
|
||||||
async function loadData() {
|
|
||||||
try {
|
|
||||||
const result = await apiGet('endpoint');
|
|
||||||
if (result.success) {
|
|
||||||
return result.data;
|
|
||||||
} else {
|
|
||||||
throw new Error(result.error);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load data:', error);
|
|
||||||
toast.error('Не удалось загрузить данные');
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Naming Conventions
|
|
||||||
|
|
||||||
### Variables and Functions
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ camelCase
|
|
||||||
const customerData = {};
|
|
||||||
const totalRecords = 0;
|
|
||||||
function loadCustomers() {}
|
|
||||||
|
|
||||||
// ✅ Константы UPPER_SNAKE_CASE
|
|
||||||
const MAX_RETRIES = 3;
|
|
||||||
const API_BASE_URL = '/api';
|
|
||||||
```
|
|
||||||
|
|
||||||
### Components
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<!-- ✅ PascalCase для компонентов -->
|
|
||||||
<CustomerCard />
|
|
||||||
<ProductsList />
|
|
||||||
```
|
|
||||||
|
|
||||||
### Files
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ kebab-case для файлов
|
|
||||||
// customers-view.vue
|
|
||||||
// http-utils.js
|
|
||||||
// customer-service.js
|
|
||||||
```
|
|
||||||
|
|
||||||
## Imports
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ✅ Группируй импорты
|
|
||||||
// 1. Vue core
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
|
|
||||||
// 2. Third-party
|
|
||||||
import { apiGet } from '@/utils/http.js';
|
|
||||||
import { useToast } from 'primevue';
|
|
||||||
|
|
||||||
// 3. Local components
|
|
||||||
import CustomerCard from '@/components/CustomerCard.vue';
|
|
||||||
|
|
||||||
// 4. Types (если TypeScript)
|
|
||||||
import type { Customer } from '@/types';
|
|
||||||
```
|
|
||||||
|
|
||||||
## TypeScript (где используется)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// ✅ Используй типы
|
|
||||||
interface Customer {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
email?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCustomer(id: number): Promise<Customer> {
|
|
||||||
return apiGet(`customers/${id}`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
# PHP Code Style Rules
|
|
||||||
|
|
||||||
## PHP Version
|
|
||||||
|
|
||||||
Проект поддерживает PHP 7.4+
|
|
||||||
|
|
||||||
## PSR Standards
|
|
||||||
|
|
||||||
- **PSR-1**: Basic Coding Standard
|
|
||||||
- **PSR-4**: Autoloading Standard
|
|
||||||
- **PSR-12**: Extended Coding Style
|
|
||||||
|
|
||||||
## Code Style
|
|
||||||
|
|
||||||
### Type Declarations
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Правильно - строгая типизация
|
|
||||||
public function getCustomers(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
$id = (int) $request->get('id');
|
|
||||||
return new JsonResponse(['data' => $customers]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ❌ Неправильно - без типов
|
|
||||||
public function getCustomers($request)
|
|
||||||
{
|
|
||||||
return ['data' => $customers];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nullable Types
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Правильно
|
|
||||||
public function findById(?int $id): ?array
|
|
||||||
{
|
|
||||||
if ($id === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return $this->query->where('id', '=', $id)->firstOrNull();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Strict Types
|
|
||||||
|
|
||||||
Всегда используй `declare(strict_types=1);`:
|
|
||||||
|
|
||||||
```php
|
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Services;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Array Syntax
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Предпочтительно - короткий синтаксис
|
|
||||||
$array = ['key' => 'value'];
|
|
||||||
|
|
||||||
// ❌ Не использовать
|
|
||||||
$array = array('key' => 'value');
|
|
||||||
```
|
|
||||||
|
|
||||||
### String Interpolation
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Предпочтительно
|
|
||||||
$message = "User {$userId} not found";
|
|
||||||
|
|
||||||
// ✅ Альтернатива
|
|
||||||
$message = sprintf('User %d not found', $userId);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Arrow Functions (PHP 7.4+)
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Для простых операций
|
|
||||||
$filtered = array_filter($items, fn($item) => $item->isActive());
|
|
||||||
|
|
||||||
// ❌ Для сложной логики - используй обычные функции
|
|
||||||
```
|
|
||||||
|
|
||||||
### Nullsafe Operator (PHP 8.0+)
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Для PHP 7.4
|
|
||||||
$name = $user && $user->profile ? $user->profile->name : null;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Naming Conventions
|
|
||||||
|
|
||||||
### Classes
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ PascalCase
|
|
||||||
class TelegramCustomerService {}
|
|
||||||
class UserRepository {}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Methods
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ camelCase
|
|
||||||
public function getCustomers(): array {}
|
|
||||||
public function saveOrUpdate(array $data): array {}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Variables
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ camelCase
|
|
||||||
$customerData = [];
|
|
||||||
$totalRecords = 0;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Constants
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ UPPER_SNAKE_CASE
|
|
||||||
private const MAX_RETRIES = 3;
|
|
||||||
public const DEFAULT_PAGE_SIZE = 20;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Private Properties
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ camelCase с модификатором доступа
|
|
||||||
private string $tableName;
|
|
||||||
private Builder $builder;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
### PHPDoc
|
|
||||||
|
|
||||||
```php
|
|
||||||
/**
|
|
||||||
* @throws ValidationException Если параметры невалидны
|
|
||||||
*/
|
|
||||||
public function getCustomers(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Inline Comments
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Полезные комментарии
|
|
||||||
// Применяем фильтры для подсчета общего количества записей
|
|
||||||
$countQuery = $this->buildCountQuery($filters);
|
|
||||||
|
|
||||||
// ❌ Очевидные комментарии
|
|
||||||
// Получаем данные
|
|
||||||
$data = $this->getData();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
### Exceptions
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Специфичные исключения
|
|
||||||
if (!$userId) {
|
|
||||||
throw new InvalidArgumentException('User ID is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Логирование
|
|
||||||
try {
|
|
||||||
$result = $this->service->process();
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$this->logger->error('Processing failed', [
|
|
||||||
'exception' => $e,
|
|
||||||
'context' => $context,
|
|
||||||
]);
|
|
||||||
throw new ProcessingException('Failed to process', 0, $e);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Query Builder Usage
|
|
||||||
|
|
||||||
### Always Use Query Builder
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Правильно
|
|
||||||
$customers = $this->builder->newQuery()
|
|
||||||
->select(['id', 'name', 'email'])
|
|
||||||
->from('megapay_customers')
|
|
||||||
->where('status', '=', 'active')
|
|
||||||
->orderBy('created_at', 'DESC')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
// В крайних случаях можно использовать прямые SQL
|
|
||||||
$result = $this->database->query("SELECT * FROM megapay_customers");
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parameter Binding
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Query Builder автоматически биндит параметры
|
|
||||||
$query->where('name', 'LIKE', "%{$search}%");
|
|
||||||
|
|
||||||
// ❌ Никогда не конкатенируй значения в SQL, избегай SQL Injection.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Array Access
|
|
||||||
|
|
||||||
### Safe Array Access
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Используй Arr::get()
|
|
||||||
use Openguru\OpenCartFramework\Support\Arr;
|
|
||||||
|
|
||||||
$value = Arr::get($data, 'key', 'default');
|
|
||||||
|
|
||||||
// ❌ Небезопасно
|
|
||||||
$value = $data['key']; // может вызвать ошибку
|
|
||||||
```
|
|
||||||
|
|
||||||
## Return Types
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Всегда указывай return type
|
|
||||||
public function getData(): array {}
|
|
||||||
public function findById(int $id): ?array {}
|
|
||||||
public function process(): void {}
|
|
||||||
|
|
||||||
// ❌ Без типа
|
|
||||||
public function getData() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Visibility Modifiers
|
|
||||||
|
|
||||||
```php
|
|
||||||
// ✅ Всегда указывай модификатор доступа
|
|
||||||
private string $tableName;
|
|
||||||
protected Builder $builder;
|
|
||||||
public function getData(): array {}
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,370 +0,0 @@
|
|||||||
# Vue.js 3 Rules
|
|
||||||
|
|
||||||
## Component Structure
|
|
||||||
|
|
||||||
### Template
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- ✅ Логическая структура -->
|
|
||||||
<div class="container">
|
|
||||||
<header>
|
|
||||||
<h2>{{ title }}</h2>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<DataTable :value="items" />
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
|
||||||
<Button @click="handleSave">Save</Button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Script Setup
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Всегда используй <script setup>
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
import DataTable from 'primevue/datatable';
|
|
||||||
import { apiGet } from '@/utils/http.js';
|
|
||||||
|
|
||||||
// Props
|
|
||||||
const props = defineProps({
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Emits
|
|
||||||
const emit = defineEmits(['update', 'delete']);
|
|
||||||
|
|
||||||
// State
|
|
||||||
const items = ref([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
// Computed
|
|
||||||
const totalItems = computed(() => items.value.length);
|
|
||||||
|
|
||||||
// Methods
|
|
||||||
async function loadItems() {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const result = await apiGet('getItems');
|
|
||||||
items.value = result.data || [];
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lifecycle
|
|
||||||
onMounted(() => {
|
|
||||||
loadItems();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Styles
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<style scoped>
|
|
||||||
/* ✅ Используй scoped стили */
|
|
||||||
.container {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ✅ Используй :deep() для доступа к дочерним компонентам */
|
|
||||||
:deep(.p-datatable) {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Component Naming
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<!-- ✅ PascalCase для компонентов -->
|
|
||||||
<CustomerCard />
|
|
||||||
<ProductsList />
|
|
||||||
<OrderDetails />
|
|
||||||
|
|
||||||
<!-- ✅ kebab-case в шаблоне тоже работает -->
|
|
||||||
<customer-card />
|
|
||||||
```
|
|
||||||
|
|
||||||
## Props
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Всегда определяй типы и валидацию
|
|
||||||
const props = defineProps({
|
|
||||||
customerId: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
validator: (value) => value > 0
|
|
||||||
},
|
|
||||||
showDetails: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
items: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Emits
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Определяй emits с типами
|
|
||||||
const emit = defineEmits<{
|
|
||||||
update: [id: number, data: object];
|
|
||||||
delete: [id: number];
|
|
||||||
cancel: [];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
// ✅ Или с валидацией
|
|
||||||
const emit = defineEmits({
|
|
||||||
update: (id: number, data: object) => {
|
|
||||||
if (id > 0 && typeof data === 'object') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
console.warn('Invalid emit arguments');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Reactive State
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ ref для примитивов
|
|
||||||
const count = ref(0);
|
|
||||||
const name = ref('');
|
|
||||||
|
|
||||||
// ✅ ref для объектов (предпочтительно)
|
|
||||||
const customer = ref({
|
|
||||||
id: null,
|
|
||||||
name: '',
|
|
||||||
email: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
// ✅ reactive только если нужно
|
|
||||||
import { reactive } from 'vue';
|
|
||||||
const state = reactive({
|
|
||||||
items: [],
|
|
||||||
loading: false
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Computed Properties
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Используй computed для производных значений
|
|
||||||
const filteredItems = computed(() => {
|
|
||||||
return items.value.filter(item => item.isActive);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ✅ Computed с getter/setter
|
|
||||||
const fullName = computed({
|
|
||||||
get: () => `${firstName.value} ${lastName.value}`,
|
|
||||||
set: (value) => {
|
|
||||||
const parts = value.split(' ');
|
|
||||||
firstName.value = parts[0];
|
|
||||||
lastName.value = parts[1];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Event Handlers
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- ✅ Используй kebab-case для событий -->
|
|
||||||
<Button @click="handleClick" />
|
|
||||||
<Input @input="handleInput" />
|
|
||||||
<Form @submit.prevent="handleSubmit" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
// ✅ Именуй обработчики с префиксом handle
|
|
||||||
function handleClick() {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleInput(event) {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSubmit() {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Conditional Rendering
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- ✅ Используй v-if для условного рендеринга -->
|
|
||||||
<div v-if="loading">
|
|
||||||
<LoadingSpinner />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ✅ v-show для частых переключений -->
|
|
||||||
<div v-show="hasItems">
|
|
||||||
<ItemsList :items="items" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ✅ v-else для альтернатив -->
|
|
||||||
<div v-else>
|
|
||||||
<EmptyState />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Lists
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- ✅ Всегда используй :key -->
|
|
||||||
<div v-for="item in items" :key="item.id">
|
|
||||||
{{ item.name }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ✅ Для индексов -->
|
|
||||||
<div v-for="(item, index) in items" :key="`item-${index}`">
|
|
||||||
{{ item.name }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Form Handling
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<form @submit.prevent="handleSubmit">
|
|
||||||
<!-- ✅ Используй v-model -->
|
|
||||||
<InputText v-model="form.name" />
|
|
||||||
<Textarea v-model="form.description" />
|
|
||||||
|
|
||||||
<!-- ✅ Для кастомных компонентов -->
|
|
||||||
<CustomInput v-model="form.email" />
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
const form = ref({
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
email: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleSubmit() {
|
|
||||||
// Валидация и отправка
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## PrimeVue Components
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<template>
|
|
||||||
<!-- ✅ Используй PrimeVue компоненты в админке -->
|
|
||||||
<DataTable
|
|
||||||
:value="customers"
|
|
||||||
:loading="loading"
|
|
||||||
paginator
|
|
||||||
:rows="20"
|
|
||||||
@page="onPage"
|
|
||||||
>
|
|
||||||
<Column field="name" header="Name" sortable />
|
|
||||||
</DataTable>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Styling
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<style scoped>
|
|
||||||
/* ✅ Используй scoped -->
|
|
||||||
.container {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ✅ :deep() для дочерних компонентов */
|
|
||||||
:deep(.p-datatable) {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ✅ :slotted() для слотов */
|
|
||||||
:slotted(.header) {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Composition Functions
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
// ✅ Выноси сложную логику в composables
|
|
||||||
import { useCustomers } from '@/composables/useCustomers.js';
|
|
||||||
|
|
||||||
const {
|
|
||||||
customers,
|
|
||||||
loading,
|
|
||||||
loadCustomers,
|
|
||||||
totalRecords
|
|
||||||
} = useCustomers();
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
loadCustomers();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
```vue
|
|
||||||
<script setup>
|
|
||||||
import { useToast } from 'primevue';
|
|
||||||
|
|
||||||
const toast = useToast();
|
|
||||||
|
|
||||||
async function loadData() {
|
|
||||||
try {
|
|
||||||
const result = await apiGet('endpoint');
|
|
||||||
if (result.success) {
|
|
||||||
data.value = result.data;
|
|
||||||
} else {
|
|
||||||
toast.add({
|
|
||||||
severity: 'error',
|
|
||||||
summary: 'Ошибка',
|
|
||||||
detail: result.error
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
toast.add({
|
|
||||||
severity: 'error',
|
|
||||||
summary: 'Ошибка',
|
|
||||||
detail: 'Не удалось загрузить данные'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
6
.github/workflows/main.yaml
vendored
6
.github/workflows/main.yaml
vendored
@@ -4,7 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- 'issue/**'
|
|
||||||
- develop
|
- develop
|
||||||
pull_request:
|
pull_request:
|
||||||
types:
|
types:
|
||||||
@@ -135,6 +134,11 @@ jobs:
|
|||||||
MODULE_ROOT="module/oc_telegram_shop/upload/oc_telegram_shop"
|
MODULE_ROOT="module/oc_telegram_shop/upload/oc_telegram_shop"
|
||||||
echo "${{ needs.version_meta.outputs.tag }}" > "${MODULE_ROOT}/version.txt"
|
echo "${{ needs.version_meta.outputs.tag }}" > "${MODULE_ROOT}/version.txt"
|
||||||
|
|
||||||
|
- name: Set version in install.xml
|
||||||
|
run: |
|
||||||
|
TAG="${{ needs.version_meta.outputs.tag }}"
|
||||||
|
sed -i "s#<version>.*</version>#<version>${TAG}</version>#" module/oc_telegram_shop/install.xml
|
||||||
|
|
||||||
- name: Build module
|
- name: Build module
|
||||||
run: |
|
run: |
|
||||||
bash scripts/ci/build.sh "${GITHUB_WORKSPACE}"
|
bash scripts/ci/build.sh "${GITHUB_WORKSPACE}"
|
||||||
|
|||||||
575
CHANGELOG.md
575
CHANGELOG.md
@@ -4,539 +4,576 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
<!--- END HEADER -->
|
<!--- END HEADER -->
|
||||||
|
|
||||||
## [2.2.1](https://github.com/megapay-labs/megapay/compare/v2.2.0...v2.2.1) (2026-02-22)
|
## [2.3.0](https://github.com/telecart-labs/telecart/compare/v2.2.2...v2.3.0) (2026-05-25)
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## [2.2.0](https://github.com/megapay-labs/megapay/compare/v2.1.0...v2.2.0) (2026-01-09)
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* Add BETA label and UI improvements for MegaPay Pulse tab ([551c4a](https://github.com/megapay-labs/megapay/commit/551c4a3506ddedb1b11851e3d3cbcb4f3ed34e03))
|
|
||||||
* Add cache:clear CLI command for module cache clearing (#46) ([3d0a75](https://github.com/megapay-labs/megapay/commit/3d0a7536a64bc88dbb349a9640260757b46009c4))
|
|
||||||
* Add changelog ([bf99bf](https://github.com/megapay-labs/megapay/commit/bf99bfe8a442c8eaad64f348792b7ddcbfb4486c))
|
|
||||||
* Add config redis cache, categories cache (#44) ([0798f5](https://github.com/megapay-labs/megapay/commit/0798f5c3e98721efbb45e2988350364b606622cd))
|
|
||||||
* Add customer account page with profile information and actions ([ad94af](https://github.com/megapay-labs/megapay/commit/ad94afda6826dd1d120599353121bad000b675a7))
|
|
||||||
* Add customizable text for manager contact button ([0a7877](https://github.com/megapay-labs/megapay/commit/0a7877ddbe7908d6a17089e1005308a945d3d21f))
|
|
||||||
* Add haptic feedback toggle setting ([afade8](https://github.com/megapay-labs/megapay/commit/afade85d004872d10929119db3ac95ee3acd0251))
|
|
||||||
* Add product interaction mode selector with three scenarios ([ecf4df](https://github.com/megapay-labs/megapay/commit/ecf4df363d49bf0d8bdc1b9ca3a241f99f26cfb8))
|
|
||||||
* Add store_id conditions (#43) ([846418](https://github.com/megapay-labs/megapay/commit/84641868e98786264f517865d48619ac4bc1ef7a))
|
|
||||||
* Add system information drawer (#44) ([9da605](https://github.com/megapay-labs/megapay/commit/9da605b9eac82045b46c3edbb998d28a63c22188))
|
|
||||||
* Increase dock icons size and add click animation ([ce2ea9](https://github.com/megapay-labs/megapay/commit/ce2ea9dea1fcd24d70ea66345d761a739d25f9d1))
|
|
||||||
|
|
||||||
##### Admin
|
##### Admin
|
||||||
|
|
||||||
* Improve navigation UI and move logs to drawer ([6a635e](https://github.com/megapay-labs/megapay/commit/6a635e189614c50c8d4bdbeb2486eb5b32ba7da0))
|
* Telegram settings improvements (#63) ([80c246](https://github.com/telecart-labs/telecart/commit/80c2467b4a7c2cb1c4101c483fe3ac31a5ad0cf1))
|
||||||
|
|
||||||
##### Search
|
##### Ocmod
|
||||||
|
|
||||||
* Improvement search cache (#44) ([8a9bac](https://github.com/megapay-labs/megapay/commit/8a9bac8221146b5db4960cc10de3e29dcd75c9bf))
|
* Add TeleCart admin sidebar menu and local dev setup (#57) ([28c8ce](https://github.com/telecart-labs/telecart/commit/28c8ce3b6841ca1d4cc98c8e7bb275e1ad3adcf3))
|
||||||
|
|
||||||
##### Spa
|
##### Spa
|
||||||
|
|
||||||
* Add UTM markers for product view on OpenCart (#47) ([647e20](https://github.com/megapay-labs/megapay/commit/647e20c6b093f3de5e6aafcad474cf1a99189d2e))
|
* Increase max page limit to product lists (#88) ([a3f54a](https://github.com/telecart-labs/telecart/commit/a3f54a87dc8799b3355b294fc155c3cf9d22c3eb))
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* Correct external .env loading ([089b68](https://github.com/megapay-labs/megapay/commit/089b68672262286f4568a6a40627a0c2e0c51b14))
|
|
||||||
* Correctly work with megapay customers without usernames ([0312b8](https://github.com/megapay-labs/megapay/commit/0312b882e1ad5596e823943924d1b284d5592b14))
|
|
||||||
* Missing store_id for carousel products ([3a1f8d](https://github.com/megapay-labs/megapay/commit/3a1f8dbf948c65c6f2d94392c277317f1ce5da75))
|
|
||||||
|
|
||||||
##### Admin
|
|
||||||
|
|
||||||
* Correct logs sorting by datetime with milliseconds ([115c13](https://github.com/megapay-labs/megapay/commit/115c13393f045a8f2eb7992be11ce20e94b23b96))
|
|
||||||
|
|
||||||
##### Spa
|
##### Spa
|
||||||
|
|
||||||
* Correct line breaks for long attribute names and values in Product.vue ([ff7263](https://github.com/megapay-labs/megapay/commit/ff7263649c208449f0b0b65df3e6088115ec78f6))
|
* Handle empty category and products in carousel block ([d43564](https://github.com/telecart-labs/telecart/commit/d43564481d1e35ea8bb92310d20bff2989ca6ea7))
|
||||||
* Correct privacy policy message margin ([79f234](https://github.com/megapay-labs/megapay/commit/79f23400d20ba3cb43160cf898ea589f30c7aa83))
|
* Visual fixes for SPA (#106) ([c24e30](https://github.com/telecart-labs/telecart/commit/c24e30f14cef8a4b6956aaa2ada5dc9b12fbb9cb))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [2.1.0](https://github.com/megapay-labs/megapay/compare/v2.0.0...v2.1.0) (2025-12-24)
|
## [2.2.2](https://github.com/telecart-labs/telecart/compare/v2.2.1...v2.2.2) (2026-03-15)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Normalize order response to valid UTF-8 before JSON encoding (#83) ([f894d3](https://github.com/telecart-labs/telecart/commit/f894d33c464e28a15cbdcd5c72dfd90bd5379e92))
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2.2.1](https://github.com/telecart-labs/telecart/compare/v2.2.0...v2.2.1) (2026-02-22)
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2.2.0](https://github.com/telecart-labs/telecart/compare/v2.1.0...v2.2.0) (2026-01-09)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* Add setting to control category products button visibility ([c3994b](https://github.com/megapay-labs/megapay/commit/c3994b2291790f21cd219d1c5e820c274cb6e085))
|
* Add BETA label and UI improvements for TeleCart Pulse tab ([551c4a](https://github.com/telecart-labs/telecart/commit/551c4a3506ddedb1b11851e3d3cbcb4f3ed34e03))
|
||||||
|
* Add cache:clear CLI command for module cache clearing (#46) ([3d0a75](https://github.com/telecart-labs/telecart/commit/3d0a7536a64bc88dbb349a9640260757b46009c4))
|
||||||
|
* Add changelog ([bf99bf](https://github.com/telecart-labs/telecart/commit/bf99bfe8a442c8eaad64f348792b7ddcbfb4486c))
|
||||||
|
* Add config redis cache, categories cache (#44) ([0798f5](https://github.com/telecart-labs/telecart/commit/0798f5c3e98721efbb45e2988350364b606622cd))
|
||||||
|
* Add customer account page with profile information and actions ([ad94af](https://github.com/telecart-labs/telecart/commit/ad94afda6826dd1d120599353121bad000b675a7))
|
||||||
|
* Add customizable text for manager contact button ([0a7877](https://github.com/telecart-labs/telecart/commit/0a7877ddbe7908d6a17089e1005308a945d3d21f))
|
||||||
|
* Add haptic feedback toggle setting ([afade8](https://github.com/telecart-labs/telecart/commit/afade85d004872d10929119db3ac95ee3acd0251))
|
||||||
|
* Add product interaction mode selector with three scenarios ([ecf4df](https://github.com/telecart-labs/telecart/commit/ecf4df363d49bf0d8bdc1b9ca3a241f99f26cfb8))
|
||||||
|
* Add store_id conditions (#43) ([846418](https://github.com/telecart-labs/telecart/commit/84641868e98786264f517865d48619ac4bc1ef7a))
|
||||||
|
* Add system information drawer (#44) ([9da605](https://github.com/telecart-labs/telecart/commit/9da605b9eac82045b46c3edbb998d28a63c22188))
|
||||||
|
* Increase dock icons size and add click animation ([ce2ea9](https://github.com/telecart-labs/telecart/commit/ce2ea9dea1fcd24d70ea66345d761a739d25f9d1))
|
||||||
|
|
||||||
|
##### Admin
|
||||||
|
|
||||||
|
* Improve navigation UI and move logs to drawer ([6a635e](https://github.com/telecart-labs/telecart/commit/6a635e189614c50c8d4bdbeb2486eb5b32ba7da0))
|
||||||
|
|
||||||
|
##### Search
|
||||||
|
|
||||||
|
* Improvement search cache (#44) ([8a9bac](https://github.com/telecart-labs/telecart/commit/8a9bac8221146b5db4960cc10de3e29dcd75c9bf))
|
||||||
|
|
||||||
|
##### Spa
|
||||||
|
|
||||||
|
* Add UTM markers for product view on OpenCart (#47) ([647e20](https://github.com/telecart-labs/telecart/commit/647e20c6b093f3de5e6aafcad474cf1a99189d2e))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Correct external .env loading ([089b68](https://github.com/telecart-labs/telecart/commit/089b68672262286f4568a6a40627a0c2e0c51b14))
|
||||||
|
* Correctly work with telecart customers without usernames ([0312b8](https://github.com/telecart-labs/telecart/commit/0312b882e1ad5596e823943924d1b284d5592b14))
|
||||||
|
* Missing store_id for carousel products ([3a1f8d](https://github.com/telecart-labs/telecart/commit/3a1f8dbf948c65c6f2d94392c277317f1ce5da75))
|
||||||
|
|
||||||
|
##### Admin
|
||||||
|
|
||||||
|
* Correct logs sorting by datetime with milliseconds ([115c13](https://github.com/telecart-labs/telecart/commit/115c13393f045a8f2eb7992be11ce20e94b23b96))
|
||||||
|
|
||||||
|
##### Spa
|
||||||
|
|
||||||
|
* Correct line breaks for long attribute names and values in Product.vue ([ff7263](https://github.com/telecart-labs/telecart/commit/ff7263649c208449f0b0b65df3e6088115ec78f6))
|
||||||
|
* Correct privacy policy message margin ([79f234](https://github.com/telecart-labs/telecart/commit/79f23400d20ba3cb43160cf898ea589f30c7aa83))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [2.0.0](https://github.com/megapay-labs/megapay/compare/v1.3.2...v2.0.0) (2025-12-23)
|
## [2.1.0](https://github.com/telecart-labs/telecart/compare/v2.0.0...v2.1.0) (2025-12-24)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add setting to control category products button visibility ([c3994b](https://github.com/telecart-labs/telecart/commit/c3994b2291790f21cd219d1c5e820c274cb6e085))
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## [2.0.0](https://github.com/telecart-labs/telecart/compare/v1.3.2...v2.0.0) (2025-12-23)
|
||||||
|
|
||||||
### ⚠ BREAKING CHANGES
|
### ⚠ BREAKING CHANGES
|
||||||
|
|
||||||
* None ([9a93cc](https://github.com/megapay-labs/megapay/commit/9a93cc73421c9c85e3cfbe403cd2c8fb41ba3406))
|
* None ([9a93cc](https://github.com/telecart-labs/telecart/commit/9a93cc73421c9c85e3cfbe403cd2c8fb41ba3406))
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* Add aspect ratio selector for products_carousel ([615e8c](https://github.com/megapay-labs/megapay/commit/615e8c54a60d076a65bc04e60d26c5cbb21c264f))
|
* Add aspect ratio selector for products_carousel ([615e8c](https://github.com/telecart-labs/telecart/commit/615e8c54a60d076a65bc04e60d26c5cbb21c264f))
|
||||||
* Add cron service to run megapay schedule tasks ([16a258](https://github.com/megapay-labs/megapay/commit/16a258ab682947f9856459797bc99b0adbf0d335))
|
* Add cron service to run telecart schedule tasks ([16a258](https://github.com/telecart-labs/telecart/commit/16a258ab682947f9856459797bc99b0adbf0d335))
|
||||||
* Add debug mode for developers. Logs improvements ([fbccd5](https://github.com/megapay-labs/megapay/commit/fbccd506752e8cdada461e92a85b7603335a8f23))
|
* Add debug mode for developers. Logs improvements ([fbccd5](https://github.com/telecart-labs/telecart/commit/fbccd506752e8cdada461e92a85b7603335a8f23))
|
||||||
* Add default configs ([2bc751](https://github.com/megapay-labs/megapay/commit/2bc751119cb5c55c7d29a90d28a24f015ba76692))
|
* Add default configs ([2bc751](https://github.com/telecart-labs/telecart/commit/2bc751119cb5c55c7d29a90d28a24f015ba76692))
|
||||||
* Added new products_carousel bock type ([f0837e](https://github.com/megapay-labs/megapay/commit/f0837e5c94ef3327f0d249e1994dc73a6da1c42b))
|
* Added new products_carousel bock type ([f0837e](https://github.com/telecart-labs/telecart/commit/f0837e5c94ef3327f0d249e1994dc73a6da1c42b))
|
||||||
* Add FormKit framework support and update dependencies ([6a59dc](https://github.com/megapay-labs/megapay/commit/6a59dcc0c9b4f8e6ee003c7e168b632d8199981e))
|
* Add FormKit framework support and update dependencies ([6a59dc](https://github.com/telecart-labs/telecart/commit/6a59dcc0c9b4f8e6ee003c7e168b632d8199981e))
|
||||||
* Add hide keyboard button on search page ([17ff88](https://github.com/megapay-labs/megapay/commit/17ff888c053983a7ae334ba695338ccd8b2db3ab))
|
* Add hide keyboard button on search page ([17ff88](https://github.com/telecart-labs/telecart/commit/17ff888c053983a7ae334ba695338ccd8b2db3ab))
|
||||||
* Add html editor for telegram messages ([97df5b](https://github.com/megapay-labs/megapay/commit/97df5b4c0aa1d5fbf19c2132436045af0846b5f1))
|
* Add html editor for telegram messages ([97df5b](https://github.com/telecart-labs/telecart/commit/97df5b4c0aa1d5fbf19c2132436045af0846b5f1))
|
||||||
* Add italy dump ([13f63e](https://github.com/megapay-labs/megapay/commit/13f63e09fcc3c33cb4de2e809a981a0bf532bb63))
|
* Add italy dump ([13f63e](https://github.com/telecart-labs/telecart/commit/13f63e09fcc3c33cb4de2e809a981a0bf532bb63))
|
||||||
* Add migrations, mantenance tasks, database cache, blocks cache ([c0a6cb](https://github.com/megapay-labs/megapay/commit/c0a6cb17b3fa5a75185ad2e42e8979b1c848c285))
|
* Add migrations, mantenance tasks, database cache, blocks cache ([c0a6cb](https://github.com/telecart-labs/telecart/commit/c0a6cb17b3fa5a75185ad2e42e8979b1c848c285))
|
||||||
* Add old browser checks ([76c32c](https://github.com/megapay-labs/megapay/commit/76c32c53200f33a5de8fee3587b6aa597ce6d04a))
|
* Add old browser checks ([76c32c](https://github.com/telecart-labs/telecart/commit/76c32c53200f33a5de8fee3587b6aa597ce6d04a))
|
||||||
* Add options to select aspect ratio and cron algo for product images ([e9c6ed](https://github.com/megapay-labs/megapay/commit/e9c6ed8ddf801d3cfbb91c08733ab118fec3de21))
|
* Add options to select aspect ratio and cron algo for product images ([e9c6ed](https://github.com/telecart-labs/telecart/commit/e9c6ed8ddf801d3cfbb91c08733ab118fec3de21))
|
||||||
* Add reactivity to formkit ([fdcfce](https://github.com/megapay-labs/megapay/commit/fdcfce0a79af94f5f7ff05e19b4edec0fad4d452))
|
* Add reactivity to formkit ([fdcfce](https://github.com/telecart-labs/telecart/commit/fdcfce0a79af94f5f7ff05e19b4edec0fad4d452))
|
||||||
* Add redis cache driver ([2b0f04](https://github.com/megapay-labs/megapay/commit/2b0f04eb9455e2f1abb5b9374f3348072ffd1d6a))
|
* Add redis cache driver ([2b0f04](https://github.com/telecart-labs/telecart/commit/2b0f04eb9455e2f1abb5b9374f3348072ffd1d6a))
|
||||||
* Add scheduler module ([65973d](https://github.com/megapay-labs/megapay/commit/65973d2d79a8c6bfbfc367b56a2b83e465fa2e32))
|
* Add scheduler module ([65973d](https://github.com/telecart-labs/telecart/commit/65973d2d79a8c6bfbfc367b56a2b83e465fa2e32))
|
||||||
* Add MegaPay Pulse heartbeat telemetry ([b60c77](https://github.com/megapay-labs/megapay/commit/b60c77e4539aab9d2cdb1e9916b7e60c9848d686))
|
* Add TeleCart Pulse heartbeat telemetry ([b60c77](https://github.com/telecart-labs/telecart/commit/b60c77e4539aab9d2cdb1e9916b7e60c9848d686))
|
||||||
* Add MegaPayPulse telemetry system and ETL endpoints ([e8d0f8](https://github.com/megapay-labs/megapay/commit/e8d0f8a8190c2877ac5aa1e0cc7a5a1663598fe5))
|
* Add TeleCartPulse telemetry system and ETL endpoints ([e8d0f8](https://github.com/telecart-labs/telecart/commit/e8d0f8a8190c2877ac5aa1e0cc7a5a1663598fe5))
|
||||||
* Add Telegram customers management system with admin panel ([9a93cc](https://github.com/megapay-labs/megapay/commit/9a93cc73421c9c85e3cfbe403cd2c8fb41ba3406))
|
* Add Telegram customers management system with admin panel ([9a93cc](https://github.com/telecart-labs/telecart/commit/9a93cc73421c9c85e3cfbe403cd2c8fb41ba3406))
|
||||||
* Add texts configuration ([34dfe9](https://github.com/megapay-labs/megapay/commit/34dfe9028693ad488d40f2015af482d789f012c6))
|
* Add texts configuration ([34dfe9](https://github.com/telecart-labs/telecart/commit/34dfe9028693ad488d40f2015af482d789f012c6))
|
||||||
* Add UI for CRON Scheduler ([7372b9](https://github.com/megapay-labs/megapay/commit/7372b9c330ba4ba83458ca8d722cc71f57316180))
|
* Add UI for CRON Scheduler ([7372b9](https://github.com/telecart-labs/telecart/commit/7372b9c330ba4ba83458ca8d722cc71f57316180))
|
||||||
* Add warmup images command ([ecd372](https://github.com/megapay-labs/megapay/commit/ecd372dad30e05c5913fa489e561475584b89079))
|
* Add warmup images command ([ecd372](https://github.com/telecart-labs/telecart/commit/ecd372dad30e05c5913fa489e561475584b89079))
|
||||||
* Better algorythm for image resize ([13e5bc](https://github.com/megapay-labs/megapay/commit/13e5bce8a548439da3dcd892b0c5600ffc995be6))
|
* Better algorythm for image resize ([13e5bc](https://github.com/telecart-labs/telecart/commit/13e5bce8a548439da3dcd892b0c5600ffc995be6))
|
||||||
* Button to show all products from category ([b2d29f](https://github.com/megapay-labs/megapay/commit/b2d29fd3e288991f77ba6c0bee4bc7c5092b6594))
|
* Button to show all products from category ([b2d29f](https://github.com/telecart-labs/telecart/commit/b2d29fd3e288991f77ba6c0bee4bc7c5092b6594))
|
||||||
* Change image crop algorythm for product view page ([262f52](https://github.com/megapay-labs/megapay/commit/262f52929063802404af6f0592741ca836c91bcd))
|
* Change image crop algorythm for product view page ([262f52](https://github.com/telecart-labs/telecart/commit/262f52929063802404af6f0592741ca836c91bcd))
|
||||||
* Clear cache after settings update ([6f9855](https://github.com/megapay-labs/megapay/commit/6f9855995dd3603b622a9e601162ac0b6da9a694))
|
* Clear cache after settings update ([6f9855](https://github.com/telecart-labs/telecart/commit/6f9855995dd3603b622a9e601162ac0b6da9a694))
|
||||||
* Correct stats for megapay dashboard ([05af49](https://github.com/megapay-labs/megapay/commit/05af4949bfcf2a42ece30f1d77816a3c3018eae2))
|
* Correct stats for telecart dashboard ([05af49](https://github.com/telecart-labs/telecart/commit/05af4949bfcf2a42ece30f1d77816a3c3018eae2))
|
||||||
* Design update, show avatar in navbar ([6ac6a4](https://github.com/megapay-labs/megapay/commit/6ac6a42e2105bb6f234c108e3a5d21096b87660f))
|
* Design update, show avatar in navbar ([6ac6a4](https://github.com/telecart-labs/telecart/commit/6ac6a42e2105bb6f234c108e3a5d21096b87660f))
|
||||||
* Disable source maps for frontend production builds ([770ec8](https://github.com/megapay-labs/megapay/commit/770ec81fdcd1456ad7787c9d1e31d92383849f8f))
|
* Disable source maps for frontend production builds ([770ec8](https://github.com/telecart-labs/telecart/commit/770ec81fdcd1456ad7787c9d1e31d92383849f8f))
|
||||||
* Dont migrate tg messages from v1 ([b87797](https://github.com/megapay-labs/megapay/commit/b87797ee6728523d8e17eeab13b85b014c157d95))
|
* Dont migrate tg messages from v1 ([b87797](https://github.com/telecart-labs/telecart/commit/b87797ee6728523d8e17eeab13b85b014c157d95))
|
||||||
* Expose module version ([f1a39e](https://github.com/megapay-labs/megapay/commit/f1a39eeb0023d9fdf99cfd95b21288d950730b23))
|
* Expose module version ([f1a39e](https://github.com/telecart-labs/telecart/commit/f1a39eeb0023d9fdf99cfd95b21288d950730b23))
|
||||||
* Fixed width and preloader for product view page ([5d775e](https://github.com/megapay-labs/megapay/commit/5d775e8eb6710cc1ca3501be1fdfc957582e8663))
|
* Fixed width and preloader for product view page ([5d775e](https://github.com/telecart-labs/telecart/commit/5d775e8eb6710cc1ca3501be1fdfc957582e8663))
|
||||||
* Fix opecart module status, remove .vite ([e72948](https://github.com/megapay-labs/megapay/commit/e729484fd7a698fcadacdfc99a30beb9d4acbb09))
|
* Fix opecart module status, remove .vite ([e72948](https://github.com/telecart-labs/telecart/commit/e729484fd7a698fcadacdfc99a30beb9d4acbb09))
|
||||||
* Hide greeting image from frontend ([2ec683](https://github.com/megapay-labs/megapay/commit/2ec683f0163804e7562de14d098b8a0c0f0f28da))
|
* Hide greeting image from frontend ([2ec683](https://github.com/telecart-labs/telecart/commit/2ec683f0163804e7562de14d098b8a0c0f0f28da))
|
||||||
* Image processing improve ([38668f](https://github.com/megapay-labs/megapay/commit/38668fb4a7f2a3f94a85e06e20ecdff98f5d160d))
|
* Image processing improve ([38668f](https://github.com/telecart-labs/telecart/commit/38668fb4a7f2a3f94a85e06e20ecdff98f5d160d))
|
||||||
* Images and products loading optimization ([bf6744](https://github.com/megapay-labs/megapay/commit/bf674473e97111aa7a2acad9c835fcea37c3b2ec))
|
* Images and products loading optimization ([bf6744](https://github.com/telecart-labs/telecart/commit/bf674473e97111aa7a2acad9c835fcea37c3b2ec))
|
||||||
* Improve mainpage ui/ux ([f5d9d4](https://github.com/megapay-labs/megapay/commit/f5d9d417b3b86c7b710da5751a1d50af10a42b6e))
|
* Improve mainpage ui/ux ([f5d9d4](https://github.com/telecart-labs/telecart/commit/f5d9d417b3b86c7b710da5751a1d50af10a42b6e))
|
||||||
* Increase default per_page products ([6ed2fd](https://github.com/megapay-labs/megapay/commit/6ed2fd2062295bc4296d9ef5c4852541e0e4138f))
|
* Increase default per_page products ([6ed2fd](https://github.com/telecart-labs/telecart/commit/6ed2fd2062295bc4296d9ef5c4852541e0e4138f))
|
||||||
* Integrate yandex metrika ecommerce ([2f74ab](https://github.com/megapay-labs/megapay/commit/2f74aba35f548d632beed65d81693483942289d5))
|
* Integrate yandex metrika ecommerce ([2f74ab](https://github.com/telecart-labs/telecart/commit/2f74aba35f548d632beed65d81693483942289d5))
|
||||||
* Maintenance tasks, logs ([ae9771](https://github.com/megapay-labs/megapay/commit/ae9771dec436bd3ff619b26c9c6ce811b1e876dd))
|
* Maintenance tasks, logs ([ae9771](https://github.com/telecart-labs/telecart/commit/ae9771dec436bd3ff619b26c9c6ce811b1e876dd))
|
||||||
* More fluent vuejs app error handler ([955747](https://github.com/megapay-labs/megapay/commit/955747334d7a7f4863e145f52bcc2864beb8818e))
|
* More fluent vuejs app error handler ([955747](https://github.com/telecart-labs/telecart/commit/955747334d7a7f4863e145f52bcc2864beb8818e))
|
||||||
* Move getImage response to admin ([f539bb](https://github.com/megapay-labs/megapay/commit/f539bbfbbf023995f88b684406ac5eb8f16fff66))
|
* Move getImage response to admin ([f539bb](https://github.com/telecart-labs/telecart/commit/f539bbfbbf023995f88b684406ac5eb8f16fff66))
|
||||||
* New settings and mainpage blocks ([6176c7](https://github.com/megapay-labs/megapay/commit/6176c720b1f4c0ce9f06a3cc4ff50b72a52ab0fb))
|
* New settings and mainpage blocks ([6176c7](https://github.com/telecart-labs/telecart/commit/6176c720b1f4c0ce9f06a3cc4ff50b72a52ab0fb))
|
||||||
* Provide current opencart timezone to App ([51f462](https://github.com/megapay-labs/megapay/commit/51f462922ec49c8cc5e1b0c7909a69180cbe8e72))
|
* Provide current opencart timezone to App ([51f462](https://github.com/telecart-labs/telecart/commit/51f462922ec49c8cc5e1b0c7909a69180cbe8e72))
|
||||||
* Remove unused js libs ([08f0e2](https://github.com/megapay-labs/megapay/commit/08f0e24859c4e201e85075f2186ed741e3180b38))
|
* Remove unused js libs ([08f0e2](https://github.com/telecart-labs/telecart/commit/08f0e24859c4e201e85075f2186ed741e3180b38))
|
||||||
* Send xdebug trigger from frontend ([2743b8](https://github.com/megapay-labs/megapay/commit/2743b83a2c624191d2b65a1b13f5b3645e69b71a))
|
* Send xdebug trigger from frontend ([2743b8](https://github.com/telecart-labs/telecart/commit/2743b83a2c624191d2b65a1b13f5b3645e69b71a))
|
||||||
* Separated coupon and voucher errors ([dd12cb](https://github.com/megapay-labs/megapay/commit/dd12cb8c3434cd3d6f3b8eed4e469db8cd02e3f5))
|
* Separated coupon and voucher errors ([dd12cb](https://github.com/telecart-labs/telecart/commit/dd12cb8c3434cd3d6f3b8eed4e469db8cd02e3f5))
|
||||||
* Set environment variables ([3716e8](https://github.com/megapay-labs/megapay/commit/3716e89811f2a4135d644cb5a6bae0bb57c367ee))
|
* Set environment variables ([3716e8](https://github.com/telecart-labs/telecart/commit/3716e89811f2a4135d644cb5a6bae0bb57c367ee))
|
||||||
* Show module version in admin ([116821](https://github.com/megapay-labs/megapay/commit/116821a20946bf3f341e1589af2b24ace1e904da))
|
* Show module version in admin ([116821](https://github.com/telecart-labs/telecart/commit/116821a20946bf3f341e1589af2b24ace1e904da))
|
||||||
* Store customer_id in with order ([8260d2](https://github.com/megapay-labs/megapay/commit/8260d2bc96bfb256e73673e13740b242756eede2))
|
* Store customer_id in with order ([8260d2](https://github.com/telecart-labs/telecart/commit/8260d2bc96bfb256e73673e13740b242756eede2))
|
||||||
* Tg bot start message customization ([152e6d](https://github.com/megapay-labs/megapay/commit/152e6d715bfff1cfd05bdab72c4d4b54f7878e4a))
|
* Tg bot start message customization ([152e6d](https://github.com/telecart-labs/telecart/commit/152e6d715bfff1cfd05bdab72c4d4b54f7878e4a))
|
||||||
* Track and push MegaPay Pulse events ([ef7856](https://github.com/megapay-labs/megapay/commit/ef785654b969e7abc955ed452d8367d6cf3aa55e))
|
* Track and push TeleCart Pulse events ([ef7856](https://github.com/telecart-labs/telecart/commit/ef785654b969e7abc955ed452d8367d6cf3aa55e))
|
||||||
* UI/UX, add reset cache to admin ([09f1e5](https://github.com/megapay-labs/megapay/commit/09f1e514a975fea5c4fcd3b8cc587f906ab30bd3))
|
* UI/UX, add reset cache to admin ([09f1e5](https://github.com/telecart-labs/telecart/commit/09f1e514a975fea5c4fcd3b8cc587f906ab30bd3))
|
||||||
* Update admin page ([cd818d](https://github.com/megapay-labs/megapay/commit/cd818d3356d5738a9fb534e056d2e1055b2016ce))
|
* Update admin page ([cd818d](https://github.com/telecart-labs/telecart/commit/cd818d3356d5738a9fb534e056d2e1055b2016ce))
|
||||||
* Update design for product and product cards ([8a777c](https://github.com/megapay-labs/megapay/commit/8a777cd4d280b7049b60fdd0d3fa0586561e0a65))
|
* Update design for product and product cards ([8a777c](https://github.com/telecart-labs/telecart/commit/8a777cd4d280b7049b60fdd0d3fa0586561e0a65))
|
||||||
* Update product page design ([c64170](https://github.com/megapay-labs/megapay/commit/c64170f2d8058d99ae60323d907579b59566c119))
|
* Update product page design ([c64170](https://github.com/telecart-labs/telecart/commit/c64170f2d8058d99ae60323d907579b59566c119))
|
||||||
* Update readme ([5fb450](https://github.com/megapay-labs/megapay/commit/5fb45000ac77de0019a256150089256d5c423d68), [540595](https://github.com/megapay-labs/megapay/commit/540595c9f0661a2ca4c16d8876be54f9258bc0a3), [1361fe](https://github.com/megapay-labs/megapay/commit/1361fea993bcc37b0495b8fff7c20a45ccbd8ca2))
|
* Update readme ([5fb450](https://github.com/telecart-labs/telecart/commit/5fb45000ac77de0019a256150089256d5c423d68), [540595](https://github.com/telecart-labs/telecart/commit/540595c9f0661a2ca4c16d8876be54f9258bc0a3), [1361fe](https://github.com/telecart-labs/telecart/commit/1361fea993bcc37b0495b8fff7c20a45ccbd8ca2))
|
||||||
* Update styles for swipe to back ([e6a9e6](https://github.com/megapay-labs/megapay/commit/e6a9e6797f518d27caba507ac79d07ac8c113b06))
|
* Update styles for swipe to back ([e6a9e6](https://github.com/telecart-labs/telecart/commit/e6a9e6797f518d27caba507ac79d07ac8c113b06))
|
||||||
* Use yaMetrika number in settings ([cedc49](https://github.com/megapay-labs/megapay/commit/cedc49f0d5c3107791c1e6ff87a2f024a8baf828))
|
* Use yaMetrika number in settings ([cedc49](https://github.com/telecart-labs/telecart/commit/cedc49f0d5c3107791c1e6ff87a2f024a8baf828))
|
||||||
* Visualize swipe back ([50bdb8](https://github.com/megapay-labs/megapay/commit/50bdb8601c04799a4ecdb1b854ee1151a02f00f1))
|
* Visualize swipe back ([50bdb8](https://github.com/telecart-labs/telecart/commit/50bdb8601c04799a4ecdb1b854ee1151a02f00f1))
|
||||||
* WIP add yandex metrika goals ([4e59c4](https://github.com/megapay-labs/megapay/commit/4e59c4e7888925a87ce63eb53587d5e21fec4561))
|
* WIP add yandex metrika goals ([4e59c4](https://github.com/telecart-labs/telecart/commit/4e59c4e7888925a87ce63eb53587d5e21fec4561))
|
||||||
* добавлена функциональность политики конфиденциальности и согласия на обработку ПД ([7a5eeb](https://github.com/megapay-labs/megapay/commit/7a5eebec91ee73a2d38509cfa4f9bbb87cb75225))
|
* добавлена функциональность политики конфиденциальности и согласия на обработку ПД ([7a5eeb](https://github.com/telecart-labs/telecart/commit/7a5eebec91ee73a2d38509cfa4f9bbb87cb75225))
|
||||||
* добавлен жест swipe back для навигации назад ([179729](https://github.com/megapay-labs/megapay/commit/17972993ca815072ad5ded2bbc7a29e97f1abc6f))
|
* добавлен жест swipe back для навигации назад ([179729](https://github.com/telecart-labs/telecart/commit/17972993ca815072ad5ded2bbc7a29e97f1abc6f))
|
||||||
|
|
||||||
##### Admin
|
##### Admin
|
||||||
|
|
||||||
* Add more details for admin errors ([17865d](https://github.com/megapay-labs/megapay/commit/17865d8af4ed4b7f1f02a5b065847281fa5ede5f))
|
* Add more details for admin errors ([17865d](https://github.com/telecart-labs/telecart/commit/17865d8af4ed4b7f1f02a5b065847281fa5ede5f))
|
||||||
* Refactor logs viewer with table display and detailed dialog ([b39a34](https://github.com/megapay-labs/megapay/commit/b39a344a7dac32225d6fe939ea81fcc67f4b5750))
|
* Refactor logs viewer with table display and detailed dialog ([b39a34](https://github.com/telecart-labs/telecart/commit/b39a344a7dac32225d6fe939ea81fcc67f4b5750))
|
||||||
* Remove legacy setting keys that not defined in defaults ([107741](https://github.com/megapay-labs/megapay/commit/1077417d717cbd601bdff82ab3dfbb61402c3640))
|
* Remove legacy setting keys that not defined in defaults ([107741](https://github.com/telecart-labs/telecart/commit/1077417d717cbd601bdff82ab3dfbb61402c3640))
|
||||||
|
|
||||||
##### Banner
|
##### Banner
|
||||||
|
|
||||||
* Add banner feature ([05e7ca](https://github.com/megapay-labs/megapay/commit/05e7cafd0f36b204e0dea51a0f46f1a2c795dceb))
|
* Add banner feature ([05e7ca](https://github.com/telecart-labs/telecart/commit/05e7cafd0f36b204e0dea51a0f46f1a2c795dceb))
|
||||||
|
|
||||||
##### Customers
|
##### Customers
|
||||||
|
|
||||||
* Track order meta and OC sync ([952d8e](https://github.com/megapay-labs/megapay/commit/952d8e58da2972ff834d7f6609749b6dbd15a938))
|
* Track order meta and OC sync ([952d8e](https://github.com/telecart-labs/telecart/commit/952d8e58da2972ff834d7f6609749b6dbd15a938))
|
||||||
|
|
||||||
##### Products-feed
|
##### Products-feed
|
||||||
|
|
||||||
* Replace fixed image dimensions with aspect ratio selection ([cd0606](https://github.com/megapay-labs/megapay/commit/cd060610fe991c7c6d0db81a24bfa2b062192d20))
|
* Replace fixed image dimensions with aspect ratio selection ([cd0606](https://github.com/telecart-labs/telecart/commit/cd060610fe991c7c6d0db81a24bfa2b062192d20))
|
||||||
|
|
||||||
##### Pulse
|
##### Pulse
|
||||||
|
|
||||||
* Implement reliable event tracking and delivery system ([4a3dcc](https://github.com/megapay-labs/megapay/commit/4a3dcc11d161420c58494d744909f48982bd2582))
|
* Implement reliable event tracking and delivery system ([4a3dcc](https://github.com/telecart-labs/telecart/commit/4a3dcc11d161420c58494d744909f48982bd2582))
|
||||||
|
|
||||||
##### Search
|
##### Search
|
||||||
|
|
||||||
* Add keyboard hide button and auto-hide Dock ([db8d13](https://github.com/megapay-labs/megapay/commit/db8d1360fc9d8702fa7f2607337ac447ea646c5d))
|
* Add keyboard hide button and auto-hide Dock ([db8d13](https://github.com/telecart-labs/telecart/commit/db8d1360fc9d8702fa7f2607337ac447ea646c5d))
|
||||||
* Improve search UI with sticky bar and keyboard handling ([64ead2](https://github.com/megapay-labs/megapay/commit/64ead29583086dc55ae59e5d2b775dae31f36944))
|
* Improve search UI with sticky bar and keyboard handling ([64ead2](https://github.com/telecart-labs/telecart/commit/64ead29583086dc55ae59e5d2b775dae31f36944))
|
||||||
|
|
||||||
##### Slider
|
##### Slider
|
||||||
|
|
||||||
* Add slider feature ([3049bd](https://github.com/megapay-labs/megapay/commit/3049bd3101a44259f2883b351244c6eb5564cf89))
|
* Add slider feature ([3049bd](https://github.com/telecart-labs/telecart/commit/3049bd3101a44259f2883b351244c6eb5564cf89))
|
||||||
|
|
||||||
##### Spa
|
##### Spa
|
||||||
|
|
||||||
* Add custom dock ([4936e6](https://github.com/megapay-labs/megapay/commit/4936e6f16c0cd44299d086911a347cd3626fa2af))
|
* Add custom dock ([4936e6](https://github.com/telecart-labs/telecart/commit/4936e6f16c0cd44299d086911a347cd3626fa2af))
|
||||||
* Add dock ([2e699e](https://github.com/megapay-labs/megapay/commit/2e699eb0d6aca08d3f87030ea822c1fc79d3d477))
|
* Add dock ([2e699e](https://github.com/telecart-labs/telecart/commit/2e699eb0d6aca08d3f87030ea822c1fc79d3d477))
|
||||||
* Correct radius for floating panel, small ui fixes ([72ab84](https://github.com/megapay-labs/megapay/commit/72ab842a95f090b886787f551bee274fc2f6932c))
|
* Correct radius for floating panel, small ui fixes ([72ab84](https://github.com/telecart-labs/telecart/commit/72ab842a95f090b886787f551bee274fc2f6932c))
|
||||||
* Show navbar with app logo and app name ([c3c0d6](https://github.com/megapay-labs/megapay/commit/c3c0d6d2c179c83a1700d773c496ff7a44cce99c))
|
* Show navbar with app logo and app name ([c3c0d6](https://github.com/telecart-labs/telecart/commit/c3c0d6d2c179c83a1700d773c496ff7a44cce99c))
|
||||||
* UI changes ([ed8592](https://github.com/megapay-labs/megapay/commit/ed8592c19dabf4f26d6ed45e55a3c6f7398d667e))
|
* UI changes ([ed8592](https://github.com/telecart-labs/telecart/commit/ed8592c19dabf4f26d6ed45e55a3c6f7398d667e))
|
||||||
|
|
||||||
##### Megapay
|
##### Telecart
|
||||||
|
|
||||||
* Add vouchers and coupons (#9) ([ac24f0](https://github.com/megapay-labs/megapay/commit/ac24f0376bee13cc14db49a2904867ef173dcf95))
|
* Add vouchers and coupons (#9) ([ac24f0](https://github.com/telecart-labs/telecart/commit/ac24f0376bee13cc14db49a2904867ef173dcf95))
|
||||||
|
|
||||||
##### Texts
|
##### Texts
|
||||||
|
|
||||||
* Add options to redefine text for zero product prices ([1fbbb7](https://github.com/megapay-labs/megapay/commit/1fbbb7b6db13a9dac745c32d11f2e71ed79e854e))
|
* Add options to redefine text for zero product prices ([1fbbb7](https://github.com/telecart-labs/telecart/commit/1fbbb7b6db13a9dac745c32d11f2e71ed79e854e))
|
||||||
|
|
||||||
##### Ya metrika
|
##### Ya metrika
|
||||||
|
|
||||||
* WIP yandex metrika ([d7666f](https://github.com/megapay-labs/megapay/commit/d7666f94ba22fc1a808299e9a91ead14e6b58b25))
|
* WIP yandex metrika ([d7666f](https://github.com/telecart-labs/telecart/commit/d7666f94ba22fc1a808299e9a91ead14e6b58b25))
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* Admin mainpage builder drawer doesnot show ([ad54b1](https://github.com/megapay-labs/megapay/commit/ad54b14c6804fae8960a5e15dbceb0549d91c732))
|
* Admin mainpage builder drawer doesnot show ([ad54b1](https://github.com/telecart-labs/telecart/commit/ad54b14c6804fae8960a5e15dbceb0549d91c732))
|
||||||
* Base header color ([28d80d](https://github.com/megapay-labs/megapay/commit/28d80d0f19ee31fea5011c2b466edea1590ab71e))
|
* Base header color ([28d80d](https://github.com/telecart-labs/telecart/commit/28d80d0f19ee31fea5011c2b466edea1590ab71e))
|
||||||
* Browser check ([4cd49b](https://github.com/megapay-labs/megapay/commit/4cd49b17a6df65863dc9fd32efdd0bba4b4e44ff))
|
* Browser check ([4cd49b](https://github.com/telecart-labs/telecart/commit/4cd49b17a6df65863dc9fd32efdd0bba4b4e44ff))
|
||||||
* Center image on product view ([dc198c](https://github.com/megapay-labs/megapay/commit/dc198c63b7c4f66b92eeb55958983d8eaed0260f))
|
* Center image on product view ([dc198c](https://github.com/telecart-labs/telecart/commit/dc198c63b7c4f66b92eeb55958983d8eaed0260f))
|
||||||
* Correct cli.php path for phar ([57c840](https://github.com/megapay-labs/megapay/commit/57c8400904b74569c843cd898fe6c39552f91e6b))
|
* Correct cli.php path for phar ([57c840](https://github.com/telecart-labs/telecart/commit/57c8400904b74569c843cd898fe6c39552f91e6b))
|
||||||
* Correct counter id for yandex metrika test ([9870f2](https://github.com/megapay-labs/megapay/commit/9870f2f36364ec7d968b3aec14091aefae774199))
|
* Correct counter id for yandex metrika test ([9870f2](https://github.com/telecart-labs/telecart/commit/9870f2f36364ec7d968b3aec14091aefae774199))
|
||||||
* Correct crontab line ([613ce5](https://github.com/megapay-labs/megapay/commit/613ce520ee53be47ee06e101daf54c8f5136184b))
|
* Correct crontab line ([613ce5](https://github.com/telecart-labs/telecart/commit/613ce520ee53be47ee06e101daf54c8f5136184b))
|
||||||
* Correct path for cron ([185f30](https://github.com/megapay-labs/megapay/commit/185f3096e1e17507f4191104794991448d4d44bb))
|
* Correct path for cron ([185f30](https://github.com/telecart-labs/telecart/commit/185f3096e1e17507f4191104794991448d4d44bb))
|
||||||
* Correct url for hit ([515b82](https://github.com/megapay-labs/megapay/commit/515b82302ba603f61324ace576e23adbf82560fd))
|
* Correct url for hit ([515b82](https://github.com/telecart-labs/telecart/commit/515b82302ba603f61324ace576e23adbf82560fd))
|
||||||
* Disable fullscreen for desktop ([bf32d9](https://github.com/megapay-labs/megapay/commit/bf32d9081169206cef62d92f27338321d1cc1e69))
|
* Disable fullscreen for desktop ([bf32d9](https://github.com/telecart-labs/telecart/commit/bf32d9081169206cef62d92f27338321d1cc1e69))
|
||||||
* Fix dock layout ([bdbdfc](https://github.com/megapay-labs/megapay/commit/bdbdfc3650ff24e77f7f35059ac72e87cd02ddf2))
|
* Fix dock layout ([bdbdfc](https://github.com/telecart-labs/telecart/commit/bdbdfc3650ff24e77f7f35059ac72e87cd02ddf2))
|
||||||
* Fix errors and small improvements ([3b2e2c](https://github.com/megapay-labs/megapay/commit/3b2e2cb656bb8db6feebdbb23612202f96cdde3f))
|
* Fix errors and small improvements ([3b2e2c](https://github.com/telecart-labs/telecart/commit/3b2e2cb656bb8db6feebdbb23612202f96cdde3f))
|
||||||
* Fix search issues ([2f9a55](https://github.com/megapay-labs/megapay/commit/2f9a553ae356fe4fb7ee3a481010d14be7d94ad7))
|
* Fix search issues ([2f9a55](https://github.com/telecart-labs/telecart/commit/2f9a553ae356fe4fb7ee3a481010d14be7d94ad7))
|
||||||
* Fix type error ([836161](https://github.com/megapay-labs/megapay/commit/8361616dd647397777849fd87267134e0bc1fb9b))
|
* Fix type error ([836161](https://github.com/telecart-labs/telecart/commit/8361616dd647397777849fd87267134e0bc1fb9b))
|
||||||
* Glob not work with phar ([24db69](https://github.com/megapay-labs/megapay/commit/24db69fbbad6758d11dabdac54f075611cde9593))
|
* Glob not work with phar ([24db69](https://github.com/telecart-labs/telecart/commit/24db69fbbad6758d11dabdac54f075611cde9593))
|
||||||
* Grant +x permissions for cli.php ([0ee3b7](https://github.com/megapay-labs/megapay/commit/0ee3b7d091da970d19a755207c73c28689bfd2a4))
|
* Grant +x permissions for cli.php ([0ee3b7](https://github.com/telecart-labs/telecart/commit/0ee3b7d091da970d19a755207c73c28689bfd2a4))
|
||||||
* Handle missing tags in workflow ([bc50cf](https://github.com/megapay-labs/megapay/commit/bc50cf064854ad0597f1d7a39b0eb32d88d2598a))
|
* Handle missing tags in workflow ([bc50cf](https://github.com/telecart-labs/telecart/commit/bc50cf064854ad0597f1d7a39b0eb32d88d2598a))
|
||||||
* Image picker component name type ([30b010](https://github.com/megapay-labs/megapay/commit/30b0108fe78b2a594db0c749f563577921c189d0))
|
* Image picker component name type ([30b010](https://github.com/telecart-labs/telecart/commit/30b0108fe78b2a594db0c749f563577921c189d0))
|
||||||
* Many products in search ([a5e91d](https://github.com/megapay-labs/megapay/commit/a5e91dd488b1f13abf739797e75400ddf36ba7e1))
|
* Many products in search ([a5e91d](https://github.com/telecart-labs/telecart/commit/a5e91dd488b1f13abf739797e75400ddf36ba7e1))
|
||||||
* Order creation ([82ab81](https://github.com/megapay-labs/megapay/commit/82ab8134e19f2cc4066de5241e7ff29905d79b17))
|
* Order creation ([82ab81](https://github.com/telecart-labs/telecart/commit/82ab8134e19f2cc4066de5241e7ff29905d79b17))
|
||||||
* Pulse ingest ([95dd54](https://github.com/megapay-labs/megapay/commit/95dd545dc5718046cd421d70ad2d4ea137919852))
|
* Pulse ingest ([95dd54](https://github.com/telecart-labs/telecart/commit/95dd545dc5718046cd421d70ad2d4ea137919852))
|
||||||
* Scroll behaviour ([359395](https://github.com/megapay-labs/megapay/commit/359395b7e880d72dd34da504f6d9fe001d6f0aff))
|
* Scroll behaviour ([359395](https://github.com/telecart-labs/telecart/commit/359395b7e880d72dd34da504f6d9fe001d6f0aff))
|
||||||
* Search ([e5792a](https://github.com/megapay-labs/megapay/commit/e5792a059a0986b6d6c86df9dbcbda212bc0f548))
|
* Search ([e5792a](https://github.com/telecart-labs/telecart/commit/e5792a059a0986b6d6c86df9dbcbda212bc0f548))
|
||||||
* Settings numeric error ([44d2af](https://github.com/megapay-labs/megapay/commit/44d2af3b30a7133b550e56385c3096c0e8848df5))
|
* Settings numeric error ([44d2af](https://github.com/telecart-labs/telecart/commit/44d2af3b30a7133b550e56385c3096c0e8848df5))
|
||||||
* Store error ([ab5c2f](https://github.com/megapay-labs/megapay/commit/ab5c2f42b907d19f0c52c631cea02b981a199c39))
|
* Store error ([ab5c2f](https://github.com/telecart-labs/telecart/commit/ab5c2f42b907d19f0c52c631cea02b981a199c39))
|
||||||
* Switch between code and visual for custom forms ([0ab09a](https://github.com/megapay-labs/megapay/commit/0ab09aad10eb724cf0378bcc2f46001b5108fade))
|
* Switch between code and visual for custom forms ([0ab09a](https://github.com/telecart-labs/telecart/commit/0ab09aad10eb724cf0378bcc2f46001b5108fade))
|
||||||
* Test ([c4b192](https://github.com/megapay-labs/megapay/commit/c4b19286f36ad166a1092dda86e65d48e3390723))
|
* Test ([c4b192](https://github.com/telecart-labs/telecart/commit/c4b19286f36ad166a1092dda86e65d48e3390723))
|
||||||
* Use html for tg bot ([7e6502](https://github.com/megapay-labs/megapay/commit/7e6502b07e74e27e27326c9593f25c2c9c03418b))
|
* Use html for tg bot ([7e6502](https://github.com/telecart-labs/telecart/commit/7e6502b07e74e27e27326c9593f25c2c9c03418b))
|
||||||
|
|
||||||
##### Admin
|
##### Admin
|
||||||
|
|
||||||
* Fix error when chat_id is string ([8f6af0](https://github.com/megapay-labs/megapay/commit/8f6af04e732f853eb79504676dc5ae83ba151c93))
|
* Fix error when chat_id is string ([8f6af0](https://github.com/telecart-labs/telecart/commit/8f6af04e732f853eb79504676dc5ae83ba151c93))
|
||||||
|
|
||||||
##### Spa
|
##### Spa
|
||||||
|
|
||||||
* Remove html in price for some opencart custom themes ([3423dd](https://github.com/megapay-labs/megapay/commit/3423dd172748845ce5177ea6bf5894a6da977c37), [d6a436](https://github.com/megapay-labs/megapay/commit/d6a43605acaff1cf335dc044e2b297132d6eb2ce))
|
* Remove html in price for some opencart custom themes ([3423dd](https://github.com/telecart-labs/telecart/commit/3423dd172748845ce5177ea6bf5894a6da977c37), [d6a436](https://github.com/telecart-labs/telecart/commit/d6a43605acaff1cf335dc044e2b297132d6eb2ce))
|
||||||
|
|
||||||
##### Megapay
|
##### Telecart
|
||||||
|
|
||||||
* Fix products search ([98ee6d](https://github.com/megapay-labs/megapay/commit/98ee6d9ecac4349cad847bdba1b10cf8660c251f))
|
* Fix products search ([98ee6d](https://github.com/telecart-labs/telecart/commit/98ee6d9ecac4349cad847bdba1b10cf8660c251f))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.3.2](https://github.com/megapay-labs/megapay/compare/v1.3.1...v1.3.2) (2025-10-24)
|
## [1.3.2](https://github.com/telecart-labs/telecart/compare/v1.3.1...v1.3.2) (2025-10-24)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
|
||||||
##### Products
|
##### Products
|
||||||
|
|
||||||
* Encode html for title on products page ([78ca4f](https://github.com/megapay-labs/megapay/commit/78ca4fd309e2254771a01ade75197d46e149c5f3))
|
* Encode html for title on products page ([78ca4f](https://github.com/telecart-labs/telecart/commit/78ca4fd309e2254771a01ade75197d46e149c5f3))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.3.1](https://github.com/megapay-labs/megapay/compare/v1.3.0...v1.3.1) (2025-10-19)
|
## [1.3.1](https://github.com/telecart-labs/telecart/compare/v1.3.0...v1.3.1) (2025-10-19)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
|
||||||
##### App
|
##### App
|
||||||
|
|
||||||
* Fix unhandled exceptions ([aa4264](https://github.com/megapay-labs/megapay/commit/aa42643c34c1a7cb11aae2d3191ac63c0af3236a))
|
* Fix unhandled exceptions ([aa4264](https://github.com/telecart-labs/telecart/commit/aa42643c34c1a7cb11aae2d3191ac63c0af3236a))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.3.0](https://github.com/megapay-labs/megapay/compare/v1.2.0...v1.3.0) (2025-10-19)
|
## [1.3.0](https://github.com/telecart-labs/telecart/compare/v1.2.0...v1.3.0) (2025-10-19)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* Add filters to mainpage ([1e2a9b](https://github.com/megapay-labs/megapay/commit/1e2a9bc7051e14c65eb44b392dba11f766b95d33))
|
* Add filters to mainpage ([1e2a9b](https://github.com/telecart-labs/telecart/commit/1e2a9bc7051e14c65eb44b392dba11f766b95d33))
|
||||||
* Handle start command for megapay bot ([c936d7](https://github.com/megapay-labs/megapay/commit/c936d727b495b06f63d7f15949d540f2c9a2b9c0))
|
* Handle start command for telecart bot ([c936d7](https://github.com/telecart-labs/telecart/commit/c936d727b495b06f63d7f15949d540f2c9a2b9c0))
|
||||||
|
|
||||||
##### Admin
|
##### Admin
|
||||||
|
|
||||||
* Do not log assets cleanup message if nothing deleted ([00165b](https://github.com/megapay-labs/megapay/commit/00165b3b61841303a6eff0447ee134d860f4a8b9))
|
* Do not log assets cleanup message if nothing deleted ([00165b](https://github.com/telecart-labs/telecart/commit/00165b3b61841303a6eff0447ee134d860f4a8b9))
|
||||||
* Remove old assets ([01368b](https://github.com/megapay-labs/megapay/commit/01368bbfce831cd8949500ffdf5f5e4614316459))
|
* Remove old assets ([01368b](https://github.com/telecart-labs/telecart/commit/01368bbfce831cd8949500ffdf5f5e4614316459))
|
||||||
* Remove old maps ([31a990](https://github.com/megapay-labs/megapay/commit/31a9909cc37c953113c743d248c9dd4065f89acb))
|
* Remove old maps ([31a990](https://github.com/telecart-labs/telecart/commit/31a9909cc37c953113c743d248c9dd4065f89acb))
|
||||||
|
|
||||||
##### Bot
|
##### Bot
|
||||||
|
|
||||||
* Add bot commands ([023ace](https://github.com/megapay-labs/megapay/commit/023acee68fb8f247a5e84f62aade44c77cfc0ed5))
|
* Add bot commands ([023ace](https://github.com/telecart-labs/telecart/commit/023acee68fb8f247a5e84f62aade44c77cfc0ed5))
|
||||||
|
|
||||||
##### Filters
|
##### Filters
|
||||||
|
|
||||||
* Add filters for the main page ([e7e045](https://github.com/megapay-labs/megapay/commit/e7e045b695d227d2d895242b4c0f19883d07f69e))
|
* Add filters for the main page ([e7e045](https://github.com/telecart-labs/telecart/commit/e7e045b695d227d2d895242b4c0f19883d07f69e))
|
||||||
|
|
||||||
##### Spa
|
##### Spa
|
||||||
|
|
||||||
* Hide floating cart btn for filters page ([259154](https://github.com/megapay-labs/megapay/commit/259154e4f1ca2ac0e2e6a357e8a53be78a00441a))
|
* Hide floating cart btn for filters page ([259154](https://github.com/telecart-labs/telecart/commit/259154e4f1ca2ac0e2e6a357e8a53be78a00441a))
|
||||||
* Lock vertical orientation ([646721](https://github.com/megapay-labs/megapay/commit/6467216775c44a7f4cc924d4551cc88ca246b757))
|
* Lock vertical orientation ([646721](https://github.com/telecart-labs/telecart/commit/6467216775c44a7f4cc924d4551cc88ca246b757))
|
||||||
* Update Telegram Mini App to 59 version ([3ecb51](https://github.com/megapay-labs/megapay/commit/3ecb51b5cd1751f4e2ace73171225ee3a33e46c4))
|
* Update Telegram Mini App to 59 version ([3ecb51](https://github.com/telecart-labs/telecart/commit/3ecb51b5cd1751f4e2ace73171225ee3a33e46c4))
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* Escape character for start message command ([a051ff](https://github.com/megapay-labs/megapay/commit/a051ff545e920760e3a0e6c34ef3cc94a0c1bfdb))
|
* Escape character for start message command ([a051ff](https://github.com/telecart-labs/telecart/commit/a051ff545e920760e3a0e6c34ef3cc94a0c1bfdb))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.2.0](https://github.com/megapay-labs/megapay/compare/v1.1.0...v1.2.0) (2025-09-27)
|
## [1.2.0](https://github.com/telecart-labs/telecart/compare/v1.1.0...v1.2.0) (2025-09-27)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
|
||||||
##### Product
|
##### Product
|
||||||
|
|
||||||
* Add option to disable store feature ([d7dd05](https://github.com/megapay-labs/megapay/commit/d7dd055e245a5bb0772b382ca8542394e92fecd5))
|
* Add option to disable store feature ([d7dd05](https://github.com/telecart-labs/telecart/commit/d7dd055e245a5bb0772b382ca8542394e92fecd5))
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* Correct update opencart config after defaults diff update ([e24e7c](https://github.com/megapay-labs/megapay/commit/e24e7c6d106597c627451abf8014723f42fdda34))
|
* Correct update opencart config after defaults diff update ([e24e7c](https://github.com/telecart-labs/telecart/commit/e24e7c6d106597c627451abf8014723f42fdda34))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.1.0](https://github.com/megapay-labs/megapay/compare/v1.0.7...v1.1.0) (2025-09-26)
|
## [1.1.0](https://github.com/telecart-labs/telecart/compare/v1.0.7...v1.1.0) (2025-09-26)
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.0.7](https://github.com/megapay-labs/megapay/compare/v1.0.6...v1.0.7) (2025-09-26)
|
## [1.0.7](https://github.com/telecart-labs/telecart/compare/v1.0.6...v1.0.7) (2025-09-26)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
|
||||||
##### Categories
|
##### Categories
|
||||||
|
|
||||||
* Added animations for categories list ([b7b255](https://github.com/megapay-labs/megapay/commit/b7b255887db2d04b8ba70a966382f44c92475df0))
|
* Added animations for categories list ([b7b255](https://github.com/telecart-labs/telecart/commit/b7b255887db2d04b8ba70a966382f44c92475df0))
|
||||||
* Add skeleton for categories loading ([294e0c](https://github.com/megapay-labs/megapay/commit/294e0cd17e2038f3088b504331ad9b009129e8ed))
|
* Add skeleton for categories loading ([294e0c](https://github.com/telecart-labs/telecart/commit/294e0cd17e2038f3088b504331ad9b009129e8ed))
|
||||||
* Hide button from categories ([f06606](https://github.com/megapay-labs/megapay/commit/f066069a1b6cf186046e272bc7af61ab46f79c0e))
|
* Hide button from categories ([f06606](https://github.com/telecart-labs/telecart/commit/f066069a1b6cf186046e272bc7af61ab46f79c0e))
|
||||||
|
|
||||||
##### Design
|
##### Design
|
||||||
|
|
||||||
* Add safe top padding for product page ([a3e5b8](https://github.com/megapay-labs/megapay/commit/a3e5b8b07a28813115662b566284f8622f0b3722))
|
* Add safe top padding for product page ([a3e5b8](https://github.com/telecart-labs/telecart/commit/a3e5b8b07a28813115662b566284f8622f0b3722))
|
||||||
* Product link in cart ([39a350](https://github.com/megapay-labs/megapay/commit/39a350d517d5d762720236f0e9b682299fd2b746))
|
* Product link in cart ([39a350](https://github.com/telecart-labs/telecart/commit/39a350d517d5d762720236f0e9b682299fd2b746))
|
||||||
|
|
||||||
##### Products
|
##### Products
|
||||||
|
|
||||||
* Show correct product prices ([35dd0d](https://github.com/megapay-labs/megapay/commit/35dd0de261a4497c01cd6eb54ed0d7032cea5f8b))
|
* Show correct product prices ([35dd0d](https://github.com/telecart-labs/telecart/commit/35dd0de261a4497c01cd6eb54ed0d7032cea5f8b))
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
|
||||||
##### Product
|
##### Product
|
||||||
|
|
||||||
* Decode html entities for product and category names ([acbfae](https://github.com/megapay-labs/megapay/commit/acbfaebcf415f42c6fb16c6a39d5e10f0776da90))
|
* Decode html entities for product and category names ([acbfae](https://github.com/telecart-labs/telecart/commit/acbfaebcf415f42c6fb16c6a39d5e10f0776da90))
|
||||||
* Fix error when image not found ([a381b3](https://github.com/megapay-labs/megapay/commit/a381b3a6ee6972775815db382269ec8ab3d31a4f))
|
* Fix error when image not found ([a381b3](https://github.com/telecart-labs/telecart/commit/a381b3a6ee6972775815db382269ec8ab3d31a4f))
|
||||||
* Fix select product option UI ([22a783](https://github.com/megapay-labs/megapay/commit/22a783f0ef833f5797e798222dce65493d71b34b))
|
* Fix select product option UI ([22a783](https://github.com/telecart-labs/telecart/commit/22a783f0ef833f5797e798222dce65493d71b34b))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.0.6](https://github.com/megapay-labs/megapay/compare/v1.0.5...v1.0.6) (2025-09-24)
|
## [1.0.6](https://github.com/telecart-labs/telecart/compare/v1.0.5...v1.0.6) (2025-09-24)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* Fix possible foreign error message on megapay page ([016eeb](https://github.com/megapay-labs/megapay/commit/016eeb445db7ce692825d323bed7c1dd815e30af))
|
* Fix possible foreign error message on telecart page ([016eeb](https://github.com/telecart-labs/telecart/commit/016eeb445db7ce692825d323bed7c1dd815e30af))
|
||||||
|
|
||||||
##### Categories
|
##### Categories
|
||||||
|
|
||||||
* Fix nested lvl > 2 categories rendering ([0f04cb](https://github.com/megapay-labs/megapay/commit/0f04cbf105252b88358095ae5be33fedca6f1e63))
|
* Fix nested lvl > 2 categories rendering ([0f04cb](https://github.com/telecart-labs/telecart/commit/0f04cbf105252b88358095ae5be33fedca6f1e63))
|
||||||
* Increase max categories count to display up to 100 ([9f6416](https://github.com/megapay-labs/megapay/commit/9f6416a1b7b7f065b558ecd3089c42ef397bd817))
|
* Increase max categories count to display up to 100 ([9f6416](https://github.com/telecart-labs/telecart/commit/9f6416a1b7b7f065b558ecd3089c42ef397bd817))
|
||||||
|
|
||||||
##### Database
|
##### Database
|
||||||
|
|
||||||
* Fix db connection error when not standard mysql port ([ec5cdf](https://github.com/megapay-labs/megapay/commit/ec5cdfcaa9321cb824c858df91ce4464d6158a2c))
|
* Fix db connection error when not standard mysql port ([ec5cdf](https://github.com/telecart-labs/telecart/commit/ec5cdfcaa9321cb824c858df91ce4464d6158a2c))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.0.5](https://github.com/megapay-labs/megapay/compare/v1.0.4...v1.0.5) (2025-09-24)
|
## [1.0.5](https://github.com/telecart-labs/telecart/compare/v1.0.4...v1.0.5) (2025-09-24)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
|
|
||||||
##### Categories
|
##### Categories
|
||||||
|
|
||||||
* Add options to select what categories to show on front page ([9e4022](https://github.com/megapay-labs/megapay/commit/9e4022f64856082fffa7a0264949373319cdf9ff))
|
* Add options to select what categories to show on front page ([9e4022](https://github.com/telecart-labs/telecart/commit/9e4022f64856082fffa7a0264949373319cdf9ff))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.0.4](https://github.com/megapay-labs/megapay/compare/v1.0.3...v1.0.4) (2025-09-24)
|
## [1.0.4](https://github.com/telecart-labs/telecart/compare/v1.0.3...v1.0.4) (2025-09-24)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* Error when category doesnt have image ([490cbf](https://github.com/megapay-labs/megapay/commit/490cbfacf72095001dccaf374034292ea247e21b))
|
* Error when category doesnt have image ([490cbf](https://github.com/telecart-labs/telecart/commit/490cbfacf72095001dccaf374034292ea247e21b))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.0.3](https://github.com/megapay-labs/megapay/compare/v1.0.2...v1.0.3) (2025-09-24)
|
## [1.0.3](https://github.com/telecart-labs/telecart/compare/v1.0.2...v1.0.3) (2025-09-24)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* Init exception for some opencart versions ([0cf0c4](https://github.com/megapay-labs/megapay/commit/0cf0c438433f8c1895bef5f490bc0f9af86b0c04))
|
* Init exception for some opencart versions ([0cf0c4](https://github.com/telecart-labs/telecart/commit/0cf0c438433f8c1895bef5f490bc0f9af86b0c04))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.0.2](https://github.com/megapay-labs/megapay/compare/v1.0.1...v1.0.2) (2025-08-16)
|
## [1.0.2](https://github.com/telecart-labs/telecart/compare/v1.0.1...v1.0.2) (2025-08-16)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* UI fixes ([854dfd](https://github.com/megapay-labs/megapay/commit/854dfdf7f2dba7bc78b53c19f345c1909298c474))
|
* UI fixes ([854dfd](https://github.com/telecart-labs/telecart/commit/854dfdf7f2dba7bc78b53c19f345c1909298c474))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.0.1](https://github.com/megapay-labs/megapay/compare/v1.0.0...v1.0.1) (2025-08-16)
|
## [1.0.1](https://github.com/telecart-labs/telecart/compare/v1.0.0...v1.0.1) (2025-08-16)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* Check code phrase when configure chat_id ([a0abc1](https://github.com/megapay-labs/megapay/commit/a0abc14c6db91fb6cec14f8aa64297d671e88a7e))
|
* Check code phrase when configure chat_id ([a0abc1](https://github.com/telecart-labs/telecart/commit/a0abc14c6db91fb6cec14f8aa64297d671e88a7e))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [1.0.0](https://github.com/megapay-labs/megapay/compare/v0.0.2...v1.0.0) (2025-08-16)
|
## [1.0.0](https://github.com/telecart-labs/telecart/compare/v0.0.2...v1.0.0) (2025-08-16)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* Add bot_token validation ([d7df5a](https://github.com/megapay-labs/megapay/commit/d7df5a4b5c8abdf5117c07a9bb7fc7744c23eb1d))
|
* Add bot_token validation ([d7df5a](https://github.com/telecart-labs/telecart/commit/d7df5a4b5c8abdf5117c07a9bb7fc7744c23eb1d))
|
||||||
* Add carousel for images ([a40089](https://github.com/megapay-labs/megapay/commit/a40089ef553eaf30d813a9e2b2495fe3aa7dd0d4))
|
* Add carousel for images ([a40089](https://github.com/telecart-labs/telecart/commit/a40089ef553eaf30d813a9e2b2495fe3aa7dd0d4))
|
||||||
* Add Categories ([6a8ea0](https://github.com/megapay-labs/megapay/commit/6a8ea048ea52e6bd3c146b4ec311e9633fce269a))
|
* Add Categories ([6a8ea0](https://github.com/telecart-labs/telecart/commit/6a8ea048ea52e6bd3c146b4ec311e9633fce269a))
|
||||||
* Add custom BottomButton instead of TG ([b0cc02](https://github.com/megapay-labs/megapay/commit/b0cc0237af12ea5560835092bb808e4bc742c380))
|
* Add custom BottomButton instead of TG ([b0cc02](https://github.com/telecart-labs/telecart/commit/b0cc0237af12ea5560835092bb808e4bc742c380))
|
||||||
* Add fullscreen viewer ([4ae8d5](https://github.com/megapay-labs/megapay/commit/4ae8d593280774527fbeda3e52d924bd23a12813))
|
* Add fullscreen viewer ([4ae8d5](https://github.com/telecart-labs/telecart/commit/4ae8d593280774527fbeda3e52d924bd23a12813))
|
||||||
* Add fulscreen mode, dark mode ([252854](https://github.com/megapay-labs/megapay/commit/252854e67ea93716c271e2e20d25b0d73e24e380))
|
* Add fulscreen mode, dark mode ([252854](https://github.com/telecart-labs/telecart/commit/252854e67ea93716c271e2e20d25b0d73e24e380))
|
||||||
* Add haptictouch to bottom buttons ([51ce6e](https://github.com/megapay-labs/megapay/commit/51ce6ed959e9b673a0cfd9fac614f743b24d582f))
|
* Add haptictouch to bottom buttons ([51ce6e](https://github.com/telecart-labs/telecart/commit/51ce6ed959e9b673a0cfd9fac614f743b24d582f))
|
||||||
* Add hero block ([3c819e](https://github.com/megapay-labs/megapay/commit/3c819e6c6cf9d25088c2a8024da13e8e0180bde7))
|
* Add hero block ([3c819e](https://github.com/telecart-labs/telecart/commit/3c819e6c6cf9d25088c2a8024da13e8e0180bde7))
|
||||||
* Add manufacturer to product view ([b25f6d](https://github.com/megapay-labs/megapay/commit/b25f6d3c7335c42487702aa7fff2c5003fd63046))
|
* Add manufacturer to product view ([b25f6d](https://github.com/telecart-labs/telecart/commit/b25f6d3c7335c42487702aa7fff2c5003fd63046))
|
||||||
* Add new mainpage products options, hide attributes ([d9fd26](https://github.com/megapay-labs/megapay/commit/d9fd26d3541e02d4656d32af547a3e338bbbc4ff))
|
* Add new mainpage products options, hide attributes ([d9fd26](https://github.com/telecart-labs/telecart/commit/d9fd26d3541e02d4656d32af547a3e338bbbc4ff))
|
||||||
* Add preloader for product page ([b66a02](https://github.com/megapay-labs/megapay/commit/b66a02fd57a2f0233b37bb76b30a360e71333256))
|
* Add preloader for product page ([b66a02](https://github.com/telecart-labs/telecart/commit/b66a02fd57a2f0233b37bb76b30a360e71333256))
|
||||||
* Add product view page ([f13e12](https://github.com/megapay-labs/megapay/commit/f13e128d03831598ecd058217a0e8874f0831f75))
|
* Add product view page ([f13e12](https://github.com/telecart-labs/telecart/commit/f13e128d03831598ecd058217a0e8874f0831f75))
|
||||||
* Add telegram api ([b958fe](https://github.com/megapay-labs/megapay/commit/b958feaec751b2e3a4134f925a74c75d5d2d1b42))
|
* Add telegram api ([b958fe](https://github.com/telecart-labs/telecart/commit/b958feaec751b2e3a4134f925a74c75d5d2d1b42))
|
||||||
* Add telegram safe content area ([1715c0](https://github.com/megapay-labs/megapay/commit/1715c01b1d1b99d4e99a8fe6f40107a384250326))
|
* Add telegram safe content area ([1715c0](https://github.com/telecart-labs/telecart/commit/1715c01b1d1b99d4e99a8fe6f40107a384250326))
|
||||||
* Add validation and use opencart logger ([9f35ac](https://github.com/megapay-labs/megapay/commit/9f35acf39935416bfbb35735c3749baf0af20995))
|
* Add validation and use opencart logger ([9f35ac](https://github.com/telecart-labs/telecart/commit/9f35acf39935416bfbb35735c3749baf0af20995))
|
||||||
* Allow only vertical orientation ([fe4188](https://github.com/megapay-labs/megapay/commit/fe4188eb8b3d58cb5fa25c267e0e0ba46effbbac))
|
* Allow only vertical orientation ([fe4188](https://github.com/telecart-labs/telecart/commit/fe4188eb8b3d58cb5fa25c267e0e0ba46effbbac))
|
||||||
* Cache frontpage products and categories ([5f785e](https://github.com/megapay-labs/megapay/commit/5f785e82e6689283526dd5a218d76908078e7942))
|
* Cache frontpage products and categories ([5f785e](https://github.com/telecart-labs/telecart/commit/5f785e82e6689283526dd5a218d76908078e7942))
|
||||||
* Create new order ([c057f4](https://github.com/megapay-labs/megapay/commit/c057f4be76544466af62556237f7031c874f5f51))
|
* Create new order ([c057f4](https://github.com/telecart-labs/telecart/commit/c057f4be76544466af62556237f7031c874f5f51))
|
||||||
* Deny direct access to the spa ([41e74b](https://github.com/megapay-labs/megapay/commit/41e74bad121d76b9a4be2a2f02822d8323e739cc))
|
* Deny direct access to the spa ([41e74b](https://github.com/telecart-labs/telecart/commit/41e74bad121d76b9a4be2a2f02822d8323e739cc))
|
||||||
* Diplicate webhook info request ([6249b2](https://github.com/megapay-labs/megapay/commit/6249b218a137e105e64fbfb0b6c8829e2ca01349))
|
* Diplicate webhook info request ([6249b2](https://github.com/telecart-labs/telecart/commit/6249b218a137e105e64fbfb0b6c8829e2ca01349))
|
||||||
* Display product options ([f47bb4](https://github.com/megapay-labs/megapay/commit/f47bb46751fea79e43a96e2b63afde4cb7ef801b))
|
* Display product options ([f47bb4](https://github.com/telecart-labs/telecart/commit/f47bb46751fea79e43a96e2b63afde4cb7ef801b))
|
||||||
* Do not check signature if bot token not set ([1d892f](https://github.com/megapay-labs/megapay/commit/1d892f7d090a1ff91f724871e688b18a40df768e))
|
* Do not check signature if bot token not set ([1d892f](https://github.com/telecart-labs/telecart/commit/1d892f7d090a1ff91f724871e688b18a40df768e))
|
||||||
* Encode images to webp for telegram mini app ([c282b6](https://github.com/megapay-labs/megapay/commit/c282b6ea3b5c04ae92708eb1984ad14d2ea46cfa))
|
* Encode images to webp for telegram mini app ([c282b6](https://github.com/telecart-labs/telecart/commit/c282b6ea3b5c04ae92708eb1984ad14d2ea46cfa))
|
||||||
* Expand mini app on mounted ([1e454b](https://github.com/megapay-labs/megapay/commit/1e454b8f2387d9a4e2e4316253d7f8bddadccc1c))
|
* Expand mini app on mounted ([1e454b](https://github.com/telecart-labs/telecart/commit/1e454b8f2387d9a4e2e4316253d7f8bddadccc1c))
|
||||||
* Fix module name in admin ([9770a0](https://github.com/megapay-labs/megapay/commit/9770a09fc0abe57d7b97137c9fef4bfaf5687278))
|
* Fix module name in admin ([9770a0](https://github.com/telecart-labs/telecart/commit/9770a09fc0abe57d7b97137c9fef4bfaf5687278))
|
||||||
* Infinity scroll, load more, resore scroll ([bb2ee3](https://github.com/megapay-labs/megapay/commit/bb2ee38118e8626f8d85070047e256ad8305c1e5))
|
* Infinity scroll, load more, resore scroll ([bb2ee3](https://github.com/telecart-labs/telecart/commit/bb2ee38118e8626f8d85070047e256ad8305c1e5))
|
||||||
* Make two columns grid for product list ([34bd64](https://github.com/megapay-labs/megapay/commit/34bd64e9025fbd61cd3c64c1e9a9bebb4bf98e5d))
|
* Make two columns grid for product list ([34bd64](https://github.com/telecart-labs/telecart/commit/34bd64e9025fbd61cd3c64c1e9a9bebb4bf98e5d))
|
||||||
* Product options, speedup home page, themes ([e3cc0d](https://github.com/megapay-labs/megapay/commit/e3cc0d4b10edf3a7c655a8e6d9a39ca587d6ecbc))
|
* Product options, speedup home page, themes ([e3cc0d](https://github.com/telecart-labs/telecart/commit/e3cc0d4b10edf3a7c655a8e6d9a39ca587d6ecbc))
|
||||||
* Remove cache, refactor ([7404ec](https://github.com/megapay-labs/megapay/commit/7404ecb33e1289439a3b4b9b5926175fe5d3872d))
|
* Remove cache, refactor ([7404ec](https://github.com/telecart-labs/telecart/commit/7404ecb33e1289439a3b4b9b5926175fe5d3872d))
|
||||||
* Remove prefilled fields in checkout ([33b350](https://github.com/megapay-labs/megapay/commit/33b3500aa470438963af90ee2edccdff9a27233d))
|
* Remove prefilled fields in checkout ([33b350](https://github.com/telecart-labs/telecart/commit/33b3500aa470438963af90ee2edccdff9a27233d))
|
||||||
* Safe-top and search ([a8bb5e](https://github.com/megapay-labs/megapay/commit/a8bb5eb493ab329bebca8c7903d4facf4a22d76a))
|
* Safe-top and search ([a8bb5e](https://github.com/telecart-labs/telecart/commit/a8bb5eb493ab329bebca8c7903d4facf4a22d76a))
|
||||||
* Search component and loading splashscreen ([2fb841](https://github.com/megapay-labs/megapay/commit/2fb841ef08027eeabdade90d9a4725ea602b3f48))
|
* Search component and loading splashscreen ([2fb841](https://github.com/telecart-labs/telecart/commit/2fb841ef08027eeabdade90d9a4725ea602b3f48))
|
||||||
* Show tg app link ([b1ea16](https://github.com/megapay-labs/megapay/commit/b1ea169e2f83cd3d3108d9d11d2b9bb8ee234211))
|
* Show tg app link ([b1ea16](https://github.com/telecart-labs/telecart/commit/b1ea169e2f83cd3d3108d9d11d2b9bb8ee234211))
|
||||||
* UI changes ([d522cb](https://github.com/megapay-labs/megapay/commit/d522cbef8389adb05cc6e70ed6665db37915233c))
|
* UI changes ([d522cb](https://github.com/telecart-labs/telecart/commit/d522cbef8389adb05cc6e70ed6665db37915233c))
|
||||||
* Ui improvements, show only active products, limit max page for infinity scroll ([d499d7](https://github.com/megapay-labs/megapay/commit/d499d7d846d55cc158306160c51d4b871f5b6376))
|
* Ui improvements, show only active products, limit max page for infinity scroll ([d499d7](https://github.com/telecart-labs/telecart/commit/d499d7d846d55cc158306160c51d4b871f5b6376))
|
||||||
* Update styles ([ca3a59](https://github.com/megapay-labs/megapay/commit/ca3a59f43ae19f9c8417993e45c63f29696f46c8))
|
* Update styles ([ca3a59](https://github.com/telecart-labs/telecart/commit/ca3a59f43ae19f9c8417993e45c63f29696f46c8))
|
||||||
|
|
||||||
##### Admin
|
##### Admin
|
||||||
|
|
||||||
* Correct getting chat_id ([1e80fd](https://github.com/megapay-labs/megapay/commit/1e80fdb2ebaf47e39a6cbd45438860428146aac6))
|
* Correct getting chat_id ([1e80fd](https://github.com/telecart-labs/telecart/commit/1e80fdb2ebaf47e39a6cbd45438860428146aac6))
|
||||||
* Correct merge new default settings after initializing app ([469077](https://github.com/megapay-labs/megapay/commit/469077d0c9006f3bcfffcecf4454f2e5e4492fac))
|
* Correct merge new default settings after initializing app ([469077](https://github.com/telecart-labs/telecart/commit/469077d0c9006f3bcfffcecf4454f2e5e4492fac))
|
||||||
* Update disclaimer text ([133bad](https://github.com/megapay-labs/megapay/commit/133badf45b9727fbf2bee7c9b9f74ff274fa3cc8))
|
* Update disclaimer text ([133bad](https://github.com/telecart-labs/telecart/commit/133badf45b9727fbf2bee7c9b9f74ff274fa3cc8))
|
||||||
|
|
||||||
##### App
|
##### App
|
||||||
|
|
||||||
* Add maintenance mode ([2752ec](https://github.com/megapay-labs/megapay/commit/2752ec3dd18261af9894c8a28a6775bdb22301c3))
|
* Add maintenance mode ([2752ec](https://github.com/telecart-labs/telecart/commit/2752ec3dd18261af9894c8a28a6775bdb22301c3))
|
||||||
* Telegram init data signature validator ([350ec4](https://github.com/megapay-labs/megapay/commit/350ec4f64bf6534e57cf613e6b38d39a052fd646))
|
* Telegram init data signature validator ([350ec4](https://github.com/telecart-labs/telecart/commit/350ec4f64bf6534e57cf613e6b38d39a052fd646))
|
||||||
|
|
||||||
##### Order
|
##### Order
|
||||||
|
|
||||||
* Add success haptic for order created event ([858be6](https://github.com/megapay-labs/megapay/commit/858be67c89130ab291b34d8bd7fb4340b6fff422))
|
* Add success haptic for order created event ([858be6](https://github.com/telecart-labs/telecart/commit/858be67c89130ab291b34d8bd7fb4340b6fff422))
|
||||||
* Order default status and customer group ([14d42c](https://github.com/megapay-labs/megapay/commit/14d42c6ecb1967cc626c57ae7ccb60f66b361aec))
|
* Order default status and customer group ([14d42c](https://github.com/telecart-labs/telecart/commit/14d42c6ecb1967cc626c57ae7ccb60f66b361aec))
|
||||||
* Order process enchancements ([85101b](https://github.com/megapay-labs/megapay/commit/85101b988140c1d0114d3176115aab0864011b16))
|
* Order process enchancements ([85101b](https://github.com/telecart-labs/telecart/commit/85101b988140c1d0114d3176115aab0864011b16))
|
||||||
* WIP: telegram notifications ([454bd3](https://github.com/megapay-labs/megapay/commit/454bd39f1f12a6fa004f80c3b13ebc17032a35f9))
|
* WIP: telegram notifications ([454bd3](https://github.com/telecart-labs/telecart/commit/454bd39f1f12a6fa004f80c3b13ebc17032a35f9))
|
||||||
|
|
||||||
##### Orders
|
##### Orders
|
||||||
|
|
||||||
* Tg notifications, ya metrika, meta tags ([86d0fa](https://github.com/megapay-labs/megapay/commit/86d0fa95941fd2b1d491de8280817d0e80b461f2))
|
* Tg notifications, ya metrika, meta tags ([86d0fa](https://github.com/telecart-labs/telecart/commit/86d0fa95941fd2b1d491de8280817d0e80b461f2))
|
||||||
|
|
||||||
##### Product
|
##### Product
|
||||||
|
|
||||||
* Change router history driver, change add to cart behaviour ([ebc352](https://github.com/megapay-labs/megapay/commit/ebc352dcdfcf08694d2590ee94c9e799e795a2fc))
|
* Change router history driver, change add to cart behaviour ([ebc352](https://github.com/telecart-labs/telecart/commit/ebc352dcdfcf08694d2590ee94c9e799e795a2fc))
|
||||||
* Display attributes ([63adf9](https://github.com/megapay-labs/megapay/commit/63adf96908137ab0c173415f77278ee7483a2fb8))
|
* Display attributes ([63adf9](https://github.com/telecart-labs/telecart/commit/63adf96908137ab0c173415f77278ee7483a2fb8))
|
||||||
|
|
||||||
##### Shop
|
##### Shop
|
||||||
|
|
||||||
* Change grid image resize algorythm ([c3c256](https://github.com/megapay-labs/megapay/commit/c3c25619326e292575236979e389f8ddb68b6958))
|
* Change grid image resize algorythm ([c3c256](https://github.com/telecart-labs/telecart/commit/c3c25619326e292575236979e389f8ddb68b6958))
|
||||||
|
|
||||||
##### Style
|
##### Style
|
||||||
|
|
||||||
* Change pagination swiper styles ([50bf90](https://github.com/megapay-labs/megapay/commit/50bf9061be778b37f7f6869f4c39a4833af31b1d))
|
* Change pagination swiper styles ([50bf90](https://github.com/telecart-labs/telecart/commit/50bf9061be778b37f7f6869f4c39a4833af31b1d))
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* Add CORS headers, make ci builds as preleases ([551535](https://github.com/megapay-labs/megapay/commit/55153531fb4899d0f3e699b70231d32290800ee2))
|
* Add CORS headers, make ci builds as preleases ([551535](https://github.com/telecart-labs/telecart/commit/55153531fb4899d0f3e699b70231d32290800ee2))
|
||||||
* Add route names ([47bb2c](https://github.com/megapay-labs/megapay/commit/47bb2cae85e9a16b0076898cd6265512c3adfc3c))
|
* Add route names ([47bb2c](https://github.com/telecart-labs/telecart/commit/47bb2cae85e9a16b0076898cd6265512c3adfc3c))
|
||||||
* Change hardcoded axios url ([4bb983](https://github.com/megapay-labs/megapay/commit/4bb983e4af53baf2a7a5aa39f15b5389906a4c71))
|
* Change hardcoded axios url ([4bb983](https://github.com/telecart-labs/telecart/commit/4bb983e4af53baf2a7a5aa39f15b5389906a4c71))
|
||||||
* Correct back button work ([08af20](https://github.com/megapay-labs/megapay/commit/08af204d7403572dbc45f3a74e13cf5d3d560a42))
|
* Correct back button work ([08af20](https://github.com/telecart-labs/telecart/commit/08af204d7403572dbc45f3a74e13cf5d3d560a42))
|
||||||
* Correct controller class ([5af66d](https://github.com/megapay-labs/megapay/commit/5af66d228a3defbc6f0b4fd15a9e2a3c192bf41d))
|
* Correct controller class ([5af66d](https://github.com/telecart-labs/telecart/commit/5af66d228a3defbc6f0b4fd15a9e2a3c192bf41d))
|
||||||
* Corrent telegram mini app url in settings ([ea2a60](https://github.com/megapay-labs/megapay/commit/ea2a60b59b20d2bede9d6884349b09e55e345774))
|
* Corrent telegram mini app url in settings ([ea2a60](https://github.com/telecart-labs/telecart/commit/ea2a60b59b20d2bede9d6884349b09e55e345774))
|
||||||
* Exception if no images ([9bcf32](https://github.com/megapay-labs/megapay/commit/9bcf32841ebd4663b5c6bd5e855b18e8cd486e45))
|
* Exception if no images ([9bcf32](https://github.com/telecart-labs/telecart/commit/9bcf32841ebd4663b5c6bd5e855b18e8cd486e45))
|
||||||
* Fullscreen slide index ([4114c3](https://github.com/megapay-labs/megapay/commit/4114c3366e4090e41e29bf6e48fe5f54d0dd4a9c))
|
* Fullscreen slide index ([4114c3](https://github.com/telecart-labs/telecart/commit/4114c3366e4090e41e29bf6e48fe5f54d0dd4a9c))
|
||||||
* Glitch ([db24be](https://github.com/megapay-labs/megapay/commit/db24be6f92bbe485985892ea017f4e4ef457cd52))
|
* Glitch ([db24be](https://github.com/telecart-labs/telecart/commit/db24be6f92bbe485985892ea017f4e4ef457cd52))
|
||||||
* Icon error ([19911c](https://github.com/megapay-labs/megapay/commit/19911c8f871e456c51836c3d07add3f066744ace))
|
* Icon error ([19911c](https://github.com/telecart-labs/telecart/commit/19911c8f871e456c51836c3d07add3f066744ace))
|
||||||
* Infinity scroll, init data in base64 ([f2f161](https://github.com/megapay-labs/megapay/commit/f2f1618e0ee591bc58a830a333b1f759b0a860d6))
|
* Infinity scroll, init data in base64 ([f2f161](https://github.com/telecart-labs/telecart/commit/f2f1618e0ee591bc58a830a333b1f759b0a860d6))
|
||||||
* Night theme ([06a6dc](https://github.com/megapay-labs/megapay/commit/06a6dca656871a920092dc6767990ab70b9fc6c2))
|
* Night theme ([06a6dc](https://github.com/telecart-labs/telecart/commit/06a6dca656871a920092dc6767990ab70b9fc6c2))
|
||||||
* Router in opencart ([ad92db](https://github.com/megapay-labs/megapay/commit/ad92dbfad48f993e2393c0e235083614581ae0c6))
|
* Router in opencart ([ad92db](https://github.com/telecart-labs/telecart/commit/ad92dbfad48f993e2393c0e235083614581ae0c6))
|
||||||
* Router scroll scrollBehavior ([08d245](https://github.com/megapay-labs/megapay/commit/08d2453df92ffc89c5e6c4e264370d8b9c32a432))
|
* Router scroll scrollBehavior ([08d245](https://github.com/telecart-labs/telecart/commit/08d2453df92ffc89c5e6c4e264370d8b9c32a432))
|
||||||
* Totals ([eb1f1d](https://github.com/megapay-labs/megapay/commit/eb1f1dc9c1de7c4733d0117257f7902f145614b2))
|
* Totals ([eb1f1d](https://github.com/telecart-labs/telecart/commit/eb1f1dc9c1de7c4733d0117257f7902f145614b2))
|
||||||
* Watch router ([1ffb1c](https://github.com/megapay-labs/megapay/commit/1ffb1cef12df1bde4330a7c9531b6574a07d2fe6))
|
* Watch router ([1ffb1c](https://github.com/telecart-labs/telecart/commit/1ffb1cef12df1bde4330a7c9531b6574a07d2fe6))
|
||||||
|
|
||||||
##### Admin
|
##### Admin
|
||||||
|
|
||||||
* Fix shop url ([c61dfd](https://github.com/megapay-labs/megapay/commit/c61dfd824a532512703c207c464954b51dbcce5a))
|
* Fix shop url ([c61dfd](https://github.com/telecart-labs/telecart/commit/c61dfd824a532512703c207c464954b51dbcce5a))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [0.0.2](https://github.com/megapay-labs/megapay/compare/v0.0.1+a26c8ba...v0.0.2) (2025-07-10)
|
## [0.0.2](https://github.com/telecart-labs/telecart/compare/v0.0.1+a26c8ba...v0.0.2) (2025-07-10)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* Add CORS headers, make ci builds as preleases ([551535](https://github.com/megapay-labs/megapay/commit/55153531fb4899d0f3e699b70231d32290800ee2))
|
* Add CORS headers, make ci builds as preleases ([551535](https://github.com/telecart-labs/telecart/commit/55153531fb4899d0f3e699b70231d32290800ee2))
|
||||||
* Correct controller class ([5af66d](https://github.com/megapay-labs/megapay/commit/5af66d228a3defbc6f0b4fd15a9e2a3c192bf41d))
|
* Correct controller class ([5af66d](https://github.com/telecart-labs/telecart/commit/5af66d228a3defbc6f0b4fd15a9e2a3c192bf41d))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [0.0.1+a26c8ba](https://github.com/megapay-labs/megapay/compare/v0.0.1...v0.0.1+a26c8ba) (2025-07-10)
|
## [0.0.1+a26c8ba](https://github.com/telecart-labs/telecart/compare/v0.0.1...v0.0.1+a26c8ba) (2025-07-10)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* Add CORS headers, make ci builds as preleases ([551535](https://github.com/megapay-labs/megapay/commit/55153531fb4899d0f3e699b70231d32290800ee2))
|
* Add CORS headers, make ci builds as preleases ([551535](https://github.com/telecart-labs/telecart/commit/55153531fb4899d0f3e699b70231d32290800ee2))
|
||||||
* Correct controller class ([5af66d](https://github.com/megapay-labs/megapay/commit/5af66d228a3defbc6f0b4fd15a9e2a3c192bf41d))
|
* Correct controller class ([5af66d](https://github.com/telecart-labs/telecart/commit/5af66d228a3defbc6f0b4fd15a9e2a3c192bf41d))
|
||||||
* Move files to the correct folder ([9735d4](https://github.com/megapay-labs/megapay/commit/9735d48957b7d9947be5a1be18edba8aebc45531))
|
* Move files to the correct folder ([9735d4](https://github.com/telecart-labs/telecart/commit/9735d48957b7d9947be5a1be18edba8aebc45531))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [0.0.1](https://github.com/megapay-labs/megapay/compare/c3664025ba6b608920a0182799102a207980d7be...v0.0.1) (2025-07-10)
|
## [0.0.1](https://github.com/telecart-labs/telecart/compare/c3664025ba6b608920a0182799102a207980d7be...v0.0.1) (2025-07-10)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* WIP ([846fa6](https://github.com/megapay-labs/megapay/commit/846fa64fb4db9760c4264179098c43e7f53b557c))
|
* WIP ([846fa6](https://github.com/telecart-labs/telecart/commit/846fa64fb4db9760c4264179098c43e7f53b557c))
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
147
CLAUDE.md
Normal file
147
CLAUDE.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
TeleCart — коммерческий модуль для OpenCart, превращающий магазин в Telegram Mini App. Это монорепозиторий из трёх
|
||||||
|
частей:
|
||||||
|
|
||||||
|
- **PHP-модуль OpenCart** — `module/oc_telegram_shop/upload/` (вся бизнес-логика и собственный фреймворк).
|
||||||
|
- **SPA** — `frontend/spa/` (Telegram Mini App, витрина для покупателей).
|
||||||
|
- **Admin** — `frontend/admin/` (панель управления настройками модуля).
|
||||||
|
|
||||||
|
`src/` в корне — это распакованная установка OpenCart (генерируется скриптами, **не редактировать**; код модуля
|
||||||
|
«линкуется» в неё через `make link`).
|
||||||
|
|
||||||
|
## Документация по фичам (`docs/features/`)
|
||||||
|
|
||||||
|
Контекстная документация хранится в `docs/features/` — по одной поддиректории на фичу, внутри индексный
|
||||||
|
`CLAUDE.md`. Вложенные `CLAUDE.md` Claude Code подхватывает автоматически при чтении файлов из их директории,
|
||||||
|
поэтому индекс фичи читается «по требованию», когда работа касается этой фичи.
|
||||||
|
|
||||||
|
Перед тем как трогать незнакомую часть системы — загляни в её папку в `docs/features/`. Конвенции, шаблон новой
|
||||||
|
фичи и **реестр всех фич с прямыми путями** — в [`docs/features/CLAUDE.md`](docs/features/CLAUDE.md). Шаблон для
|
||||||
|
новой фичи — `docs/features/_TEMPLATE.md`. При изменении фичи поддерживай её доку в актуальном состоянии (код —
|
||||||
|
источник истины; при расхождении правь доку).
|
||||||
|
|
||||||
|
## Где живёт код модуля
|
||||||
|
|
||||||
|
Сердце проекта — `module/oc_telegram_shop/upload/oc_telegram_shop/` (далее «корень модуля»). Его структура:
|
||||||
|
|
||||||
|
| Каталог | Namespace | Назначение |
|
||||||
|
|------------------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `framework/` | `Openguru\OpenCartFramework\` | Собственный Laravel-подобный фреймворк: Container (DI), Router, QueryBuilder, Cache, Migrations, Telegram, Http, Translator |
|
||||||
|
| `src/` | `App\` | API витрины/SPA (catalog). Handlers, Services, Models, DTO, Filters, ServiceProviders |
|
||||||
|
| `bastion/` | `Bastion\` | API админ-панели. Handlers, Services, Tasks, ScheduledTasks |
|
||||||
|
| `console/` | `Console\` | CLI-команды (Symfony Console), точка входа `upload/cli.php` |
|
||||||
|
| `database/migrations/` | — | Миграции БД |
|
||||||
|
| `configs/` | — | `app.php`, `maintenance.php`, `schedule.php` |
|
||||||
|
| `tests/` | `Tests\` | `Unit/`, `Integration/`, `Telegram/` |
|
||||||
|
|
||||||
|
**Маршрутизация.** Запросы приходят через action-параметр и резолвятся по двум картам:
|
||||||
|
|
||||||
|
- `src/routes.php` — действия SPA (`products`, `getCart`, `checkout`, `webhook`, …)
|
||||||
|
- `bastion/routes.php` — действия админки (`getSettingsForm`, `getTelegramCustomers`, `getDashboardStats`, …)
|
||||||
|
|
||||||
|
Каждая запись — `'actionName' => [HandlerClass::class, 'method']`.
|
||||||
|
|
||||||
|
**Точки входа OpenCart** (тонкие обёртки, грузят autoload и поднимают `App\ApplicationFactory`):
|
||||||
|
|
||||||
|
- `module/.../upload/catalog/controller/extension/tgshop/handle.php` — SPA API
|
||||||
|
- `module/.../upload/admin/controller/extension/module/tgshop.php` — админка
|
||||||
|
|
||||||
|
В продакшене модуль собирается в `oc_telegram_shop.phar` и подключается из `system/library/oc_telegram_shop/`; в dev —
|
||||||
|
из исходников через `vendor/autoload.php`.
|
||||||
|
|
||||||
|
## Архитектурные правила (бэкенд)
|
||||||
|
|
||||||
|
Паттерн — MVC-L поверх собственного фреймворка. Соблюдай строго:
|
||||||
|
|
||||||
|
- **Совместимость с PHP 7.4.** Минимальные требования OpenCart и модуля — PHP 7.4 (`composer.json`: `^7.4`). Не используй синтаксис и функции PHP 8.0+ (named arguments, `match`, constructor property promotion, `enum`, `readonly`, nullsafe `?->`, union types и т.п.).
|
||||||
|
- **Dependency Injection через конструктор.** Никогда не создавай зависимости через `new` внутри классов — внедряй их.
|
||||||
|
Регистрация сервисов идёт в Service Providers (`src/ServiceProviders/`) через `$this->app->singleton(...)`.
|
||||||
|
- **Query Builder вместо сырого SQL.** Используй `$this->builder->newQuery()->from(...)->where(...)->get()`. Прямой
|
||||||
|
SQL — только в крайних случаях; никогда не конкатенируй значения в запрос (SQL injection).
|
||||||
|
- **Слои:** Handler (валидация + вызов сервиса + `JsonResponse`) → Service (бизнес-логика) → Model (доступ к данным). Не
|
||||||
|
смешивай.
|
||||||
|
- **Строгая типизация:** `declare(strict_types=1)`, типы аргументов и возвращаемых значений, nullable где нужно. PSR-12.
|
||||||
|
- **Безопасный доступ к массивам:** `Arr::get($data, 'key', $default)` вместо `$data['key']`.
|
||||||
|
- **Таблицы:** префикс OpenCart (`oc_`) + собственные таблицы модуля.
|
||||||
|
|
||||||
|
## Архитектура фронтенда
|
||||||
|
|
||||||
|
Оба приложения — Vue 3, **только `<script setup>` + Composition API**, Pinia (setup-stores), Vue Router.
|
||||||
|
|
||||||
|
- **SPA** (`frontend/spa/`): Tailwind CSS, FormKit, Swiper, `ofetch`. Сборка →
|
||||||
|
`module/.../upload/image/catalog/tgshopspa/`. Тесты — Vitest.
|
||||||
|
- **Admin** (`frontend/admin/`): PrimeVue (основной UI-кит), Tailwind/DaisyUI, axios, CodeMirror, `vuedraggable`.
|
||||||
|
Сборка → `module/.../upload/admin/view/javascript/telecart/`.
|
||||||
|
|
||||||
|
Конвенции: компоненты — PascalCase, файлы логики — camelCase; обработчики событий с префиксом `handle`; `computed` для
|
||||||
|
производных значений; `??` (не `||`) для дефолтов чисел/булевых; сложную логику выносить в composables. Ошибки API
|
||||||
|
ловить и показывать через toast (admin: `useToast` из PrimeVue).
|
||||||
|
|
||||||
|
**FormBuilder** (admin) — отдельная подсистема визуального конструктора форм. Единственный источник истины — FormKit
|
||||||
|
Schema JSON (без промежуточных DTO). Drag-and-drop строго на `vuedraggable` (не `@formkit/drag-and-drop`).
|
||||||
|
Tailwind-классы там идут с префиксом `tw:`.
|
||||||
|
|
||||||
|
## Команды
|
||||||
|
|
||||||
|
Все `make`-цели выполняются **внутри Docker-контейнера `web`** — нужен запущенный `docker compose`.
|
||||||
|
|
||||||
|
**Старт / окружение:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make setup # первичная установка: скачать OpenCart, установить, поднять, link
|
||||||
|
make start # поднять контейнеры
|
||||||
|
make dev # link + параллельно vite dev для SPA и Admin
|
||||||
|
make dev-spa # только SPA (vite)
|
||||||
|
make dev-admin # только Admin (vite)
|
||||||
|
make link # «слинковать» код модуля в установку OpenCart (после правок структуры)
|
||||||
|
make ssh # шелл в контейнер в каталоге модуля
|
||||||
|
```
|
||||||
|
|
||||||
|
**Тесты (PHP, PHPUnit в контейнере):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test # все тесты (--testdox)
|
||||||
|
make test-unit # tests/Unit
|
||||||
|
make test-integration # tests/Integration
|
||||||
|
make test-telegram # tests/Telegram
|
||||||
|
make test-coverage # HTML-покрытие в coverage/
|
||||||
|
```
|
||||||
|
|
||||||
|
Запуск одного теста — через ssh в контейнер:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make ssh
|
||||||
|
./vendor/bin/phpunit --filter testMethodName tests/Unit/SomeTest.php
|
||||||
|
```
|
||||||
|
|
||||||
|
**Тесты фронтенда (SPA):** `cd frontend/spa && npm run test` (Vitest), `npm run test:run` для однократного прогона.
|
||||||
|
|
||||||
|
**Линтеры / статический анализ:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make lint # PHPStan (level 1) по src, bastion, framework, console
|
||||||
|
make phpcs # PHP_CodeSniffer PSR-12 (проверка)
|
||||||
|
make phpcbf # PHP_CodeSniffer PSR-12 (автофикс)
|
||||||
|
```
|
||||||
|
|
||||||
|
Admin-фронтенд: `cd frontend/admin && npm run lint` (oxlint + eslint), `npm run format` (prettier).
|
||||||
|
|
||||||
|
**Сборка / релиз:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make phar # собрать oc_telegram_shop.phar (через docker/build.dockerfile)
|
||||||
|
make ocmod # применить OCMOD-модификации
|
||||||
|
make cli ARGS="..." # запустить console-команду модуля
|
||||||
|
make changelog # сгенерировать CHANGELOG из conventional commits
|
||||||
|
make release # changelog + commit релиза
|
||||||
|
```
|
||||||
|
|
||||||
|
## Коммиты
|
||||||
|
|
||||||
|
Используются **Conventional Commits** (`feat:`, `fix:`, `chore:`, `build(deps):` …) — CHANGELOG и версии генерируются
|
||||||
|
автоматически. **Не делать `git commit`/`git push` без явного указания пользователя.**
|
||||||
9
Makefile
9
Makefile
@@ -42,9 +42,12 @@ dev-spa:
|
|||||||
dev-admin:
|
dev-admin:
|
||||||
rm -rf module/oc_telegram_shop/upload/admin/view/javascript && \
|
rm -rf module/oc_telegram_shop/upload/admin/view/javascript && \
|
||||||
rm -rf module/oc_telegram_shop/upload/system/library/oc_telegram_shop && \
|
rm -rf module/oc_telegram_shop/upload/system/library/oc_telegram_shop && \
|
||||||
rm -rf src/upload/admin/view/javascript/megapay && \
|
rm -rf src/upload/admin/view/javascript/telecart && \
|
||||||
cd frontend/admin && npm run dev
|
cd frontend/admin && npm run dev
|
||||||
|
|
||||||
|
ocmod:
|
||||||
|
./scripts/apply_ocmod.sh
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
docker compose exec -w /module/oc_telegram_shop/upload/oc_telegram_shop web bash -c "./vendor/bin/phpstan analyse"
|
docker compose exec -w /module/oc_telegram_shop/upload/oc_telegram_shop web bash -c "./vendor/bin/phpstan analyse"
|
||||||
|
|
||||||
@@ -70,8 +73,8 @@ test-coverage:
|
|||||||
docker compose exec -w /module/oc_telegram_shop/upload/oc_telegram_shop web bash -c "./vendor/bin/phpunit --coverage-html coverage tests/"
|
docker compose exec -w /module/oc_telegram_shop/upload/oc_telegram_shop web bash -c "./vendor/bin/phpunit --coverage-html coverage tests/"
|
||||||
|
|
||||||
phar:
|
phar:
|
||||||
docker build -t megapay_local_build -f ./deployment/build.dockerfile . && \
|
docker build -t telecart_local_build -f ./docker/build.dockerfile . && \
|
||||||
docker run -v "./src/upload/system/library/oc_telegram_shop:/build" megapay_local_build sh -c 'sh /scripts/build_phar.sh'
|
docker run -v "./src/upload/system/library/oc_telegram_shop:/build" telecart_local_build sh -c 'sh /scripts/build_phar.sh'
|
||||||
|
|
||||||
cli:
|
cli:
|
||||||
docker compose exec -w /module/oc_telegram_shop/upload web bash -c "/usr/local/bin/php cli.php $(ARGS)"
|
docker compose exec -w /module/oc_telegram_shop/upload web bash -c "/usr/local/bin/php cli.php $(ARGS)"
|
||||||
|
|||||||
84
README.md
84
README.md
@@ -1 +1,83 @@
|
|||||||
# Demo code for interviewing
|
# TeleCart 2.0
|
||||||
|
|
||||||
|
[](https://github.com/telecart-labs/telecart/actions/workflows/main.yaml)
|
||||||
|
[](LICENSE)
|
||||||
|
[](https://php.net)
|
||||||
|
[](https://vuejs.org)
|
||||||
|
[](docker-compose.yaml)
|
||||||
|
[](https://www.opencart.com)
|
||||||
|
|
||||||
|
Telegram магазин на базе OpenCart. Позволяет создать полноценный интернет-магазин в виде Telegram Mini App.
|
||||||
|
|
||||||
|
## 🚀 Возможности
|
||||||
|
|
||||||
|
- 📱 Telegram Mini App интерфейс
|
||||||
|
- 🛒 Полноценная корзина и оформление заказов
|
||||||
|
- 🎨 Адаптивный дизайн с поддержкой темной/светлой темы
|
||||||
|
- 📊 Административная панель для управления настройками
|
||||||
|
- 🔍 Поиск и фильтрация товаров
|
||||||
|
- 📦 Интеграция с OpenCart
|
||||||
|
|
||||||
|
## 🛠 Технологии
|
||||||
|
|
||||||
|
- **Backend**: PHP (OpenCart), Composer
|
||||||
|
- **Frontend**: Vue.js 3, Vite, Tailwind CSS, Pinia
|
||||||
|
- **Инфраструктура**: Docker, Docker Compose
|
||||||
|
- **Тестирование**: PHPUnit, Vitest
|
||||||
|
|
||||||
|
## 📦 Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
telecart/
|
||||||
|
├── frontend/
|
||||||
|
│ ├── admin/ # Административная панель
|
||||||
|
│ └── spa/ # Telegram Mini App
|
||||||
|
├── module/ # Модуль OpenCart
|
||||||
|
├── docker/ # Docker конфигурации
|
||||||
|
└── src/ # Исходный код OpenCart
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
|
### Требования
|
||||||
|
|
||||||
|
- Docker и Docker Compose
|
||||||
|
- PHP >= 7.4
|
||||||
|
- Node.js >= 20.19.0 или >= 22.12.0
|
||||||
|
|
||||||
|
### Установка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Клонировать репозиторий
|
||||||
|
git clone git@github.com:telecart-labs/telecart.git
|
||||||
|
cd telecart
|
||||||
|
|
||||||
|
# Настроить проект
|
||||||
|
make setup
|
||||||
|
|
||||||
|
# Запустить в режиме разработки
|
||||||
|
make dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Доступные команды
|
||||||
|
|
||||||
|
- `make setup` - Первоначальная настройка проекта
|
||||||
|
- `make start` - Запустить контейнеры
|
||||||
|
- `make stop` - Остановить контейнеры
|
||||||
|
- `make dev` - Запустить в режиме разработки
|
||||||
|
- `make dev-admin` - Запустить админ-панель в режиме разработки
|
||||||
|
- `make test` - Запустить тесты
|
||||||
|
- `make lint` - Проверить код линтером
|
||||||
|
|
||||||
|
## 📝 Лицензия
|
||||||
|
|
||||||
|
Это коммерческий платный модуль. Все права защищены. Использование модуля возможно только после приобретения лицензии.
|
||||||
|
|
||||||
|
## 🤝 Вклад в проект
|
||||||
|
|
||||||
|
Мы приветствуем вклад в развитие проекта! Пожалуйста, создавайте issue и pull request'ы.
|
||||||
|
|
||||||
|
## 📄 Документация
|
||||||
|
|
||||||
|
Дополнительная документация находится в папке [`docs/`](docs/).
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
APP_DEBUG=true
|
|
||||||
PULSE_API_HOST=https://pulse.megapay.pro/api/
|
|
||||||
PULSE_HEARTBEAT_SECRET=c5261f5d-529e-45ad-a69c-9778b755b7cb
|
|
||||||
|
|
||||||
MEGAPAY_CACHE_DRIVER=redis
|
|
||||||
#MEGAPAY_REDIS_HOST=redis
|
|
||||||
#MEGAPAY_REDIS_PORT=6379
|
|
||||||
#MEGAPAY_REDIS_DATABASE=0
|
|
||||||
|
|
||||||
SENTRY_ENABLED=false
|
|
||||||
SENTRY_DSN=
|
|
||||||
SENTRY_ENABLE_LOGS=false
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
APP_DEBUG=false
|
|
||||||
PULSE_API_HOST=https://pulse.megapay.pro/api/
|
|
||||||
PULSE_HEARTBEAT_SECRET=c5261f5d-529e-45ad-a69c-9778b755b7cb
|
|
||||||
MEGAPAY_CACHE_DRIVER=mysql
|
|
||||||
MEGAPAY_REDIS_HOST=redis
|
|
||||||
MEGAPAY_REDIS_PORT=6379
|
|
||||||
MEGAPAY_REDIS_DATABASE=0
|
|
||||||
|
|
||||||
SENTRY_ENABLED=false
|
|
||||||
SENTRY_DSN=
|
|
||||||
SENTRY_ENABLE_LOGS=false
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Handlers;
|
|
||||||
|
|
||||||
use Openguru\OpenCartFramework\Config\Settings;
|
|
||||||
use Openguru\OpenCartFramework\Http\Request;
|
|
||||||
use Openguru\OpenCartFramework\Scheduler\SchedulerService;
|
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
|
||||||
|
|
||||||
class CronHandler
|
|
||||||
{
|
|
||||||
private Settings $settings;
|
|
||||||
private SchedulerService $schedulerService;
|
|
||||||
|
|
||||||
public function __construct(Settings $settings, SchedulerService $schedulerService)
|
|
||||||
{
|
|
||||||
$this->settings = $settings;
|
|
||||||
$this->schedulerService = $schedulerService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Запуск планировщика по HTTP (для cron-job.org и аналогов).
|
|
||||||
* Требует api_key в query, совпадающий с настройкой cron.api_key.
|
|
||||||
*/
|
|
||||||
public function runSchedule(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
$mode = $this->settings->get('cron.mode', 'disabled');
|
|
||||||
if ($mode !== 'cron_job_org') {
|
|
||||||
return new JsonResponse(['error' => 'Scheduler is not in cron-job.org mode'], Response::HTTP_FORBIDDEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
$apiKey = $request->get('api_key', '');
|
|
||||||
$expectedKey = $this->settings->get('cron.api_key', '');
|
|
||||||
if ($expectedKey === '' || $apiKey === '' || !hash_equals($expectedKey, $apiKey)) {
|
|
||||||
return new JsonResponse(['error' => 'Invalid or missing API key'], Response::HTTP_UNAUTHORIZED);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Увеличиваем лимит времени выполнения при запуске по HTTP, чтобы снизить риск timeout
|
|
||||||
$limit = 300; // 5 минут
|
|
||||||
if (function_exists('set_time_limit')) {
|
|
||||||
@set_time_limit($limit);
|
|
||||||
}
|
|
||||||
if (function_exists('ini_set')) {
|
|
||||||
@ini_set('max_execution_time', (string) $limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = $this->schedulerService->run(true);
|
|
||||||
$data = [
|
|
||||||
'success' => true,
|
|
||||||
'executed' => count($result->executed),
|
|
||||||
'failed' => count($result->failed),
|
|
||||||
'skipped' => count($result->skipped),
|
|
||||||
];
|
|
||||||
|
|
||||||
return new JsonResponse($data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Bastion\Services;
|
|
||||||
|
|
||||||
use Openguru\OpenCartFramework\Config\Settings;
|
|
||||||
|
|
||||||
class CronApiKeyRegenerator
|
|
||||||
{
|
|
||||||
private Settings $settings;
|
|
||||||
private SettingsService $settingsUpdateService;
|
|
||||||
|
|
||||||
public function __construct(Settings $settings, SettingsService $settingsUpdateService)
|
|
||||||
{
|
|
||||||
$this->settings = $settings;
|
|
||||||
$this->settingsUpdateService = $settingsUpdateService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Генерирует новый API-ключ для URL cron-job.org и сохраняет в настройки.
|
|
||||||
*
|
|
||||||
* @return string новый api_key
|
|
||||||
*/
|
|
||||||
public function regenerate(): string
|
|
||||||
{
|
|
||||||
$newApiKey = bin2hex(random_bytes(32));
|
|
||||||
$all = $this->settings->getAll();
|
|
||||||
if (! isset($all['cron'])) {
|
|
||||||
$all['cron'] = [];
|
|
||||||
}
|
|
||||||
$all['cron']['api_key'] = $newApiKey;
|
|
||||||
$this->settingsUpdateService->update($all);
|
|
||||||
|
|
||||||
return $newApiKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Openguru\OpenCartFramework\Migrations\Migration;
|
|
||||||
|
|
||||||
return new class extends Migration {
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$sql = <<<SQL
|
|
||||||
CREATE TABLE IF NOT EXISTS `megapay_scheduled_jobs` (
|
|
||||||
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
||||||
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
|
|
||||||
`task` varchar(1024) COLLATE utf8mb4_unicode_ci NOT NULL,
|
|
||||||
`is_enabled` tinyint(1) NOT NULL DEFAULT 1,
|
|
||||||
`cron_expression` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
|
|
||||||
`last_success_at` DATETIME DEFAULT NULL,
|
|
||||||
`last_duration_seconds` FLOAT UNSIGNED NOT NULL DEFAULT 0.0,
|
|
||||||
`failed_at` DATETIME DEFAULT NULL,
|
|
||||||
`failed_reason` varchar(1024) COLLATE utf8mb4_unicode_ci DEFAULT NULL
|
|
||||||
)
|
|
||||||
ENGINE=InnoDB
|
|
||||||
DEFAULT CHARSET=utf8mb4
|
|
||||||
COLLATE=utf8mb4_unicode_ci
|
|
||||||
SQL;
|
|
||||||
|
|
||||||
$this->database->statement($sql);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Bastion\ScheduledTasks\MegaPayPulseSendEventsTask;
|
|
||||||
use Openguru\OpenCartFramework\Migrations\Migration;
|
|
||||||
|
|
||||||
return new class extends Migration {
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$this->database->insert('megapay_scheduled_jobs', [
|
|
||||||
'name' => 'megapay_pulse_send_events',
|
|
||||||
'task' => MegaPayPulseSendEventsTask::class,
|
|
||||||
'is_enabled' => 0,
|
|
||||||
'cron_expression' => '*/10 * * * *',
|
|
||||||
'last_success_at' => null,
|
|
||||||
'last_duration_seconds' => 0,
|
|
||||||
'failed_at' => null,
|
|
||||||
'failed_reason' => null,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Bastion\Services\CronApiKeyRegenerator;
|
|
||||||
use Openguru\OpenCartFramework\Config\Settings;
|
|
||||||
use Openguru\OpenCartFramework\Migrations\Migration;
|
|
||||||
|
|
||||||
return new class extends Migration {
|
|
||||||
public function up(): void
|
|
||||||
{
|
|
||||||
$settings = $this->app->get(Settings::class);
|
|
||||||
$currentKey = $settings->get('cron.api_key', '');
|
|
||||||
|
|
||||||
if ($currentKey !== '') {
|
|
||||||
$this->logger->info('cron.api_key already set, migration skipped');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$regenerator = $this->app->get(CronApiKeyRegenerator::class);
|
|
||||||
$regenerator->regenerate();
|
|
||||||
$this->logger->info('cron.api_key initialized');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Openguru\OpenCartFramework\Scheduler\Models;
|
|
||||||
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Carbon\CarbonTimeZone;
|
|
||||||
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
|
||||||
|
|
||||||
class ScheduledJob
|
|
||||||
{
|
|
||||||
private const TABLE_NAME = 'megapay_scheduled_jobs';
|
|
||||||
|
|
||||||
private Builder $builder;
|
|
||||||
|
|
||||||
public function __construct(Builder $builder)
|
|
||||||
{
|
|
||||||
$this->builder = $builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function all(): array
|
|
||||||
{
|
|
||||||
return $this->builder->newQuery()
|
|
||||||
->from(self::TABLE_NAME)
|
|
||||||
->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getEnabledScheduledJobs(): array
|
|
||||||
{
|
|
||||||
return $this->builder->newQuery()
|
|
||||||
->from(self::TABLE_NAME)
|
|
||||||
->where('is_enabled', '=', 1)
|
|
||||||
->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function enable(int $id): bool
|
|
||||||
{
|
|
||||||
return $this->builder->newQuery()
|
|
||||||
->where('id', '=', $id)
|
|
||||||
->update(self::TABLE_NAME, [
|
|
||||||
'is_enabled' => 1,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function disable(int $id): bool
|
|
||||||
{
|
|
||||||
return $this->builder->newQuery()
|
|
||||||
->where('id', '=', $id)
|
|
||||||
->update(self::TABLE_NAME, [
|
|
||||||
'is_enabled' => 0,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateCronExpression(int $id, string $cronExpression): bool
|
|
||||||
{
|
|
||||||
return $this->builder->newQuery()
|
|
||||||
->where('id', '=', $id)
|
|
||||||
->update(self::TABLE_NAME, [
|
|
||||||
'cron_expression' => mb_substr($cronExpression, 0, 64),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateLastSuccessAt(int $id, float $durationSeconds): void
|
|
||||||
{
|
|
||||||
$this->builder->newQuery()
|
|
||||||
->where('id', '=', $id)
|
|
||||||
->update(self::TABLE_NAME, [
|
|
||||||
'last_success_at' => Carbon::now(),
|
|
||||||
'last_duration_seconds' => $durationSeconds,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateFailedAt(int $id, string $message): void
|
|
||||||
{
|
|
||||||
$this->builder->newQuery()
|
|
||||||
->where('id', '=', $id)
|
|
||||||
->update(self::TABLE_NAME, [
|
|
||||||
'failed_at' => Carbon::now(),
|
|
||||||
'failed_reason' => mb_substr($message, 0, 1000),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function clearFailedInfo(int $id): void
|
|
||||||
{
|
|
||||||
$this->builder->newQuery()
|
|
||||||
->where('id', '=', $id)
|
|
||||||
->update(self::TABLE_NAME, [
|
|
||||||
'failed_at' => null,
|
|
||||||
'failed_reason' => null,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Openguru\OpenCartFramework\Scheduler;
|
|
||||||
|
|
||||||
use Openguru\OpenCartFramework\Container\Container;
|
|
||||||
use Openguru\OpenCartFramework\Scheduler\Models\ScheduledJob;
|
|
||||||
|
|
||||||
class ScheduleJobRegistry
|
|
||||||
{
|
|
||||||
private Container $container;
|
|
||||||
|
|
||||||
/** @var Job[] */
|
|
||||||
private array $jobs = [];
|
|
||||||
private ScheduledJob $scheduledJob;
|
|
||||||
|
|
||||||
public function __construct(Container $container, ScheduledJob $scheduledJob)
|
|
||||||
{
|
|
||||||
$this->container = $container;
|
|
||||||
$this->scheduledJob = $scheduledJob;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string|callable|TaskInterface $job
|
|
||||||
* @param string|null $name
|
|
||||||
* @return Job
|
|
||||||
*/
|
|
||||||
public function add(int $id, $job, ?string $name = null): Job
|
|
||||||
{
|
|
||||||
$newJob = new Job($this->container, $id, $job, $name);
|
|
||||||
$this->jobs[] = $newJob;
|
|
||||||
|
|
||||||
return $newJob;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Job[]
|
|
||||||
*/
|
|
||||||
public function getJobs(): array
|
|
||||||
{
|
|
||||||
return $this->jobs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function loadJobsFromDatabase(): void
|
|
||||||
{
|
|
||||||
$jobs = $this->scheduledJob->getEnabledScheduledJobs();
|
|
||||||
|
|
||||||
foreach ($jobs as $job) {
|
|
||||||
$this->add($job['id'], $job['task'], $job['name'])
|
|
||||||
->at($job['cron_expression']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Openguru\OpenCartFramework\Scheduler;
|
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use Openguru\OpenCartFramework\Cache\CacheInterface;
|
|
||||||
use Openguru\OpenCartFramework\Config\Settings;
|
|
||||||
use Openguru\OpenCartFramework\Scheduler\Models\ScheduledJob;
|
|
||||||
use Psr\Log\LoggerInterface;
|
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
class SchedulerService
|
|
||||||
{
|
|
||||||
private LoggerInterface $logger;
|
|
||||||
private CacheInterface $cache;
|
|
||||||
private Settings $settings;
|
|
||||||
private ScheduleJobRegistry $registry;
|
|
||||||
|
|
||||||
private const GLOBAL_LOCK_KEY = 'scheduler.global_lock';
|
|
||||||
private const GLOBAL_LOCK_TTL = 300; // 5 minutes
|
|
||||||
private ScheduledJob $scheduledJob;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
LoggerInterface $logger,
|
|
||||||
CacheInterface $cache,
|
|
||||||
Settings $settings,
|
|
||||||
ScheduleJobRegistry $registry,
|
|
||||||
ScheduledJob $scheduledJob
|
|
||||||
) {
|
|
||||||
$this->logger = $logger;
|
|
||||||
$this->cache = $cache;
|
|
||||||
$this->settings = $settings;
|
|
||||||
$this->registry = $registry;
|
|
||||||
$this->scheduledJob = $scheduledJob;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For testing purposes
|
|
||||||
public function setRegistry(ScheduleJobRegistry $registry): void
|
|
||||||
{
|
|
||||||
$this->registry = $registry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function run(bool $ignoreGlobalLock = false): SchedulerResult
|
|
||||||
{
|
|
||||||
$result = new SchedulerResult();
|
|
||||||
|
|
||||||
$mode = $this->settings->get('cron.mode', 'disabled');
|
|
||||||
if ($mode === 'disabled') {
|
|
||||||
$result->addSkipped('Global', 'Scheduler is disabled');
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $ignoreGlobalLock && $this->isGlobalLocked()) {
|
|
||||||
$result->addSkipped('Global', 'Global scheduler lock active');
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $ignoreGlobalLock) {
|
|
||||||
$this->acquireGlobalLock();
|
|
||||||
}
|
|
||||||
$this->updateGlobalLastRun();
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->registry->loadJobsFromDatabase();
|
|
||||||
|
|
||||||
foreach ($this->registry->getJobs() as $job) {
|
|
||||||
$this->processJob($job, $result);
|
|
||||||
}
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
$this->logger->error('Scheduler run failed: ' . $e->getMessage(), ['exception' => $e]);
|
|
||||||
$result->addFailed('Scheduler', $e->getMessage());
|
|
||||||
} finally {
|
|
||||||
if (! $ignoreGlobalLock) {
|
|
||||||
$this->releaseGlobalLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function processJob(Job $job, SchedulerResult $result): void
|
|
||||||
{
|
|
||||||
$name = $job->getName();
|
|
||||||
$id = $job->getId();
|
|
||||||
|
|
||||||
// 1. Check if due by Cron expression
|
|
||||||
if (! $job->isDue()) {
|
|
||||||
$result->addSkipped($name, 'Not due');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Check Last Run (Prevent running multiple times in the same minute)
|
|
||||||
if ($this->hasRanRecently($id)) {
|
|
||||||
$result->addSkipped($name, 'Already ran recently');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Check Lock (Prevent parallel execution)
|
|
||||||
if ($this->isJobLocked($id)) {
|
|
||||||
$result->addSkipped($name, 'Job is locked (running)');
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->lockJob($id);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$this->scheduledJob->clearFailedInfo($id);
|
|
||||||
$startTime = microtime(true);
|
|
||||||
$job->run();
|
|
||||||
$duration = microtime(true) - $startTime;
|
|
||||||
$this->scheduledJob->updateLastSuccessAt($id, $duration);
|
|
||||||
$this->logger->debug("Job executed: {$name}", ['duration' => $duration]);
|
|
||||||
$result->addExecuted($name, $duration);
|
|
||||||
} catch (Throwable $e) {
|
|
||||||
$this->logger->error("Job failed: {$name}", ['exception' => $e]);
|
|
||||||
$this->scheduledJob->updateFailedAt($id, $e->getMessage());
|
|
||||||
$result->addFailed($name, $e->getMessage());
|
|
||||||
} finally {
|
|
||||||
$this->updateLastRun($id);
|
|
||||||
$this->unlockJob($id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function isGlobalLocked(): bool
|
|
||||||
{
|
|
||||||
return (bool) $this->cache->get(self::GLOBAL_LOCK_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function acquireGlobalLock(): void
|
|
||||||
{
|
|
||||||
$this->cache->set(self::GLOBAL_LOCK_KEY, 1, self::GLOBAL_LOCK_TTL);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function releaseGlobalLock(): void
|
|
||||||
{
|
|
||||||
$this->cache->delete(self::GLOBAL_LOCK_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function isJobLocked(int $id): bool
|
|
||||||
{
|
|
||||||
return (bool) $this->cache->get("scheduler.lock.{$id}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private function lockJob(int $id): void
|
|
||||||
{
|
|
||||||
// 30 minutes max execution time for a single job safe-guard
|
|
||||||
$this->cache->set("scheduler.lock.{$id}", 1, 1800);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function unlockJob(int $id): void
|
|
||||||
{
|
|
||||||
$this->cache->delete("scheduler.lock.{$id}");
|
|
||||||
}
|
|
||||||
|
|
||||||
private function hasRanRecently(int $id): bool
|
|
||||||
{
|
|
||||||
$lastRun = $this->getLastRun($id);
|
|
||||||
if (! $lastRun) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$lastRunDate = (new DateTime())->setTimestamp($lastRun);
|
|
||||||
$now = new DateTime();
|
|
||||||
|
|
||||||
return $lastRunDate->format('Y-m-d H:i') === $now->format('Y-m-d H:i');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function updateLastRun(int $id): void
|
|
||||||
{
|
|
||||||
$this->cache->set("scheduler.last_run.{$id}", time());
|
|
||||||
}
|
|
||||||
|
|
||||||
private function updateGlobalLastRun(): void
|
|
||||||
{
|
|
||||||
$this->cache->set("scheduler.global_last_run", time());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLastRun(int $id): ?int
|
|
||||||
{
|
|
||||||
return $this->cache->get("scheduler.last_run.{$id}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Openguru\OpenCartFramework\Scheduler;
|
|
||||||
|
|
||||||
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
|
||||||
|
|
||||||
class SchedulerServiceProvider extends ServiceProvider
|
|
||||||
{
|
|
||||||
public function register(): void
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Openguru\OpenCartFramework\Sentry;
|
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use Sentry\SentrySdk;
|
|
||||||
use Sentry\Tracing\Span;
|
|
||||||
use Sentry\Tracing\SpanContext;
|
|
||||||
use Sentry\Tracing\Transaction;
|
|
||||||
use Sentry\Tracing\TransactionContext;
|
|
||||||
use SplObjectStorage;
|
|
||||||
|
|
||||||
use function Sentry\init;
|
|
||||||
use function Sentry\startTransaction;
|
|
||||||
|
|
||||||
class SentryService
|
|
||||||
{
|
|
||||||
private static ?string $currentAction = null;
|
|
||||||
public static ?Transaction $currentTransaction = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores parent span for each created child span to correctly restore nesting.
|
|
||||||
*
|
|
||||||
* @var SplObjectStorage<Span, Span|null>|null
|
|
||||||
*/
|
|
||||||
private static ?SplObjectStorage $spanParents = null;
|
|
||||||
|
|
||||||
public static array $excludeActions = [
|
|
||||||
'health',
|
|
||||||
];
|
|
||||||
|
|
||||||
private static function resolveOptionsFromEnv(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'dsn' => env('SENTRY_DSN'),
|
|
||||||
'send_default_pii' => true,
|
|
||||||
'enable_logs' => (bool) filter_var(env('SENTRY_ENABLE_LOGS', false), FILTER_VALIDATE_BOOLEAN),
|
|
||||||
'traces_sample_rate' => (float) env('SENTRY_TRACES_SAMPLE_RATE', 1.0),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function isSentryEnabled(): bool
|
|
||||||
{
|
|
||||||
return self::$currentAction !== null
|
|
||||||
&& ! in_array(self::$currentAction, self::$excludeActions, true)
|
|
||||||
&& env('SENTRY_ENABLED', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function init(?string $action): void
|
|
||||||
{
|
|
||||||
$options = self::resolveOptionsFromEnv();
|
|
||||||
self::$currentAction = $action;
|
|
||||||
|
|
||||||
if (! self::isSentryEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
init($options);
|
|
||||||
|
|
||||||
$transactionContext = TransactionContext::make()
|
|
||||||
->setName(self::$currentAction)
|
|
||||||
->setOp('http.server');
|
|
||||||
|
|
||||||
self::$currentTransaction = startTransaction($transactionContext);
|
|
||||||
SentrySdk::getCurrentHub()->setSpan(self::$currentTransaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function startSpan(string $description, string $op, ?Closure $closure = null): ?Span
|
|
||||||
{
|
|
||||||
$parent = self::resolveParent();
|
|
||||||
|
|
||||||
if (! self::isSentryEnabled() || ! $parent) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$context = SpanContext::make()
|
|
||||||
->setOp($op)
|
|
||||||
->setDescription($description);
|
|
||||||
|
|
||||||
if ($closure) {
|
|
||||||
$closure($context);
|
|
||||||
}
|
|
||||||
|
|
||||||
$span = $parent->startChild($context);
|
|
||||||
|
|
||||||
if (self::$spanParents === null) {
|
|
||||||
self::$spanParents = new SplObjectStorage();
|
|
||||||
}
|
|
||||||
self::$spanParents[$span] = $parent;
|
|
||||||
|
|
||||||
SentrySdk::getCurrentHub()->setSpan($span);
|
|
||||||
|
|
||||||
return $span;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function endSpan(?Span $span = null): void
|
|
||||||
{
|
|
||||||
if (! $span || ! self::isSentryEnabled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$parent = null;
|
|
||||||
if (self::$spanParents !== null && self::$spanParents->contains($span)) {
|
|
||||||
$parent = self::$spanParents[$span] ?? null;
|
|
||||||
self::$spanParents->detach($span);
|
|
||||||
}
|
|
||||||
|
|
||||||
$span->finish();
|
|
||||||
SentrySdk::getCurrentHub()->setSpan($parent ?? self::$currentTransaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function resolveParent(): ?Span
|
|
||||||
{
|
|
||||||
return SentrySdk::getCurrentHub()->getSpan() ?? self::$currentTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function measure(string $description, string $op, Closure $closure, ?Closure $params = null): void
|
|
||||||
{
|
|
||||||
$span = self::startSpan($description, $op, $params);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$closure();
|
|
||||||
} finally {
|
|
||||||
self::endSpan($span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function finishTransaction(): void
|
|
||||||
{
|
|
||||||
if (self::$currentTransaction) {
|
|
||||||
self::$currentTransaction->finish();
|
|
||||||
self::$currentTransaction = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2
build/.gitignore
vendored
Normal file
2
build/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
2
cli
Executable file
2
cli
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
docker compose exec -w /module/oc_telegram_shop/upload web php cli.php "$@"
|
||||||
@@ -7,6 +7,7 @@ services:
|
|||||||
- "./scripts:/scripts"
|
- "./scripts:/scripts"
|
||||||
- "./module:/module"
|
- "./module:/module"
|
||||||
- "./build:/build"
|
- "./build:/build"
|
||||||
|
# - "/Users/nikitakiselev/code/italy-moda/image/catalog:/web/upload/image/catalog"
|
||||||
ports:
|
ports:
|
||||||
- "8000:80"
|
- "8000:80"
|
||||||
restart: always
|
restart: always
|
||||||
@@ -28,12 +29,12 @@ services:
|
|||||||
- XDEBUG_CLIENT_HOST=host.docker.internal
|
- XDEBUG_CLIENT_HOST=host.docker.internal
|
||||||
- XDEBUG_CLIENT_PORT=9003
|
- XDEBUG_CLIENT_PORT=9003
|
||||||
- XDEBUG_DISCOVER_CLIENT_HOST=false
|
- XDEBUG_DISCOVER_CLIENT_HOST=false
|
||||||
- PHP_IDE_CONFIG=serverName=megapay
|
- PHP_IDE_CONFIG=serverName=telecart
|
||||||
- php.session.gc_maxlifetime=28800
|
- php.session.gc_maxlifetime=28800
|
||||||
- php.session.cookie_lifetime=0
|
- php.session.cookie_lifetime=0
|
||||||
- MEGAPAY_REDIS_HOST=redis
|
- TELECART_REDIS_HOST=redis
|
||||||
- MEGAPAY_REDIS_PORT=6379
|
- TELECART_REDIS_PORT=6379
|
||||||
- MEGAPAY_REDIS_DATABASE=0
|
- TELECART_REDIS_DATABASE=0
|
||||||
depends_on:
|
depends_on:
|
||||||
- mysql
|
- mysql
|
||||||
- redis
|
- redis
|
||||||
@@ -60,10 +61,10 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- mysql_data:/var/lib/mysql
|
- mysql_data:/var/lib/mysql
|
||||||
- ./sql_dumps:/sql_dumps
|
- ./sql_dumps:/sql_dumps
|
||||||
- ./deployment/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
|
- ./docker/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
|
||||||
|
|
||||||
cron:
|
cron:
|
||||||
image: ghcr.io/megapay-labs/scheduler:latest
|
image: ghcr.io/telecart-labs/scheduler:latest
|
||||||
platform: linux/amd64
|
platform: linux/amd64
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
7
docs/TELEGRAM_ANNOUNCEMENTS.md
Normal file
7
docs/TELEGRAM_ANNOUNCEMENTS.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
🚀 Вышла версия Telecart v2.0.0!
|
||||||
|
|
||||||
|
Система конфигурации главной страницы через блоки - баннеры и карусели на главной странице. Визуальный конструктор формы оформления заказов на базе FormKit, доступно более 15 типов полей с гибкой настройкой. Ещё больше кастомизации магазина с помощью переработанной админки. Управление клиентами Telegram прямо в админ-панели. Улучшенная Яндекс.Метрика, интеграция с электронной коммерцией. Поддержка купонов и подарочных сертификатов. Улучшение UI/UX, поиска, производительности и многое другое. Более 20 нововведений и улучшений. Весь список изменений можно посмотреть в документации: https://docs.telecart.pro/changelog/2.0.0/
|
||||||
|
|
||||||
|
Демонстрация работы: https://t.me/OpenGuruStoreBot?startapp
|
||||||
|
|
||||||
|
Купить модуль можно у меня напрямую, либо на https://liveopencart.ru/opencart-moduli-shablony/moduli/telecart
|
||||||
43
docs/features/CLAUDE.md
Normal file
43
docs/features/CLAUDE.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# docs/features — документация по фичам
|
||||||
|
|
||||||
|
Эта директория хранит описания **фич** проекта TeleCart: по одной поддиректории на фичу.
|
||||||
|
Цель — дать агенту (и человеку) возможность быстро восстановить контекст по конкретной
|
||||||
|
части системы, не перечитывая весь код: что фича делает, через какие точки входит, какие
|
||||||
|
файлы за неё отвечают и как данные текут по слоям.
|
||||||
|
|
||||||
|
Вложенные `CLAUDE.md` подхватываются Claude Code автоматически при чтении файлов из их
|
||||||
|
директории — поэтому индекс фичи читается «по требованию», когда работа касается этой фичи.
|
||||||
|
|
||||||
|
## Структура
|
||||||
|
|
||||||
|
```
|
||||||
|
docs/features/
|
||||||
|
├── CLAUDE.md # этот файл: конвенции + реестр фич
|
||||||
|
├── _TEMPLATE.md # шаблон для новой фичи (копировать при описании)
|
||||||
|
└── <feature-slug>/
|
||||||
|
└── CLAUDE.md # индексный файл конкретной фичи
|
||||||
|
```
|
||||||
|
|
||||||
|
## Конвенции
|
||||||
|
|
||||||
|
- **Имя папки** — `kebab-case`, по сути фичи: `cart`, `telegram-webhook`, `form-builder`.
|
||||||
|
- **Один индекс на фичу** — `<feature-slug>/CLAUDE.md`, заполняется по `_TEMPLATE.md`.
|
||||||
|
- **Ссылки на код — обязательны.** Указывай реальные пути (`module/.../src/Handlers/CartHandler.php`)
|
||||||
|
и `action` из карт маршрутов. Код — источник истины; документация лишь навигирует к нему.
|
||||||
|
- **Не дублируй код.** Описывай назначение, поток и нюансы, а не построчный пересказ — он устареет.
|
||||||
|
- **Связывай фичи** через ссылки на их папки (например `[checkout](../checkout/CLAUDE.md)`).
|
||||||
|
- Если описанное в доке расходится с кодом — прав код; обнови доку.
|
||||||
|
|
||||||
|
## Как добавить фичу
|
||||||
|
|
||||||
|
1. `cp _TEMPLATE.md <feature-slug>/CLAUDE.md` (создав папку).
|
||||||
|
2. Заполни секции шаблона, опираясь на реальный код.
|
||||||
|
3. Добавь строку в реестр ниже.
|
||||||
|
|
||||||
|
## Реестр фич
|
||||||
|
|
||||||
|
| Фича | Путь | Краткое описание |
|
||||||
|
|------|----------------------------------|-------------------------------------------------------------|
|
||||||
|
| cart | [cart/CLAUDE.md](cart/CLAUDE.md) | Корзина: наполнение, просмотр, передача в оформление заказа |
|
||||||
|
|
||||||
|
<!-- Новые фичи добавляй строкой в таблицу выше, по алфавиту. -->
|
||||||
38
docs/features/_TEMPLATE.md
Normal file
38
docs/features/_TEMPLATE.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# <Название фичи>
|
||||||
|
|
||||||
|
> Скопируй этот файл в `docs/features/<feature-slug>/CLAUDE.md` и заполни секции.
|
||||||
|
> Удали подсказки в кавычках после заполнения. Не оставляй пустых «TODO».
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
> 1–3 предложения: какую задачу решает фича и для кого (SPA-покупатель / админ / системное).
|
||||||
|
|
||||||
|
## Точки входа
|
||||||
|
|
||||||
|
> `action` из карт маршрутов и где они объявлены. Таблица:
|
||||||
|
|
||||||
|
| action | Метод | Где объявлен |
|
||||||
|
|-----------------|-------------------|-------------------------------------------|
|
||||||
|
| `exampleAction` | `Handler::method` | `src/routes.php` или `bastion/routes.php` |
|
||||||
|
|
||||||
|
## Ключевые файлы по слоям
|
||||||
|
|
||||||
|
> Путь от корня репозитория. Только то, что реально относится к фиче.
|
||||||
|
|
||||||
|
- **Handler:** `module/.../src/Handlers/XxxHandler.php` — валидация, вызов сервиса, `JsonResponse`.
|
||||||
|
- **Service:** `module/.../src/Services/XxxService.php` — бизнес-логика.
|
||||||
|
- **Model / данные:** `module/.../src/Models/Xxx.php` или таблицы БД.
|
||||||
|
- **Фронтенд:** `frontend/spa/...` или `frontend/admin/...` (store, view, компоненты).
|
||||||
|
- **Регистрация в DI:** `src/ServiceProviders/...` (если есть нестандартная привязка).
|
||||||
|
|
||||||
|
## Поток данных
|
||||||
|
|
||||||
|
> Кратко: запрос → слои → ответ. Можно списком шагов или стрелками.
|
||||||
|
|
||||||
|
## Связанные фичи
|
||||||
|
|
||||||
|
> Ссылки на соседние папки: `[checkout](../checkout/CLAUDE.md)`.
|
||||||
|
|
||||||
|
## Нюансы и подводные камни
|
||||||
|
|
||||||
|
> Неочевидное: ограничения PHP 7.4, особенности OpenCart-реестра, кэш, edge-cases, известные баги.
|
||||||
59
docs/features/cart/CLAUDE.md
Normal file
59
docs/features/cart/CLAUDE.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Cart — корзина
|
||||||
|
|
||||||
|
## Назначение
|
||||||
|
|
||||||
|
Корзина покупателя в SPA (Telegram Mini App): отдаёт текущее содержимое корзины с
|
||||||
|
посчитанными ценами/итогами, принимает набор позиций из SPA и наполняет ими корзину
|
||||||
|
OpenCart перед оформлением заказа. Создание самого заказа — за пределами фичи (см.
|
||||||
|
[order](../order/CLAUDE.md), если описан).
|
||||||
|
|
||||||
|
## Точки входа
|
||||||
|
|
||||||
|
| action | Метод | Где объявлен |
|
||||||
|
|------------|-------------------------|------------------|
|
||||||
|
| `getCart` | `CartHandler::index` | `src/routes.php` |
|
||||||
|
| `checkout` | `CartHandler::checkout` | `src/routes.php` |
|
||||||
|
|
||||||
|
> `storeOrder` (`OrderHandler::store`) относится к оформлению заказа, не к корзине.
|
||||||
|
|
||||||
|
## Ключевые файлы по слоям
|
||||||
|
|
||||||
|
- **Handler:** `module/oc_telegram_shop/upload/oc_telegram_shop/src/Handlers/CartHandler.php`
|
||||||
|
— `index()` возвращает содержимое корзины, `checkout()` принимает позиции из SPA
|
||||||
|
(`productId`, `quantity`, `options`) и кладёт их в корзину через `Cart\Cart::add()`.
|
||||||
|
- **Service:** `module/oc_telegram_shop/upload/oc_telegram_shop/src/Services/CartService.php`
|
||||||
|
— `getCart()` собирает данные через OpenCart-реестр (`OcRegistryDecorator`): язык
|
||||||
|
`checkout/cart`, проверки стока, веса, цен, форматирование через `Cart\Currency`.
|
||||||
|
- **OpenCart-зависимости:** `Cart\Cart`, `Cart\Currency` — нативные классы OpenCart,
|
||||||
|
внедряются через DI.
|
||||||
|
- **Фронтенд (SPA):**
|
||||||
|
- `frontend/spa/src/stores/CartStore.js` — Pinia-стор корзины.
|
||||||
|
- `frontend/spa/src/ShoppingCart.js` — клиентская логика корзины.
|
||||||
|
- `frontend/spa/src/views/Cart.vue` — экран корзины.
|
||||||
|
- `frontend/spa/src/components/CartButton.vue` — кнопка/индикатор.
|
||||||
|
|
||||||
|
## Поток данных
|
||||||
|
|
||||||
|
**Просмотр:** SPA → `action=getCart` → `CartHandler::index` → `CartService::getCart()`
|
||||||
|
читает корзину из OpenCart-реестра → ответ оборачивается `Utf8Cleaner::clean()` →
|
||||||
|
`JsonResponse { data }`.
|
||||||
|
|
||||||
|
**Передача в оформление:** SPA отправляет массив позиций → `action=checkout` →
|
||||||
|
`CartHandler::checkout` маппит `options` (берёт `product_option_value_id`) и для каждой
|
||||||
|
позиции вызывает `$cart->add(productId, quantity, options)` → возвращает очищенные позиции.
|
||||||
|
|
||||||
|
## Связанные фичи
|
||||||
|
|
||||||
|
- Оформление заказа (`OrderHandler::store` / `OrderCreateService`).
|
||||||
|
- Настройки (`SettingsService`) влияют на отображение цен/стока в `getCart`.
|
||||||
|
|
||||||
|
## Нюансы и подводные камни
|
||||||
|
|
||||||
|
- **PHP 7.4.** Без синтаксиса 8.0+ (см. корневой `CLAUDE.md`).
|
||||||
|
- **Весь вывод чистится `App\Support\Utf8Cleaner::clean()`** перед отдачей — невалидный
|
||||||
|
UTF-8 из товаров OpenCart ломает `json_encode`.
|
||||||
|
- **`getCart` зависит от состояния OpenCart-сессии и реестра** (сток, валюта, логин
|
||||||
|
покупателя, язык `checkout/cart`) — это не чистая функция, результат меняется от конфига
|
||||||
|
магазина (`config_stock_checkout`, `config_customer_price`, `config_cart_weight` и т.д.).
|
||||||
|
- **`checkout` мутирует корзину OpenCart** (`Cart\Cart::add`), а не локальное состояние —
|
||||||
|
он переносит выбор из SPA в нативную корзину OpenCart.
|
||||||
56
docs/liveopencart.txt
Normal file
56
docs/liveopencart.txt
Normal file
File diff suppressed because one or more lines are too long
@@ -0,0 +1,168 @@
|
|||||||
|
# Дизайн: честный и отключаемый heartbeat
|
||||||
|
|
||||||
|
Дата: 2026-06-30
|
||||||
|
Статус: согласовано (ожидает финального ревью)
|
||||||
|
|
||||||
|
## Проблема
|
||||||
|
|
||||||
|
Модуль скрытно отправляет на сервер разработчика (`pulse.telecart.pro`) сведения об
|
||||||
|
окружении, где он запущен (heartbeat). Покупатель об этом не уведомляется и не может
|
||||||
|
это отключить. Это нечестно по отношению к покупателю и создаёт риски (GDPR / 152-ФЗ,
|
||||||
|
претензии маркетплейсов).
|
||||||
|
|
||||||
|
## Цель
|
||||||
|
|
||||||
|
Сделать heartbeat **честным**: прозрачным и отключаемым, сохранив поведение по умолчанию.
|
||||||
|
|
||||||
|
- **Прозрачность:** в админ-панели покупатель видит, что именно собирается, куда
|
||||||
|
отправляется и зачем.
|
||||||
|
- **Контроль:** отдельный переключатель, которым покупатель может выключить heartbeat.
|
||||||
|
- **По умолчанию включено** (текущее поведение сохраняется, но становится видимым и
|
||||||
|
управляемым).
|
||||||
|
|
||||||
|
## Границы (scope)
|
||||||
|
|
||||||
|
- **Только heartbeat.** Остальной TeleCart Pulse (Telegram-рассылки, события кампаний)
|
||||||
|
**не трогаем** — он гейтится наличием `pulse.api_key` и, по позиции владельца, прав не
|
||||||
|
нарушает.
|
||||||
|
- Концептуально heartbeat считаем частью фичи **TeleCart Pulse** (одна папка доков), но
|
||||||
|
у него **отдельный собственный выключатель**, не завязанный на `pulse.api_key` и не
|
||||||
|
завязанный на секрет подписи. Pulse и heartbeat включаются/выключаются независимо.
|
||||||
|
|
||||||
|
## Терминология
|
||||||
|
|
||||||
|
- **heartbeat** — периодическая (раз в час по кэшу) отправка информации об окружении:
|
||||||
|
`domain`, `bot_name`, `php_version`, `module_version`, `opencart_version`,
|
||||||
|
`opencart_version_core`. Триггерится из SPA при открытии WebApp.
|
||||||
|
- **TeleCart Pulse** — фича рассылок и аналитики кампаний; heartbeat живёт внутри её
|
||||||
|
namespace в коде (`framework/TeleCartPulse/`).
|
||||||
|
|
||||||
|
## Текущее устройство (как есть)
|
||||||
|
|
||||||
|
- `TeleCartPulseService::handleHeartbeat()`
|
||||||
|
(`module/.../framework/TeleCartPulse/TeleCartPulseService.php:133`) — ставит кэш-флаг
|
||||||
|
`telecart_pulse_heartbeat` (ttl 3600), дёргает `getMe()`, формирует payload и шлёт POST
|
||||||
|
на `PULSE_API_HOST/heartbeat`.
|
||||||
|
- Отправка фактически происходит только если есть `heartbeatPayloadSigner` (строки 168–170),
|
||||||
|
который создаётся в провайдере при наличии секрета (`pulse.heartbeat_secret` /
|
||||||
|
`PULSE_HEARTBEAT_SECRET`).
|
||||||
|
- `TeleCartPulseServiceProvider`
|
||||||
|
(`module/.../framework/TeleCartPulse/TeleCartPulseServiceProvider.php:22-35`) читает
|
||||||
|
значения из конфига и инжектит их в `TeleCartPulseService` через конструктор.
|
||||||
|
- **Пользовательской (per-install) настройки для heartbeat сейчас нет.**
|
||||||
|
|
||||||
|
## Решение
|
||||||
|
|
||||||
|
### Принцип
|
||||||
|
|
||||||
|
Использовать существующий паттерн: провайдер читает значение из конфига и **инжектит**
|
||||||
|
его в сервис через конструктор (как уже делается для `pulse.api_key` и signer). Не вводить
|
||||||
|
новых механизмов гейтинга.
|
||||||
|
|
||||||
|
### 1. Backend
|
||||||
|
|
||||||
|
**`module/.../configs/app.php`** — секция `pulse` (строки 86–90): добавить флаг с дефолтом
|
||||||
|
`true`.
|
||||||
|
|
||||||
|
```php
|
||||||
|
'pulse' => [
|
||||||
|
'api_key' => '',
|
||||||
|
'heartbeat_enabled' => true, // новый флаг
|
||||||
|
'batch_size' => 50,
|
||||||
|
'max_attempts' => 3,
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
**`TeleCartPulseServiceProvider`** — прочитать `pulse.heartbeat_enabled` и передать булевый
|
||||||
|
флаг в конструктор `TeleCartPulseService` (рядом с `apiKey` и `heartbeatSigner`). Привести
|
||||||
|
к `bool` явно.
|
||||||
|
|
||||||
|
**`TeleCartPulseService`** — принять флаг в конструкторе (новое поле `private bool
|
||||||
|
$heartbeatEnabled`). В `handleHeartbeat()` — **ранний `return` в самом начале метода**, до
|
||||||
|
проверки/установки кэша и до `getMe()`, если флаг выключен:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function handleHeartbeat(): void
|
||||||
|
{
|
||||||
|
if (! $this->heartbeatEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ... существующая логика
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Совместимость с PHP 7.4: без named arguments / промоушена свойств / union-типов. Параметр
|
||||||
|
конструктора — позиционный, со значением по умолчанию `true` для обратной совместимости
|
||||||
|
вызовов в тестах.
|
||||||
|
|
||||||
|
### 2. Frontend (админка) + прозрачность
|
||||||
|
|
||||||
|
**`frontend/admin/src/stores/settings.js`** — секция `pulse` (строки 89–92): добавить
|
||||||
|
`heartbeat_enabled` в дефолтное состояние state.
|
||||||
|
|
||||||
|
**`frontend/admin/src/views/TeleCartPulseView.vue`** — добавить `ItemBool` с переключателем,
|
||||||
|
привязанным к `settings.items.pulse.heartbeat_enabled`. В слот-описание — **честное, но
|
||||||
|
нейтральное по тону раскрытие**:
|
||||||
|
|
||||||
|
- перечислить, что отправляется: домен, имя бота, версия PHP, версия модуля, версии
|
||||||
|
OpenCart;
|
||||||
|
- указать адрес назначения: `pulse.telecart.pro`;
|
||||||
|
- описать цель мягко: для улучшения продукта и техподдержки (без акцента на проверке
|
||||||
|
лицензии).
|
||||||
|
|
||||||
|
Точную формулировку владелец отредактирует; черновик в нейтральном тоне готовит реализация.
|
||||||
|
|
||||||
|
### 3. Поведение для существующих установок
|
||||||
|
|
||||||
|
Дефолт `true` в конфиге мержится с сохранёнными настройками
|
||||||
|
(`ApplicationFactory`: `Arr::mergeArraysRecursively`). У установок без сохранённого значения
|
||||||
|
heartbeat остаётся включённым — поведение не меняется, но теперь переключатель виден.
|
||||||
|
Когда покупатель выключает тумблер и сохраняет, значение пишется в `oc_setting`
|
||||||
|
(`module_telecart_settings`) и читается на backend.
|
||||||
|
|
||||||
|
### 4. Гейтинг — итоговая логика
|
||||||
|
|
||||||
|
heartbeat реально отправляется только если **одновременно**: `heartbeat_enabled = true`
|
||||||
|
**и** есть signer (секрет настроен). Переключатель — пользовательский контроль поверх
|
||||||
|
существующего серверного гейта; они независимы.
|
||||||
|
|
||||||
|
## Тестирование
|
||||||
|
|
||||||
|
Unit-тест на `TeleCartPulseService::handleHeartbeat()`:
|
||||||
|
|
||||||
|
- `heartbeat_enabled = false` → метод выходит сразу: HTTP-запрос не выполняется, кэш-флаг
|
||||||
|
`telecart_pulse_heartbeat` **не** ставится, `getMe()` не вызывается.
|
||||||
|
- `heartbeat_enabled = true` + signer присутствует → payload формируется и уходит (как
|
||||||
|
сейчас).
|
||||||
|
|
||||||
|
Запуск: `make test-unit` (или `./vendor/bin/phpunit --filter ...` через `make ssh`).
|
||||||
|
|
||||||
|
## Документация (в этом порядке)
|
||||||
|
|
||||||
|
1. **`docs/features/settings/CLAUDE.md`** — каркас системы настроек: путь поля «форма в
|
||||||
|
админке → `saveSettingsForm` → таблица `oc_setting` (`module_telecart_settings`) →
|
||||||
|
`Config\Settings` / DTO → backend», и **чек-лист «как добавить новый toggle»** по слоям
|
||||||
|
(config-дефолт → провайдер/DTO → store → view-компонент `ItemBool`). По шаблону
|
||||||
|
`docs/features/_TEMPLATE.md`. Добавить строку в реестр в `docs/features/CLAUDE.md`.
|
||||||
|
2. **`docs/features/telecart-pulse/CLAUDE.md`** — фича Pulse целиком: рассылки, события
|
||||||
|
кампаний (гейт `api_key`) и heartbeat с новым выключателем. Ссылка на `[[settings]]`.
|
||||||
|
Добавить строку в реестр.
|
||||||
|
|
||||||
|
## Затрагиваемые файлы (сводка)
|
||||||
|
|
||||||
|
- `module/.../configs/app.php` — флаг `pulse.heartbeat_enabled`.
|
||||||
|
- `module/.../framework/TeleCartPulse/TeleCartPulseServiceProvider.php` — чтение и инжект.
|
||||||
|
- `module/.../framework/TeleCartPulse/TeleCartPulseService.php` — поле + ранний return.
|
||||||
|
- `frontend/admin/src/stores/settings.js` — дефолт в state.
|
||||||
|
- `frontend/admin/src/views/TeleCartPulseView.vue` — `ItemBool` + текст раскрытия.
|
||||||
|
- `module/.../tests/Unit/...` — тест на гейтинг heartbeat.
|
||||||
|
- `docs/features/settings/CLAUDE.md`, `docs/features/telecart-pulse/CLAUDE.md`,
|
||||||
|
`docs/features/CLAUDE.md` (реестр).
|
||||||
|
|
||||||
|
## Явно вне scope (YAGNI)
|
||||||
|
|
||||||
|
- Не делаем opt-in (по умолчанию остаётся включено).
|
||||||
|
- Не делаем отдельный consent-баннер на первой установке (достаточно видимого
|
||||||
|
переключателя с раскрытием).
|
||||||
|
- Не трогаем события кампаний и рассылки Pulse.
|
||||||
|
- Не выносим heartbeat в отдельный namespace/фичу из `TeleCartPulse`.
|
||||||
BIN
dumps/italy.sql.zip
Normal file
BIN
dumps/italy.sql.zip
Normal file
Binary file not shown.
9
frontend/admin/.vscode/extensions.json
vendored
Normal file
9
frontend/admin/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"Vue.volar",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"EditorConfig.EditorConfig",
|
||||||
|
"oxc.oxc-vscode",
|
||||||
|
"esbenp.prettier-vscode"
|
||||||
|
]
|
||||||
|
}
|
||||||
1896
frontend/admin/package-lock.json
generated
1896
frontend/admin/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,7 @@
|
|||||||
"@primeuix/themes": "^1.2.5",
|
"@primeuix/themes": "^1.2.5",
|
||||||
"@tailwindcss/vite": "^4.1.16",
|
"@tailwindcss/vite": "^4.1.16",
|
||||||
"@vueuse/core": "^14.0.0",
|
"@vueuse/core": "^14.0.0",
|
||||||
"axios": "^1.13.5",
|
"axios": "^1.15.2",
|
||||||
"codemirror": "^6.0.2",
|
"codemirror": "^6.0.2",
|
||||||
"daisyui": "^5.4.2",
|
"daisyui": "^5.4.2",
|
||||||
"js-md5": "^0.8.3",
|
"js-md5": "^0.8.3",
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
"npm-run-all2": "^8.0.4",
|
"npm-run-all2": "^8.0.4",
|
||||||
"oxlint": "~1.23.0",
|
"oxlint": "~1.23.0",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"vite": "^7.1.11",
|
"vite": "^7.3.3",
|
||||||
"vite-plugin-vue-devtools": "^8.0.3"
|
"vite-plugin-vue-devtools": "^8.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
|
|
||||||
<li :class="{active: route.name === 'pulse'}">
|
<li :class="{active: route.name === 'pulse'}">
|
||||||
<RouterLink :to="{name: 'pulse'}">
|
<RouterLink :to="{name: 'pulse'}">
|
||||||
<i class="fa fa-heartbeat pulse-icon tw:text-red-200"></i> MegaPay Pulse <span class="pulse-beta-label tw:ml-1 tw:px-1.5 tw:py-0.5 tw:text-xs tw:font-semibold tw:text-white tw:rounded">BETA</span>
|
<i class="fa fa-heartbeat pulse-icon tw:text-red-200"></i> TeleCart Pulse <span class="pulse-beta-label tw:ml-1 tw:px-1.5 tw:py-0.5 tw:text-xs tw:font-semibold tw:text-white tw:rounded">BETA</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ legend.p-fieldset-legend {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.megapay-admin-app {
|
.telecart-admin-app {
|
||||||
color: var(--color-slate-700);
|
color: var(--color-slate-700);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Dropdown
|
|
||||||
:model-value="modelValue"
|
|
||||||
:options="options"
|
|
||||||
option-label="label"
|
|
||||||
option-value="value"
|
|
||||||
placeholder="Интервал"
|
|
||||||
class="tw:w-[14rem] tw:shrink-0"
|
|
||||||
@update:model-value="$emit('update:modelValue', $event ?? '')"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import Dropdown from 'primevue/dropdown';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
defineEmits(['update:modelValue']);
|
|
||||||
|
|
||||||
/** Пресеты интервалов (label — для отображения, value — cron expression) */
|
|
||||||
const PRESETS = [
|
|
||||||
{ label: 'Раз в минуту', value: '* * * * *' },
|
|
||||||
{ label: 'Раз в 5 минут', value: '*/5 * * * *' },
|
|
||||||
{ label: 'Раз в 10 минут', value: '*/10 * * * *' },
|
|
||||||
{ label: 'Раз в час', value: '0 * * * *' },
|
|
||||||
{ label: 'Раз в 3 часа', value: '0 */3 * * *' },
|
|
||||||
{ label: 'Раз в 6 часов', value: '0 */6 * * *' },
|
|
||||||
{ label: 'Раз в сутки', value: '0 0 * * *' },
|
|
||||||
{ label: 'Раз в неделю', value: '0 0 * * 0' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const presetValues = new Set(PRESETS.map((p) => p.value));
|
|
||||||
|
|
||||||
/** Только пресеты; если текущее значение не из списка — показываем его в списке (уже сохранённое в БД), чтобы не терять отображение */
|
|
||||||
const options = computed(() => {
|
|
||||||
const current = props.modelValue ?? '';
|
|
||||||
if (!current || presetValues.has(current)) {
|
|
||||||
return PRESETS;
|
|
||||||
}
|
|
||||||
return [{ label: current, value: current }, ...PRESETS];
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
<template>
|
|
||||||
<SettingsItem label="URL для cron-job.org">
|
|
||||||
<template #default>
|
|
||||||
<InputGroup>
|
|
||||||
<Button
|
|
||||||
icon="fa fa-refresh"
|
|
||||||
severity="secondary"
|
|
||||||
:loading="regeneratingUrl"
|
|
||||||
v-tooltip.top="'Перегенерировать URL'"
|
|
||||||
@click="confirmRegenerateUrl"
|
|
||||||
/>
|
|
||||||
<Button icon="fa fa-copy" severity="secondary" @click="copyToClipboard"/>
|
|
||||||
<InputText readonly :model-value="cronJobOrgUrl" class="tw:w-full"/>
|
|
||||||
</InputGroup>
|
|
||||||
</template>
|
|
||||||
<template #help>
|
|
||||||
Создайте задачу на <a href="https://cron-job.org/" target="_blank" rel="noopener" class="tw:underline">cron-job.org</a>, укажите этот URL и интервал (например, каждые 5 минут). Метод: GET. Учитывайте лимиты по времени запроса на вашем хостинге — для тяжёлых задач возможны таймауты. При утечке URL нажмите «Перегенерировать URL» и обновите задачу на cron-job.org.
|
|
||||||
</template>
|
|
||||||
</SettingsItem>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { computed, ref } from 'vue';
|
|
||||||
import { useSettingsStore } from '@/stores/settings.js';
|
|
||||||
import SettingsItem from '@/components/SettingsItem.vue';
|
|
||||||
import InputText from 'primevue/inputtext';
|
|
||||||
import Button from 'primevue/button';
|
|
||||||
import InputGroup from 'primevue/inputgroup';
|
|
||||||
import { useConfirm } from 'primevue/useconfirm';
|
|
||||||
import { toastBus } from '@/utils/toastHelper.js';
|
|
||||||
import { apiPost } from '@/utils/http.js';
|
|
||||||
|
|
||||||
const settings = useSettingsStore();
|
|
||||||
const confirm = useConfirm();
|
|
||||||
const regeneratingUrl = ref(false);
|
|
||||||
|
|
||||||
const cronJobOrgUrl = computed(() => settings.items.cron?.schedule_url ?? '');
|
|
||||||
|
|
||||||
function confirmRegenerateUrl(event) {
|
|
||||||
confirm.require({
|
|
||||||
group: 'popup',
|
|
||||||
target: event.currentTarget,
|
|
||||||
message: 'После смены URL его нужно будет обновить в задаче на cron-job.org. Продолжить?',
|
|
||||||
icon: 'pi pi-exclamation-triangle',
|
|
||||||
rejectProps: { label: 'Отмена', severity: 'secondary', outlined: true },
|
|
||||||
acceptProps: { label: 'Перегенерировать', severity: 'secondary' },
|
|
||||||
accept: () => regenerateUrl(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function regenerateUrl() {
|
|
||||||
regeneratingUrl.value = true;
|
|
||||||
try {
|
|
||||||
const res = await apiPost('regenerateCronScheduleUrl', {});
|
|
||||||
if (res?.success && res.data?.api_key) {
|
|
||||||
settings.items.cron.api_key = res.data.api_key;
|
|
||||||
if (res.data.schedule_url !== undefined) {
|
|
||||||
settings.items.cron.schedule_url = res.data.schedule_url;
|
|
||||||
}
|
|
||||||
toastBus.emit('show', {
|
|
||||||
severity: 'success',
|
|
||||||
summary: 'URL обновлён',
|
|
||||||
detail: 'Обновите URL в задаче на cron-job.org',
|
|
||||||
life: 4000,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
toastBus.emit('show', {
|
|
||||||
severity: 'error',
|
|
||||||
summary: 'Ошибка',
|
|
||||||
detail: res?.data?.error ?? 'Не удалось перегенерировать URL',
|
|
||||||
life: 4000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
toastBus.emit('show', {
|
|
||||||
severity: 'error',
|
|
||||||
summary: 'Ошибка',
|
|
||||||
detail: err?.response?.data?.error ?? 'Не удалось перегенерировать URL',
|
|
||||||
life: 4000,
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
regeneratingUrl.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function copyToClipboard() {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(cronJobOrgUrl.value);
|
|
||||||
toastBus.emit('show', {
|
|
||||||
severity: 'success',
|
|
||||||
summary: 'Скопировано',
|
|
||||||
detail: 'URL скопирован в буфер обмена',
|
|
||||||
life: 2000,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
toastBus.emit('show', {
|
|
||||||
severity: 'error',
|
|
||||||
summary: 'Ошибка',
|
|
||||||
detail: 'Не удалось скопировать текст',
|
|
||||||
life: 2000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="tw:border tw:border-gray-300 tw:rounded-md tw:bg-white tw:overflow-hidden">
|
|
||||||
<div class="tw:flex tw:flex-col">
|
|
||||||
<div
|
|
||||||
v-for="job in scheduledJobs"
|
|
||||||
:key="job.id"
|
|
||||||
class="tw:flex tw:items-center tw:gap-4 tw:py-3 tw:px-3 tw:border-b tw:border-gray-200 tw:last:border-b-0"
|
|
||||||
>
|
|
||||||
<ToggleSwitch
|
|
||||||
:model-value="Boolean(job.is_enabled)"
|
|
||||||
@update:model-value="onJobToggle(job, $event)"
|
|
||||||
/>
|
|
||||||
<div class="tw:min-w-0 tw:flex-1 tw:flex tw:flex-col tw:gap-0.5">
|
|
||||||
<span class="tw:text-[inherit] tw:font-bold">{{ hasJobMeta(job.name) ? jobMeta(job.name).friendlyName : job.name }}</span>
|
|
||||||
<span v-if="hasJobMeta(job.name) && jobMeta(job.name).description" class="tw:text-sm tw:text-gray-500">
|
|
||||||
{{ jobMeta(job.name).description }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
v-if="job.failed_reason"
|
|
||||||
class="tw:inline-flex tw:items-center tw:gap-1 tw:shrink-0 tw:px-2 tw:py-1 tw:rounded tw:text-sm tw:bg-red-100 tw:text-red-700 tw:cursor-default"
|
|
||||||
role="img"
|
|
||||||
:aria-label="'Ошибка: ' + job.failed_reason"
|
|
||||||
v-tooltip.top="errorTooltip(job)"
|
|
||||||
>
|
|
||||||
<i class="fa fa-exclamation-circle" aria-hidden="true"/>
|
|
||||||
Ошибка
|
|
||||||
</span>
|
|
||||||
<div
|
|
||||||
v-else
|
|
||||||
class="tw:flex tw:flex-col tw:gap-0.5 tw:shrink-0 tw:items-end"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="tw:inline-flex tw:items-center tw:shrink-0 tw:px-2 tw:py-1 tw:rounded tw:text-sm tw:bg-green-100 tw:text-green-800 tw:cursor-default"
|
|
||||||
v-tooltip.top="'Дата последнего успешного запуска'"
|
|
||||||
>
|
|
||||||
{{ formatLastRun(job.last_success_at) }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="formatDuration(job.last_duration_seconds)"
|
|
||||||
class="tw:text-xs tw:text-gray-500"
|
|
||||||
>
|
|
||||||
Время выполнения: {{ formatDuration(job.last_duration_seconds) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="tw:shrink-0 tw:w-[14rem]">
|
|
||||||
<CronExpressionSelect
|
|
||||||
:model-value="job.cron_expression"
|
|
||||||
@update:model-value="job.cron_expression = $event"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!scheduledJobs.length" class="tw:text-gray-500 tw:py-4 tw:px-3">
|
|
||||||
Нет запланированных задач
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useSettingsStore } from '@/stores/settings.js';
|
|
||||||
import ToggleSwitch from 'primevue/toggleswitch';
|
|
||||||
import CronExpressionSelect from '@/components/CronExpressionSelect.vue';
|
|
||||||
|
|
||||||
const settings = useSettingsStore();
|
|
||||||
|
|
||||||
/** Человекочитаемое имя и описание задач (ключ — job.name с бэкенда) */
|
|
||||||
const JOB_META = {
|
|
||||||
megapay_pulse_send_events: {
|
|
||||||
friendlyName: 'Отправка данных в MegaPay Pulse',
|
|
||||||
description: 'Отправка данных телеметрии о действиях в MegaPay. Требуется для сбора метрик по рассылкам и кампаниям, сделанных через сервис MegaPay Pulse',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function hasJobMeta(jobName) {
|
|
||||||
return jobName in JOB_META;
|
|
||||||
}
|
|
||||||
|
|
||||||
function jobMeta(jobName) {
|
|
||||||
return JOB_META[jobName];
|
|
||||||
}
|
|
||||||
|
|
||||||
const scheduledJobs = computed(() => settings.items.scheduled_jobs ?? []);
|
|
||||||
|
|
||||||
function formatLastRun(value) {
|
|
||||||
if (!value) return '—';
|
|
||||||
const d = typeof value === 'string' ? new Date(value.replace(' ', 'T')) : new Date(value);
|
|
||||||
return Number.isNaN(d.getTime()) ? value : d.toLocaleString('ru-RU');
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatDuration(seconds) {
|
|
||||||
if (seconds == null || seconds === '' || Number(seconds) <= 0) return '';
|
|
||||||
const n = Number(seconds);
|
|
||||||
if (Number.isNaN(n)) return '';
|
|
||||||
if (n < 1) return 'менее 1с';
|
|
||||||
return `${n % 1 ? n.toFixed(1) : Math.round(n)}с`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function errorTooltip(job) {
|
|
||||||
const dateStr = formatLastRun(job.failed_at);
|
|
||||||
return `${dateStr}\n${job.failed_reason ?? ''}`.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onJobToggle(job, isEnabled) {
|
|
||||||
job.is_enabled = isEnabled ? 1 : 0;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -94,7 +94,7 @@ onMounted(() => {
|
|||||||
$(searchInput.value).autocomplete({
|
$(searchInput.value).autocomplete({
|
||||||
source: function (request, response) {
|
source: function (request, response) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: `/admin/index.php?route=catalog/category/autocomplete&user_token=${window.MegaPay.user_token}&filter_name=${encodeURIComponent(request)}`,
|
url: `/admin/index.php?route=catalog/category/autocomplete&user_token=${window.TeleCart.user_token}&filter_name=${encodeURIComponent(request)}`,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function (json) {
|
success: function (json) {
|
||||||
response($.map(json, function (item) {
|
response($.map(json, function (item) {
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ onMounted(() => {
|
|||||||
$(productsInput.value).autocomplete({
|
$(productsInput.value).autocomplete({
|
||||||
source: function (request, response) {
|
source: function (request, response) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: `/admin/index.php?route=catalog/product/autocomplete&user_token=${window.MegaPay.user_token}&filter_name=${encodeURIComponent(request)}`,
|
url: `/admin/index.php?route=catalog/product/autocomplete&user_token=${window.TeleCart.user_token}&filter_name=${encodeURIComponent(request)}`,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function (json) {
|
success: function (json) {
|
||||||
response($.map(json, function (item) {
|
response($.map(json, function (item) {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
<template #help>
|
<template #help>
|
||||||
Подробная инструкция доступна в
|
Подробная инструкция доступна в
|
||||||
<a href="https://megapay-labs.github.io/docs/telegram/telegram/#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%B1%D0%BE%D1%82%D0%B0" target="_blank">документации
|
<a href="https://telecart-labs.github.io/docs/telegram/telegram/#%D1%81%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5-%D0%B1%D0%BE%D1%82%D0%B0" target="_blank">документации
|
||||||
<i class="fa fa-external-link"></i>
|
<i class="fa fa-external-link"></i>
|
||||||
</a>.
|
</a>.
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
Telegram поддерживает только часть HTML тегов, которые описаны в их
|
Telegram поддерживает только часть HTML тегов, которые описаны в их
|
||||||
<a href="https://core.telegram.org/bots/api#html-style" target="_blank">документации <i class="fa fa-external-link"></i></a>.
|
<a href="https://core.telegram.org/bots/api#html-style" target="_blank">документации <i class="fa fa-external-link"></i></a>.
|
||||||
</p>
|
</p>
|
||||||
<p>Дополнительно к этому MegaPay добавляет переменные, которые вы можете использовать, чтобы сделать сообщения динамическими.</p>
|
<p>Дополнительно к этому TeleCart добавляет переменные, которые вы можете использовать, чтобы сделать сообщения динамическими.</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><code>{store_name}</code> — название магазина</li>
|
<li><code>{store_name}</code> — название магазина</li>
|
||||||
<li><code>{order_id}</code> — номер заказа</li>
|
<li><code>{order_id}</code> — номер заказа</li>
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
:modelValue="model"
|
:modelValue="model"
|
||||||
:allowCopy="true"
|
:allowCopy="true"
|
||||||
>
|
>
|
||||||
Ссылка на сайт с MegaPay витриной, которую нужно указывать в настройках MiniApp в @BotFather.<br>
|
Ссылка на сайт с TeleCart витриной, которую нужно указывать в настройках MiniApp в @BotFather.<br>
|
||||||
Подробная инструкция по настройке в
|
Подробная инструкция по настройке в
|
||||||
<a href="https://docs.megapay.pro/telegram/telegram/" target="_blank">
|
<a href="https://docs.telecart.pro/telegram/telegram/" target="_blank">
|
||||||
документации <i class="fa fa-external-link"></i>
|
документации <i class="fa fa-external-link"></i>
|
||||||
</a>.
|
</a>.
|
||||||
</ItemInput>
|
</ItemInput>
|
||||||
|
|||||||
@@ -1,65 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-2 tw:flex tw:flex-col tw:gap-1">
|
<label class="col-sm-2 control-label" for="module_tgshop_status">
|
||||||
<label class="control-label" for="module_tgshop_status">
|
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</label>
|
</label>
|
||||||
<a
|
|
||||||
v-if="docHref"
|
|
||||||
:href="docHref"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="tw:inline-flex tw:items-center tw:gap-1 tw:text-sm tw:text-gray-500 hover:tw:text-gray-700 tw:underline tw:decoration-dotted tw:decoration-1 tw:underline-offset-2 tw:w-fit tw:self-end"
|
|
||||||
>
|
|
||||||
<i class="fa fa-external-link tw:text-xs" aria-hidden="true"></i>
|
|
||||||
<span>Документация</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<slot name="default"></slot>
|
<slot name="default"></slot>
|
||||||
<div class="help-block">
|
<div class="help-block">
|
||||||
<slot name="help"></slot>
|
<slot name="help"></slot>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hasExpandable">
|
|
||||||
<Button
|
|
||||||
:label="expandableLabel"
|
|
||||||
severity="info"
|
|
||||||
link
|
|
||||||
size="small"
|
|
||||||
@click="expanded = !expanded"
|
|
||||||
/>
|
|
||||||
<div v-show="expanded" class="tw:mt-2 tw:space-y-2">
|
|
||||||
<slot name="expandable"></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, useSlots, computed } from 'vue';
|
|
||||||
import Button from 'primevue/button';
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
label: {
|
label: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
/** Ссылка на документацию: отображается под label, открывается в новой вкладке */
|
|
||||||
docHref: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
/** Подпись кнопки раскрытия блока #expandable (по умолчанию «Подробнее») */
|
|
||||||
expandableLabel: {
|
|
||||||
type: String,
|
|
||||||
default: 'Подробнее',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const slots = useSlots();
|
|
||||||
const hasExpandable = computed(() => !!slots.expandable);
|
|
||||||
const expanded = ref(false);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: `index.php?route=catalog/category/autocomplete&user_token=${window.MegaPay.user_token}&filter_name=` + encodeURIComponent(request),
|
url: `index.php?route=catalog/category/autocomplete&user_token=${window.TeleCart.user_token}&filter_name=` + encodeURIComponent(request),
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function (json) {
|
success: function (json) {
|
||||||
response($.map(json, function (item) {
|
response($.map(json, function (item) {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: `index.php?route=catalog/product/autocomplete&user_token=${window.MegaPay.user_token}&filter_name=` + encodeURIComponent(request),
|
url: `index.php?route=catalog/product/autocomplete&user_token=${window.TeleCart.user_token}&filter_name=` + encodeURIComponent(request),
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
success: function (json) {
|
success: function (json) {
|
||||||
response($.map(json, function (item) {
|
response($.map(json, function (item) {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<div class="tw:flex tw:items-center tw:flex-wrap tw:gap-8">
|
<div class="tw:flex tw:items-center tw:flex-wrap tw:gap-8">
|
||||||
<div>
|
<div>
|
||||||
<span
|
<span
|
||||||
v-tooltip.top="'Общее количество заказов, сделанное через MegaPay за всё время.'"
|
v-tooltip.top="'Общее количество заказов, сделанное через TeleCart за всё время.'"
|
||||||
class="tw:text-surface-500 tw:dark:text-surface-300"
|
class="tw:text-surface-500 tw:dark:text-surface-300"
|
||||||
>
|
>
|
||||||
Количество заказов
|
Количество заказов
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span
|
<span
|
||||||
v-tooltip.top="'Итоговая сумма заказов, сделанных через MegaPay за всё время.'"
|
v-tooltip.top="'Итоговая сумма заказов, сделанных через TeleCart за всё время.'"
|
||||||
class="tw:text-surface-500 tw:dark:text-surface-300"
|
class="tw:text-surface-500 tw:dark:text-surface-300"
|
||||||
>Общая сумма</span>
|
>Общая сумма</span>
|
||||||
<div
|
<div
|
||||||
@@ -101,14 +101,14 @@
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
icon="fa fa-book"
|
icon="fa fa-book"
|
||||||
v-tooltip.top="'Документация по модулю MegaPay'"
|
v-tooltip.top="'Документация по модулю TeleCart'"
|
||||||
as="a"
|
as="a"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://megapay-labs.github.io/docs/"
|
href="https://telecart-labs.github.io/docs/"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
icon="fa fa-group"
|
icon="fa fa-group"
|
||||||
v-tooltip.top="'Официальная Telegram группа модуля MegaPay'"
|
v-tooltip.top="'Официальная Telegram группа модуля TeleCart'"
|
||||||
as="a"
|
as="a"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="https://t.me/ocstore3"
|
href="https://t.me/ocstore3"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import StoreView from "@/views/StoreView.vue";
|
|||||||
import MainPageView from "@/views/MainPageView.vue";
|
import MainPageView from "@/views/MainPageView.vue";
|
||||||
import FormBuilderView from "@/views/FormBuilderView.vue";
|
import FormBuilderView from "@/views/FormBuilderView.vue";
|
||||||
import CustomersView from "@/views/CustomersView.vue";
|
import CustomersView from "@/views/CustomersView.vue";
|
||||||
import MegaPayPulseView from "@/views/MegaPayPulseView.vue";
|
import TeleCartPulseView from "@/views/TeleCartPulseView.vue";
|
||||||
import CronView from "@/views/CronView.vue";
|
import CronView from "@/views/CronView.vue";
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
@@ -20,7 +20,7 @@ const router = createRouter({
|
|||||||
{path: '/mainpage', name: 'mainpage', component: MainPageView},
|
{path: '/mainpage', name: 'mainpage', component: MainPageView},
|
||||||
{path: '/metrics', name: 'metrics', component: MetricsView},
|
{path: '/metrics', name: 'metrics', component: MetricsView},
|
||||||
{path: '/orders', name: 'orders', component: OrdersView},
|
{path: '/orders', name: 'orders', component: OrdersView},
|
||||||
{path: '/pulse', name: 'pulse', component: MegaPayPulseView},
|
{path: '/pulse', name: 'pulse', component: TeleCartPulseView},
|
||||||
{path: '/store', name: 'store', component: StoreView},
|
{path: '/store', name: 'store', component: StoreView},
|
||||||
{path: '/telegram', name: 'telegram', component: TelegramView},
|
{path: '/telegram', name: 'telegram', component: TelegramView},
|
||||||
{path: '/texts', name: 'texts', component: TextsView},
|
{path: '/texts', name: 'texts', component: TextsView},
|
||||||
|
|||||||
@@ -93,11 +93,7 @@ export const useSettingsStore = defineStore('settings', {
|
|||||||
|
|
||||||
cron: {
|
cron: {
|
||||||
mode: 'disabled',
|
mode: 'disabled',
|
||||||
api_key: '',
|
|
||||||
schedule_url: '',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
scheduled_jobs: [],
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -125,14 +121,6 @@ export const useSettingsStore = defineStore('settings', {
|
|||||||
...this.items,
|
...this.items,
|
||||||
...response.data,
|
...response.data,
|
||||||
};
|
};
|
||||||
// Нормализуем типы у запланированных задач (is_enabled — число 0/1), чтобы переключение туда-обратно не меняло хеш
|
|
||||||
const jobs = this.items.scheduled_jobs;
|
|
||||||
if (Array.isArray(jobs)) {
|
|
||||||
this.items.scheduled_jobs = jobs.map((job) => ({
|
|
||||||
...job,
|
|
||||||
is_enabled: job.is_enabled === true || job.is_enabled === 1 || job.is_enabled === '1' ? 1 : 0,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
// Сохраняем хеш исходного состояния после загрузки
|
// Сохраняем хеш исходного состояния после загрузки
|
||||||
this.originalItemsHash = md5(JSON.stringify(this.items));
|
this.originalItemsHash = md5(JSON.stringify(this.items));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
export function getThumb(imageUrl) {
|
export function getThumb(imageUrl) {
|
||||||
const url = new URL(`${window.MegaPay.shop_base_url}/admin/index.php`);
|
const url = new URL(`${window.TeleCart.shop_base_url}/admin/index.php`);
|
||||||
url.searchParams.set('route', 'extension/module/tgshop/handle');
|
url.searchParams.set('route', 'extension/module/tgshop/handle');
|
||||||
url.searchParams.set('api_action', 'getImage');
|
url.searchParams.set('api_action', 'getImage');
|
||||||
url.searchParams.set('path', imageUrl);
|
url.searchParams.set('path', imageUrl);
|
||||||
url.searchParams.set('size', '100x100');
|
url.searchParams.set('size', '100x100');
|
||||||
url.searchParams.set('user_token', window.MegaPay.user_token);
|
url.searchParams.set('user_token', window.TeleCart.user_token);
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Получает user_token из глобального объекта MegaPay
|
* Получает user_token из глобального объекта TeleCart
|
||||||
*/
|
*/
|
||||||
function getUserToken() {
|
function getUserToken() {
|
||||||
if (typeof window !== 'undefined' && window.MegaPay?.user_token) {
|
if (typeof window !== 'undefined' && window.TeleCart?.user_token) {
|
||||||
return window.MegaPay.user_token;
|
return window.TeleCart.user_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: пытаемся получить из URL как запасной вариант
|
// Fallback: пытаемся получить из URL как запасной вариант
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<SettingsItem label="Режим работы планировщика" doc-href="https://docs.megapay.pro/features/cron/">
|
<SettingsItem label="Режим работы планировщика">
|
||||||
<template #default>
|
<template #default>
|
||||||
<SelectButton
|
<SelectButton
|
||||||
v-model="settings.items.cron.mode"
|
v-model="settings.items.cron.mode"
|
||||||
@@ -13,35 +13,22 @@
|
|||||||
<div v-if="settings.items.cron.mode === 'disabled'" class="tw:text-red-600 tw:font-bold">
|
<div v-if="settings.items.cron.mode === 'disabled'" class="tw:text-red-600 tw:font-bold">
|
||||||
Все фоновые задачи отключены.
|
Все фоновые задачи отключены.
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="settings.items.cron.mode === 'cron_job_org'">
|
|
||||||
Задачи запускаются по вызову URL с сервиса <a href="https://cron-job.org/" target="_blank" rel="noopener" class="tw:underline">cron-job.org</a>. Подходит для лёгких задач; при большом количестве товаров или тяжёлых операциях возможны таймауты.
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
Рекомендуемый режим. Использует системный планировщик задач Linux.
|
Рекомендуемый режим. Использует системный планировщик задач Linux.
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
<template #expandable>
|
<div class="tw:mt-2">
|
||||||
<p>
|
<p>
|
||||||
<strong>Системный CRON (рекомендуется):</strong> Задачи выполняются через команду PHP в оболочке сервера (CLI), без HTTP и без ограничений по времени запроса. Не зависит от посещаемости сайта и подходит для любых объёмов данных, в том числе для тяжёлых задач и больших каталогов. Требует доступа к серверу (SSH или панель с CRON). Добавьте команду ниже в планировщик (обычно <code class="tw:px-1 tw:py-0.5 tw:bg-gray-100 tw:dark:bg-gray-800 tw:rounded">crontab -e</code>) для запуска каждые 5 минут.
|
<strong>Системный CRON (рекомендуется):</strong> Стабильное выполнение задач по расписанию, независимо от
|
||||||
</p>
|
посещаемости сайта. Добавьте команду в CRON для автоматического выполнения каждые 5 минут.
|
||||||
<p>
|
|
||||||
<strong>cron-job.org:</strong> Внешний сервис по расписанию вызывает URL вашего сайта по HTTP. Не требует доступа к серверу — удобно для shared-хостинга без CRON. Ограничения: выполнение идёт через веб-запрос, поэтому есть лимиты по времени (timeout у хостинга и у cron-job.org). <strong>Не подходит для тяжёлых сайтов</strong> (много товаров, большие каталоги, тяжёлые задачи): запрос может обрываться по таймауту, задачи не успеют завершиться. Выбирайте этот способ только если нет доступа к системному CRON и нагрузка на планировщик небольшая.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Выключено:</strong> Все фоновые задачи отключены. Планировщик не будет выполнять никаких задач.
|
<strong>Выключено:</strong> Все фоновые задачи отключены. Планировщик не будет выполнять никаких задач.
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
|
|
||||||
<div class="tw:relative tw:mt-4">
|
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
'tw:transition-all tw:duration-200',
|
|
||||||
settings.items.cron.mode === 'disabled'
|
|
||||||
? 'tw:blur-[2px] tw:pointer-events-none tw:select-none'
|
|
||||||
: '',
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<SettingsItem label="Последний запуск CRON">
|
<SettingsItem label="Последний запуск CRON">
|
||||||
<template #default>
|
<template #default>
|
||||||
<div v-if="lastRunDate" class="tw:text-green-600 tw:font-bold tw:py-2">
|
<div v-if="lastRunDate" class="tw:text-green-600 tw:font-bold tw:py-2">
|
||||||
@@ -71,48 +58,22 @@
|
|||||||
5 минут.
|
5 минут.
|
||||||
</template>
|
</template>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
|
|
||||||
<CronJobOrgUrlField v-if="settings.items.cron.mode === 'cron_job_org'"/>
|
|
||||||
|
|
||||||
<SettingsItem label="Задачи планировщика">
|
|
||||||
<template #default>
|
|
||||||
<ScheduledJobsList />
|
|
||||||
</template>
|
|
||||||
<template #help>
|
|
||||||
Включение и отключение задач планировщика. Дата последнего успешного запуска; при ошибке отображается иконка с подсказкой.
|
|
||||||
</template>
|
|
||||||
</SettingsItem>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="settings.items.cron.mode === 'disabled'"
|
|
||||||
class="tw:absolute tw:inset-0 tw:flex tw:items-center tw:justify-center tw:rounded-lg tw:bg-white/80 tw:dark:bg-gray-900/80 tw:z-10"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
<span class="tw:text-lg tw:font-semibold tw:text-gray-600 tw:dark:text-gray-400">
|
|
||||||
Планировщик выключен
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {computed} from 'vue';
|
import {computed} from 'vue';
|
||||||
import { useSettingsStore } from '@/stores/settings.js';
|
import {useSettingsStore} from "@/stores/settings.js";
|
||||||
import SettingsItem from '@/components/SettingsItem.vue';
|
import SettingsItem from "@/components/SettingsItem.vue";
|
||||||
import ScheduledJobsList from '@/components/ScheduledJobsList.vue';
|
import SelectButton from "primevue/selectbutton";
|
||||||
import CronJobOrgUrlField from '@/components/CronJobOrgUrlField.vue';
|
import InputText from "primevue/inputtext";
|
||||||
import SelectButton from 'primevue/selectbutton';
|
import Button from "primevue/button";
|
||||||
import InputText from 'primevue/inputtext';
|
|
||||||
import Button from 'primevue/button';
|
|
||||||
import InputGroup from 'primevue/inputgroup';
|
import InputGroup from 'primevue/inputgroup';
|
||||||
import { toastBus } from '@/utils/toastHelper.js';
|
import {toastBus} from "@/utils/toastHelper.js";
|
||||||
|
|
||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
|
|
||||||
const cronModes = [
|
const cronModes = [
|
||||||
{value: 'system', label: 'Системный CRON (Linux)'},
|
{value: 'system', label: 'Системный CRON (Linux)'},
|
||||||
{value: 'cron_job_org', label: 'cron-job.org'},
|
|
||||||
{value: 'disabled', label: 'Выключено'},
|
{value: 'disabled', label: 'Выключено'},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ import ItemSelect from "@/components/Settings/ItemSelect.vue";
|
|||||||
import ItemInput from "@/components/Settings/ItemInput.vue";
|
import ItemInput from "@/components/Settings/ItemInput.vue";
|
||||||
|
|
||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
const themes = JSON.parse(window.MegaPay.themes);
|
const themes = JSON.parse(window.TeleCart.themes);
|
||||||
|
|
||||||
const aspectRatioOptions = {
|
const aspectRatioOptions = {
|
||||||
'1:1': '1:1 - Квадрат (универсально, аксессуары, мелкие товары)',
|
'1:1': '1:1 - Квадрат (универсально, аксессуары, мелкие товары)',
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
as="a"
|
as="a"
|
||||||
href="https://megapay-labs.github.io/docs/analitycs/start/"
|
href="https://telecart-labs.github.io/docs/analitycs/start/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
variant="text"
|
variant="text"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -13,5 +13,5 @@ import {useSettingsStore} from "@/stores/settings.js";
|
|||||||
import ItemSelect from "@/components/Settings/ItemSelect.vue";
|
import ItemSelect from "@/components/Settings/ItemSelect.vue";
|
||||||
|
|
||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
const orderStatuses = JSON.parse(window.MegaPay.order_statuses);
|
const orderStatuses = JSON.parse(window.TeleCart.order_statuses);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -61,5 +61,5 @@ const productInteractionOptions = {
|
|||||||
browser: 'Открытие товара на сайте',
|
browser: 'Открытие товара на сайте',
|
||||||
};
|
};
|
||||||
|
|
||||||
const userToken = window.MegaPay.user_token;
|
const userToken = window.TeleCart.user_token;
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tw:space-y-6">
|
<div class="tw:space-y-6">
|
||||||
<div class="megapay-pulse-info">
|
<div class="telecart-pulse-info">
|
||||||
<h3>🚀 Расширьте возможности вашего магазина с <strong><a href="https://megapay.pro/" target="_blank">MegaPay Pulse</a>!</strong></h3>
|
<h3>🚀 Расширьте возможности вашего магазина с <strong><a href="https://telecart.pro/" target="_blank">TeleCart Pulse</a>!</strong></h3>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Если вы хотите не только показывать товары в Telegram, но и активно общаться с клиентами,
|
Если вы хотите не только показывать товары в Telegram, но и активно общаться с клиентами,
|
||||||
рассылать новости, акции и уведомления — для этого есть <strong>MegaPay Pulse</strong>.
|
рассылать новости, акции и уведомления — для этого есть <strong>TeleCart Pulse</strong>.
|
||||||
Это <strong>SaaS-платформа с месячной подпиской</strong>, которая полностью интегрируется
|
Это <strong>SaaS-платформа с месячной подпиской</strong>, которая полностью интегрируется
|
||||||
с вашим OpenCart-магазином и витриной MegaPay.
|
с вашим OpenCart-магазином и витриной TeleCart.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p><strong>С MegaPay Pulse вы сможете:</strong></p>
|
<p><strong>С TeleCart Pulse вы сможете:</strong></p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>📣 Делать массовые рассылки сообщений покупателям прямо в Telegram</li>
|
<li>📣 Делать массовые рассылки сообщений покупателям прямо в Telegram</li>
|
||||||
<li>📊 Анализировать эффективность сообщений и взаимодействие клиентов</li>
|
<li>📊 Анализировать эффективность сообщений и взаимодействие клиентов</li>
|
||||||
<li>🔗 Легко синхронизироваться с вашей витриной MegaPay — все данные остаются в одном месте</li>
|
<li>🔗 Легко синхронизироваться с вашей витриной TeleCart — все данные остаются в одном месте</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
🧪 Платформа <strong>MegaPay Pulse находится на ранней стадии тестирования</strong>.
|
🧪 Платформа <strong>TeleCart Pulse находится на ранней стадии тестирования</strong>.
|
||||||
Если вам интересно и вы хотите принять участи в тестировании интересно, свяжитесь со мной через
|
Если вам интересно и вы хотите принять участи в тестировании интересно, свяжитесь со мной через
|
||||||
<a href="https://t.me/ocstore3" target="_blank">официальную группу MegaPay в Telegram</a>.
|
<a href="https://t.me/ocstore3" target="_blank">официальную группу TeleCart в Telegram</a>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
type="number"
|
type="number"
|
||||||
placeholder="50"
|
placeholder="50"
|
||||||
>
|
>
|
||||||
Определяет, сколько событий отправляется в MegaPay Pulse за один запуск фоновой задачи.
|
Определяет, сколько событий отправляется в TeleCart Pulse за один запуск фоновой задачи.
|
||||||
При большом значении события обрабатываются быстрее, но увеличивается нагрузка на сервер.
|
При большом значении события обрабатываются быстрее, но увеличивается нагрузка на сервер.
|
||||||
При малом значении нагрузка ниже, но обработка занимает больше времени.
|
При малом значении нагрузка ниже, но обработка занимает больше времени.
|
||||||
Рекомендуемое значение: 50.
|
Рекомендуемое значение: 50.
|
||||||
@@ -110,7 +110,7 @@ const settings = useSettingsStore();
|
|||||||
const stats = ref(null);
|
const stats = ref(null);
|
||||||
|
|
||||||
const loadStats = async () => {
|
const loadStats = async () => {
|
||||||
const response = await apiGet('getMegaPayPulseStats');
|
const response = await apiGet('getTeleCartPulseStats');
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
stats.value = response.data;
|
stats.value = response.data;
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.megapay-pulse-info {
|
.telecart-pulse-info {
|
||||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
border: 1px solid #dee2e6;
|
border: 1px solid #dee2e6;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -130,7 +130,7 @@ onMounted(() => {
|
|||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.megapay-pulse-info h3 {
|
.telecart-pulse-info h3 {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
@@ -138,43 +138,43 @@ onMounted(() => {
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.megapay-pulse-info h3 a {
|
.telecart-pulse-info h3 a {
|
||||||
color: #dc3545;
|
color: #dc3545;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.megapay-pulse-info h3 a:hover {
|
.telecart-pulse-info h3 a:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.megapay-pulse-info p {
|
.telecart-pulse-info p {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
color: #495057;
|
color: #495057;
|
||||||
}
|
}
|
||||||
|
|
||||||
.megapay-pulse-info p:last-child {
|
.telecart-pulse-info p:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.megapay-pulse-info p strong {
|
.telecart-pulse-info p strong {
|
||||||
color: #212529;
|
color: #212529;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.megapay-pulse-info ul {
|
.telecart-pulse-info ul {
|
||||||
margin: 16px 0;
|
margin: 16px 0;
|
||||||
padding-left: 24px;
|
padding-left: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.megapay-pulse-info ul li {
|
.telecart-pulse-info ul li {
|
||||||
color: #495057;
|
color: #495057;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.megapay-pulse-info ul li:last-child {
|
.telecart-pulse-info ul li:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ export default defineConfig({
|
|||||||
build: {
|
build: {
|
||||||
manifest: false,
|
manifest: false,
|
||||||
sourcemap: false,
|
sourcemap: false,
|
||||||
outDir: '../../module/oc_telegram_shop/upload/admin/view/javascript/megapay',
|
outDir: '../../module/oc_telegram_shop/upload/admin/view/javascript/telecart',
|
||||||
emptyOutDir: true, // also necessary
|
emptyOutDir: true, // also necessary
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
megapay: '/src/main.js',
|
telecart: '/src/main.js',
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
entryFileNames: `[name].js`,
|
entryFileNames: `[name].js`,
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg"/>
|
<link rel="icon" type="image/svg+xml" href="/vite.svg"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
||||||
<title>MegaPay</title>
|
<title>TeleCart</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<div id="app-error"></div>
|
<div id="app-error"></div>
|
||||||
<script src="https://telegram.org/js/telegram-web-app.js?59"></script>
|
<script src="https://telegram.org/js/telegram-web-app.js?61"></script>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
1786
frontend/spa/package-lock.json
generated
1786
frontend/spa/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -28,18 +28,19 @@
|
|||||||
"vue-router": "^4.6.3"
|
"vue-router": "^4.6.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
"@testing-library/vue": "^8.1.0",
|
"@testing-library/vue": "^8.1.0",
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"@vitest/ui": "^4.0.8",
|
"@vitest/ui": "^4.1.9",
|
||||||
"@vue/test-utils": "^2.4.6",
|
"@vue/test-utils": "^2.4.6",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"daisyui": "^5.3.10",
|
"daisyui": "^5.3.10",
|
||||||
"jsdom": "^27.1.0",
|
"jsdom": "^27.1.0",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.15",
|
||||||
"tailwindcss": "^4.1.16",
|
"tailwindcss": "^4.1.16",
|
||||||
"terser": "^5.44.0",
|
"terser": "^5.44.0",
|
||||||
"vite": "^7.1.12",
|
"vite": "^7.3.3",
|
||||||
"vitest": "^4.0.8"
|
"vitest": "^4.1.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
frontend/spa/public/vite.svg
Normal file
1
frontend/spa/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -2,16 +2,16 @@
|
|||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
:key="category.id"
|
:key="category.id"
|
||||||
class="flex items-center"
|
class="flex items-center gap-3"
|
||||||
@click.prevent="$emit('onSelect', category)"
|
@click.prevent="$emit('onSelect', category)"
|
||||||
>
|
>
|
||||||
<div class="avatar">
|
<div class="avatar shrink-0">
|
||||||
<div class="w-8 h-8 rounded">
|
<div class="w-10 h-10 rounded">
|
||||||
<img :src="category.image" :alt="category.name" loading="lazy" width="30" height="30" class="bg-base-400"/>
|
<img :src="category.image" :alt="category.name" loading="lazy" width="40" height="40" class="bg-base-400"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="ml-4 text-lg line-clamp-2">{{ category.name }}</h3>
|
<h3 class="text-base font-medium leading-snug line-clamp-2">{{ category.name }}</h3>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="px-4">
|
<section class="px-4">
|
||||||
<header class="flex justify-between items-end mb-4">
|
<header class="flex justify-between items-end mb-4 gap-2">
|
||||||
<div>
|
<div class="min-w-0">
|
||||||
<div v-if="title" class="font-bold uppercase">{{ title }}</div>
|
<div v-if="title" class="font-bold uppercase">{{ title }}</div>
|
||||||
<div v-if="description" class="text-sm text-base-content/50">{{ description }}</div>
|
<div v-if="description" class="text-sm text-base-content/50">{{ description }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="moreLink">
|
<div v-if="moreLink" class="shrink-0">
|
||||||
<RouterLink :to="moreLink" class="btn btn-soft btn-xs" @click="haptic.selectionChanged">
|
<RouterLink :to="moreLink" class="btn btn-soft btn-xs whitespace-nowrap" @click="haptic.selectionChanged">
|
||||||
{{ moreText || 'Смотреть всё' }}
|
{{ moreText || 'Смотреть всё' }}
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-4">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
|
||||||
|
|||||||
@@ -2,10 +2,23 @@
|
|||||||
<BaseBlock
|
<BaseBlock
|
||||||
:title="block.title"
|
:title="block.title"
|
||||||
:description="block.description"
|
:description="block.description"
|
||||||
:moreLink="{name: 'product.categories.show', params: { category_id: block.data.category_id }}"
|
:moreLink="hasCategory ? {name: 'product.categories.show', params: { category_id: block.data.category_id }} : null"
|
||||||
:moreText="block.data.all_text"
|
:moreText="block.data.all_text"
|
||||||
>
|
>
|
||||||
|
<div v-if="!hasCategory" role="alert" class="alert alert-warning alert-soft">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||||
|
</svg>
|
||||||
|
<span>Для блока не выбрана категория.</span>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="!hasProducts" role="alert" class="alert alert-soft">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<span>В этой категории пока нет товаров.</span>
|
||||||
|
</div>
|
||||||
<Swiper
|
<Swiper
|
||||||
|
v-else
|
||||||
class="select-none block-products-carousel"
|
class="select-none block-products-carousel"
|
||||||
:slides-per-view="block.data?.carousel?.slides_per_view || 2.5"
|
:slides-per-view="block.data?.carousel?.slides_per_view || 2.5"
|
||||||
:space-between="block.data?.carousel?.space_between || 20"
|
:space-between="block.data?.carousel?.space_between || 20"
|
||||||
@@ -36,6 +49,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import {computed} from "vue";
|
||||||
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
import {useYaMetrikaStore} from "@/stores/yaMetrikaStore.js";
|
||||||
import {Swiper, SwiperSlide} from "swiper/vue";
|
import {Swiper, SwiperSlide} from "swiper/vue";
|
||||||
import {useHapticScroll} from "@/composables/useHapticScroll.js";
|
import {useHapticScroll} from "@/composables/useHapticScroll.js";
|
||||||
@@ -52,6 +66,9 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const hasCategory = computed(() => Boolean(props.block.data?.category_id));
|
||||||
|
const hasProducts = computed(() => (props.block.data?.products?.data?.length ?? 0) > 0);
|
||||||
|
|
||||||
const freeModeSettings = {
|
const freeModeSettings = {
|
||||||
enabled: props.block.data?.carousel?.freemode?.enabled || false,
|
enabled: props.block.data?.carousel?.freemode?.enabled || false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
На главной странице не сконфигурировано ни одного блока для отображения.
|
На главной странице не сконфигурировано ни одного блока для отображения.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm text-base-content/70 max-w-md">
|
<p class="text-sm text-base-content/70 max-w-md">
|
||||||
Перейдите в настройки модуля <span class="font-semibold">MegaPay</span> и добавьте блоки на главную страницу.
|
Перейдите в настройки модуля <span class="font-semibold">TeleCart</span> и добавьте блоки на главную страницу.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-6 p-4 bg-base-200 rounded-lg max-w-md">
|
<div class="mt-6 p-4 bg-base-200 rounded-lg max-w-md">
|
||||||
<div class="flex items-start gap-3">
|
<div class="flex items-start gap-3">
|
||||||
|
|||||||
@@ -2,10 +2,19 @@
|
|||||||
|
|
||||||
@custom-variant dark (&:where(.dark, .dark *));
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
|
@plugin "@tailwindcss/typography";
|
||||||
|
|
||||||
@plugin "daisyui" {
|
@plugin "daisyui" {
|
||||||
themes: all;
|
themes: all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.prose :where(table):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
--color-base-100 - DaisyUI background
|
--color-base-100 - DaisyUI background
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -9,31 +9,22 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<button v-if="parentId && parentCategory" class="py-1 px-4 flex items-center mb-3 cursor-pointer" @click="goBack">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6 min-w-6">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<span class="ml-2 line-clamp-2">Назад к "{{ parentCategory.name }}"</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
v-if="parentCategory && parentCategory.children?.length && settings.show_category_products_button"
|
v-if="parentCategory && parentCategory.children?.length && settings.show_category_products_button"
|
||||||
class="py-2 px-4 flex items-center mb-3 cursor-pointer border-b w-full pb-2 border-base-200"
|
class="btn btn-soft btn-block btn-sm mb-3 justify-start"
|
||||||
@click.prevent="showProductsInParentCategory"
|
@click.prevent="showProductsInParentCategory"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-5 shrink-0">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6ZM3.75 15.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6ZM13.5 15.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25A2.25 2.25 0 0 1 13.5 18v-2.25Z" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6ZM3.75 15.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6ZM13.5 15.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25A2.25 2.25 0 0 1 13.5 18v-2.25Z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
<span>Показать все товары категории</span>
|
||||||
<span class="ml-2">Показать товары из "{{ parentCategory.name }}"</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<TransitionGroup
|
<TransitionGroup
|
||||||
name="stagger"
|
name="stagger"
|
||||||
tag="ul"
|
tag="ul"
|
||||||
appear
|
appear
|
||||||
class="space-y-4"
|
class="space-y-3"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
v-for="(category, i) in categories"
|
v-for="(category, i) in categories"
|
||||||
@@ -99,10 +90,6 @@ function onSelect(category) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function goBack() {
|
|
||||||
router.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
function showProductsInParentCategory() {
|
function showProductsInParentCategory() {
|
||||||
if (parentId.value != null) {
|
if (parentId.value != null) {
|
||||||
router.push({name: "product.categories.show", params: {category_id: parentId.value}});
|
router.push({name: "product.categories.show", params: {category_id: parentId.value}});
|
||||||
|
|||||||
@@ -87,7 +87,11 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Описание
|
Описание
|
||||||
</h3>
|
</h3>
|
||||||
<div class="prose prose-sm max-w-none text-base-content/80" v-html="product.description"></div>
|
<div
|
||||||
|
class="prose prose-sm max-w-none text-base-content/80"
|
||||||
|
v-html="product.description"
|
||||||
|
@click="handleDescriptionClick"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Attributes -->
|
<!-- Attributes -->
|
||||||
@@ -306,6 +310,29 @@ async function onCartBtnClick() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleDescriptionClick(event) {
|
||||||
|
const link = event.target.closest('a');
|
||||||
|
if (!link) return;
|
||||||
|
|
||||||
|
const href = link.getAttribute('href');
|
||||||
|
if (!href || href.startsWith('#') || href.startsWith('javascript:')) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const telegramWebApp = window.Telegram?.WebApp;
|
||||||
|
if (!telegramWebApp) {
|
||||||
|
window.open(href, '_blank', 'noopener,noreferrer');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^https?:\/\/(t|telegram)\.me\//i.test(href) || href.startsWith('tg://')) {
|
||||||
|
telegramWebApp.openTelegramLink(href);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
telegramWebApp.openLink(href, {try_instant_view: false});
|
||||||
|
}
|
||||||
|
|
||||||
function openProductInMarketplace() {
|
function openProductInMarketplace() {
|
||||||
if (!product.value.share) {
|
if (!product.value.share) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
30
module/oc_telegram_shop/install.xml
Normal file
30
module/oc_telegram_shop/install.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<modification>
|
||||||
|
<name>TeleCart</name>
|
||||||
|
<code>telecart</code>
|
||||||
|
<version>1.0</version>
|
||||||
|
<author>TeleCart</author>
|
||||||
|
<link>https://docs.telecart.pro/</link>
|
||||||
|
<file path="admin/controller/common/column_left.php">
|
||||||
|
<operation>
|
||||||
|
<search><![CDATA[// System]]></search>
|
||||||
|
<add position="before"><![CDATA[ // BEGIN TeleCart modification
|
||||||
|
$telecart = array();
|
||||||
|
$telecart[] = array(
|
||||||
|
'name' => 'Настройки',
|
||||||
|
'href' => $this->url->link('extension/module/tgshop', 'user_token=' . $this->session->data['user_token'], true),
|
||||||
|
'children' => array()
|
||||||
|
);
|
||||||
|
$data['menus'][] = array(
|
||||||
|
'id' => 'menu-telecart',
|
||||||
|
'icon' => 'fa-telegram',
|
||||||
|
'name' => 'TeleCart',
|
||||||
|
'href' => '',
|
||||||
|
'children' => $telecart
|
||||||
|
);
|
||||||
|
// END TeleCart modification
|
||||||
|
|
||||||
|
]]></add>
|
||||||
|
</operation>
|
||||||
|
</file>
|
||||||
|
</modification>
|
||||||
282
module/oc_telegram_shop/upload/admin/controller/extension/module/tgshop.php
Executable file
282
module/oc_telegram_shop/upload/admin/controller/extension/module/tgshop.php
Executable file
@@ -0,0 +1,282 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Bastion\ApplicationFactory;
|
||||||
|
use Cart\User;
|
||||||
|
use Monolog\Handler\RotatingFileHandler;
|
||||||
|
use Monolog\Logger;
|
||||||
|
use Openguru\OpenCartFramework\Application;
|
||||||
|
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
|
||||||
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
$sysLibPath = rtrim(DIR_SYSTEM, '/') . '/library/oc_telegram_shop';
|
||||||
|
$basePath = rtrim(DIR_APPLICATION, '/') . '/..';
|
||||||
|
if (is_readable($sysLibPath . '/oc_telegram_shop.phar')) {
|
||||||
|
require_once "phar://{$sysLibPath}/oc_telegram_shop.phar/vendor/autoload.php";
|
||||||
|
} elseif (is_dir("$basePath/oc_telegram_shop")) {
|
||||||
|
require_once "$basePath/oc_telegram_shop/vendor/autoload.php";
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException('Unable to locate application directory.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property Document $document
|
||||||
|
* @property Loader $load
|
||||||
|
* @property Config $config
|
||||||
|
* @property Language $language
|
||||||
|
* @property Request $request
|
||||||
|
* @property Session $session
|
||||||
|
* @property ModelSettingSetting $model_setting_setting
|
||||||
|
* @property Response $response
|
||||||
|
* @property Url $url
|
||||||
|
* @property ModelToolImage $model_tool_image
|
||||||
|
* @property ModelCatalogProduct $model_catalog_product
|
||||||
|
* @property User $user
|
||||||
|
* @property ModelCustomerCustomerGroup $model_customer_customer_group
|
||||||
|
* @property ModelLocalisationOrderStatus $model_localisation_order_status
|
||||||
|
* @property ModelDesignBanner $model_design_banner
|
||||||
|
* @property DB $db
|
||||||
|
* @property Log $log
|
||||||
|
*/
|
||||||
|
class ControllerExtensionModuleTgshop extends Controller
|
||||||
|
{
|
||||||
|
private static array $themes = [
|
||||||
|
'light' => 'Светлая (light)',
|
||||||
|
'dark' => 'Тёмная (dark)',
|
||||||
|
'cupcake' => 'Капкейк (cupcake)',
|
||||||
|
'bumblebee' => 'Шмель (bumblebee)',
|
||||||
|
'emerald' => 'Изумруд (emerald)',
|
||||||
|
'corporate' => 'Корпоративная (corporate)',
|
||||||
|
'synthwave' => 'Синтвейв (synthwave)',
|
||||||
|
'retro' => 'Ретро (retro)',
|
||||||
|
'cyberpunk' => 'Киберпанк (cyberpunk)',
|
||||||
|
'valentine' => 'Валентинка (valentine)',
|
||||||
|
'halloween' => 'Хэллоуин (halloween)',
|
||||||
|
'garden' => 'Сад (garden)',
|
||||||
|
'forest' => 'Лес (forest)',
|
||||||
|
'aqua' => 'Аква (aqua)',
|
||||||
|
'lofi' => 'Лоу-фай (lofi)',
|
||||||
|
'pastel' => 'Пастель (pastel)',
|
||||||
|
'fantasy' => 'Фэнтези (fantasy)',
|
||||||
|
'wireframe' => 'Каркас (wireframe)',
|
||||||
|
'black' => 'Чёрная (black)',
|
||||||
|
'luxury' => 'Люкс (luxury)',
|
||||||
|
'dracula' => 'Дракула (dracula)',
|
||||||
|
'cmyk' => 'CMYK (cmyk)',
|
||||||
|
'autumn' => 'Осень (autumn)',
|
||||||
|
'business' => 'Бизнес (business)',
|
||||||
|
'acid' => 'Кислотная (acid)',
|
||||||
|
'lemonade' => 'Лимонад (lemonade)',
|
||||||
|
'night' => 'Ночная (night)',
|
||||||
|
'coffee' => 'Кофейная (coffee)',
|
||||||
|
'winter' => 'Зимняя (winter)',
|
||||||
|
'dim' => 'Тусклая (dim)',
|
||||||
|
'nord' => 'Нордическая (nord)',
|
||||||
|
'sunset' => 'Закат (sunset)',
|
||||||
|
'caramellatte' => 'Карамель-латте (caramellatte)',
|
||||||
|
'abyss' => 'Бездна (abyss)',
|
||||||
|
'silk' => 'Шёлк (silk)',
|
||||||
|
];
|
||||||
|
|
||||||
|
private array $error = [];
|
||||||
|
|
||||||
|
public function __construct($registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry);
|
||||||
|
|
||||||
|
$this->load->language('extension/module/tgshop');
|
||||||
|
$this->load->model('setting/setting');
|
||||||
|
$this->load->model('tool/image');
|
||||||
|
$this->load->model('catalog/product');
|
||||||
|
$this->load->model('catalog/category');
|
||||||
|
$this->load->model('localisation/order_status');
|
||||||
|
$this->load->model('customer/customer_group');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(): void
|
||||||
|
{
|
||||||
|
$this->runMaintenanceTasks();
|
||||||
|
$this->injectAssets();
|
||||||
|
$this->injectVueJs();
|
||||||
|
$this->showConfigPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function showConfigPage(): void
|
||||||
|
{
|
||||||
|
$data = [];
|
||||||
|
$this->document->setTitle($this->language->get('heading_title'));
|
||||||
|
|
||||||
|
$this->baseData($data);
|
||||||
|
|
||||||
|
$data['order_statuses'] = $this->getOrderStatuses();
|
||||||
|
$data['customer_groups'] = $this->getCustomerGroups();
|
||||||
|
$data['themes'] = self::$themes;
|
||||||
|
$data['telecart_module_version'] = module_version();
|
||||||
|
$data['shop_base_url'] = HTTPS_CATALOG;
|
||||||
|
|
||||||
|
$data['action'] = $this->url->link(
|
||||||
|
'extension/module/tgshop',
|
||||||
|
'user_token=' . $this->session->data['user_token'],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->response->setOutput($this->load->view('extension/module/tgshop', $data));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
$logger = $this->createLogger();
|
||||||
|
|
||||||
|
$this
|
||||||
|
->createApplication($logger)
|
||||||
|
->bootAndHandleRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function validate(): bool
|
||||||
|
{
|
||||||
|
if (! $this->user->hasPermission('modify', 'extension/module/tgshop')) {
|
||||||
|
$this->error['telecart_error_warning'] = $this->language->get('error_permission');
|
||||||
|
}
|
||||||
|
|
||||||
|
return ! $this->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function baseData(array &$data): void
|
||||||
|
{
|
||||||
|
$this->document->setTitle($this->language->get('heading_title'));
|
||||||
|
|
||||||
|
$data['header'] = $this->load->controller('common/header');
|
||||||
|
$data['column_left'] = $this->load->controller('common/column_left');
|
||||||
|
$data['footer'] = $this->load->controller('common/footer');
|
||||||
|
|
||||||
|
$data['cancel'] = $this->url->link(
|
||||||
|
'marketplace/extension',
|
||||||
|
'user_token=' . $this->session->data['user_token'] . '&type=module',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
$data = array_merge($data, $this->error);
|
||||||
|
|
||||||
|
if (isset($this->session->data['success'])) {
|
||||||
|
$data['success'] = $this->session->data['success'];
|
||||||
|
|
||||||
|
unset($this->session->data['success']);
|
||||||
|
} else {
|
||||||
|
$data['success'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$data['user_token'] = $this->session->data['user_token'];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCustomerGroups(): array
|
||||||
|
{
|
||||||
|
$map = [];
|
||||||
|
|
||||||
|
foreach ($this->model_customer_customer_group->getCustomerGroups() as $customer_group) {
|
||||||
|
$map[(int) $customer_group['customer_group_id']] = $customer_group['name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getOrderStatuses(): array
|
||||||
|
{
|
||||||
|
$statuses = $this->model_localisation_order_status->getOrderStatuses();
|
||||||
|
$map = [];
|
||||||
|
|
||||||
|
foreach ($statuses as $item) {
|
||||||
|
$map[(int) $item['order_status_id']] = $item['name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function injectVueJs(): void
|
||||||
|
{
|
||||||
|
$appDir = rtrim(DIR_APPLICATION, '/');
|
||||||
|
if (file_exists("$appDir/view/javascript/telecart/telecart.js")) {
|
||||||
|
$hash = file_get_contents($appDir . '/view/javascript/telecart/checksum.hash');
|
||||||
|
$this->document->addScript('view/javascript/telecart/telecart.js?v=' . $hash);
|
||||||
|
$this->document->addStyle('view/javascript/telecart/telecart.css?v=' . $hash);
|
||||||
|
} elseif (file_exists("$appDir/view/integration.js")) {
|
||||||
|
$this->document->addScript('view/integration.js');
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException('Unable to load Vuejs frontend.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createApplication(LoggerInterface $logger): Application
|
||||||
|
{
|
||||||
|
$json = $this->model_setting_setting->getSetting('module_telecart');
|
||||||
|
if (! isset($json['module_telecart_settings'])) {
|
||||||
|
$json['module_telecart_settings'] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = Arr::mergeArraysRecursively($json['module_telecart_settings'], [
|
||||||
|
'app' => [
|
||||||
|
'shop_base_url' => HTTPS_CATALOG, // for catalog: HTTPS_SERVER, for admin: HTTPS_CATALOG
|
||||||
|
'language_id' => (int) $this->config->get('config_language_id'),
|
||||||
|
],
|
||||||
|
'paths' => [
|
||||||
|
'images' => DIR_IMAGE,
|
||||||
|
],
|
||||||
|
'logs' => [
|
||||||
|
'path' => DIR_LOGS,
|
||||||
|
],
|
||||||
|
'database' => [
|
||||||
|
'host' => DB_HOSTNAME,
|
||||||
|
'database' => DB_DATABASE,
|
||||||
|
'username' => DB_USERNAME,
|
||||||
|
'password' => DB_PASSWORD,
|
||||||
|
'prefix' => DB_PREFIX,
|
||||||
|
'port' => (int) DB_PORT,
|
||||||
|
],
|
||||||
|
'store' => [
|
||||||
|
'oc_store_id' => 0,
|
||||||
|
'oc_default_currency' => $this->config->get('config_currency'),
|
||||||
|
'oc_config_tax' => filter_var($this->config->get('config_tax'), FILTER_VALIDATE_BOOLEAN),
|
||||||
|
],
|
||||||
|
'orders' => [
|
||||||
|
'oc_customer_group_id' => (int) $this->config->get('config_customer_group_id'),
|
||||||
|
],
|
||||||
|
'telegram' => [
|
||||||
|
'mini_app_url' => rtrim(HTTPS_CATALOG, '/') . '/image/catalog/tgshopspa/#/',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$app = ApplicationFactory::create($items);
|
||||||
|
$app->bind(OcRegistryDecorator::class, fn() => new OcRegistryDecorator($this->registry));
|
||||||
|
$app->setLogger($logger);
|
||||||
|
|
||||||
|
return $app;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function runMaintenanceTasks(): void
|
||||||
|
{
|
||||||
|
$logger = $this->createLogger();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->createApplication($logger)->runMaintenanceTasks();
|
||||||
|
} catch (Throwable $exception) {
|
||||||
|
$logger->error($exception->getMessage(), ['exception' => $exception]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createLogger(bool $debug = false): Logger
|
||||||
|
{
|
||||||
|
$log = new Logger('TeleCart_Admin', [], [], new DateTimeZone('UTC'));
|
||||||
|
$log->pushHandler(
|
||||||
|
new RotatingFileHandler(
|
||||||
|
DIR_LOGS . '/telecart.log', 14, $debug ? Logger::DEBUG : Logger::INFO
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $log;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function injectAssets(): void
|
||||||
|
{
|
||||||
|
$this->document->addScript('view/javascript/summernote/summernote.js');
|
||||||
|
$this->document->addStyle('view/javascript/summernote/summernote.css');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Heading
|
||||||
|
$_['heading_title'] = 'TeleCart';
|
||||||
|
|
||||||
|
$_['text_module'] = 'Модули';
|
||||||
|
$_['text_success'] = 'Настройки успешно изменены!';
|
||||||
|
$_['text_edit'] = 'Настройки';
|
||||||
|
|
||||||
|
// Entry
|
||||||
|
$_['entry_status'] = 'Статус';
|
||||||
|
|
||||||
|
// Error
|
||||||
|
$_['error_permission'] = 'У вас недостаточно прав для внесения изменений!';
|
||||||
18
module/oc_telegram_shop/upload/admin/view/integration.js
Executable file
18
module/oc_telegram_shop/upload/admin/view/integration.js
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
(function() {
|
||||||
|
function injectVueScript() {
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.type = 'module';
|
||||||
|
//if (window.vueIntegration && window.vueIntegration.developmentEnv) {
|
||||||
|
script.src = 'http://localhost:3000/src/main.js';
|
||||||
|
//} else {
|
||||||
|
// script.src = '/path/to/built/main.js';
|
||||||
|
//}
|
||||||
|
document.body.appendChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', injectVueScript);
|
||||||
|
} else { // Документ уже загружен
|
||||||
|
injectVueScript();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{{ header }}{{ column_left }}
|
||||||
|
<div id="content">
|
||||||
|
<div class="container-fluid" style="margin-top: 10px;">
|
||||||
|
{% if telecart_error_warning %}
|
||||||
|
<div class="alert alert-danger alert-dismissible"><i
|
||||||
|
class="fa fa-exclamation-circle"></i> {{ telecart_error_warning }}
|
||||||
|
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if success %}
|
||||||
|
<div class="alert alert-success alert-dismissible"><i class="fa fa-check-circle"></i> {{ success }}
|
||||||
|
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-body">
|
||||||
|
<script>
|
||||||
|
window.TeleCart = {
|
||||||
|
user_token: '{{ user_token }}',
|
||||||
|
themes: '{{ themes | json_encode }}',
|
||||||
|
order_statuses: '{{ order_statuses | json_encode }}',
|
||||||
|
shop_base_url: '{{ shop_base_url }}',
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<div id="app" class="telecart-admin-app">App Loading...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted text-right" style="margin-top: 10px; font-size: 12px;">
|
||||||
|
Версия модуля: {{ telecart_module_version }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="view/javascript/summernote/summernote.js"></script>
|
||||||
|
<link href="view/javascript/summernote/summernote.css" rel="stylesheet" />
|
||||||
|
{{ footer }}
|
||||||
137
module/oc_telegram_shop/upload/catalog/controller/extension/tgshop/handle.php
Executable file
137
module/oc_telegram_shop/upload/catalog/controller/extension/tgshop/handle.php
Executable file
@@ -0,0 +1,137 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Adapters\OcModelCatalogProductAdapter;
|
||||||
|
use App\ApplicationFactory;
|
||||||
|
use Cart\Cart;
|
||||||
|
use Cart\Currency;
|
||||||
|
use Cart\Tax;
|
||||||
|
use Monolog\Handler\RotatingFileHandler;
|
||||||
|
use Monolog\Logger;
|
||||||
|
use Openguru\OpenCartFramework\OpenCart\Currency as TelecartCurrency;
|
||||||
|
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
|
||||||
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
$sysLibPath = rtrim(DIR_SYSTEM, '/') . '/library/oc_telegram_shop';
|
||||||
|
$basePath = rtrim(DIR_APPLICATION, '/') . '/..';
|
||||||
|
if (is_readable($sysLibPath . '/oc_telegram_shop.phar')) {
|
||||||
|
require_once "phar://{$sysLibPath}/oc_telegram_shop.phar/vendor/autoload.php";
|
||||||
|
} elseif (is_dir("$basePath/oc_telegram_shop")) {
|
||||||
|
require_once "$basePath/oc_telegram_shop/vendor/autoload.php";
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException('Unable to locate application directory.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property Config $config
|
||||||
|
* @property Log $log
|
||||||
|
*/
|
||||||
|
class ControllerExtensionTgshopHandle extends Controller
|
||||||
|
{
|
||||||
|
public function __construct($registry)
|
||||||
|
{
|
||||||
|
parent::__construct($registry);
|
||||||
|
|
||||||
|
$this->load->model('catalog/product');
|
||||||
|
$this->load->model('checkout/order');
|
||||||
|
$this->load->model('setting/setting');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index(): void
|
||||||
|
{
|
||||||
|
$this->session->data['language'] = $this->config->get('config_language');
|
||||||
|
|
||||||
|
$json = $this->model_setting_setting->getSetting('module_telecart');
|
||||||
|
if (! isset($json['module_telecart_settings'])) {
|
||||||
|
$json['module_telecart_settings'] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = Arr::mergeArraysRecursively($json['module_telecart_settings'], [
|
||||||
|
'app' => [
|
||||||
|
'shop_base_url' => HTTPS_SERVER, // for catalog: HTTPS_SERVER, for admin: HTTPS_CATALOG
|
||||||
|
'language_id' => (int)$this->config->get('config_language_id'),
|
||||||
|
'oc_timezone' => $this->config->get('config_timezone'),
|
||||||
|
],
|
||||||
|
'paths' => [
|
||||||
|
'images' => DIR_IMAGE,
|
||||||
|
],
|
||||||
|
'logs' => [
|
||||||
|
'path' => DIR_LOGS,
|
||||||
|
],
|
||||||
|
'database' => [
|
||||||
|
'host' => DB_HOSTNAME,
|
||||||
|
'database' => DB_DATABASE,
|
||||||
|
'username' => DB_USERNAME,
|
||||||
|
'password' => DB_PASSWORD,
|
||||||
|
'prefix' => DB_PREFIX,
|
||||||
|
'port' => (int)DB_PORT,
|
||||||
|
],
|
||||||
|
'store' => [
|
||||||
|
'oc_store_id' => 0,
|
||||||
|
'oc_default_currency' => $this->config->get('config_currency'),
|
||||||
|
'oc_config_tax' => filter_var($this->config->get('config_tax'), FILTER_VALIDATE_BOOLEAN),
|
||||||
|
],
|
||||||
|
'orders' => [
|
||||||
|
'oc_customer_group_id' => (int)$this->config->get('config_customer_group_id'),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$appDebug = Arr::get($items, 'app.app_debug', false);
|
||||||
|
|
||||||
|
$app = ApplicationFactory::create($items);
|
||||||
|
|
||||||
|
$app->bind(OcModelCatalogProductAdapter::class, function () {
|
||||||
|
return new OcModelCatalogProductAdapter($this->model_catalog_product);
|
||||||
|
});
|
||||||
|
|
||||||
|
$app->bind(Url::class, fn() => $this->url);
|
||||||
|
$app->bind(Currency::class, fn() => new TelecartCurrency($this->registry));
|
||||||
|
$app->bind(Tax::class, fn() => $this->tax);
|
||||||
|
$app->bind(Cart::class, fn() => $this->cart);
|
||||||
|
$app->bind(OcRegistryDecorator::class, fn() => new OcRegistryDecorator($this->registry));
|
||||||
|
$app->singleton(Log::class, fn() => $this->log);
|
||||||
|
|
||||||
|
$app
|
||||||
|
->withLogger(fn($app) => $this->createLogger($appDebug))
|
||||||
|
->bootAndHandleRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @noinspection PhpUnused */
|
||||||
|
public function ya_metrika(): void
|
||||||
|
{
|
||||||
|
$json = $this->model_setting_setting->getSetting('module_telecart');
|
||||||
|
if (isset($json['module_telecart_settings'])) {
|
||||||
|
$yaCounterId = Arr::get($json, 'module_telecart_settings.metrics.yandex_metrika_counter');
|
||||||
|
$raw = <<<JS
|
||||||
|
(function(m,e,t,r,i,k,a){
|
||||||
|
m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
||||||
|
m[i].l=1*new Date();
|
||||||
|
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
|
||||||
|
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
|
||||||
|
})(window, document,'script','https://mc.yandex.ru/metrika/tag.js?id={$yaCounterId}', 'ym');
|
||||||
|
|
||||||
|
ym({$yaCounterId}, 'init', {ssr: true, defer: true, ecommerce:"dataLayer", accurateTrackBounce:true, trackLinks:true});
|
||||||
|
JS;
|
||||||
|
|
||||||
|
http_response_code(200);
|
||||||
|
header('Content-Type: application/javascript');
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
header('Access-Control-Allow-Methods: GET, POST');
|
||||||
|
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
||||||
|
header('Access-Control-Allow-Credentials: true');
|
||||||
|
echo $raw;
|
||||||
|
} else {
|
||||||
|
http_response_code(404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createLogger(bool $appDebug = false, string $app = 'TeleCart'): LoggerInterface
|
||||||
|
{
|
||||||
|
$log = new Logger($app, [], [], new DateTimeZone('UTC'));
|
||||||
|
$log->pushHandler(
|
||||||
|
new RotatingFileHandler(DIR_LOGS . '/telecart.log', 14, $appDebug ? Logger::DEBUG : Logger::INFO),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $log;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ use Console\ApplicationFactory;
|
|||||||
use Console\Commands\CacheClearCommand;
|
use Console\Commands\CacheClearCommand;
|
||||||
use Console\Commands\CustomerCountsCommand;
|
use Console\Commands\CustomerCountsCommand;
|
||||||
use Console\Commands\PulseSendEventsCommand;
|
use Console\Commands\PulseSendEventsCommand;
|
||||||
|
use Console\Commands\ScheduleListCommand;
|
||||||
use Console\Commands\ScheduleRunCommand;
|
use Console\Commands\ScheduleRunCommand;
|
||||||
use Console\Commands\VersionCommand;
|
use Console\Commands\VersionCommand;
|
||||||
use Console\Commands\ImagesWarmupCacheCommand;
|
use Console\Commands\ImagesWarmupCacheCommand;
|
||||||
@@ -20,7 +21,7 @@ if (PHP_SAPI !== 'cli') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$baseDir = __DIR__;
|
$baseDir = __DIR__;
|
||||||
$debug = false;
|
$debug = true;
|
||||||
|
|
||||||
if (is_readable($baseDir . '/oc_telegram_shop.phar')) {
|
if (is_readable($baseDir . '/oc_telegram_shop.phar')) {
|
||||||
require_once "phar://{$baseDir}/oc_telegram_shop.phar/vendor/autoload.php";
|
require_once "phar://{$baseDir}/oc_telegram_shop.phar/vendor/autoload.php";
|
||||||
@@ -32,6 +33,8 @@ if (is_readable($baseDir . '/oc_telegram_shop.phar')) {
|
|||||||
throw new RuntimeException('Unable to locate application directory.');
|
throw new RuntimeException('Unable to locate application directory.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
date_default_timezone_set('UTC');
|
||||||
|
|
||||||
// Get Settings from Database
|
// Get Settings from Database
|
||||||
$host = DB_HOSTNAME;
|
$host = DB_HOSTNAME;
|
||||||
$username = DB_USERNAME;
|
$username = DB_USERNAME;
|
||||||
@@ -42,15 +45,12 @@ $prefix = DB_PREFIX;
|
|||||||
$dsn = "mysql:host=$host;port=$port;dbname=$dbName";
|
$dsn = "mysql:host=$host;port=$port;dbname=$dbName";
|
||||||
$pdo = new PDO($dsn, $username, $password);
|
$pdo = new PDO($dsn, $username, $password);
|
||||||
$connection = new MySqlConnection($pdo);
|
$connection = new MySqlConnection($pdo);
|
||||||
$raw = $connection->select("SELECT value FROM `{$prefix}setting` WHERE `key` = 'module_megapay_settings'");
|
$raw = $connection->select("SELECT value FROM `{$prefix}setting` WHERE `key` = 'module_telecart_settings'");
|
||||||
$timezone = $connection->select("SELECT value FROM `{$prefix}setting` WHERE `key` = 'config_timezone'");
|
|
||||||
$timezone = $timezone[0]['value'] ?? 'UTC';
|
|
||||||
$json = json_decode($raw[0]['value'], true, 512, JSON_THROW_ON_ERROR);
|
$json = json_decode($raw[0]['value'], true, 512, JSON_THROW_ON_ERROR);
|
||||||
$items = Arr::mergeArraysRecursively($json, [
|
$items = Arr::mergeArraysRecursively($json, [
|
||||||
'app' => [
|
'app' => [
|
||||||
'shop_base_url' => HTTPS_CATALOG, // for catalog: HTTPS_SERVER, for admin: HTTPS_CATALOG
|
'shop_base_url' => HTTPS_CATALOG, // for catalog: HTTPS_SERVER, for admin: HTTPS_CATALOG
|
||||||
'language_id' => 1,
|
'language_id' => 1,
|
||||||
'oc_timezone' => $timezone,
|
|
||||||
],
|
],
|
||||||
'paths' => [
|
'paths' => [
|
||||||
'images' => DIR_IMAGE,
|
'images' => DIR_IMAGE,
|
||||||
@@ -79,20 +79,24 @@ $items = Arr::mergeArraysRecursively($json, [
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$logger = new Logger('MegaPay_CLI', [], [], new DateTimeZone('UTC'));
|
// Create logger
|
||||||
|
$logger = new Logger('TeleCart_CLI', [], [], new DateTimeZone('UTC'));
|
||||||
$logger->pushHandler(
|
$logger->pushHandler(
|
||||||
new RotatingFileHandler(
|
new RotatingFileHandler(
|
||||||
DIR_LOGS . '/megapay.log', 14, $debug ? Logger::DEBUG : Logger::INFO
|
DIR_LOGS . '/telecart.log', 14, $debug ? Logger::DEBUG : Logger::INFO
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Creates TeleCart application.
|
||||||
$app = ApplicationFactory::create($items);
|
$app = ApplicationFactory::create($items);
|
||||||
$app->setLogger($logger);
|
$app->setLogger($logger);
|
||||||
$app->boot();
|
$app->boot();
|
||||||
|
|
||||||
$console = new Application('MegaPay', module_version());
|
// Creates Console and bind commands.
|
||||||
|
$console = new Application('TeleCart', module_version());
|
||||||
$console->add($app->get(VersionCommand::class));
|
$console->add($app->get(VersionCommand::class));
|
||||||
$console->add($app->get(ScheduleRunCommand::class));
|
$console->add($app->get(ScheduleRunCommand::class));
|
||||||
|
$console->add($app->get(ScheduleListCommand::class));
|
||||||
$console->add($app->get(PulseSendEventsCommand::class));
|
$console->add($app->get(PulseSendEventsCommand::class));
|
||||||
$console->add($app->get(ImagesWarmupCacheCommand::class));
|
$console->add($app->get(ImagesWarmupCacheCommand::class));
|
||||||
$console->add($app->get(ImagesCacheClearCommand::class));
|
$console->add($app->get(ImagesCacheClearCommand::class));
|
||||||
8
module/oc_telegram_shop/upload/oc_telegram_shop/.env.example
Executable file
8
module/oc_telegram_shop/upload/oc_telegram_shop/.env.example
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
APP_DEBUG=true
|
||||||
|
PULSE_API_HOST=https://pulse.telecart.pro/api/
|
||||||
|
PULSE_HEARTBEAT_SECRET=c5261f5d-529e-45ad-a69c-9778b755b7cb
|
||||||
|
|
||||||
|
TELECART_CACHE_DRIVER=redis
|
||||||
|
#TELECART_REDIS_HOST=redis
|
||||||
|
#TELECART_REDIS_PORT=6379
|
||||||
|
#TELECART_REDIS_DATABASE=0
|
||||||
7
module/oc_telegram_shop/upload/oc_telegram_shop/.env.production
Executable file
7
module/oc_telegram_shop/upload/oc_telegram_shop/.env.production
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
APP_DEBUG=false
|
||||||
|
PULSE_API_HOST=https://pulse.telecart.pro/api/
|
||||||
|
PULSE_HEARTBEAT_SECRET=c5261f5d-529e-45ad-a69c-9778b755b7cb
|
||||||
|
TELECART_CACHE_DRIVER=mysql
|
||||||
|
TELECART_REDIS_HOST=redis
|
||||||
|
TELECART_REDIS_PORT=6379
|
||||||
|
TELECART_REDIS_DATABASE=0
|
||||||
@@ -12,7 +12,7 @@ use Openguru\OpenCartFramework\ImageTool\ImageToolServiceProvider;
|
|||||||
use Openguru\OpenCartFramework\QueryBuilder\QueryBuilderServiceProvider;
|
use Openguru\OpenCartFramework\QueryBuilder\QueryBuilderServiceProvider;
|
||||||
use Openguru\OpenCartFramework\Router\RouteServiceProvider;
|
use Openguru\OpenCartFramework\Router\RouteServiceProvider;
|
||||||
use Openguru\OpenCartFramework\Support\Arr;
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
use Openguru\OpenCartFramework\MegaPayPulse\MegaPayPulseServiceProvider;
|
use Openguru\OpenCartFramework\TeleCartPulse\TeleCartPulseServiceProvider;
|
||||||
use Openguru\OpenCartFramework\Telegram\TelegramServiceProvider;
|
use Openguru\OpenCartFramework\Telegram\TelegramServiceProvider;
|
||||||
|
|
||||||
class ApplicationFactory
|
class ApplicationFactory
|
||||||
@@ -33,7 +33,7 @@ class ApplicationFactory
|
|||||||
AppServiceProvider::class,
|
AppServiceProvider::class,
|
||||||
CacheServiceProvider::class,
|
CacheServiceProvider::class,
|
||||||
TelegramServiceProvider::class,
|
TelegramServiceProvider::class,
|
||||||
MegaPayPulseServiceProvider::class,
|
TeleCartPulseServiceProvider::class,
|
||||||
ImageToolServiceProvider::class,
|
ImageToolServiceProvider::class,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ class FormsHandler
|
|||||||
}
|
}
|
||||||
|
|
||||||
$form = $this->builder->newQuery()
|
$form = $this->builder->newQuery()
|
||||||
->from('megapay_forms')
|
->from('telecart_forms')
|
||||||
->where('alias', '=', $alias)
|
->where('alias', '=', $alias)
|
||||||
->firstOrNull();
|
->firstOrNull();
|
||||||
|
|
||||||
@@ -198,7 +198,7 @@ class LogsHandler
|
|||||||
|
|
||||||
private function findLastLogsFileInDir(string $dir): ?string
|
private function findLastLogsFileInDir(string $dir): ?string
|
||||||
{
|
{
|
||||||
$files = glob($dir . '/megapay-*.log');
|
$files = glob($dir . '/telecart-*.log');
|
||||||
|
|
||||||
return $files ? end($files) : null;
|
return $files ? end($files) : null;
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ class SendMessageHandler
|
|||||||
* Извлечь ID записи из запроса
|
* Извлечь ID записи из запроса
|
||||||
*
|
*
|
||||||
* @param Request $request HTTP запрос
|
* @param Request $request HTTP запрос
|
||||||
* @return int ID записи в таблице megapay_customers
|
* @return int ID записи в таблице telecart_customers
|
||||||
* @throws RuntimeException Если ID отсутствует или невалиден
|
* @throws RuntimeException Если ID отсутствует или невалиден
|
||||||
*/
|
*/
|
||||||
private function extractCustomerId(Request $request): int
|
private function extractCustomerId(Request $request): int
|
||||||
@@ -4,9 +4,7 @@ namespace Bastion\Handlers;
|
|||||||
|
|
||||||
use Bastion\Exceptions\BotTokenConfiguratorException;
|
use Bastion\Exceptions\BotTokenConfiguratorException;
|
||||||
use Bastion\Services\BotTokenConfigurator;
|
use Bastion\Services\BotTokenConfigurator;
|
||||||
use Bastion\Services\CronApiKeyRegenerator;
|
|
||||||
use Bastion\Services\SettingsService;
|
use Bastion\Services\SettingsService;
|
||||||
use Carbon\Carbon;
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Openguru\OpenCartFramework\Cache\CacheInterface;
|
use Openguru\OpenCartFramework\Cache\CacheInterface;
|
||||||
use Openguru\OpenCartFramework\Config\Settings;
|
use Openguru\OpenCartFramework\Config\Settings;
|
||||||
@@ -15,56 +13,35 @@ use Openguru\OpenCartFramework\Http\Request;
|
|||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
||||||
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
||||||
use Openguru\OpenCartFramework\Scheduler\Models\ScheduledJob;
|
|
||||||
use Openguru\OpenCartFramework\Support\Arr;
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
class SettingsHandler
|
class SettingsHandler
|
||||||
{
|
{
|
||||||
private BotTokenConfigurator $botTokenConfigurator;
|
private BotTokenConfigurator $botTokenConfigurator;
|
||||||
private CronApiKeyRegenerator $cronApiKeyRegenerator;
|
|
||||||
private Settings $settings;
|
private Settings $settings;
|
||||||
private SettingsService $settingsUpdateService;
|
private SettingsService $settingsUpdateService;
|
||||||
private CacheInterface $cache;
|
private CacheInterface $cache;
|
||||||
private LoggerInterface $logger;
|
private LoggerInterface $logger;
|
||||||
private Builder $builder;
|
private Builder $builder;
|
||||||
private ConnectionInterface $connection;
|
private ConnectionInterface $connection;
|
||||||
private ScheduledJob $scheduledJob;
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
BotTokenConfigurator $botTokenConfigurator,
|
BotTokenConfigurator $botTokenConfigurator,
|
||||||
CronApiKeyRegenerator $cronApiKeyRegenerator,
|
|
||||||
Settings $settings,
|
Settings $settings,
|
||||||
SettingsService $settingsUpdateService,
|
SettingsService $settingsUpdateService,
|
||||||
CacheInterface $cache,
|
CacheInterface $cache,
|
||||||
LoggerInterface $logger,
|
LoggerInterface $logger,
|
||||||
Builder $builder,
|
Builder $builder,
|
||||||
ConnectionInterface $connection,
|
ConnectionInterface $connection
|
||||||
ScheduledJob $scheduledJob
|
|
||||||
) {
|
) {
|
||||||
$this->botTokenConfigurator = $botTokenConfigurator;
|
$this->botTokenConfigurator = $botTokenConfigurator;
|
||||||
$this->cronApiKeyRegenerator = $cronApiKeyRegenerator;
|
|
||||||
$this->settings = $settings;
|
$this->settings = $settings;
|
||||||
$this->settingsUpdateService = $settingsUpdateService;
|
$this->settingsUpdateService = $settingsUpdateService;
|
||||||
$this->cache = $cache;
|
$this->cache = $cache;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->builder = $builder;
|
$this->builder = $builder;
|
||||||
$this->connection = $connection;
|
$this->connection = $connection;
|
||||||
$this->scheduledJob = $scheduledJob;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Перегенерировать секретный ключ в URL для cron-job.org (сохраняет cron.api_key).
|
|
||||||
*/
|
|
||||||
public function regenerateCronScheduleUrl(Request $request): JsonResponse
|
|
||||||
{
|
|
||||||
$newApiKey = $this->cronApiKeyRegenerator->regenerate();
|
|
||||||
$scheduleUrl = $this->buildCronScheduleUrl(
|
|
||||||
$this->settings->get('app.shop_base_url', ''),
|
|
||||||
$newApiKey
|
|
||||||
);
|
|
||||||
|
|
||||||
return new JsonResponse(['api_key' => $newApiKey, 'schedule_url' => $scheduleUrl]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configureBotToken(Request $request): JsonResponse
|
public function configureBotToken(Request $request): JsonResponse
|
||||||
@@ -104,15 +81,9 @@ class SettingsHandler
|
|||||||
// Add CRON system details (read-only)
|
// Add CRON system details (read-only)
|
||||||
$data['cron']['cli_path'] = BP_REAL_BASE_PATH . '/cli.php';
|
$data['cron']['cli_path'] = BP_REAL_BASE_PATH . '/cli.php';
|
||||||
$data['cron']['last_run'] = $this->getLastCronRunDate();
|
$data['cron']['last_run'] = $this->getLastCronRunDate();
|
||||||
$data['cron']['schedule_url'] = $this->buildCronScheduleUrl(
|
|
||||||
$this->settings->get('app.shop_base_url', ''),
|
|
||||||
$this->settings->get('cron.api_key', '')
|
|
||||||
);
|
|
||||||
|
|
||||||
$data['scheduled_jobs'] = $this->scheduledJob->all();
|
|
||||||
|
|
||||||
$forms = $this->builder->newQuery()
|
$forms = $this->builder->newQuery()
|
||||||
->from('megapay_forms')
|
->from('telecart_forms')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
if ($forms) {
|
if ($forms) {
|
||||||
@@ -135,21 +106,6 @@ class SettingsHandler
|
|||||||
return new JsonResponse(compact('data'));
|
return new JsonResponse(compact('data'));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function buildCronScheduleUrl(string $shopBaseUrl, string $apiKey): string
|
|
||||||
{
|
|
||||||
$base = rtrim($shopBaseUrl, '/');
|
|
||||||
if ($base === '') {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
$params = http_build_query([
|
|
||||||
'route' => 'extension/tgshop/handle',
|
|
||||||
'api_action' => 'runSchedule',
|
|
||||||
'api_key' => $apiKey,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return $base . '/index.php?' . $params;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function saveSettingsForm(Request $request): JsonResponse
|
public function saveSettingsForm(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$input = $request->json();
|
$input = $request->json();
|
||||||
@@ -160,7 +116,6 @@ class SettingsHandler
|
|||||||
if (isset($input['cron'])) {
|
if (isset($input['cron'])) {
|
||||||
unset($input['cron']['cli_path']);
|
unset($input['cron']['cli_path']);
|
||||||
unset($input['cron']['last_run']);
|
unset($input['cron']['last_run']);
|
||||||
unset($input['cron']['schedule_url']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->settingsUpdateService->update(
|
$this->settingsUpdateService->update(
|
||||||
@@ -184,32 +139,13 @@ class SettingsHandler
|
|||||||
$schema = json_encode($form['schema'], JSON_THROW_ON_ERROR);
|
$schema = json_encode($form['schema'], JSON_THROW_ON_ERROR);
|
||||||
$this->builder->newQuery()
|
$this->builder->newQuery()
|
||||||
->where('alias', '=', $form['alias'])
|
->where('alias', '=', $form['alias'])
|
||||||
->update('megapay_forms', [
|
->update('telecart_forms', [
|
||||||
'friendly_name' => $form['friendly_name'],
|
'friendly_name' => $form['friendly_name'],
|
||||||
'is_custom' => $form['is_custom'],
|
'is_custom' => $form['is_custom'],
|
||||||
'schema' => $schema,
|
'schema' => $schema,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update scheduled jobs is_enabled and cron_expression
|
|
||||||
$scheduledJobs = Arr::get($input, 'scheduled_jobs', []);
|
|
||||||
foreach ($scheduledJobs as $job) {
|
|
||||||
$id = (int) ($job['id'] ?? 0);
|
|
||||||
if ($id <= 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$isEnabled = filter_var($job['is_enabled'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
|
||||||
if ($isEnabled) {
|
|
||||||
$this->scheduledJob->enable($id);
|
|
||||||
} else {
|
|
||||||
$this->scheduledJob->disable($id);
|
|
||||||
}
|
|
||||||
$cronExpression = trim((string) ($job['cron_expression'] ?? ''));
|
|
||||||
if ($cronExpression !== '') {
|
|
||||||
$this->scheduledJob->updateCronExpression($id, $cronExpression);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new JsonResponse([], Response::HTTP_ACCEPTED);
|
return new JsonResponse([], Response::HTTP_ACCEPTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +174,7 @@ class SettingsHandler
|
|||||||
$lastRunTimestamp = $this->cache->get("scheduler.global_last_run");
|
$lastRunTimestamp = $this->cache->get("scheduler.global_last_run");
|
||||||
|
|
||||||
if ($lastRunTimestamp) {
|
if ($lastRunTimestamp) {
|
||||||
return Carbon::createFromTimestamp($lastRunTimestamp)->toDateTimeString();
|
return date('d.m.Y H:i:s', (int)$lastRunTimestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -267,7 +203,7 @@ class SettingsHandler
|
|||||||
$info['MySQL Version'] = 'Error: ' . $e->getMessage();
|
$info['MySQL Version'] = 'Error: ' . $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
$cacheDriver = env('MEGAPAY_CACHE_DRIVER', 'mysql');
|
$cacheDriver = env('TELECART_CACHE_DRIVER', 'mysql');
|
||||||
$cacheClass = get_class($this->cache);
|
$cacheClass = get_class($this->cache);
|
||||||
$info['Cache Driver'] = $cacheDriver . ' (' . basename(str_replace('\\', '/', $cacheClass)) . ')';
|
$info['Cache Driver'] = $cacheDriver . ' (' . basename(str_replace('\\', '/', $cacheClass)) . ')';
|
||||||
|
|
||||||
@@ -30,12 +30,12 @@ class StatsHandler
|
|||||||
new RawExpression('SUM(orders.total) AS orders_total_amount'),
|
new RawExpression('SUM(orders.total) AS orders_total_amount'),
|
||||||
])
|
])
|
||||||
->from(db_table('order'), 'orders')
|
->from(db_table('order'), 'orders')
|
||||||
->join('megapay_customers', function (JoinClause $join) {
|
->join('telecart_customers', function (JoinClause $join) {
|
||||||
$join->on('orders.customer_id', '=', 'megapay_customers.oc_customer_id');
|
$join->on('orders.customer_id', '=', 'telecart_customers.oc_customer_id');
|
||||||
})
|
})
|
||||||
->join('megapay_order_meta', function (JoinClause $join) {
|
->join('telecart_order_meta', function (JoinClause $join) {
|
||||||
$join->on('orders.order_id', '=', 'megapay_order_meta.oc_order_id')
|
$join->on('orders.order_id', '=', 'telecart_order_meta.oc_order_id')
|
||||||
->whereRaw('orders.store_id = megapay_order_meta.oc_store_id');
|
->whereRaw('orders.store_id = telecart_order_meta.oc_store_id');
|
||||||
})
|
})
|
||||||
->firstOrNull();
|
->firstOrNull();
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ class StatsHandler
|
|||||||
private function countCustomersCount(): int
|
private function countCustomersCount(): int
|
||||||
{
|
{
|
||||||
return $this->builder->newQuery()
|
return $this->builder->newQuery()
|
||||||
->from('megapay_customers')
|
->from('telecart_customers')
|
||||||
->count();
|
->count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,17 +3,17 @@
|
|||||||
namespace Bastion\Handlers;
|
namespace Bastion\Handlers;
|
||||||
|
|
||||||
use Openguru\OpenCartFramework\Cache\CacheInterface;
|
use Openguru\OpenCartFramework\Cache\CacheInterface;
|
||||||
use Openguru\OpenCartFramework\MegaPayPulse\MegaPayEvent;
|
use Openguru\OpenCartFramework\TeleCartPulse\TeleCartEvent;
|
||||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
|
||||||
class MegaPayPulseStatsHandler
|
class TeleCartPulseStatsHandler
|
||||||
{
|
{
|
||||||
private MegaPayEvent $eventModel;
|
private TeleCartEvent $eventModel;
|
||||||
private CacheInterface $cache;
|
private CacheInterface $cache;
|
||||||
private const CACHE_KEY = 'megapay_pulse_stats';
|
private const CACHE_KEY = 'telecart_pulse_stats';
|
||||||
private const CACHE_TTL = 3600; // 1 час
|
private const CACHE_TTL = 3600; // 1 час
|
||||||
|
|
||||||
public function __construct(MegaPayEvent $eventModel, CacheInterface $cache)
|
public function __construct(TeleCartEvent $eventModel, CacheInterface $cache)
|
||||||
{
|
{
|
||||||
$this->eventModel = $eventModel;
|
$this->eventModel = $eventModel;
|
||||||
$this->cache = $cache;
|
$this->cache = $cache;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user