Squashed commit message
Some checks failed
Telegram Mini App Shop Builder / Compute version metadata (push) Has been cancelled
Telegram Mini App Shop Builder / Run Frontend tests (push) Has been cancelled
Telegram Mini App Shop Builder / Run Backend tests (push) Has been cancelled
Telegram Mini App Shop Builder / Run PHP_CodeSniffer (push) Has been cancelled
Telegram Mini App Shop Builder / Build module. (push) Has been cancelled
Telegram Mini App Shop Builder / release (push) Has been cancelled

This commit is contained in:
2026-03-11 22:08:41 +03:00
commit 393bbb286b
585 changed files with 65605 additions and 0 deletions

64
.cursor/agents.md Normal file
View File

@@ -0,0 +1,64 @@
# Cursor AI Agents Configuration
## AI Roles and Behavior Rules
### Primary Role: Senior Full-Stack Developer
You are an experienced full-stack developer specializing in:
- Modular ECommerce development
- Custom frameworks (ECommerce Framework)
- PHP 7.4+ with modern best practices
- Vue.js 3 (Composition API)
- Telegram Mini App development
### Coding Rules
1. **Always follow existing project patterns**
2. **Do not create duplicates reuse existing utilities**
3. **Follow project naming conventions**
4. **Test changes before committing**
5. **Document public APIs**
6. **Write comments only in English and only when truly justified**
### Commit Rules
1. **Follow Conventional Commits**
- Use prefixes: `feat:`, `fix:`, `chore:`, `refactor:`, `style:`, `test:`, `docs:`
- Format: `<type>: <subject>` (first line up to 72 characters)
- After an empty line detailed description of changes
2. **Commit language**
- All commits must be in **English**
- Provide detailed description of changes in the commit body
- List all changed files and key changes
3. **Examples of good commits**
```
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
```
### Forbidden
- Hardcoding values (use configs/settings instead)
- Ignoring error handling
- Creating circular dependencies
For frontend development use:
- Vue.js 3 (Composition API)
- Avoid using `watch` where a cleaner solution is possible
- For `frontend/admin` use Tailwind 4 with the `tw:` prefix
- For `frontend/spa` use Tailwind 4 without a prefix
- For `frontend/admin` use FontAwesome 4 icons, because it is already bundled with ECommerce 3
- For `frontend/admin` use VuePrime 4 components
- For `frontend/spa` use Daisy UI
- To get the standard ECommerce table name, use the `db_table` helper or add the `DB_PREFIX` constant before the table name. This way you will get the table name with prefix.
- All tables of my `AcmeShop` module start with the `acmeshop_` prefix. Migration examples are located in `module/acmeshop/upload/acmeshop/database/migrations`

44
.cursor/config.json Normal file
View File

@@ -0,0 +1,44 @@
{
"rules": {
"preferCompositionAPI": true,
"strictTypes": true,
"noHardcodedValues": true,
"useDependencyInjection": true
},
"paths": {
"acmeshop_module": "module/acmeshop/upload/acmeshop",
"frontendAdmin": "frontend/admin",
"telegramShopSpa": "frontend/spa",
"migrations": "module/acmeshop/upload/acmeshop/database/migrations",
"acmeshopHandlers": "module/acmeshop/upload/acmeshop/src/Handlers",
"adminHandlers": "module/acmeshop/upload/acmeshop/bastion/Handlers",
"models": "module/acmeshop/upload/acmeshop/src/Models",
"framework": "module/acmeshop/upload/acmeshop/framework"
},
"naming": {
"classes": "PascalCase",
"methods": "camelCase",
"variables": "camelCase",
"constants": "UPPER_SNAKE_CASE",
"files": "PascalCase for classes, kebab-case for others",
"tables": "snake_case with acmeshop_ 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": "acmeshop_",
"noForeignKeys": true
}
}

View File

@@ -0,0 +1,38 @@
## AcmeShop Pulse Heartbeat Telemetry
### Goal
Send heartbeat telemetry to AcmeShop Pulse once per hour to capture store state and environment versions without any user interaction.
### Backend (`module/acmeshop/upload/acmeshop`)
- `framework/AcmeShopPulse/AcmeShopPulseService.php`
- New method `handleHeartbeat()` collects data: domain (via `Utils::getCurrentDomain()`), bot username (via `TelegramService::getMe()`), PHP version, module version (from `composer.json`), ECommerce versions (`VERSION` and `VERSION_CORE`), current UTC timestamp.
- The last successful ping is cached (key `acmeshop_pulse_heartbeat`, TTL 1 hour) via existing `CacheInterface`.
- Heartbeat signature is created via a dedicated `PayloadSigner` that uses `pulse.heartbeat_secret`/`PULSE_HEARTBEAT_SECRET`. Warnings are logged on cache/bot/signature failures.
- Request is sent to the `heartbeat` endpoint with a 2second timeout and `X-MEGAPAY-VERSION` header taken from `composer.json`.
- `framework/AcmeShopPulse/AcmeShopPulseServiceProvider.php`
- Registers main `PayloadSigner` (by `pulse.api_key`) and a separate heartbeat signer (by `pulse.heartbeat_secret` or `PULSE_HEARTBEAT_SECRET`), injects `LoggerInterface`.
- `src/Handlers/TelemetryHandler.php` + `src/routes.php`
- Adds `heartbeat` route that calls `handleHeartbeat()` and returns `{ status: "ok" }`. Logger writes warnings on problems.
### Frontend (`frontend/spa`)
- `src/utils/ftch.js`: new `heartbeat()` function calls `api_action=heartbeat`.
- `src/stores/Pulse.js`: new `heartbeat` action uses the API function and logs the result.
- `src/main.js`: after `pulse.ingest(...)` the code calls `pulse.heartbeat()` without blocking the chain.
### Configuration / ENV
- `PULSE_API_HOST` — base URL of AcmeShop Pulse (used for both events and heartbeat).
- `PULSE_TIMEOUT` — global HTTP timeout (for heartbeat forced to 2 seconds).
- `PULSE_HEARTBEAT_SECRET` (or `pulse.heartbeat_secret` in settings) — shared secret for signing heartbeat. Required; otherwise heartbeat will not be sent.
- `pulse.api_key` — legacy API key, used only for event ingest.
### Behavior
1. Frontend (SPA) calls `heartbeat` on app initialization (fire-and-forget).
2. Backend checks the cache. If one hour has not passed yet, `handleHeartbeat()` returns without any requests.
3. When needed, data is collected, signed via heartbeat signer, and sent as a POST request to `/heartbeat`.
4. Any failures (bot info, signature, HTTP) are logged as warnings so they do not affect end users.
### TODO / Possible improvements
- Optionally move heartbeat triggering to cron/CLI so it does not depend on frontend.
- Add heartbeat success metrics to the admin panel.

View File

@@ -0,0 +1,127 @@
# Prompts for API generation
## Creating a new API endpoint
```
Create a new API endpoint [ENDPOINT_NAME] for [DESCRIPTION]:
1. Handler in [HANDLER_PATH]:
- Method handle() accepts Request
- Validate input data
- Use a Service for business logic
- Return JsonResponse with correct structure
- Handle errors with logging
2. Service in [SERVICE_PATH]:
- Business logic
- Work with Model
- Data validation
- Exception handling
3. Model in [MODEL_PATH] (if needed):
- Methods for working with the database
- Use Query Builder
- Proper method typing
4. Route in routes.php:
- Add a route with the correct name
5. Migration (if a new table is needed):
- Create a migration in database/migrations/
- Use fixed acmeshop_ prefix for the table
- Add indexes where necessary
Follow the project MVC-L architecture and reuse existing patterns.
```
## Creating a CRUD API
```
Create a full CRUD API for entity [ENTITY_NAME]:
1. Handler with methods:
- list() list with pagination and filtering
- get() fetch a single record
- create() create
- update() update
- delete() delete
2. Service with business logic for all operations
3. Model with methods:
- findAll() list
- findById() by ID
- create() create
- update() update
- delete() delete
4. DTO for data validation
5. Migration for table [TABLE_NAME]
6. Routes for all endpoints
Use server-side pagination, filtering, and sorting for list().
```
## Creating an Admin API endpoint
```
Create an Admin API endpoint [ENDPOINT_NAME] in bastion/Handlers/:
1. Handler in bastion/Handlers/[HANDLER_NAME].php:
- Use Request to read parameters
- Validate data
- Call a Service for business logic
- Return JsonResponse with structure { data: { data: [...], totalRecords: ... } }
- Handle errors
2. Service in bastion/Services/ (if needed):
- Admin-specific business logic
- Work with Models
3. Route in bastion/routes.php
4. Frontend component (if UI is needed):
- Vue component in frontend/admin/src/views/
- Use PrimeVue components
- Server-side pagination/filtering
- Error handling with toast notifications
Follow existing project patterns.
```
## Creating a Frontend API client
```
Create a function for working with API endpoint [ENDPOINT_NAME]:
1. In frontend/[admin|spa]/src/utils/http.js:
- api[Method] function to call the endpoint
- Proper error handling
- Return a structured response
2. Usage:
- Import in components
- Handle loading states
- Show toast notifications on errors
Follow existing patterns in http.js.
```
## Creating a migration
```
Create a migration for table [TABLE_NAME]:
1. File: database/migrations/[TIMESTAMP]_[DESCRIPTION].php
2. Use fixed acmeshop_ prefix for the table
3. Add all required fields with correct types
4. Add indexes for frequently used fields
5. Use utf8mb4_unicode_ci collation
6. Use InnoDB engine
7. Add created_at and updated_at timestamps
8. Do not create foreign keys (use indexes only)
Follow the structure of existing migrations.
```

View File

@@ -0,0 +1,101 @@
# Changelog Documentation Rules
## General requirements
- **Format**: Markdown
- **Structure**: One version = one page
- **Style**: Professional, concise, without marketing slogans
- **Language**: English
- **Target audience**: Developers and store owners
- **Content**: Only key changes, without unnecessary technical details
## Page structure
### Intro paragraph
- Short release description (12 sentences)
- Add 12 sentences about key changes
- Mention the main features and improvements
### Sections (strictly in this order)
1. **🚀 Added** new features and capabilities
2. **✨ Improved** improvements to existing features
3. **🐞 Fixed** bug fixes
4. **⚠️ Breaking changes** nonbackward compatible changes
5. **🔧 Technical changes** technical changes (separate section)
### Section rules
- **Do NOT** add a section if it has no items
- Use markdown bullet lists, no numbering
- Do not add links unless they are explicitly specified
- Do not add extra explanations, conclusions, or summaries
- Output only the content of the Markdown file, without comments
## Separating changes
### Business logic and processes
Sections "Added", "Improved", "Fixed", "Breaking changes" contain only changes related to:
- Store business processes
- User experience
- Functionality for store owners
- Selling / valuedriven features
### Technical changes
All technical changes go into a separate **🔧 Technical changes** section:
- No subsections, everything in a single list
- Only the most important technical changes
- Do not describe details that are not interesting to end users
## Writing style
### Terminology
- Use English terms (e.g. "navbar", not a localized variant)
- Avoid lowlevel technical terms in business sections (e.g. "customer_id" → "automatic customer linking")
### Descriptions
**For key selling features:**
- Provide detailed descriptions
- Highlight capabilities and advantages
- Emphasize value for the user
**For technical changes:**
- Be brief and to the point
- Avoid unnecessary details
**For regular features:**
- Be concise but informative
- Focus on user benefit
## Ordering
### In the "Added" section
Place key selling features **first**:
1. Main page configuration system based on blocks
2. Form builder based on FormKit
3. Yandex.Metrica integration
4. Privacy policy
5. Support for coupons and gift certificates
6. Other features
## Examples of good wording
### ✅ Good
- "Automatic linking of Telegram customers with ECommerce customers: the system automatically finds and links customers from the Telegram shop with existing customers in ECommerce by email or phone number, creating a unified customer base"
### ❌ Bad
- "Saving customer_id with the order to link with ECommerce customers: automatic linking of orders from Telegram with customers in ECommerce, unified customer base"
### ✅ Good
- "Navbar with logo and application name"
### ❌ Bad
- "Navigation bar with logo and application name" (too verbose, no added value)
## What not to include
- Internal development details (tests, static analysis, obfuscation)
- Technical details not interesting to users
- Excessively detailed technical descriptions
- Marketing slogans and calls to action

View File

@@ -0,0 +1,61 @@
# Prompts for documentation
## Documenting a class
```
Add PHPDoc documentation for class [CLASS_NAME]:
1. Description of the class and its purpose
2. @package tag
3. @author tag
4. Documentation for all public methods
5. Documentation for public properties
6. Usage examples where appropriate
```
## Documenting a method
```
Add PHPDoc for method [METHOD_NAME]:
1. Method description
2. @param for all parameters with types
3. @return with the return type
4. @throws for all exceptions
5. Usage examples if the logic is complex
```
## Documenting an API endpoint
```
Create documentation for API endpoint [ENDPOINT_NAME]:
1. Description of purpose
2. HTTP method and path
3. Request parameters (query/body)
4. Response format (JSON structure)
5. Error codes
6. Request/response examples
7. Authorization requirements
```
## Documenting a Vue component
```
Add documentation for Vue component [COMPONENT_NAME]:
1. Component description
2. Props with types and descriptions
3. Emits with descriptions
4. Slots if present
5. Usage examples
6. Dependencies on other components
```
## Creating a README
```
Create README.md for [MODULE/COMPONENT]:
1. Purpose description
2. Installation/configuration
3. Usage with examples
4. API documentation
5. Configuration options
6. Troubleshooting
```

View File

@@ -0,0 +1,87 @@
# Prompts for refactoring
## General refactoring
```
Analyze the code in file [FILE_PATH] and perform refactoring:
1. Remove duplicated code
2. Improve readability
3. Apply SOLID principles
4. Add error handling where necessary
5. Improve typing
6. Add documentation for public methods
7. Ensure the code follows the project's MVC-L architecture
8. Use existing utilities and services instead of creating new ones
```
## Refactoring a Handler
```
Refactor Handler [HANDLER_NAME]:
1. Extract business logic into a separate Service
2. Add validation for input data
3. Improve error handling with logging
4. Use DTOs for data transfer
5. Add PHPDoc comments
6. Ensure Dependency Injection is used
7. Optimize database queries if needed
```
## Refactoring a Model
```
Refactor Model [MODEL_NAME]:
1. Ensure all queries use the Query Builder
2. Add methods for common operations (findBy, findAll, create, update)
3. Add data validation before saving
4. Improve method typing
5. Add PHPDoc comments
6. Use transactions for complex operations
```
## Refactoring a Vue component
```
Refactor Vue component [COMPONENT_NAME]:
1. Extract logic into composable functions
2. Improve typing for props and emits
3. Optimize computed properties
4. Add error handling
5. Improve template structure
6. Add loading states
7. Use existing project utilities
```
## Removing duplication
```
Find and remove code duplication in:
- [FILE_PATH_1]
- [FILE_PATH_2]
- [FILE_PATH_3]
Create shared utilities/services where appropriate, following the project architecture.
```
## Improving performance
```
Analyze performance of the code in [FILE_PATH]:
1. Optimize database queries (use indexes, avoid N+1)
2. Add caching where appropriate
3. Optimize algorithms
4. Reduce the number of API calls
5. Use lazy loading on the frontend
```
## Improving security
```
Improve security of the code in [FILE_PATH]:
1. Add validation for all input data
2. Use prepared statements (Query Builder)
3. Add CSRF protection where necessary
4. Validate access rights
5. Sanitize output data
6. Add rate limiting where necessary
```

View File

@@ -0,0 +1,52 @@
# Prompts for testing
## Creating a unit test
```
Create a unit test for [CLASS_NAME] in tests/Unit/:
1. Use PHPUnit
2. Cover all public methods
3. Test successful scenarios
4. Test error handling
5. Use mocks for dependencies
6. Follow the structure of existing tests
7. Use the project's base TestCase class
```
## Creating an integration test
```
Create an integration test for [FEATURE_NAME] in tests/Integration/:
1. Test the full flow from request to response
2. Use a test database
3. Clean up data after tests
4. Test real-life usage scenarios
5. Verify data validation
6. Verify error handling
```
## Creating a Vue component test
```
Create a test for Vue component [COMPONENT_NAME] in frontend/[admin|spa]/tests/:
1. Use Vitest
2. Test component rendering
3. Test props
4. Test events (emits)
5. Test user interactions
6. Use mocks for API calls
7. Follow the structure of existing tests
```
## Test coverage
```
Analyze test coverage for [FILE_PATH]:
1. Determine which methods are not covered by tests
2. Create tests for critical methods
3. Ensure edge cases are tested
4. Add tests for error handling
```

View File

@@ -0,0 +1,201 @@
# Architectural Rules
## ECommerce Framework Architecture
### MVC-L Pattern
The project uses a modified MVC-L pattern (Model-View-Controller-Language):
- **Model**: Classes in `src/Models/` data access and database operations
- **View**: Vue components on the frontend, JSON responses on the backend
- **Controller**: Handlers in `src/Handlers/` and `bastion/Handlers/`
- **Language**: Translator in `framework/Translator/`
### Dependency Injection
All dependencies are injected via the Container:
```php
// ✅ Correct
public function __construct(
private Builder $builder,
private TelegramCustomer $telegramCustomerModel
) {}
// ❌ Incorrect
public function __construct() {
$this->builder = new Builder(...);
}
```
### Service Providers
Services are registered via 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 are defined in `routes.php`:
```php
return [
'actionName' => [HandlerClass::class, 'methodName'],
];
```
### Handlers (Controllers)
Handlers process HTTP requests:
```php
class MyHandler
{
public function handle(Request $request): JsonResponse
{
// Validation
// Business logic via Services
// Return JsonResponse
}
}
```
### Models
Models work with data:
```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 contain business logic:
```php
class MyService
{
public function __construct(
private MyModel $model
) {}
public function doSomething(array $data): array
{
// Business logic
return $this->model->create($data);
}
}
```
### Migrations
Migrations live in `database/migrations/`:
```php
return new class extends Migration {
public function up(): void
{
$this->database->statement('CREATE TABLE ...');
}
};
```
### Query Builder
Always use Query Builder instead of raw SQL:
```php
// ✅ Correct
$query = $this->builder->newQuery()
->select(['id', 'name'])
->from('table_name')
->where('status', '=', 'active')
->get();
// ❌ Incorrect
$result = $this->database->query("SELECT * FROM table_name WHERE status = 'active'");
```
### Frontend Architecture
#### Admin Panel (Vue 3)
- Composition API
- Pinia for state management
- PrimeVue for UI components
- Axios for HTTP requests
- Vue Router for navigation
#### SPA (Telegram Mini App)
- Composition API
- Pinia stores
- Tailwind CSS for styles
- Telegram WebApp API
- Vue Router
### Naming Conventions
- **Classes**: PascalCase (`TelegramCustomerService`)
- **Methods**: camelCase (`getCustomers`)
- **Variables**: camelCase (`$customerData`)
- **Constants**: UPPER_SNAKE_CASE (`MAX_RETRIES`)
- **Files**: PascalCase for classes, kebab-case for everything else
- **Tables**: snake_case with `acmeshop_` prefix
### Error Handling
Always handle errors:
```php
try {
$result = $this->service->doSomething();
} catch (SpecificException $e) {
$this->logger->error('Error message', ['exception' => $e]);
throw new UserFriendlyException('User message');
}
```
### Configuration
Use configuration files in `configs/`:
```php
$config = $this->app->getConfigValue('app.setting_name');
```
### Caching
Use Cache Service for caching:
```php
$cache = $this->app->get(CacheInterface::class);
$value = $cache->get('key', function() {
return expensiveOperation();
});
```

View File

@@ -0,0 +1,70 @@
# 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.

332
.cursor/rules/javascript.md Normal file
View File

@@ -0,0 +1,332 @@
# JavaScript/TypeScript Code Style Rules
## JavaScript Version
- ES2020+ features
- Modern async/await
- Optional chaining (`?.`)
- Nullish coalescing (`??`)
- Template literals
## Code Style
### Variable Declarations
```javascript
// ✅ Use const by default
const customers = [];
const totalRecords = 0;
// ✅ Use let only when reassignment is needed
let currentPage = 1;
currentPage = 2;
// ❌ Do not use var
var oldVariable = 'bad';
```
### Arrow Functions
```javascript
// ✅ Preferred for short functions
const filtered = items.filter(item => item.isActive);
// ✅ For object methods
const api = {
get: async (url) => {
return await fetch(url);
}
};
// ✅ For complex logic use regular functions
function complexCalculation(data) {
// many lines of code
return result;
}
```
### Template Literals
```javascript
// ✅ Preferred
const message = `User ${userId} not found`;
const url = `${baseUrl}/api/${endpoint}`;
// ❌ Do not use concatenation
const message = 'User ' + userId + ' not found';
```
### Optional Chaining
```javascript
// ✅ Use optional chaining
const name = user?.profile?.name;
const count = data?.items?.length ?? 0;
// ❌ Avoid long nested checks
const name = user && user.profile && user.profile.name;
```
### Nullish Coalescing
```javascript
// ✅ Use ?? for default values
const page = params.page ?? 1;
const name = user.name ?? 'Unknown';
// ❌ Do not use || for numbers/booleans
const page = params.page || 1; // 0 will be replaced with 1
```
### Destructuring
```javascript
// ✅ Use destructuring
const { data, totalRecords } = response.data;
const [first, second] = items;
// ✅ In function parameters
function processUser({ id, name, email }) {
// ...
}
// ✅ With default values
const { page = 1, limit = 20 } = params;
```
### Async/Await
```javascript
// ✅ Preferred
async function loadCustomers() {
try {
const response = await apiGet('getCustomers', params);
return response.data;
} catch (error) {
console.error('Error:', error);
throw error;
}
}
// ❌ Avoid .then() chains
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>
// ✅ Use <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
// ✅ Use ref for primitives
const count = ref(0);
const name = ref('');
// ✅ Use reactive for objects
import { reactive } from 'vue';
const state = reactive({
customers: [],
loading: false
});
// ✅ Or ref for objects (preferred)
const state = ref({
customers: [],
loading: false
});
```
### Computed Properties
```javascript
// ✅ Use computed for derived values
const filteredCustomers = computed(() => {
return customers.value.filter(c => c.isActive);
});
// ❌ Do not use methods for derived values
function filteredCustomers() {
return customers.value.filter(c => c.isActive);
}
```
### Props
```vue
<script setup>
// ✅ Define props with types
const props = defineProps({
customerId: {
type: Number,
required: true
},
showDetails: {
type: Boolean,
default: false
}
});
</script>
```
### Emits
```vue
<script setup>
// ✅ Define emits
const emit = defineEmits(['update', 'delete']);
function handleUpdate() {
emit('update', data);
}
</script>
```
## Pinia Stores
```javascript
// ✅ Use 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
// ✅ Always handle errors
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('Failed to load data');
throw error;
}
}
```
## Naming Conventions
### Variables and Functions
```javascript
// ✅ camelCase
const customerData = {};
const totalRecords = 0;
function loadCustomers() {}
// ✅ Constants in UPPER_SNAKE_CASE
const MAX_RETRIES = 3;
const API_BASE_URL = '/api';
```
### Components
```vue
<!-- PascalCase for components -->
<CustomerCard />
<ProductsList />
```
### Files
```javascript
// ✅ Use kebab-case for files
// customers-view.vue
// http-utils.js
// customer-service.js
```
## Imports
```javascript
// ✅ Group imports
// 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 (if using TypeScript)
import type { Customer } from '@/types';
```
## TypeScript (where used)
```typescript
// ✅ Use types
interface Customer {
id: number;
name: string;
email?: string;
}
function getCustomer(id: number): Promise<Customer> {
return apiGet(`customers/${id}`);
}
```

243
.cursor/rules/php.md Normal file
View File

@@ -0,0 +1,243 @@
# PHP Code Style Rules
## PHP Version
The project supports PHP 7.4+
## PSR Standards
- **PSR-1**: Basic Coding Standard
- **PSR-4**: Autoloading Standard
- **PSR-12**: Extended Coding Style
## Code Style
### Type Declarations
```php
// ✅ Correct strict typing
public function getCustomers(Request $request): JsonResponse
{
$id = (int) $request->get('id');
return new JsonResponse(['data' => $customers]);
}
// ❌ Incorrect no types
public function getCustomers($request)
{
return ['data' => $customers];
}
```
### Nullable Types
```php
// ✅ Correct
public function findById(?int $id): ?array
{
if ($id === null) {
return null;
}
return $this->query->where('id', '=', $id)->firstOrNull();
}
```
### Strict Types
Always use `declare(strict_types=1);`:
```php
<?php
declare(strict_types=1);
namespace App\Services;
```
### Array Syntax
```php
// ✅ Preferred short syntax
$array = ['key' => 'value'];
// ❌ Do not use
$array = array('key' => 'value');
```
### String Interpolation
```php
// ✅ Preferred
$message = "User {$userId} not found";
// ✅ Alternative
$message = sprintf('User %d not found', $userId);
```
### Arrow Functions (PHP 7.4+)
```php
// ✅ For simple operations
$filtered = array_filter($items, fn($item) => $item->isActive());
// ❌ For complex logic use regular functions
```
### Nullsafe Operator (PHP 8.0+)
```php
// ✅ For 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 with visibility modifier
private string $tableName;
private Builder $builder;
```
## Documentation
### PHPDoc
```php
/**
* @throws ValidationException If parameters are invalid
*/
public function getCustomers(Request $request): JsonResponse
{
// ...
}
```
### Inline Comments
```php
// ✅ Useful comments
// Apply filters to calculate total number of records
$countQuery = $this->buildCountQuery($filters);
// ❌ Obvious comments
// Get data
$data = $this->getData();
```
## Error Handling
### Exceptions
```php
// ✅ Specific exceptions
if (!$userId) {
throw new InvalidArgumentException('User ID is required');
}
// ✅ Logging
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
// ✅ Correct
$customers = $this->builder->newQuery()
->select(['id', 'name', 'email'])
->from('acmeshop_customers')
->where('status', '=', 'active')
->orderBy('created_at', 'DESC')
->get();
// In edge cases raw SQL may be used
$result = $this->database->query("SELECT * FROM acmeshop_customers");
```
### Parameter Binding
```php
// ✅ Query Builder automatically binds parameters
$query->where('name', 'LIKE', "%{$search}%");
// ❌ Never concatenate values into SQL, avoid SQL Injection.
```
## Array Access
### Safe Array Access
```php
// ✅ Use Arr::get()
use Acme\ECommerceFramework\Support\Arr;
$value = Arr::get($data, 'key', 'default');
// ❌ Unsafe
$value = $data['key']; // may trigger an error
```
## Return Types
```php
// ✅ Always specify return type
public function getData(): array {}
public function findById(int $id): ?array {}
public function process(): void {}
// ❌ Without type
public function getData() {}
```
## Visibility Modifiers
```php
// ✅ Always specify visibility modifier
private string $tableName;
protected Builder $builder;
public function getData(): array {}
```

370
.cursor/rules/vue.md Normal file
View File

@@ -0,0 +1,370 @@
# Vue.js 3 Rules
## Component Structure
### Template
```vue
<template>
<!-- Logical structure -->
<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>
// ✅ Always use <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>
/* ✅ Use scoped styles */
.container {
padding: 1rem;
}
/* ✅ Use :deep() to style nested components */
:deep(.p-datatable) {
border: 1px solid #ccc;
}
</style>
```
## Component Naming
```vue
<!-- PascalCase for components -->
<CustomerCard />
<ProductsList />
<OrderDetails />
<!-- kebab-case in templates also works -->
<customer-card />
```
## Props
```vue
<script setup>
// ✅ Always define types and validation
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>
// ✅ Define emits with types
const emit = defineEmits<{
update: [id: number, data: object];
delete: [id: number];
cancel: [];
}>();
// ✅ Or with validation
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 for primitives
const count = ref(0);
const name = ref('');
// ✅ ref for objects (preferred)
const customer = ref({
id: null,
name: '',
email: ''
});
// ✅ Use reactive only when necessary
import { reactive } from 'vue';
const state = reactive({
items: [],
loading: false
});
</script>
```
## Computed Properties
```vue
<script setup>
// ✅ Use computed for derived values
const filteredItems = computed(() => {
return items.value.filter(item => item.isActive);
});
// ✅ Computed with 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>
<!-- Use kebab-case for events -->
<Button @click="handleClick" />
<Input @input="handleInput" />
<Form @submit.prevent="handleSubmit" />
</template>
<script setup>
// ✅ Name handlers with handle* prefix
function handleClick() {
// ...
}
function handleInput(event) {
// ...
}
function handleSubmit() {
// ...
}
</script>
```
## Conditional Rendering
```vue
<template>
<!-- Use v-if for conditional rendering -->
<div v-if="loading">
<LoadingSpinner />
</div>
<!-- v-show for frequent toggling -->
<div v-show="hasItems">
<ItemsList :items="items" />
</div>
<!-- v-else for alternatives -->
<div v-else>
<EmptyState />
</div>
</template>
```
## Lists
```vue
<template>
<!-- Always use :key -->
<div v-for="item in items" :key="item.id">
{{ item.name }}
</div>
<!-- For index-based lists -->
<div v-for="(item, index) in items" :key="`item-${index}`">
{{ item.name }}
</div>
</template>
```
## Form Handling
```vue
<template>
<form @submit.prevent="handleSubmit">
<!-- Use v-model -->
<InputText v-model="form.name" />
<Textarea v-model="form.description" />
<!-- For custom components -->
<CustomInput v-model="form.email" />
</form>
</template>
<script setup>
const form = ref({
name: '',
description: '',
email: ''
});
function handleSubmit() {
// Validation and submit
}
</script>
```
## PrimeVue Components
```vue
<template>
<!-- Use PrimeVue components in the admin panel -->
<DataTable
:value="customers"
:loading="loading"
paginator
:rows="20"
@page="onPage"
>
<Column field="name" header="Name" sortable />
</DataTable>
</template>
```
## Styling
```vue
<style scoped>
/* ✅ Use scoped -->
.container {
padding: 1rem;
}
/* ✅ :deep() for nested components */
:deep(.p-datatable) {
border: 1px solid #ccc;
}
/* ✅ :slotted() for slots */
:slotted(.header) {
font-weight: bold;
}
</style>
```
## Composition Functions
```vue
<script setup>
// ✅ Extract complex logic into 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: 'Error',
detail: result.error
});
}
} catch (error) {
console.error('Error:', error);
toast.add({
severity: 'error',
summary: 'Error',
detail: 'Failed to load data'
});
}
}
</script>
```