feat: add Telegram customers management system with admin panel
Implement comprehensive Telegram customers storage and management functionality: Backend: - Add database migration for telecart_customers table with indexes - Create TelegramCustomer model with CRUD operations - Implement TelegramCustomerService for business logic - Add TelegramCustomerHandler for API endpoint (saveOrUpdate) - Add TelegramCustomersHandler for admin API (getCustomers with pagination, filtering, sorting) - Add SendMessageHandler for sending messages to customers via Telegram - Create custom exceptions: TelegramCustomerNotFoundException, TelegramCustomerWriteNotAllowedException - Refactor TelegramInitDataDecoder to separate decoding logic - Add TelegramHeader enum for header constants - Update SignatureValidator to use TelegramInitDataDecoder - Register new routes in bastion/routes.php and src/routes.php Frontend (Admin): - Add CustomersView.vue component with PrimeVue DataTable - Implement advanced filtering (text, date, boolean filters) - Add column visibility toggle functionality - Add global search with debounce - Implement message sending dialog with validation - Add Russian locale for PrimeVue components - Add navigation link in App.vue - Register route in router Frontend (SPA): - Add saveTelegramCustomer utility function - Integrate automatic customer data saving on app initialization - Extract user data from Telegram.WebApp.initDataUnsafe The system automatically saves/updates customer data when users access the Telegram Mini App, and provides admin interface for viewing, filtering, and messaging customers. BREAKING CHANGE: None
This commit is contained in:
243
.cursor/rules/php.md
Normal file
243
.cursor/rules/php.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# 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('telecart_customers')
|
||||
->where('status', '=', 'active')
|
||||
->orderBy('created_at', 'DESC')
|
||||
->get();
|
||||
|
||||
// В крайних случаях можно использовать прямые SQL
|
||||
$result = $this->database->query("SELECT * FROM telecart_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 {}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user