{"owner":"EvolutionAPI","repo":"evolution-api","hasSkills":true,"totalSkillsCount":18,"totalTokensCount":48065,"categories":["anthropic-skill","cursor-rule","root-instruction","claude-rule"],"hasMcp":false,"mcpConfig":null,"found":[".cursor/rules/README.md",".cursor/rules/core-development.mdc",".cursor/rules/cursor.json",".cursor/rules/project-context.mdc",".cursor/rules/specialized-rules/controller-rules.mdc",".cursor/rules/specialized-rules/dto-rules.mdc",".cursor/rules/specialized-rules/guard-rules.mdc",".cursor/rules/specialized-rules/integration-channel-rules.mdc",".cursor/rules/specialized-rules/integration-chatbot-rules.mdc",".cursor/rules/specialized-rules/integration-event-rules.mdc",".cursor/rules/specialized-rules/integration-storage-rules.mdc",".cursor/rules/specialized-rules/route-rules.mdc",".cursor/rules/specialized-rules/service-rules.mdc",".cursor/rules/specialized-rules/type-rules.mdc",".cursor/rules/specialized-rules/util-rules.mdc",".cursor/rules/specialized-rules/validate-rules.mdc","AGENTS.md","CLAUDE.md"],"skills":{".cursor/rules/README.md":"# Evolution API Cursor Rules\n\nEste diretório contém as regras e configurações do Cursor IDE para o projeto Evolution API.\n\n## Estrutura dos Arquivos\n\n### Arquivos Principais (alwaysApply: true)\n- **`core-development.mdc`** - Princípios fundamentais de desenvolvimento\n- **`project-context.mdc`** - Contexto específico do projeto Evolution API\n- **`cursor.json`** - Configurações do Cursor IDE\n\n### Regras Especializadas (alwaysApply: false)\nEstas regras são ativadas automaticamente quando você trabalha nos arquivos correspondentes:\n\n#### Camadas da Aplicação\n- **`specialized-rules/service-rules.mdc`** - Padrões para services (`src/api/services/`)\n- **`specialized-rules/controller-rules.mdc`** - Padrões para controllers (`src/api/controllers/`)\n- **`specialized-rules/dto-rules.mdc`** - Padrões para DTOs (`src/api/dto/`)\n- **`specialized-rules/guard-rules.mdc`** - Padrões para guards (`src/api/guards/`)\n- **`specialized-rules/route-rules.mdc`** - Padrões para routers (`src/api/routes/`)\n\n#### Tipos e Validação\n- **`specialized-rules/type-rules.mdc`** - Definições TypeScript (`src/api/types/`)\n- **`specialized-rules/validate-rules.mdc`** - Schemas de validação (`src/validate/`)\n\n#### Utilitários\n- **`specialized-rules/util-rules.mdc`** - Funções utilitárias (`src/utils/`)\n\n#### Integrações\n- **`specialized-rules/integration-channel-rules.mdc`** - Integrações de canal (`src/api/integrations/channel/`)\n- **`specialized-rules/integration-chatbot-rules.mdc`** - Integrações de chatbot (`src/api/integrations/chatbot/`)\n- **`specialized-rules/integration-storage-rules.mdc`** - Integrações de storage (`src/api/integrations/storage/`)\n- **`specialized-rules/integration-event-rules.mdc`** - Integrações de eventos (`src/api/integrations/event/`)\n\n## Como Usar\n\n### Referências Cruzadas\nOs arquivos principais fazem referência aos especializados usando a sintaxe `@specialized-rules/nome-do-arquivo.mdc`. Quando você trabalha em um arquivo específico, o Cursor automaticamente carrega as regras relevantes.\n\n### Exemplo de Uso\nQuando você edita um arquivo em `src/api/services/`, o Cursor automaticamente:\n1. Carrega `core-development.mdc` (sempre ativo)\n2. Carrega `project-context.mdc` (sempre ativo)\n3. Carrega `specialized-rules/service-rules.mdc` (ativado pelo glob pattern)\n\n### Padrões de Código\nCada arquivo de regras contém:\n- **Estruturas padrão** - Como organizar o código\n- **Padrões de nomenclatura** - Convenções de nomes\n- **Exemplos práticos** - Código de exemplo\n- **Anti-padrões** - O que evitar\n- **Testes** - Como testar o código\n- **Padrões de Commit** - Conventional Commits com commitlint\n\n## Configuração do Cursor\n\nO arquivo `cursor.json` contém:\n- Configurações de formatação\n- Padrões de código específicos do Evolution API\n- Diretórios principais do projeto\n- Integrações e tecnologias utilizadas\n\n## Manutenção\n\nPara manter as regras atualizadas:\n1. Analise novos padrões no código\n2. Atualize as regras especializadas correspondentes\n3. Mantenha os exemplos sincronizados com o código real\n4. Documente mudanças significativas\n\n## Tecnologias Cobertas\n\n- **Backend**: Node.js 20+ + TypeScript 5+ + Express.js\n- **Database**: Prisma ORM (PostgreSQL/MySQL)\n- **Cache**: Redis + Node-cache\n- **Queue**: RabbitMQ + Amazon SQS\n- **Real-time**: Socket.io\n- **Storage**: AWS S3 + Minio\n- **Validation**: JSONSchema7\n- **Logging**: Pino\n- **WhatsApp**: Baileys + Meta Business API\n- **Integrations**: Chatwoot, Typebot, OpenAI, Dify\n\n## Estrutura do Projeto\n\n```\nsrc/\n├── api/\n│   ├── controllers/     # Controllers (HTTP handlers)\n│   ├── services/        # Business logic\n│   ├── dto/            # Data Transfer Objects\n│   ├── guards/         # Authentication/Authorization\n│   ├── routes/         # Express routers\n│   ├── types/          # TypeScript definitions\n│   └── integrations/   # External integrations\n│       ├── channel/    # WhatsApp channels (Baileys, Business API)\n│       ├── chatbot/    # Chatbot integrations\n│       ├── event/      # Event integrations\n│       └── storage/    # Storage integrations\n├── cache/              # Cache implementations\n├── config/             # Configuration files\n├── utils/              # Utility functions\n├── validate/           # Validation schemas\n└── exceptions/         # Custom exceptions\n```\n\nEste sistema de regras garante consistência no código e facilita o desenvolvimento seguindo os padrões estabelecidos do Evolution API.\n",".cursor/rules/core-development.mdc":"---\ndescription: Core development principles and standards for Evolution API development\nglobs:\nalwaysApply: true\n---\n\n# Evolution API Development Standards\n\n## Cross-References\n- **Project Context**: @project-context.mdc for Evolution API-specific patterns\n- **Specialized Rules**: \n  - @specialized-rules/service-rules.mdc for service layer patterns\n  - @specialized-rules/controller-rules.mdc for controller patterns\n  - @specialized-rules/dto-rules.mdc for DTO validation patterns\n  - @specialized-rules/guard-rules.mdc for authentication/authorization\n  - @specialized-rules/route-rules.mdc for router patterns\n  - @specialized-rules/type-rules.mdc for TypeScript definitions\n  - @specialized-rules/util-rules.mdc for utility functions\n  - @specialized-rules/validate-rules.mdc for validation schemas\n  - @specialized-rules/integration-channel-rules.mdc for channel integrations\n  - @specialized-rules/integration-chatbot-rules.mdc for chatbot integrations\n  - @specialized-rules/integration-storage-rules.mdc for storage integrations\n  - @specialized-rules/integration-event-rules.mdc for event integrations\n- **TypeScript/Node.js**: Node.js 20+ + TypeScript 5+ best practices\n- **Express/Prisma**: Express.js + Prisma ORM patterns\n- **WhatsApp Integrations**: Baileys + Meta Business API patterns\n\n## Senior Engineer Context - Evolution API Platform\n- You are a senior software engineer working on a WhatsApp API platform\n- Focus on Node.js + TypeScript + Express.js full-stack development\n- Specialized in real-time messaging, WhatsApp integrations, and event-driven architecture\n- Apply scalable patterns for multi-tenant API platform\n- Consider WhatsApp integration workflow implications and performance at scale\n\n## Fundamental Principles\n\n### Code Quality Standards\n- **Simplicity First**: Always prefer simple solutions over complex ones\n- **DRY Principle**: Avoid code duplication - check for existing similar functionality before implementing\n- **Single Responsibility**: Each function/class should have one clear purpose\n- **Readable Code**: Write code that tells a story - clear naming and structure\n\n### Problem Resolution Approach\n- **Follow Existing Patterns**: Use established Service patterns, DTOs, and Integration patterns\n- **Event-Driven First**: Leverage EventEmitter2 for event publishing when adding new features\n- **Integration Pattern**: Follow existing WhatsApp integration patterns for new channels\n- **Conservative Changes**: Prefer extending existing services over creating new architecture\n- **Clean Migration**: Remove deprecated patterns when introducing new ones\n- **Incremental Changes**: Break large changes into smaller, testable increments with proper migrations\n\n### File and Function Organization - Node.js/TypeScript Structure\n- **Services**: Keep services focused and under 200 lines\n- **Controllers**: Keep controllers thin - only routing and validation\n- **DTOs**: Use JSONSchema7 for all input validation\n- **Integrations**: Follow `src/api/integrations/` structure for new integrations\n- **Utils**: Extract common functionality into well-named utilities\n- **Types**: Define clear TypeScript interfaces and types\n\n### Code Analysis and Reflection\n- After writing code, deeply reflect on scalability and maintainability\n- Provide 1-2 paragraph analysis of code changes\n- Suggest improvements or next steps based on reflection\n- Consider performance, security, and maintenance implications\n\n## Development Standards\n\n### TypeScript Standards\n- **Strict Mode**: Always use TypeScript strict mode\n- **No Any**: Avoid `any` type - use proper typing\n- **Interfaces**: Define clear contracts with interfaces\n- **Enums**: Use enums for constants and status values\n- **Generics**: Use generics for reusable components\n\n### Error Handling Standards\n- **HTTP Exceptions**: Use appropriate HTTP status codes\n- **Logging**: Structured logging with context\n- **Retry Logic**: Implement retry for external services\n- **Graceful Degradation**: Handle service failures gracefully\n\n### Security Standards\n- **Input Validation**: Validate all inputs with JSONSchema7\n- **Authentication**: Use API keys and JWT tokens\n- **Rate Limiting**: Implement rate limiting for APIs\n- **Data Sanitization**: Sanitize sensitive data in logs\n\n### Performance Standards\n- **Caching**: Use Redis for frequently accessed data\n- **Database**: Optimize Prisma queries with proper indexing\n- **Memory**: Monitor memory usage and implement cleanup\n- **Async**: Use async/await properly with error handling\n\n## Communication Standards\n\n### Language Requirements\n- **User Communication**: Always respond in Portuguese (PT-BR)\n- **Code Comments**: English for technical documentation\n- **API Documentation**: English for consistency\n- **Error Messages**: Portuguese for user-facing errors\n\n### Documentation Standards\n- **Code Comments**: Document complex business logic\n- **API Documentation**: Document all public endpoints\n- **README**: Keep project documentation updated\n- **Changelog**: Document breaking changes\n\n## Quality Assurance\n\n### Testing Standards\n- **Unit Tests**: Test business logic in services\n- **Integration Tests**: Test API endpoints\n- **Mocks**: Mock external dependencies\n- **Coverage**: Aim for 70%+ test coverage\n\n### Code Review Standards\n- **Peer Review**: All code must be reviewed\n- **Automated Checks**: ESLint, Prettier, TypeScript\n- **Security Review**: Check for security vulnerabilities\n- **Performance Review**: Check for performance issues\n\n### Commit Standards (Conventional Commits)\n- **Format**: `type(scope): subject` (max 100 characters)\n- **Types**: \n  - `feat` - New feature\n  - `fix` - Bug fix\n  - `docs` - Documentation changes\n  - `style` - Code style changes (formatting, etc)\n  - `refactor` - Code refactoring\n  - `perf` - Performance improvements\n  - `test` - Adding or updating tests\n  - `chore` - Maintenance tasks\n  - `ci` - CI/CD changes\n  - `build` - Build system changes\n  - `revert` - Reverting changes\n  - `security` - Security fixes\n- **Examples**:\n  - `feat(api): add WhatsApp message status endpoint`\n  - `fix(baileys): resolve connection timeout issue`\n  - `docs(readme): update installation instructions`\n  - `refactor(service): extract common message validation logic`\n- **Tools**: Use `npm run commit` (Commitizen) for guided commits\n- **Validation**: Enforced by commitlint on commit-msg hook\n\n## Evolution API Specific Patterns\n\n### WhatsApp Integration Patterns\n- **Connection Management**: One connection per instance\n- **Event Handling**: Proper event listeners for Baileys\n- **Message Processing**: Queue-based message processing\n- **Error Recovery**: Automatic reconnection logic\n\n### Multi-Database Support\n- **Schema Compatibility**: Support PostgreSQL and MySQL\n- **Migration Sync**: Keep migrations synchronized\n- **Type Safety**: Use Prisma generated types\n- **Connection Pooling**: Proper database connection management\n\n### Cache Strategy\n- **Redis Primary**: Use Redis for distributed caching\n- **Local Fallback**: Node-cache for local fallback\n- **TTL Strategy**: Appropriate TTL for different data types\n- **Cache Invalidation**: Proper cache invalidation patterns\n\n### Event System\n- **EventEmitter2**: Use for internal events\n- **WebSocket**: Socket.io for real-time updates\n- **Queue Systems**: RabbitMQ/SQS for async processing\n- **Webhook Processing**: Proper webhook validation and processing",".cursor/rules/cursor.json":"{\n  \"version\": \"1.0\",\n  \"description\": \"Cursor IDE configuration for Evolution API project\",\n  \"rules\": {\n    \"general\": {\n      \"max_line_length\": 120,\n      \"indent_size\": 2,\n      \"end_of_line\": \"lf\",\n      \"charset\": \"utf-8\",\n      \"trim_trailing_whitespace\": true,\n      \"insert_final_newline\": true\n    },\n    \"typescript\": {\n      \"quotes\": \"single\",\n      \"semi\": true,\n      \"trailing_comma\": \"es5\",\n      \"bracket_spacing\": true,\n      \"arrow_parens\": \"avoid\",\n      \"print_width\": 120,\n      \"tab_width\": 2,\n      \"use_tabs\": false,\n      \"single_quote\": true,\n      \"end_of_line\": \"lf\",\n      \"strict\": true,\n      \"no_implicit_any\": true,\n      \"strict_null_checks\": true\n    },\n    \"javascript\": {\n      \"quotes\": \"single\",\n      \"semi\": true,\n      \"trailing_comma\": \"es5\",\n      \"bracket_spacing\": true,\n      \"arrow_parens\": \"avoid\",\n      \"print_width\": 120,\n      \"tab_width\": 2,\n      \"use_tabs\": false,\n      \"single_quote\": true,\n      \"end_of_line\": \"lf\",\n      \"style_guide\": \"eslint-airbnb\"\n    },\n    \"json\": {\n      \"tab_width\": 2,\n      \"use_tabs\": false,\n      \"parser\": \"json\"\n    },\n    \"ignore\": {\n      \"files\": [\n        \"node_modules/**\",\n        \"dist/**\",\n        \"build/**\",\n        \".git/**\",\n        \"*.min.js\",\n        \"*.min.css\",\n        \".env\",\n        \".env.*\",\n        \".env.example\",\n        \"coverage/**\",\n        \"*.log\",\n        \"*.lock\",\n        \"pnpm-lock.yaml\",\n        \"package-lock.json\",\n        \"yarn.lock\",\n        \"log/**\",\n        \"tmp/**\",\n        \"instances/**\",\n        \"public/uploads/**\",\n        \"*.dump\",\n        \"*.rdb\",\n        \"*.mmdb\",\n        \".DS_Store\",\n        \"*.swp\",\n        \"*.swo\",\n        \"*.un~\",\n        \".jest-cache\",\n        \".idea/**\",\n        \".vscode/**\",\n        \".yalc/**\",\n        \"yalc.lock\",\n        \"*.local\",\n        \"prisma/migrations/**\",\n        \"prisma/mysql-migrations/**\",\n        \"prisma/postgresql-migrations/**\"\n      ]\n    },\n    \"search\": {\n      \"exclude_patterns\": [\n        \"**/node_modules/**\",\n        \"**/dist/**\",\n        \"**/build/**\",\n        \"**/.git/**\",\n        \"**/coverage/**\",\n        \"**/log/**\",\n        \"**/tmp/**\",\n        \"**/instances/**\",\n        \"**/public/uploads/**\",\n        \"**/*.min.js\",\n        \"**/*.min.css\",\n        \"**/*.log\",\n        \"**/*.lock\",\n        \"**/pnpm-lock.yaml\",\n        \"**/package-lock.json\",\n        \"**/yarn.lock\",\n        \"**/*.dump\",\n        \"**/*.rdb\",\n        \"**/*.mmdb\",\n        \"**/.DS_Store\",\n        \"**/*.swp\",\n        \"**/*.swo\",\n        \"**/*.un~\",\n        \"**/.jest-cache\",\n        \"**/.idea/**\",\n        \"**/.vscode/**\",\n        \"**/.yalc/**\",\n        \"**/yalc.lock\",\n        \"**/*.local\",\n        \"**/prisma/migrations/**\",\n        \"**/prisma/mysql-migrations/**\",\n        \"**/prisma/postgresql-migrations/**\"\n      ]\n    },\n    \"evolution_api\": {\n      \"project_type\": \"nodejs_typescript_api\",\n      \"backend_framework\": \"express_prisma\",\n      \"database\": [\"postgresql\", \"mysql\"],\n      \"cache\": [\"redis\", \"node_cache\"],\n      \"queue\": [\"rabbitmq\", \"sqs\"],\n      \"real_time\": \"socket_io\",\n      \"file_storage\": [\"aws_s3\", \"minio\"],\n      \"validation\": \"class_validator\",\n      \"logging\": \"pino\",\n      \"main_directories\": {\n        \"source\": \"src/\",\n        \"api\": \"src/api/\",\n        \"controllers\": \"src/api/controllers/\",\n        \"services\": \"src/api/services/\",\n        \"integrations\": \"src/api/integrations/\",\n        \"dto\": \"src/api/dto/\",\n        \"types\": \"src/api/types/\",\n        \"guards\": \"src/api/guards/\",\n        \"routes\": \"src/api/routes/\",\n        \"cache\": \"src/cache/\",\n        \"config\": \"src/config/\",\n        \"utils\": \"src/utils/\",\n        \"exceptions\": \"src/exceptions/\",\n        \"validate\": \"src/validate/\",\n        \"prisma\": \"prisma/\",\n        \"tests\": \"test/\",\n        \"docs\": \"docs/\"\n      },\n      \"key_patterns\": [\n        \"whatsapp_integration\",\n        \"multi_database_support\",\n        \"instance_management\",\n        \"event_driven_architecture\",\n        \"service_layer_pattern\",\n        \"dto_validation\",\n        \"webhook_processing\",\n        \"message_queuing\",\n        \"real_time_communication\",\n        \"file_storage_integration\"\n      ],\n      \"whatsapp_integrations\": [\n        \"baileys\",\n        \"meta_business_api\",\n        \"whatsapp_cloud_api\"\n      ],\n      \"external_integrations\": [\n        \"chatwoot\",\n        \"typebot\",\n        \"openai\",\n        \"dify\",\n        \"rabbitmq\",\n        \"sqs\",\n        \"s3\",\n        \"minio\"\n      ]\n    }\n  }\n}\n",".cursor/rules/project-context.mdc":"---\ndescription: Evolution API project-specific context and constraints\nglobs:\nalwaysApply: true\n---\n\n# Evolution API Project Context\n\n## Cross-References\n- **Core Development**: @core-development.mdc for fundamental development principles\n- **Specialized Rules**: Reference specific specialized rules when working on:\n  - Services: @specialized-rules/service-rules.mdc\n  - Controllers: @specialized-rules/controller-rules.mdc\n  - DTOs: @specialized-rules/dto-rules.mdc\n  - Guards: @specialized-rules/guard-rules.mdc\n  - Routes: @specialized-rules/route-rules.mdc\n  - Types: @specialized-rules/type-rules.mdc\n  - Utils: @specialized-rules/util-rules.mdc\n  - Validation: @specialized-rules/validate-rules.mdc\n  - Channel Integrations: @specialized-rules/integration-channel-rules.mdc\n  - Chatbot Integrations: @specialized-rules/integration-chatbot-rules.mdc\n  - Storage Integrations: @specialized-rules/integration-storage-rules.mdc\n  - Event Integrations: @specialized-rules/integration-event-rules.mdc\n- **TypeScript/Node.js**: Node.js 20+ + TypeScript 5+ backend standards\n- **Express/Prisma**: Express.js + Prisma ORM patterns\n- **WhatsApp Integrations**: Baileys, Meta Business API, and other messaging platforms\n\n## Technology Stack\n- **Backend**: Node.js 20+ + TypeScript 5+ + Express.js\n- **Database**: Prisma ORM (PostgreSQL/MySQL support)\n- **Cache**: Redis + Node-cache for local fallback\n- **Queue**: RabbitMQ + Amazon SQS for message processing\n- **Real-time**: Socket.io for WebSocket connections\n- **Storage**: AWS S3 + Minio for file storage\n- **Validation**: JSONSchema7 for input validation\n- **Logging**: Pino for structured logging\n- **Architecture**: Multi-tenant API with WhatsApp integrations\n\n## Project-Specific Patterns\n\n### WhatsApp Integration Architecture\n- **MANDATORY**: All WhatsApp integrations must follow established patterns\n- **BAILEYS**: Use `whatsapp.baileys.service.ts` patterns for WhatsApp Web\n- **META BUSINESS**: Use `whatsapp.business.service.ts` for official API\n- **CONNECTION MANAGEMENT**: One connection per instance with proper lifecycle\n- **EVENT HANDLING**: Proper event listeners and error handling\n\n### Multi-Database Architecture\n- **CRITICAL**: Support both PostgreSQL and MySQL\n- **SCHEMAS**: Use appropriate schema files (postgresql-schema.prisma / mysql-schema.prisma)\n- **MIGRATIONS**: Keep migrations synchronized between databases\n- **TYPES**: Use database-specific types (@db.JsonB vs @db.Json)\n- **COMPATIBILITY**: Ensure feature parity between databases\n\n### API Integration Workflow\n- **CORE FEATURE**: REST API for WhatsApp communication\n- **COMPLEXITY**: High - involves webhook processing, message routing, and instance management\n- **COMPONENTS**: Instance management, message handling, media processing\n- **INTEGRATIONS**: Baileys, Meta Business API, Chatwoot, Typebot, OpenAI, Dify\n\n### Multi-Tenant Instance Architecture\n- **CRITICAL**: All operations must be scoped by instance\n- **ISOLATION**: Complete data isolation between instances\n- **SECURITY**: Validate instance ownership before operations\n- **SCALING**: Support thousands of concurrent instances\n- **AUTHENTICATION**: API key-based authentication per instance\n\n## Documentation Requirements\n\n### Implementation Documentation\n- **MANDATORY**: Document complex integration patterns\n- **LOCATION**: Use inline comments for business logic\n- **API DOCS**: Document all public endpoints\n- **WEBHOOK DOCS**: Document webhook payloads and signatures\n\n### Change Documentation\n- **CHANGELOG**: Document breaking changes\n- **MIGRATION GUIDES**: Document database migrations\n- **INTEGRATION GUIDES**: Document new integration patterns\n\n## Environment and Security\n\n### Environment Variables\n- **CRITICAL**: Never hardcode sensitive values\n- **VALIDATION**: Validate required environment variables on startup\n- **SECURITY**: Use secure defaults and proper encryption\n- **DOCUMENTATION**: Document all environment variables\n\n### File Organization - Node.js/TypeScript Structure\n- **CONTROLLERS**: Organized by feature (`api/controllers/`)\n- **SERVICES**: Business logic in service classes (`api/services/`)\n- **INTEGRATIONS**: External integrations (`api/integrations/`)\n- **DTOS**: Data transfer objects (`api/dto/`)\n- **TYPES**: TypeScript types (`api/types/`)\n- **UTILS**: Utility functions (`utils/`)\n\n## Integration Points\n\n### WhatsApp Providers\n- **BAILEYS**: WhatsApp Web integration with QR code\n- **META BUSINESS**: Official WhatsApp Business API\n- **CLOUD API**: WhatsApp Cloud API integration\n- **WEBHOOK PROCESSING**: Proper webhook validation and processing\n\n### External Integrations\n- **CHATWOOT**: Customer support platform integration\n- **TYPEBOT**: Chatbot flow integration\n- **OPENAI**: AI-powered chat integration\n- **DIFY**: AI workflow integration\n- **STORAGE**: S3/Minio for media file storage\n\n### Event-Driven Communication\n- **EVENTEMITTER2**: Internal event system\n- **SOCKET.IO**: Real-time WebSocket communication\n- **RABBITMQ**: Message queue for async processing\n- **SQS**: Amazon SQS for cloud-based queuing\n- **WEBHOOKS**: Outbound webhook system\n\n## Development Constraints\n\n### Language Requirements\n- **USER COMMUNICATION**: Always respond in Portuguese (PT-BR)\n- **CODE/COMMENTS**: English for code and technical documentation\n- **API RESPONSES**: English for consistency\n- **ERROR MESSAGES**: Portuguese for user-facing errors\n\n### Performance Constraints\n- **MEMORY**: Efficient memory usage for multiple instances\n- **DATABASE**: Optimized queries with proper indexing\n- **CACHE**: Strategic caching for frequently accessed data\n- **CONNECTIONS**: Proper connection pooling and management\n\n### Security Constraints\n- **AUTHENTICATION**: API key validation for all endpoints\n- **AUTHORIZATION**: Instance-based access control\n- **INPUT VALIDATION**: Validate all inputs with JSONSchema7\n- **RATE LIMITING**: Prevent abuse with rate limiting\n- **WEBHOOK SECURITY**: Validate webhook signatures\n\n## Quality Standards\n- **TYPE SAFETY**: Full TypeScript coverage with strict mode\n- **ERROR HANDLING**: Comprehensive error scenarios with proper logging\n- **TESTING**: Unit and integration tests for critical paths\n- **MONITORING**: Proper logging and error tracking\n- **DOCUMENTATION**: Clear API documentation and code comments\n- **PERFORMANCE**: Optimized for high-throughput message processing\n- **SECURITY**: Secure by default with proper validation\n- **SCALABILITY**: Design for horizontal scaling\n\n## Evolution API Specific Development Patterns\n\n### Instance Management\n- **LIFECYCLE**: Proper instance creation, connection, and cleanup\n- **STATE MANAGEMENT**: Track connection status and health\n- **RECOVERY**: Automatic reconnection and error recovery\n- **MONITORING**: Health checks and status reporting\n\n### Message Processing\n- **QUEUE-BASED**: Use queues for message processing\n- **RETRY LOGIC**: Implement exponential backoff for failures\n- **MEDIA HANDLING**: Proper media upload and processing\n- **WEBHOOK DELIVERY**: Reliable webhook delivery with retries\n\n### Integration Patterns\n- **SERVICE LAYER**: Business logic in service classes\n- **DTO VALIDATION**: Input validation with JSONSchema7\n- **ERROR HANDLING**: Consistent error responses\n- **LOGGING**: Structured logging with correlation IDs\n\n### Database Patterns\n- **PRISMA**: Use Prisma ORM for all database operations\n- **TRANSACTIONS**: Use transactions for multi-step operations\n- **MIGRATIONS**: Proper migration management\n- **INDEXING**: Optimize queries with appropriate indexes",".cursor/rules/specialized-rules/controller-rules.mdc":"---\ndescription: Controller patterns for Evolution API\nglobs:\n  - \"src/api/controllers/**/*.ts\"\n  - \"src/api/integrations/**/controllers/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Controller Rules\n\n## Controller Structure Pattern\n\n### Standard Controller Class\n```typescript\nexport class ExampleController {\n  constructor(private readonly exampleService: ExampleService) {}\n\n  public async createExample(instance: InstanceDto, data: ExampleDto) {\n    return this.exampleService.create(instance, data);\n  }\n\n  public async findExample(instance: InstanceDto) {\n    return this.exampleService.find(instance);\n  }\n}\n```\n\n## Dependency Injection Pattern\n\n### Service Injection\n```typescript\n// CORRECT - Evolution API pattern\nexport class ChatController {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n\n  public async whatsappNumber({ instanceName }: InstanceDto, data: WhatsAppNumberDto) {\n    return await this.waMonitor.waInstances[instanceName].getWhatsAppNumbers(data);\n  }\n}\n\n// INCORRECT - Don't inject multiple services when waMonitor is sufficient\nexport class ChatController {\n  constructor(\n    private readonly waMonitor: WAMonitoringService,\n    private readonly prismaRepository: PrismaRepository, // ❌ Unnecessary if waMonitor has access\n    private readonly configService: ConfigService, // ❌ Unnecessary if waMonitor has access\n  ) {}\n}\n```\n\n## Method Signature Pattern\n\n### Instance Parameter Pattern\n```typescript\n// CORRECT - Evolution API pattern (destructuring instanceName)\npublic async fetchCatalog({ instanceName }: InstanceDto, data: getCatalogDto) {\n  return await this.waMonitor.waInstances[instanceName].fetchCatalog(instanceName, data);\n}\n\n// CORRECT - Alternative pattern for full instance (when using services)\npublic async createTemplate(instance: InstanceDto, data: TemplateDto) {\n  return this.templateService.create(instance, data);\n}\n\n// INCORRECT - Don't use generic method names\npublic async methodName(instance: InstanceDto, data: DataDto) { // ❌ Use specific names\n  return this.service.performAction(instance, data);\n}\n```\n\n## WAMonitor Access Pattern\n\n### Direct WAMonitor Usage\n```typescript\n// CORRECT - Standard pattern in controllers\nexport class CallController {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n\n  public async offerCall({ instanceName }: InstanceDto, data: OfferCallDto) {\n    return await this.waMonitor.waInstances[instanceName].offerCall(data);\n  }\n}\n```\n\n## Controller Registration Pattern\n\n### Server Module Registration\n```typescript\n// In server.module.ts\nexport const templateController = new TemplateController(templateService);\nexport const businessController = new BusinessController(waMonitor);\nexport const chatController = new ChatController(waMonitor);\nexport const callController = new CallController(waMonitor);\n```\n\n## Error Handling in Controllers\n\n### Let Services Handle Errors\n```typescript\n// CORRECT - Let service handle errors\npublic async fetchCatalog(instance: InstanceDto, data: getCatalogDto) {\n  return await this.waMonitor.waInstances[instance.instanceName].fetchCatalog(instance.instanceName, data);\n}\n\n// INCORRECT - Don't add try-catch in controllers unless specific handling needed\npublic async fetchCatalog(instance: InstanceDto, data: getCatalogDto) {\n  try {\n    return await this.waMonitor.waInstances[instance.instanceName].fetchCatalog(instance.instanceName, data);\n  } catch (error) {\n    throw error; // ❌ Unnecessary try-catch\n  }\n}\n```\n\n## Complex Controller Pattern\n\n### Instance Controller Pattern\n```typescript\nexport class InstanceController {\n  constructor(\n    private readonly waMonitor: WAMonitoringService,\n    private readonly configService: ConfigService,\n    private readonly prismaRepository: PrismaRepository,\n    private readonly eventEmitter: EventEmitter2,\n    private readonly chatwootService: ChatwootService,\n    private readonly settingsService: SettingsService,\n    private readonly proxyService: ProxyController,\n    private readonly cache: CacheService,\n    private readonly chatwootCache: CacheService,\n    private readonly baileysCache: CacheService,\n    private readonly providerFiles: ProviderFiles,\n  ) {}\n\n  private readonly logger = new Logger('InstanceController');\n\n  // Multiple methods handling different aspects\n  public async createInstance(data: InstanceDto) {\n    // Complex instance creation logic\n  }\n\n  public async deleteInstance({ instanceName }: InstanceDto) {\n    // Complex instance deletion logic\n  }\n}\n```\n\n## Channel Controller Pattern\n\n### Base Channel Controller\n```typescript\nexport class ChannelController {\n  public prismaRepository: PrismaRepository;\n  public waMonitor: WAMonitoringService;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    this.prisma = prismaRepository;\n    this.monitor = waMonitor;\n  }\n\n  // Getters and setters for dependency access\n  public set prisma(prisma: PrismaRepository) {\n    this.prismaRepository = prisma;\n  }\n\n  public get prisma() {\n    return this.prismaRepository;\n  }\n\n  public set monitor(waMonitor: WAMonitoringService) {\n    this.waMonitor = waMonitor;\n  }\n\n  public get monitor() {\n    return this.waMonitor;\n  }\n}\n```\n\n### Extended Channel Controller\n```typescript\nexport class EvolutionController extends ChannelController implements ChannelControllerInterface {\n  private readonly logger = new Logger('EvolutionController');\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n  }\n\n  integrationEnabled: boolean;\n\n  public async receiveWebhook(data: any) {\n    const numberId = data.numberId;\n\n    if (!numberId) {\n      this.logger.error('WebhookService -> receiveWebhookEvolution -> numberId not found');\n      return;\n    }\n\n    const instance = await this.prismaRepository.instance.findFirst({\n      where: { number: numberId },\n    });\n\n    if (!instance) {\n      this.logger.error('WebhookService -> receiveWebhook -> instance not found');\n      return;\n    }\n\n    await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data);\n\n    return {\n      status: 'success',\n    };\n  }\n}\n```\n\n## Chatbot Controller Pattern\n\n### Base Chatbot Controller\n```typescript\nexport abstract class BaseChatbotController<BotType = any, BotData extends BaseChatbotDto = BaseChatbotDto>\n  extends ChatbotController\n  implements ChatbotControllerInterface\n{\n  public readonly logger: Logger;\n  integrationEnabled: boolean;\n  \n  // Abstract methods to be implemented\n  protected abstract readonly integrationName: string;\n  protected abstract processBot(/* parameters */): Promise<void>;\n  protected abstract getFallbackBotId(settings: any): string | undefined;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n  }\n\n  // Base implementation methods\n  public async createBot(instance: InstanceDto, data: BotData) {\n    // Common bot creation logic\n  }\n}\n```\n\n## Method Naming Conventions\n\n### Standard Method Names\n- `create*()` - Create operations\n- `find*()` - Find operations  \n- `fetch*()` - Fetch from external APIs\n- `send*()` - Send operations\n- `receive*()` - Receive webhook/data\n- `handle*()` - Handle specific actions\n- `offer*()` - Offer services (like calls)\n\n## Return Patterns\n\n### Direct Return Pattern\n```typescript\n// CORRECT - Direct return from service\npublic async createTemplate(instance: InstanceDto, data: TemplateDto) {\n  return this.templateService.create(instance, data);\n}\n\n// CORRECT - Direct return from waMonitor\npublic async offerCall({ instanceName }: InstanceDto, data: OfferCallDto) {\n  return await this.waMonitor.waInstances[instanceName].offerCall(data);\n}\n```\n\n## Controller Testing Pattern\n\n### Unit Test Structure\n```typescript\ndescribe('ExampleController', () => {\n  let controller: ExampleController;\n  let service: jest.Mocked<ExampleService>;\n\n  beforeEach(() => {\n    const mockService = {\n      create: jest.fn(),\n      find: jest.fn(),\n    };\n\n    controller = new ExampleController(mockService as any);\n    service = mockService as any;\n  });\n\n  describe('createExample', () => {\n    it('should call service create method', async () => {\n      const instance = { instanceName: 'test' };\n      const data = { test: 'data' };\n      const expectedResult = { success: true };\n\n      service.create.mockResolvedValue(expectedResult);\n\n      const result = await controller.createExample(instance, data);\n\n      expect(service.create).toHaveBeenCalledWith(instance, data);\n      expect(result).toEqual(expectedResult);\n    });\n  });\n});\n```\n\n## Interface Implementation\n\n### Controller Interface Pattern\n```typescript\nexport interface ChannelControllerInterface {\n  integrationEnabled: boolean;\n}\n\nexport interface ChatbotControllerInterface {\n  integrationEnabled: boolean;\n  createBot(instance: InstanceDto, data: any): Promise<any>;\n  findBot(instance: InstanceDto): Promise<any>;\n  // ... other methods\n}\n```\n\n## Controller Organization\n\n### File Naming Convention\n- `*.controller.ts` - Main controllers\n- `*/*.controller.ts` - Integration-specific controllers\n\n### Method Organization\n1. Constructor\n2. Public methods (alphabetical order)\n3. Private methods (if any)\n\n### Import Organization\n```typescript\n// DTOs first\nimport { InstanceDto } from '@api/dto/instance.dto';\nimport { ExampleDto } from '@api/dto/example.dto';\n\n// Services\nimport { ExampleService } from '@api/services/example.service';\n\n// Types\nimport { WAMonitoringService } from '@api/services/monitor.service';\n```",".cursor/rules/specialized-rules/dto-rules.mdc":"---\ndescription: DTO patterns and validation for Evolution API\nglobs:\n  - \"src/api/dto/**/*.ts\"\n  - \"src/api/integrations/**/dto/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API DTO Rules\n\n## DTO Structure Pattern\n\n### Basic DTO Class\n```typescript\nexport class ExampleDto {\n  name: string;\n  category: string;\n  allowCategoryChange: boolean;\n  language: string;\n  components: any;\n  webhookUrl?: string;\n}\n```\n\n## Inheritance Pattern\n\n### DTO Inheritance\n```typescript\n// CORRECT - Evolution API pattern\nexport class InstanceDto extends IntegrationDto {\n  instanceName: string;\n  instanceId?: string;\n  qrcode?: boolean;\n  businessId?: string;\n  number?: string;\n  integration?: string;\n  token?: string;\n  status?: string;\n  ownerJid?: string;\n  profileName?: string;\n  profilePicUrl?: string;\n  \n  // Settings\n  rejectCall?: boolean;\n  msgCall?: string;\n  groupsIgnore?: boolean;\n  alwaysOnline?: boolean;\n  readMessages?: boolean;\n  readStatus?: boolean;\n  syncFullHistory?: boolean;\n  wavoipToken?: string;\n  \n  // Proxy settings\n  proxyHost?: string;\n  proxyPort?: string;\n  proxyProtocol?: string;\n  proxyUsername?: string;\n  proxyPassword?: string;\n  \n  // Webhook configuration\n  webhook?: {\n    enabled?: boolean;\n    events?: string[];\n    headers?: JsonValue;\n    url?: string;\n    byEvents?: boolean;\n    base64?: boolean;\n  };\n  \n  // Chatwoot integration\n  chatwootAccountId?: string;\n  chatwootConversationPending?: boolean;\n  chatwootAutoCreate?: boolean;\n  chatwootDaysLimitImportMessages?: number;\n  chatwootImportContacts?: boolean;\n  chatwootImportMessages?: boolean;\n  chatwootLogo?: string;\n  chatwootMergeBrazilContacts?: boolean;\n  chatwootNameInbox?: string;\n  chatwootOrganization?: string;\n  chatwootReopenConversation?: boolean;\n  chatwootSignMsg?: boolean;\n  chatwootToken?: string;\n  chatwootUrl?: string;\n}\n```\n\n## Base DTO Pattern\n\n### Base Chatbot DTO\n```typescript\n/**\n * Base DTO for all chatbot integrations\n * Contains common properties shared by all chatbot types\n */\nexport class BaseChatbotDto {\n  enabled?: boolean;\n  description: string;\n  expire?: number;\n  keywordFinish?: string;\n  delayMessage?: number;\n  unknownMessage?: string;\n  listeningFromMe?: boolean;\n  stopBotFromMe?: boolean;\n  keepOpen?: boolean;\n  debounceTime?: number;\n  triggerType: TriggerType;\n  triggerOperator?: TriggerOperator;\n  triggerValue?: string;\n  ignoreJids?: string[];\n  splitMessages?: boolean;\n  timePerChar?: number;\n}\n\n/**\n * Base settings DTO for all chatbot integrations\n */\nexport class BaseChatbotSettingDto {\n  expire?: number;\n  keywordFinish?: string;\n  delayMessage?: number;\n  unknownMessage?: string;\n  listeningFromMe?: boolean;\n  stopBotFromMe?: boolean;\n  keepOpen?: boolean;\n  debounceTime?: number;\n  ignoreJids?: string[];\n  splitMessages?: boolean;\n  timePerChar?: number;\n}\n```\n\n## Message DTO Patterns\n\n### Send Message DTOs\n```typescript\nexport class Metadata {\n  number: string;\n  delay?: number;\n}\n\nexport class SendTextDto extends Metadata {\n  text: string;\n  linkPreview?: boolean;\n  mentionsEveryOne?: boolean;\n  mentioned?: string[];\n}\n\nexport class SendListDto extends Metadata {\n  title: string;\n  description: string;\n  buttonText: string;\n  footerText?: string;\n\n  sections: Section[];\n}\n\nexport class ContactMessage {\n  fullName: string;\n  wuid: string;\n  phoneNumber: string;\n  organization?: string;\n  email?: string;\n  url?: string;\n}\n\nexport class SendTemplateDto extends Metadata {\n  name: string;\n  language: string;\n  components: any;\n}\n```\n\n## Simple DTO Patterns\n\n### Basic DTOs\n```typescript\nexport class NumberDto {\n  number: string;\n}\n\nexport class LabelDto {\n  id?: string;\n  name: string;\n  color: string;\n  predefinedId?: string;\n}\n\nexport class HandleLabelDto {\n  number: string;\n  labelId: string;\n}\n\nexport class ProfileNameDto {\n  name: string;\n}\n\nexport class WhatsAppNumberDto {\n  numbers: string[];\n}\n```\n\n## Complex DTO Patterns\n\n### Business DTOs\n```typescript\nexport class getCatalogDto {\n  number?: string;\n  limit?: number;\n  cursor?: string;\n}\n\nexport class getCollectionsDto {\n  number?: string;\n  limit?: number;\n  cursor?: string;\n}\n\nexport class NumberBusiness {\n  number: string;\n  name?: string;\n  description?: string;\n  email?: string;\n  websites?: string[];\n  latitude?: number;\n  longitude?: number;\n  address?: string;\n  profilehandle?: string;\n}\n```\n\n## Settings DTO Pattern\n\n### Settings Configuration\n```typescript\nexport class SettingsDto {\n  rejectCall?: boolean;\n  msgCall?: string;\n  groupsIgnore?: boolean;\n  alwaysOnline?: boolean;\n  readMessages?: boolean;\n  readStatus?: boolean;\n  syncFullHistory?: boolean;\n  wavoipToken?: string;\n}\n\nexport class ProxyDto {\n  host?: string;\n  port?: string;\n  protocol?: string;\n  username?: string;\n  password?: string;\n}\n```\n\n## Presence DTO Pattern\n\n### WhatsApp Presence\n```typescript\nexport class SetPresenceDto {\n  presence: WAPresence;\n}\n\nexport class SendPresenceDto {\n  number: string;\n  presence: WAPresence;\n}\n```\n\n## DTO Structure (No Decorators)\n\n### Simple DTO Classes (Evolution API Pattern)\n```typescript\n// CORRECT - Evolution API pattern (no decorators)\nexport class ExampleDto {\n  name: string;\n  description?: string;\n  enabled: boolean;\n  items?: string[];\n  timeout?: number;\n}\n\n// INCORRECT - Don't use class-validator decorators\nexport class ValidatedDto {\n  @IsString() // ❌ Evolution API doesn't use decorators\n  name: string;\n}\n```\n\n## Type Safety Patterns\n\n### Prisma Type Integration\n```typescript\nimport { JsonValue } from '@prisma/client/runtime/library';\nimport { WAPresence } from 'baileys';\nimport { TriggerOperator, TriggerType } from '@prisma/client';\n\nexport class TypeSafeDto {\n  presence: WAPresence;\n  triggerType: TriggerType;\n  triggerOperator?: TriggerOperator;\n  metadata?: JsonValue;\n}\n```\n\n## DTO Documentation\n\n### JSDoc Comments\n```typescript\n/**\n * DTO for creating WhatsApp templates\n * Used by Meta Business API integration\n */\nexport class TemplateDto {\n  /** Template name - must be unique */\n  name: string;\n  \n  /** Template category (MARKETING, UTILITY, AUTHENTICATION) */\n  category: string;\n  \n  /** Whether category can be changed after creation */\n  allowCategoryChange: boolean;\n  \n  /** Language code (e.g., 'pt_BR', 'en_US') */\n  language: string;\n  \n  /** Template components (header, body, footer, buttons) */\n  components: any;\n  \n  /** Optional webhook URL for template status updates */\n  webhookUrl?: string;\n}\n```\n\n## DTO Naming Conventions\n\n### Standard Naming Patterns\n- `*Dto` - Data transfer objects\n- `Create*Dto` - Creation DTOs\n- `Update*Dto` - Update DTOs\n- `Send*Dto` - Message sending DTOs\n- `Get*Dto` - Query DTOs\n- `Handle*Dto` - Action DTOs\n\n## File Organization\n\n### DTO File Structure\n```\nsrc/api/dto/\n├── instance.dto.ts          # Main instance DTO\n├── template.dto.ts          # Template management\n├── sendMessage.dto.ts       # Message sending DTOs\n├── chat.dto.ts             # Chat operations\n├── business.dto.ts         # Business API DTOs\n├── group.dto.ts            # Group management\n├── label.dto.ts            # Label management\n├── proxy.dto.ts            # Proxy configuration\n├── settings.dto.ts         # Instance settings\n└── call.dto.ts             # Call operations\n```\n\n## Integration DTO Patterns\n\n### Chatbot Integration DTOs\n```typescript\n// Base for all chatbot DTOs\nexport class BaseChatbotDto {\n  enabled?: boolean;\n  description: string;\n  // ... common properties\n}\n\n// Specific chatbot DTOs extend base\nexport class TypebotDto extends BaseChatbotDto {\n  url: string;\n  typebot: string;\n  // ... typebot-specific properties\n}\n\nexport class OpenaiDto extends BaseChatbotDto {\n  apiKey: string;\n  model: string;\n  // ... openai-specific properties\n}\n```\n\n## DTO Testing Pattern\n\n### DTO Validation Tests\n```typescript\ndescribe('ExampleDto', () => {\n  it('should validate required fields', () => {\n    const dto = new ExampleDto();\n    dto.name = 'test';\n    dto.category = 'MARKETING';\n    dto.allowCategoryChange = true;\n    dto.language = 'pt_BR';\n    dto.components = {};\n\n    expect(dto.name).toBe('test');\n    expect(dto.category).toBe('MARKETING');\n  });\n\n  it('should handle optional fields', () => {\n    const dto = new ExampleDto();\n    dto.name = 'test';\n    dto.category = 'MARKETING';\n    dto.allowCategoryChange = true;\n    dto.language = 'pt_BR';\n    dto.components = {};\n    dto.webhookUrl = 'https://example.com/webhook';\n\n    expect(dto.webhookUrl).toBe('https://example.com/webhook');\n  });\n});\n```\n\n## DTO Transformation\n\n### Request to DTO Mapping (Evolution API Pattern)\n```typescript\n// CORRECT - Evolution API uses RouterBroker dataValidate\nconst response = await this.dataValidate<ExampleDto>({\n  request: req,\n  schema: exampleSchema, // JSONSchema7\n  ClassRef: ExampleDto,\n  execute: (instance, data) => controller.method(instance, data),\n});\n\n// INCORRECT - Don't use class-validator\nconst dto = plainToClass(ExampleDto, req.body); // ❌ Not used in Evolution API\nconst errors = await validate(dto); // ❌ Not used in Evolution API\n```",".cursor/rules/specialized-rules/guard-rules.mdc":"---\ndescription: Guard patterns for authentication and authorization in Evolution API\nglobs:\n  - \"src/api/guards/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Guard Rules\n\n## Guard Structure Pattern\n\n### Standard Guard Function\n```typescript\nimport { NextFunction, Request, Response } from 'express';\nimport { Logger } from '@config/logger.config';\nimport { UnauthorizedException, ForbiddenException } from '@exceptions';\n\nconst logger = new Logger('GUARD');\n\nasync function guardFunction(req: Request, _: Response, next: NextFunction) {\n  // Guard logic here\n  \n  if (validationFails) {\n    throw new UnauthorizedException();\n  }\n  \n  return next();\n}\n\nexport const guardName = { guardFunction };\n```\n\n## Authentication Guard Pattern\n\n### API Key Authentication\n```typescript\nasync function apikey(req: Request, _: Response, next: NextFunction) {\n  const env = configService.get<Auth>('AUTHENTICATION').API_KEY;\n  const key = req.get('apikey');\n  const db = configService.get<Database>('DATABASE');\n\n  if (!key) {\n    throw new UnauthorizedException();\n  }\n\n  // Global API key check\n  if (env.KEY === key) {\n    return next();\n  }\n\n  // Special routes handling\n  if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) {\n    throw new ForbiddenException('Missing global api key', 'The global api key must be set');\n  }\n\n  const param = req.params as unknown as InstanceDto;\n\n  try {\n    if (param?.instanceName) {\n      const instance = await prismaRepository.instance.findUnique({\n        where: { name: param.instanceName },\n      });\n      if (instance.token === key) {\n        return next();\n      }\n    } else {\n      if (req.originalUrl.includes('/instance/fetchInstances') && db.SAVE_DATA.INSTANCE) {\n        const instanceByKey = await prismaRepository.instance.findFirst({\n          where: { token: key },\n        });\n        if (instanceByKey) {\n          return next();\n        }\n      }\n    }\n  } catch (error) {\n    logger.error(error);\n  }\n\n  throw new UnauthorizedException();\n}\n\nexport const authGuard = { apikey };\n```\n\n## Instance Validation Guards\n\n### Instance Exists Guard\n```typescript\nasync function getInstance(instanceName: string) {\n  try {\n    const cacheConf = configService.get<CacheConf>('CACHE');\n\n    const exists = !!waMonitor.waInstances[instanceName];\n\n    if (cacheConf.REDIS.ENABLED && cacheConf.REDIS.SAVE_INSTANCES) {\n      const keyExists = await cache.has(instanceName);\n      return exists || keyExists;\n    }\n\n    return exists || (await prismaRepository.instance.findMany({ where: { name: instanceName } })).length > 0;\n  } catch (error) {\n    throw new InternalServerErrorException(error?.toString());\n  }\n}\n\nexport async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) {\n  if (req.originalUrl.includes('/instance/create')) {\n    return next();\n  }\n\n  const param = req.params as unknown as InstanceDto;\n  if (!param?.instanceName) {\n    throw new BadRequestException('\"instanceName\" not provided.');\n  }\n\n  if (!(await getInstance(param.instanceName))) {\n    throw new NotFoundException(`The \"${param.instanceName}\" instance does not exist`);\n  }\n\n  next();\n}\n```\n\n### Instance Logged Guard\n```typescript\nexport async function instanceLoggedGuard(req: Request, _: Response, next: NextFunction) {\n  if (req.originalUrl.includes('/instance/create')) {\n    const instance = req.body as InstanceDto;\n    if (await getInstance(instance.instanceName)) {\n      throw new ForbiddenException(`This name \"${instance.instanceName}\" is already in use.`);\n    }\n\n    if (waMonitor.waInstances[instance.instanceName]) {\n      delete waMonitor.waInstances[instance.instanceName];\n    }\n  }\n\n  next();\n}\n```\n\n## Telemetry Guard Pattern\n\n### Telemetry Collection\n```typescript\nclass Telemetry {\n  public collectTelemetry(req: Request, res: Response, next: NextFunction): void {\n    // Collect telemetry data\n    const telemetryData = {\n      route: req.originalUrl,\n      method: req.method,\n      timestamp: new Date(),\n      userAgent: req.get('User-Agent'),\n    };\n\n    // Send telemetry asynchronously (don't block request)\n    setImmediate(() => {\n      this.sendTelemetry(telemetryData);\n    });\n\n    next();\n  }\n\n  private async sendTelemetry(data: any): Promise<void> {\n    try {\n      // Send telemetry data\n    } catch (error) {\n      // Silently fail - don't affect main request\n    }\n  }\n}\n\nexport default Telemetry;\n```\n\n## Guard Composition Pattern\n\n### Multiple Guards Usage\n```typescript\n// In router setup\nconst guards = [instanceExistsGuard, instanceLoggedGuard, authGuard['apikey']];\n\nrouter\n  .use('/instance', new InstanceRouter(configService, ...guards).router)\n  .use('/message', new MessageRouter(...guards).router)\n  .use('/chat', new ChatRouter(...guards).router);\n```\n\n## Error Handling in Guards\n\n### Proper Exception Throwing\n```typescript\n// CORRECT - Use proper HTTP exceptions\nif (!apiKey) {\n  throw new UnauthorizedException('API key required');\n}\n\nif (instanceExists) {\n  throw new ForbiddenException('Instance already exists');\n}\n\nif (!instanceFound) {\n  throw new NotFoundException('Instance not found');\n}\n\nif (validationFails) {\n  throw new BadRequestException('Invalid request parameters');\n}\n\n// INCORRECT - Don't use generic Error\nif (!apiKey) {\n  throw new Error('API key required'); // ❌ Use specific exceptions\n}\n```\n\n## Configuration Access in Guards\n\n### Config Service Usage\n```typescript\nasync function configAwareGuard(req: Request, _: Response, next: NextFunction) {\n  const authConfig = configService.get<Auth>('AUTHENTICATION');\n  const cacheConfig = configService.get<CacheConf>('CACHE');\n  const dbConfig = configService.get<Database>('DATABASE');\n\n  // Use configuration for guard logic\n  if (authConfig.API_KEY.KEY === providedKey) {\n    return next();\n  }\n\n  throw new UnauthorizedException();\n}\n```\n\n## Database Access in Guards\n\n### Prisma Repository Usage\n```typescript\nasync function databaseGuard(req: Request, _: Response, next: NextFunction) {\n  try {\n    const param = req.params as unknown as InstanceDto;\n    \n    const instance = await prismaRepository.instance.findUnique({\n      where: { name: param.instanceName },\n    });\n\n    if (!instance) {\n      throw new NotFoundException('Instance not found');\n    }\n\n    // Additional validation logic\n    if (instance.status !== 'active') {\n      throw new ForbiddenException('Instance not active');\n    }\n\n    return next();\n  } catch (error) {\n    logger.error('Database guard error:', error);\n    throw new InternalServerErrorException('Database access failed');\n  }\n}\n```\n\n## Cache Integration in Guards\n\n### Cache Service Usage\n```typescript\nasync function cacheAwareGuard(req: Request, _: Response, next: NextFunction) {\n  const cacheConf = configService.get<CacheConf>('CACHE');\n  \n  if (cacheConf.REDIS.ENABLED) {\n    const cached = await cache.get(`guard:${req.params.instanceName}`);\n    if (cached) {\n      // Use cached validation result\n      return next();\n    }\n  }\n\n  // Perform validation and cache result\n  const isValid = await performValidation(req.params.instanceName);\n  \n  if (cacheConf.REDIS.ENABLED) {\n    await cache.set(`guard:${req.params.instanceName}`, isValid, 300); // 5 min TTL\n  }\n\n  if (isValid) {\n    return next();\n  }\n\n  throw new UnauthorizedException();\n}\n```\n\n## Logging in Guards\n\n### Structured Logging\n```typescript\nconst logger = new Logger('GUARD');\n\nasync function loggedGuard(req: Request, _: Response, next: NextFunction) {\n  logger.log(`Guard validation started for ${req.originalUrl}`);\n\n  try {\n    // Guard logic\n    const isValid = await validateRequest(req);\n    \n    if (isValid) {\n      logger.log(`Guard validation successful for ${req.params.instanceName}`);\n      return next();\n    }\n\n    logger.warn(`Guard validation failed for ${req.params.instanceName}`);\n    throw new UnauthorizedException();\n  } catch (error) {\n    logger.error(`Guard validation error: ${error.message}`, error.stack);\n    throw error;\n  }\n}\n```\n\n## Guard Testing Pattern\n\n### Unit Test Structure\n```typescript\ndescribe('authGuard', () => {\n  let req: Partial<Request>;\n  let res: Partial<Response>;\n  let next: NextFunction;\n\n  beforeEach(() => {\n    req = {\n      get: jest.fn(),\n      params: {},\n      originalUrl: '/test',\n    };\n    res = {};\n    next = jest.fn();\n  });\n\n  describe('apikey', () => {\n    it('should pass with valid global API key', async () => {\n      (req.get as jest.Mock).mockReturnValue('valid-global-key');\n      \n      await authGuard.apikey(req as Request, res as Response, next);\n      \n      expect(next).toHaveBeenCalled();\n    });\n\n    it('should throw UnauthorizedException with no API key', async () => {\n      (req.get as jest.Mock).mockReturnValue(undefined);\n      \n      await expect(\n        authGuard.apikey(req as Request, res as Response, next)\n      ).rejects.toThrow(UnauthorizedException);\n    });\n\n    it('should pass with valid instance token', async () => {\n      (req.get as jest.Mock).mockReturnValue('instance-token');\n      req.params = { instanceName: 'test-instance' };\n      \n      // Mock prisma repository\n      jest.spyOn(prismaRepository.instance, 'findUnique').mockResolvedValue({\n        token: 'instance-token',\n      } as any);\n      \n      await authGuard.apikey(req as Request, res as Response, next);\n      \n      expect(next).toHaveBeenCalled();\n    });\n  });\n});\n```\n\n## Guard Performance Considerations\n\n### Efficient Validation\n```typescript\n// CORRECT - Efficient guard with early returns\nasync function efficientGuard(req: Request, _: Response, next: NextFunction) {\n  // Quick checks first\n  if (req.originalUrl.includes('/public')) {\n    return next(); // Skip validation for public routes\n  }\n\n  const apiKey = req.get('apikey');\n  if (!apiKey) {\n    throw new UnauthorizedException(); // Fail fast\n  }\n\n  // More expensive checks only if needed\n  if (apiKey === globalKey) {\n    return next(); // Skip database check\n  }\n\n  // Database check only as last resort\n  const isValid = await validateInDatabase(apiKey);\n  if (isValid) {\n    return next();\n  }\n\n  throw new UnauthorizedException();\n}\n\n// INCORRECT - Inefficient guard\nasync function inefficientGuard(req: Request, _: Response, next: NextFunction) {\n  // Always do expensive database check first\n  const dbResult = await expensiveDatabaseQuery(); // ❌ Expensive operation first\n  \n  const apiKey = req.get('apikey');\n  if (!apiKey && dbResult) { // ❌ Complex logic\n    throw new UnauthorizedException();\n  }\n  \n  next();\n}\n```",".cursor/rules/specialized-rules/integration-channel-rules.mdc":"---\ndescription: Channel integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/channel/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Channel Integration Rules\n\n## Channel Controller Pattern\n\n### Base Channel Controller\n```typescript\nexport interface ChannelControllerInterface {\n  integrationEnabled: boolean;\n}\n\nexport class ChannelController {\n  public prismaRepository: PrismaRepository;\n  public waMonitor: WAMonitoringService;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    this.prisma = prismaRepository;\n    this.monitor = waMonitor;\n  }\n\n  public set prisma(prisma: PrismaRepository) {\n    this.prismaRepository = prisma;\n  }\n\n  public get prisma() {\n    return this.prismaRepository;\n  }\n\n  public set monitor(waMonitor: WAMonitoringService) {\n    this.waMonitor = waMonitor;\n  }\n\n  public get monitor() {\n    return this.waMonitor;\n  }\n\n  public init(instanceData: InstanceDto, data: ChannelDataType) {\n    if (!instanceData.token && instanceData.integration === Integration.WHATSAPP_BUSINESS) {\n      throw new BadRequestException('token is required');\n    }\n\n    if (instanceData.integration === Integration.WHATSAPP_BUSINESS) {\n      return new BusinessStartupService(/* dependencies */);\n    }\n\n    if (instanceData.integration === Integration.EVOLUTION) {\n      return new EvolutionStartupService(/* dependencies */);\n    }\n\n    if (instanceData.integration === Integration.WHATSAPP_BAILEYS) {\n      return new BaileysStartupService(/* dependencies */);\n    }\n\n    return null;\n  }\n}\n```\n\n### Extended Channel Controller\n```typescript\nexport class EvolutionController extends ChannelController implements ChannelControllerInterface {\n  private readonly logger = new Logger('EvolutionController');\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n  }\n\n  integrationEnabled: boolean;\n\n  public async receiveWebhook(data: any) {\n    const numberId = data.numberId;\n\n    if (!numberId) {\n      this.logger.error('WebhookService -> receiveWebhookEvolution -> numberId not found');\n      return;\n    }\n\n    const instance = await this.prismaRepository.instance.findFirst({\n      where: { number: numberId },\n    });\n\n    if (!instance) {\n      this.logger.error('WebhookService -> receiveWebhook -> instance not found');\n      return;\n    }\n\n    await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data);\n\n    return {\n      status: 'success',\n    };\n  }\n}\n```\n\n## Channel Service Pattern\n\n### Base Channel Service\n```typescript\nexport class ChannelStartupService {\n  constructor(\n    private readonly configService: ConfigService,\n    private readonly eventEmitter: EventEmitter2,\n    private readonly prismaRepository: PrismaRepository,\n    public readonly cache: CacheService,\n    public readonly chatwootCache: CacheService,\n  ) {}\n\n  public readonly logger = new Logger('ChannelStartupService');\n\n  public client: WASocket;\n  public readonly instance: wa.Instance = {};\n  public readonly localChatwoot: wa.LocalChatwoot = {};\n  public readonly localProxy: wa.LocalProxy = {};\n  public readonly localSettings: wa.LocalSettings = {};\n  public readonly localWebhook: wa.LocalWebHook = {};\n\n  public setInstance(instance: InstanceDto) {\n    this.logger.setInstance(instance.instanceName);\n\n    this.instance.name = instance.instanceName;\n    this.instance.id = instance.instanceId;\n    this.instance.integration = instance.integration;\n    this.instance.number = instance.number;\n    this.instance.token = instance.token;\n    this.instance.businessId = instance.businessId;\n\n    if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {\n      this.chatwootService.eventWhatsapp(\n        Events.STATUS_INSTANCE,\n        { instanceName: this.instance.name },\n        {\n          instance: this.instance.name,\n          status: 'created',\n        },\n      );\n    }\n  }\n\n  public set instanceName(name: string) {\n    this.logger.setInstance(name);\n    this.instance.name = name;\n  }\n\n  public get instanceName() {\n    return this.instance.name;\n  }\n}\n```\n\n### Extended Channel Service\n```typescript\nexport class EvolutionStartupService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache);\n  }\n\n  public async sendMessage(data: SendTextDto): Promise<any> {\n    // Evolution-specific message sending logic\n    const response = await this.evolutionApiCall('/send-message', data);\n    return response;\n  }\n\n  public async connectToWhatsapp(data: any): Promise<void> {\n    // Evolution-specific connection logic\n    this.logger.log('Connecting to Evolution API');\n    \n    // Set up webhook listeners\n    this.setupWebhookHandlers();\n    \n    // Initialize connection\n    await this.initializeConnection(data);\n  }\n\n  private async evolutionApiCall(endpoint: string, data: any): Promise<any> {\n    const config = this.configService.get<Evolution>('EVOLUTION');\n    \n    try {\n      const response = await axios.post(`${config.API_URL}${endpoint}`, data, {\n        headers: {\n          'Authorization': `Bearer ${this.instance.token}`,\n          'Content-Type': 'application/json',\n        },\n      });\n      \n      return response.data;\n    } catch (error) {\n      this.logger.error(`Evolution API call failed: ${error.message}`);\n      throw new InternalServerErrorException('Evolution API call failed');\n    }\n  }\n\n  private setupWebhookHandlers(): void {\n    // Set up webhook event handlers\n  }\n\n  private async initializeConnection(data: any): Promise<void> {\n    // Initialize connection with Evolution API\n  }\n}\n```\n\n## Business API Service Pattern\n\n### Meta Business Service\n```typescript\nexport class BusinessStartupService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n    baileysCache: CacheService,\n    providerFiles: ProviderFiles,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache);\n  }\n\n  public async sendMessage(data: SendTextDto): Promise<any> {\n    const businessConfig = this.configService.get<WaBusiness>('WA_BUSINESS');\n    \n    const payload = {\n      messaging_product: 'whatsapp',\n      to: data.number,\n      type: 'text',\n      text: {\n        body: data.text,\n      },\n    };\n\n    try {\n      const response = await axios.post(\n        `${businessConfig.URL}/${businessConfig.VERSION}/${this.instance.businessId}/messages`,\n        payload,\n        {\n          headers: {\n            'Authorization': `Bearer ${this.instance.token}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      return response.data;\n    } catch (error) {\n      this.logger.error(`Business API call failed: ${error.message}`);\n      throw new BadRequestException('Failed to send message via Business API');\n    }\n  }\n\n  public async receiveWebhook(data: any): Promise<void> {\n    // Process incoming webhook from Meta Business API\n    const { entry } = data;\n    \n    for (const entryItem of entry) {\n      const { changes } = entryItem;\n      \n      for (const change of changes) {\n        if (change.field === 'messages') {\n          await this.processMessage(change.value);\n        }\n      }\n    }\n  }\n\n  private async processMessage(messageData: any): Promise<void> {\n    // Process incoming message from Business API\n    const { messages, contacts } = messageData;\n    \n    if (messages) {\n      for (const message of messages) {\n        await this.handleIncomingMessage(message, contacts);\n      }\n    }\n  }\n\n  private async handleIncomingMessage(message: any, contacts: any[]): Promise<void> {\n    // Handle individual message\n    const contact = contacts?.find(c => c.wa_id === message.from);\n    \n    // Emit event for message processing\n    this.eventEmitter.emit(Events.MESSAGES_UPSERT, {\n      instanceName: this.instance.name,\n      message,\n      contact,\n    });\n  }\n}\n```\n\n## Baileys Service Pattern\n\n### Baileys Integration Service\n```typescript\nexport class BaileysStartupService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n    baileysCache: CacheService,\n    providerFiles: ProviderFiles,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache);\n  }\n\n  public async connectToWhatsapp(): Promise<void> {\n    const authPath = path.join(INSTANCE_DIR, this.instance.name);\n    const { state, saveCreds } = await useMultiFileAuthState(authPath);\n\n    this.client = makeWASocket({\n      auth: state,\n      logger: P({ level: 'error' }),\n      printQRInTerminal: false,\n      browser: ['Evolution API', 'Chrome', '4.0.0'],\n      defaultQueryTimeoutMs: 60000,\n    });\n\n    this.setupEventHandlers();\n    this.client.ev.on('creds.update', saveCreds);\n  }\n\n  private setupEventHandlers(): void {\n    this.client.ev.on('connection.update', (update) => {\n      this.handleConnectionUpdate(update);\n    });\n\n    this.client.ev.on('messages.upsert', ({ messages, type }) => {\n      this.handleIncomingMessages(messages, type);\n    });\n\n    this.client.ev.on('messages.update', (updates) => {\n      this.handleMessageUpdates(updates);\n    });\n\n    this.client.ev.on('contacts.upsert', (contacts) => {\n      this.handleContactsUpdate(contacts);\n    });\n\n    this.client.ev.on('chats.upsert', (chats) => {\n      this.handleChatsUpdate(chats);\n    });\n  }\n\n  private async handleConnectionUpdate(update: ConnectionUpdate): Promise<void> {\n    const { connection, lastDisconnect, qr } = update;\n\n    if (qr) {\n      this.instance.qrcode = {\n        count: this.instance.qrcode?.count ? this.instance.qrcode.count + 1 : 1,\n        base64: qr,\n      };\n\n      this.eventEmitter.emit(Events.QRCODE_UPDATED, {\n        instanceName: this.instance.name,\n        qrcode: this.instance.qrcode,\n      });\n    }\n\n    if (connection === 'close') {\n      const shouldReconnect = (lastDisconnect?.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut;\n      \n      if (shouldReconnect) {\n        this.logger.log('Connection closed, reconnecting...');\n        await this.connectToWhatsapp();\n      } else {\n        this.logger.log('Connection closed, logged out');\n        this.eventEmitter.emit(Events.LOGOUT_INSTANCE, {\n          instanceName: this.instance.name,\n        });\n      }\n    }\n\n    if (connection === 'open') {\n      this.logger.log('Connection opened successfully');\n      this.instance.wuid = this.client.user?.id;\n      \n      this.eventEmitter.emit(Events.CONNECTION_UPDATE, {\n        instanceName: this.instance.name,\n        state: 'open',\n      });\n    }\n  }\n\n  public async sendMessage(data: SendTextDto): Promise<any> {\n    const jid = createJid(data.number);\n    \n    const message = {\n      text: data.text,\n    };\n\n    if (data.linkPreview !== undefined) {\n      message.linkPreview = data.linkPreview;\n    }\n\n    if (data.mentionsEveryOne) {\n      // Handle mentions\n    }\n\n    try {\n      const response = await this.client.sendMessage(jid, message);\n      return response;\n    } catch (error) {\n      this.logger.error(`Failed to send message: ${error.message}`);\n      throw new BadRequestException('Failed to send message');\n    }\n  }\n}\n```\n\n## Channel Router Pattern\n\n### Channel Router Structure\n```typescript\nexport class ChannelRouter {\n  public readonly router: Router;\n\n  constructor(configService: any, ...guards: any[]) {\n    this.router = Router();\n\n    this.router.use('/', new EvolutionRouter(configService).router);\n    this.router.use('/', new MetaRouter(configService).router);\n    this.router.use('/baileys', new BaileysRouter(...guards).router);\n  }\n}\n```\n\n### Specific Channel Router\n```typescript\nexport class EvolutionRouter extends RouterBroker {\n  constructor(private readonly configService: ConfigService) {\n    super();\n    this.router\n      .post(this.routerPath('webhook'), async (req, res) => {\n        const response = await evolutionController.receiveWebhook(req.body);\n        return res.status(HttpStatus.OK).json(response);\n      });\n  }\n\n  public readonly router: Router = Router();\n}\n```\n\n## Integration Types\n\n### Channel Data Types\n```typescript\ntype ChannelDataType = {\n  configService: ConfigService;\n  eventEmitter: EventEmitter2;\n  prismaRepository: PrismaRepository;\n  cache: CacheService;\n  chatwootCache: CacheService;\n  baileysCache: CacheService;\n  providerFiles: ProviderFiles;\n};\n\nexport enum Integration {\n  WHATSAPP_BUSINESS = 'WHATSAPP-BUSINESS',\n  WHATSAPP_BAILEYS = 'WHATSAPP-BAILEYS',\n  EVOLUTION = 'EVOLUTION',\n}\n```\n\n## Error Handling in Channels\n\n### Channel-Specific Error Handling\n```typescript\n// CORRECT - Channel-specific error handling\npublic async sendMessage(data: SendTextDto): Promise<any> {\n  try {\n    const response = await this.channelSpecificSend(data);\n    return response;\n  } catch (error) {\n    this.logger.error(`${this.constructor.name} send failed: ${error.message}`);\n    \n    if (error.response?.status === 401) {\n      throw new UnauthorizedException('Invalid token for channel');\n    }\n    \n    if (error.response?.status === 429) {\n      throw new BadRequestException('Rate limit exceeded');\n    }\n    \n    throw new InternalServerErrorException('Channel communication failed');\n  }\n}\n```\n\n## Channel Testing Pattern\n\n### Channel Service Testing\n```typescript\ndescribe('EvolutionStartupService', () => {\n  let service: EvolutionStartupService;\n  let configService: jest.Mocked<ConfigService>;\n  let eventEmitter: jest.Mocked<EventEmitter2>;\n\n  beforeEach(() => {\n    const mockConfig = {\n      get: jest.fn().mockReturnValue({\n        API_URL: 'https://api.evolution.com',\n      }),\n    };\n\n    service = new EvolutionStartupService(\n      mockConfig as any,\n      eventEmitter,\n      prismaRepository,\n      cache,\n      chatwootCache,\n    );\n  });\n\n  describe('sendMessage', () => {\n    it('should send message successfully', async () => {\n      const data = { number: '5511999999999', text: 'Test message' };\n      \n      // Mock axios response\n      jest.spyOn(axios, 'post').mockResolvedValue({\n        data: { success: true, messageId: '123' },\n      });\n\n      const result = await service.sendMessage(data);\n\n      expect(result.success).toBe(true);\n      expect(axios.post).toHaveBeenCalledWith(\n        expect.stringContaining('/send-message'),\n        data,\n        expect.objectContaining({\n          headers: expect.objectContaining({\n            'Authorization': expect.stringContaining('Bearer'),\n          }),\n        })\n      );\n    });\n  });\n});\n```",".cursor/rules/specialized-rules/integration-chatbot-rules.mdc":"---\ndescription: Chatbot integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/chatbot/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Chatbot Integration Rules\n\n## Base Chatbot Pattern\n\n### Base Chatbot DTO\n```typescript\n/**\n * Base DTO for all chatbot integrations\n * Contains common properties shared by all chatbot types\n */\nexport class BaseChatbotDto {\n  enabled?: boolean;\n  description: string;\n  expire?: number;\n  keywordFinish?: string;\n  delayMessage?: number;\n  unknownMessage?: string;\n  listeningFromMe?: boolean;\n  stopBotFromMe?: boolean;\n  keepOpen?: boolean;\n  debounceTime?: number;\n  triggerType: TriggerType;\n  triggerOperator?: TriggerOperator;\n  triggerValue?: string;\n  ignoreJids?: string[];\n  splitMessages?: boolean;\n  timePerChar?: number;\n}\n```\n\n### Base Chatbot Controller\n```typescript\nexport abstract class BaseChatbotController<BotType = any, BotData extends BaseChatbotDto = BaseChatbotDto>\n  extends ChatbotController\n  implements ChatbotControllerInterface\n{\n  public readonly logger: Logger;\n  integrationEnabled: boolean;\n  botRepository: any;\n  settingsRepository: any;\n  sessionRepository: any;\n  userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};\n\n  // Abstract methods to be implemented by specific chatbots\n  protected abstract readonly integrationName: string;\n  protected abstract processBot(\n    waInstance: any,\n    remoteJid: string,\n    bot: BotType,\n    session: any,\n    settings: ChatbotSettings,\n    content: string,\n    pushName?: string,\n    msg?: any,\n  ): Promise<void>;\n  protected abstract getFallbackBotId(settings: any): string | undefined;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n    this.sessionRepository = this.prismaRepository.integrationSession;\n  }\n\n  // Base implementation methods\n  public async createBot(instance: InstanceDto, data: BotData) {\n    if (!data.enabled) {\n      throw new BadRequestException(`${this.integrationName} is disabled`);\n    }\n\n    // Common bot creation logic\n    const bot = await this.botRepository.create({\n      data: {\n        ...data,\n        instanceId: instance.instanceId,\n      },\n    });\n\n    return bot;\n  }\n}\n```\n\n### Base Chatbot Service\n```typescript\n/**\n * Base class for all chatbot service implementations\n * Contains common methods shared across different chatbot integrations\n */\nexport abstract class BaseChatbotService<BotType = any, SettingsType = any> {\n  protected readonly logger: Logger;\n  protected readonly waMonitor: WAMonitoringService;\n  protected readonly prismaRepository: PrismaRepository;\n  protected readonly configService?: ConfigService;\n\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    loggerName: string,\n    configService?: ConfigService,\n  ) {\n    this.waMonitor = waMonitor;\n    this.prismaRepository = prismaRepository;\n    this.logger = new Logger(loggerName);\n    this.configService = configService;\n  }\n\n  /**\n   * Check if a message contains an image\n   */\n  protected isImageMessage(content: string): boolean {\n    return content.includes('imageMessage');\n  }\n\n  /**\n   * Extract text content from message\n   */\n  protected getMessageContent(msg: any): string {\n    return getConversationMessage(msg);\n  }\n\n  /**\n   * Send typing indicator\n   */\n  protected async sendTyping(instanceName: string, remoteJid: string): Promise<void> {\n    await this.waMonitor.waInstances[instanceName].sendPresence(remoteJid, 'composing');\n  }\n}\n```\n\n## Typebot Integration Pattern\n\n### Typebot Service\n```typescript\nexport class TypebotService extends BaseChatbotService<TypebotModel, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    configService: ConfigService,\n    prismaRepository: PrismaRepository,\n    private readonly openaiService: OpenaiService,\n  ) {\n    super(waMonitor, prismaRepository, 'TypebotService', configService);\n  }\n\n  public async sendTypebotMessage(\n    instanceName: string,\n    remoteJid: string,\n    typebot: TypebotModel,\n    content: string,\n  ): Promise<void> {\n    try {\n      const response = await axios.post(\n        `${typebot.url}/api/v1/typebots/${typebot.typebot}/startChat`,\n        {\n          message: content,\n          sessionId: `${instanceName}-${remoteJid}`,\n        },\n        {\n          headers: {\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      const { messages } = response.data;\n      \n      for (const message of messages) {\n        await this.processTypebotMessage(instanceName, remoteJid, message);\n      }\n    } catch (error) {\n      this.logger.error(`Typebot API error: ${error.message}`);\n      throw new InternalServerErrorException('Typebot communication failed');\n    }\n  }\n\n  private async processTypebotMessage(\n    instanceName: string,\n    remoteJid: string,\n    message: any,\n  ): Promise<void> {\n    const waInstance = this.waMonitor.waInstances[instanceName];\n    \n    if (message.type === 'text') {\n      await waInstance.sendMessage({\n        number: remoteJid.split('@')[0],\n        text: message.content.richText[0].children[0].text,\n      });\n    }\n    \n    if (message.type === 'image') {\n      await waInstance.sendMessage({\n        number: remoteJid.split('@')[0],\n        mediaMessage: {\n          mediatype: 'image',\n          media: message.content.url,\n        },\n      });\n    }\n  }\n}\n```\n\n## OpenAI Integration Pattern\n\n### OpenAI Service\n```typescript\nexport class OpenaiService extends BaseChatbotService<OpenaiModel, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(waMonitor, prismaRepository, 'OpenaiService', configService);\n  }\n\n  public async sendOpenaiMessage(\n    instanceName: string,\n    remoteJid: string,\n    openai: OpenaiModel,\n    content: string,\n    pushName?: string,\n  ): Promise<void> {\n    try {\n      const openaiConfig = this.configService.get<Openai>('OPENAI');\n      \n      const response = await axios.post(\n        'https://api.openai.com/v1/chat/completions',\n        {\n          model: openai.model || 'gpt-3.5-turbo',\n          messages: [\n            {\n              role: 'system',\n              content: openai.systemMessage || 'You are a helpful assistant.',\n            },\n            {\n              role: 'user',\n              content: content,\n            },\n          ],\n          max_tokens: openai.maxTokens || 1000,\n          temperature: openai.temperature || 0.7,\n        },\n        {\n          headers: {\n            'Authorization': `Bearer ${openai.apiKey || openaiConfig.API_KEY}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      const aiResponse = response.data.choices[0].message.content;\n      \n      await this.waMonitor.waInstances[instanceName].sendMessage({\n        number: remoteJid.split('@')[0],\n        text: aiResponse,\n      });\n    } catch (error) {\n      this.logger.error(`OpenAI API error: ${error.message}`);\n      \n      // Send fallback message\n      await this.waMonitor.waInstances[instanceName].sendMessage({\n        number: remoteJid.split('@')[0],\n        text: openai.unknownMessage || 'Desculpe, não consegui processar sua mensagem.',\n      });\n    }\n  }\n}\n```\n\n## Chatwoot Integration Pattern\n\n### Chatwoot Service\n```typescript\nexport class ChatwootService extends BaseChatbotService<any, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    configService: ConfigService,\n    prismaRepository: PrismaRepository,\n    private readonly chatwootCache: CacheService,\n  ) {\n    super(waMonitor, prismaRepository, 'ChatwootService', configService);\n  }\n\n  public async eventWhatsapp(\n    event: Events,\n    instanceName: { instanceName: string },\n    data: any,\n  ): Promise<void> {\n    const chatwootConfig = this.configService.get<Chatwoot>('CHATWOOT');\n    \n    if (!chatwootConfig.ENABLED) {\n      return;\n    }\n\n    try {\n      const instance = await this.prismaRepository.instance.findUnique({\n        where: { name: instanceName.instanceName },\n      });\n\n      if (!instance?.chatwootAccountId) {\n        return;\n      }\n\n      const webhook = {\n        event,\n        instance: instanceName.instanceName,\n        data,\n        timestamp: new Date().toISOString(),\n      };\n\n      await axios.post(\n        `${instance.chatwootUrl}/api/v1/accounts/${instance.chatwootAccountId}/webhooks`,\n        webhook,\n        {\n          headers: {\n            'Authorization': `Bearer ${instance.chatwootToken}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n    } catch (error) {\n      this.logger.error(`Chatwoot webhook error: ${error.message}`);\n    }\n  }\n\n  public async createConversation(\n    instanceName: string,\n    contact: any,\n    message: any,\n  ): Promise<void> {\n    // Create conversation in Chatwoot\n    const instance = await this.prismaRepository.instance.findUnique({\n      where: { name: instanceName },\n    });\n\n    if (!instance?.chatwootAccountId) {\n      return;\n    }\n\n    try {\n      const conversation = await axios.post(\n        `${instance.chatwootUrl}/api/v1/accounts/${instance.chatwootAccountId}/conversations`,\n        {\n          source_id: contact.id,\n          inbox_id: instance.chatwootInboxId,\n          contact_id: contact.chatwootContactId,\n        },\n        {\n          headers: {\n            'Authorization': `Bearer ${instance.chatwootToken}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      // Cache conversation\n      await this.chatwootCache.set(\n        `conversation:${instanceName}:${contact.id}`,\n        conversation.data,\n        3600\n      );\n    } catch (error) {\n      this.logger.error(`Chatwoot conversation creation error: ${error.message}`);\n    }\n  }\n}\n```\n\n## Dify Integration Pattern\n\n### Dify Service\n```typescript\nexport class DifyService extends BaseChatbotService<DifyModel, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    private readonly openaiService: OpenaiService,\n  ) {\n    super(waMonitor, prismaRepository, 'DifyService', configService);\n  }\n\n  public async sendDifyMessage(\n    instanceName: string,\n    remoteJid: string,\n    dify: DifyModel,\n    content: string,\n  ): Promise<void> {\n    try {\n      const response = await axios.post(\n        `${dify.apiUrl}/v1/chat-messages`,\n        {\n          inputs: {},\n          query: content,\n          user: remoteJid,\n          conversation_id: `${instanceName}-${remoteJid}`,\n          response_mode: 'blocking',\n        },\n        {\n          headers: {\n            'Authorization': `Bearer ${dify.apiKey}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      const aiResponse = response.data.answer;\n      \n      await this.waMonitor.waInstances[instanceName].sendMessage({\n        number: remoteJid.split('@')[0],\n        text: aiResponse,\n      });\n    } catch (error) {\n      this.logger.error(`Dify API error: ${error.message}`);\n      \n      // Fallback to OpenAI if configured\n      if (dify.fallbackOpenai && this.openaiService) {\n        await this.openaiService.sendOpenaiMessage(instanceName, remoteJid, dify.openaiBot, content);\n      }\n    }\n  }\n}\n```\n\n## Chatbot Router Pattern\n\n### Chatbot Router Structure (Evolution API Real Pattern)\n```typescript\nexport class ChatbotRouter {\n  public readonly router: Router;\n\n  constructor(...guards: any[]) {\n    this.router = Router();\n\n    // Real Evolution API chatbot integrations\n    this.router.use('/evolutionBot', new EvolutionBotRouter(...guards).router);\n    this.router.use('/chatwoot', new ChatwootRouter(...guards).router);\n    this.router.use('/typebot', new TypebotRouter(...guards).router);\n    this.router.use('/openai', new OpenaiRouter(...guards).router);\n    this.router.use('/dify', new DifyRouter(...guards).router);\n    this.router.use('/flowise', new FlowiseRouter(...guards).router);\n    this.router.use('/n8n', new N8nRouter(...guards).router);\n    this.router.use('/evoai', new EvoaiRouter(...guards).router);\n  }\n}\n```\n\n## Chatbot Validation Patterns\n\n### Chatbot Schema Validation (Evolution API Pattern)\n```typescript\nimport { JSONSchema7 } from 'json-schema';\nimport { v4 } from 'uuid';\n\nconst isNotEmpty = (...fields: string[]) => {\n  const properties = {};\n  fields.forEach((field) => {\n    properties[field] = {\n      if: { properties: { [field]: { type: 'string' } } },\n      then: { properties: { [field]: { minLength: 1 } } },\n    };\n  });\n\n  return {\n    allOf: Object.values(properties),\n  };\n};\n\nexport const evolutionBotSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    enabled: { type: 'boolean' },\n    description: { type: 'string' },\n    apiUrl: { type: 'string' },\n    apiKey: { type: 'string' },\n    triggerType: { type: 'string', enum: ['all', 'keyword', 'none', 'advanced'] },\n    triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith', 'regex'] },\n    triggerValue: { type: 'string' },\n    expire: { type: 'integer' },\n    keywordFinish: { type: 'string' },\n    delayMessage: { type: 'integer' },\n    unknownMessage: { type: 'string' },\n    listeningFromMe: { type: 'boolean' },\n    stopBotFromMe: { type: 'boolean' },\n    keepOpen: { type: 'boolean' },\n    debounceTime: { type: 'integer' },\n    ignoreJids: { type: 'array', items: { type: 'string' } },\n    splitMessages: { type: 'boolean' },\n    timePerChar: { type: 'integer' },\n  },\n  required: ['enabled', 'apiUrl', 'triggerType'],\n  ...isNotEmpty('enabled', 'apiUrl', 'triggerType'),\n};\n\nfunction validateKeywordTrigger(\n  content: string,\n  operator: TriggerOperator,\n  value: string,\n): boolean {\n  const normalizedContent = content.toLowerCase().trim();\n  const normalizedValue = value.toLowerCase().trim();\n\n  switch (operator) {\n    case TriggerOperator.EQUALS:\n      return normalizedContent === normalizedValue;\n    case TriggerOperator.CONTAINS:\n      return normalizedContent.includes(normalizedValue);\n    case TriggerOperator.STARTS_WITH:\n      return normalizedContent.startsWith(normalizedValue);\n    case TriggerOperator.ENDS_WITH:\n      return normalizedContent.endsWith(normalizedValue);\n    default:\n      return false;\n  }\n}\n```\n\n## Session Management Pattern\n\n### Chatbot Session Handling\n```typescript\nexport class ChatbotSessionManager {\n  constructor(\n    private readonly prismaRepository: PrismaRepository,\n    private readonly cache: CacheService,\n  ) {}\n\n  public async getSession(\n    instanceName: string,\n    remoteJid: string,\n    botId: string,\n  ): Promise<IntegrationSession | null> {\n    const cacheKey = `session:${instanceName}:${remoteJid}:${botId}`;\n    \n    // Try cache first\n    let session = await this.cache.get(cacheKey);\n    if (session) {\n      return session;\n    }\n\n    // Query database\n    session = await this.prismaRepository.integrationSession.findFirst({\n      where: {\n        instanceId: instanceName,\n        remoteJid,\n        botId,\n        status: 'opened',\n      },\n    });\n\n    // Cache result\n    if (session) {\n      await this.cache.set(cacheKey, session, 300); // 5 min TTL\n    }\n\n    return session;\n  }\n\n  public async createSession(\n    instanceName: string,\n    remoteJid: string,\n    botId: string,\n  ): Promise<IntegrationSession> {\n    const session = await this.prismaRepository.integrationSession.create({\n      data: {\n        instanceId: instanceName,\n        remoteJid,\n        botId,\n        status: 'opened',\n        createdAt: new Date(),\n      },\n    });\n\n    // Cache new session\n    const cacheKey = `session:${instanceName}:${remoteJid}:${botId}`;\n    await this.cache.set(cacheKey, session, 300);\n\n    return session;\n  }\n\n  public async closeSession(sessionId: string): Promise<void> {\n    await this.prismaRepository.integrationSession.update({\n      where: { id: sessionId },\n      data: { status: 'closed', updatedAt: new Date() },\n    });\n\n    // Invalidate cache\n    // Note: In a real implementation, you'd need to track cache keys by session ID\n  }\n}\n```",".cursor/rules/specialized-rules/integration-event-rules.mdc":"---\ndescription: Event integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/event/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Event Integration Rules\n\n## Event Manager Pattern\n\n### Event Manager Structure\n```typescript\nimport { PrismaRepository } from '@api/repository/repository.service';\nimport { ConfigService } from '@config/env.config';\nimport { Logger } from '@config/logger.config';\nimport { Server } from 'http';\n\nexport class EventManager {\n  private prismaRepository: PrismaRepository;\n  private configService: ConfigService;\n  private logger = new Logger('EventManager');\n\n  // Event integrations\n  private webhook: WebhookController;\n  private websocket: WebsocketController;\n  private rabbitmq: RabbitmqController;\n  private nats: NatsController;\n  private sqs: SqsController;\n  private pusher: PusherController;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    server?: Server,\n  ) {\n    this.prismaRepository = prismaRepository;\n    this.configService = configService;\n\n    // Initialize event controllers\n    this.webhook = new WebhookController(prismaRepository, configService);\n    this.websocket = new WebsocketController(prismaRepository, configService, server);\n    this.rabbitmq = new RabbitmqController(prismaRepository, configService);\n    this.nats = new NatsController(prismaRepository, configService);\n    this.sqs = new SqsController(prismaRepository, configService);\n    this.pusher = new PusherController(prismaRepository, configService);\n  }\n\n  public async emit(eventData: {\n    instanceName: string;\n    origin: string;\n    event: string;\n    data: Object;\n    serverUrl: string;\n    dateTime: string;\n    sender: string;\n    apiKey?: string;\n    local?: boolean;\n    integration?: string[];\n  }): Promise<void> {\n    this.logger.log(`Emitting event ${eventData.event} for instance ${eventData.instanceName}`);\n\n    // Emit to all configured integrations\n    await Promise.allSettled([\n      this.webhook.emit(eventData),\n      this.websocket.emit(eventData),\n      this.rabbitmq.emit(eventData),\n      this.nats.emit(eventData),\n      this.sqs.emit(eventData),\n      this.pusher.emit(eventData),\n    ]);\n  }\n\n  public async setInstance(instanceName: string, data: any): Promise<any> {\n    const promises = [];\n\n    if (data.websocket) {\n      promises.push(\n        this.websocket.set(instanceName, {\n          websocket: {\n            enabled: true,\n            events: data.websocket?.events,\n          },\n        })\n      );\n    }\n\n    if (data.rabbitmq) {\n      promises.push(\n        this.rabbitmq.set(instanceName, {\n          rabbitmq: {\n            enabled: true,\n            events: data.rabbitmq?.events,\n          },\n        })\n      );\n    }\n\n    if (data.webhook) {\n      promises.push(\n        this.webhook.set(instanceName, {\n          webhook: {\n            enabled: true,\n            events: data.webhook?.events,\n            url: data.webhook?.url,\n            headers: data.webhook?.headers,\n            base64: data.webhook?.base64,\n            byEvents: data.webhook?.byEvents,\n          },\n        })\n      );\n    }\n\n    // Set other integrations...\n\n    await Promise.allSettled(promises);\n  }\n}\n```\n\n## Base Event Controller Pattern\n\n### Abstract Event Controller\n```typescript\nimport { PrismaRepository } from '@api/repository/repository.service';\nimport { ConfigService } from '@config/env.config';\nimport { Logger } from '@config/logger.config';\n\nexport type EmitData = {\n  instanceName: string;\n  origin: string;\n  event: string;\n  data: Object;\n  serverUrl: string;\n  dateTime: string;\n  sender: string;\n  apiKey?: string;\n  local?: boolean;\n  integration?: string[];\n};\n\nexport interface EventControllerInterface {\n  integrationEnabled: boolean;\n  emit(data: EmitData): Promise<void>;\n  set(instanceName: string, data: any): Promise<any>;\n}\n\nexport abstract class EventController implements EventControllerInterface {\n  protected readonly logger: Logger;\n  protected readonly prismaRepository: PrismaRepository;\n  protected readonly configService: ConfigService;\n\n  public integrationEnabled: boolean = false;\n\n  // Available events for all integrations\n  public static readonly events = [\n    'APPLICATION_STARTUP',\n    'INSTANCE_CREATE',\n    'INSTANCE_DELETE',\n    'QRCODE_UPDATED',\n    'CONNECTION_UPDATE',\n    'STATUS_INSTANCE',\n    'MESSAGES_SET',\n    'MESSAGES_UPSERT',\n    'MESSAGES_EDITED',\n    'MESSAGES_UPDATE',\n    'MESSAGES_DELETE',\n    'SEND_MESSAGE',\n    'CONTACTS_SET',\n    'CONTACTS_UPSERT',\n    'CONTACTS_UPDATE',\n    'PRESENCE_UPDATE',\n    'CHATS_SET',\n    'CHATS_UPDATE',\n    'CHATS_UPSERT',\n    'CHATS_DELETE',\n    'GROUPS_UPSERT',\n    'GROUPS_UPDATE',\n    'GROUP_PARTICIPANTS_UPDATE',\n    'CALL',\n    'TYPEBOT_START',\n    'TYPEBOT_CHANGE_STATUS',\n    'LABELS_EDIT',\n    'LABELS_ASSOCIATION',\n    'CREDS_UPDATE',\n    'MESSAGING_HISTORY_SET',\n    'REMOVE_INSTANCE',\n    'LOGOUT_INSTANCE',\n  ];\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    loggerName: string,\n  ) {\n    this.prismaRepository = prismaRepository;\n    this.configService = configService;\n    this.logger = new Logger(loggerName);\n  }\n\n  // Abstract methods to be implemented by specific integrations\n  public abstract emit(data: EmitData): Promise<void>;\n  public abstract set(instanceName: string, data: any): Promise<any>;\n\n  // Helper method to check if event should be processed\n  protected shouldProcessEvent(eventName: string, configuredEvents?: string[]): boolean {\n    if (!configuredEvents || configuredEvents.length === 0) {\n      return true; // Process all events if none specified\n    }\n    return configuredEvents.includes(eventName);\n  }\n\n  // Helper method to get instance configuration\n  protected async getInstanceConfig(instanceName: string): Promise<any> {\n    try {\n      const instance = await this.prismaRepository.instance.findUnique({\n        where: { name: instanceName },\n      });\n      return instance;\n    } catch (error) {\n      this.logger.error(`Failed to get instance config for ${instanceName}:`, error);\n      return null;\n    }\n  }\n}\n```\n\n## Webhook Integration Pattern\n\n### Webhook Controller Implementation\n```typescript\nexport class WebhookController extends EventController {\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(prismaRepository, configService, 'WebhookController');\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.webhook?.enabled) {\n        return;\n      }\n\n      const webhookConfig = instance.webhook;\n      \n      if (!this.shouldProcessEvent(data.event, webhookConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n        server: {\n          version: process.env.npm_package_version,\n          url: data.serverUrl,\n        },\n      };\n\n      // Encode data as base64 if configured\n      if (webhookConfig.base64) {\n        payload.data = Buffer.from(JSON.stringify(payload.data)).toString('base64');\n      }\n\n      const headers = {\n        'Content-Type': 'application/json',\n        'User-Agent': 'Evolution-API-Webhook',\n        ...webhookConfig.headers,\n      };\n\n      if (webhookConfig.byEvents) {\n        // Send to event-specific endpoint\n        const eventUrl = `${webhookConfig.url}/${data.event.toLowerCase()}`;\n        await this.sendWebhook(eventUrl, payload, headers);\n      } else {\n        // Send to main webhook URL\n        await this.sendWebhook(webhookConfig.url, payload, headers);\n      }\n\n      this.logger.log(`Webhook sent for event ${data.event} to instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`Webhook emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const webhookData = data.webhook;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          webhook: webhookData,\n        },\n      });\n\n      this.logger.log(`Webhook configuration set for instance ${instanceName}`);\n      return { webhook: webhookData };\n    } catch (error) {\n      this.logger.error(`Failed to set webhook config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n\n  private async sendWebhook(url: string, payload: any, headers: any): Promise<void> {\n    try {\n      const response = await axios.post(url, payload, {\n        headers,\n        timeout: 30000,\n        maxRedirects: 3,\n      });\n\n      if (response.status >= 200 && response.status < 300) {\n        this.logger.log(`Webhook delivered successfully to ${url}`);\n      } else {\n        this.logger.warn(`Webhook returned status ${response.status} for ${url}`);\n      }\n    } catch (error) {\n      this.logger.error(`Webhook delivery failed to ${url}:`, error.message);\n      \n      // Implement retry logic here if needed\n      if (error.response?.status >= 500) {\n        // Server error - might be worth retrying\n        this.logger.log(`Server error detected, webhook might be retried later`);\n      }\n    }\n  }\n}\n```\n\n## WebSocket Integration Pattern\n\n### WebSocket Controller Implementation\n```typescript\nimport { Server as SocketIOServer } from 'socket.io';\nimport { Server } from 'http';\n\nexport class WebsocketController extends EventController {\n  private io: SocketIOServer;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    server?: Server,\n  ) {\n    super(prismaRepository, configService, 'WebsocketController');\n    \n    if (server) {\n      this.io = new SocketIOServer(server, {\n        cors: {\n          origin: \"*\",\n          methods: [\"GET\", \"POST\"],\n        },\n      });\n\n      this.setupSocketHandlers();\n    }\n  }\n\n  private setupSocketHandlers(): void {\n    this.io.on('connection', (socket) => {\n      this.logger.log(`WebSocket client connected: ${socket.id}`);\n\n      socket.on('join-instance', (instanceName: string) => {\n        socket.join(`instance:${instanceName}`);\n        this.logger.log(`Client ${socket.id} joined instance ${instanceName}`);\n      });\n\n      socket.on('leave-instance', (instanceName: string) => {\n        socket.leave(`instance:${instanceName}`);\n        this.logger.log(`Client ${socket.id} left instance ${instanceName}`);\n      });\n\n      socket.on('disconnect', () => {\n        this.logger.log(`WebSocket client disconnected: ${socket.id}`);\n      });\n    });\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    if (!this.io) {\n      return;\n    }\n\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.websocket?.enabled) {\n        return;\n      }\n\n      const websocketConfig = instance.websocket;\n      \n      if (!this.shouldProcessEvent(data.event, websocketConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n      };\n\n      // Emit to specific instance room\n      this.io.to(`instance:${data.instanceName}`).emit('evolution-event', payload);\n      \n      // Also emit to global room for monitoring\n      this.io.emit('global-event', payload);\n\n      this.logger.log(`WebSocket event ${data.event} emitted for instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`WebSocket emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const websocketData = data.websocket;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          websocket: websocketData,\n        },\n      });\n\n      this.logger.log(`WebSocket configuration set for instance ${instanceName}`);\n      return { websocket: websocketData };\n    } catch (error) {\n      this.logger.error(`Failed to set WebSocket config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n}\n```\n\n## Queue Integration Patterns\n\n### RabbitMQ Controller Implementation\n```typescript\nimport amqp from 'amqplib';\n\nexport class RabbitmqController extends EventController {\n  private connection: amqp.Connection | null = null;\n  private channel: amqp.Channel | null = null;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(prismaRepository, configService, 'RabbitmqController');\n    this.initializeConnection();\n  }\n\n  private async initializeConnection(): Promise<void> {\n    try {\n      const rabbitmqConfig = this.configService.get('RABBITMQ');\n      if (!rabbitmqConfig?.ENABLED) {\n        return;\n      }\n\n      this.connection = await amqp.connect(rabbitmqConfig.URI);\n      this.channel = await this.connection.createChannel();\n\n      // Declare exchange for Evolution API events\n      await this.channel.assertExchange('evolution-events', 'topic', { durable: true });\n\n      this.logger.log('RabbitMQ connection established');\n    } catch (error) {\n      this.logger.error('Failed to initialize RabbitMQ connection:', error);\n    }\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    if (!this.channel) {\n      return;\n    }\n\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.rabbitmq?.enabled) {\n        return;\n      }\n\n      const rabbitmqConfig = instance.rabbitmq;\n      \n      if (!this.shouldProcessEvent(data.event, rabbitmqConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n      };\n\n      const routingKey = `evolution.${data.instanceName}.${data.event.toLowerCase()}`;\n      \n      await this.channel.publish(\n        'evolution-events',\n        routingKey,\n        Buffer.from(JSON.stringify(payload)),\n        {\n          persistent: true,\n          timestamp: Date.now(),\n          messageId: `${data.instanceName}-${Date.now()}`,\n        }\n      );\n\n      this.logger.log(`RabbitMQ message published for event ${data.event} to instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`RabbitMQ emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const rabbitmqData = data.rabbitmq;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          rabbitmq: rabbitmqData,\n        },\n      });\n\n      this.logger.log(`RabbitMQ configuration set for instance ${instanceName}`);\n      return { rabbitmq: rabbitmqData };\n    } catch (error) {\n      this.logger.error(`Failed to set RabbitMQ config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n}\n```\n\n### SQS Controller Implementation\n```typescript\nimport { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';\n\nexport class SqsController extends EventController {\n  private sqsClient: SQSClient | null = null;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(prismaRepository, configService, 'SqsController');\n    this.initializeSQSClient();\n  }\n\n  private initializeSQSClient(): void {\n    try {\n      const sqsConfig = this.configService.get('SQS');\n      if (!sqsConfig?.ENABLED) {\n        return;\n      }\n\n      this.sqsClient = new SQSClient({\n        region: sqsConfig.REGION,\n        credentials: {\n          accessKeyId: sqsConfig.ACCESS_KEY_ID,\n          secretAccessKey: sqsConfig.SECRET_ACCESS_KEY,\n        },\n      });\n\n      this.logger.log('SQS client initialized');\n    } catch (error) {\n      this.logger.error('Failed to initialize SQS client:', error);\n    }\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    if (!this.sqsClient) {\n      return;\n    }\n\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.sqs?.enabled) {\n        return;\n      }\n\n      const sqsConfig = instance.sqs;\n      \n      if (!this.shouldProcessEvent(data.event, sqsConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n      };\n\n      const command = new SendMessageCommand({\n        QueueUrl: sqsConfig.queueUrl,\n        MessageBody: JSON.stringify(payload),\n        MessageAttributes: {\n          event: {\n            DataType: 'String',\n            StringValue: data.event,\n          },\n          instance: {\n            DataType: 'String',\n            StringValue: data.instanceName,\n          },\n        },\n        MessageGroupId: data.instanceName, // For FIFO queues\n        MessageDeduplicationId: `${data.instanceName}-${Date.now()}`, // For FIFO queues\n      });\n\n      await this.sqsClient.send(command);\n\n      this.logger.log(`SQS message sent for event ${data.event} to instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`SQS emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const sqsData = data.sqs;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          sqs: sqsData,\n        },\n      });\n\n      this.logger.log(`SQS configuration set for instance ${instanceName}`);\n      return { sqs: sqsData };\n    } catch (error) {\n      this.logger.error(`Failed to set SQS config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n}\n```\n\n## Event DTO Pattern\n\n### Event Configuration DTO\n```typescript\nimport { JsonValue } from '@prisma/client/runtime/library';\n\nexport class EventDto {\n  webhook?: {\n    enabled?: boolean;\n    events?: string[];\n    url?: string;\n    headers?: JsonValue;\n    byEvents?: boolean;\n    base64?: boolean;\n  };\n\n  websocket?: {\n    enabled?: boolean;\n    events?: string[];\n  };\n\n  sqs?: {\n    enabled?: boolean;\n    events?: string[];\n    queueUrl?: string;\n  };\n\n  rabbitmq?: {\n    enabled?: boolean;\n    events?: string[];\n    exchange?: string;\n  };\n\n  nats?: {\n    enabled?: boolean;\n    events?: string[];\n    subject?: string;\n  };\n\n  pusher?: {\n    enabled?: boolean;\n    appId?: string;\n    key?: string;\n    secret?: string;\n    cluster?: string;\n    useTLS?: boolean;\n    events?: string[];\n  };\n}\n```\n\n## Event Router Pattern\n\n### Event Router Structure\n```typescript\nimport { NatsRouter } from '@api/integrations/event/nats/nats.router';\nimport { PusherRouter } from '@api/integrations/event/pusher/pusher.router';\nimport { RabbitmqRouter } from '@api/integrations/event/rabbitmq/rabbitmq.router';\nimport { SqsRouter } from '@api/integrations/event/sqs/sqs.router';\nimport { WebhookRouter } from '@api/integrations/event/webhook/webhook.router';\nimport { WebsocketRouter } from '@api/integrations/event/websocket/websocket.router';\nimport { Router } from 'express';\n\nexport class EventRouter {\n  public readonly router: Router;\n\n  constructor(configService: any, ...guards: any[]) {\n    this.router = Router();\n\n    this.router.use('/webhook', new WebhookRouter(configService, ...guards).router);\n    this.router.use('/websocket', new WebsocketRouter(...guards).router);\n    this.router.use('/rabbitmq', new RabbitmqRouter(...guards).router);\n    this.router.use('/nats', new NatsRouter(...guards).router);\n    this.router.use('/pusher', new PusherRouter(...guards).router);\n    this.router.use('/sqs', new SqsRouter(...guards).router);\n  }\n}\n```\n\n## Event Validation Schema\n\n### Event Configuration Validation\n```typescript\nimport Joi from 'joi';\nimport { EventController } from '@api/integrations/event/event.controller';\n\nconst eventListSchema = Joi.array().items(\n  Joi.string().valid(...EventController.events)\n).optional();\n\nexport const webhookSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  url: Joi.string().when('enabled', {\n    is: true,\n    then: Joi.required().uri({ scheme: ['http', 'https'] }),\n    otherwise: Joi.optional(),\n  }),\n  events: eventListSchema,\n  headers: Joi.object().pattern(Joi.string(), Joi.string()).optional(),\n  byEvents: Joi.boolean().optional().default(false),\n  base64: Joi.boolean().optional().default(false),\n}).required();\n\nexport const websocketSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  events: eventListSchema,\n}).required();\n\nexport const rabbitmqSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  events: eventListSchema,\n  exchange: Joi.string().optional().default('evolution-events'),\n}).required();\n\nexport const sqsSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  events: eventListSchema,\n  queueUrl: Joi.string().when('enabled', {\n    is: true,\n    then: Joi.required().uri(),\n    otherwise: Joi.optional(),\n  }),\n}).required();\n\nexport const eventSchema = Joi.object({\n  webhook: webhookSchema.optional(),\n  websocket: websocketSchema.optional(),\n  rabbitmq: rabbitmqSchema.optional(),\n  sqs: sqsSchema.optional(),\n  nats: Joi.object({\n    enabled: Joi.boolean().required(),\n    events: eventListSchema,\n    subject: Joi.string().optional().default('evolution.events'),\n  }).optional(),\n  pusher: Joi.object({\n    enabled: Joi.boolean().required(),\n    appId: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    key: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    secret: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    cluster: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    useTLS: Joi.boolean().optional().default(true),\n    events: eventListSchema,\n  }).optional(),\n}).min(1).required();\n```\n\n## Event Testing Pattern\n\n### Event Controller Testing\n```typescript\ndescribe('WebhookController', () => {\n  let controller: WebhookController;\n  let prismaRepository: jest.Mocked<PrismaRepository>;\n  let configService: jest.Mocked<ConfigService>;\n\n  beforeEach(() => {\n    controller = new WebhookController(prismaRepository, configService);\n  });\n\n  describe('emit', () => {\n    it('should send webhook when enabled', async () => {\n      const mockInstance = {\n        webhook: {\n          enabled: true,\n          url: 'https://example.com/webhook',\n          events: ['MESSAGES_UPSERT'],\n        },\n      };\n\n      prismaRepository.instance.findUnique.mockResolvedValue(mockInstance);\n      jest.spyOn(axios, 'post').mockResolvedValue({ status: 200 });\n\n      const eventData = {\n        instanceName: 'test-instance',\n        event: 'MESSAGES_UPSERT',\n        data: { message: 'test' },\n        origin: 'test',\n        serverUrl: 'http://localhost',\n        dateTime: new Date().toISOString(),\n        sender: 'test',\n      };\n\n      await controller.emit(eventData);\n\n      expect(axios.post).toHaveBeenCalledWith(\n        'https://example.com/webhook',\n        expect.objectContaining({\n          event: 'MESSAGES_UPSERT',\n          instance: 'test-instance',\n        }),\n        expect.objectContaining({\n          headers: expect.objectContaining({\n            'Content-Type': 'application/json',\n          }),\n        })\n      );\n    });\n  });\n});\n```",".cursor/rules/specialized-rules/integration-storage-rules.mdc":"---\ndescription: Storage integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/storage/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Storage Integration Rules\n\n## Storage Service Pattern\n\n### Base Storage Service Structure\n```typescript\nimport { InstanceDto } from '@api/dto/instance.dto';\nimport { PrismaRepository } from '@api/repository/repository.service';\nimport { Logger } from '@config/logger.config';\nimport { BadRequestException } from '@exceptions';\n\nexport class StorageService {\n  constructor(private readonly prismaRepository: PrismaRepository) {}\n\n  private readonly logger = new Logger('StorageService');\n\n  public async getMedia(instance: InstanceDto, query?: MediaDto) {\n    try {\n      const where: any = {\n        instanceId: instance.instanceId,\n        ...query,\n      };\n\n      const media = await this.prismaRepository.media.findMany({\n        where,\n        select: {\n          id: true,\n          fileName: true,\n          type: true,\n          mimetype: true,\n          createdAt: true,\n          Message: true,\n        },\n      });\n\n      if (!media || media.length === 0) {\n        throw 'Media not found';\n      }\n\n      return media;\n    } catch (error) {\n      throw new BadRequestException(error);\n    }\n  }\n\n  public async getMediaUrl(instance: InstanceDto, data: MediaDto) {\n    const media = (await this.getMedia(instance, { id: data.id }))[0];\n    const mediaUrl = await this.generateUrl(media.fileName, data.expiry);\n    return {\n      mediaUrl,\n      ...media,\n    };\n  }\n\n  protected abstract generateUrl(fileName: string, expiry?: number): Promise<string>;\n}\n```\n\n## S3/MinIO Integration Pattern\n\n### MinIO Client Setup\n```typescript\nimport { ConfigService, S3 } from '@config/env.config';\nimport { Logger } from '@config/logger.config';\nimport { BadRequestException } from '@exceptions';\nimport * as MinIo from 'minio';\nimport { join } from 'path';\nimport { Readable, Transform } from 'stream';\n\nconst logger = new Logger('S3 Service');\nconst BUCKET = new ConfigService().get<S3>('S3');\n\ninterface Metadata extends MinIo.ItemBucketMetadata {\n  instanceId: string;\n  messageId?: string;\n}\n\nconst minioClient = (() => {\n  if (BUCKET?.ENABLE) {\n    return new MinIo.Client({\n      endPoint: BUCKET.ENDPOINT,\n      port: BUCKET.PORT,\n      useSSL: BUCKET.USE_SSL,\n      accessKey: BUCKET.ACCESS_KEY,\n      secretKey: BUCKET.SECRET_KEY,\n      region: BUCKET.REGION,\n    });\n  }\n})();\n\nconst bucketName = process.env.S3_BUCKET;\n```\n\n### Bucket Management Functions\n```typescript\nconst bucketExists = async (): Promise<boolean> => {\n  if (minioClient) {\n    try {\n      const list = await minioClient.listBuckets();\n      return !!list.find((bucket) => bucket.name === bucketName);\n    } catch (error) {\n      logger.error('Error checking bucket existence:', error);\n      return false;\n    }\n  }\n  return false;\n};\n\nconst setBucketPolicy = async (): Promise<void> => {\n  if (minioClient && bucketName) {\n    try {\n      const policy = {\n        Version: '2012-10-17',\n        Statement: [\n          {\n            Effect: 'Allow',\n            Principal: { AWS: ['*'] },\n            Action: ['s3:GetObject'],\n            Resource: [`arn:aws:s3:::${bucketName}/*`],\n          },\n        ],\n      };\n\n      await minioClient.setBucketPolicy(bucketName, JSON.stringify(policy));\n      logger.log('Bucket policy set successfully');\n    } catch (error) {\n      logger.error('Error setting bucket policy:', error);\n    }\n  }\n};\n\nconst createBucket = async (): Promise<void> => {\n  if (minioClient && bucketName) {\n    try {\n      const exists = await bucketExists();\n      if (!exists) {\n        await minioClient.makeBucket(bucketName, BUCKET.REGION || 'us-east-1');\n        await setBucketPolicy();\n        logger.log(`Bucket ${bucketName} created successfully`);\n      }\n    } catch (error) {\n      logger.error('Error creating bucket:', error);\n    }\n  }\n};\n```\n\n### File Upload Functions\n```typescript\nexport const uploadFile = async (\n  fileName: string,\n  buffer: Buffer,\n  mimetype: string,\n  metadata?: Metadata,\n): Promise<string> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    await createBucket();\n\n    const uploadMetadata = {\n      'Content-Type': mimetype,\n      ...metadata,\n    };\n\n    await minioClient.putObject(bucketName, fileName, buffer, buffer.length, uploadMetadata);\n    \n    logger.log(`File ${fileName} uploaded successfully`);\n    return fileName;\n  } catch (error) {\n    logger.error(`Error uploading file ${fileName}:`, error);\n    throw new BadRequestException(`Failed to upload file: ${error.message}`);\n  }\n};\n\nexport const uploadStream = async (\n  fileName: string,\n  stream: Readable,\n  size: number,\n  mimetype: string,\n  metadata?: Metadata,\n): Promise<string> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    await createBucket();\n\n    const uploadMetadata = {\n      'Content-Type': mimetype,\n      ...metadata,\n    };\n\n    await minioClient.putObject(bucketName, fileName, stream, size, uploadMetadata);\n    \n    logger.log(`Stream ${fileName} uploaded successfully`);\n    return fileName;\n  } catch (error) {\n    logger.error(`Error uploading stream ${fileName}:`, error);\n    throw new BadRequestException(`Failed to upload stream: ${error.message}`);\n  }\n};\n```\n\n### File Download Functions\n```typescript\nexport const getObject = async (fileName: string): Promise<Buffer> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const stream = await minioClient.getObject(bucketName, fileName);\n    const chunks: Buffer[] = [];\n\n    return new Promise((resolve, reject) => {\n      stream.on('data', (chunk) => chunks.push(chunk));\n      stream.on('end', () => resolve(Buffer.concat(chunks)));\n      stream.on('error', reject);\n    });\n  } catch (error) {\n    logger.error(`Error getting object ${fileName}:`, error);\n    throw new BadRequestException(`Failed to get object: ${error.message}`);\n  }\n};\n\nexport const getObjectUrl = async (fileName: string, expiry: number = 3600): Promise<string> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const url = await minioClient.presignedGetObject(bucketName, fileName, expiry);\n    logger.log(`Generated URL for ${fileName} with expiry ${expiry}s`);\n    return url;\n  } catch (error) {\n    logger.error(`Error generating URL for ${fileName}:`, error);\n    throw new BadRequestException(`Failed to generate URL: ${error.message}`);\n  }\n};\n\nexport const getObjectStream = async (fileName: string): Promise<Readable> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const stream = await minioClient.getObject(bucketName, fileName);\n    return stream;\n  } catch (error) {\n    logger.error(`Error getting object stream ${fileName}:`, error);\n    throw new BadRequestException(`Failed to get object stream: ${error.message}`);\n  }\n};\n```\n\n### File Management Functions\n```typescript\nexport const deleteObject = async (fileName: string): Promise<void> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    await minioClient.removeObject(bucketName, fileName);\n    logger.log(`File ${fileName} deleted successfully`);\n  } catch (error) {\n    logger.error(`Error deleting file ${fileName}:`, error);\n    throw new BadRequestException(`Failed to delete file: ${error.message}`);\n  }\n};\n\nexport const listObjects = async (prefix?: string): Promise<MinIo.BucketItem[]> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const objects: MinIo.BucketItem[] = [];\n    const stream = minioClient.listObjects(bucketName, prefix, true);\n\n    return new Promise((resolve, reject) => {\n      stream.on('data', (obj) => objects.push(obj));\n      stream.on('end', () => resolve(objects));\n      stream.on('error', reject);\n    });\n  } catch (error) {\n    logger.error('Error listing objects:', error);\n    throw new BadRequestException(`Failed to list objects: ${error.message}`);\n  }\n};\n\nexport const objectExists = async (fileName: string): Promise<boolean> => {\n  if (!minioClient || !bucketName) {\n    return false;\n  }\n\n  try {\n    await minioClient.statObject(bucketName, fileName);\n    return true;\n  } catch (error) {\n    return false;\n  }\n};\n```\n\n## Storage Controller Pattern\n\n### S3 Controller Implementation\n```typescript\nimport { InstanceDto } from '@api/dto/instance.dto';\nimport { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';\nimport { S3Service } from '@api/integrations/storage/s3/services/s3.service';\n\nexport class S3Controller {\n  constructor(private readonly s3Service: S3Service) {}\n\n  public async getMedia(instance: InstanceDto, data: MediaDto) {\n    return this.s3Service.getMedia(instance, data);\n  }\n\n  public async getMediaUrl(instance: InstanceDto, data: MediaDto) {\n    return this.s3Service.getMediaUrl(instance, data);\n  }\n\n  public async uploadMedia(instance: InstanceDto, data: UploadMediaDto) {\n    return this.s3Service.uploadMedia(instance, data);\n  }\n\n  public async deleteMedia(instance: InstanceDto, data: MediaDto) {\n    return this.s3Service.deleteMedia(instance, data);\n  }\n}\n```\n\n## Storage Router Pattern\n\n### Storage Router Structure\n```typescript\nimport { S3Router } from '@api/integrations/storage/s3/routes/s3.router';\nimport { Router } from 'express';\n\nexport class StorageRouter {\n  public readonly router: Router;\n\n  constructor(...guards: any[]) {\n    this.router = Router();\n\n    this.router.use('/s3', new S3Router(...guards).router);\n    // Add other storage providers here\n    // this.router.use('/gcs', new GCSRouter(...guards).router);\n    // this.router.use('/azure', new AzureRouter(...guards).router);\n  }\n}\n```\n\n### S3 Specific Router\n```typescript\nimport { RouterBroker } from '@api/abstract/abstract.router';\nimport { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';\nimport { s3Schema, s3UrlSchema } from '@api/integrations/storage/s3/validate/s3.schema';\nimport { HttpStatus } from '@api/routes/index.router';\nimport { s3Controller } from '@api/server.module';\nimport { RequestHandler, Router } from 'express';\n\nexport class S3Router extends RouterBroker {\n  constructor(...guards: RequestHandler[]) {\n    super();\n    this.router\n      .post(this.routerPath('getMedia'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<MediaDto>({\n          request: req,\n          schema: s3Schema,\n          ClassRef: MediaDto,\n          execute: (instance, data) => s3Controller.getMedia(instance, data),\n        });\n\n        res.status(HttpStatus.OK).json(response);\n      })\n      .post(this.routerPath('getMediaUrl'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<MediaDto>({\n          request: req,\n          schema: s3UrlSchema,\n          ClassRef: MediaDto,\n          execute: (instance, data) => s3Controller.getMediaUrl(instance, data),\n        });\n\n        res.status(HttpStatus.OK).json(response);\n      })\n      .post(this.routerPath('uploadMedia'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<UploadMediaDto>({\n          request: req,\n          schema: uploadSchema,\n          ClassRef: UploadMediaDto,\n          execute: (instance, data) => s3Controller.uploadMedia(instance, data),\n        });\n\n        res.status(HttpStatus.CREATED).json(response);\n      })\n      .delete(this.routerPath('deleteMedia'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<MediaDto>({\n          request: req,\n          schema: s3Schema,\n          ClassRef: MediaDto,\n          execute: (instance, data) => s3Controller.deleteMedia(instance, data),\n        });\n\n        res.status(HttpStatus.OK).json(response);\n      });\n  }\n\n  public readonly router: Router = Router();\n}\n```\n\n## Storage DTO Pattern\n\n### Media DTO\n```typescript\nexport class MediaDto {\n  id?: string;\n  fileName?: string;\n  type?: string;\n  mimetype?: string;\n  expiry?: number;\n}\n\nexport class UploadMediaDto {\n  fileName: string;\n  mimetype: string;\n  buffer?: Buffer;\n  base64?: string;\n  url?: string;\n  metadata?: {\n    instanceId: string;\n    messageId?: string;\n    contactId?: string;\n    [key: string]: any;\n  };\n}\n```\n\n## Storage Validation Schema\n\n### S3 Validation Schemas\n```typescript\nimport Joi from 'joi';\n\nexport const s3Schema = Joi.object({\n  id: Joi.string().optional(),\n  fileName: Joi.string().optional(),\n  type: Joi.string().optional().valid('image', 'video', 'audio', 'document'),\n  mimetype: Joi.string().optional(),\n  expiry: Joi.number().optional().min(60).max(604800).default(3600), // 1 min to 7 days\n}).min(1).required();\n\nexport const s3UrlSchema = Joi.object({\n  id: Joi.string().required(),\n  expiry: Joi.number().optional().min(60).max(604800).default(3600),\n}).required();\n\nexport const uploadSchema = Joi.object({\n  fileName: Joi.string().required().max(255),\n  mimetype: Joi.string().required(),\n  buffer: Joi.binary().optional(),\n  base64: Joi.string().base64().optional(),\n  url: Joi.string().uri().optional(),\n  metadata: Joi.object({\n    instanceId: Joi.string().required(),\n    messageId: Joi.string().optional(),\n    contactId: Joi.string().optional(),\n  }).optional(),\n}).xor('buffer', 'base64', 'url').required(); // Exactly one of these must be present\n```\n\n## Error Handling in Storage\n\n### Storage-Specific Error Handling\n```typescript\n// CORRECT - Storage-specific error handling\npublic async uploadFile(fileName: string, buffer: Buffer): Promise<string> {\n  try {\n    const result = await this.storageClient.upload(fileName, buffer);\n    return result;\n  } catch (error) {\n    this.logger.error(`Storage upload failed: ${error.message}`);\n    \n    if (error.code === 'NoSuchBucket') {\n      throw new BadRequestException('Storage bucket not found');\n    }\n    \n    if (error.code === 'AccessDenied') {\n      throw new UnauthorizedException('Storage access denied');\n    }\n    \n    if (error.code === 'EntityTooLarge') {\n      throw new BadRequestException('File too large');\n    }\n    \n    throw new InternalServerErrorException('Storage operation failed');\n  }\n}\n```\n\n## Storage Configuration Pattern\n\n### Environment Configuration\n```typescript\nexport interface S3Config {\n  ENABLE: boolean;\n  ENDPOINT: string;\n  PORT: number;\n  USE_SSL: boolean;\n  ACCESS_KEY: string;\n  SECRET_KEY: string;\n  REGION: string;\n  BUCKET: string;\n}\n\n// Usage in service\nconst s3Config = this.configService.get<S3Config>('S3');\nif (!s3Config.ENABLE) {\n  throw new BadRequestException('S3 storage is disabled');\n}\n```\n\n## Storage Testing Pattern\n\n### Storage Service Testing\n```typescript\ndescribe('S3Service', () => {\n  let service: S3Service;\n  let prismaRepository: jest.Mocked<PrismaRepository>;\n\n  beforeEach(() => {\n    service = new S3Service(prismaRepository);\n  });\n\n  describe('getMedia', () => {\n    it('should return media list', async () => {\n      const instance = { instanceId: 'test-instance' };\n      const mockMedia = [\n        { id: '1', fileName: 'test.jpg', type: 'image', mimetype: 'image/jpeg' },\n      ];\n\n      prismaRepository.media.findMany.mockResolvedValue(mockMedia);\n\n      const result = await service.getMedia(instance);\n\n      expect(result).toEqual(mockMedia);\n      expect(prismaRepository.media.findMany).toHaveBeenCalledWith({\n        where: { instanceId: 'test-instance' },\n        select: expect.objectContaining({\n          id: true,\n          fileName: true,\n          type: true,\n          mimetype: true,\n        }),\n      });\n    });\n\n    it('should throw error when no media found', async () => {\n      const instance = { instanceId: 'test-instance' };\n      prismaRepository.media.findMany.mockResolvedValue([]);\n\n      await expect(service.getMedia(instance)).rejects.toThrow(BadRequestException);\n    });\n  });\n});\n```\n\n## Storage Performance Considerations\n\n### Efficient File Handling\n```typescript\n// CORRECT - Stream-based upload for large files\npublic async uploadLargeFile(fileName: string, stream: Readable, size: number): Promise<string> {\n  const uploadStream = new Transform({\n    transform(chunk, encoding, callback) {\n      // Optional: Add compression, encryption, etc.\n      callback(null, chunk);\n    },\n  });\n\n  return new Promise((resolve, reject) => {\n    stream\n      .pipe(uploadStream)\n      .on('error', reject)\n      .on('finish', () => resolve(fileName));\n  });\n}\n\n// INCORRECT - Loading entire file into memory\npublic async uploadLargeFile(fileName: string, filePath: string): Promise<string> {\n  const buffer = fs.readFileSync(filePath); // ❌ Memory intensive for large files\n  return await this.uploadFile(fileName, buffer);\n}\n```",".cursor/rules/specialized-rules/route-rules.mdc":"---\ndescription: Router patterns for Evolution API\nglobs:\n  - \"src/api/routes/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Route Rules\n\n## Router Base Pattern\n\n### RouterBroker Extension\n```typescript\nimport { RouterBroker } from '@api/abstract/abstract.router';\nimport { RequestHandler, Router } from 'express';\nimport { HttpStatus } from './index.router';\n\nexport class ExampleRouter extends RouterBroker {\n  constructor(...guards: RequestHandler[]) {\n    super();\n    this.router\n      .get(this.routerPath('findExample'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<ExampleDto>({\n          request: req,\n          schema: null,\n          ClassRef: ExampleDto,\n          execute: (instance) => exampleController.find(instance),\n        });\n\n        return res.status(HttpStatus.OK).json(response);\n      })\n      .post(this.routerPath('createExample'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<ExampleDto>({\n          request: req,\n          schema: exampleSchema,\n          ClassRef: ExampleDto,\n          execute: (instance, data) => exampleController.create(instance, data),\n        });\n\n        return res.status(HttpStatus.CREATED).json(response);\n      });\n  }\n\n  public readonly router: Router = Router();\n}\n```\n\n## Main Router Pattern\n\n### Index Router Structure\n```typescript\nimport { Router } from 'express';\nimport { authGuard } from '@api/guards/auth.guard';\nimport { instanceExistsGuard, instanceLoggedGuard } from '@api/guards/instance.guard';\nimport Telemetry from '@api/guards/telemetry.guard';\n\nenum HttpStatus {\n  OK = 200,\n  CREATED = 201,\n  NOT_FOUND = 404,\n  FORBIDDEN = 403,\n  BAD_REQUEST = 400,\n  UNAUTHORIZED = 401,\n  INTERNAL_SERVER_ERROR = 500,\n}\n\nconst router: Router = Router();\nconst guards = [instanceExistsGuard, instanceLoggedGuard, authGuard['apikey']];\nconst telemetry = new Telemetry();\n\nrouter\n  .use((req, res, next) => telemetry.collectTelemetry(req, res, next))\n  .get('/', async (req, res) => {\n    res.status(HttpStatus.OK).json({\n      status: HttpStatus.OK,\n      message: 'Welcome to the Evolution API, it is working!',\n      version: packageJson.version,\n      clientName: process.env.DATABASE_CONNECTION_CLIENT_NAME,\n      manager: !serverConfig.DISABLE_MANAGER ? `${req.protocol}://${req.get('host')}/manager` : undefined,\n      documentation: `https://doc.evolution-api.com`,\n      whatsappWebVersion: (await fetchLatestWaWebVersion({})).version.join('.'),\n    });\n  })\n  .use('/instance', new InstanceRouter(configService, ...guards).router)\n  .use('/message', new MessageRouter(...guards).router)\n  .use('/chat', new ChatRouter(...guards).router)\n  .use('/business', new BusinessRouter(...guards).router);\n\nexport { HttpStatus, router };\n```\n\n## Data Validation Pattern\n\n### RouterBroker dataValidate Usage\n```typescript\n// CORRECT - Standard validation pattern\n.post(this.routerPath('createTemplate'), ...guards, async (req, res) => {\n  const response = await this.dataValidate<TemplateDto>({\n    request: req,\n    schema: templateSchema,\n    ClassRef: TemplateDto,\n    execute: (instance, data) => templateController.create(instance, data),\n  });\n\n  return res.status(HttpStatus.CREATED).json(response);\n})\n\n// CORRECT - No schema validation (for simple DTOs)\n.get(this.routerPath('findTemplate'), ...guards, async (req, res) => {\n  const response = await this.dataValidate<InstanceDto>({\n    request: req,\n    schema: null,\n    ClassRef: InstanceDto,\n    execute: (instance) => templateController.find(instance),\n  });\n\n  return res.status(HttpStatus.OK).json(response);\n})\n```\n\n## Error Handling in Routes\n\n### Try-Catch Pattern\n```typescript\n// CORRECT - Error handling with utility function\n.post(this.routerPath('getCatalog'), ...guards, async (req, res) => {\n  try {\n    const response = await this.dataValidate<NumberDto>({\n      request: req,\n      schema: catalogSchema,\n      ClassRef: NumberDto,\n      execute: (instance, data) => businessController.fetchCatalog(instance, data),\n    });\n\n    return res.status(HttpStatus.OK).json(response);\n  } catch (error) {\n    // Log error for debugging\n    console.error('Business catalog error:', error);\n\n    // Use utility function to create standardized error response\n    const errorResponse = createMetaErrorResponse(error, 'business_catalog');\n    return res.status(errorResponse.status).json(errorResponse);\n  }\n})\n\n// INCORRECT - Let RouterBroker handle errors (when possible)\n.post(this.routerPath('simpleOperation'), ...guards, async (req, res) => {\n  try {\n    const response = await this.dataValidate<SimpleDto>({\n      request: req,\n      schema: simpleSchema,\n      ClassRef: SimpleDto,\n      execute: (instance, data) => controller.simpleOperation(instance, data),\n    });\n\n    return res.status(HttpStatus.OK).json(response);\n  } catch (error) {\n    throw error; // ❌ Unnecessary - RouterBroker handles this\n  }\n})\n```\n\n## Route Path Pattern\n\n### routerPath Usage\n```typescript\n// CORRECT - Use routerPath for consistent naming\n.get(this.routerPath('findLabels'), ...guards, async (req, res) => {\n  // Implementation\n})\n.post(this.routerPath('handleLabel'), ...guards, async (req, res) => {\n  // Implementation\n})\n\n// INCORRECT - Hardcoded paths\n.get('/labels', ...guards, async (req, res) => { // ❌ Use routerPath\n  // Implementation\n})\n```\n\n## Guard Application Pattern\n\n### Guards Usage\n```typescript\n// CORRECT - Apply guards to protected routes\nexport class ProtectedRouter extends RouterBroker {\n  constructor(...guards: RequestHandler[]) {\n    super();\n    this.router\n      .get(this.routerPath('protectedAction'), ...guards, async (req, res) => {\n        // Protected action\n      })\n      .post(this.routerPath('anotherAction'), ...guards, async (req, res) => {\n        // Another protected action\n      });\n  }\n}\n\n// CORRECT - No guards for public routes\nexport class PublicRouter extends RouterBroker {\n  constructor() {\n    super();\n    this.router\n      .get('/health', async (req, res) => {\n        res.status(HttpStatus.OK).json({ status: 'healthy' });\n      })\n      .get('/version', async (req, res) => {\n        res.status(HttpStatus.OK).json({ version: packageJson.version });\n      });\n  }\n}\n```\n\n## Static File Serving Pattern\n\n### Static Assets Route\n```typescript\n// CORRECT - Secure static file serving\nrouter.get('/assets/*', (req, res) => {\n  const fileName = req.params[0];\n  \n  // Security: Reject paths containing traversal patterns\n  if (!fileName || fileName.includes('..') || fileName.includes('\\\\') || path.isAbsolute(fileName)) {\n    return res.status(403).send('Forbidden');\n  }\n  \n  const basePath = path.join(process.cwd(), 'manager', 'dist');\n  const assetsPath = path.join(basePath, 'assets');\n  const filePath = path.join(assetsPath, fileName);\n  \n  // Security: Ensure the resolved path is within the assets directory\n  const resolvedPath = path.resolve(filePath);\n  const resolvedAssetsPath = path.resolve(assetsPath);\n  \n  if (!resolvedPath.startsWith(resolvedAssetsPath + path.sep) && resolvedPath !== resolvedAssetsPath) {\n    return res.status(403).send('Forbidden');\n  }\n\n  if (fs.existsSync(resolvedPath)) {\n    res.set('Content-Type', mimeTypes.lookup(resolvedPath) || 'text/css');\n    res.send(fs.readFileSync(resolvedPath));\n  } else {\n    res.status(404).send('File not found');\n  }\n});\n```\n\n## Special Route Patterns\n\n### Manager Route Pattern\n```typescript\nexport class ViewsRouter extends RouterBroker {\n  public readonly router: Router;\n\n  constructor() {\n    super();\n    this.router = Router();\n\n    const basePath = path.join(process.cwd(), 'manager', 'dist');\n    const indexPath = path.join(basePath, 'index.html');\n\n    this.router.use(express.static(basePath));\n\n    this.router.get('*', (req, res) => {\n      res.sendFile(indexPath);\n    });\n  }\n}\n```\n\n### Webhook Route Pattern\n```typescript\n// CORRECT - Webhook without guards\n.post('/webhook/evolution', async (req, res) => {\n  const response = await evolutionController.receiveWebhook(req.body);\n  return res.status(HttpStatus.OK).json(response);\n})\n\n// CORRECT - Webhook with signature validation\n.post('/webhook/meta', validateWebhookSignature, async (req, res) => {\n  const response = await metaController.receiveWebhook(req.body);\n  return res.status(HttpStatus.OK).json(response);\n})\n```\n\n## Response Pattern\n\n### Standard Response Format\n```typescript\n// CORRECT - Standard success response\nreturn res.status(HttpStatus.OK).json(response);\n\n// CORRECT - Created response\nreturn res.status(HttpStatus.CREATED).json(response);\n\n// CORRECT - Custom response with additional data\nreturn res.status(HttpStatus.OK).json({\n  ...response,\n  timestamp: new Date().toISOString(),\n  instanceName: req.params.instanceName,\n});\n```\n\n## Route Organization\n\n### File Structure\n```\nsrc/api/routes/\n├── index.router.ts          # Main router with all route registrations\n├── instance.router.ts       # Instance management routes\n├── sendMessage.router.ts    # Message sending routes\n├── chat.router.ts          # Chat operations routes\n├── business.router.ts      # Business API routes\n├── group.router.ts         # Group management routes\n├── label.router.ts         # Label management routes\n├── proxy.router.ts         # Proxy configuration routes\n├── settings.router.ts      # Instance settings routes\n├── template.router.ts      # Template management routes\n├── call.router.ts          # Call operations routes\n└── view.router.ts          # Frontend views routes\n```\n\n## Route Testing Pattern\n\n### Router Testing\n```typescript\ndescribe('ExampleRouter', () => {\n  let app: express.Application;\n  let router: ExampleRouter;\n\n  beforeEach(() => {\n    app = express();\n    router = new ExampleRouter();\n    app.use('/api', router.router);\n    app.use(express.json());\n  });\n\n  describe('GET /findExample', () => {\n    it('should return example data', async () => {\n      const response = await request(app)\n        .get('/api/findExample/test-instance')\n        .set('apikey', 'test-key')\n        .expect(200);\n\n      expect(response.body).toBeDefined();\n      expect(response.body.instanceName).toBe('test-instance');\n    });\n\n    it('should return 401 without API key', async () => {\n      await request(app)\n        .get('/api/findExample/test-instance')\n        .expect(401);\n    });\n  });\n\n  describe('POST /createExample', () => {\n    it('should create example successfully', async () => {\n      const data = {\n        name: 'Test Example',\n        description: 'Test Description',\n      };\n\n      const response = await request(app)\n        .post('/api/createExample/test-instance')\n        .set('apikey', 'test-key')\n        .send(data)\n        .expect(201);\n\n      expect(response.body.name).toBe(data.name);\n    });\n\n    it('should validate required fields', async () => {\n      const data = {\n        description: 'Test Description',\n        // Missing required 'name' field\n      };\n\n      await request(app)\n        .post('/api/createExample/test-instance')\n        .set('apikey', 'test-key')\n        .send(data)\n        .expect(400);\n    });\n  });\n});\n```\n\n## Route Documentation\n\n### JSDoc for Routes\n```typescript\n/**\n * @route GET /api/template/findTemplate/:instanceName\n * @description Find template for instance\n * @param {string} instanceName - Instance name\n * @returns {TemplateDto} Template data\n * @throws {404} Template not found\n * @throws {401} Unauthorized\n */\n.get(this.routerPath('findTemplate'), ...guards, async (req, res) => {\n  // Implementation\n})\n\n/**\n * @route POST /api/template/createTemplate/:instanceName\n * @description Create new template\n * @param {string} instanceName - Instance name\n * @body {TemplateDto} Template data\n * @returns {TemplateDto} Created template\n * @throws {400} Validation error\n * @throws {401} Unauthorized\n */\n.post(this.routerPath('createTemplate'), ...guards, async (req, res) => {\n  // Implementation\n})\n```",".cursor/rules/specialized-rules/service-rules.mdc":"---\ndescription: Service layer patterns for Evolution API\nglobs:\n  - \"src/api/services/**/*.ts\"\n  - \"src/api/integrations/**/services/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Service Rules\n\n## Service Structure Pattern\n\n### Standard Service Class\n```typescript\nexport class ExampleService {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n\n  private readonly logger = new Logger('ExampleService');\n\n  public async create(instance: InstanceDto, data: ExampleDto) {\n    await this.waMonitor.waInstances[instance.instanceName].setData(data);\n    return { example: { ...instance, data } };\n  }\n\n  public async find(instance: InstanceDto): Promise<ExampleDto> {\n    try {\n      const result = await this.waMonitor.waInstances[instance.instanceName].findData();\n\n      if (Object.keys(result).length === 0) {\n        throw new Error('Data not found');\n      }\n\n      return result;\n    } catch (error) {\n      return null; // Evolution pattern - return null on error\n    }\n  }\n}\n```\n\n## Dependency Injection Pattern\n\n### Constructor Pattern\n```typescript\n// CORRECT - Evolution API pattern\nconstructor(\n  private readonly waMonitor: WAMonitoringService,\n  private readonly prismaRepository: PrismaRepository,\n  private readonly configService: ConfigService,\n) {}\n\n// INCORRECT - Don't use\nconstructor(waMonitor, prismaRepository, configService) {} // ❌ No types\n```\n\n## Logger Pattern\n\n### Standard Logger Usage\n```typescript\n// CORRECT - Evolution API pattern\nprivate readonly logger = new Logger('ServiceName');\n\n// Usage\nthis.logger.log('Operation started');\nthis.logger.error('Operation failed', error);\n\n// INCORRECT\nconsole.log('Operation started'); // ❌ Use Logger\n```\n\n## WAMonitor Integration Pattern\n\n### Instance Access Pattern\n```typescript\n// CORRECT - Standard pattern\npublic async operation(instance: InstanceDto, data: DataDto) {\n  await this.waMonitor.waInstances[instance.instanceName].performAction(data);\n  return { result: { ...instance, data } };\n}\n\n// Instance validation\nconst waInstance = this.waMonitor.waInstances[instance.instanceName];\nif (!waInstance) {\n  throw new NotFoundException('Instance not found');\n}\n```\n\n## Error Handling Pattern\n\n### Try-Catch Pattern\n```typescript\n// CORRECT - Evolution API pattern\npublic async find(instance: InstanceDto): Promise<DataDto> {\n  try {\n    const result = await this.waMonitor.waInstances[instance.instanceName].findData();\n    \n    if (Object.keys(result).length === 0) {\n      throw new Error('Data not found');\n    }\n\n    return result;\n  } catch (error) {\n    this.logger.error('Find operation failed', error);\n    return null; // Return null on error (Evolution pattern)\n  }\n}\n```\n\n## Cache Integration Pattern\n\n### Cache Service Usage\n```typescript\nexport class CacheAwareService {\n  constructor(\n    private readonly cache: CacheService,\n    private readonly chatwootCache: CacheService,\n    private readonly baileysCache: CacheService,\n  ) {}\n\n  public async getCachedData(key: string): Promise<any> {\n    const cached = await this.cache.get(key);\n    if (cached) return cached;\n\n    const data = await this.fetchFromSource(key);\n    await this.cache.set(key, data, 300); // 5 min TTL\n    return data;\n  }\n}\n```\n\n## Integration Service Patterns\n\n### Chatbot Service Base Pattern\n```typescript\nexport class ChatbotService extends BaseChatbotService<BotType, SettingsType> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(waMonitor, prismaRepository, 'ChatbotService', configService);\n  }\n\n  protected async processBot(\n    waInstance: any,\n    remoteJid: string,\n    bot: BotType,\n    session: any,\n    settings: any,\n    content: string,\n  ): Promise<void> {\n    // Implementation\n  }\n}\n```\n\n### Channel Service Pattern\n```typescript\nexport class ChannelService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n    baileysCache: CacheService,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache, baileysCache);\n  }\n\n  public readonly logger = new Logger('ChannelService');\n  public client: WASocket;\n  public readonly instance: wa.Instance = {};\n}\n```\n\n## Service Initialization Pattern\n\n### Service Registration\n```typescript\n// In server.module.ts pattern\nexport const templateService = new TemplateService(\n  waMonitor,\n  prismaRepository,\n  configService,\n);\n\nexport const settingsService = new SettingsService(waMonitor);\n```\n\n## Async Operation Patterns\n\n### Promise Handling\n```typescript\n// CORRECT - Evolution API pattern\npublic async sendMessage(instance: InstanceDto, data: MessageDto) {\n  const waInstance = this.waMonitor.waInstances[instance.instanceName];\n  return await waInstance.sendMessage(data);\n}\n\n// INCORRECT - Don't use .then()\npublic sendMessage(instance: InstanceDto, data: MessageDto) {\n  return this.waMonitor.waInstances[instance.instanceName]\n    .sendMessage(data)\n    .then(result => result); // ❌ Use async/await\n}\n```\n\n## Configuration Access Pattern\n\n### Config Service Usage\n```typescript\n// CORRECT - Evolution API pattern\nconst serverConfig = this.configService.get<HttpServer>('SERVER');\nconst authConfig = this.configService.get<Auth>('AUTHENTICATION');\nconst dbConfig = this.configService.get<Database>('DATABASE');\n\n// Type-safe configuration access\nif (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) {\n  // Chatwoot logic\n}\n```\n\n## Event Emission Pattern\n\n### EventEmitter2 Usage\n```typescript\n// CORRECT - Evolution API pattern\nthis.eventEmitter.emit(Events.INSTANCE_CREATE, {\n  instanceName: instance.name,\n  status: 'created',\n});\n\n// Chatwoot event pattern\nif (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {\n  this.chatwootService.eventWhatsapp(\n    Events.STATUS_INSTANCE,\n    { instanceName: this.instance.name },\n    {\n      instance: this.instance.name,\n      status: 'created',\n    },\n  );\n}\n```\n\n## Service Method Naming\n\n### Standard Method Names\n- `create()` - Create new resource\n- `find()` - Find single resource\n- `findAll()` - Find multiple resources\n- `update()` - Update resource\n- `delete()` - Delete resource\n- `fetch*()` - Fetch from external API\n- `send*()` - Send data/messages\n- `process*()` - Process data\n\n## Service Testing Pattern\n\n### Unit Test Structure\n```typescript\ndescribe('ExampleService', () => {\n  let service: ExampleService;\n  let waMonitor: jest.Mocked<WAMonitoringService>;\n  let prismaRepository: jest.Mocked<PrismaRepository>;\n\n  beforeEach(() => {\n    const mockWaMonitor = {\n      waInstances: {\n        'test-instance': {\n          performAction: jest.fn(),\n        },\n      },\n    };\n\n    service = new ExampleService(\n      mockWaMonitor as any,\n      prismaRepository,\n      configService,\n    );\n  });\n\n  it('should perform action successfully', async () => {\n    const instance = { instanceName: 'test-instance' };\n    const data = { test: 'data' };\n\n    const result = await service.create(instance, data);\n\n    expect(result).toBeDefined();\n    expect(waMonitor.waInstances['test-instance'].performAction).toHaveBeenCalledWith(data);\n  });\n});\n```",".cursor/rules/specialized-rules/type-rules.mdc":"---\ndescription: Type definitions and interfaces for Evolution API\nglobs:\n  - \"src/api/types/**/*.ts\"\n  - \"src/@types/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Type Rules\n\n## Namespace Pattern\n\n### WhatsApp Types Namespace\n```typescript\n/* eslint-disable @typescript-eslint/no-namespace */\nimport { JsonValue } from '@prisma/client/runtime/library';\nimport { AuthenticationState, WAConnectionState } from 'baileys';\n\nexport declare namespace wa {\n  export type QrCode = {\n    count?: number;\n    pairingCode?: string;\n    base64?: string;\n    code?: string;\n  };\n\n  export type Instance = {\n    id?: string;\n    qrcode?: QrCode;\n    pairingCode?: string;\n    authState?: { state: AuthenticationState; saveCreds: () => void };\n    name?: string;\n    wuid?: string;\n    profileName?: string;\n    profilePictureUrl?: string;\n    token?: string;\n    number?: string;\n    integration?: string;\n    businessId?: string;\n  };\n\n  export type LocalChatwoot = {\n    enabled?: boolean;\n    accountId?: string;\n    token?: string;\n    url?: string;\n    nameInbox?: string;\n    mergeBrazilContacts?: boolean;\n    importContacts?: boolean;\n    importMessages?: boolean;\n    daysLimitImportMessages?: number;\n    organization?: string;\n    logo?: string;\n  };\n\n  export type LocalProxy = {\n    enabled?: boolean;\n    host?: string;\n    port?: string;\n    protocol?: string;\n    username?: string;\n    password?: string;\n  };\n\n  export type LocalSettings = {\n    rejectCall?: boolean;\n    msgCall?: string;\n    groupsIgnore?: boolean;\n    alwaysOnline?: boolean;\n    readMessages?: boolean;\n    readStatus?: boolean;\n    syncFullHistory?: boolean;\n  };\n\n  export type LocalWebHook = {\n    enabled?: boolean;\n    url?: string;\n    events?: string[];\n    headers?: JsonValue;\n    byEvents?: boolean;\n    base64?: boolean;\n  };\n\n  export type StatusMessage = 'ERROR' | 'PENDING' | 'SERVER_ACK' | 'DELIVERY_ACK' | 'READ' | 'DELETED' | 'PLAYED';\n}\n```\n\n## Enum Definitions\n\n### Events Enum\n```typescript\nexport enum Events {\n  APPLICATION_STARTUP = 'application.startup',\n  INSTANCE_CREATE = 'instance.create',\n  INSTANCE_DELETE = 'instance.delete',\n  QRCODE_UPDATED = 'qrcode.updated',\n  CONNECTION_UPDATE = 'connection.update',\n  STATUS_INSTANCE = 'status.instance',\n  MESSAGES_SET = 'messages.set',\n  MESSAGES_UPSERT = 'messages.upsert',\n  MESSAGES_EDITED = 'messages.edited',\n  MESSAGES_UPDATE = 'messages.update',\n  MESSAGES_DELETE = 'messages.delete',\n  SEND_MESSAGE = 'send.message',\n  SEND_MESSAGE_UPDATE = 'send.message.update',\n  CONTACTS_SET = 'contacts.set',\n  CONTACTS_UPSERT = 'contacts.upsert',\n  CONTACTS_UPDATE = 'contacts.update',\n  PRESENCE_UPDATE = 'presence.update',\n  CHATS_SET = 'chats.set',\n  CHATS_UPDATE = 'chats.update',\n  CHATS_UPSERT = 'chats.upsert',\n  CHATS_DELETE = 'chats.delete',\n  GROUPS_UPSERT = 'groups.upsert',\n  GROUPS_UPDATE = 'groups.update',\n  GROUP_PARTICIPANTS_UPDATE = 'group-participants.update',\n  CALL = 'call',\n  TYPEBOT_START = 'typebot.start',\n  TYPEBOT_CHANGE_STATUS = 'typebot.change-status',\n  LABELS_EDIT = 'labels.edit',\n  LABELS_ASSOCIATION = 'labels.association',\n  CREDS_UPDATE = 'creds.update',\n  MESSAGING_HISTORY_SET = 'messaging-history.set',\n  REMOVE_INSTANCE = 'remove.instance',\n  LOGOUT_INSTANCE = 'logout.instance',\n}\n```\n\n### Integration Types\n```typescript\nexport const Integration = {\n  WHATSAPP_BUSINESS: 'WHATSAPP-BUSINESS',\n  WHATSAPP_BAILEYS: 'WHATSAPP-BAILEYS',\n  EVOLUTION: 'EVOLUTION',\n} as const;\n\nexport type IntegrationType = typeof Integration[keyof typeof Integration];\n```\n\n## Constant Arrays\n\n### Message Type Constants\n```typescript\nexport const TypeMediaMessage = [\n  'imageMessage',\n  'documentMessage',\n  'audioMessage',\n  'videoMessage',\n  'stickerMessage',\n  'ptvMessage', // Evolution API includes this\n];\n\nexport const MessageSubtype = [\n  'ephemeralMessage',\n  'documentWithCaptionMessage',\n  'viewOnceMessage',\n  'viewOnceMessageV2',\n];\n\nexport type MediaMessageType = typeof TypeMediaMessage[number];\nexport type MessageSubtypeType = typeof MessageSubtype[number];\n```\n\n## Interface Definitions\n\n### Service Interfaces\n```typescript\nexport interface ServiceInterface {\n  create(instance: InstanceDto, data: any): Promise<any>;\n  find(instance: InstanceDto): Promise<any>;\n  update?(instance: InstanceDto, data: any): Promise<any>;\n  delete?(instance: InstanceDto): Promise<any>;\n}\n\nexport interface ChannelServiceInterface extends ServiceInterface {\n  sendMessage(data: SendMessageDto): Promise<any>;\n  connectToWhatsapp(data?: any): Promise<void>;\n  receiveWebhook?(data: any): Promise<void>;\n}\n\nexport interface ChatbotServiceInterface extends ServiceInterface {\n  processMessage(\n    instanceName: string,\n    remoteJid: string,\n    message: any,\n    pushName?: string,\n  ): Promise<void>;\n}\n```\n\n## Configuration Types\n\n### Environment Configuration Types\n```typescript\nexport interface DatabaseConfig {\n  CONNECTION: {\n    URI: string;\n    DB_PREFIX_NAME: string;\n    CLIENT_NAME?: string;\n  };\n  ENABLED: boolean;\n  SAVE_DATA: {\n    INSTANCE: boolean;\n    NEW_MESSAGE: boolean;\n    MESSAGE_UPDATE: boolean;\n    CONTACTS: boolean;\n    CHATS: boolean;\n  };\n}\n\nexport interface AuthConfig {\n  TYPE: 'apikey' | 'jwt';\n  API_KEY: {\n    KEY: string;\n  };\n  JWT?: {\n    EXPIRIN_IN: number;\n    SECRET: string;\n  };\n}\n\nexport interface CacheConfig {\n  REDIS: {\n    ENABLED: boolean;\n    URI: string;\n    PREFIX_KEY: string;\n    SAVE_INSTANCES: boolean;\n  };\n  LOCAL: {\n    ENABLED: boolean;\n    TTL: number;\n  };\n}\n```\n\n## Message Types\n\n### Message Structure Types\n```typescript\nexport interface MessageContent {\n  text?: string;\n  caption?: string;\n  media?: Buffer | string;\n  mediatype?: 'image' | 'video' | 'audio' | 'document' | 'sticker';\n  fileName?: string;\n  mimetype?: string;\n}\n\nexport interface MessageOptions {\n  delay?: number;\n  presence?: 'unavailable' | 'available' | 'composing' | 'recording' | 'paused';\n  linkPreview?: boolean;\n  mentionsEveryOne?: boolean;\n  mentioned?: string[];\n  quoted?: {\n    key: {\n      remoteJid: string;\n      fromMe: boolean;\n      id: string;\n    };\n    message: any;\n  };\n}\n\nexport interface SendMessageRequest {\n  number: string;\n  content: MessageContent;\n  options?: MessageOptions;\n}\n```\n\n## Webhook Types\n\n### Webhook Payload Types\n```typescript\nexport interface WebhookPayload {\n  event: Events;\n  instance: string;\n  data: any;\n  timestamp: string;\n  server?: {\n    version: string;\n    host: string;\n  };\n}\n\nexport interface WebhookConfig {\n  enabled: boolean;\n  url: string;\n  events: Events[];\n  headers?: Record<string, string>;\n  byEvents?: boolean;\n  base64?: boolean;\n}\n```\n\n## Error Types\n\n### Custom Error Types\n```typescript\nexport interface ApiError {\n  status: number;\n  message: string;\n  error?: string;\n  details?: any;\n  timestamp: string;\n  path: string;\n}\n\nexport interface ValidationError extends ApiError {\n  status: 400;\n  validationErrors: Array<{\n    field: string;\n    message: string;\n    value?: any;\n  }>;\n}\n\nexport interface AuthenticationError extends ApiError {\n  status: 401;\n  message: 'Unauthorized' | 'Invalid API Key' | 'Token Expired';\n}\n```\n\n## Utility Types\n\n### Generic Utility Types\n```typescript\nexport type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;\n\nexport type RequiredFields<T, K extends keyof T> = T & Required<Pick<T, K>>;\n\nexport type DeepPartial<T> = {\n  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];\n};\n\nexport type NonEmptyArray<T> = [T, ...T[]];\n\nexport type StringKeys<T> = {\n  [K in keyof T]: T[K] extends string ? K : never;\n}[keyof T];\n```\n\n## Response Types\n\n### API Response Types\n```typescript\nexport interface ApiResponse<T = any> {\n  success: boolean;\n  data?: T;\n  message?: string;\n  error?: string;\n  timestamp: string;\n}\n\nexport interface PaginatedResponse<T> extends ApiResponse<T[]> {\n  pagination: {\n    page: number;\n    limit: number;\n    total: number;\n    totalPages: number;\n    hasNext: boolean;\n    hasPrev: boolean;\n  };\n}\n\nexport interface InstanceResponse extends ApiResponse {\n  instance: {\n    instanceName: string;\n    status: 'connecting' | 'open' | 'close' | 'qr';\n    qrcode?: string;\n    profileName?: string;\n    profilePicUrl?: string;\n  };\n}\n```\n\n## Integration-Specific Types\n\n### Baileys Types Extension\n```typescript\nimport { WASocket, ConnectionState, DisconnectReason } from 'baileys';\n\nexport interface BaileysInstance {\n  client: WASocket;\n  state: ConnectionState;\n  qrRetry: number;\n  authPath: string;\n}\n\nexport interface BaileysConfig {\n  qrTimeout: number;\n  maxQrRetries: number;\n  authTimeout: number;\n  reconnectInterval: number;\n}\n```\n\n### Business API Types\n```typescript\nexport interface BusinessApiConfig {\n  version: string;\n  baseUrl: string;\n  timeout: number;\n  retries: number;\n}\n\nexport interface BusinessApiMessage {\n  messaging_product: 'whatsapp';\n  to: string;\n  type: 'text' | 'image' | 'document' | 'audio' | 'video' | 'template';\n  text?: {\n    body: string;\n    preview_url?: boolean;\n  };\n  image?: {\n    link?: string;\n    id?: string;\n    caption?: string;\n  };\n  template?: {\n    name: string;\n    language: {\n      code: string;\n    };\n    components?: any[];\n  };\n}\n```\n\n## Type Guards\n\n### Type Guard Functions\n```typescript\nexport function isMediaMessage(message: any): message is MediaMessage {\n  return message && TypeMediaMessage.some(type => message[type]);\n}\n\nexport function isTextMessage(message: any): message is TextMessage {\n  return message && message.conversation;\n}\n\nexport function isValidIntegration(integration: string): integration is IntegrationType {\n  return Object.values(Integration).includes(integration as IntegrationType);\n}\n\nexport function isValidEvent(event: string): event is Events {\n  return Object.values(Events).includes(event as Events);\n}\n```\n\n## Module Augmentation\n\n### Express Request Extension\n```typescript\ndeclare global {\n  namespace Express {\n    interface Request {\n      instanceName?: string;\n      instanceData?: InstanceDto;\n      user?: {\n        id: string;\n        apiKey: string;\n      };\n    }\n  }\n}\n```\n\n## Type Documentation\n\n### JSDoc Type Documentation\n```typescript\n/**\n * WhatsApp instance configuration\n * @interface InstanceConfig\n * @property {string} name - Unique instance name\n * @property {IntegrationType} integration - Integration type\n * @property {string} [token] - API token for business integrations\n * @property {WebhookConfig} [webhook] - Webhook configuration\n * @property {ProxyConfig} [proxy] - Proxy configuration\n */\nexport interface InstanceConfig {\n  name: string;\n  integration: IntegrationType;\n  token?: string;\n  webhook?: WebhookConfig;\n  proxy?: ProxyConfig;\n}\n```",".cursor/rules/specialized-rules/util-rules.mdc":"---\ndescription: Utility functions and helpers for Evolution API\nglobs:\n  - \"src/utils/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Utility Rules\n\n## Utility Function Structure\n\n### Standard Utility Pattern\n```typescript\nimport { Logger } from '@config/logger.config';\n\nconst logger = new Logger('UtilityName');\n\nexport function utilityFunction(param: ParamType): ReturnType {\n  try {\n    // Utility logic\n    return result;\n  } catch (error) {\n    logger.error(`Utility function failed: ${error.message}`);\n    throw error;\n  }\n}\n\nexport default utilityFunction;\n```\n\n## Authentication Utilities\n\n### Multi-File Auth State Pattern\n```typescript\nimport { AuthenticationState } from 'baileys';\nimport { CacheService } from '@api/services/cache.service';\nimport fs from 'fs/promises';\nimport path from 'path';\n\nexport default async function useMultiFileAuthStatePrisma(\n  sessionId: string,\n  cache: CacheService,\n): Promise<{\n  state: AuthenticationState;\n  saveCreds: () => Promise<void>;\n}> {\n  const localFolder = path.join(INSTANCE_DIR, sessionId);\n  const localFile = (key: string) => path.join(localFolder, fixFileName(key) + '.json');\n  await fs.mkdir(localFolder, { recursive: true });\n\n  async function writeData(data: any, key: string): Promise<any> {\n    const dataString = JSON.stringify(data, BufferJSON.replacer);\n\n    if (key !== 'creds') {\n      if (process.env.CACHE_REDIS_ENABLED === 'true') {\n        return await cache.hSet(sessionId, key, data);\n      } else {\n        await fs.writeFile(localFile(key), dataString);\n        return;\n      }\n    }\n    await saveKey(sessionId, dataString);\n    return;\n  }\n\n  async function readData(key: string): Promise<any> {\n    try {\n      let rawData;\n\n      if (key !== 'creds') {\n        if (process.env.CACHE_REDIS_ENABLED === 'true') {\n          return await cache.hGet(sessionId, key);\n        } else {\n          if (!(await fileExists(localFile(key)))) return null;\n          rawData = await fs.readFile(localFile(key), { encoding: 'utf-8' });\n          return JSON.parse(rawData, BufferJSON.reviver);\n        }\n      } else {\n        rawData = await getAuthKey(sessionId);\n      }\n\n      const parsedData = JSON.parse(rawData, BufferJSON.reviver);\n      return parsedData;\n    } catch (error) {\n      return null;\n    }\n  }\n\n  async function removeData(key: string): Promise<any> {\n    try {\n      if (key !== 'creds') {\n        if (process.env.CACHE_REDIS_ENABLED === 'true') {\n          return await cache.hDelete(sessionId, key);\n        } else {\n          await fs.unlink(localFile(key));\n        }\n      } else {\n        await deleteAuthKey(sessionId);\n      }\n    } catch (error) {\n      return;\n    }\n  }\n\n  let creds = await readData('creds');\n  if (!creds) {\n    creds = initAuthCreds();\n    await writeData(creds, 'creds');\n  }\n\n  return {\n    state: {\n      creds,\n      keys: {\n        get: async (type, ids) => {\n          const data = {};\n          await Promise.all(\n            ids.map(async (id) => {\n              let value = await readData(`${type}-${id}`);\n              if (type === 'app-state-sync-key' && value) {\n                value = proto.Message.AppStateSyncKeyData.fromObject(value);\n              }\n              data[id] = value;\n            })\n          );\n          return data;\n        },\n        set: async (data) => {\n          const tasks = [];\n          for (const category in data) {\n            for (const id in data[category]) {\n              const value = data[category][id];\n              const key = `${category}-${id}`;\n              tasks.push(value ? writeData(value, key) : removeData(key));\n            }\n          }\n          await Promise.all(tasks);\n        },\n      },\n    },\n    saveCreds: () => writeData(creds, 'creds'),\n  };\n}\n```\n\n## Message Processing Utilities\n\n### Message Content Extraction\n```typescript\nexport const getConversationMessage = (msg: any): string => {\n  const types = getTypeMessage(msg);\n  const messageContent = getMessageContent(types);\n  return messageContent;\n};\n\nconst getTypeMessage = (msg: any): any => {\n  return Object.keys(msg?.message || msg || {})[0];\n};\n\nconst getMessageContent = (type: string, msg?: any): string => {\n  const typeKey = type?.replace('Message', '');\n  \n  const types = {\n    conversation: msg?.message?.conversation,\n    extendedTextMessage: msg?.message?.extendedTextMessage?.text,\n    imageMessage: msg?.message?.imageMessage?.caption || 'Image',\n    videoMessage: msg?.message?.videoMessage?.caption || 'Video',\n    audioMessage: 'Audio',\n    documentMessage: msg?.message?.documentMessage?.caption || 'Document',\n    stickerMessage: 'Sticker',\n    contactMessage: 'Contact',\n    locationMessage: 'Location',\n    liveLocationMessage: 'Live Location',\n    viewOnceMessage: 'View Once',\n    reactionMessage: 'Reaction',\n    pollCreationMessage: 'Poll',\n    pollUpdateMessage: 'Poll Update',\n  };\n\n  let result = types[typeKey] || types[type] || 'Unknown';\n  \n  if (!result || result === 'Unknown') {\n    result = JSON.stringify(msg);\n  }\n\n  return result;\n};\n```\n\n### JID Creation Utility\n```typescript\nexport const createJid = (number: string): string => {\n  if (number.includes('@')) {\n    return number;\n  }\n\n  // Remove any non-numeric characters except +\n  let cleanNumber = number.replace(/[^\\d+]/g, '');\n  \n  // Remove + if present\n  if (cleanNumber.startsWith('+')) {\n    cleanNumber = cleanNumber.substring(1);\n  }\n\n  // Add country code if missing (assuming Brazil as default)\n  if (cleanNumber.length === 11 && cleanNumber.startsWith('11')) {\n    cleanNumber = '55' + cleanNumber;\n  } else if (cleanNumber.length === 10) {\n    cleanNumber = '5511' + cleanNumber;\n  }\n\n  // Determine if it's a group or individual\n  const isGroup = cleanNumber.includes('-');\n  const domain = isGroup ? 'g.us' : 's.whatsapp.net';\n\n  return `${cleanNumber}@${domain}`;\n};\n```\n\n## Cache Utilities\n\n### WhatsApp Number Cache\n```typescript\ninterface ISaveOnWhatsappCacheParams {\n  remoteJid: string;\n  lid?: string;\n}\n\nfunction getAvailableNumbers(remoteJid: string): string[] {\n  const numbersAvailable: string[] = [];\n\n  if (remoteJid.startsWith('+')) {\n    remoteJid = remoteJid.slice(1);\n  }\n\n  const [number, domain] = remoteJid.split('@');\n\n  // Brazilian numbers\n  if (remoteJid.startsWith('55')) {\n    const numberWithDigit =\n      number.slice(4, 5) === '9' && number.length === 13 ? number : `${number.slice(0, 4)}9${number.slice(4)}`;\n    const numberWithoutDigit = number.length === 12 ? number : number.slice(0, 4) + number.slice(5);\n\n    numbersAvailable.push(numberWithDigit);\n    numbersAvailable.push(numberWithoutDigit);\n  }\n  // Mexican/Argentina numbers\n  else if (number.startsWith('52') || number.startsWith('54')) {\n    let prefix = '';\n    if (number.startsWith('52')) {\n      prefix = '1';\n    }\n    if (number.startsWith('54')) {\n      prefix = '9';\n    }\n\n    const numberWithDigit =\n      number.slice(2, 3) === prefix && number.length === 13\n        ? number\n        : `${number.slice(0, 2)}${prefix}${number.slice(2)}`;\n    const numberWithoutDigit = number.length === 12 ? number : number.slice(0, 2) + number.slice(3);\n\n    numbersAvailable.push(numberWithDigit);\n    numbersAvailable.push(numberWithoutDigit);\n  }\n  // Other countries\n  else {\n    numbersAvailable.push(remoteJid);\n  }\n\n  return numbersAvailable.map((number) => `${number}@${domain}`);\n}\n\nexport async function saveOnWhatsappCache(params: ISaveOnWhatsappCacheParams): Promise<void> {\n  const { remoteJid, lid } = params;\n  const db = configService.get<Database>('DATABASE');\n\n  if (!db.SAVE_DATA.CONTACTS) {\n    return;\n  }\n\n  try {\n    const numbersAvailable = getAvailableNumbers(remoteJid);\n    \n    const existingContact = await prismaRepository.contact.findFirst({\n      where: {\n        OR: numbersAvailable.map(number => ({ id: number })),\n      },\n    });\n\n    if (!existingContact) {\n      await prismaRepository.contact.create({\n        data: {\n          id: remoteJid,\n          pushName: '',\n          profilePicUrl: '',\n          isOnWhatsapp: true,\n          lid: lid || null,\n          createdAt: new Date(),\n          updatedAt: new Date(),\n        },\n      });\n    } else {\n      await prismaRepository.contact.update({\n        where: { id: existingContact.id },\n        data: {\n          isOnWhatsapp: true,\n          lid: lid || existingContact.lid,\n          updatedAt: new Date(),\n        },\n      });\n    }\n  } catch (error) {\n    console.error('Error saving WhatsApp cache:', error);\n  }\n}\n```\n\n## Search Utilities\n\n### Advanced Search Operators\n```typescript\nfunction normalizeString(str: string): string {\n  return str\n    .toLowerCase()\n    .normalize('NFD')\n    .replace(/[\\u0300-\\u036f]/g, '');\n}\n\nexport function advancedOperatorsSearch(data: string, query: string): boolean {\n  const normalizedData = normalizeString(data);\n  const normalizedQuery = normalizeString(query);\n\n  // Exact phrase search with quotes\n  if (normalizedQuery.startsWith('\"') && normalizedQuery.endsWith('\"')) {\n    const phrase = normalizedQuery.slice(1, -1);\n    return normalizedData.includes(phrase);\n  }\n\n  // OR operator\n  if (normalizedQuery.includes(' OR ')) {\n    const terms = normalizedQuery.split(' OR ');\n    return terms.some(term => normalizedData.includes(term.trim()));\n  }\n\n  // AND operator (default behavior)\n  if (normalizedQuery.includes(' AND ')) {\n    const terms = normalizedQuery.split(' AND ');\n    return terms.every(term => normalizedData.includes(term.trim()));\n  }\n\n  // NOT operator\n  if (normalizedQuery.startsWith('NOT ')) {\n    const term = normalizedQuery.slice(4);\n    return !normalizedData.includes(term);\n  }\n\n  // Wildcard search\n  if (normalizedQuery.includes('*')) {\n    const regex = new RegExp(normalizedQuery.replace(/\\*/g, '.*'), 'i');\n    return regex.test(normalizedData);\n  }\n\n  // Default: simple contains search\n  return normalizedData.includes(normalizedQuery);\n}\n```\n\n## Proxy Utilities\n\n### Proxy Agent Creation\n```typescript\nimport { HttpsProxyAgent } from 'https-proxy-agent';\nimport { SocksProxyAgent } from 'socks-proxy-agent';\n\ntype Proxy = {\n  host: string;\n  port: string;\n  protocol: 'http' | 'https' | 'socks4' | 'socks5';\n  username?: string;\n  password?: string;\n};\n\nfunction selectProxyAgent(proxyUrl: string): HttpsProxyAgent<string> | SocksProxyAgent {\n  const url = new URL(proxyUrl);\n  \n  if (url.protocol === 'socks4:' || url.protocol === 'socks5:') {\n    return new SocksProxyAgent(proxyUrl);\n  } else {\n    return new HttpsProxyAgent(proxyUrl);\n  }\n}\n\nexport function makeProxyAgent(proxy: Proxy): HttpsProxyAgent<string> | SocksProxyAgent | null {\n  if (!proxy.host || !proxy.port) {\n    return null;\n  }\n\n  let proxyUrl = `${proxy.protocol}://`;\n  \n  if (proxy.username && proxy.password) {\n    proxyUrl += `${proxy.username}:${proxy.password}@`;\n  }\n  \n  proxyUrl += `${proxy.host}:${proxy.port}`;\n\n  try {\n    return selectProxyAgent(proxyUrl);\n  } catch (error) {\n    console.error('Failed to create proxy agent:', error);\n    return null;\n  }\n}\n```\n\n## Telemetry Utilities\n\n### Telemetry Data Collection\n```typescript\nexport interface TelemetryData {\n  route: string;\n  apiVersion: string;\n  timestamp: Date;\n  method?: string;\n  statusCode?: number;\n  responseTime?: number;\n  userAgent?: string;\n  instanceName?: string;\n}\n\nexport const sendTelemetry = async (route: string): Promise<void> => {\n  try {\n    const telemetryData: TelemetryData = {\n      route,\n      apiVersion: packageJson.version,\n      timestamp: new Date(),\n    };\n\n    // Only send telemetry if enabled\n    if (process.env.DISABLE_TELEMETRY === 'true') {\n      return;\n    }\n\n    // Send to telemetry service (implement as needed)\n    await axios.post('https://telemetry.evolution-api.com/collect', telemetryData, {\n      timeout: 5000,\n    });\n  } catch (error) {\n    // Silently fail - don't affect main application\n    console.debug('Telemetry failed:', error.message);\n  }\n};\n```\n\n## Internationalization Utilities\n\n### i18n Setup\n```typescript\nimport { ConfigService, Language } from '@config/env.config';\nimport fs from 'fs';\nimport i18next from 'i18next';\nimport path from 'path';\n\nconst __dirname = path.resolve(process.cwd(), 'src', 'utils');\nconst languages = ['en', 'pt-BR', 'es'];\nconst translationsPath = path.join(__dirname, 'translations');\nconst configService: ConfigService = new ConfigService();\n\nconst resources: any = {};\n\nlanguages.forEach((language) => {\n  const languagePath = path.join(translationsPath, `${language}.json`);\n  if (fs.existsSync(languagePath)) {\n    const translationContent = fs.readFileSync(languagePath, 'utf8');\n    resources[language] = {\n      translation: JSON.parse(translationContent),\n    };\n  }\n});\n\ni18next.init({\n  resources,\n  fallbackLng: 'en',\n  lng: configService.get<Language>('LANGUAGE') || 'pt-BR',\n  interpolation: {\n    escapeValue: false,\n  },\n});\n\nexport const t = i18next.t.bind(i18next);\nexport default i18next;\n```\n\n## Bot Trigger Utilities\n\n### Bot Trigger Matching\n```typescript\nimport { TriggerOperator, TriggerType } from '@prisma/client';\n\nexport function findBotByTrigger(\n  bots: any[],\n  content: string,\n  remoteJid: string,\n): any | null {\n  for (const bot of bots) {\n    if (!bot.enabled) continue;\n    \n    // Check ignore list\n    if (bot.ignoreJids && bot.ignoreJids.includes(remoteJid)) {\n      continue;\n    }\n\n    // Check trigger\n    if (matchesTrigger(content, bot.triggerType, bot.triggerOperator, bot.triggerValue)) {\n      return bot;\n    }\n  }\n\n  return null;\n}\n\nfunction matchesTrigger(\n  content: string,\n  triggerType: TriggerType,\n  triggerOperator: TriggerOperator,\n  triggerValue: string,\n): boolean {\n  const normalizedContent = content.toLowerCase().trim();\n  const normalizedValue = triggerValue.toLowerCase().trim();\n\n  switch (triggerType) {\n    case TriggerType.ALL:\n      return true;\n\n    case TriggerType.KEYWORD:\n      return matchesKeyword(normalizedContent, triggerOperator, normalizedValue);\n\n    case TriggerType.REGEX:\n      try {\n        const regex = new RegExp(triggerValue, 'i');\n        return regex.test(content);\n      } catch {\n        return false;\n      }\n\n    default:\n      return false;\n  }\n}\n\nfunction matchesKeyword(\n  content: string,\n  operator: TriggerOperator,\n  value: string,\n): boolean {\n  switch (operator) {\n    case TriggerOperator.EQUALS:\n      return content === value;\n    case TriggerOperator.CONTAINS:\n      return content.includes(value);\n    case TriggerOperator.STARTS_WITH:\n      return content.startsWith(value);\n    case TriggerOperator.ENDS_WITH:\n      return content.endsWith(value);\n    default:\n      return false;\n  }\n}\n```\n\n## Server Utilities\n\n### Server Status Check\n```typescript\nexport class ServerUP {\n  private static instance: ServerUP;\n  private isServerUp: boolean = false;\n\n  private constructor() {}\n\n  public static getInstance(): ServerUP {\n    if (!ServerUP.instance) {\n      ServerUP.instance = new ServerUP();\n    }\n    return ServerUP.instance;\n  }\n\n  public setServerStatus(status: boolean): void {\n    this.isServerUp = status;\n  }\n\n  public getServerStatus(): boolean {\n    return this.isServerUp;\n  }\n\n  public async waitForServer(timeout: number = 30000): Promise<boolean> {\n    const startTime = Date.now();\n    \n    while (!this.isServerUp && (Date.now() - startTime) < timeout) {\n      await new Promise(resolve => setTimeout(resolve, 100));\n    }\n    \n    return this.isServerUp;\n  }\n}\n```\n\n## Error Response Utilities\n\n### Standardized Error Responses\n```typescript\nexport function createMetaErrorResponse(error: any, context: string) {\n  const timestamp = new Date().toISOString();\n  \n  if (error.response?.data) {\n    return {\n      status: error.response.status || 500,\n      error: {\n        message: error.response.data.error?.message || 'External API error',\n        type: error.response.data.error?.type || 'api_error',\n        code: error.response.data.error?.code || 'unknown_error',\n        context,\n        timestamp,\n      },\n    };\n  }\n\n  return {\n    status: 500,\n    error: {\n      message: error.message || 'Internal server error',\n      type: 'internal_error',\n      code: 'server_error',\n      context,\n      timestamp,\n    },\n  };\n}\n\nexport function createValidationErrorResponse(errors: any[], context: string) {\n  return {\n    status: 400,\n    error: {\n      message: 'Validation failed',\n      type: 'validation_error',\n      code: 'invalid_input',\n      context,\n      details: errors,\n      timestamp: new Date().toISOString(),\n    },\n  };\n}\n```",".cursor/rules/specialized-rules/validate-rules.mdc":"---\ndescription: Validation schemas and patterns for Evolution API\nglobs:\n  - \"src/validate/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Validation Rules\n\n## Validation Schema Structure\n\n### JSONSchema7 Pattern (Evolution API Standard)\n```typescript\nimport { JSONSchema7 } from 'json-schema';\nimport { v4 } from 'uuid';\n\nconst isNotEmpty = (...fields: string[]) => {\n  const properties = {};\n  fields.forEach((field) => {\n    properties[field] = {\n      if: { properties: { [field]: { type: 'string' } } },\n      then: { properties: { [field]: { minLength: 1 } } },\n    };\n  });\n\n  return {\n    allOf: Object.values(properties),\n  };\n};\n\nexport const exampleSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    name: { type: 'string' },\n    description: { type: 'string' },\n    enabled: { type: 'boolean' },\n    settings: {\n      type: 'object',\n      properties: {\n        timeout: { type: 'number', minimum: 1000, maximum: 60000 },\n        retries: { type: 'number', minimum: 0, maximum: 5 },\n      },\n    },\n    tags: {\n      type: 'array',\n      items: { type: 'string' },\n    },\n  },\n  required: ['name', 'enabled'],\n  ...isNotEmpty('name'),\n};\n```\n\n## Message Validation Schemas\n\n### Send Message Validation\n```typescript\nconst numberDefinition = {\n  type: 'string',\n  pattern: '^\\\\d+[\\\\.@\\\\w-]+',\n  description: 'Invalid number',\n};\n\nexport const sendTextSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    number: numberDefinition,\n    text: { type: 'string', minLength: 1, maxLength: 4096 },\n    delay: { type: 'number', minimum: 0, maximum: 60000 },\n    linkPreview: { type: 'boolean' },\n    mentionsEveryOne: { type: 'boolean' },\n    mentioned: {\n      type: 'array',\n      items: { type: 'string' },\n    },\n  },\n  required: ['number', 'text'],\n  ...isNotEmpty('number', 'text'),\n};\n\nexport const sendMediaSchema = Joi.object({\n  number: Joi.string().required().pattern(/^\\d+$/),\n  mediatype: Joi.string().required().valid('image', 'video', 'audio', 'document'),\n  media: Joi.alternatives().try(\n    Joi.string().uri(),\n    Joi.string().base64(),\n  ).required(),\n  caption: Joi.string().optional().max(1024),\n  fileName: Joi.string().optional().max(255),\n  delay: Joi.number().optional().min(0).max(60000),\n}).required();\n\nexport const sendButtonsSchema = Joi.object({\n  number: Joi.string().required().pattern(/^\\d+$/),\n  title: Joi.string().required().max(1024),\n  description: Joi.string().optional().max(1024),\n  footer: Joi.string().optional().max(60),\n  buttons: Joi.array().items(\n    Joi.object({\n      type: Joi.string().required().valid('replyButton', 'urlButton', 'callButton'),\n      displayText: Joi.string().required().max(20),\n      id: Joi.string().when('type', {\n        is: 'replyButton',\n        then: Joi.required().max(256),\n        otherwise: Joi.optional(),\n      }),\n      url: Joi.string().when('type', {\n        is: 'urlButton',\n        then: Joi.required().uri(),\n        otherwise: Joi.optional(),\n      }),\n      phoneNumber: Joi.string().when('type', {\n        is: 'callButton',\n        then: Joi.required().pattern(/^\\+?\\d+$/),\n        otherwise: Joi.optional(),\n      }),\n    })\n  ).min(1).max(3).required(),\n}).required();\n```\n\n## Instance Validation Schemas\n\n### Instance Creation Validation\n```typescript\nexport const instanceSchema = Joi.object({\n  instanceName: Joi.string().required().min(1).max(100).pattern(/^[a-zA-Z0-9_-]+$/),\n  integration: Joi.string().required().valid('WHATSAPP-BAILEYS', 'WHATSAPP-BUSINESS', 'EVOLUTION'),\n  token: Joi.string().when('integration', {\n    is: Joi.valid('WHATSAPP-BUSINESS', 'EVOLUTION'),\n    then: Joi.required().min(10),\n    otherwise: Joi.optional(),\n  }),\n  qrcode: Joi.boolean().optional().default(false),\n  number: Joi.string().optional().pattern(/^\\d+$/),\n  businessId: Joi.string().when('integration', {\n    is: 'WHATSAPP-BUSINESS',\n    then: Joi.required(),\n    otherwise: Joi.optional(),\n  }),\n}).required();\n\nexport const settingsSchema = Joi.object({\n  rejectCall: Joi.boolean().optional(),\n  msgCall: Joi.string().optional().max(500),\n  groupsIgnore: Joi.boolean().optional(),\n  alwaysOnline: Joi.boolean().optional(),\n  readMessages: Joi.boolean().optional(),\n  readStatus: Joi.boolean().optional(),\n  syncFullHistory: Joi.boolean().optional(),\n  wavoipToken: Joi.string().optional(),\n}).optional();\n\nexport const proxySchema = Joi.object({\n  host: Joi.string().required().hostname(),\n  port: Joi.string().required().pattern(/^\\d+$/).custom((value) => {\n    const port = parseInt(value);\n    if (port < 1 || port > 65535) {\n      throw new Error('Port must be between 1 and 65535');\n    }\n    return value;\n  }),\n  protocol: Joi.string().required().valid('http', 'https', 'socks4', 'socks5'),\n  username: Joi.string().optional(),\n  password: Joi.string().optional(),\n}).optional();\n```\n\n## Webhook Validation Schemas\n\n### Webhook Configuration Validation\n```typescript\nexport const webhookSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  url: Joi.string().when('enabled', {\n    is: true,\n    then: Joi.required().uri({ scheme: ['http', 'https'] }),\n    otherwise: Joi.optional(),\n  }),\n  events: Joi.array().items(\n    Joi.string().valid(\n      'APPLICATION_STARTUP',\n      'INSTANCE_CREATE',\n      'INSTANCE_DELETE',\n      'QRCODE_UPDATED',\n      'CONNECTION_UPDATE',\n      'STATUS_INSTANCE',\n      'MESSAGES_SET',\n      'MESSAGES_UPSERT',\n      'MESSAGES_UPDATE',\n      'MESSAGES_DELETE',\n      'CONTACTS_SET',\n      'CONTACTS_UPSERT',\n      'CONTACTS_UPDATE',\n      'CHATS_SET',\n      'CHATS_UPDATE',\n      'CHATS_UPSERT',\n      'CHATS_DELETE',\n      'GROUPS_UPSERT',\n      'GROUPS_UPDATE',\n      'GROUP_PARTICIPANTS_UPDATE',\n      'CALL'\n    )\n  ).min(1).when('enabled', {\n    is: true,\n    then: Joi.required(),\n    otherwise: Joi.optional(),\n  }),\n  headers: Joi.object().pattern(\n    Joi.string(),\n    Joi.string()\n  ).optional(),\n  byEvents: Joi.boolean().optional().default(false),\n  base64: Joi.boolean().optional().default(false),\n}).required();\n```\n\n## Chatbot Validation Schemas\n\n### Base Chatbot Validation\n```typescript\nexport const baseChatbotSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  description: Joi.string().required().min(1).max(500),\n  expire: Joi.number().optional().min(0).max(86400), // 24 hours in seconds\n  keywordFinish: Joi.string().optional().max(100),\n  delayMessage: Joi.number().optional().min(0).max(10000),\n  unknownMessage: Joi.string().optional().max(1000),\n  listeningFromMe: Joi.boolean().optional().default(false),\n  stopBotFromMe: Joi.boolean().optional().default(false),\n  keepOpen: Joi.boolean().optional().default(false),\n  debounceTime: Joi.number().optional().min(0).max(60000),\n  triggerType: Joi.string().required().valid('ALL', 'KEYWORD', 'REGEX'),\n  triggerOperator: Joi.string().when('triggerType', {\n    is: 'KEYWORD',\n    then: Joi.required().valid('EQUALS', 'CONTAINS', 'STARTS_WITH', 'ENDS_WITH'),\n    otherwise: Joi.optional(),\n  }),\n  triggerValue: Joi.string().when('triggerType', {\n    is: Joi.valid('KEYWORD', 'REGEX'),\n    then: Joi.required().min(1).max(500),\n    otherwise: Joi.optional(),\n  }),\n  ignoreJids: Joi.array().items(Joi.string()).optional(),\n  splitMessages: Joi.boolean().optional().default(false),\n  timePerChar: Joi.number().optional().min(10).max(1000).default(100),\n}).required();\n\nexport const typebotSchema = baseChatbotSchema.keys({\n  url: Joi.string().required().uri({ scheme: ['http', 'https'] }),\n  typebot: Joi.string().required().min(1).max(100),\n  apiVersion: Joi.string().optional().valid('v1', 'v2').default('v1'),\n}).required();\n\nexport const openaiSchema = baseChatbotSchema.keys({\n  apiKey: Joi.string().required().min(10),\n  model: Joi.string().optional().valid(\n    'gpt-3.5-turbo',\n    'gpt-3.5-turbo-16k',\n    'gpt-4',\n    'gpt-4-32k',\n    'gpt-4-turbo-preview'\n  ).default('gpt-3.5-turbo'),\n  systemMessage: Joi.string().optional().max(2000),\n  maxTokens: Joi.number().optional().min(1).max(4000).default(1000),\n  temperature: Joi.number().optional().min(0).max(2).default(0.7),\n}).required();\n```\n\n## Business API Validation Schemas\n\n### Template Validation\n```typescript\nexport const templateSchema = Joi.object({\n  name: Joi.string().required().min(1).max(512).pattern(/^[a-z0-9_]+$/),\n  category: Joi.string().required().valid('MARKETING', 'UTILITY', 'AUTHENTICATION'),\n  allowCategoryChange: Joi.boolean().required(),\n  language: Joi.string().required().pattern(/^[a-z]{2}_[A-Z]{2}$/), // e.g., pt_BR, en_US\n  components: Joi.array().items(\n    Joi.object({\n      type: Joi.string().required().valid('HEADER', 'BODY', 'FOOTER', 'BUTTONS'),\n      format: Joi.string().when('type', {\n        is: 'HEADER',\n        then: Joi.valid('TEXT', 'IMAGE', 'VIDEO', 'DOCUMENT'),\n        otherwise: Joi.optional(),\n      }),\n      text: Joi.string().when('type', {\n        is: Joi.valid('HEADER', 'BODY', 'FOOTER'),\n        then: Joi.required().min(1).max(1024),\n        otherwise: Joi.optional(),\n      }),\n      buttons: Joi.array().when('type', {\n        is: 'BUTTONS',\n        then: Joi.items(\n          Joi.object({\n            type: Joi.string().required().valid('QUICK_REPLY', 'URL', 'PHONE_NUMBER'),\n            text: Joi.string().required().min(1).max(25),\n            url: Joi.string().when('type', {\n              is: 'URL',\n              then: Joi.required().uri(),\n              otherwise: Joi.optional(),\n            }),\n            phone_number: Joi.string().when('type', {\n              is: 'PHONE_NUMBER',\n              then: Joi.required().pattern(/^\\+?\\d+$/),\n              otherwise: Joi.optional(),\n            }),\n          })\n        ).min(1).max(10),\n        otherwise: Joi.optional(),\n      }),\n    })\n  ).min(1).required(),\n  webhookUrl: Joi.string().optional().uri({ scheme: ['http', 'https'] }),\n}).required();\n\nexport const catalogSchema = Joi.object({\n  number: Joi.string().optional().pattern(/^\\d+$/),\n  limit: Joi.number().optional().min(1).max(1000).default(10),\n  cursor: Joi.string().optional(),\n}).optional();\n```\n\n## Group Validation Schemas\n\n### Group Management Validation\n```typescript\nexport const createGroupSchema = Joi.object({\n  subject: Joi.string().required().min(1).max(100),\n  description: Joi.string().optional().max(500),\n  participants: Joi.array().items(\n    Joi.string().pattern(/^\\d+$/)\n  ).min(1).max(256).required(),\n  promoteParticipants: Joi.boolean().optional().default(false),\n}).required();\n\nexport const updateGroupSchema = Joi.object({\n  subject: Joi.string().optional().min(1).max(100),\n  description: Joi.string().optional().max(500),\n}).min(1).required();\n\nexport const groupParticipantsSchema = Joi.object({\n  participants: Joi.array().items(\n    Joi.string().pattern(/^\\d+$/)\n  ).min(1).max(50).required(),\n  action: Joi.string().required().valid('add', 'remove', 'promote', 'demote'),\n}).required();\n```\n\n## Label Validation Schemas\n\n### Label Management Validation\n```typescript\nexport const labelSchema = Joi.object({\n  name: Joi.string().required().min(1).max(100),\n  color: Joi.string().required().pattern(/^#[0-9A-Fa-f]{6}$/), // Hex color\n  predefinedId: Joi.string().optional(),\n}).required();\n\nexport const handleLabelSchema = Joi.object({\n  number: Joi.string().required().pattern(/^\\d+$/),\n  labelId: Joi.string().required(),\n  action: Joi.string().required().valid('add', 'remove'),\n}).required();\n```\n\n## Custom Validation Functions\n\n### Phone Number Validation\n```typescript\nexport function validatePhoneNumber(number: string): boolean {\n  // Remove any non-digit characters\n  const cleaned = number.replace(/\\D/g, '');\n  \n  // Check minimum and maximum length\n  if (cleaned.length < 10 || cleaned.length > 15) {\n    return false;\n  }\n  \n  // Check for valid country codes (basic validation)\n  const validCountryCodes = ['1', '7', '20', '27', '30', '31', '32', '33', '34', '36', '39', '40', '41', '43', '44', '45', '46', '47', '48', '49', '51', '52', '53', '54', '55', '56', '57', '58', '60', '61', '62', '63', '64', '65', '66', '81', '82', '84', '86', '90', '91', '92', '93', '94', '95', '98'];\n  \n  // Check if starts with valid country code\n  const startsWithValidCode = validCountryCodes.some(code => cleaned.startsWith(code));\n  \n  return startsWithValidCode;\n}\n\nexport const phoneNumberValidator = Joi.string().custom((value, helpers) => {\n  if (!validatePhoneNumber(value)) {\n    return helpers.error('any.invalid');\n  }\n  return value;\n}, 'Phone number validation');\n```\n\n### Base64 Validation\n```typescript\nexport function validateBase64(base64: string): boolean {\n  try {\n    // Check if it's a valid base64 string\n    const decoded = Buffer.from(base64, 'base64').toString('base64');\n    return decoded === base64;\n  } catch {\n    return false;\n  }\n}\n\nexport const base64Validator = Joi.string().custom((value, helpers) => {\n  if (!validateBase64(value)) {\n    return helpers.error('any.invalid');\n  }\n  return value;\n}, 'Base64 validation');\n```\n\n### URL Validation with Protocol Check\n```typescript\nexport function validateWebhookUrl(url: string): boolean {\n  try {\n    const parsed = new URL(url);\n    return ['http:', 'https:'].includes(parsed.protocol);\n  } catch {\n    return false;\n  }\n}\n\nexport const webhookUrlValidator = Joi.string().custom((value, helpers) => {\n  if (!validateWebhookUrl(value)) {\n    return helpers.error('any.invalid');\n  }\n  return value;\n}, 'Webhook URL validation');\n```\n\n## Validation Error Handling\n\n### Error Message Customization\n```typescript\nexport const validationMessages = {\n  'any.required': 'O campo {#label} é obrigatório',\n  'string.empty': 'O campo {#label} não pode estar vazio',\n  'string.min': 'O campo {#label} deve ter pelo menos {#limit} caracteres',\n  'string.max': 'O campo {#label} deve ter no máximo {#limit} caracteres',\n  'string.pattern.base': 'O campo {#label} possui formato inválido',\n  'number.min': 'O campo {#label} deve ser maior ou igual a {#limit}',\n  'number.max': 'O campo {#label} deve ser menor ou igual a {#limit}',\n  'array.min': 'O campo {#label} deve ter pelo menos {#limit} itens',\n  'array.max': 'O campo {#label} deve ter no máximo {#limit} itens',\n  'any.only': 'O campo {#label} deve ser um dos valores: {#valids}',\n};\n\nexport function formatValidationError(error: Joi.ValidationError): any {\n  return {\n    message: 'Dados de entrada inválidos',\n    details: error.details.map(detail => ({\n      field: detail.path.join('.'),\n      message: detail.message,\n      value: detail.context?.value,\n    })),\n  };\n}\n```\n\n## Schema Composition\n\n### Reusable Schema Components\n```typescript\nexport const commonFields = {\n  instanceName: Joi.string().required().min(1).max(100).pattern(/^[a-zA-Z0-9_-]+$/),\n  number: phoneNumberValidator.required(),\n  delay: Joi.number().optional().min(0).max(60000),\n  enabled: Joi.boolean().optional().default(true),\n};\n\nexport const mediaFields = {\n  mediatype: Joi.string().required().valid('image', 'video', 'audio', 'document'),\n  media: Joi.alternatives().try(\n    Joi.string().uri(),\n    base64Validator,\n  ).required(),\n  caption: Joi.string().optional().max(1024),\n  fileName: Joi.string().optional().max(255),\n};\n\n// Compose schemas using common fields\nexport const quickMessageSchema = Joi.object({\n  ...commonFields,\n  text: Joi.string().required().min(1).max(4096),\n}).required();\n\nexport const quickMediaSchema = Joi.object({\n  ...commonFields,\n  ...mediaFields,\n}).required();\n```","AGENTS.md":"# Evolution API - AI Agent Guidelines\n\nThis document provides comprehensive guidelines for AI agents (Claude, GPT, Cursor, etc.) working with the Evolution API codebase.\n\n## Project Overview\n\n**Evolution API** is a production-ready, multi-tenant WhatsApp API platform built with Node.js, TypeScript, and Express.js. It supports multiple WhatsApp providers and extensive integrations with chatbots, CRM systems, and messaging platforms.\n\n## Project Structure & Module Organization\n\n### Core Directories\n- **`src/`** – TypeScript source code with modular architecture\n  - `api/controllers/` – HTTP route handlers (thin layer)\n  - `api/services/` – Business logic (core functionality)\n  - `api/routes/` – Express route definitions (RouterBroker pattern)\n  - `api/integrations/` – External service integrations\n    - `channel/` – WhatsApp providers (Baileys, Business API, Evolution)\n    - `chatbot/` – AI/Bot integrations (OpenAI, Dify, Typebot, Chatwoot)\n    - `event/` – Event systems (WebSocket, RabbitMQ, SQS, NATS, Pusher)\n    - `storage/` – File storage (S3, MinIO)\n  - `dto/` – Data Transfer Objects (simple classes, no decorators)\n  - `guards/` – Authentication/authorization middleware\n  - `types/` – TypeScript type definitions\n  - `repository/` – Data access layer (Prisma)\n- **`prisma/`** – Database schemas and migrations\n  - `postgresql-schema.prisma` / `mysql-schema.prisma` – Provider-specific schemas\n  - `postgresql-migrations/` / `mysql-migrations/` – Provider-specific migrations\n- **`config/`** – Environment and application configuration\n- **`utils/`** – Shared utilities and helper functions\n- **`validate/`** – JSONSchema7 validation schemas\n- **`exceptions/`** – Custom HTTP exception classes\n- **`cache/`** – Redis and local cache implementations\n\n### Build & Deployment\n- **`dist/`** – Build output (do not edit directly)\n- **`public/`** – Static assets and media files\n- **`Docker*`**, **`docker-compose*.yaml`** – Containerization and local development stack\n\n## Build, Test, and Development Commands\n\n### Development Workflow\n```bash\n# Development server with hot reload\nnpm run dev:server\n\n# Direct execution for testing\nnpm start\n\n# Production build and run\nnpm run build\nnpm run start:prod\n```\n\n### Code Quality\n```bash\n# Linting and formatting\nnpm run lint        # ESLint with auto-fix\nnpm run lint:check  # ESLint check only\n\n# Commit with conventional commits\nnpm run commit      # Interactive commit with Commitizen\n```\n\n### Database Management\n```bash\n# Set database provider first (CRITICAL)\nexport DATABASE_PROVIDER=postgresql  # or mysql\n\n# Generate Prisma client\nnpm run db:generate\n\n# Development migrations (with provider sync)\nnpm run db:migrate:dev      # Unix/Mac\nnpm run db:migrate:dev:win  # Windows\n\n# Production deployment\nnpm run db:deploy      # Unix/Mac\nnpm run db:deploy:win  # Windows\n\n# Database tools\nnpm run db:studio      # Open Prisma Studio\n```\n\n### Docker Development\n```bash\n# Start local services (Redis, PostgreSQL, etc.)\ndocker-compose up -d\n\n# Full development stack\ndocker-compose -f docker-compose.dev.yaml up -d\n```\n\n## Coding Standards & Architecture Patterns\n\n### Code Style (Enforced by ESLint + Prettier)\n- **TypeScript strict mode** with full type coverage\n- **2-space indentation**, single quotes, trailing commas\n- **120-character line limit**\n- **Import order** via `simple-import-sort`\n- **File naming**: `feature.kind.ts` (e.g., `whatsapp.baileys.service.ts`)\n- **Naming conventions**:\n  - Classes: `PascalCase`\n  - Functions/variables: `camelCase`\n  - Constants: `UPPER_SNAKE_CASE`\n  - Files: `kebab-case.type.ts`\n\n### Architecture Patterns\n\n#### Service Layer Pattern\n```typescript\nexport class ExampleService {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n  \n  private readonly logger = new Logger('ExampleService');\n  \n  public async create(instance: InstanceDto, data: ExampleDto) {\n    // Business logic here\n    return { example: { ...instance, data } };\n  }\n  \n  public async find(instance: InstanceDto): Promise<ExampleDto | null> {\n    try {\n      const result = await this.waMonitor.waInstances[instance.instanceName].findData();\n      return result || null; // Return null on not found (Evolution pattern)\n    } catch (error) {\n      this.logger.error('Error finding data:', error);\n      return null; // Return null on error (Evolution pattern)\n    }\n  }\n}\n```\n\n#### Controller Pattern (Thin Layer)\n```typescript\nexport class ExampleController {\n  constructor(private readonly exampleService: ExampleService) {}\n  \n  public async createExample(instance: InstanceDto, data: ExampleDto) {\n    return this.exampleService.create(instance, data);\n  }\n}\n```\n\n#### RouterBroker Pattern\n```typescript\nexport class ExampleRouter extends RouterBroker {\n  constructor(...guards: any[]) {\n    super();\n    this.router.post(this.routerPath('create'), ...guards, async (req, res) => {\n      const response = await this.dataValidate<ExampleDto>({\n        request: req,\n        schema: exampleSchema, // JSONSchema7\n        ClassRef: ExampleDto,\n        execute: (instance, data) => controller.createExample(instance, data),\n      });\n      res.status(201).json(response);\n    });\n  }\n}\n```\n\n#### DTO Pattern (Simple Classes)\n```typescript\n// CORRECT - Evolution API pattern (no decorators)\nexport class ExampleDto {\n  name: string;\n  description?: string;\n  enabled: boolean;\n}\n\n// INCORRECT - Don't use class-validator decorators\nexport class BadExampleDto {\n  @IsString() // ❌ Evolution API doesn't use decorators\n  name: string;\n}\n```\n\n#### Validation Pattern (JSONSchema7)\n```typescript\nimport { JSONSchema7 } from 'json-schema';\nimport { v4 } from 'uuid';\n\nexport const exampleSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    name: { type: 'string' },\n    description: { type: 'string' },\n    enabled: { type: 'boolean' },\n  },\n  required: ['name', 'enabled'],\n};\n```\n\n## Multi-Tenant Architecture\n\n### Instance Isolation\n- **CRITICAL**: All operations must be scoped by `instanceName` or `instanceId`\n- **Database queries**: Always include `where: { instanceId: ... }`\n- **Authentication**: Validate instance ownership before operations\n- **Data isolation**: Complete separation between tenant instances\n\n### WhatsApp Instance Management\n```typescript\n// Access instance via WAMonitoringService\nconst waInstance = this.waMonitor.waInstances[instance.instanceName];\nif (!waInstance) {\n  throw new NotFoundException(`Instance ${instance.instanceName} not found`);\n}\n```\n\n## Database Patterns\n\n### Multi-Provider Support\n- **PostgreSQL**: Uses `@db.Integer`, `@db.JsonB`, `@default(now())`\n- **MySQL**: Uses `@db.Int`, `@db.Json`, `@default(now())`\n- **Environment**: Set `DATABASE_PROVIDER=postgresql` or `mysql`\n- **Migrations**: Provider-specific folders auto-selected\n\n### Prisma Repository Pattern\n```typescript\n// Always use PrismaRepository for database operations\nconst result = await this.prismaRepository.instance.findUnique({\n  where: { name: instanceName },\n});\n```\n\n## Integration Patterns\n\n### Channel Integration (WhatsApp Providers)\n- **Baileys**: WhatsApp Web with QR code authentication\n- **Business API**: Official Meta WhatsApp Business API  \n- **Evolution API**: Custom WhatsApp integration\n- **Pattern**: Extend base channel service classes\n\n### Chatbot Integration\n- **Base classes**: Extend `BaseChatbotService` and `BaseChatbotController`\n- **Trigger system**: Support keyword, regex, and advanced triggers\n- **Session management**: Handle conversation state per user\n- **Available integrations**: EvolutionBot, OpenAI, Dify, Typebot, Chatwoot, Flowise, N8N, EvoAI\n\n### Event Integration\n- **Internal events**: EventEmitter2 for application events\n- **External events**: WebSocket, RabbitMQ, SQS, NATS, Pusher\n- **Webhook delivery**: Reliable delivery with retry logic\n\n## Testing Guidelines\n\n### Current State\n- **No formal test suite** currently implemented\n- **Manual testing** is the primary approach\n- **Integration testing** in development environment\n\n### Testing Strategy\n```typescript\n// Place tests in test/ directory as *.test.ts\n// Run: npm test (watches test/all.test.ts)\n\ndescribe('ExampleService', () => {\n  it('should create example', async () => {\n    // Mock external dependencies\n    // Test business logic\n    // Assert expected behavior\n  });\n});\n```\n\n### Recommended Approach\n- Focus on **critical business logic** in services\n- **Mock external dependencies** (WhatsApp APIs, databases)\n- **Integration tests** for API endpoints\n- **Manual testing** for WhatsApp connection flows\n\n## Commit & Pull Request Guidelines\n\n### Conventional Commits (Enforced by commitlint)\n```bash\n# Use interactive commit tool\nnpm run commit\n\n# Commit format: type(scope): subject (max 100 chars)\n# Types: feat, fix, docs, style, refactor, perf, test, chore, ci, build, revert, security\n```\n\n### Examples\n- `feat(api): add WhatsApp message status endpoint`\n- `fix(baileys): resolve connection timeout issue`\n- `docs(readme): update installation instructions`\n- `refactor(service): extract common message validation logic`\n\n### Pull Request Requirements\n- **Clear description** of changes and motivation\n- **Linked issues** if applicable\n- **Migration impact** (specify database provider)\n- **Local testing steps** with screenshots/logs\n- **Breaking changes** clearly documented\n\n## Security & Configuration\n\n### Environment Setup\n```bash\n# Copy example environment file\ncp .env.example .env\n\n# NEVER commit secrets to version control\n# Set DATABASE_PROVIDER before database commands\nexport DATABASE_PROVIDER=postgresql  # or mysql\n```\n\n### Security Best Practices\n- **API key authentication** via `apikey` header\n- **Input validation** with JSONSchema7\n- **Rate limiting** on all endpoints\n- **Webhook signature validation**\n- **Instance-based access control**\n- **Secure defaults** for all configurations\n\n### Vulnerability Reporting\n- See `SECURITY.md` for security vulnerability reporting process\n- Contact: `contato@evolution-api.com`\n\n## Communication Standards\n\n### Language Requirements\n- **User communication**: Always respond in Portuguese (PT-BR)\n- **Code/comments**: English for technical documentation\n- **API responses**: English for consistency\n- **Error messages**: Portuguese for user-facing errors\n\n### Documentation Standards\n- **Inline comments**: Document complex business logic\n- **API documentation**: Document all public endpoints\n- **Integration guides**: Document new integration patterns\n- **Migration guides**: Document database schema changes\n\n## Performance & Scalability\n\n### Caching Strategy\n- **Redis primary**: Distributed caching for production\n- **Node-cache fallback**: Local caching when Redis unavailable\n- **TTL strategy**: Appropriate cache expiration per data type\n- **Cache invalidation**: Proper invalidation on data changes\n\n### Connection Management\n- **Database**: Prisma connection pooling\n- **WhatsApp**: One connection per instance with lifecycle management\n- **Redis**: Connection pooling and retry logic\n- **External APIs**: Rate limiting and retry with exponential backoff\n\n### Monitoring & Observability\n- **Structured logging**: Pino logger with correlation IDs\n- **Error tracking**: Comprehensive error scenarios\n- **Health checks**: Instance status and connection monitoring\n- **Telemetry**: Usage analytics (non-sensitive data only)\n\n","CLAUDE.md":"# CLAUDE.md\n\nThis file provides comprehensive guidance to Claude AI when working with the Evolution API codebase.\n\n## Project Overview\n\n**Evolution API** is a powerful, production-ready REST API for WhatsApp communication that supports multiple WhatsApp providers:\n- **Baileys** (WhatsApp Web) - Open-source WhatsApp Web client\n- **Meta Business API** - Official WhatsApp Business API\n- **Evolution API** - Custom WhatsApp integration\n\nBuilt with **Node.js 20+**, **TypeScript 5+**, and **Express.js**, it provides extensive integrations with chatbots, CRM systems, and messaging platforms in a **multi-tenant architecture**.\n\n## Common Development Commands\n\n### Build and Run\n```bash\n# Development\nnpm run dev:server    # Run in development with hot reload (tsx watch)\n\n# Production\nnpm run build        # TypeScript check + tsup build\nnpm run start:prod   # Run production build\n\n# Direct execution\nnpm start           # Run with tsx\n```\n\n### Code Quality\n```bash\nnpm run lint        # ESLint with auto-fix\nnpm run lint:check  # ESLint check only\nnpm run commit      # Interactive commit with commitizen\n```\n\n### Database Management\n```bash\n# Set database provider first\nexport DATABASE_PROVIDER=postgresql  # or mysql\n\n# Generate Prisma client (automatically uses DATABASE_PROVIDER env)\nnpm run db:generate\n\n# Deploy migrations (production)\nnpm run db:deploy      # Unix/Mac\nnpm run db:deploy:win  # Windows\n\n# Development migrations (with sync to provider folder)\nnpm run db:migrate:dev      # Unix/Mac\nnpm run db:migrate:dev:win  # Windows\n\n# Open Prisma Studio\nnpm run db:studio\n\n# Development migrations\nnpm run db:migrate:dev      # Unix/Mac\nnpm run db:migrate:dev:win  # Windows\n```\n\n### Testing\n```bash\nnpm test    # Run tests with watch mode\n```\n\n## Architecture Overview\n\n### Core Structure\n- **Multi-tenant SaaS**: Complete instance isolation with per-tenant authentication\n- **Multi-provider database**: PostgreSQL and MySQL via Prisma ORM with provider-specific schemas and migrations\n- **WhatsApp integrations**: Baileys, Meta Business API, and Evolution API with unified interface\n- **Event-driven architecture**: EventEmitter2 for internal events + WebSocket, RabbitMQ, SQS, NATS, Pusher for external events\n- **Microservices pattern**: Modular integrations for chatbots, storage, and external services\n\n### Directory Layout\n```\nsrc/\n├── api/\n│   ├── controllers/     # HTTP route handlers (thin layer)\n│   ├── services/        # Business logic (core functionality)\n│   ├── repository/      # Data access layer (Prisma)\n│   ├── dto/            # Data Transfer Objects (simple classes)\n│   ├── guards/         # Authentication/authorization middleware\n│   ├── integrations/   # External service integrations\n│   │   ├── channel/    # WhatsApp providers (Baileys, Business API, Evolution)\n│   │   ├── chatbot/    # AI/Bot integrations (OpenAI, Dify, Typebot, Chatwoot)\n│   │   ├── event/      # Event systems (WebSocket, RabbitMQ, SQS, NATS, Pusher)\n│   │   └── storage/    # File storage (S3, MinIO)\n│   ├── routes/         # Express route definitions (RouterBroker pattern)\n│   └── types/          # TypeScript type definitions\n├── config/             # Environment and app configuration\n├── cache/             # Redis and local cache implementations\n├── exceptions/        # Custom HTTP exception classes\n├── utils/            # Shared utilities and helpers\n└── validate/         # JSONSchema7 validation schemas\n```\n\n### Key Integration Points\n\n**Channel Integrations** (`src/api/integrations/channel/`):\n- **Baileys**: WhatsApp Web client with QR code authentication\n- **Business API**: Official Meta WhatsApp Business API\n- **Evolution API**: Custom WhatsApp integration\n- Connection lifecycle management per instance with automatic reconnection\n\n**Chatbot Integrations** (`src/api/integrations/chatbot/`):\n- **EvolutionBot**: Native chatbot with trigger system\n- **Chatwoot**: Customer service platform integration\n- **Typebot**: Visual chatbot flow builder\n- **OpenAI**: AI capabilities including GPT and Whisper (audio transcription)\n- **Dify**: AI agent workflow platform\n- **Flowise**: LangChain visual builder\n- **N8N**: Workflow automation platform\n- **EvoAI**: Custom AI integration\n\n**Event Integrations** (`src/api/integrations/event/`):\n- **WebSocket**: Real-time Socket.io connections\n- **RabbitMQ**: Message queue for async processing\n- **Amazon SQS**: Cloud-based message queuing\n- **NATS**: High-performance messaging system\n- **Pusher**: Real-time push notifications\n\n**Storage Integrations** (`src/api/integrations/storage/`):\n- **AWS S3**: Cloud object storage\n- **MinIO**: Self-hosted S3-compatible storage\n- Media file management and URL generation\n\n### Database Schema Management\n- Separate schema files: `postgresql-schema.prisma` and `mysql-schema.prisma`\n- Environment variable `DATABASE_PROVIDER` determines active database\n- Migration folders are provider-specific and auto-selected during deployment\n\n### Authentication & Security\n- **API key-based authentication** via `apikey` header (global or per-instance)\n- **Instance-specific tokens** for WhatsApp connection authentication\n- **Guards system** for route protection and authorization\n- **Input validation** using JSONSchema7 with RouterBroker `dataValidate`\n- **Rate limiting** and security middleware\n- **Webhook signature validation** for external integrations\n\n## Important Implementation Details\n\n### WhatsApp Instance Management\n- Each WhatsApp connection is an \"instance\" with unique name\n- Instance data stored in database with connection state\n- Session persistence in database or file system (configurable)\n- Automatic reconnection handling with exponential backoff\n\n### Message Queue Architecture\n- Supports RabbitMQ, Amazon SQS, and WebSocket for events\n- Event types: message.received, message.sent, connection.update, etc.\n- Configurable per instance which events to send\n\n### Media Handling\n- Local storage or S3/Minio for media files\n- Automatic media download from WhatsApp\n- Media URL generation for external access\n- Support for audio transcription via OpenAI\n\n### Multi-tenancy Support\n- Instance isolation at database level\n- Separate webhook configurations per instance\n- Independent integration settings per instance\n\n## Environment Configuration\n\nKey environment variables are defined in `.env.example`. The system uses a strongly-typed configuration system via `src/config/env.config.ts`.\n\nCritical configurations:\n- `DATABASE_PROVIDER`: postgresql or mysql\n- `DATABASE_CONNECTION_URI`: Database connection string\n- `AUTHENTICATION_API_KEY`: Global API authentication\n- `REDIS_ENABLED`: Enable Redis cache\n- `RABBITMQ_ENABLED`/`SQS_ENABLED`: Message queue options\n\n## Development Guidelines\n\nThe project follows comprehensive development standards defined in `.cursor/rules/`:\n\n### Core Principles\n- **Always respond in Portuguese (PT-BR)** for user communication\n- **Follow established architecture patterns** (Service Layer, RouterBroker, etc.)\n- **Robust error handling** with retry logic and graceful degradation\n- **Multi-database compatibility** (PostgreSQL and MySQL)\n- **Security-first approach** with input validation and rate limiting\n- **Performance optimizations** with Redis caching and connection pooling\n\n### Code Standards\n- **TypeScript strict mode** with full type coverage\n- **JSONSchema7** for input validation (not class-validator)\n- **Conventional Commits** enforced by commitlint\n- **ESLint + Prettier** for code formatting\n- **Service Object pattern** for business logic\n- **RouterBroker pattern** for route handling with `dataValidate`\n\n### Architecture Patterns\n- **Multi-tenant isolation** at database and instance level\n- **Event-driven communication** with EventEmitter2\n- **Microservices integration** pattern for external services\n- **Connection pooling** and lifecycle management\n- **Caching strategy** with Redis primary and Node-cache fallback\n\n## Testing Approach\n\nCurrently, the project has minimal formal testing infrastructure:\n- **Manual testing** is the primary approach\n- **Integration testing** in development environment\n- **No unit test suite** currently implemented\n- Test files can be placed in `test/` directory as `*.test.ts`\n- Run `npm test` for watch mode development testing\n\n### Recommended Testing Strategy\n- Focus on **critical business logic** in services\n- **Mock external dependencies** (WhatsApp APIs, databases)\n- **Integration tests** for API endpoints\n- **Manual testing** for WhatsApp connection flows\n\n## Deployment Considerations\n\n- Docker support with `Dockerfile` and `docker-compose.yaml`\n- Graceful shutdown handling for connections\n- Health check endpoints for monitoring\n- Sentry integration for error tracking\n- Telemetry for usage analytics (non-sensitive data only)"},"files":{".cursor/rules/README.md":"# Evolution API Cursor Rules\n\nEste diretório contém as regras e configurações do Cursor IDE para o projeto Evolution API.\n\n## Estrutura dos Arquivos\n\n### Arquivos Principais (alwaysApply: true)\n- **`core-development.mdc`** - Princípios fundamentais de desenvolvimento\n- **`project-context.mdc`** - Contexto específico do projeto Evolution API\n- **`cursor.json`** - Configurações do Cursor IDE\n\n### Regras Especializadas (alwaysApply: false)\nEstas regras são ativadas automaticamente quando você trabalha nos arquivos correspondentes:\n\n#### Camadas da Aplicação\n- **`specialized-rules/service-rules.mdc`** - Padrões para services (`src/api/services/`)\n- **`specialized-rules/controller-rules.mdc`** - Padrões para controllers (`src/api/controllers/`)\n- **`specialized-rules/dto-rules.mdc`** - Padrões para DTOs (`src/api/dto/`)\n- **`specialized-rules/guard-rules.mdc`** - Padrões para guards (`src/api/guards/`)\n- **`specialized-rules/route-rules.mdc`** - Padrões para routers (`src/api/routes/`)\n\n#### Tipos e Validação\n- **`specialized-rules/type-rules.mdc`** - Definições TypeScript (`src/api/types/`)\n- **`specialized-rules/validate-rules.mdc`** - Schemas de validação (`src/validate/`)\n\n#### Utilitários\n- **`specialized-rules/util-rules.mdc`** - Funções utilitárias (`src/utils/`)\n\n#### Integrações\n- **`specialized-rules/integration-channel-rules.mdc`** - Integrações de canal (`src/api/integrations/channel/`)\n- **`specialized-rules/integration-chatbot-rules.mdc`** - Integrações de chatbot (`src/api/integrations/chatbot/`)\n- **`specialized-rules/integration-storage-rules.mdc`** - Integrações de storage (`src/api/integrations/storage/`)\n- **`specialized-rules/integration-event-rules.mdc`** - Integrações de eventos (`src/api/integrations/event/`)\n\n## Como Usar\n\n### Referências Cruzadas\nOs arquivos principais fazem referência aos especializados usando a sintaxe `@specialized-rules/nome-do-arquivo.mdc`. Quando você trabalha em um arquivo específico, o Cursor automaticamente carrega as regras relevantes.\n\n### Exemplo de Uso\nQuando você edita um arquivo em `src/api/services/`, o Cursor automaticamente:\n1. Carrega `core-development.mdc` (sempre ativo)\n2. Carrega `project-context.mdc` (sempre ativo)\n3. Carrega `specialized-rules/service-rules.mdc` (ativado pelo glob pattern)\n\n### Padrões de Código\nCada arquivo de regras contém:\n- **Estruturas padrão** - Como organizar o código\n- **Padrões de nomenclatura** - Convenções de nomes\n- **Exemplos práticos** - Código de exemplo\n- **Anti-padrões** - O que evitar\n- **Testes** - Como testar o código\n- **Padrões de Commit** - Conventional Commits com commitlint\n\n## Configuração do Cursor\n\nO arquivo `cursor.json` contém:\n- Configurações de formatação\n- Padrões de código específicos do Evolution API\n- Diretórios principais do projeto\n- Integrações e tecnologias utilizadas\n\n## Manutenção\n\nPara manter as regras atualizadas:\n1. Analise novos padrões no código\n2. Atualize as regras especializadas correspondentes\n3. Mantenha os exemplos sincronizados com o código real\n4. Documente mudanças significativas\n\n## Tecnologias Cobertas\n\n- **Backend**: Node.js 20+ + TypeScript 5+ + Express.js\n- **Database**: Prisma ORM (PostgreSQL/MySQL)\n- **Cache**: Redis + Node-cache\n- **Queue**: RabbitMQ + Amazon SQS\n- **Real-time**: Socket.io\n- **Storage**: AWS S3 + Minio\n- **Validation**: JSONSchema7\n- **Logging**: Pino\n- **WhatsApp**: Baileys + Meta Business API\n- **Integrations**: Chatwoot, Typebot, OpenAI, Dify\n\n## Estrutura do Projeto\n\n```\nsrc/\n├── api/\n│   ├── controllers/     # Controllers (HTTP handlers)\n│   ├── services/        # Business logic\n│   ├── dto/            # Data Transfer Objects\n│   ├── guards/         # Authentication/Authorization\n│   ├── routes/         # Express routers\n│   ├── types/          # TypeScript definitions\n│   └── integrations/   # External integrations\n│       ├── channel/    # WhatsApp channels (Baileys, Business API)\n│       ├── chatbot/    # Chatbot integrations\n│       ├── event/      # Event integrations\n│       └── storage/    # Storage integrations\n├── cache/              # Cache implementations\n├── config/             # Configuration files\n├── utils/              # Utility functions\n├── validate/           # Validation schemas\n└── exceptions/         # Custom exceptions\n```\n\nEste sistema de regras garante consistência no código e facilita o desenvolvimento seguindo os padrões estabelecidos do Evolution API.\n",".cursor/rules/core-development.mdc":"---\ndescription: Core development principles and standards for Evolution API development\nglobs:\nalwaysApply: true\n---\n\n# Evolution API Development Standards\n\n## Cross-References\n- **Project Context**: @project-context.mdc for Evolution API-specific patterns\n- **Specialized Rules**: \n  - @specialized-rules/service-rules.mdc for service layer patterns\n  - @specialized-rules/controller-rules.mdc for controller patterns\n  - @specialized-rules/dto-rules.mdc for DTO validation patterns\n  - @specialized-rules/guard-rules.mdc for authentication/authorization\n  - @specialized-rules/route-rules.mdc for router patterns\n  - @specialized-rules/type-rules.mdc for TypeScript definitions\n  - @specialized-rules/util-rules.mdc for utility functions\n  - @specialized-rules/validate-rules.mdc for validation schemas\n  - @specialized-rules/integration-channel-rules.mdc for channel integrations\n  - @specialized-rules/integration-chatbot-rules.mdc for chatbot integrations\n  - @specialized-rules/integration-storage-rules.mdc for storage integrations\n  - @specialized-rules/integration-event-rules.mdc for event integrations\n- **TypeScript/Node.js**: Node.js 20+ + TypeScript 5+ best practices\n- **Express/Prisma**: Express.js + Prisma ORM patterns\n- **WhatsApp Integrations**: Baileys + Meta Business API patterns\n\n## Senior Engineer Context - Evolution API Platform\n- You are a senior software engineer working on a WhatsApp API platform\n- Focus on Node.js + TypeScript + Express.js full-stack development\n- Specialized in real-time messaging, WhatsApp integrations, and event-driven architecture\n- Apply scalable patterns for multi-tenant API platform\n- Consider WhatsApp integration workflow implications and performance at scale\n\n## Fundamental Principles\n\n### Code Quality Standards\n- **Simplicity First**: Always prefer simple solutions over complex ones\n- **DRY Principle**: Avoid code duplication - check for existing similar functionality before implementing\n- **Single Responsibility**: Each function/class should have one clear purpose\n- **Readable Code**: Write code that tells a story - clear naming and structure\n\n### Problem Resolution Approach\n- **Follow Existing Patterns**: Use established Service patterns, DTOs, and Integration patterns\n- **Event-Driven First**: Leverage EventEmitter2 for event publishing when adding new features\n- **Integration Pattern**: Follow existing WhatsApp integration patterns for new channels\n- **Conservative Changes**: Prefer extending existing services over creating new architecture\n- **Clean Migration**: Remove deprecated patterns when introducing new ones\n- **Incremental Changes**: Break large changes into smaller, testable increments with proper migrations\n\n### File and Function Organization - Node.js/TypeScript Structure\n- **Services**: Keep services focused and under 200 lines\n- **Controllers**: Keep controllers thin - only routing and validation\n- **DTOs**: Use JSONSchema7 for all input validation\n- **Integrations**: Follow `src/api/integrations/` structure for new integrations\n- **Utils**: Extract common functionality into well-named utilities\n- **Types**: Define clear TypeScript interfaces and types\n\n### Code Analysis and Reflection\n- After writing code, deeply reflect on scalability and maintainability\n- Provide 1-2 paragraph analysis of code changes\n- Suggest improvements or next steps based on reflection\n- Consider performance, security, and maintenance implications\n\n## Development Standards\n\n### TypeScript Standards\n- **Strict Mode**: Always use TypeScript strict mode\n- **No Any**: Avoid `any` type - use proper typing\n- **Interfaces**: Define clear contracts with interfaces\n- **Enums**: Use enums for constants and status values\n- **Generics**: Use generics for reusable components\n\n### Error Handling Standards\n- **HTTP Exceptions**: Use appropriate HTTP status codes\n- **Logging**: Structured logging with context\n- **Retry Logic**: Implement retry for external services\n- **Graceful Degradation**: Handle service failures gracefully\n\n### Security Standards\n- **Input Validation**: Validate all inputs with JSONSchema7\n- **Authentication**: Use API keys and JWT tokens\n- **Rate Limiting**: Implement rate limiting for APIs\n- **Data Sanitization**: Sanitize sensitive data in logs\n\n### Performance Standards\n- **Caching**: Use Redis for frequently accessed data\n- **Database**: Optimize Prisma queries with proper indexing\n- **Memory**: Monitor memory usage and implement cleanup\n- **Async**: Use async/await properly with error handling\n\n## Communication Standards\n\n### Language Requirements\n- **User Communication**: Always respond in Portuguese (PT-BR)\n- **Code Comments**: English for technical documentation\n- **API Documentation**: English for consistency\n- **Error Messages**: Portuguese for user-facing errors\n\n### Documentation Standards\n- **Code Comments**: Document complex business logic\n- **API Documentation**: Document all public endpoints\n- **README**: Keep project documentation updated\n- **Changelog**: Document breaking changes\n\n## Quality Assurance\n\n### Testing Standards\n- **Unit Tests**: Test business logic in services\n- **Integration Tests**: Test API endpoints\n- **Mocks**: Mock external dependencies\n- **Coverage**: Aim for 70%+ test coverage\n\n### Code Review Standards\n- **Peer Review**: All code must be reviewed\n- **Automated Checks**: ESLint, Prettier, TypeScript\n- **Security Review**: Check for security vulnerabilities\n- **Performance Review**: Check for performance issues\n\n### Commit Standards (Conventional Commits)\n- **Format**: `type(scope): subject` (max 100 characters)\n- **Types**: \n  - `feat` - New feature\n  - `fix` - Bug fix\n  - `docs` - Documentation changes\n  - `style` - Code style changes (formatting, etc)\n  - `refactor` - Code refactoring\n  - `perf` - Performance improvements\n  - `test` - Adding or updating tests\n  - `chore` - Maintenance tasks\n  - `ci` - CI/CD changes\n  - `build` - Build system changes\n  - `revert` - Reverting changes\n  - `security` - Security fixes\n- **Examples**:\n  - `feat(api): add WhatsApp message status endpoint`\n  - `fix(baileys): resolve connection timeout issue`\n  - `docs(readme): update installation instructions`\n  - `refactor(service): extract common message validation logic`\n- **Tools**: Use `npm run commit` (Commitizen) for guided commits\n- **Validation**: Enforced by commitlint on commit-msg hook\n\n## Evolution API Specific Patterns\n\n### WhatsApp Integration Patterns\n- **Connection Management**: One connection per instance\n- **Event Handling**: Proper event listeners for Baileys\n- **Message Processing**: Queue-based message processing\n- **Error Recovery**: Automatic reconnection logic\n\n### Multi-Database Support\n- **Schema Compatibility**: Support PostgreSQL and MySQL\n- **Migration Sync**: Keep migrations synchronized\n- **Type Safety**: Use Prisma generated types\n- **Connection Pooling**: Proper database connection management\n\n### Cache Strategy\n- **Redis Primary**: Use Redis for distributed caching\n- **Local Fallback**: Node-cache for local fallback\n- **TTL Strategy**: Appropriate TTL for different data types\n- **Cache Invalidation**: Proper cache invalidation patterns\n\n### Event System\n- **EventEmitter2**: Use for internal events\n- **WebSocket**: Socket.io for real-time updates\n- **Queue Systems**: RabbitMQ/SQS for async processing\n- **Webhook Processing**: Proper webhook validation and processing",".cursor/rules/cursor.json":"{\n  \"version\": \"1.0\",\n  \"description\": \"Cursor IDE configuration for Evolution API project\",\n  \"rules\": {\n    \"general\": {\n      \"max_line_length\": 120,\n      \"indent_size\": 2,\n      \"end_of_line\": \"lf\",\n      \"charset\": \"utf-8\",\n      \"trim_trailing_whitespace\": true,\n      \"insert_final_newline\": true\n    },\n    \"typescript\": {\n      \"quotes\": \"single\",\n      \"semi\": true,\n      \"trailing_comma\": \"es5\",\n      \"bracket_spacing\": true,\n      \"arrow_parens\": \"avoid\",\n      \"print_width\": 120,\n      \"tab_width\": 2,\n      \"use_tabs\": false,\n      \"single_quote\": true,\n      \"end_of_line\": \"lf\",\n      \"strict\": true,\n      \"no_implicit_any\": true,\n      \"strict_null_checks\": true\n    },\n    \"javascript\": {\n      \"quotes\": \"single\",\n      \"semi\": true,\n      \"trailing_comma\": \"es5\",\n      \"bracket_spacing\": true,\n      \"arrow_parens\": \"avoid\",\n      \"print_width\": 120,\n      \"tab_width\": 2,\n      \"use_tabs\": false,\n      \"single_quote\": true,\n      \"end_of_line\": \"lf\",\n      \"style_guide\": \"eslint-airbnb\"\n    },\n    \"json\": {\n      \"tab_width\": 2,\n      \"use_tabs\": false,\n      \"parser\": \"json\"\n    },\n    \"ignore\": {\n      \"files\": [\n        \"node_modules/**\",\n        \"dist/**\",\n        \"build/**\",\n        \".git/**\",\n        \"*.min.js\",\n        \"*.min.css\",\n        \".env\",\n        \".env.*\",\n        \".env.example\",\n        \"coverage/**\",\n        \"*.log\",\n        \"*.lock\",\n        \"pnpm-lock.yaml\",\n        \"package-lock.json\",\n        \"yarn.lock\",\n        \"log/**\",\n        \"tmp/**\",\n        \"instances/**\",\n        \"public/uploads/**\",\n        \"*.dump\",\n        \"*.rdb\",\n        \"*.mmdb\",\n        \".DS_Store\",\n        \"*.swp\",\n        \"*.swo\",\n        \"*.un~\",\n        \".jest-cache\",\n        \".idea/**\",\n        \".vscode/**\",\n        \".yalc/**\",\n        \"yalc.lock\",\n        \"*.local\",\n        \"prisma/migrations/**\",\n        \"prisma/mysql-migrations/**\",\n        \"prisma/postgresql-migrations/**\"\n      ]\n    },\n    \"search\": {\n      \"exclude_patterns\": [\n        \"**/node_modules/**\",\n        \"**/dist/**\",\n        \"**/build/**\",\n        \"**/.git/**\",\n        \"**/coverage/**\",\n        \"**/log/**\",\n        \"**/tmp/**\",\n        \"**/instances/**\",\n        \"**/public/uploads/**\",\n        \"**/*.min.js\",\n        \"**/*.min.css\",\n        \"**/*.log\",\n        \"**/*.lock\",\n        \"**/pnpm-lock.yaml\",\n        \"**/package-lock.json\",\n        \"**/yarn.lock\",\n        \"**/*.dump\",\n        \"**/*.rdb\",\n        \"**/*.mmdb\",\n        \"**/.DS_Store\",\n        \"**/*.swp\",\n        \"**/*.swo\",\n        \"**/*.un~\",\n        \"**/.jest-cache\",\n        \"**/.idea/**\",\n        \"**/.vscode/**\",\n        \"**/.yalc/**\",\n        \"**/yalc.lock\",\n        \"**/*.local\",\n        \"**/prisma/migrations/**\",\n        \"**/prisma/mysql-migrations/**\",\n        \"**/prisma/postgresql-migrations/**\"\n      ]\n    },\n    \"evolution_api\": {\n      \"project_type\": \"nodejs_typescript_api\",\n      \"backend_framework\": \"express_prisma\",\n      \"database\": [\"postgresql\", \"mysql\"],\n      \"cache\": [\"redis\", \"node_cache\"],\n      \"queue\": [\"rabbitmq\", \"sqs\"],\n      \"real_time\": \"socket_io\",\n      \"file_storage\": [\"aws_s3\", \"minio\"],\n      \"validation\": \"class_validator\",\n      \"logging\": \"pino\",\n      \"main_directories\": {\n        \"source\": \"src/\",\n        \"api\": \"src/api/\",\n        \"controllers\": \"src/api/controllers/\",\n        \"services\": \"src/api/services/\",\n        \"integrations\": \"src/api/integrations/\",\n        \"dto\": \"src/api/dto/\",\n        \"types\": \"src/api/types/\",\n        \"guards\": \"src/api/guards/\",\n        \"routes\": \"src/api/routes/\",\n        \"cache\": \"src/cache/\",\n        \"config\": \"src/config/\",\n        \"utils\": \"src/utils/\",\n        \"exceptions\": \"src/exceptions/\",\n        \"validate\": \"src/validate/\",\n        \"prisma\": \"prisma/\",\n        \"tests\": \"test/\",\n        \"docs\": \"docs/\"\n      },\n      \"key_patterns\": [\n        \"whatsapp_integration\",\n        \"multi_database_support\",\n        \"instance_management\",\n        \"event_driven_architecture\",\n        \"service_layer_pattern\",\n        \"dto_validation\",\n        \"webhook_processing\",\n        \"message_queuing\",\n        \"real_time_communication\",\n        \"file_storage_integration\"\n      ],\n      \"whatsapp_integrations\": [\n        \"baileys\",\n        \"meta_business_api\",\n        \"whatsapp_cloud_api\"\n      ],\n      \"external_integrations\": [\n        \"chatwoot\",\n        \"typebot\",\n        \"openai\",\n        \"dify\",\n        \"rabbitmq\",\n        \"sqs\",\n        \"s3\",\n        \"minio\"\n      ]\n    }\n  }\n}\n",".cursor/rules/project-context.mdc":"---\ndescription: Evolution API project-specific context and constraints\nglobs:\nalwaysApply: true\n---\n\n# Evolution API Project Context\n\n## Cross-References\n- **Core Development**: @core-development.mdc for fundamental development principles\n- **Specialized Rules**: Reference specific specialized rules when working on:\n  - Services: @specialized-rules/service-rules.mdc\n  - Controllers: @specialized-rules/controller-rules.mdc\n  - DTOs: @specialized-rules/dto-rules.mdc\n  - Guards: @specialized-rules/guard-rules.mdc\n  - Routes: @specialized-rules/route-rules.mdc\n  - Types: @specialized-rules/type-rules.mdc\n  - Utils: @specialized-rules/util-rules.mdc\n  - Validation: @specialized-rules/validate-rules.mdc\n  - Channel Integrations: @specialized-rules/integration-channel-rules.mdc\n  - Chatbot Integrations: @specialized-rules/integration-chatbot-rules.mdc\n  - Storage Integrations: @specialized-rules/integration-storage-rules.mdc\n  - Event Integrations: @specialized-rules/integration-event-rules.mdc\n- **TypeScript/Node.js**: Node.js 20+ + TypeScript 5+ backend standards\n- **Express/Prisma**: Express.js + Prisma ORM patterns\n- **WhatsApp Integrations**: Baileys, Meta Business API, and other messaging platforms\n\n## Technology Stack\n- **Backend**: Node.js 20+ + TypeScript 5+ + Express.js\n- **Database**: Prisma ORM (PostgreSQL/MySQL support)\n- **Cache**: Redis + Node-cache for local fallback\n- **Queue**: RabbitMQ + Amazon SQS for message processing\n- **Real-time**: Socket.io for WebSocket connections\n- **Storage**: AWS S3 + Minio for file storage\n- **Validation**: JSONSchema7 for input validation\n- **Logging**: Pino for structured logging\n- **Architecture**: Multi-tenant API with WhatsApp integrations\n\n## Project-Specific Patterns\n\n### WhatsApp Integration Architecture\n- **MANDATORY**: All WhatsApp integrations must follow established patterns\n- **BAILEYS**: Use `whatsapp.baileys.service.ts` patterns for WhatsApp Web\n- **META BUSINESS**: Use `whatsapp.business.service.ts` for official API\n- **CONNECTION MANAGEMENT**: One connection per instance with proper lifecycle\n- **EVENT HANDLING**: Proper event listeners and error handling\n\n### Multi-Database Architecture\n- **CRITICAL**: Support both PostgreSQL and MySQL\n- **SCHEMAS**: Use appropriate schema files (postgresql-schema.prisma / mysql-schema.prisma)\n- **MIGRATIONS**: Keep migrations synchronized between databases\n- **TYPES**: Use database-specific types (@db.JsonB vs @db.Json)\n- **COMPATIBILITY**: Ensure feature parity between databases\n\n### API Integration Workflow\n- **CORE FEATURE**: REST API for WhatsApp communication\n- **COMPLEXITY**: High - involves webhook processing, message routing, and instance management\n- **COMPONENTS**: Instance management, message handling, media processing\n- **INTEGRATIONS**: Baileys, Meta Business API, Chatwoot, Typebot, OpenAI, Dify\n\n### Multi-Tenant Instance Architecture\n- **CRITICAL**: All operations must be scoped by instance\n- **ISOLATION**: Complete data isolation between instances\n- **SECURITY**: Validate instance ownership before operations\n- **SCALING**: Support thousands of concurrent instances\n- **AUTHENTICATION**: API key-based authentication per instance\n\n## Documentation Requirements\n\n### Implementation Documentation\n- **MANDATORY**: Document complex integration patterns\n- **LOCATION**: Use inline comments for business logic\n- **API DOCS**: Document all public endpoints\n- **WEBHOOK DOCS**: Document webhook payloads and signatures\n\n### Change Documentation\n- **CHANGELOG**: Document breaking changes\n- **MIGRATION GUIDES**: Document database migrations\n- **INTEGRATION GUIDES**: Document new integration patterns\n\n## Environment and Security\n\n### Environment Variables\n- **CRITICAL**: Never hardcode sensitive values\n- **VALIDATION**: Validate required environment variables on startup\n- **SECURITY**: Use secure defaults and proper encryption\n- **DOCUMENTATION**: Document all environment variables\n\n### File Organization - Node.js/TypeScript Structure\n- **CONTROLLERS**: Organized by feature (`api/controllers/`)\n- **SERVICES**: Business logic in service classes (`api/services/`)\n- **INTEGRATIONS**: External integrations (`api/integrations/`)\n- **DTOS**: Data transfer objects (`api/dto/`)\n- **TYPES**: TypeScript types (`api/types/`)\n- **UTILS**: Utility functions (`utils/`)\n\n## Integration Points\n\n### WhatsApp Providers\n- **BAILEYS**: WhatsApp Web integration with QR code\n- **META BUSINESS**: Official WhatsApp Business API\n- **CLOUD API**: WhatsApp Cloud API integration\n- **WEBHOOK PROCESSING**: Proper webhook validation and processing\n\n### External Integrations\n- **CHATWOOT**: Customer support platform integration\n- **TYPEBOT**: Chatbot flow integration\n- **OPENAI**: AI-powered chat integration\n- **DIFY**: AI workflow integration\n- **STORAGE**: S3/Minio for media file storage\n\n### Event-Driven Communication\n- **EVENTEMITTER2**: Internal event system\n- **SOCKET.IO**: Real-time WebSocket communication\n- **RABBITMQ**: Message queue for async processing\n- **SQS**: Amazon SQS for cloud-based queuing\n- **WEBHOOKS**: Outbound webhook system\n\n## Development Constraints\n\n### Language Requirements\n- **USER COMMUNICATION**: Always respond in Portuguese (PT-BR)\n- **CODE/COMMENTS**: English for code and technical documentation\n- **API RESPONSES**: English for consistency\n- **ERROR MESSAGES**: Portuguese for user-facing errors\n\n### Performance Constraints\n- **MEMORY**: Efficient memory usage for multiple instances\n- **DATABASE**: Optimized queries with proper indexing\n- **CACHE**: Strategic caching for frequently accessed data\n- **CONNECTIONS**: Proper connection pooling and management\n\n### Security Constraints\n- **AUTHENTICATION**: API key validation for all endpoints\n- **AUTHORIZATION**: Instance-based access control\n- **INPUT VALIDATION**: Validate all inputs with JSONSchema7\n- **RATE LIMITING**: Prevent abuse with rate limiting\n- **WEBHOOK SECURITY**: Validate webhook signatures\n\n## Quality Standards\n- **TYPE SAFETY**: Full TypeScript coverage with strict mode\n- **ERROR HANDLING**: Comprehensive error scenarios with proper logging\n- **TESTING**: Unit and integration tests for critical paths\n- **MONITORING**: Proper logging and error tracking\n- **DOCUMENTATION**: Clear API documentation and code comments\n- **PERFORMANCE**: Optimized for high-throughput message processing\n- **SECURITY**: Secure by default with proper validation\n- **SCALABILITY**: Design for horizontal scaling\n\n## Evolution API Specific Development Patterns\n\n### Instance Management\n- **LIFECYCLE**: Proper instance creation, connection, and cleanup\n- **STATE MANAGEMENT**: Track connection status and health\n- **RECOVERY**: Automatic reconnection and error recovery\n- **MONITORING**: Health checks and status reporting\n\n### Message Processing\n- **QUEUE-BASED**: Use queues for message processing\n- **RETRY LOGIC**: Implement exponential backoff for failures\n- **MEDIA HANDLING**: Proper media upload and processing\n- **WEBHOOK DELIVERY**: Reliable webhook delivery with retries\n\n### Integration Patterns\n- **SERVICE LAYER**: Business logic in service classes\n- **DTO VALIDATION**: Input validation with JSONSchema7\n- **ERROR HANDLING**: Consistent error responses\n- **LOGGING**: Structured logging with correlation IDs\n\n### Database Patterns\n- **PRISMA**: Use Prisma ORM for all database operations\n- **TRANSACTIONS**: Use transactions for multi-step operations\n- **MIGRATIONS**: Proper migration management\n- **INDEXING**: Optimize queries with appropriate indexes",".cursor/rules/specialized-rules/controller-rules.mdc":"---\ndescription: Controller patterns for Evolution API\nglobs:\n  - \"src/api/controllers/**/*.ts\"\n  - \"src/api/integrations/**/controllers/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Controller Rules\n\n## Controller Structure Pattern\n\n### Standard Controller Class\n```typescript\nexport class ExampleController {\n  constructor(private readonly exampleService: ExampleService) {}\n\n  public async createExample(instance: InstanceDto, data: ExampleDto) {\n    return this.exampleService.create(instance, data);\n  }\n\n  public async findExample(instance: InstanceDto) {\n    return this.exampleService.find(instance);\n  }\n}\n```\n\n## Dependency Injection Pattern\n\n### Service Injection\n```typescript\n// CORRECT - Evolution API pattern\nexport class ChatController {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n\n  public async whatsappNumber({ instanceName }: InstanceDto, data: WhatsAppNumberDto) {\n    return await this.waMonitor.waInstances[instanceName].getWhatsAppNumbers(data);\n  }\n}\n\n// INCORRECT - Don't inject multiple services when waMonitor is sufficient\nexport class ChatController {\n  constructor(\n    private readonly waMonitor: WAMonitoringService,\n    private readonly prismaRepository: PrismaRepository, // ❌ Unnecessary if waMonitor has access\n    private readonly configService: ConfigService, // ❌ Unnecessary if waMonitor has access\n  ) {}\n}\n```\n\n## Method Signature Pattern\n\n### Instance Parameter Pattern\n```typescript\n// CORRECT - Evolution API pattern (destructuring instanceName)\npublic async fetchCatalog({ instanceName }: InstanceDto, data: getCatalogDto) {\n  return await this.waMonitor.waInstances[instanceName].fetchCatalog(instanceName, data);\n}\n\n// CORRECT - Alternative pattern for full instance (when using services)\npublic async createTemplate(instance: InstanceDto, data: TemplateDto) {\n  return this.templateService.create(instance, data);\n}\n\n// INCORRECT - Don't use generic method names\npublic async methodName(instance: InstanceDto, data: DataDto) { // ❌ Use specific names\n  return this.service.performAction(instance, data);\n}\n```\n\n## WAMonitor Access Pattern\n\n### Direct WAMonitor Usage\n```typescript\n// CORRECT - Standard pattern in controllers\nexport class CallController {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n\n  public async offerCall({ instanceName }: InstanceDto, data: OfferCallDto) {\n    return await this.waMonitor.waInstances[instanceName].offerCall(data);\n  }\n}\n```\n\n## Controller Registration Pattern\n\n### Server Module Registration\n```typescript\n// In server.module.ts\nexport const templateController = new TemplateController(templateService);\nexport const businessController = new BusinessController(waMonitor);\nexport const chatController = new ChatController(waMonitor);\nexport const callController = new CallController(waMonitor);\n```\n\n## Error Handling in Controllers\n\n### Let Services Handle Errors\n```typescript\n// CORRECT - Let service handle errors\npublic async fetchCatalog(instance: InstanceDto, data: getCatalogDto) {\n  return await this.waMonitor.waInstances[instance.instanceName].fetchCatalog(instance.instanceName, data);\n}\n\n// INCORRECT - Don't add try-catch in controllers unless specific handling needed\npublic async fetchCatalog(instance: InstanceDto, data: getCatalogDto) {\n  try {\n    return await this.waMonitor.waInstances[instance.instanceName].fetchCatalog(instance.instanceName, data);\n  } catch (error) {\n    throw error; // ❌ Unnecessary try-catch\n  }\n}\n```\n\n## Complex Controller Pattern\n\n### Instance Controller Pattern\n```typescript\nexport class InstanceController {\n  constructor(\n    private readonly waMonitor: WAMonitoringService,\n    private readonly configService: ConfigService,\n    private readonly prismaRepository: PrismaRepository,\n    private readonly eventEmitter: EventEmitter2,\n    private readonly chatwootService: ChatwootService,\n    private readonly settingsService: SettingsService,\n    private readonly proxyService: ProxyController,\n    private readonly cache: CacheService,\n    private readonly chatwootCache: CacheService,\n    private readonly baileysCache: CacheService,\n    private readonly providerFiles: ProviderFiles,\n  ) {}\n\n  private readonly logger = new Logger('InstanceController');\n\n  // Multiple methods handling different aspects\n  public async createInstance(data: InstanceDto) {\n    // Complex instance creation logic\n  }\n\n  public async deleteInstance({ instanceName }: InstanceDto) {\n    // Complex instance deletion logic\n  }\n}\n```\n\n## Channel Controller Pattern\n\n### Base Channel Controller\n```typescript\nexport class ChannelController {\n  public prismaRepository: PrismaRepository;\n  public waMonitor: WAMonitoringService;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    this.prisma = prismaRepository;\n    this.monitor = waMonitor;\n  }\n\n  // Getters and setters for dependency access\n  public set prisma(prisma: PrismaRepository) {\n    this.prismaRepository = prisma;\n  }\n\n  public get prisma() {\n    return this.prismaRepository;\n  }\n\n  public set monitor(waMonitor: WAMonitoringService) {\n    this.waMonitor = waMonitor;\n  }\n\n  public get monitor() {\n    return this.waMonitor;\n  }\n}\n```\n\n### Extended Channel Controller\n```typescript\nexport class EvolutionController extends ChannelController implements ChannelControllerInterface {\n  private readonly logger = new Logger('EvolutionController');\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n  }\n\n  integrationEnabled: boolean;\n\n  public async receiveWebhook(data: any) {\n    const numberId = data.numberId;\n\n    if (!numberId) {\n      this.logger.error('WebhookService -> receiveWebhookEvolution -> numberId not found');\n      return;\n    }\n\n    const instance = await this.prismaRepository.instance.findFirst({\n      where: { number: numberId },\n    });\n\n    if (!instance) {\n      this.logger.error('WebhookService -> receiveWebhook -> instance not found');\n      return;\n    }\n\n    await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data);\n\n    return {\n      status: 'success',\n    };\n  }\n}\n```\n\n## Chatbot Controller Pattern\n\n### Base Chatbot Controller\n```typescript\nexport abstract class BaseChatbotController<BotType = any, BotData extends BaseChatbotDto = BaseChatbotDto>\n  extends ChatbotController\n  implements ChatbotControllerInterface\n{\n  public readonly logger: Logger;\n  integrationEnabled: boolean;\n  \n  // Abstract methods to be implemented\n  protected abstract readonly integrationName: string;\n  protected abstract processBot(/* parameters */): Promise<void>;\n  protected abstract getFallbackBotId(settings: any): string | undefined;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n  }\n\n  // Base implementation methods\n  public async createBot(instance: InstanceDto, data: BotData) {\n    // Common bot creation logic\n  }\n}\n```\n\n## Method Naming Conventions\n\n### Standard Method Names\n- `create*()` - Create operations\n- `find*()` - Find operations  \n- `fetch*()` - Fetch from external APIs\n- `send*()` - Send operations\n- `receive*()` - Receive webhook/data\n- `handle*()` - Handle specific actions\n- `offer*()` - Offer services (like calls)\n\n## Return Patterns\n\n### Direct Return Pattern\n```typescript\n// CORRECT - Direct return from service\npublic async createTemplate(instance: InstanceDto, data: TemplateDto) {\n  return this.templateService.create(instance, data);\n}\n\n// CORRECT - Direct return from waMonitor\npublic async offerCall({ instanceName }: InstanceDto, data: OfferCallDto) {\n  return await this.waMonitor.waInstances[instanceName].offerCall(data);\n}\n```\n\n## Controller Testing Pattern\n\n### Unit Test Structure\n```typescript\ndescribe('ExampleController', () => {\n  let controller: ExampleController;\n  let service: jest.Mocked<ExampleService>;\n\n  beforeEach(() => {\n    const mockService = {\n      create: jest.fn(),\n      find: jest.fn(),\n    };\n\n    controller = new ExampleController(mockService as any);\n    service = mockService as any;\n  });\n\n  describe('createExample', () => {\n    it('should call service create method', async () => {\n      const instance = { instanceName: 'test' };\n      const data = { test: 'data' };\n      const expectedResult = { success: true };\n\n      service.create.mockResolvedValue(expectedResult);\n\n      const result = await controller.createExample(instance, data);\n\n      expect(service.create).toHaveBeenCalledWith(instance, data);\n      expect(result).toEqual(expectedResult);\n    });\n  });\n});\n```\n\n## Interface Implementation\n\n### Controller Interface Pattern\n```typescript\nexport interface ChannelControllerInterface {\n  integrationEnabled: boolean;\n}\n\nexport interface ChatbotControllerInterface {\n  integrationEnabled: boolean;\n  createBot(instance: InstanceDto, data: any): Promise<any>;\n  findBot(instance: InstanceDto): Promise<any>;\n  // ... other methods\n}\n```\n\n## Controller Organization\n\n### File Naming Convention\n- `*.controller.ts` - Main controllers\n- `*/*.controller.ts` - Integration-specific controllers\n\n### Method Organization\n1. Constructor\n2. Public methods (alphabetical order)\n3. Private methods (if any)\n\n### Import Organization\n```typescript\n// DTOs first\nimport { InstanceDto } from '@api/dto/instance.dto';\nimport { ExampleDto } from '@api/dto/example.dto';\n\n// Services\nimport { ExampleService } from '@api/services/example.service';\n\n// Types\nimport { WAMonitoringService } from '@api/services/monitor.service';\n```",".cursor/rules/specialized-rules/dto-rules.mdc":"---\ndescription: DTO patterns and validation for Evolution API\nglobs:\n  - \"src/api/dto/**/*.ts\"\n  - \"src/api/integrations/**/dto/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API DTO Rules\n\n## DTO Structure Pattern\n\n### Basic DTO Class\n```typescript\nexport class ExampleDto {\n  name: string;\n  category: string;\n  allowCategoryChange: boolean;\n  language: string;\n  components: any;\n  webhookUrl?: string;\n}\n```\n\n## Inheritance Pattern\n\n### DTO Inheritance\n```typescript\n// CORRECT - Evolution API pattern\nexport class InstanceDto extends IntegrationDto {\n  instanceName: string;\n  instanceId?: string;\n  qrcode?: boolean;\n  businessId?: string;\n  number?: string;\n  integration?: string;\n  token?: string;\n  status?: string;\n  ownerJid?: string;\n  profileName?: string;\n  profilePicUrl?: string;\n  \n  // Settings\n  rejectCall?: boolean;\n  msgCall?: string;\n  groupsIgnore?: boolean;\n  alwaysOnline?: boolean;\n  readMessages?: boolean;\n  readStatus?: boolean;\n  syncFullHistory?: boolean;\n  wavoipToken?: string;\n  \n  // Proxy settings\n  proxyHost?: string;\n  proxyPort?: string;\n  proxyProtocol?: string;\n  proxyUsername?: string;\n  proxyPassword?: string;\n  \n  // Webhook configuration\n  webhook?: {\n    enabled?: boolean;\n    events?: string[];\n    headers?: JsonValue;\n    url?: string;\n    byEvents?: boolean;\n    base64?: boolean;\n  };\n  \n  // Chatwoot integration\n  chatwootAccountId?: string;\n  chatwootConversationPending?: boolean;\n  chatwootAutoCreate?: boolean;\n  chatwootDaysLimitImportMessages?: number;\n  chatwootImportContacts?: boolean;\n  chatwootImportMessages?: boolean;\n  chatwootLogo?: string;\n  chatwootMergeBrazilContacts?: boolean;\n  chatwootNameInbox?: string;\n  chatwootOrganization?: string;\n  chatwootReopenConversation?: boolean;\n  chatwootSignMsg?: boolean;\n  chatwootToken?: string;\n  chatwootUrl?: string;\n}\n```\n\n## Base DTO Pattern\n\n### Base Chatbot DTO\n```typescript\n/**\n * Base DTO for all chatbot integrations\n * Contains common properties shared by all chatbot types\n */\nexport class BaseChatbotDto {\n  enabled?: boolean;\n  description: string;\n  expire?: number;\n  keywordFinish?: string;\n  delayMessage?: number;\n  unknownMessage?: string;\n  listeningFromMe?: boolean;\n  stopBotFromMe?: boolean;\n  keepOpen?: boolean;\n  debounceTime?: number;\n  triggerType: TriggerType;\n  triggerOperator?: TriggerOperator;\n  triggerValue?: string;\n  ignoreJids?: string[];\n  splitMessages?: boolean;\n  timePerChar?: number;\n}\n\n/**\n * Base settings DTO for all chatbot integrations\n */\nexport class BaseChatbotSettingDto {\n  expire?: number;\n  keywordFinish?: string;\n  delayMessage?: number;\n  unknownMessage?: string;\n  listeningFromMe?: boolean;\n  stopBotFromMe?: boolean;\n  keepOpen?: boolean;\n  debounceTime?: number;\n  ignoreJids?: string[];\n  splitMessages?: boolean;\n  timePerChar?: number;\n}\n```\n\n## Message DTO Patterns\n\n### Send Message DTOs\n```typescript\nexport class Metadata {\n  number: string;\n  delay?: number;\n}\n\nexport class SendTextDto extends Metadata {\n  text: string;\n  linkPreview?: boolean;\n  mentionsEveryOne?: boolean;\n  mentioned?: string[];\n}\n\nexport class SendListDto extends Metadata {\n  title: string;\n  description: string;\n  buttonText: string;\n  footerText?: string;\n\n  sections: Section[];\n}\n\nexport class ContactMessage {\n  fullName: string;\n  wuid: string;\n  phoneNumber: string;\n  organization?: string;\n  email?: string;\n  url?: string;\n}\n\nexport class SendTemplateDto extends Metadata {\n  name: string;\n  language: string;\n  components: any;\n}\n```\n\n## Simple DTO Patterns\n\n### Basic DTOs\n```typescript\nexport class NumberDto {\n  number: string;\n}\n\nexport class LabelDto {\n  id?: string;\n  name: string;\n  color: string;\n  predefinedId?: string;\n}\n\nexport class HandleLabelDto {\n  number: string;\n  labelId: string;\n}\n\nexport class ProfileNameDto {\n  name: string;\n}\n\nexport class WhatsAppNumberDto {\n  numbers: string[];\n}\n```\n\n## Complex DTO Patterns\n\n### Business DTOs\n```typescript\nexport class getCatalogDto {\n  number?: string;\n  limit?: number;\n  cursor?: string;\n}\n\nexport class getCollectionsDto {\n  number?: string;\n  limit?: number;\n  cursor?: string;\n}\n\nexport class NumberBusiness {\n  number: string;\n  name?: string;\n  description?: string;\n  email?: string;\n  websites?: string[];\n  latitude?: number;\n  longitude?: number;\n  address?: string;\n  profilehandle?: string;\n}\n```\n\n## Settings DTO Pattern\n\n### Settings Configuration\n```typescript\nexport class SettingsDto {\n  rejectCall?: boolean;\n  msgCall?: string;\n  groupsIgnore?: boolean;\n  alwaysOnline?: boolean;\n  readMessages?: boolean;\n  readStatus?: boolean;\n  syncFullHistory?: boolean;\n  wavoipToken?: string;\n}\n\nexport class ProxyDto {\n  host?: string;\n  port?: string;\n  protocol?: string;\n  username?: string;\n  password?: string;\n}\n```\n\n## Presence DTO Pattern\n\n### WhatsApp Presence\n```typescript\nexport class SetPresenceDto {\n  presence: WAPresence;\n}\n\nexport class SendPresenceDto {\n  number: string;\n  presence: WAPresence;\n}\n```\n\n## DTO Structure (No Decorators)\n\n### Simple DTO Classes (Evolution API Pattern)\n```typescript\n// CORRECT - Evolution API pattern (no decorators)\nexport class ExampleDto {\n  name: string;\n  description?: string;\n  enabled: boolean;\n  items?: string[];\n  timeout?: number;\n}\n\n// INCORRECT - Don't use class-validator decorators\nexport class ValidatedDto {\n  @IsString() // ❌ Evolution API doesn't use decorators\n  name: string;\n}\n```\n\n## Type Safety Patterns\n\n### Prisma Type Integration\n```typescript\nimport { JsonValue } from '@prisma/client/runtime/library';\nimport { WAPresence } from 'baileys';\nimport { TriggerOperator, TriggerType } from '@prisma/client';\n\nexport class TypeSafeDto {\n  presence: WAPresence;\n  triggerType: TriggerType;\n  triggerOperator?: TriggerOperator;\n  metadata?: JsonValue;\n}\n```\n\n## DTO Documentation\n\n### JSDoc Comments\n```typescript\n/**\n * DTO for creating WhatsApp templates\n * Used by Meta Business API integration\n */\nexport class TemplateDto {\n  /** Template name - must be unique */\n  name: string;\n  \n  /** Template category (MARKETING, UTILITY, AUTHENTICATION) */\n  category: string;\n  \n  /** Whether category can be changed after creation */\n  allowCategoryChange: boolean;\n  \n  /** Language code (e.g., 'pt_BR', 'en_US') */\n  language: string;\n  \n  /** Template components (header, body, footer, buttons) */\n  components: any;\n  \n  /** Optional webhook URL for template status updates */\n  webhookUrl?: string;\n}\n```\n\n## DTO Naming Conventions\n\n### Standard Naming Patterns\n- `*Dto` - Data transfer objects\n- `Create*Dto` - Creation DTOs\n- `Update*Dto` - Update DTOs\n- `Send*Dto` - Message sending DTOs\n- `Get*Dto` - Query DTOs\n- `Handle*Dto` - Action DTOs\n\n## File Organization\n\n### DTO File Structure\n```\nsrc/api/dto/\n├── instance.dto.ts          # Main instance DTO\n├── template.dto.ts          # Template management\n├── sendMessage.dto.ts       # Message sending DTOs\n├── chat.dto.ts             # Chat operations\n├── business.dto.ts         # Business API DTOs\n├── group.dto.ts            # Group management\n├── label.dto.ts            # Label management\n├── proxy.dto.ts            # Proxy configuration\n├── settings.dto.ts         # Instance settings\n└── call.dto.ts             # Call operations\n```\n\n## Integration DTO Patterns\n\n### Chatbot Integration DTOs\n```typescript\n// Base for all chatbot DTOs\nexport class BaseChatbotDto {\n  enabled?: boolean;\n  description: string;\n  // ... common properties\n}\n\n// Specific chatbot DTOs extend base\nexport class TypebotDto extends BaseChatbotDto {\n  url: string;\n  typebot: string;\n  // ... typebot-specific properties\n}\n\nexport class OpenaiDto extends BaseChatbotDto {\n  apiKey: string;\n  model: string;\n  // ... openai-specific properties\n}\n```\n\n## DTO Testing Pattern\n\n### DTO Validation Tests\n```typescript\ndescribe('ExampleDto', () => {\n  it('should validate required fields', () => {\n    const dto = new ExampleDto();\n    dto.name = 'test';\n    dto.category = 'MARKETING';\n    dto.allowCategoryChange = true;\n    dto.language = 'pt_BR';\n    dto.components = {};\n\n    expect(dto.name).toBe('test');\n    expect(dto.category).toBe('MARKETING');\n  });\n\n  it('should handle optional fields', () => {\n    const dto = new ExampleDto();\n    dto.name = 'test';\n    dto.category = 'MARKETING';\n    dto.allowCategoryChange = true;\n    dto.language = 'pt_BR';\n    dto.components = {};\n    dto.webhookUrl = 'https://example.com/webhook';\n\n    expect(dto.webhookUrl).toBe('https://example.com/webhook');\n  });\n});\n```\n\n## DTO Transformation\n\n### Request to DTO Mapping (Evolution API Pattern)\n```typescript\n// CORRECT - Evolution API uses RouterBroker dataValidate\nconst response = await this.dataValidate<ExampleDto>({\n  request: req,\n  schema: exampleSchema, // JSONSchema7\n  ClassRef: ExampleDto,\n  execute: (instance, data) => controller.method(instance, data),\n});\n\n// INCORRECT - Don't use class-validator\nconst dto = plainToClass(ExampleDto, req.body); // ❌ Not used in Evolution API\nconst errors = await validate(dto); // ❌ Not used in Evolution API\n```",".cursor/rules/specialized-rules/guard-rules.mdc":"---\ndescription: Guard patterns for authentication and authorization in Evolution API\nglobs:\n  - \"src/api/guards/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Guard Rules\n\n## Guard Structure Pattern\n\n### Standard Guard Function\n```typescript\nimport { NextFunction, Request, Response } from 'express';\nimport { Logger } from '@config/logger.config';\nimport { UnauthorizedException, ForbiddenException } from '@exceptions';\n\nconst logger = new Logger('GUARD');\n\nasync function guardFunction(req: Request, _: Response, next: NextFunction) {\n  // Guard logic here\n  \n  if (validationFails) {\n    throw new UnauthorizedException();\n  }\n  \n  return next();\n}\n\nexport const guardName = { guardFunction };\n```\n\n## Authentication Guard Pattern\n\n### API Key Authentication\n```typescript\nasync function apikey(req: Request, _: Response, next: NextFunction) {\n  const env = configService.get<Auth>('AUTHENTICATION').API_KEY;\n  const key = req.get('apikey');\n  const db = configService.get<Database>('DATABASE');\n\n  if (!key) {\n    throw new UnauthorizedException();\n  }\n\n  // Global API key check\n  if (env.KEY === key) {\n    return next();\n  }\n\n  // Special routes handling\n  if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) {\n    throw new ForbiddenException('Missing global api key', 'The global api key must be set');\n  }\n\n  const param = req.params as unknown as InstanceDto;\n\n  try {\n    if (param?.instanceName) {\n      const instance = await prismaRepository.instance.findUnique({\n        where: { name: param.instanceName },\n      });\n      if (instance.token === key) {\n        return next();\n      }\n    } else {\n      if (req.originalUrl.includes('/instance/fetchInstances') && db.SAVE_DATA.INSTANCE) {\n        const instanceByKey = await prismaRepository.instance.findFirst({\n          where: { token: key },\n        });\n        if (instanceByKey) {\n          return next();\n        }\n      }\n    }\n  } catch (error) {\n    logger.error(error);\n  }\n\n  throw new UnauthorizedException();\n}\n\nexport const authGuard = { apikey };\n```\n\n## Instance Validation Guards\n\n### Instance Exists Guard\n```typescript\nasync function getInstance(instanceName: string) {\n  try {\n    const cacheConf = configService.get<CacheConf>('CACHE');\n\n    const exists = !!waMonitor.waInstances[instanceName];\n\n    if (cacheConf.REDIS.ENABLED && cacheConf.REDIS.SAVE_INSTANCES) {\n      const keyExists = await cache.has(instanceName);\n      return exists || keyExists;\n    }\n\n    return exists || (await prismaRepository.instance.findMany({ where: { name: instanceName } })).length > 0;\n  } catch (error) {\n    throw new InternalServerErrorException(error?.toString());\n  }\n}\n\nexport async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) {\n  if (req.originalUrl.includes('/instance/create')) {\n    return next();\n  }\n\n  const param = req.params as unknown as InstanceDto;\n  if (!param?.instanceName) {\n    throw new BadRequestException('\"instanceName\" not provided.');\n  }\n\n  if (!(await getInstance(param.instanceName))) {\n    throw new NotFoundException(`The \"${param.instanceName}\" instance does not exist`);\n  }\n\n  next();\n}\n```\n\n### Instance Logged Guard\n```typescript\nexport async function instanceLoggedGuard(req: Request, _: Response, next: NextFunction) {\n  if (req.originalUrl.includes('/instance/create')) {\n    const instance = req.body as InstanceDto;\n    if (await getInstance(instance.instanceName)) {\n      throw new ForbiddenException(`This name \"${instance.instanceName}\" is already in use.`);\n    }\n\n    if (waMonitor.waInstances[instance.instanceName]) {\n      delete waMonitor.waInstances[instance.instanceName];\n    }\n  }\n\n  next();\n}\n```\n\n## Telemetry Guard Pattern\n\n### Telemetry Collection\n```typescript\nclass Telemetry {\n  public collectTelemetry(req: Request, res: Response, next: NextFunction): void {\n    // Collect telemetry data\n    const telemetryData = {\n      route: req.originalUrl,\n      method: req.method,\n      timestamp: new Date(),\n      userAgent: req.get('User-Agent'),\n    };\n\n    // Send telemetry asynchronously (don't block request)\n    setImmediate(() => {\n      this.sendTelemetry(telemetryData);\n    });\n\n    next();\n  }\n\n  private async sendTelemetry(data: any): Promise<void> {\n    try {\n      // Send telemetry data\n    } catch (error) {\n      // Silently fail - don't affect main request\n    }\n  }\n}\n\nexport default Telemetry;\n```\n\n## Guard Composition Pattern\n\n### Multiple Guards Usage\n```typescript\n// In router setup\nconst guards = [instanceExistsGuard, instanceLoggedGuard, authGuard['apikey']];\n\nrouter\n  .use('/instance', new InstanceRouter(configService, ...guards).router)\n  .use('/message', new MessageRouter(...guards).router)\n  .use('/chat', new ChatRouter(...guards).router);\n```\n\n## Error Handling in Guards\n\n### Proper Exception Throwing\n```typescript\n// CORRECT - Use proper HTTP exceptions\nif (!apiKey) {\n  throw new UnauthorizedException('API key required');\n}\n\nif (instanceExists) {\n  throw new ForbiddenException('Instance already exists');\n}\n\nif (!instanceFound) {\n  throw new NotFoundException('Instance not found');\n}\n\nif (validationFails) {\n  throw new BadRequestException('Invalid request parameters');\n}\n\n// INCORRECT - Don't use generic Error\nif (!apiKey) {\n  throw new Error('API key required'); // ❌ Use specific exceptions\n}\n```\n\n## Configuration Access in Guards\n\n### Config Service Usage\n```typescript\nasync function configAwareGuard(req: Request, _: Response, next: NextFunction) {\n  const authConfig = configService.get<Auth>('AUTHENTICATION');\n  const cacheConfig = configService.get<CacheConf>('CACHE');\n  const dbConfig = configService.get<Database>('DATABASE');\n\n  // Use configuration for guard logic\n  if (authConfig.API_KEY.KEY === providedKey) {\n    return next();\n  }\n\n  throw new UnauthorizedException();\n}\n```\n\n## Database Access in Guards\n\n### Prisma Repository Usage\n```typescript\nasync function databaseGuard(req: Request, _: Response, next: NextFunction) {\n  try {\n    const param = req.params as unknown as InstanceDto;\n    \n    const instance = await prismaRepository.instance.findUnique({\n      where: { name: param.instanceName },\n    });\n\n    if (!instance) {\n      throw new NotFoundException('Instance not found');\n    }\n\n    // Additional validation logic\n    if (instance.status !== 'active') {\n      throw new ForbiddenException('Instance not active');\n    }\n\n    return next();\n  } catch (error) {\n    logger.error('Database guard error:', error);\n    throw new InternalServerErrorException('Database access failed');\n  }\n}\n```\n\n## Cache Integration in Guards\n\n### Cache Service Usage\n```typescript\nasync function cacheAwareGuard(req: Request, _: Response, next: NextFunction) {\n  const cacheConf = configService.get<CacheConf>('CACHE');\n  \n  if (cacheConf.REDIS.ENABLED) {\n    const cached = await cache.get(`guard:${req.params.instanceName}`);\n    if (cached) {\n      // Use cached validation result\n      return next();\n    }\n  }\n\n  // Perform validation and cache result\n  const isValid = await performValidation(req.params.instanceName);\n  \n  if (cacheConf.REDIS.ENABLED) {\n    await cache.set(`guard:${req.params.instanceName}`, isValid, 300); // 5 min TTL\n  }\n\n  if (isValid) {\n    return next();\n  }\n\n  throw new UnauthorizedException();\n}\n```\n\n## Logging in Guards\n\n### Structured Logging\n```typescript\nconst logger = new Logger('GUARD');\n\nasync function loggedGuard(req: Request, _: Response, next: NextFunction) {\n  logger.log(`Guard validation started for ${req.originalUrl}`);\n\n  try {\n    // Guard logic\n    const isValid = await validateRequest(req);\n    \n    if (isValid) {\n      logger.log(`Guard validation successful for ${req.params.instanceName}`);\n      return next();\n    }\n\n    logger.warn(`Guard validation failed for ${req.params.instanceName}`);\n    throw new UnauthorizedException();\n  } catch (error) {\n    logger.error(`Guard validation error: ${error.message}`, error.stack);\n    throw error;\n  }\n}\n```\n\n## Guard Testing Pattern\n\n### Unit Test Structure\n```typescript\ndescribe('authGuard', () => {\n  let req: Partial<Request>;\n  let res: Partial<Response>;\n  let next: NextFunction;\n\n  beforeEach(() => {\n    req = {\n      get: jest.fn(),\n      params: {},\n      originalUrl: '/test',\n    };\n    res = {};\n    next = jest.fn();\n  });\n\n  describe('apikey', () => {\n    it('should pass with valid global API key', async () => {\n      (req.get as jest.Mock).mockReturnValue('valid-global-key');\n      \n      await authGuard.apikey(req as Request, res as Response, next);\n      \n      expect(next).toHaveBeenCalled();\n    });\n\n    it('should throw UnauthorizedException with no API key', async () => {\n      (req.get as jest.Mock).mockReturnValue(undefined);\n      \n      await expect(\n        authGuard.apikey(req as Request, res as Response, next)\n      ).rejects.toThrow(UnauthorizedException);\n    });\n\n    it('should pass with valid instance token', async () => {\n      (req.get as jest.Mock).mockReturnValue('instance-token');\n      req.params = { instanceName: 'test-instance' };\n      \n      // Mock prisma repository\n      jest.spyOn(prismaRepository.instance, 'findUnique').mockResolvedValue({\n        token: 'instance-token',\n      } as any);\n      \n      await authGuard.apikey(req as Request, res as Response, next);\n      \n      expect(next).toHaveBeenCalled();\n    });\n  });\n});\n```\n\n## Guard Performance Considerations\n\n### Efficient Validation\n```typescript\n// CORRECT - Efficient guard with early returns\nasync function efficientGuard(req: Request, _: Response, next: NextFunction) {\n  // Quick checks first\n  if (req.originalUrl.includes('/public')) {\n    return next(); // Skip validation for public routes\n  }\n\n  const apiKey = req.get('apikey');\n  if (!apiKey) {\n    throw new UnauthorizedException(); // Fail fast\n  }\n\n  // More expensive checks only if needed\n  if (apiKey === globalKey) {\n    return next(); // Skip database check\n  }\n\n  // Database check only as last resort\n  const isValid = await validateInDatabase(apiKey);\n  if (isValid) {\n    return next();\n  }\n\n  throw new UnauthorizedException();\n}\n\n// INCORRECT - Inefficient guard\nasync function inefficientGuard(req: Request, _: Response, next: NextFunction) {\n  // Always do expensive database check first\n  const dbResult = await expensiveDatabaseQuery(); // ❌ Expensive operation first\n  \n  const apiKey = req.get('apikey');\n  if (!apiKey && dbResult) { // ❌ Complex logic\n    throw new UnauthorizedException();\n  }\n  \n  next();\n}\n```",".cursor/rules/specialized-rules/integration-channel-rules.mdc":"---\ndescription: Channel integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/channel/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Channel Integration Rules\n\n## Channel Controller Pattern\n\n### Base Channel Controller\n```typescript\nexport interface ChannelControllerInterface {\n  integrationEnabled: boolean;\n}\n\nexport class ChannelController {\n  public prismaRepository: PrismaRepository;\n  public waMonitor: WAMonitoringService;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    this.prisma = prismaRepository;\n    this.monitor = waMonitor;\n  }\n\n  public set prisma(prisma: PrismaRepository) {\n    this.prismaRepository = prisma;\n  }\n\n  public get prisma() {\n    return this.prismaRepository;\n  }\n\n  public set monitor(waMonitor: WAMonitoringService) {\n    this.waMonitor = waMonitor;\n  }\n\n  public get monitor() {\n    return this.waMonitor;\n  }\n\n  public init(instanceData: InstanceDto, data: ChannelDataType) {\n    if (!instanceData.token && instanceData.integration === Integration.WHATSAPP_BUSINESS) {\n      throw new BadRequestException('token is required');\n    }\n\n    if (instanceData.integration === Integration.WHATSAPP_BUSINESS) {\n      return new BusinessStartupService(/* dependencies */);\n    }\n\n    if (instanceData.integration === Integration.EVOLUTION) {\n      return new EvolutionStartupService(/* dependencies */);\n    }\n\n    if (instanceData.integration === Integration.WHATSAPP_BAILEYS) {\n      return new BaileysStartupService(/* dependencies */);\n    }\n\n    return null;\n  }\n}\n```\n\n### Extended Channel Controller\n```typescript\nexport class EvolutionController extends ChannelController implements ChannelControllerInterface {\n  private readonly logger = new Logger('EvolutionController');\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n  }\n\n  integrationEnabled: boolean;\n\n  public async receiveWebhook(data: any) {\n    const numberId = data.numberId;\n\n    if (!numberId) {\n      this.logger.error('WebhookService -> receiveWebhookEvolution -> numberId not found');\n      return;\n    }\n\n    const instance = await this.prismaRepository.instance.findFirst({\n      where: { number: numberId },\n    });\n\n    if (!instance) {\n      this.logger.error('WebhookService -> receiveWebhook -> instance not found');\n      return;\n    }\n\n    await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data);\n\n    return {\n      status: 'success',\n    };\n  }\n}\n```\n\n## Channel Service Pattern\n\n### Base Channel Service\n```typescript\nexport class ChannelStartupService {\n  constructor(\n    private readonly configService: ConfigService,\n    private readonly eventEmitter: EventEmitter2,\n    private readonly prismaRepository: PrismaRepository,\n    public readonly cache: CacheService,\n    public readonly chatwootCache: CacheService,\n  ) {}\n\n  public readonly logger = new Logger('ChannelStartupService');\n\n  public client: WASocket;\n  public readonly instance: wa.Instance = {};\n  public readonly localChatwoot: wa.LocalChatwoot = {};\n  public readonly localProxy: wa.LocalProxy = {};\n  public readonly localSettings: wa.LocalSettings = {};\n  public readonly localWebhook: wa.LocalWebHook = {};\n\n  public setInstance(instance: InstanceDto) {\n    this.logger.setInstance(instance.instanceName);\n\n    this.instance.name = instance.instanceName;\n    this.instance.id = instance.instanceId;\n    this.instance.integration = instance.integration;\n    this.instance.number = instance.number;\n    this.instance.token = instance.token;\n    this.instance.businessId = instance.businessId;\n\n    if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {\n      this.chatwootService.eventWhatsapp(\n        Events.STATUS_INSTANCE,\n        { instanceName: this.instance.name },\n        {\n          instance: this.instance.name,\n          status: 'created',\n        },\n      );\n    }\n  }\n\n  public set instanceName(name: string) {\n    this.logger.setInstance(name);\n    this.instance.name = name;\n  }\n\n  public get instanceName() {\n    return this.instance.name;\n  }\n}\n```\n\n### Extended Channel Service\n```typescript\nexport class EvolutionStartupService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache);\n  }\n\n  public async sendMessage(data: SendTextDto): Promise<any> {\n    // Evolution-specific message sending logic\n    const response = await this.evolutionApiCall('/send-message', data);\n    return response;\n  }\n\n  public async connectToWhatsapp(data: any): Promise<void> {\n    // Evolution-specific connection logic\n    this.logger.log('Connecting to Evolution API');\n    \n    // Set up webhook listeners\n    this.setupWebhookHandlers();\n    \n    // Initialize connection\n    await this.initializeConnection(data);\n  }\n\n  private async evolutionApiCall(endpoint: string, data: any): Promise<any> {\n    const config = this.configService.get<Evolution>('EVOLUTION');\n    \n    try {\n      const response = await axios.post(`${config.API_URL}${endpoint}`, data, {\n        headers: {\n          'Authorization': `Bearer ${this.instance.token}`,\n          'Content-Type': 'application/json',\n        },\n      });\n      \n      return response.data;\n    } catch (error) {\n      this.logger.error(`Evolution API call failed: ${error.message}`);\n      throw new InternalServerErrorException('Evolution API call failed');\n    }\n  }\n\n  private setupWebhookHandlers(): void {\n    // Set up webhook event handlers\n  }\n\n  private async initializeConnection(data: any): Promise<void> {\n    // Initialize connection with Evolution API\n  }\n}\n```\n\n## Business API Service Pattern\n\n### Meta Business Service\n```typescript\nexport class BusinessStartupService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n    baileysCache: CacheService,\n    providerFiles: ProviderFiles,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache);\n  }\n\n  public async sendMessage(data: SendTextDto): Promise<any> {\n    const businessConfig = this.configService.get<WaBusiness>('WA_BUSINESS');\n    \n    const payload = {\n      messaging_product: 'whatsapp',\n      to: data.number,\n      type: 'text',\n      text: {\n        body: data.text,\n      },\n    };\n\n    try {\n      const response = await axios.post(\n        `${businessConfig.URL}/${businessConfig.VERSION}/${this.instance.businessId}/messages`,\n        payload,\n        {\n          headers: {\n            'Authorization': `Bearer ${this.instance.token}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      return response.data;\n    } catch (error) {\n      this.logger.error(`Business API call failed: ${error.message}`);\n      throw new BadRequestException('Failed to send message via Business API');\n    }\n  }\n\n  public async receiveWebhook(data: any): Promise<void> {\n    // Process incoming webhook from Meta Business API\n    const { entry } = data;\n    \n    for (const entryItem of entry) {\n      const { changes } = entryItem;\n      \n      for (const change of changes) {\n        if (change.field === 'messages') {\n          await this.processMessage(change.value);\n        }\n      }\n    }\n  }\n\n  private async processMessage(messageData: any): Promise<void> {\n    // Process incoming message from Business API\n    const { messages, contacts } = messageData;\n    \n    if (messages) {\n      for (const message of messages) {\n        await this.handleIncomingMessage(message, contacts);\n      }\n    }\n  }\n\n  private async handleIncomingMessage(message: any, contacts: any[]): Promise<void> {\n    // Handle individual message\n    const contact = contacts?.find(c => c.wa_id === message.from);\n    \n    // Emit event for message processing\n    this.eventEmitter.emit(Events.MESSAGES_UPSERT, {\n      instanceName: this.instance.name,\n      message,\n      contact,\n    });\n  }\n}\n```\n\n## Baileys Service Pattern\n\n### Baileys Integration Service\n```typescript\nexport class BaileysStartupService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n    baileysCache: CacheService,\n    providerFiles: ProviderFiles,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache);\n  }\n\n  public async connectToWhatsapp(): Promise<void> {\n    const authPath = path.join(INSTANCE_DIR, this.instance.name);\n    const { state, saveCreds } = await useMultiFileAuthState(authPath);\n\n    this.client = makeWASocket({\n      auth: state,\n      logger: P({ level: 'error' }),\n      printQRInTerminal: false,\n      browser: ['Evolution API', 'Chrome', '4.0.0'],\n      defaultQueryTimeoutMs: 60000,\n    });\n\n    this.setupEventHandlers();\n    this.client.ev.on('creds.update', saveCreds);\n  }\n\n  private setupEventHandlers(): void {\n    this.client.ev.on('connection.update', (update) => {\n      this.handleConnectionUpdate(update);\n    });\n\n    this.client.ev.on('messages.upsert', ({ messages, type }) => {\n      this.handleIncomingMessages(messages, type);\n    });\n\n    this.client.ev.on('messages.update', (updates) => {\n      this.handleMessageUpdates(updates);\n    });\n\n    this.client.ev.on('contacts.upsert', (contacts) => {\n      this.handleContactsUpdate(contacts);\n    });\n\n    this.client.ev.on('chats.upsert', (chats) => {\n      this.handleChatsUpdate(chats);\n    });\n  }\n\n  private async handleConnectionUpdate(update: ConnectionUpdate): Promise<void> {\n    const { connection, lastDisconnect, qr } = update;\n\n    if (qr) {\n      this.instance.qrcode = {\n        count: this.instance.qrcode?.count ? this.instance.qrcode.count + 1 : 1,\n        base64: qr,\n      };\n\n      this.eventEmitter.emit(Events.QRCODE_UPDATED, {\n        instanceName: this.instance.name,\n        qrcode: this.instance.qrcode,\n      });\n    }\n\n    if (connection === 'close') {\n      const shouldReconnect = (lastDisconnect?.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut;\n      \n      if (shouldReconnect) {\n        this.logger.log('Connection closed, reconnecting...');\n        await this.connectToWhatsapp();\n      } else {\n        this.logger.log('Connection closed, logged out');\n        this.eventEmitter.emit(Events.LOGOUT_INSTANCE, {\n          instanceName: this.instance.name,\n        });\n      }\n    }\n\n    if (connection === 'open') {\n      this.logger.log('Connection opened successfully');\n      this.instance.wuid = this.client.user?.id;\n      \n      this.eventEmitter.emit(Events.CONNECTION_UPDATE, {\n        instanceName: this.instance.name,\n        state: 'open',\n      });\n    }\n  }\n\n  public async sendMessage(data: SendTextDto): Promise<any> {\n    const jid = createJid(data.number);\n    \n    const message = {\n      text: data.text,\n    };\n\n    if (data.linkPreview !== undefined) {\n      message.linkPreview = data.linkPreview;\n    }\n\n    if (data.mentionsEveryOne) {\n      // Handle mentions\n    }\n\n    try {\n      const response = await this.client.sendMessage(jid, message);\n      return response;\n    } catch (error) {\n      this.logger.error(`Failed to send message: ${error.message}`);\n      throw new BadRequestException('Failed to send message');\n    }\n  }\n}\n```\n\n## Channel Router Pattern\n\n### Channel Router Structure\n```typescript\nexport class ChannelRouter {\n  public readonly router: Router;\n\n  constructor(configService: any, ...guards: any[]) {\n    this.router = Router();\n\n    this.router.use('/', new EvolutionRouter(configService).router);\n    this.router.use('/', new MetaRouter(configService).router);\n    this.router.use('/baileys', new BaileysRouter(...guards).router);\n  }\n}\n```\n\n### Specific Channel Router\n```typescript\nexport class EvolutionRouter extends RouterBroker {\n  constructor(private readonly configService: ConfigService) {\n    super();\n    this.router\n      .post(this.routerPath('webhook'), async (req, res) => {\n        const response = await evolutionController.receiveWebhook(req.body);\n        return res.status(HttpStatus.OK).json(response);\n      });\n  }\n\n  public readonly router: Router = Router();\n}\n```\n\n## Integration Types\n\n### Channel Data Types\n```typescript\ntype ChannelDataType = {\n  configService: ConfigService;\n  eventEmitter: EventEmitter2;\n  prismaRepository: PrismaRepository;\n  cache: CacheService;\n  chatwootCache: CacheService;\n  baileysCache: CacheService;\n  providerFiles: ProviderFiles;\n};\n\nexport enum Integration {\n  WHATSAPP_BUSINESS = 'WHATSAPP-BUSINESS',\n  WHATSAPP_BAILEYS = 'WHATSAPP-BAILEYS',\n  EVOLUTION = 'EVOLUTION',\n}\n```\n\n## Error Handling in Channels\n\n### Channel-Specific Error Handling\n```typescript\n// CORRECT - Channel-specific error handling\npublic async sendMessage(data: SendTextDto): Promise<any> {\n  try {\n    const response = await this.channelSpecificSend(data);\n    return response;\n  } catch (error) {\n    this.logger.error(`${this.constructor.name} send failed: ${error.message}`);\n    \n    if (error.response?.status === 401) {\n      throw new UnauthorizedException('Invalid token for channel');\n    }\n    \n    if (error.response?.status === 429) {\n      throw new BadRequestException('Rate limit exceeded');\n    }\n    \n    throw new InternalServerErrorException('Channel communication failed');\n  }\n}\n```\n\n## Channel Testing Pattern\n\n### Channel Service Testing\n```typescript\ndescribe('EvolutionStartupService', () => {\n  let service: EvolutionStartupService;\n  let configService: jest.Mocked<ConfigService>;\n  let eventEmitter: jest.Mocked<EventEmitter2>;\n\n  beforeEach(() => {\n    const mockConfig = {\n      get: jest.fn().mockReturnValue({\n        API_URL: 'https://api.evolution.com',\n      }),\n    };\n\n    service = new EvolutionStartupService(\n      mockConfig as any,\n      eventEmitter,\n      prismaRepository,\n      cache,\n      chatwootCache,\n    );\n  });\n\n  describe('sendMessage', () => {\n    it('should send message successfully', async () => {\n      const data = { number: '5511999999999', text: 'Test message' };\n      \n      // Mock axios response\n      jest.spyOn(axios, 'post').mockResolvedValue({\n        data: { success: true, messageId: '123' },\n      });\n\n      const result = await service.sendMessage(data);\n\n      expect(result.success).toBe(true);\n      expect(axios.post).toHaveBeenCalledWith(\n        expect.stringContaining('/send-message'),\n        data,\n        expect.objectContaining({\n          headers: expect.objectContaining({\n            'Authorization': expect.stringContaining('Bearer'),\n          }),\n        })\n      );\n    });\n  });\n});\n```",".cursor/rules/specialized-rules/integration-chatbot-rules.mdc":"---\ndescription: Chatbot integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/chatbot/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Chatbot Integration Rules\n\n## Base Chatbot Pattern\n\n### Base Chatbot DTO\n```typescript\n/**\n * Base DTO for all chatbot integrations\n * Contains common properties shared by all chatbot types\n */\nexport class BaseChatbotDto {\n  enabled?: boolean;\n  description: string;\n  expire?: number;\n  keywordFinish?: string;\n  delayMessage?: number;\n  unknownMessage?: string;\n  listeningFromMe?: boolean;\n  stopBotFromMe?: boolean;\n  keepOpen?: boolean;\n  debounceTime?: number;\n  triggerType: TriggerType;\n  triggerOperator?: TriggerOperator;\n  triggerValue?: string;\n  ignoreJids?: string[];\n  splitMessages?: boolean;\n  timePerChar?: number;\n}\n```\n\n### Base Chatbot Controller\n```typescript\nexport abstract class BaseChatbotController<BotType = any, BotData extends BaseChatbotDto = BaseChatbotDto>\n  extends ChatbotController\n  implements ChatbotControllerInterface\n{\n  public readonly logger: Logger;\n  integrationEnabled: boolean;\n  botRepository: any;\n  settingsRepository: any;\n  sessionRepository: any;\n  userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};\n\n  // Abstract methods to be implemented by specific chatbots\n  protected abstract readonly integrationName: string;\n  protected abstract processBot(\n    waInstance: any,\n    remoteJid: string,\n    bot: BotType,\n    session: any,\n    settings: ChatbotSettings,\n    content: string,\n    pushName?: string,\n    msg?: any,\n  ): Promise<void>;\n  protected abstract getFallbackBotId(settings: any): string | undefined;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n    this.sessionRepository = this.prismaRepository.integrationSession;\n  }\n\n  // Base implementation methods\n  public async createBot(instance: InstanceDto, data: BotData) {\n    if (!data.enabled) {\n      throw new BadRequestException(`${this.integrationName} is disabled`);\n    }\n\n    // Common bot creation logic\n    const bot = await this.botRepository.create({\n      data: {\n        ...data,\n        instanceId: instance.instanceId,\n      },\n    });\n\n    return bot;\n  }\n}\n```\n\n### Base Chatbot Service\n```typescript\n/**\n * Base class for all chatbot service implementations\n * Contains common methods shared across different chatbot integrations\n */\nexport abstract class BaseChatbotService<BotType = any, SettingsType = any> {\n  protected readonly logger: Logger;\n  protected readonly waMonitor: WAMonitoringService;\n  protected readonly prismaRepository: PrismaRepository;\n  protected readonly configService?: ConfigService;\n\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    loggerName: string,\n    configService?: ConfigService,\n  ) {\n    this.waMonitor = waMonitor;\n    this.prismaRepository = prismaRepository;\n    this.logger = new Logger(loggerName);\n    this.configService = configService;\n  }\n\n  /**\n   * Check if a message contains an image\n   */\n  protected isImageMessage(content: string): boolean {\n    return content.includes('imageMessage');\n  }\n\n  /**\n   * Extract text content from message\n   */\n  protected getMessageContent(msg: any): string {\n    return getConversationMessage(msg);\n  }\n\n  /**\n   * Send typing indicator\n   */\n  protected async sendTyping(instanceName: string, remoteJid: string): Promise<void> {\n    await this.waMonitor.waInstances[instanceName].sendPresence(remoteJid, 'composing');\n  }\n}\n```\n\n## Typebot Integration Pattern\n\n### Typebot Service\n```typescript\nexport class TypebotService extends BaseChatbotService<TypebotModel, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    configService: ConfigService,\n    prismaRepository: PrismaRepository,\n    private readonly openaiService: OpenaiService,\n  ) {\n    super(waMonitor, prismaRepository, 'TypebotService', configService);\n  }\n\n  public async sendTypebotMessage(\n    instanceName: string,\n    remoteJid: string,\n    typebot: TypebotModel,\n    content: string,\n  ): Promise<void> {\n    try {\n      const response = await axios.post(\n        `${typebot.url}/api/v1/typebots/${typebot.typebot}/startChat`,\n        {\n          message: content,\n          sessionId: `${instanceName}-${remoteJid}`,\n        },\n        {\n          headers: {\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      const { messages } = response.data;\n      \n      for (const message of messages) {\n        await this.processTypebotMessage(instanceName, remoteJid, message);\n      }\n    } catch (error) {\n      this.logger.error(`Typebot API error: ${error.message}`);\n      throw new InternalServerErrorException('Typebot communication failed');\n    }\n  }\n\n  private async processTypebotMessage(\n    instanceName: string,\n    remoteJid: string,\n    message: any,\n  ): Promise<void> {\n    const waInstance = this.waMonitor.waInstances[instanceName];\n    \n    if (message.type === 'text') {\n      await waInstance.sendMessage({\n        number: remoteJid.split('@')[0],\n        text: message.content.richText[0].children[0].text,\n      });\n    }\n    \n    if (message.type === 'image') {\n      await waInstance.sendMessage({\n        number: remoteJid.split('@')[0],\n        mediaMessage: {\n          mediatype: 'image',\n          media: message.content.url,\n        },\n      });\n    }\n  }\n}\n```\n\n## OpenAI Integration Pattern\n\n### OpenAI Service\n```typescript\nexport class OpenaiService extends BaseChatbotService<OpenaiModel, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(waMonitor, prismaRepository, 'OpenaiService', configService);\n  }\n\n  public async sendOpenaiMessage(\n    instanceName: string,\n    remoteJid: string,\n    openai: OpenaiModel,\n    content: string,\n    pushName?: string,\n  ): Promise<void> {\n    try {\n      const openaiConfig = this.configService.get<Openai>('OPENAI');\n      \n      const response = await axios.post(\n        'https://api.openai.com/v1/chat/completions',\n        {\n          model: openai.model || 'gpt-3.5-turbo',\n          messages: [\n            {\n              role: 'system',\n              content: openai.systemMessage || 'You are a helpful assistant.',\n            },\n            {\n              role: 'user',\n              content: content,\n            },\n          ],\n          max_tokens: openai.maxTokens || 1000,\n          temperature: openai.temperature || 0.7,\n        },\n        {\n          headers: {\n            'Authorization': `Bearer ${openai.apiKey || openaiConfig.API_KEY}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      const aiResponse = response.data.choices[0].message.content;\n      \n      await this.waMonitor.waInstances[instanceName].sendMessage({\n        number: remoteJid.split('@')[0],\n        text: aiResponse,\n      });\n    } catch (error) {\n      this.logger.error(`OpenAI API error: ${error.message}`);\n      \n      // Send fallback message\n      await this.waMonitor.waInstances[instanceName].sendMessage({\n        number: remoteJid.split('@')[0],\n        text: openai.unknownMessage || 'Desculpe, não consegui processar sua mensagem.',\n      });\n    }\n  }\n}\n```\n\n## Chatwoot Integration Pattern\n\n### Chatwoot Service\n```typescript\nexport class ChatwootService extends BaseChatbotService<any, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    configService: ConfigService,\n    prismaRepository: PrismaRepository,\n    private readonly chatwootCache: CacheService,\n  ) {\n    super(waMonitor, prismaRepository, 'ChatwootService', configService);\n  }\n\n  public async eventWhatsapp(\n    event: Events,\n    instanceName: { instanceName: string },\n    data: any,\n  ): Promise<void> {\n    const chatwootConfig = this.configService.get<Chatwoot>('CHATWOOT');\n    \n    if (!chatwootConfig.ENABLED) {\n      return;\n    }\n\n    try {\n      const instance = await this.prismaRepository.instance.findUnique({\n        where: { name: instanceName.instanceName },\n      });\n\n      if (!instance?.chatwootAccountId) {\n        return;\n      }\n\n      const webhook = {\n        event,\n        instance: instanceName.instanceName,\n        data,\n        timestamp: new Date().toISOString(),\n      };\n\n      await axios.post(\n        `${instance.chatwootUrl}/api/v1/accounts/${instance.chatwootAccountId}/webhooks`,\n        webhook,\n        {\n          headers: {\n            'Authorization': `Bearer ${instance.chatwootToken}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n    } catch (error) {\n      this.logger.error(`Chatwoot webhook error: ${error.message}`);\n    }\n  }\n\n  public async createConversation(\n    instanceName: string,\n    contact: any,\n    message: any,\n  ): Promise<void> {\n    // Create conversation in Chatwoot\n    const instance = await this.prismaRepository.instance.findUnique({\n      where: { name: instanceName },\n    });\n\n    if (!instance?.chatwootAccountId) {\n      return;\n    }\n\n    try {\n      const conversation = await axios.post(\n        `${instance.chatwootUrl}/api/v1/accounts/${instance.chatwootAccountId}/conversations`,\n        {\n          source_id: contact.id,\n          inbox_id: instance.chatwootInboxId,\n          contact_id: contact.chatwootContactId,\n        },\n        {\n          headers: {\n            'Authorization': `Bearer ${instance.chatwootToken}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      // Cache conversation\n      await this.chatwootCache.set(\n        `conversation:${instanceName}:${contact.id}`,\n        conversation.data,\n        3600\n      );\n    } catch (error) {\n      this.logger.error(`Chatwoot conversation creation error: ${error.message}`);\n    }\n  }\n}\n```\n\n## Dify Integration Pattern\n\n### Dify Service\n```typescript\nexport class DifyService extends BaseChatbotService<DifyModel, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    private readonly openaiService: OpenaiService,\n  ) {\n    super(waMonitor, prismaRepository, 'DifyService', configService);\n  }\n\n  public async sendDifyMessage(\n    instanceName: string,\n    remoteJid: string,\n    dify: DifyModel,\n    content: string,\n  ): Promise<void> {\n    try {\n      const response = await axios.post(\n        `${dify.apiUrl}/v1/chat-messages`,\n        {\n          inputs: {},\n          query: content,\n          user: remoteJid,\n          conversation_id: `${instanceName}-${remoteJid}`,\n          response_mode: 'blocking',\n        },\n        {\n          headers: {\n            'Authorization': `Bearer ${dify.apiKey}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      const aiResponse = response.data.answer;\n      \n      await this.waMonitor.waInstances[instanceName].sendMessage({\n        number: remoteJid.split('@')[0],\n        text: aiResponse,\n      });\n    } catch (error) {\n      this.logger.error(`Dify API error: ${error.message}`);\n      \n      // Fallback to OpenAI if configured\n      if (dify.fallbackOpenai && this.openaiService) {\n        await this.openaiService.sendOpenaiMessage(instanceName, remoteJid, dify.openaiBot, content);\n      }\n    }\n  }\n}\n```\n\n## Chatbot Router Pattern\n\n### Chatbot Router Structure (Evolution API Real Pattern)\n```typescript\nexport class ChatbotRouter {\n  public readonly router: Router;\n\n  constructor(...guards: any[]) {\n    this.router = Router();\n\n    // Real Evolution API chatbot integrations\n    this.router.use('/evolutionBot', new EvolutionBotRouter(...guards).router);\n    this.router.use('/chatwoot', new ChatwootRouter(...guards).router);\n    this.router.use('/typebot', new TypebotRouter(...guards).router);\n    this.router.use('/openai', new OpenaiRouter(...guards).router);\n    this.router.use('/dify', new DifyRouter(...guards).router);\n    this.router.use('/flowise', new FlowiseRouter(...guards).router);\n    this.router.use('/n8n', new N8nRouter(...guards).router);\n    this.router.use('/evoai', new EvoaiRouter(...guards).router);\n  }\n}\n```\n\n## Chatbot Validation Patterns\n\n### Chatbot Schema Validation (Evolution API Pattern)\n```typescript\nimport { JSONSchema7 } from 'json-schema';\nimport { v4 } from 'uuid';\n\nconst isNotEmpty = (...fields: string[]) => {\n  const properties = {};\n  fields.forEach((field) => {\n    properties[field] = {\n      if: { properties: { [field]: { type: 'string' } } },\n      then: { properties: { [field]: { minLength: 1 } } },\n    };\n  });\n\n  return {\n    allOf: Object.values(properties),\n  };\n};\n\nexport const evolutionBotSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    enabled: { type: 'boolean' },\n    description: { type: 'string' },\n    apiUrl: { type: 'string' },\n    apiKey: { type: 'string' },\n    triggerType: { type: 'string', enum: ['all', 'keyword', 'none', 'advanced'] },\n    triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith', 'regex'] },\n    triggerValue: { type: 'string' },\n    expire: { type: 'integer' },\n    keywordFinish: { type: 'string' },\n    delayMessage: { type: 'integer' },\n    unknownMessage: { type: 'string' },\n    listeningFromMe: { type: 'boolean' },\n    stopBotFromMe: { type: 'boolean' },\n    keepOpen: { type: 'boolean' },\n    debounceTime: { type: 'integer' },\n    ignoreJids: { type: 'array', items: { type: 'string' } },\n    splitMessages: { type: 'boolean' },\n    timePerChar: { type: 'integer' },\n  },\n  required: ['enabled', 'apiUrl', 'triggerType'],\n  ...isNotEmpty('enabled', 'apiUrl', 'triggerType'),\n};\n\nfunction validateKeywordTrigger(\n  content: string,\n  operator: TriggerOperator,\n  value: string,\n): boolean {\n  const normalizedContent = content.toLowerCase().trim();\n  const normalizedValue = value.toLowerCase().trim();\n\n  switch (operator) {\n    case TriggerOperator.EQUALS:\n      return normalizedContent === normalizedValue;\n    case TriggerOperator.CONTAINS:\n      return normalizedContent.includes(normalizedValue);\n    case TriggerOperator.STARTS_WITH:\n      return normalizedContent.startsWith(normalizedValue);\n    case TriggerOperator.ENDS_WITH:\n      return normalizedContent.endsWith(normalizedValue);\n    default:\n      return false;\n  }\n}\n```\n\n## Session Management Pattern\n\n### Chatbot Session Handling\n```typescript\nexport class ChatbotSessionManager {\n  constructor(\n    private readonly prismaRepository: PrismaRepository,\n    private readonly cache: CacheService,\n  ) {}\n\n  public async getSession(\n    instanceName: string,\n    remoteJid: string,\n    botId: string,\n  ): Promise<IntegrationSession | null> {\n    const cacheKey = `session:${instanceName}:${remoteJid}:${botId}`;\n    \n    // Try cache first\n    let session = await this.cache.get(cacheKey);\n    if (session) {\n      return session;\n    }\n\n    // Query database\n    session = await this.prismaRepository.integrationSession.findFirst({\n      where: {\n        instanceId: instanceName,\n        remoteJid,\n        botId,\n        status: 'opened',\n      },\n    });\n\n    // Cache result\n    if (session) {\n      await this.cache.set(cacheKey, session, 300); // 5 min TTL\n    }\n\n    return session;\n  }\n\n  public async createSession(\n    instanceName: string,\n    remoteJid: string,\n    botId: string,\n  ): Promise<IntegrationSession> {\n    const session = await this.prismaRepository.integrationSession.create({\n      data: {\n        instanceId: instanceName,\n        remoteJid,\n        botId,\n        status: 'opened',\n        createdAt: new Date(),\n      },\n    });\n\n    // Cache new session\n    const cacheKey = `session:${instanceName}:${remoteJid}:${botId}`;\n    await this.cache.set(cacheKey, session, 300);\n\n    return session;\n  }\n\n  public async closeSession(sessionId: string): Promise<void> {\n    await this.prismaRepository.integrationSession.update({\n      where: { id: sessionId },\n      data: { status: 'closed', updatedAt: new Date() },\n    });\n\n    // Invalidate cache\n    // Note: In a real implementation, you'd need to track cache keys by session ID\n  }\n}\n```",".cursor/rules/specialized-rules/integration-event-rules.mdc":"---\ndescription: Event integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/event/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Event Integration Rules\n\n## Event Manager Pattern\n\n### Event Manager Structure\n```typescript\nimport { PrismaRepository } from '@api/repository/repository.service';\nimport { ConfigService } from '@config/env.config';\nimport { Logger } from '@config/logger.config';\nimport { Server } from 'http';\n\nexport class EventManager {\n  private prismaRepository: PrismaRepository;\n  private configService: ConfigService;\n  private logger = new Logger('EventManager');\n\n  // Event integrations\n  private webhook: WebhookController;\n  private websocket: WebsocketController;\n  private rabbitmq: RabbitmqController;\n  private nats: NatsController;\n  private sqs: SqsController;\n  private pusher: PusherController;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    server?: Server,\n  ) {\n    this.prismaRepository = prismaRepository;\n    this.configService = configService;\n\n    // Initialize event controllers\n    this.webhook = new WebhookController(prismaRepository, configService);\n    this.websocket = new WebsocketController(prismaRepository, configService, server);\n    this.rabbitmq = new RabbitmqController(prismaRepository, configService);\n    this.nats = new NatsController(prismaRepository, configService);\n    this.sqs = new SqsController(prismaRepository, configService);\n    this.pusher = new PusherController(prismaRepository, configService);\n  }\n\n  public async emit(eventData: {\n    instanceName: string;\n    origin: string;\n    event: string;\n    data: Object;\n    serverUrl: string;\n    dateTime: string;\n    sender: string;\n    apiKey?: string;\n    local?: boolean;\n    integration?: string[];\n  }): Promise<void> {\n    this.logger.log(`Emitting event ${eventData.event} for instance ${eventData.instanceName}`);\n\n    // Emit to all configured integrations\n    await Promise.allSettled([\n      this.webhook.emit(eventData),\n      this.websocket.emit(eventData),\n      this.rabbitmq.emit(eventData),\n      this.nats.emit(eventData),\n      this.sqs.emit(eventData),\n      this.pusher.emit(eventData),\n    ]);\n  }\n\n  public async setInstance(instanceName: string, data: any): Promise<any> {\n    const promises = [];\n\n    if (data.websocket) {\n      promises.push(\n        this.websocket.set(instanceName, {\n          websocket: {\n            enabled: true,\n            events: data.websocket?.events,\n          },\n        })\n      );\n    }\n\n    if (data.rabbitmq) {\n      promises.push(\n        this.rabbitmq.set(instanceName, {\n          rabbitmq: {\n            enabled: true,\n            events: data.rabbitmq?.events,\n          },\n        })\n      );\n    }\n\n    if (data.webhook) {\n      promises.push(\n        this.webhook.set(instanceName, {\n          webhook: {\n            enabled: true,\n            events: data.webhook?.events,\n            url: data.webhook?.url,\n            headers: data.webhook?.headers,\n            base64: data.webhook?.base64,\n            byEvents: data.webhook?.byEvents,\n          },\n        })\n      );\n    }\n\n    // Set other integrations...\n\n    await Promise.allSettled(promises);\n  }\n}\n```\n\n## Base Event Controller Pattern\n\n### Abstract Event Controller\n```typescript\nimport { PrismaRepository } from '@api/repository/repository.service';\nimport { ConfigService } from '@config/env.config';\nimport { Logger } from '@config/logger.config';\n\nexport type EmitData = {\n  instanceName: string;\n  origin: string;\n  event: string;\n  data: Object;\n  serverUrl: string;\n  dateTime: string;\n  sender: string;\n  apiKey?: string;\n  local?: boolean;\n  integration?: string[];\n};\n\nexport interface EventControllerInterface {\n  integrationEnabled: boolean;\n  emit(data: EmitData): Promise<void>;\n  set(instanceName: string, data: any): Promise<any>;\n}\n\nexport abstract class EventController implements EventControllerInterface {\n  protected readonly logger: Logger;\n  protected readonly prismaRepository: PrismaRepository;\n  protected readonly configService: ConfigService;\n\n  public integrationEnabled: boolean = false;\n\n  // Available events for all integrations\n  public static readonly events = [\n    'APPLICATION_STARTUP',\n    'INSTANCE_CREATE',\n    'INSTANCE_DELETE',\n    'QRCODE_UPDATED',\n    'CONNECTION_UPDATE',\n    'STATUS_INSTANCE',\n    'MESSAGES_SET',\n    'MESSAGES_UPSERT',\n    'MESSAGES_EDITED',\n    'MESSAGES_UPDATE',\n    'MESSAGES_DELETE',\n    'SEND_MESSAGE',\n    'CONTACTS_SET',\n    'CONTACTS_UPSERT',\n    'CONTACTS_UPDATE',\n    'PRESENCE_UPDATE',\n    'CHATS_SET',\n    'CHATS_UPDATE',\n    'CHATS_UPSERT',\n    'CHATS_DELETE',\n    'GROUPS_UPSERT',\n    'GROUPS_UPDATE',\n    'GROUP_PARTICIPANTS_UPDATE',\n    'CALL',\n    'TYPEBOT_START',\n    'TYPEBOT_CHANGE_STATUS',\n    'LABELS_EDIT',\n    'LABELS_ASSOCIATION',\n    'CREDS_UPDATE',\n    'MESSAGING_HISTORY_SET',\n    'REMOVE_INSTANCE',\n    'LOGOUT_INSTANCE',\n  ];\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    loggerName: string,\n  ) {\n    this.prismaRepository = prismaRepository;\n    this.configService = configService;\n    this.logger = new Logger(loggerName);\n  }\n\n  // Abstract methods to be implemented by specific integrations\n  public abstract emit(data: EmitData): Promise<void>;\n  public abstract set(instanceName: string, data: any): Promise<any>;\n\n  // Helper method to check if event should be processed\n  protected shouldProcessEvent(eventName: string, configuredEvents?: string[]): boolean {\n    if (!configuredEvents || configuredEvents.length === 0) {\n      return true; // Process all events if none specified\n    }\n    return configuredEvents.includes(eventName);\n  }\n\n  // Helper method to get instance configuration\n  protected async getInstanceConfig(instanceName: string): Promise<any> {\n    try {\n      const instance = await this.prismaRepository.instance.findUnique({\n        where: { name: instanceName },\n      });\n      return instance;\n    } catch (error) {\n      this.logger.error(`Failed to get instance config for ${instanceName}:`, error);\n      return null;\n    }\n  }\n}\n```\n\n## Webhook Integration Pattern\n\n### Webhook Controller Implementation\n```typescript\nexport class WebhookController extends EventController {\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(prismaRepository, configService, 'WebhookController');\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.webhook?.enabled) {\n        return;\n      }\n\n      const webhookConfig = instance.webhook;\n      \n      if (!this.shouldProcessEvent(data.event, webhookConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n        server: {\n          version: process.env.npm_package_version,\n          url: data.serverUrl,\n        },\n      };\n\n      // Encode data as base64 if configured\n      if (webhookConfig.base64) {\n        payload.data = Buffer.from(JSON.stringify(payload.data)).toString('base64');\n      }\n\n      const headers = {\n        'Content-Type': 'application/json',\n        'User-Agent': 'Evolution-API-Webhook',\n        ...webhookConfig.headers,\n      };\n\n      if (webhookConfig.byEvents) {\n        // Send to event-specific endpoint\n        const eventUrl = `${webhookConfig.url}/${data.event.toLowerCase()}`;\n        await this.sendWebhook(eventUrl, payload, headers);\n      } else {\n        // Send to main webhook URL\n        await this.sendWebhook(webhookConfig.url, payload, headers);\n      }\n\n      this.logger.log(`Webhook sent for event ${data.event} to instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`Webhook emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const webhookData = data.webhook;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          webhook: webhookData,\n        },\n      });\n\n      this.logger.log(`Webhook configuration set for instance ${instanceName}`);\n      return { webhook: webhookData };\n    } catch (error) {\n      this.logger.error(`Failed to set webhook config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n\n  private async sendWebhook(url: string, payload: any, headers: any): Promise<void> {\n    try {\n      const response = await axios.post(url, payload, {\n        headers,\n        timeout: 30000,\n        maxRedirects: 3,\n      });\n\n      if (response.status >= 200 && response.status < 300) {\n        this.logger.log(`Webhook delivered successfully to ${url}`);\n      } else {\n        this.logger.warn(`Webhook returned status ${response.status} for ${url}`);\n      }\n    } catch (error) {\n      this.logger.error(`Webhook delivery failed to ${url}:`, error.message);\n      \n      // Implement retry logic here if needed\n      if (error.response?.status >= 500) {\n        // Server error - might be worth retrying\n        this.logger.log(`Server error detected, webhook might be retried later`);\n      }\n    }\n  }\n}\n```\n\n## WebSocket Integration Pattern\n\n### WebSocket Controller Implementation\n```typescript\nimport { Server as SocketIOServer } from 'socket.io';\nimport { Server } from 'http';\n\nexport class WebsocketController extends EventController {\n  private io: SocketIOServer;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    server?: Server,\n  ) {\n    super(prismaRepository, configService, 'WebsocketController');\n    \n    if (server) {\n      this.io = new SocketIOServer(server, {\n        cors: {\n          origin: \"*\",\n          methods: [\"GET\", \"POST\"],\n        },\n      });\n\n      this.setupSocketHandlers();\n    }\n  }\n\n  private setupSocketHandlers(): void {\n    this.io.on('connection', (socket) => {\n      this.logger.log(`WebSocket client connected: ${socket.id}`);\n\n      socket.on('join-instance', (instanceName: string) => {\n        socket.join(`instance:${instanceName}`);\n        this.logger.log(`Client ${socket.id} joined instance ${instanceName}`);\n      });\n\n      socket.on('leave-instance', (instanceName: string) => {\n        socket.leave(`instance:${instanceName}`);\n        this.logger.log(`Client ${socket.id} left instance ${instanceName}`);\n      });\n\n      socket.on('disconnect', () => {\n        this.logger.log(`WebSocket client disconnected: ${socket.id}`);\n      });\n    });\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    if (!this.io) {\n      return;\n    }\n\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.websocket?.enabled) {\n        return;\n      }\n\n      const websocketConfig = instance.websocket;\n      \n      if (!this.shouldProcessEvent(data.event, websocketConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n      };\n\n      // Emit to specific instance room\n      this.io.to(`instance:${data.instanceName}`).emit('evolution-event', payload);\n      \n      // Also emit to global room for monitoring\n      this.io.emit('global-event', payload);\n\n      this.logger.log(`WebSocket event ${data.event} emitted for instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`WebSocket emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const websocketData = data.websocket;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          websocket: websocketData,\n        },\n      });\n\n      this.logger.log(`WebSocket configuration set for instance ${instanceName}`);\n      return { websocket: websocketData };\n    } catch (error) {\n      this.logger.error(`Failed to set WebSocket config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n}\n```\n\n## Queue Integration Patterns\n\n### RabbitMQ Controller Implementation\n```typescript\nimport amqp from 'amqplib';\n\nexport class RabbitmqController extends EventController {\n  private connection: amqp.Connection | null = null;\n  private channel: amqp.Channel | null = null;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(prismaRepository, configService, 'RabbitmqController');\n    this.initializeConnection();\n  }\n\n  private async initializeConnection(): Promise<void> {\n    try {\n      const rabbitmqConfig = this.configService.get('RABBITMQ');\n      if (!rabbitmqConfig?.ENABLED) {\n        return;\n      }\n\n      this.connection = await amqp.connect(rabbitmqConfig.URI);\n      this.channel = await this.connection.createChannel();\n\n      // Declare exchange for Evolution API events\n      await this.channel.assertExchange('evolution-events', 'topic', { durable: true });\n\n      this.logger.log('RabbitMQ connection established');\n    } catch (error) {\n      this.logger.error('Failed to initialize RabbitMQ connection:', error);\n    }\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    if (!this.channel) {\n      return;\n    }\n\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.rabbitmq?.enabled) {\n        return;\n      }\n\n      const rabbitmqConfig = instance.rabbitmq;\n      \n      if (!this.shouldProcessEvent(data.event, rabbitmqConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n      };\n\n      const routingKey = `evolution.${data.instanceName}.${data.event.toLowerCase()}`;\n      \n      await this.channel.publish(\n        'evolution-events',\n        routingKey,\n        Buffer.from(JSON.stringify(payload)),\n        {\n          persistent: true,\n          timestamp: Date.now(),\n          messageId: `${data.instanceName}-${Date.now()}`,\n        }\n      );\n\n      this.logger.log(`RabbitMQ message published for event ${data.event} to instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`RabbitMQ emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const rabbitmqData = data.rabbitmq;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          rabbitmq: rabbitmqData,\n        },\n      });\n\n      this.logger.log(`RabbitMQ configuration set for instance ${instanceName}`);\n      return { rabbitmq: rabbitmqData };\n    } catch (error) {\n      this.logger.error(`Failed to set RabbitMQ config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n}\n```\n\n### SQS Controller Implementation\n```typescript\nimport { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';\n\nexport class SqsController extends EventController {\n  private sqsClient: SQSClient | null = null;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(prismaRepository, configService, 'SqsController');\n    this.initializeSQSClient();\n  }\n\n  private initializeSQSClient(): void {\n    try {\n      const sqsConfig = this.configService.get('SQS');\n      if (!sqsConfig?.ENABLED) {\n        return;\n      }\n\n      this.sqsClient = new SQSClient({\n        region: sqsConfig.REGION,\n        credentials: {\n          accessKeyId: sqsConfig.ACCESS_KEY_ID,\n          secretAccessKey: sqsConfig.SECRET_ACCESS_KEY,\n        },\n      });\n\n      this.logger.log('SQS client initialized');\n    } catch (error) {\n      this.logger.error('Failed to initialize SQS client:', error);\n    }\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    if (!this.sqsClient) {\n      return;\n    }\n\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.sqs?.enabled) {\n        return;\n      }\n\n      const sqsConfig = instance.sqs;\n      \n      if (!this.shouldProcessEvent(data.event, sqsConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n      };\n\n      const command = new SendMessageCommand({\n        QueueUrl: sqsConfig.queueUrl,\n        MessageBody: JSON.stringify(payload),\n        MessageAttributes: {\n          event: {\n            DataType: 'String',\n            StringValue: data.event,\n          },\n          instance: {\n            DataType: 'String',\n            StringValue: data.instanceName,\n          },\n        },\n        MessageGroupId: data.instanceName, // For FIFO queues\n        MessageDeduplicationId: `${data.instanceName}-${Date.now()}`, // For FIFO queues\n      });\n\n      await this.sqsClient.send(command);\n\n      this.logger.log(`SQS message sent for event ${data.event} to instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`SQS emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const sqsData = data.sqs;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          sqs: sqsData,\n        },\n      });\n\n      this.logger.log(`SQS configuration set for instance ${instanceName}`);\n      return { sqs: sqsData };\n    } catch (error) {\n      this.logger.error(`Failed to set SQS config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n}\n```\n\n## Event DTO Pattern\n\n### Event Configuration DTO\n```typescript\nimport { JsonValue } from '@prisma/client/runtime/library';\n\nexport class EventDto {\n  webhook?: {\n    enabled?: boolean;\n    events?: string[];\n    url?: string;\n    headers?: JsonValue;\n    byEvents?: boolean;\n    base64?: boolean;\n  };\n\n  websocket?: {\n    enabled?: boolean;\n    events?: string[];\n  };\n\n  sqs?: {\n    enabled?: boolean;\n    events?: string[];\n    queueUrl?: string;\n  };\n\n  rabbitmq?: {\n    enabled?: boolean;\n    events?: string[];\n    exchange?: string;\n  };\n\n  nats?: {\n    enabled?: boolean;\n    events?: string[];\n    subject?: string;\n  };\n\n  pusher?: {\n    enabled?: boolean;\n    appId?: string;\n    key?: string;\n    secret?: string;\n    cluster?: string;\n    useTLS?: boolean;\n    events?: string[];\n  };\n}\n```\n\n## Event Router Pattern\n\n### Event Router Structure\n```typescript\nimport { NatsRouter } from '@api/integrations/event/nats/nats.router';\nimport { PusherRouter } from '@api/integrations/event/pusher/pusher.router';\nimport { RabbitmqRouter } from '@api/integrations/event/rabbitmq/rabbitmq.router';\nimport { SqsRouter } from '@api/integrations/event/sqs/sqs.router';\nimport { WebhookRouter } from '@api/integrations/event/webhook/webhook.router';\nimport { WebsocketRouter } from '@api/integrations/event/websocket/websocket.router';\nimport { Router } from 'express';\n\nexport class EventRouter {\n  public readonly router: Router;\n\n  constructor(configService: any, ...guards: any[]) {\n    this.router = Router();\n\n    this.router.use('/webhook', new WebhookRouter(configService, ...guards).router);\n    this.router.use('/websocket', new WebsocketRouter(...guards).router);\n    this.router.use('/rabbitmq', new RabbitmqRouter(...guards).router);\n    this.router.use('/nats', new NatsRouter(...guards).router);\n    this.router.use('/pusher', new PusherRouter(...guards).router);\n    this.router.use('/sqs', new SqsRouter(...guards).router);\n  }\n}\n```\n\n## Event Validation Schema\n\n### Event Configuration Validation\n```typescript\nimport Joi from 'joi';\nimport { EventController } from '@api/integrations/event/event.controller';\n\nconst eventListSchema = Joi.array().items(\n  Joi.string().valid(...EventController.events)\n).optional();\n\nexport const webhookSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  url: Joi.string().when('enabled', {\n    is: true,\n    then: Joi.required().uri({ scheme: ['http', 'https'] }),\n    otherwise: Joi.optional(),\n  }),\n  events: eventListSchema,\n  headers: Joi.object().pattern(Joi.string(), Joi.string()).optional(),\n  byEvents: Joi.boolean().optional().default(false),\n  base64: Joi.boolean().optional().default(false),\n}).required();\n\nexport const websocketSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  events: eventListSchema,\n}).required();\n\nexport const rabbitmqSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  events: eventListSchema,\n  exchange: Joi.string().optional().default('evolution-events'),\n}).required();\n\nexport const sqsSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  events: eventListSchema,\n  queueUrl: Joi.string().when('enabled', {\n    is: true,\n    then: Joi.required().uri(),\n    otherwise: Joi.optional(),\n  }),\n}).required();\n\nexport const eventSchema = Joi.object({\n  webhook: webhookSchema.optional(),\n  websocket: websocketSchema.optional(),\n  rabbitmq: rabbitmqSchema.optional(),\n  sqs: sqsSchema.optional(),\n  nats: Joi.object({\n    enabled: Joi.boolean().required(),\n    events: eventListSchema,\n    subject: Joi.string().optional().default('evolution.events'),\n  }).optional(),\n  pusher: Joi.object({\n    enabled: Joi.boolean().required(),\n    appId: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    key: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    secret: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    cluster: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    useTLS: Joi.boolean().optional().default(true),\n    events: eventListSchema,\n  }).optional(),\n}).min(1).required();\n```\n\n## Event Testing Pattern\n\n### Event Controller Testing\n```typescript\ndescribe('WebhookController', () => {\n  let controller: WebhookController;\n  let prismaRepository: jest.Mocked<PrismaRepository>;\n  let configService: jest.Mocked<ConfigService>;\n\n  beforeEach(() => {\n    controller = new WebhookController(prismaRepository, configService);\n  });\n\n  describe('emit', () => {\n    it('should send webhook when enabled', async () => {\n      const mockInstance = {\n        webhook: {\n          enabled: true,\n          url: 'https://example.com/webhook',\n          events: ['MESSAGES_UPSERT'],\n        },\n      };\n\n      prismaRepository.instance.findUnique.mockResolvedValue(mockInstance);\n      jest.spyOn(axios, 'post').mockResolvedValue({ status: 200 });\n\n      const eventData = {\n        instanceName: 'test-instance',\n        event: 'MESSAGES_UPSERT',\n        data: { message: 'test' },\n        origin: 'test',\n        serverUrl: 'http://localhost',\n        dateTime: new Date().toISOString(),\n        sender: 'test',\n      };\n\n      await controller.emit(eventData);\n\n      expect(axios.post).toHaveBeenCalledWith(\n        'https://example.com/webhook',\n        expect.objectContaining({\n          event: 'MESSAGES_UPSERT',\n          instance: 'test-instance',\n        }),\n        expect.objectContaining({\n          headers: expect.objectContaining({\n            'Content-Type': 'application/json',\n          }),\n        })\n      );\n    });\n  });\n});\n```",".cursor/rules/specialized-rules/integration-storage-rules.mdc":"---\ndescription: Storage integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/storage/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Storage Integration Rules\n\n## Storage Service Pattern\n\n### Base Storage Service Structure\n```typescript\nimport { InstanceDto } from '@api/dto/instance.dto';\nimport { PrismaRepository } from '@api/repository/repository.service';\nimport { Logger } from '@config/logger.config';\nimport { BadRequestException } from '@exceptions';\n\nexport class StorageService {\n  constructor(private readonly prismaRepository: PrismaRepository) {}\n\n  private readonly logger = new Logger('StorageService');\n\n  public async getMedia(instance: InstanceDto, query?: MediaDto) {\n    try {\n      const where: any = {\n        instanceId: instance.instanceId,\n        ...query,\n      };\n\n      const media = await this.prismaRepository.media.findMany({\n        where,\n        select: {\n          id: true,\n          fileName: true,\n          type: true,\n          mimetype: true,\n          createdAt: true,\n          Message: true,\n        },\n      });\n\n      if (!media || media.length === 0) {\n        throw 'Media not found';\n      }\n\n      return media;\n    } catch (error) {\n      throw new BadRequestException(error);\n    }\n  }\n\n  public async getMediaUrl(instance: InstanceDto, data: MediaDto) {\n    const media = (await this.getMedia(instance, { id: data.id }))[0];\n    const mediaUrl = await this.generateUrl(media.fileName, data.expiry);\n    return {\n      mediaUrl,\n      ...media,\n    };\n  }\n\n  protected abstract generateUrl(fileName: string, expiry?: number): Promise<string>;\n}\n```\n\n## S3/MinIO Integration Pattern\n\n### MinIO Client Setup\n```typescript\nimport { ConfigService, S3 } from '@config/env.config';\nimport { Logger } from '@config/logger.config';\nimport { BadRequestException } from '@exceptions';\nimport * as MinIo from 'minio';\nimport { join } from 'path';\nimport { Readable, Transform } from 'stream';\n\nconst logger = new Logger('S3 Service');\nconst BUCKET = new ConfigService().get<S3>('S3');\n\ninterface Metadata extends MinIo.ItemBucketMetadata {\n  instanceId: string;\n  messageId?: string;\n}\n\nconst minioClient = (() => {\n  if (BUCKET?.ENABLE) {\n    return new MinIo.Client({\n      endPoint: BUCKET.ENDPOINT,\n      port: BUCKET.PORT,\n      useSSL: BUCKET.USE_SSL,\n      accessKey: BUCKET.ACCESS_KEY,\n      secretKey: BUCKET.SECRET_KEY,\n      region: BUCKET.REGION,\n    });\n  }\n})();\n\nconst bucketName = process.env.S3_BUCKET;\n```\n\n### Bucket Management Functions\n```typescript\nconst bucketExists = async (): Promise<boolean> => {\n  if (minioClient) {\n    try {\n      const list = await minioClient.listBuckets();\n      return !!list.find((bucket) => bucket.name === bucketName);\n    } catch (error) {\n      logger.error('Error checking bucket existence:', error);\n      return false;\n    }\n  }\n  return false;\n};\n\nconst setBucketPolicy = async (): Promise<void> => {\n  if (minioClient && bucketName) {\n    try {\n      const policy = {\n        Version: '2012-10-17',\n        Statement: [\n          {\n            Effect: 'Allow',\n            Principal: { AWS: ['*'] },\n            Action: ['s3:GetObject'],\n            Resource: [`arn:aws:s3:::${bucketName}/*`],\n          },\n        ],\n      };\n\n      await minioClient.setBucketPolicy(bucketName, JSON.stringify(policy));\n      logger.log('Bucket policy set successfully');\n    } catch (error) {\n      logger.error('Error setting bucket policy:', error);\n    }\n  }\n};\n\nconst createBucket = async (): Promise<void> => {\n  if (minioClient && bucketName) {\n    try {\n      const exists = await bucketExists();\n      if (!exists) {\n        await minioClient.makeBucket(bucketName, BUCKET.REGION || 'us-east-1');\n        await setBucketPolicy();\n        logger.log(`Bucket ${bucketName} created successfully`);\n      }\n    } catch (error) {\n      logger.error('Error creating bucket:', error);\n    }\n  }\n};\n```\n\n### File Upload Functions\n```typescript\nexport const uploadFile = async (\n  fileName: string,\n  buffer: Buffer,\n  mimetype: string,\n  metadata?: Metadata,\n): Promise<string> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    await createBucket();\n\n    const uploadMetadata = {\n      'Content-Type': mimetype,\n      ...metadata,\n    };\n\n    await minioClient.putObject(bucketName, fileName, buffer, buffer.length, uploadMetadata);\n    \n    logger.log(`File ${fileName} uploaded successfully`);\n    return fileName;\n  } catch (error) {\n    logger.error(`Error uploading file ${fileName}:`, error);\n    throw new BadRequestException(`Failed to upload file: ${error.message}`);\n  }\n};\n\nexport const uploadStream = async (\n  fileName: string,\n  stream: Readable,\n  size: number,\n  mimetype: string,\n  metadata?: Metadata,\n): Promise<string> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    await createBucket();\n\n    const uploadMetadata = {\n      'Content-Type': mimetype,\n      ...metadata,\n    };\n\n    await minioClient.putObject(bucketName, fileName, stream, size, uploadMetadata);\n    \n    logger.log(`Stream ${fileName} uploaded successfully`);\n    return fileName;\n  } catch (error) {\n    logger.error(`Error uploading stream ${fileName}:`, error);\n    throw new BadRequestException(`Failed to upload stream: ${error.message}`);\n  }\n};\n```\n\n### File Download Functions\n```typescript\nexport const getObject = async (fileName: string): Promise<Buffer> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const stream = await minioClient.getObject(bucketName, fileName);\n    const chunks: Buffer[] = [];\n\n    return new Promise((resolve, reject) => {\n      stream.on('data', (chunk) => chunks.push(chunk));\n      stream.on('end', () => resolve(Buffer.concat(chunks)));\n      stream.on('error', reject);\n    });\n  } catch (error) {\n    logger.error(`Error getting object ${fileName}:`, error);\n    throw new BadRequestException(`Failed to get object: ${error.message}`);\n  }\n};\n\nexport const getObjectUrl = async (fileName: string, expiry: number = 3600): Promise<string> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const url = await minioClient.presignedGetObject(bucketName, fileName, expiry);\n    logger.log(`Generated URL for ${fileName} with expiry ${expiry}s`);\n    return url;\n  } catch (error) {\n    logger.error(`Error generating URL for ${fileName}:`, error);\n    throw new BadRequestException(`Failed to generate URL: ${error.message}`);\n  }\n};\n\nexport const getObjectStream = async (fileName: string): Promise<Readable> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const stream = await minioClient.getObject(bucketName, fileName);\n    return stream;\n  } catch (error) {\n    logger.error(`Error getting object stream ${fileName}:`, error);\n    throw new BadRequestException(`Failed to get object stream: ${error.message}`);\n  }\n};\n```\n\n### File Management Functions\n```typescript\nexport const deleteObject = async (fileName: string): Promise<void> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    await minioClient.removeObject(bucketName, fileName);\n    logger.log(`File ${fileName} deleted successfully`);\n  } catch (error) {\n    logger.error(`Error deleting file ${fileName}:`, error);\n    throw new BadRequestException(`Failed to delete file: ${error.message}`);\n  }\n};\n\nexport const listObjects = async (prefix?: string): Promise<MinIo.BucketItem[]> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const objects: MinIo.BucketItem[] = [];\n    const stream = minioClient.listObjects(bucketName, prefix, true);\n\n    return new Promise((resolve, reject) => {\n      stream.on('data', (obj) => objects.push(obj));\n      stream.on('end', () => resolve(objects));\n      stream.on('error', reject);\n    });\n  } catch (error) {\n    logger.error('Error listing objects:', error);\n    throw new BadRequestException(`Failed to list objects: ${error.message}`);\n  }\n};\n\nexport const objectExists = async (fileName: string): Promise<boolean> => {\n  if (!minioClient || !bucketName) {\n    return false;\n  }\n\n  try {\n    await minioClient.statObject(bucketName, fileName);\n    return true;\n  } catch (error) {\n    return false;\n  }\n};\n```\n\n## Storage Controller Pattern\n\n### S3 Controller Implementation\n```typescript\nimport { InstanceDto } from '@api/dto/instance.dto';\nimport { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';\nimport { S3Service } from '@api/integrations/storage/s3/services/s3.service';\n\nexport class S3Controller {\n  constructor(private readonly s3Service: S3Service) {}\n\n  public async getMedia(instance: InstanceDto, data: MediaDto) {\n    return this.s3Service.getMedia(instance, data);\n  }\n\n  public async getMediaUrl(instance: InstanceDto, data: MediaDto) {\n    return this.s3Service.getMediaUrl(instance, data);\n  }\n\n  public async uploadMedia(instance: InstanceDto, data: UploadMediaDto) {\n    return this.s3Service.uploadMedia(instance, data);\n  }\n\n  public async deleteMedia(instance: InstanceDto, data: MediaDto) {\n    return this.s3Service.deleteMedia(instance, data);\n  }\n}\n```\n\n## Storage Router Pattern\n\n### Storage Router Structure\n```typescript\nimport { S3Router } from '@api/integrations/storage/s3/routes/s3.router';\nimport { Router } from 'express';\n\nexport class StorageRouter {\n  public readonly router: Router;\n\n  constructor(...guards: any[]) {\n    this.router = Router();\n\n    this.router.use('/s3', new S3Router(...guards).router);\n    // Add other storage providers here\n    // this.router.use('/gcs', new GCSRouter(...guards).router);\n    // this.router.use('/azure', new AzureRouter(...guards).router);\n  }\n}\n```\n\n### S3 Specific Router\n```typescript\nimport { RouterBroker } from '@api/abstract/abstract.router';\nimport { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';\nimport { s3Schema, s3UrlSchema } from '@api/integrations/storage/s3/validate/s3.schema';\nimport { HttpStatus } from '@api/routes/index.router';\nimport { s3Controller } from '@api/server.module';\nimport { RequestHandler, Router } from 'express';\n\nexport class S3Router extends RouterBroker {\n  constructor(...guards: RequestHandler[]) {\n    super();\n    this.router\n      .post(this.routerPath('getMedia'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<MediaDto>({\n          request: req,\n          schema: s3Schema,\n          ClassRef: MediaDto,\n          execute: (instance, data) => s3Controller.getMedia(instance, data),\n        });\n\n        res.status(HttpStatus.OK).json(response);\n      })\n      .post(this.routerPath('getMediaUrl'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<MediaDto>({\n          request: req,\n          schema: s3UrlSchema,\n          ClassRef: MediaDto,\n          execute: (instance, data) => s3Controller.getMediaUrl(instance, data),\n        });\n\n        res.status(HttpStatus.OK).json(response);\n      })\n      .post(this.routerPath('uploadMedia'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<UploadMediaDto>({\n          request: req,\n          schema: uploadSchema,\n          ClassRef: UploadMediaDto,\n          execute: (instance, data) => s3Controller.uploadMedia(instance, data),\n        });\n\n        res.status(HttpStatus.CREATED).json(response);\n      })\n      .delete(this.routerPath('deleteMedia'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<MediaDto>({\n          request: req,\n          schema: s3Schema,\n          ClassRef: MediaDto,\n          execute: (instance, data) => s3Controller.deleteMedia(instance, data),\n        });\n\n        res.status(HttpStatus.OK).json(response);\n      });\n  }\n\n  public readonly router: Router = Router();\n}\n```\n\n## Storage DTO Pattern\n\n### Media DTO\n```typescript\nexport class MediaDto {\n  id?: string;\n  fileName?: string;\n  type?: string;\n  mimetype?: string;\n  expiry?: number;\n}\n\nexport class UploadMediaDto {\n  fileName: string;\n  mimetype: string;\n  buffer?: Buffer;\n  base64?: string;\n  url?: string;\n  metadata?: {\n    instanceId: string;\n    messageId?: string;\n    contactId?: string;\n    [key: string]: any;\n  };\n}\n```\n\n## Storage Validation Schema\n\n### S3 Validation Schemas\n```typescript\nimport Joi from 'joi';\n\nexport const s3Schema = Joi.object({\n  id: Joi.string().optional(),\n  fileName: Joi.string().optional(),\n  type: Joi.string().optional().valid('image', 'video', 'audio', 'document'),\n  mimetype: Joi.string().optional(),\n  expiry: Joi.number().optional().min(60).max(604800).default(3600), // 1 min to 7 days\n}).min(1).required();\n\nexport const s3UrlSchema = Joi.object({\n  id: Joi.string().required(),\n  expiry: Joi.number().optional().min(60).max(604800).default(3600),\n}).required();\n\nexport const uploadSchema = Joi.object({\n  fileName: Joi.string().required().max(255),\n  mimetype: Joi.string().required(),\n  buffer: Joi.binary().optional(),\n  base64: Joi.string().base64().optional(),\n  url: Joi.string().uri().optional(),\n  metadata: Joi.object({\n    instanceId: Joi.string().required(),\n    messageId: Joi.string().optional(),\n    contactId: Joi.string().optional(),\n  }).optional(),\n}).xor('buffer', 'base64', 'url').required(); // Exactly one of these must be present\n```\n\n## Error Handling in Storage\n\n### Storage-Specific Error Handling\n```typescript\n// CORRECT - Storage-specific error handling\npublic async uploadFile(fileName: string, buffer: Buffer): Promise<string> {\n  try {\n    const result = await this.storageClient.upload(fileName, buffer);\n    return result;\n  } catch (error) {\n    this.logger.error(`Storage upload failed: ${error.message}`);\n    \n    if (error.code === 'NoSuchBucket') {\n      throw new BadRequestException('Storage bucket not found');\n    }\n    \n    if (error.code === 'AccessDenied') {\n      throw new UnauthorizedException('Storage access denied');\n    }\n    \n    if (error.code === 'EntityTooLarge') {\n      throw new BadRequestException('File too large');\n    }\n    \n    throw new InternalServerErrorException('Storage operation failed');\n  }\n}\n```\n\n## Storage Configuration Pattern\n\n### Environment Configuration\n```typescript\nexport interface S3Config {\n  ENABLE: boolean;\n  ENDPOINT: string;\n  PORT: number;\n  USE_SSL: boolean;\n  ACCESS_KEY: string;\n  SECRET_KEY: string;\n  REGION: string;\n  BUCKET: string;\n}\n\n// Usage in service\nconst s3Config = this.configService.get<S3Config>('S3');\nif (!s3Config.ENABLE) {\n  throw new BadRequestException('S3 storage is disabled');\n}\n```\n\n## Storage Testing Pattern\n\n### Storage Service Testing\n```typescript\ndescribe('S3Service', () => {\n  let service: S3Service;\n  let prismaRepository: jest.Mocked<PrismaRepository>;\n\n  beforeEach(() => {\n    service = new S3Service(prismaRepository);\n  });\n\n  describe('getMedia', () => {\n    it('should return media list', async () => {\n      const instance = { instanceId: 'test-instance' };\n      const mockMedia = [\n        { id: '1', fileName: 'test.jpg', type: 'image', mimetype: 'image/jpeg' },\n      ];\n\n      prismaRepository.media.findMany.mockResolvedValue(mockMedia);\n\n      const result = await service.getMedia(instance);\n\n      expect(result).toEqual(mockMedia);\n      expect(prismaRepository.media.findMany).toHaveBeenCalledWith({\n        where: { instanceId: 'test-instance' },\n        select: expect.objectContaining({\n          id: true,\n          fileName: true,\n          type: true,\n          mimetype: true,\n        }),\n      });\n    });\n\n    it('should throw error when no media found', async () => {\n      const instance = { instanceId: 'test-instance' };\n      prismaRepository.media.findMany.mockResolvedValue([]);\n\n      await expect(service.getMedia(instance)).rejects.toThrow(BadRequestException);\n    });\n  });\n});\n```\n\n## Storage Performance Considerations\n\n### Efficient File Handling\n```typescript\n// CORRECT - Stream-based upload for large files\npublic async uploadLargeFile(fileName: string, stream: Readable, size: number): Promise<string> {\n  const uploadStream = new Transform({\n    transform(chunk, encoding, callback) {\n      // Optional: Add compression, encryption, etc.\n      callback(null, chunk);\n    },\n  });\n\n  return new Promise((resolve, reject) => {\n    stream\n      .pipe(uploadStream)\n      .on('error', reject)\n      .on('finish', () => resolve(fileName));\n  });\n}\n\n// INCORRECT - Loading entire file into memory\npublic async uploadLargeFile(fileName: string, filePath: string): Promise<string> {\n  const buffer = fs.readFileSync(filePath); // ❌ Memory intensive for large files\n  return await this.uploadFile(fileName, buffer);\n}\n```",".cursor/rules/specialized-rules/route-rules.mdc":"---\ndescription: Router patterns for Evolution API\nglobs:\n  - \"src/api/routes/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Route Rules\n\n## Router Base Pattern\n\n### RouterBroker Extension\n```typescript\nimport { RouterBroker } from '@api/abstract/abstract.router';\nimport { RequestHandler, Router } from 'express';\nimport { HttpStatus } from './index.router';\n\nexport class ExampleRouter extends RouterBroker {\n  constructor(...guards: RequestHandler[]) {\n    super();\n    this.router\n      .get(this.routerPath('findExample'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<ExampleDto>({\n          request: req,\n          schema: null,\n          ClassRef: ExampleDto,\n          execute: (instance) => exampleController.find(instance),\n        });\n\n        return res.status(HttpStatus.OK).json(response);\n      })\n      .post(this.routerPath('createExample'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<ExampleDto>({\n          request: req,\n          schema: exampleSchema,\n          ClassRef: ExampleDto,\n          execute: (instance, data) => exampleController.create(instance, data),\n        });\n\n        return res.status(HttpStatus.CREATED).json(response);\n      });\n  }\n\n  public readonly router: Router = Router();\n}\n```\n\n## Main Router Pattern\n\n### Index Router Structure\n```typescript\nimport { Router } from 'express';\nimport { authGuard } from '@api/guards/auth.guard';\nimport { instanceExistsGuard, instanceLoggedGuard } from '@api/guards/instance.guard';\nimport Telemetry from '@api/guards/telemetry.guard';\n\nenum HttpStatus {\n  OK = 200,\n  CREATED = 201,\n  NOT_FOUND = 404,\n  FORBIDDEN = 403,\n  BAD_REQUEST = 400,\n  UNAUTHORIZED = 401,\n  INTERNAL_SERVER_ERROR = 500,\n}\n\nconst router: Router = Router();\nconst guards = [instanceExistsGuard, instanceLoggedGuard, authGuard['apikey']];\nconst telemetry = new Telemetry();\n\nrouter\n  .use((req, res, next) => telemetry.collectTelemetry(req, res, next))\n  .get('/', async (req, res) => {\n    res.status(HttpStatus.OK).json({\n      status: HttpStatus.OK,\n      message: 'Welcome to the Evolution API, it is working!',\n      version: packageJson.version,\n      clientName: process.env.DATABASE_CONNECTION_CLIENT_NAME,\n      manager: !serverConfig.DISABLE_MANAGER ? `${req.protocol}://${req.get('host')}/manager` : undefined,\n      documentation: `https://doc.evolution-api.com`,\n      whatsappWebVersion: (await fetchLatestWaWebVersion({})).version.join('.'),\n    });\n  })\n  .use('/instance', new InstanceRouter(configService, ...guards).router)\n  .use('/message', new MessageRouter(...guards).router)\n  .use('/chat', new ChatRouter(...guards).router)\n  .use('/business', new BusinessRouter(...guards).router);\n\nexport { HttpStatus, router };\n```\n\n## Data Validation Pattern\n\n### RouterBroker dataValidate Usage\n```typescript\n// CORRECT - Standard validation pattern\n.post(this.routerPath('createTemplate'), ...guards, async (req, res) => {\n  const response = await this.dataValidate<TemplateDto>({\n    request: req,\n    schema: templateSchema,\n    ClassRef: TemplateDto,\n    execute: (instance, data) => templateController.create(instance, data),\n  });\n\n  return res.status(HttpStatus.CREATED).json(response);\n})\n\n// CORRECT - No schema validation (for simple DTOs)\n.get(this.routerPath('findTemplate'), ...guards, async (req, res) => {\n  const response = await this.dataValidate<InstanceDto>({\n    request: req,\n    schema: null,\n    ClassRef: InstanceDto,\n    execute: (instance) => templateController.find(instance),\n  });\n\n  return res.status(HttpStatus.OK).json(response);\n})\n```\n\n## Error Handling in Routes\n\n### Try-Catch Pattern\n```typescript\n// CORRECT - Error handling with utility function\n.post(this.routerPath('getCatalog'), ...guards, async (req, res) => {\n  try {\n    const response = await this.dataValidate<NumberDto>({\n      request: req,\n      schema: catalogSchema,\n      ClassRef: NumberDto,\n      execute: (instance, data) => businessController.fetchCatalog(instance, data),\n    });\n\n    return res.status(HttpStatus.OK).json(response);\n  } catch (error) {\n    // Log error for debugging\n    console.error('Business catalog error:', error);\n\n    // Use utility function to create standardized error response\n    const errorResponse = createMetaErrorResponse(error, 'business_catalog');\n    return res.status(errorResponse.status).json(errorResponse);\n  }\n})\n\n// INCORRECT - Let RouterBroker handle errors (when possible)\n.post(this.routerPath('simpleOperation'), ...guards, async (req, res) => {\n  try {\n    const response = await this.dataValidate<SimpleDto>({\n      request: req,\n      schema: simpleSchema,\n      ClassRef: SimpleDto,\n      execute: (instance, data) => controller.simpleOperation(instance, data),\n    });\n\n    return res.status(HttpStatus.OK).json(response);\n  } catch (error) {\n    throw error; // ❌ Unnecessary - RouterBroker handles this\n  }\n})\n```\n\n## Route Path Pattern\n\n### routerPath Usage\n```typescript\n// CORRECT - Use routerPath for consistent naming\n.get(this.routerPath('findLabels'), ...guards, async (req, res) => {\n  // Implementation\n})\n.post(this.routerPath('handleLabel'), ...guards, async (req, res) => {\n  // Implementation\n})\n\n// INCORRECT - Hardcoded paths\n.get('/labels', ...guards, async (req, res) => { // ❌ Use routerPath\n  // Implementation\n})\n```\n\n## Guard Application Pattern\n\n### Guards Usage\n```typescript\n// CORRECT - Apply guards to protected routes\nexport class ProtectedRouter extends RouterBroker {\n  constructor(...guards: RequestHandler[]) {\n    super();\n    this.router\n      .get(this.routerPath('protectedAction'), ...guards, async (req, res) => {\n        // Protected action\n      })\n      .post(this.routerPath('anotherAction'), ...guards, async (req, res) => {\n        // Another protected action\n      });\n  }\n}\n\n// CORRECT - No guards for public routes\nexport class PublicRouter extends RouterBroker {\n  constructor() {\n    super();\n    this.router\n      .get('/health', async (req, res) => {\n        res.status(HttpStatus.OK).json({ status: 'healthy' });\n      })\n      .get('/version', async (req, res) => {\n        res.status(HttpStatus.OK).json({ version: packageJson.version });\n      });\n  }\n}\n```\n\n## Static File Serving Pattern\n\n### Static Assets Route\n```typescript\n// CORRECT - Secure static file serving\nrouter.get('/assets/*', (req, res) => {\n  const fileName = req.params[0];\n  \n  // Security: Reject paths containing traversal patterns\n  if (!fileName || fileName.includes('..') || fileName.includes('\\\\') || path.isAbsolute(fileName)) {\n    return res.status(403).send('Forbidden');\n  }\n  \n  const basePath = path.join(process.cwd(), 'manager', 'dist');\n  const assetsPath = path.join(basePath, 'assets');\n  const filePath = path.join(assetsPath, fileName);\n  \n  // Security: Ensure the resolved path is within the assets directory\n  const resolvedPath = path.resolve(filePath);\n  const resolvedAssetsPath = path.resolve(assetsPath);\n  \n  if (!resolvedPath.startsWith(resolvedAssetsPath + path.sep) && resolvedPath !== resolvedAssetsPath) {\n    return res.status(403).send('Forbidden');\n  }\n\n  if (fs.existsSync(resolvedPath)) {\n    res.set('Content-Type', mimeTypes.lookup(resolvedPath) || 'text/css');\n    res.send(fs.readFileSync(resolvedPath));\n  } else {\n    res.status(404).send('File not found');\n  }\n});\n```\n\n## Special Route Patterns\n\n### Manager Route Pattern\n```typescript\nexport class ViewsRouter extends RouterBroker {\n  public readonly router: Router;\n\n  constructor() {\n    super();\n    this.router = Router();\n\n    const basePath = path.join(process.cwd(), 'manager', 'dist');\n    const indexPath = path.join(basePath, 'index.html');\n\n    this.router.use(express.static(basePath));\n\n    this.router.get('*', (req, res) => {\n      res.sendFile(indexPath);\n    });\n  }\n}\n```\n\n### Webhook Route Pattern\n```typescript\n// CORRECT - Webhook without guards\n.post('/webhook/evolution', async (req, res) => {\n  const response = await evolutionController.receiveWebhook(req.body);\n  return res.status(HttpStatus.OK).json(response);\n})\n\n// CORRECT - Webhook with signature validation\n.post('/webhook/meta', validateWebhookSignature, async (req, res) => {\n  const response = await metaController.receiveWebhook(req.body);\n  return res.status(HttpStatus.OK).json(response);\n})\n```\n\n## Response Pattern\n\n### Standard Response Format\n```typescript\n// CORRECT - Standard success response\nreturn res.status(HttpStatus.OK).json(response);\n\n// CORRECT - Created response\nreturn res.status(HttpStatus.CREATED).json(response);\n\n// CORRECT - Custom response with additional data\nreturn res.status(HttpStatus.OK).json({\n  ...response,\n  timestamp: new Date().toISOString(),\n  instanceName: req.params.instanceName,\n});\n```\n\n## Route Organization\n\n### File Structure\n```\nsrc/api/routes/\n├── index.router.ts          # Main router with all route registrations\n├── instance.router.ts       # Instance management routes\n├── sendMessage.router.ts    # Message sending routes\n├── chat.router.ts          # Chat operations routes\n├── business.router.ts      # Business API routes\n├── group.router.ts         # Group management routes\n├── label.router.ts         # Label management routes\n├── proxy.router.ts         # Proxy configuration routes\n├── settings.router.ts      # Instance settings routes\n├── template.router.ts      # Template management routes\n├── call.router.ts          # Call operations routes\n└── view.router.ts          # Frontend views routes\n```\n\n## Route Testing Pattern\n\n### Router Testing\n```typescript\ndescribe('ExampleRouter', () => {\n  let app: express.Application;\n  let router: ExampleRouter;\n\n  beforeEach(() => {\n    app = express();\n    router = new ExampleRouter();\n    app.use('/api', router.router);\n    app.use(express.json());\n  });\n\n  describe('GET /findExample', () => {\n    it('should return example data', async () => {\n      const response = await request(app)\n        .get('/api/findExample/test-instance')\n        .set('apikey', 'test-key')\n        .expect(200);\n\n      expect(response.body).toBeDefined();\n      expect(response.body.instanceName).toBe('test-instance');\n    });\n\n    it('should return 401 without API key', async () => {\n      await request(app)\n        .get('/api/findExample/test-instance')\n        .expect(401);\n    });\n  });\n\n  describe('POST /createExample', () => {\n    it('should create example successfully', async () => {\n      const data = {\n        name: 'Test Example',\n        description: 'Test Description',\n      };\n\n      const response = await request(app)\n        .post('/api/createExample/test-instance')\n        .set('apikey', 'test-key')\n        .send(data)\n        .expect(201);\n\n      expect(response.body.name).toBe(data.name);\n    });\n\n    it('should validate required fields', async () => {\n      const data = {\n        description: 'Test Description',\n        // Missing required 'name' field\n      };\n\n      await request(app)\n        .post('/api/createExample/test-instance')\n        .set('apikey', 'test-key')\n        .send(data)\n        .expect(400);\n    });\n  });\n});\n```\n\n## Route Documentation\n\n### JSDoc for Routes\n```typescript\n/**\n * @route GET /api/template/findTemplate/:instanceName\n * @description Find template for instance\n * @param {string} instanceName - Instance name\n * @returns {TemplateDto} Template data\n * @throws {404} Template not found\n * @throws {401} Unauthorized\n */\n.get(this.routerPath('findTemplate'), ...guards, async (req, res) => {\n  // Implementation\n})\n\n/**\n * @route POST /api/template/createTemplate/:instanceName\n * @description Create new template\n * @param {string} instanceName - Instance name\n * @body {TemplateDto} Template data\n * @returns {TemplateDto} Created template\n * @throws {400} Validation error\n * @throws {401} Unauthorized\n */\n.post(this.routerPath('createTemplate'), ...guards, async (req, res) => {\n  // Implementation\n})\n```",".cursor/rules/specialized-rules/service-rules.mdc":"---\ndescription: Service layer patterns for Evolution API\nglobs:\n  - \"src/api/services/**/*.ts\"\n  - \"src/api/integrations/**/services/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Service Rules\n\n## Service Structure Pattern\n\n### Standard Service Class\n```typescript\nexport class ExampleService {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n\n  private readonly logger = new Logger('ExampleService');\n\n  public async create(instance: InstanceDto, data: ExampleDto) {\n    await this.waMonitor.waInstances[instance.instanceName].setData(data);\n    return { example: { ...instance, data } };\n  }\n\n  public async find(instance: InstanceDto): Promise<ExampleDto> {\n    try {\n      const result = await this.waMonitor.waInstances[instance.instanceName].findData();\n\n      if (Object.keys(result).length === 0) {\n        throw new Error('Data not found');\n      }\n\n      return result;\n    } catch (error) {\n      return null; // Evolution pattern - return null on error\n    }\n  }\n}\n```\n\n## Dependency Injection Pattern\n\n### Constructor Pattern\n```typescript\n// CORRECT - Evolution API pattern\nconstructor(\n  private readonly waMonitor: WAMonitoringService,\n  private readonly prismaRepository: PrismaRepository,\n  private readonly configService: ConfigService,\n) {}\n\n// INCORRECT - Don't use\nconstructor(waMonitor, prismaRepository, configService) {} // ❌ No types\n```\n\n## Logger Pattern\n\n### Standard Logger Usage\n```typescript\n// CORRECT - Evolution API pattern\nprivate readonly logger = new Logger('ServiceName');\n\n// Usage\nthis.logger.log('Operation started');\nthis.logger.error('Operation failed', error);\n\n// INCORRECT\nconsole.log('Operation started'); // ❌ Use Logger\n```\n\n## WAMonitor Integration Pattern\n\n### Instance Access Pattern\n```typescript\n// CORRECT - Standard pattern\npublic async operation(instance: InstanceDto, data: DataDto) {\n  await this.waMonitor.waInstances[instance.instanceName].performAction(data);\n  return { result: { ...instance, data } };\n}\n\n// Instance validation\nconst waInstance = this.waMonitor.waInstances[instance.instanceName];\nif (!waInstance) {\n  throw new NotFoundException('Instance not found');\n}\n```\n\n## Error Handling Pattern\n\n### Try-Catch Pattern\n```typescript\n// CORRECT - Evolution API pattern\npublic async find(instance: InstanceDto): Promise<DataDto> {\n  try {\n    const result = await this.waMonitor.waInstances[instance.instanceName].findData();\n    \n    if (Object.keys(result).length === 0) {\n      throw new Error('Data not found');\n    }\n\n    return result;\n  } catch (error) {\n    this.logger.error('Find operation failed', error);\n    return null; // Return null on error (Evolution pattern)\n  }\n}\n```\n\n## Cache Integration Pattern\n\n### Cache Service Usage\n```typescript\nexport class CacheAwareService {\n  constructor(\n    private readonly cache: CacheService,\n    private readonly chatwootCache: CacheService,\n    private readonly baileysCache: CacheService,\n  ) {}\n\n  public async getCachedData(key: string): Promise<any> {\n    const cached = await this.cache.get(key);\n    if (cached) return cached;\n\n    const data = await this.fetchFromSource(key);\n    await this.cache.set(key, data, 300); // 5 min TTL\n    return data;\n  }\n}\n```\n\n## Integration Service Patterns\n\n### Chatbot Service Base Pattern\n```typescript\nexport class ChatbotService extends BaseChatbotService<BotType, SettingsType> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(waMonitor, prismaRepository, 'ChatbotService', configService);\n  }\n\n  protected async processBot(\n    waInstance: any,\n    remoteJid: string,\n    bot: BotType,\n    session: any,\n    settings: any,\n    content: string,\n  ): Promise<void> {\n    // Implementation\n  }\n}\n```\n\n### Channel Service Pattern\n```typescript\nexport class ChannelService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n    baileysCache: CacheService,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache, baileysCache);\n  }\n\n  public readonly logger = new Logger('ChannelService');\n  public client: WASocket;\n  public readonly instance: wa.Instance = {};\n}\n```\n\n## Service Initialization Pattern\n\n### Service Registration\n```typescript\n// In server.module.ts pattern\nexport const templateService = new TemplateService(\n  waMonitor,\n  prismaRepository,\n  configService,\n);\n\nexport const settingsService = new SettingsService(waMonitor);\n```\n\n## Async Operation Patterns\n\n### Promise Handling\n```typescript\n// CORRECT - Evolution API pattern\npublic async sendMessage(instance: InstanceDto, data: MessageDto) {\n  const waInstance = this.waMonitor.waInstances[instance.instanceName];\n  return await waInstance.sendMessage(data);\n}\n\n// INCORRECT - Don't use .then()\npublic sendMessage(instance: InstanceDto, data: MessageDto) {\n  return this.waMonitor.waInstances[instance.instanceName]\n    .sendMessage(data)\n    .then(result => result); // ❌ Use async/await\n}\n```\n\n## Configuration Access Pattern\n\n### Config Service Usage\n```typescript\n// CORRECT - Evolution API pattern\nconst serverConfig = this.configService.get<HttpServer>('SERVER');\nconst authConfig = this.configService.get<Auth>('AUTHENTICATION');\nconst dbConfig = this.configService.get<Database>('DATABASE');\n\n// Type-safe configuration access\nif (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) {\n  // Chatwoot logic\n}\n```\n\n## Event Emission Pattern\n\n### EventEmitter2 Usage\n```typescript\n// CORRECT - Evolution API pattern\nthis.eventEmitter.emit(Events.INSTANCE_CREATE, {\n  instanceName: instance.name,\n  status: 'created',\n});\n\n// Chatwoot event pattern\nif (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {\n  this.chatwootService.eventWhatsapp(\n    Events.STATUS_INSTANCE,\n    { instanceName: this.instance.name },\n    {\n      instance: this.instance.name,\n      status: 'created',\n    },\n  );\n}\n```\n\n## Service Method Naming\n\n### Standard Method Names\n- `create()` - Create new resource\n- `find()` - Find single resource\n- `findAll()` - Find multiple resources\n- `update()` - Update resource\n- `delete()` - Delete resource\n- `fetch*()` - Fetch from external API\n- `send*()` - Send data/messages\n- `process*()` - Process data\n\n## Service Testing Pattern\n\n### Unit Test Structure\n```typescript\ndescribe('ExampleService', () => {\n  let service: ExampleService;\n  let waMonitor: jest.Mocked<WAMonitoringService>;\n  let prismaRepository: jest.Mocked<PrismaRepository>;\n\n  beforeEach(() => {\n    const mockWaMonitor = {\n      waInstances: {\n        'test-instance': {\n          performAction: jest.fn(),\n        },\n      },\n    };\n\n    service = new ExampleService(\n      mockWaMonitor as any,\n      prismaRepository,\n      configService,\n    );\n  });\n\n  it('should perform action successfully', async () => {\n    const instance = { instanceName: 'test-instance' };\n    const data = { test: 'data' };\n\n    const result = await service.create(instance, data);\n\n    expect(result).toBeDefined();\n    expect(waMonitor.waInstances['test-instance'].performAction).toHaveBeenCalledWith(data);\n  });\n});\n```",".cursor/rules/specialized-rules/type-rules.mdc":"---\ndescription: Type definitions and interfaces for Evolution API\nglobs:\n  - \"src/api/types/**/*.ts\"\n  - \"src/@types/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Type Rules\n\n## Namespace Pattern\n\n### WhatsApp Types Namespace\n```typescript\n/* eslint-disable @typescript-eslint/no-namespace */\nimport { JsonValue } from '@prisma/client/runtime/library';\nimport { AuthenticationState, WAConnectionState } from 'baileys';\n\nexport declare namespace wa {\n  export type QrCode = {\n    count?: number;\n    pairingCode?: string;\n    base64?: string;\n    code?: string;\n  };\n\n  export type Instance = {\n    id?: string;\n    qrcode?: QrCode;\n    pairingCode?: string;\n    authState?: { state: AuthenticationState; saveCreds: () => void };\n    name?: string;\n    wuid?: string;\n    profileName?: string;\n    profilePictureUrl?: string;\n    token?: string;\n    number?: string;\n    integration?: string;\n    businessId?: string;\n  };\n\n  export type LocalChatwoot = {\n    enabled?: boolean;\n    accountId?: string;\n    token?: string;\n    url?: string;\n    nameInbox?: string;\n    mergeBrazilContacts?: boolean;\n    importContacts?: boolean;\n    importMessages?: boolean;\n    daysLimitImportMessages?: number;\n    organization?: string;\n    logo?: string;\n  };\n\n  export type LocalProxy = {\n    enabled?: boolean;\n    host?: string;\n    port?: string;\n    protocol?: string;\n    username?: string;\n    password?: string;\n  };\n\n  export type LocalSettings = {\n    rejectCall?: boolean;\n    msgCall?: string;\n    groupsIgnore?: boolean;\n    alwaysOnline?: boolean;\n    readMessages?: boolean;\n    readStatus?: boolean;\n    syncFullHistory?: boolean;\n  };\n\n  export type LocalWebHook = {\n    enabled?: boolean;\n    url?: string;\n    events?: string[];\n    headers?: JsonValue;\n    byEvents?: boolean;\n    base64?: boolean;\n  };\n\n  export type StatusMessage = 'ERROR' | 'PENDING' | 'SERVER_ACK' | 'DELIVERY_ACK' | 'READ' | 'DELETED' | 'PLAYED';\n}\n```\n\n## Enum Definitions\n\n### Events Enum\n```typescript\nexport enum Events {\n  APPLICATION_STARTUP = 'application.startup',\n  INSTANCE_CREATE = 'instance.create',\n  INSTANCE_DELETE = 'instance.delete',\n  QRCODE_UPDATED = 'qrcode.updated',\n  CONNECTION_UPDATE = 'connection.update',\n  STATUS_INSTANCE = 'status.instance',\n  MESSAGES_SET = 'messages.set',\n  MESSAGES_UPSERT = 'messages.upsert',\n  MESSAGES_EDITED = 'messages.edited',\n  MESSAGES_UPDATE = 'messages.update',\n  MESSAGES_DELETE = 'messages.delete',\n  SEND_MESSAGE = 'send.message',\n  SEND_MESSAGE_UPDATE = 'send.message.update',\n  CONTACTS_SET = 'contacts.set',\n  CONTACTS_UPSERT = 'contacts.upsert',\n  CONTACTS_UPDATE = 'contacts.update',\n  PRESENCE_UPDATE = 'presence.update',\n  CHATS_SET = 'chats.set',\n  CHATS_UPDATE = 'chats.update',\n  CHATS_UPSERT = 'chats.upsert',\n  CHATS_DELETE = 'chats.delete',\n  GROUPS_UPSERT = 'groups.upsert',\n  GROUPS_UPDATE = 'groups.update',\n  GROUP_PARTICIPANTS_UPDATE = 'group-participants.update',\n  CALL = 'call',\n  TYPEBOT_START = 'typebot.start',\n  TYPEBOT_CHANGE_STATUS = 'typebot.change-status',\n  LABELS_EDIT = 'labels.edit',\n  LABELS_ASSOCIATION = 'labels.association',\n  CREDS_UPDATE = 'creds.update',\n  MESSAGING_HISTORY_SET = 'messaging-history.set',\n  REMOVE_INSTANCE = 'remove.instance',\n  LOGOUT_INSTANCE = 'logout.instance',\n}\n```\n\n### Integration Types\n```typescript\nexport const Integration = {\n  WHATSAPP_BUSINESS: 'WHATSAPP-BUSINESS',\n  WHATSAPP_BAILEYS: 'WHATSAPP-BAILEYS',\n  EVOLUTION: 'EVOLUTION',\n} as const;\n\nexport type IntegrationType = typeof Integration[keyof typeof Integration];\n```\n\n## Constant Arrays\n\n### Message Type Constants\n```typescript\nexport const TypeMediaMessage = [\n  'imageMessage',\n  'documentMessage',\n  'audioMessage',\n  'videoMessage',\n  'stickerMessage',\n  'ptvMessage', // Evolution API includes this\n];\n\nexport const MessageSubtype = [\n  'ephemeralMessage',\n  'documentWithCaptionMessage',\n  'viewOnceMessage',\n  'viewOnceMessageV2',\n];\n\nexport type MediaMessageType = typeof TypeMediaMessage[number];\nexport type MessageSubtypeType = typeof MessageSubtype[number];\n```\n\n## Interface Definitions\n\n### Service Interfaces\n```typescript\nexport interface ServiceInterface {\n  create(instance: InstanceDto, data: any): Promise<any>;\n  find(instance: InstanceDto): Promise<any>;\n  update?(instance: InstanceDto, data: any): Promise<any>;\n  delete?(instance: InstanceDto): Promise<any>;\n}\n\nexport interface ChannelServiceInterface extends ServiceInterface {\n  sendMessage(data: SendMessageDto): Promise<any>;\n  connectToWhatsapp(data?: any): Promise<void>;\n  receiveWebhook?(data: any): Promise<void>;\n}\n\nexport interface ChatbotServiceInterface extends ServiceInterface {\n  processMessage(\n    instanceName: string,\n    remoteJid: string,\n    message: any,\n    pushName?: string,\n  ): Promise<void>;\n}\n```\n\n## Configuration Types\n\n### Environment Configuration Types\n```typescript\nexport interface DatabaseConfig {\n  CONNECTION: {\n    URI: string;\n    DB_PREFIX_NAME: string;\n    CLIENT_NAME?: string;\n  };\n  ENABLED: boolean;\n  SAVE_DATA: {\n    INSTANCE: boolean;\n    NEW_MESSAGE: boolean;\n    MESSAGE_UPDATE: boolean;\n    CONTACTS: boolean;\n    CHATS: boolean;\n  };\n}\n\nexport interface AuthConfig {\n  TYPE: 'apikey' | 'jwt';\n  API_KEY: {\n    KEY: string;\n  };\n  JWT?: {\n    EXPIRIN_IN: number;\n    SECRET: string;\n  };\n}\n\nexport interface CacheConfig {\n  REDIS: {\n    ENABLED: boolean;\n    URI: string;\n    PREFIX_KEY: string;\n    SAVE_INSTANCES: boolean;\n  };\n  LOCAL: {\n    ENABLED: boolean;\n    TTL: number;\n  };\n}\n```\n\n## Message Types\n\n### Message Structure Types\n```typescript\nexport interface MessageContent {\n  text?: string;\n  caption?: string;\n  media?: Buffer | string;\n  mediatype?: 'image' | 'video' | 'audio' | 'document' | 'sticker';\n  fileName?: string;\n  mimetype?: string;\n}\n\nexport interface MessageOptions {\n  delay?: number;\n  presence?: 'unavailable' | 'available' | 'composing' | 'recording' | 'paused';\n  linkPreview?: boolean;\n  mentionsEveryOne?: boolean;\n  mentioned?: string[];\n  quoted?: {\n    key: {\n      remoteJid: string;\n      fromMe: boolean;\n      id: string;\n    };\n    message: any;\n  };\n}\n\nexport interface SendMessageRequest {\n  number: string;\n  content: MessageContent;\n  options?: MessageOptions;\n}\n```\n\n## Webhook Types\n\n### Webhook Payload Types\n```typescript\nexport interface WebhookPayload {\n  event: Events;\n  instance: string;\n  data: any;\n  timestamp: string;\n  server?: {\n    version: string;\n    host: string;\n  };\n}\n\nexport interface WebhookConfig {\n  enabled: boolean;\n  url: string;\n  events: Events[];\n  headers?: Record<string, string>;\n  byEvents?: boolean;\n  base64?: boolean;\n}\n```\n\n## Error Types\n\n### Custom Error Types\n```typescript\nexport interface ApiError {\n  status: number;\n  message: string;\n  error?: string;\n  details?: any;\n  timestamp: string;\n  path: string;\n}\n\nexport interface ValidationError extends ApiError {\n  status: 400;\n  validationErrors: Array<{\n    field: string;\n    message: string;\n    value?: any;\n  }>;\n}\n\nexport interface AuthenticationError extends ApiError {\n  status: 401;\n  message: 'Unauthorized' | 'Invalid API Key' | 'Token Expired';\n}\n```\n\n## Utility Types\n\n### Generic Utility Types\n```typescript\nexport type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;\n\nexport type RequiredFields<T, K extends keyof T> = T & Required<Pick<T, K>>;\n\nexport type DeepPartial<T> = {\n  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];\n};\n\nexport type NonEmptyArray<T> = [T, ...T[]];\n\nexport type StringKeys<T> = {\n  [K in keyof T]: T[K] extends string ? K : never;\n}[keyof T];\n```\n\n## Response Types\n\n### API Response Types\n```typescript\nexport interface ApiResponse<T = any> {\n  success: boolean;\n  data?: T;\n  message?: string;\n  error?: string;\n  timestamp: string;\n}\n\nexport interface PaginatedResponse<T> extends ApiResponse<T[]> {\n  pagination: {\n    page: number;\n    limit: number;\n    total: number;\n    totalPages: number;\n    hasNext: boolean;\n    hasPrev: boolean;\n  };\n}\n\nexport interface InstanceResponse extends ApiResponse {\n  instance: {\n    instanceName: string;\n    status: 'connecting' | 'open' | 'close' | 'qr';\n    qrcode?: string;\n    profileName?: string;\n    profilePicUrl?: string;\n  };\n}\n```\n\n## Integration-Specific Types\n\n### Baileys Types Extension\n```typescript\nimport { WASocket, ConnectionState, DisconnectReason } from 'baileys';\n\nexport interface BaileysInstance {\n  client: WASocket;\n  state: ConnectionState;\n  qrRetry: number;\n  authPath: string;\n}\n\nexport interface BaileysConfig {\n  qrTimeout: number;\n  maxQrRetries: number;\n  authTimeout: number;\n  reconnectInterval: number;\n}\n```\n\n### Business API Types\n```typescript\nexport interface BusinessApiConfig {\n  version: string;\n  baseUrl: string;\n  timeout: number;\n  retries: number;\n}\n\nexport interface BusinessApiMessage {\n  messaging_product: 'whatsapp';\n  to: string;\n  type: 'text' | 'image' | 'document' | 'audio' | 'video' | 'template';\n  text?: {\n    body: string;\n    preview_url?: boolean;\n  };\n  image?: {\n    link?: string;\n    id?: string;\n    caption?: string;\n  };\n  template?: {\n    name: string;\n    language: {\n      code: string;\n    };\n    components?: any[];\n  };\n}\n```\n\n## Type Guards\n\n### Type Guard Functions\n```typescript\nexport function isMediaMessage(message: any): message is MediaMessage {\n  return message && TypeMediaMessage.some(type => message[type]);\n}\n\nexport function isTextMessage(message: any): message is TextMessage {\n  return message && message.conversation;\n}\n\nexport function isValidIntegration(integration: string): integration is IntegrationType {\n  return Object.values(Integration).includes(integration as IntegrationType);\n}\n\nexport function isValidEvent(event: string): event is Events {\n  return Object.values(Events).includes(event as Events);\n}\n```\n\n## Module Augmentation\n\n### Express Request Extension\n```typescript\ndeclare global {\n  namespace Express {\n    interface Request {\n      instanceName?: string;\n      instanceData?: InstanceDto;\n      user?: {\n        id: string;\n        apiKey: string;\n      };\n    }\n  }\n}\n```\n\n## Type Documentation\n\n### JSDoc Type Documentation\n```typescript\n/**\n * WhatsApp instance configuration\n * @interface InstanceConfig\n * @property {string} name - Unique instance name\n * @property {IntegrationType} integration - Integration type\n * @property {string} [token] - API token for business integrations\n * @property {WebhookConfig} [webhook] - Webhook configuration\n * @property {ProxyConfig} [proxy] - Proxy configuration\n */\nexport interface InstanceConfig {\n  name: string;\n  integration: IntegrationType;\n  token?: string;\n  webhook?: WebhookConfig;\n  proxy?: ProxyConfig;\n}\n```",".cursor/rules/specialized-rules/util-rules.mdc":"---\ndescription: Utility functions and helpers for Evolution API\nglobs:\n  - \"src/utils/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Utility Rules\n\n## Utility Function Structure\n\n### Standard Utility Pattern\n```typescript\nimport { Logger } from '@config/logger.config';\n\nconst logger = new Logger('UtilityName');\n\nexport function utilityFunction(param: ParamType): ReturnType {\n  try {\n    // Utility logic\n    return result;\n  } catch (error) {\n    logger.error(`Utility function failed: ${error.message}`);\n    throw error;\n  }\n}\n\nexport default utilityFunction;\n```\n\n## Authentication Utilities\n\n### Multi-File Auth State Pattern\n```typescript\nimport { AuthenticationState } from 'baileys';\nimport { CacheService } from '@api/services/cache.service';\nimport fs from 'fs/promises';\nimport path from 'path';\n\nexport default async function useMultiFileAuthStatePrisma(\n  sessionId: string,\n  cache: CacheService,\n): Promise<{\n  state: AuthenticationState;\n  saveCreds: () => Promise<void>;\n}> {\n  const localFolder = path.join(INSTANCE_DIR, sessionId);\n  const localFile = (key: string) => path.join(localFolder, fixFileName(key) + '.json');\n  await fs.mkdir(localFolder, { recursive: true });\n\n  async function writeData(data: any, key: string): Promise<any> {\n    const dataString = JSON.stringify(data, BufferJSON.replacer);\n\n    if (key !== 'creds') {\n      if (process.env.CACHE_REDIS_ENABLED === 'true') {\n        return await cache.hSet(sessionId, key, data);\n      } else {\n        await fs.writeFile(localFile(key), dataString);\n        return;\n      }\n    }\n    await saveKey(sessionId, dataString);\n    return;\n  }\n\n  async function readData(key: string): Promise<any> {\n    try {\n      let rawData;\n\n      if (key !== 'creds') {\n        if (process.env.CACHE_REDIS_ENABLED === 'true') {\n          return await cache.hGet(sessionId, key);\n        } else {\n          if (!(await fileExists(localFile(key)))) return null;\n          rawData = await fs.readFile(localFile(key), { encoding: 'utf-8' });\n          return JSON.parse(rawData, BufferJSON.reviver);\n        }\n      } else {\n        rawData = await getAuthKey(sessionId);\n      }\n\n      const parsedData = JSON.parse(rawData, BufferJSON.reviver);\n      return parsedData;\n    } catch (error) {\n      return null;\n    }\n  }\n\n  async function removeData(key: string): Promise<any> {\n    try {\n      if (key !== 'creds') {\n        if (process.env.CACHE_REDIS_ENABLED === 'true') {\n          return await cache.hDelete(sessionId, key);\n        } else {\n          await fs.unlink(localFile(key));\n        }\n      } else {\n        await deleteAuthKey(sessionId);\n      }\n    } catch (error) {\n      return;\n    }\n  }\n\n  let creds = await readData('creds');\n  if (!creds) {\n    creds = initAuthCreds();\n    await writeData(creds, 'creds');\n  }\n\n  return {\n    state: {\n      creds,\n      keys: {\n        get: async (type, ids) => {\n          const data = {};\n          await Promise.all(\n            ids.map(async (id) => {\n              let value = await readData(`${type}-${id}`);\n              if (type === 'app-state-sync-key' && value) {\n                value = proto.Message.AppStateSyncKeyData.fromObject(value);\n              }\n              data[id] = value;\n            })\n          );\n          return data;\n        },\n        set: async (data) => {\n          const tasks = [];\n          for (const category in data) {\n            for (const id in data[category]) {\n              const value = data[category][id];\n              const key = `${category}-${id}`;\n              tasks.push(value ? writeData(value, key) : removeData(key));\n            }\n          }\n          await Promise.all(tasks);\n        },\n      },\n    },\n    saveCreds: () => writeData(creds, 'creds'),\n  };\n}\n```\n\n## Message Processing Utilities\n\n### Message Content Extraction\n```typescript\nexport const getConversationMessage = (msg: any): string => {\n  const types = getTypeMessage(msg);\n  const messageContent = getMessageContent(types);\n  return messageContent;\n};\n\nconst getTypeMessage = (msg: any): any => {\n  return Object.keys(msg?.message || msg || {})[0];\n};\n\nconst getMessageContent = (type: string, msg?: any): string => {\n  const typeKey = type?.replace('Message', '');\n  \n  const types = {\n    conversation: msg?.message?.conversation,\n    extendedTextMessage: msg?.message?.extendedTextMessage?.text,\n    imageMessage: msg?.message?.imageMessage?.caption || 'Image',\n    videoMessage: msg?.message?.videoMessage?.caption || 'Video',\n    audioMessage: 'Audio',\n    documentMessage: msg?.message?.documentMessage?.caption || 'Document',\n    stickerMessage: 'Sticker',\n    contactMessage: 'Contact',\n    locationMessage: 'Location',\n    liveLocationMessage: 'Live Location',\n    viewOnceMessage: 'View Once',\n    reactionMessage: 'Reaction',\n    pollCreationMessage: 'Poll',\n    pollUpdateMessage: 'Poll Update',\n  };\n\n  let result = types[typeKey] || types[type] || 'Unknown';\n  \n  if (!result || result === 'Unknown') {\n    result = JSON.stringify(msg);\n  }\n\n  return result;\n};\n```\n\n### JID Creation Utility\n```typescript\nexport const createJid = (number: string): string => {\n  if (number.includes('@')) {\n    return number;\n  }\n\n  // Remove any non-numeric characters except +\n  let cleanNumber = number.replace(/[^\\d+]/g, '');\n  \n  // Remove + if present\n  if (cleanNumber.startsWith('+')) {\n    cleanNumber = cleanNumber.substring(1);\n  }\n\n  // Add country code if missing (assuming Brazil as default)\n  if (cleanNumber.length === 11 && cleanNumber.startsWith('11')) {\n    cleanNumber = '55' + cleanNumber;\n  } else if (cleanNumber.length === 10) {\n    cleanNumber = '5511' + cleanNumber;\n  }\n\n  // Determine if it's a group or individual\n  const isGroup = cleanNumber.includes('-');\n  const domain = isGroup ? 'g.us' : 's.whatsapp.net';\n\n  return `${cleanNumber}@${domain}`;\n};\n```\n\n## Cache Utilities\n\n### WhatsApp Number Cache\n```typescript\ninterface ISaveOnWhatsappCacheParams {\n  remoteJid: string;\n  lid?: string;\n}\n\nfunction getAvailableNumbers(remoteJid: string): string[] {\n  const numbersAvailable: string[] = [];\n\n  if (remoteJid.startsWith('+')) {\n    remoteJid = remoteJid.slice(1);\n  }\n\n  const [number, domain] = remoteJid.split('@');\n\n  // Brazilian numbers\n  if (remoteJid.startsWith('55')) {\n    const numberWithDigit =\n      number.slice(4, 5) === '9' && number.length === 13 ? number : `${number.slice(0, 4)}9${number.slice(4)}`;\n    const numberWithoutDigit = number.length === 12 ? number : number.slice(0, 4) + number.slice(5);\n\n    numbersAvailable.push(numberWithDigit);\n    numbersAvailable.push(numberWithoutDigit);\n  }\n  // Mexican/Argentina numbers\n  else if (number.startsWith('52') || number.startsWith('54')) {\n    let prefix = '';\n    if (number.startsWith('52')) {\n      prefix = '1';\n    }\n    if (number.startsWith('54')) {\n      prefix = '9';\n    }\n\n    const numberWithDigit =\n      number.slice(2, 3) === prefix && number.length === 13\n        ? number\n        : `${number.slice(0, 2)}${prefix}${number.slice(2)}`;\n    const numberWithoutDigit = number.length === 12 ? number : number.slice(0, 2) + number.slice(3);\n\n    numbersAvailable.push(numberWithDigit);\n    numbersAvailable.push(numberWithoutDigit);\n  }\n  // Other countries\n  else {\n    numbersAvailable.push(remoteJid);\n  }\n\n  return numbersAvailable.map((number) => `${number}@${domain}`);\n}\n\nexport async function saveOnWhatsappCache(params: ISaveOnWhatsappCacheParams): Promise<void> {\n  const { remoteJid, lid } = params;\n  const db = configService.get<Database>('DATABASE');\n\n  if (!db.SAVE_DATA.CONTACTS) {\n    return;\n  }\n\n  try {\n    const numbersAvailable = getAvailableNumbers(remoteJid);\n    \n    const existingContact = await prismaRepository.contact.findFirst({\n      where: {\n        OR: numbersAvailable.map(number => ({ id: number })),\n      },\n    });\n\n    if (!existingContact) {\n      await prismaRepository.contact.create({\n        data: {\n          id: remoteJid,\n          pushName: '',\n          profilePicUrl: '',\n          isOnWhatsapp: true,\n          lid: lid || null,\n          createdAt: new Date(),\n          updatedAt: new Date(),\n        },\n      });\n    } else {\n      await prismaRepository.contact.update({\n        where: { id: existingContact.id },\n        data: {\n          isOnWhatsapp: true,\n          lid: lid || existingContact.lid,\n          updatedAt: new Date(),\n        },\n      });\n    }\n  } catch (error) {\n    console.error('Error saving WhatsApp cache:', error);\n  }\n}\n```\n\n## Search Utilities\n\n### Advanced Search Operators\n```typescript\nfunction normalizeString(str: string): string {\n  return str\n    .toLowerCase()\n    .normalize('NFD')\n    .replace(/[\\u0300-\\u036f]/g, '');\n}\n\nexport function advancedOperatorsSearch(data: string, query: string): boolean {\n  const normalizedData = normalizeString(data);\n  const normalizedQuery = normalizeString(query);\n\n  // Exact phrase search with quotes\n  if (normalizedQuery.startsWith('\"') && normalizedQuery.endsWith('\"')) {\n    const phrase = normalizedQuery.slice(1, -1);\n    return normalizedData.includes(phrase);\n  }\n\n  // OR operator\n  if (normalizedQuery.includes(' OR ')) {\n    const terms = normalizedQuery.split(' OR ');\n    return terms.some(term => normalizedData.includes(term.trim()));\n  }\n\n  // AND operator (default behavior)\n  if (normalizedQuery.includes(' AND ')) {\n    const terms = normalizedQuery.split(' AND ');\n    return terms.every(term => normalizedData.includes(term.trim()));\n  }\n\n  // NOT operator\n  if (normalizedQuery.startsWith('NOT ')) {\n    const term = normalizedQuery.slice(4);\n    return !normalizedData.includes(term);\n  }\n\n  // Wildcard search\n  if (normalizedQuery.includes('*')) {\n    const regex = new RegExp(normalizedQuery.replace(/\\*/g, '.*'), 'i');\n    return regex.test(normalizedData);\n  }\n\n  // Default: simple contains search\n  return normalizedData.includes(normalizedQuery);\n}\n```\n\n## Proxy Utilities\n\n### Proxy Agent Creation\n```typescript\nimport { HttpsProxyAgent } from 'https-proxy-agent';\nimport { SocksProxyAgent } from 'socks-proxy-agent';\n\ntype Proxy = {\n  host: string;\n  port: string;\n  protocol: 'http' | 'https' | 'socks4' | 'socks5';\n  username?: string;\n  password?: string;\n};\n\nfunction selectProxyAgent(proxyUrl: string): HttpsProxyAgent<string> | SocksProxyAgent {\n  const url = new URL(proxyUrl);\n  \n  if (url.protocol === 'socks4:' || url.protocol === 'socks5:') {\n    return new SocksProxyAgent(proxyUrl);\n  } else {\n    return new HttpsProxyAgent(proxyUrl);\n  }\n}\n\nexport function makeProxyAgent(proxy: Proxy): HttpsProxyAgent<string> | SocksProxyAgent | null {\n  if (!proxy.host || !proxy.port) {\n    return null;\n  }\n\n  let proxyUrl = `${proxy.protocol}://`;\n  \n  if (proxy.username && proxy.password) {\n    proxyUrl += `${proxy.username}:${proxy.password}@`;\n  }\n  \n  proxyUrl += `${proxy.host}:${proxy.port}`;\n\n  try {\n    return selectProxyAgent(proxyUrl);\n  } catch (error) {\n    console.error('Failed to create proxy agent:', error);\n    return null;\n  }\n}\n```\n\n## Telemetry Utilities\n\n### Telemetry Data Collection\n```typescript\nexport interface TelemetryData {\n  route: string;\n  apiVersion: string;\n  timestamp: Date;\n  method?: string;\n  statusCode?: number;\n  responseTime?: number;\n  userAgent?: string;\n  instanceName?: string;\n}\n\nexport const sendTelemetry = async (route: string): Promise<void> => {\n  try {\n    const telemetryData: TelemetryData = {\n      route,\n      apiVersion: packageJson.version,\n      timestamp: new Date(),\n    };\n\n    // Only send telemetry if enabled\n    if (process.env.DISABLE_TELEMETRY === 'true') {\n      return;\n    }\n\n    // Send to telemetry service (implement as needed)\n    await axios.post('https://telemetry.evolution-api.com/collect', telemetryData, {\n      timeout: 5000,\n    });\n  } catch (error) {\n    // Silently fail - don't affect main application\n    console.debug('Telemetry failed:', error.message);\n  }\n};\n```\n\n## Internationalization Utilities\n\n### i18n Setup\n```typescript\nimport { ConfigService, Language } from '@config/env.config';\nimport fs from 'fs';\nimport i18next from 'i18next';\nimport path from 'path';\n\nconst __dirname = path.resolve(process.cwd(), 'src', 'utils');\nconst languages = ['en', 'pt-BR', 'es'];\nconst translationsPath = path.join(__dirname, 'translations');\nconst configService: ConfigService = new ConfigService();\n\nconst resources: any = {};\n\nlanguages.forEach((language) => {\n  const languagePath = path.join(translationsPath, `${language}.json`);\n  if (fs.existsSync(languagePath)) {\n    const translationContent = fs.readFileSync(languagePath, 'utf8');\n    resources[language] = {\n      translation: JSON.parse(translationContent),\n    };\n  }\n});\n\ni18next.init({\n  resources,\n  fallbackLng: 'en',\n  lng: configService.get<Language>('LANGUAGE') || 'pt-BR',\n  interpolation: {\n    escapeValue: false,\n  },\n});\n\nexport const t = i18next.t.bind(i18next);\nexport default i18next;\n```\n\n## Bot Trigger Utilities\n\n### Bot Trigger Matching\n```typescript\nimport { TriggerOperator, TriggerType } from '@prisma/client';\n\nexport function findBotByTrigger(\n  bots: any[],\n  content: string,\n  remoteJid: string,\n): any | null {\n  for (const bot of bots) {\n    if (!bot.enabled) continue;\n    \n    // Check ignore list\n    if (bot.ignoreJids && bot.ignoreJids.includes(remoteJid)) {\n      continue;\n    }\n\n    // Check trigger\n    if (matchesTrigger(content, bot.triggerType, bot.triggerOperator, bot.triggerValue)) {\n      return bot;\n    }\n  }\n\n  return null;\n}\n\nfunction matchesTrigger(\n  content: string,\n  triggerType: TriggerType,\n  triggerOperator: TriggerOperator,\n  triggerValue: string,\n): boolean {\n  const normalizedContent = content.toLowerCase().trim();\n  const normalizedValue = triggerValue.toLowerCase().trim();\n\n  switch (triggerType) {\n    case TriggerType.ALL:\n      return true;\n\n    case TriggerType.KEYWORD:\n      return matchesKeyword(normalizedContent, triggerOperator, normalizedValue);\n\n    case TriggerType.REGEX:\n      try {\n        const regex = new RegExp(triggerValue, 'i');\n        return regex.test(content);\n      } catch {\n        return false;\n      }\n\n    default:\n      return false;\n  }\n}\n\nfunction matchesKeyword(\n  content: string,\n  operator: TriggerOperator,\n  value: string,\n): boolean {\n  switch (operator) {\n    case TriggerOperator.EQUALS:\n      return content === value;\n    case TriggerOperator.CONTAINS:\n      return content.includes(value);\n    case TriggerOperator.STARTS_WITH:\n      return content.startsWith(value);\n    case TriggerOperator.ENDS_WITH:\n      return content.endsWith(value);\n    default:\n      return false;\n  }\n}\n```\n\n## Server Utilities\n\n### Server Status Check\n```typescript\nexport class ServerUP {\n  private static instance: ServerUP;\n  private isServerUp: boolean = false;\n\n  private constructor() {}\n\n  public static getInstance(): ServerUP {\n    if (!ServerUP.instance) {\n      ServerUP.instance = new ServerUP();\n    }\n    return ServerUP.instance;\n  }\n\n  public setServerStatus(status: boolean): void {\n    this.isServerUp = status;\n  }\n\n  public getServerStatus(): boolean {\n    return this.isServerUp;\n  }\n\n  public async waitForServer(timeout: number = 30000): Promise<boolean> {\n    const startTime = Date.now();\n    \n    while (!this.isServerUp && (Date.now() - startTime) < timeout) {\n      await new Promise(resolve => setTimeout(resolve, 100));\n    }\n    \n    return this.isServerUp;\n  }\n}\n```\n\n## Error Response Utilities\n\n### Standardized Error Responses\n```typescript\nexport function createMetaErrorResponse(error: any, context: string) {\n  const timestamp = new Date().toISOString();\n  \n  if (error.response?.data) {\n    return {\n      status: error.response.status || 500,\n      error: {\n        message: error.response.data.error?.message || 'External API error',\n        type: error.response.data.error?.type || 'api_error',\n        code: error.response.data.error?.code || 'unknown_error',\n        context,\n        timestamp,\n      },\n    };\n  }\n\n  return {\n    status: 500,\n    error: {\n      message: error.message || 'Internal server error',\n      type: 'internal_error',\n      code: 'server_error',\n      context,\n      timestamp,\n    },\n  };\n}\n\nexport function createValidationErrorResponse(errors: any[], context: string) {\n  return {\n    status: 400,\n    error: {\n      message: 'Validation failed',\n      type: 'validation_error',\n      code: 'invalid_input',\n      context,\n      details: errors,\n      timestamp: new Date().toISOString(),\n    },\n  };\n}\n```",".cursor/rules/specialized-rules/validate-rules.mdc":"---\ndescription: Validation schemas and patterns for Evolution API\nglobs:\n  - \"src/validate/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Validation Rules\n\n## Validation Schema Structure\n\n### JSONSchema7 Pattern (Evolution API Standard)\n```typescript\nimport { JSONSchema7 } from 'json-schema';\nimport { v4 } from 'uuid';\n\nconst isNotEmpty = (...fields: string[]) => {\n  const properties = {};\n  fields.forEach((field) => {\n    properties[field] = {\n      if: { properties: { [field]: { type: 'string' } } },\n      then: { properties: { [field]: { minLength: 1 } } },\n    };\n  });\n\n  return {\n    allOf: Object.values(properties),\n  };\n};\n\nexport const exampleSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    name: { type: 'string' },\n    description: { type: 'string' },\n    enabled: { type: 'boolean' },\n    settings: {\n      type: 'object',\n      properties: {\n        timeout: { type: 'number', minimum: 1000, maximum: 60000 },\n        retries: { type: 'number', minimum: 0, maximum: 5 },\n      },\n    },\n    tags: {\n      type: 'array',\n      items: { type: 'string' },\n    },\n  },\n  required: ['name', 'enabled'],\n  ...isNotEmpty('name'),\n};\n```\n\n## Message Validation Schemas\n\n### Send Message Validation\n```typescript\nconst numberDefinition = {\n  type: 'string',\n  pattern: '^\\\\d+[\\\\.@\\\\w-]+',\n  description: 'Invalid number',\n};\n\nexport const sendTextSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    number: numberDefinition,\n    text: { type: 'string', minLength: 1, maxLength: 4096 },\n    delay: { type: 'number', minimum: 0, maximum: 60000 },\n    linkPreview: { type: 'boolean' },\n    mentionsEveryOne: { type: 'boolean' },\n    mentioned: {\n      type: 'array',\n      items: { type: 'string' },\n    },\n  },\n  required: ['number', 'text'],\n  ...isNotEmpty('number', 'text'),\n};\n\nexport const sendMediaSchema = Joi.object({\n  number: Joi.string().required().pattern(/^\\d+$/),\n  mediatype: Joi.string().required().valid('image', 'video', 'audio', 'document'),\n  media: Joi.alternatives().try(\n    Joi.string().uri(),\n    Joi.string().base64(),\n  ).required(),\n  caption: Joi.string().optional().max(1024),\n  fileName: Joi.string().optional().max(255),\n  delay: Joi.number().optional().min(0).max(60000),\n}).required();\n\nexport const sendButtonsSchema = Joi.object({\n  number: Joi.string().required().pattern(/^\\d+$/),\n  title: Joi.string().required().max(1024),\n  description: Joi.string().optional().max(1024),\n  footer: Joi.string().optional().max(60),\n  buttons: Joi.array().items(\n    Joi.object({\n      type: Joi.string().required().valid('replyButton', 'urlButton', 'callButton'),\n      displayText: Joi.string().required().max(20),\n      id: Joi.string().when('type', {\n        is: 'replyButton',\n        then: Joi.required().max(256),\n        otherwise: Joi.optional(),\n      }),\n      url: Joi.string().when('type', {\n        is: 'urlButton',\n        then: Joi.required().uri(),\n        otherwise: Joi.optional(),\n      }),\n      phoneNumber: Joi.string().when('type', {\n        is: 'callButton',\n        then: Joi.required().pattern(/^\\+?\\d+$/),\n        otherwise: Joi.optional(),\n      }),\n    })\n  ).min(1).max(3).required(),\n}).required();\n```\n\n## Instance Validation Schemas\n\n### Instance Creation Validation\n```typescript\nexport const instanceSchema = Joi.object({\n  instanceName: Joi.string().required().min(1).max(100).pattern(/^[a-zA-Z0-9_-]+$/),\n  integration: Joi.string().required().valid('WHATSAPP-BAILEYS', 'WHATSAPP-BUSINESS', 'EVOLUTION'),\n  token: Joi.string().when('integration', {\n    is: Joi.valid('WHATSAPP-BUSINESS', 'EVOLUTION'),\n    then: Joi.required().min(10),\n    otherwise: Joi.optional(),\n  }),\n  qrcode: Joi.boolean().optional().default(false),\n  number: Joi.string().optional().pattern(/^\\d+$/),\n  businessId: Joi.string().when('integration', {\n    is: 'WHATSAPP-BUSINESS',\n    then: Joi.required(),\n    otherwise: Joi.optional(),\n  }),\n}).required();\n\nexport const settingsSchema = Joi.object({\n  rejectCall: Joi.boolean().optional(),\n  msgCall: Joi.string().optional().max(500),\n  groupsIgnore: Joi.boolean().optional(),\n  alwaysOnline: Joi.boolean().optional(),\n  readMessages: Joi.boolean().optional(),\n  readStatus: Joi.boolean().optional(),\n  syncFullHistory: Joi.boolean().optional(),\n  wavoipToken: Joi.string().optional(),\n}).optional();\n\nexport const proxySchema = Joi.object({\n  host: Joi.string().required().hostname(),\n  port: Joi.string().required().pattern(/^\\d+$/).custom((value) => {\n    const port = parseInt(value);\n    if (port < 1 || port > 65535) {\n      throw new Error('Port must be between 1 and 65535');\n    }\n    return value;\n  }),\n  protocol: Joi.string().required().valid('http', 'https', 'socks4', 'socks5'),\n  username: Joi.string().optional(),\n  password: Joi.string().optional(),\n}).optional();\n```\n\n## Webhook Validation Schemas\n\n### Webhook Configuration Validation\n```typescript\nexport const webhookSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  url: Joi.string().when('enabled', {\n    is: true,\n    then: Joi.required().uri({ scheme: ['http', 'https'] }),\n    otherwise: Joi.optional(),\n  }),\n  events: Joi.array().items(\n    Joi.string().valid(\n      'APPLICATION_STARTUP',\n      'INSTANCE_CREATE',\n      'INSTANCE_DELETE',\n      'QRCODE_UPDATED',\n      'CONNECTION_UPDATE',\n      'STATUS_INSTANCE',\n      'MESSAGES_SET',\n      'MESSAGES_UPSERT',\n      'MESSAGES_UPDATE',\n      'MESSAGES_DELETE',\n      'CONTACTS_SET',\n      'CONTACTS_UPSERT',\n      'CONTACTS_UPDATE',\n      'CHATS_SET',\n      'CHATS_UPDATE',\n      'CHATS_UPSERT',\n      'CHATS_DELETE',\n      'GROUPS_UPSERT',\n      'GROUPS_UPDATE',\n      'GROUP_PARTICIPANTS_UPDATE',\n      'CALL'\n    )\n  ).min(1).when('enabled', {\n    is: true,\n    then: Joi.required(),\n    otherwise: Joi.optional(),\n  }),\n  headers: Joi.object().pattern(\n    Joi.string(),\n    Joi.string()\n  ).optional(),\n  byEvents: Joi.boolean().optional().default(false),\n  base64: Joi.boolean().optional().default(false),\n}).required();\n```\n\n## Chatbot Validation Schemas\n\n### Base Chatbot Validation\n```typescript\nexport const baseChatbotSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  description: Joi.string().required().min(1).max(500),\n  expire: Joi.number().optional().min(0).max(86400), // 24 hours in seconds\n  keywordFinish: Joi.string().optional().max(100),\n  delayMessage: Joi.number().optional().min(0).max(10000),\n  unknownMessage: Joi.string().optional().max(1000),\n  listeningFromMe: Joi.boolean().optional().default(false),\n  stopBotFromMe: Joi.boolean().optional().default(false),\n  keepOpen: Joi.boolean().optional().default(false),\n  debounceTime: Joi.number().optional().min(0).max(60000),\n  triggerType: Joi.string().required().valid('ALL', 'KEYWORD', 'REGEX'),\n  triggerOperator: Joi.string().when('triggerType', {\n    is: 'KEYWORD',\n    then: Joi.required().valid('EQUALS', 'CONTAINS', 'STARTS_WITH', 'ENDS_WITH'),\n    otherwise: Joi.optional(),\n  }),\n  triggerValue: Joi.string().when('triggerType', {\n    is: Joi.valid('KEYWORD', 'REGEX'),\n    then: Joi.required().min(1).max(500),\n    otherwise: Joi.optional(),\n  }),\n  ignoreJids: Joi.array().items(Joi.string()).optional(),\n  splitMessages: Joi.boolean().optional().default(false),\n  timePerChar: Joi.number().optional().min(10).max(1000).default(100),\n}).required();\n\nexport const typebotSchema = baseChatbotSchema.keys({\n  url: Joi.string().required().uri({ scheme: ['http', 'https'] }),\n  typebot: Joi.string().required().min(1).max(100),\n  apiVersion: Joi.string().optional().valid('v1', 'v2').default('v1'),\n}).required();\n\nexport const openaiSchema = baseChatbotSchema.keys({\n  apiKey: Joi.string().required().min(10),\n  model: Joi.string().optional().valid(\n    'gpt-3.5-turbo',\n    'gpt-3.5-turbo-16k',\n    'gpt-4',\n    'gpt-4-32k',\n    'gpt-4-turbo-preview'\n  ).default('gpt-3.5-turbo'),\n  systemMessage: Joi.string().optional().max(2000),\n  maxTokens: Joi.number().optional().min(1).max(4000).default(1000),\n  temperature: Joi.number().optional().min(0).max(2).default(0.7),\n}).required();\n```\n\n## Business API Validation Schemas\n\n### Template Validation\n```typescript\nexport const templateSchema = Joi.object({\n  name: Joi.string().required().min(1).max(512).pattern(/^[a-z0-9_]+$/),\n  category: Joi.string().required().valid('MARKETING', 'UTILITY', 'AUTHENTICATION'),\n  allowCategoryChange: Joi.boolean().required(),\n  language: Joi.string().required().pattern(/^[a-z]{2}_[A-Z]{2}$/), // e.g., pt_BR, en_US\n  components: Joi.array().items(\n    Joi.object({\n      type: Joi.string().required().valid('HEADER', 'BODY', 'FOOTER', 'BUTTONS'),\n      format: Joi.string().when('type', {\n        is: 'HEADER',\n        then: Joi.valid('TEXT', 'IMAGE', 'VIDEO', 'DOCUMENT'),\n        otherwise: Joi.optional(),\n      }),\n      text: Joi.string().when('type', {\n        is: Joi.valid('HEADER', 'BODY', 'FOOTER'),\n        then: Joi.required().min(1).max(1024),\n        otherwise: Joi.optional(),\n      }),\n      buttons: Joi.array().when('type', {\n        is: 'BUTTONS',\n        then: Joi.items(\n          Joi.object({\n            type: Joi.string().required().valid('QUICK_REPLY', 'URL', 'PHONE_NUMBER'),\n            text: Joi.string().required().min(1).max(25),\n            url: Joi.string().when('type', {\n              is: 'URL',\n              then: Joi.required().uri(),\n              otherwise: Joi.optional(),\n            }),\n            phone_number: Joi.string().when('type', {\n              is: 'PHONE_NUMBER',\n              then: Joi.required().pattern(/^\\+?\\d+$/),\n              otherwise: Joi.optional(),\n            }),\n          })\n        ).min(1).max(10),\n        otherwise: Joi.optional(),\n      }),\n    })\n  ).min(1).required(),\n  webhookUrl: Joi.string().optional().uri({ scheme: ['http', 'https'] }),\n}).required();\n\nexport const catalogSchema = Joi.object({\n  number: Joi.string().optional().pattern(/^\\d+$/),\n  limit: Joi.number().optional().min(1).max(1000).default(10),\n  cursor: Joi.string().optional(),\n}).optional();\n```\n\n## Group Validation Schemas\n\n### Group Management Validation\n```typescript\nexport const createGroupSchema = Joi.object({\n  subject: Joi.string().required().min(1).max(100),\n  description: Joi.string().optional().max(500),\n  participants: Joi.array().items(\n    Joi.string().pattern(/^\\d+$/)\n  ).min(1).max(256).required(),\n  promoteParticipants: Joi.boolean().optional().default(false),\n}).required();\n\nexport const updateGroupSchema = Joi.object({\n  subject: Joi.string().optional().min(1).max(100),\n  description: Joi.string().optional().max(500),\n}).min(1).required();\n\nexport const groupParticipantsSchema = Joi.object({\n  participants: Joi.array().items(\n    Joi.string().pattern(/^\\d+$/)\n  ).min(1).max(50).required(),\n  action: Joi.string().required().valid('add', 'remove', 'promote', 'demote'),\n}).required();\n```\n\n## Label Validation Schemas\n\n### Label Management Validation\n```typescript\nexport const labelSchema = Joi.object({\n  name: Joi.string().required().min(1).max(100),\n  color: Joi.string().required().pattern(/^#[0-9A-Fa-f]{6}$/), // Hex color\n  predefinedId: Joi.string().optional(),\n}).required();\n\nexport const handleLabelSchema = Joi.object({\n  number: Joi.string().required().pattern(/^\\d+$/),\n  labelId: Joi.string().required(),\n  action: Joi.string().required().valid('add', 'remove'),\n}).required();\n```\n\n## Custom Validation Functions\n\n### Phone Number Validation\n```typescript\nexport function validatePhoneNumber(number: string): boolean {\n  // Remove any non-digit characters\n  const cleaned = number.replace(/\\D/g, '');\n  \n  // Check minimum and maximum length\n  if (cleaned.length < 10 || cleaned.length > 15) {\n    return false;\n  }\n  \n  // Check for valid country codes (basic validation)\n  const validCountryCodes = ['1', '7', '20', '27', '30', '31', '32', '33', '34', '36', '39', '40', '41', '43', '44', '45', '46', '47', '48', '49', '51', '52', '53', '54', '55', '56', '57', '58', '60', '61', '62', '63', '64', '65', '66', '81', '82', '84', '86', '90', '91', '92', '93', '94', '95', '98'];\n  \n  // Check if starts with valid country code\n  const startsWithValidCode = validCountryCodes.some(code => cleaned.startsWith(code));\n  \n  return startsWithValidCode;\n}\n\nexport const phoneNumberValidator = Joi.string().custom((value, helpers) => {\n  if (!validatePhoneNumber(value)) {\n    return helpers.error('any.invalid');\n  }\n  return value;\n}, 'Phone number validation');\n```\n\n### Base64 Validation\n```typescript\nexport function validateBase64(base64: string): boolean {\n  try {\n    // Check if it's a valid base64 string\n    const decoded = Buffer.from(base64, 'base64').toString('base64');\n    return decoded === base64;\n  } catch {\n    return false;\n  }\n}\n\nexport const base64Validator = Joi.string().custom((value, helpers) => {\n  if (!validateBase64(value)) {\n    return helpers.error('any.invalid');\n  }\n  return value;\n}, 'Base64 validation');\n```\n\n### URL Validation with Protocol Check\n```typescript\nexport function validateWebhookUrl(url: string): boolean {\n  try {\n    const parsed = new URL(url);\n    return ['http:', 'https:'].includes(parsed.protocol);\n  } catch {\n    return false;\n  }\n}\n\nexport const webhookUrlValidator = Joi.string().custom((value, helpers) => {\n  if (!validateWebhookUrl(value)) {\n    return helpers.error('any.invalid');\n  }\n  return value;\n}, 'Webhook URL validation');\n```\n\n## Validation Error Handling\n\n### Error Message Customization\n```typescript\nexport const validationMessages = {\n  'any.required': 'O campo {#label} é obrigatório',\n  'string.empty': 'O campo {#label} não pode estar vazio',\n  'string.min': 'O campo {#label} deve ter pelo menos {#limit} caracteres',\n  'string.max': 'O campo {#label} deve ter no máximo {#limit} caracteres',\n  'string.pattern.base': 'O campo {#label} possui formato inválido',\n  'number.min': 'O campo {#label} deve ser maior ou igual a {#limit}',\n  'number.max': 'O campo {#label} deve ser menor ou igual a {#limit}',\n  'array.min': 'O campo {#label} deve ter pelo menos {#limit} itens',\n  'array.max': 'O campo {#label} deve ter no máximo {#limit} itens',\n  'any.only': 'O campo {#label} deve ser um dos valores: {#valids}',\n};\n\nexport function formatValidationError(error: Joi.ValidationError): any {\n  return {\n    message: 'Dados de entrada inválidos',\n    details: error.details.map(detail => ({\n      field: detail.path.join('.'),\n      message: detail.message,\n      value: detail.context?.value,\n    })),\n  };\n}\n```\n\n## Schema Composition\n\n### Reusable Schema Components\n```typescript\nexport const commonFields = {\n  instanceName: Joi.string().required().min(1).max(100).pattern(/^[a-zA-Z0-9_-]+$/),\n  number: phoneNumberValidator.required(),\n  delay: Joi.number().optional().min(0).max(60000),\n  enabled: Joi.boolean().optional().default(true),\n};\n\nexport const mediaFields = {\n  mediatype: Joi.string().required().valid('image', 'video', 'audio', 'document'),\n  media: Joi.alternatives().try(\n    Joi.string().uri(),\n    base64Validator,\n  ).required(),\n  caption: Joi.string().optional().max(1024),\n  fileName: Joi.string().optional().max(255),\n};\n\n// Compose schemas using common fields\nexport const quickMessageSchema = Joi.object({\n  ...commonFields,\n  text: Joi.string().required().min(1).max(4096),\n}).required();\n\nexport const quickMediaSchema = Joi.object({\n  ...commonFields,\n  ...mediaFields,\n}).required();\n```","AGENTS.md":"# Evolution API - AI Agent Guidelines\n\nThis document provides comprehensive guidelines for AI agents (Claude, GPT, Cursor, etc.) working with the Evolution API codebase.\n\n## Project Overview\n\n**Evolution API** is a production-ready, multi-tenant WhatsApp API platform built with Node.js, TypeScript, and Express.js. It supports multiple WhatsApp providers and extensive integrations with chatbots, CRM systems, and messaging platforms.\n\n## Project Structure & Module Organization\n\n### Core Directories\n- **`src/`** – TypeScript source code with modular architecture\n  - `api/controllers/` – HTTP route handlers (thin layer)\n  - `api/services/` – Business logic (core functionality)\n  - `api/routes/` – Express route definitions (RouterBroker pattern)\n  - `api/integrations/` – External service integrations\n    - `channel/` – WhatsApp providers (Baileys, Business API, Evolution)\n    - `chatbot/` – AI/Bot integrations (OpenAI, Dify, Typebot, Chatwoot)\n    - `event/` – Event systems (WebSocket, RabbitMQ, SQS, NATS, Pusher)\n    - `storage/` – File storage (S3, MinIO)\n  - `dto/` – Data Transfer Objects (simple classes, no decorators)\n  - `guards/` – Authentication/authorization middleware\n  - `types/` – TypeScript type definitions\n  - `repository/` – Data access layer (Prisma)\n- **`prisma/`** – Database schemas and migrations\n  - `postgresql-schema.prisma` / `mysql-schema.prisma` – Provider-specific schemas\n  - `postgresql-migrations/` / `mysql-migrations/` – Provider-specific migrations\n- **`config/`** – Environment and application configuration\n- **`utils/`** – Shared utilities and helper functions\n- **`validate/`** – JSONSchema7 validation schemas\n- **`exceptions/`** – Custom HTTP exception classes\n- **`cache/`** – Redis and local cache implementations\n\n### Build & Deployment\n- **`dist/`** – Build output (do not edit directly)\n- **`public/`** – Static assets and media files\n- **`Docker*`**, **`docker-compose*.yaml`** – Containerization and local development stack\n\n## Build, Test, and Development Commands\n\n### Development Workflow\n```bash\n# Development server with hot reload\nnpm run dev:server\n\n# Direct execution for testing\nnpm start\n\n# Production build and run\nnpm run build\nnpm run start:prod\n```\n\n### Code Quality\n```bash\n# Linting and formatting\nnpm run lint        # ESLint with auto-fix\nnpm run lint:check  # ESLint check only\n\n# Commit with conventional commits\nnpm run commit      # Interactive commit with Commitizen\n```\n\n### Database Management\n```bash\n# Set database provider first (CRITICAL)\nexport DATABASE_PROVIDER=postgresql  # or mysql\n\n# Generate Prisma client\nnpm run db:generate\n\n# Development migrations (with provider sync)\nnpm run db:migrate:dev      # Unix/Mac\nnpm run db:migrate:dev:win  # Windows\n\n# Production deployment\nnpm run db:deploy      # Unix/Mac\nnpm run db:deploy:win  # Windows\n\n# Database tools\nnpm run db:studio      # Open Prisma Studio\n```\n\n### Docker Development\n```bash\n# Start local services (Redis, PostgreSQL, etc.)\ndocker-compose up -d\n\n# Full development stack\ndocker-compose -f docker-compose.dev.yaml up -d\n```\n\n## Coding Standards & Architecture Patterns\n\n### Code Style (Enforced by ESLint + Prettier)\n- **TypeScript strict mode** with full type coverage\n- **2-space indentation**, single quotes, trailing commas\n- **120-character line limit**\n- **Import order** via `simple-import-sort`\n- **File naming**: `feature.kind.ts` (e.g., `whatsapp.baileys.service.ts`)\n- **Naming conventions**:\n  - Classes: `PascalCase`\n  - Functions/variables: `camelCase`\n  - Constants: `UPPER_SNAKE_CASE`\n  - Files: `kebab-case.type.ts`\n\n### Architecture Patterns\n\n#### Service Layer Pattern\n```typescript\nexport class ExampleService {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n  \n  private readonly logger = new Logger('ExampleService');\n  \n  public async create(instance: InstanceDto, data: ExampleDto) {\n    // Business logic here\n    return { example: { ...instance, data } };\n  }\n  \n  public async find(instance: InstanceDto): Promise<ExampleDto | null> {\n    try {\n      const result = await this.waMonitor.waInstances[instance.instanceName].findData();\n      return result || null; // Return null on not found (Evolution pattern)\n    } catch (error) {\n      this.logger.error('Error finding data:', error);\n      return null; // Return null on error (Evolution pattern)\n    }\n  }\n}\n```\n\n#### Controller Pattern (Thin Layer)\n```typescript\nexport class ExampleController {\n  constructor(private readonly exampleService: ExampleService) {}\n  \n  public async createExample(instance: InstanceDto, data: ExampleDto) {\n    return this.exampleService.create(instance, data);\n  }\n}\n```\n\n#### RouterBroker Pattern\n```typescript\nexport class ExampleRouter extends RouterBroker {\n  constructor(...guards: any[]) {\n    super();\n    this.router.post(this.routerPath('create'), ...guards, async (req, res) => {\n      const response = await this.dataValidate<ExampleDto>({\n        request: req,\n        schema: exampleSchema, // JSONSchema7\n        ClassRef: ExampleDto,\n        execute: (instance, data) => controller.createExample(instance, data),\n      });\n      res.status(201).json(response);\n    });\n  }\n}\n```\n\n#### DTO Pattern (Simple Classes)\n```typescript\n// CORRECT - Evolution API pattern (no decorators)\nexport class ExampleDto {\n  name: string;\n  description?: string;\n  enabled: boolean;\n}\n\n// INCORRECT - Don't use class-validator decorators\nexport class BadExampleDto {\n  @IsString() // ❌ Evolution API doesn't use decorators\n  name: string;\n}\n```\n\n#### Validation Pattern (JSONSchema7)\n```typescript\nimport { JSONSchema7 } from 'json-schema';\nimport { v4 } from 'uuid';\n\nexport const exampleSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    name: { type: 'string' },\n    description: { type: 'string' },\n    enabled: { type: 'boolean' },\n  },\n  required: ['name', 'enabled'],\n};\n```\n\n## Multi-Tenant Architecture\n\n### Instance Isolation\n- **CRITICAL**: All operations must be scoped by `instanceName` or `instanceId`\n- **Database queries**: Always include `where: { instanceId: ... }`\n- **Authentication**: Validate instance ownership before operations\n- **Data isolation**: Complete separation between tenant instances\n\n### WhatsApp Instance Management\n```typescript\n// Access instance via WAMonitoringService\nconst waInstance = this.waMonitor.waInstances[instance.instanceName];\nif (!waInstance) {\n  throw new NotFoundException(`Instance ${instance.instanceName} not found`);\n}\n```\n\n## Database Patterns\n\n### Multi-Provider Support\n- **PostgreSQL**: Uses `@db.Integer`, `@db.JsonB`, `@default(now())`\n- **MySQL**: Uses `@db.Int`, `@db.Json`, `@default(now())`\n- **Environment**: Set `DATABASE_PROVIDER=postgresql` or `mysql`\n- **Migrations**: Provider-specific folders auto-selected\n\n### Prisma Repository Pattern\n```typescript\n// Always use PrismaRepository for database operations\nconst result = await this.prismaRepository.instance.findUnique({\n  where: { name: instanceName },\n});\n```\n\n## Integration Patterns\n\n### Channel Integration (WhatsApp Providers)\n- **Baileys**: WhatsApp Web with QR code authentication\n- **Business API**: Official Meta WhatsApp Business API  \n- **Evolution API**: Custom WhatsApp integration\n- **Pattern**: Extend base channel service classes\n\n### Chatbot Integration\n- **Base classes**: Extend `BaseChatbotService` and `BaseChatbotController`\n- **Trigger system**: Support keyword, regex, and advanced triggers\n- **Session management**: Handle conversation state per user\n- **Available integrations**: EvolutionBot, OpenAI, Dify, Typebot, Chatwoot, Flowise, N8N, EvoAI\n\n### Event Integration\n- **Internal events**: EventEmitter2 for application events\n- **External events**: WebSocket, RabbitMQ, SQS, NATS, Pusher\n- **Webhook delivery**: Reliable delivery with retry logic\n\n## Testing Guidelines\n\n### Current State\n- **No formal test suite** currently implemented\n- **Manual testing** is the primary approach\n- **Integration testing** in development environment\n\n### Testing Strategy\n```typescript\n// Place tests in test/ directory as *.test.ts\n// Run: npm test (watches test/all.test.ts)\n\ndescribe('ExampleService', () => {\n  it('should create example', async () => {\n    // Mock external dependencies\n    // Test business logic\n    // Assert expected behavior\n  });\n});\n```\n\n### Recommended Approach\n- Focus on **critical business logic** in services\n- **Mock external dependencies** (WhatsApp APIs, databases)\n- **Integration tests** for API endpoints\n- **Manual testing** for WhatsApp connection flows\n\n## Commit & Pull Request Guidelines\n\n### Conventional Commits (Enforced by commitlint)\n```bash\n# Use interactive commit tool\nnpm run commit\n\n# Commit format: type(scope): subject (max 100 chars)\n# Types: feat, fix, docs, style, refactor, perf, test, chore, ci, build, revert, security\n```\n\n### Examples\n- `feat(api): add WhatsApp message status endpoint`\n- `fix(baileys): resolve connection timeout issue`\n- `docs(readme): update installation instructions`\n- `refactor(service): extract common message validation logic`\n\n### Pull Request Requirements\n- **Clear description** of changes and motivation\n- **Linked issues** if applicable\n- **Migration impact** (specify database provider)\n- **Local testing steps** with screenshots/logs\n- **Breaking changes** clearly documented\n\n## Security & Configuration\n\n### Environment Setup\n```bash\n# Copy example environment file\ncp .env.example .env\n\n# NEVER commit secrets to version control\n# Set DATABASE_PROVIDER before database commands\nexport DATABASE_PROVIDER=postgresql  # or mysql\n```\n\n### Security Best Practices\n- **API key authentication** via `apikey` header\n- **Input validation** with JSONSchema7\n- **Rate limiting** on all endpoints\n- **Webhook signature validation**\n- **Instance-based access control**\n- **Secure defaults** for all configurations\n\n### Vulnerability Reporting\n- See `SECURITY.md` for security vulnerability reporting process\n- Contact: `contato@evolution-api.com`\n\n## Communication Standards\n\n### Language Requirements\n- **User communication**: Always respond in Portuguese (PT-BR)\n- **Code/comments**: English for technical documentation\n- **API responses**: English for consistency\n- **Error messages**: Portuguese for user-facing errors\n\n### Documentation Standards\n- **Inline comments**: Document complex business logic\n- **API documentation**: Document all public endpoints\n- **Integration guides**: Document new integration patterns\n- **Migration guides**: Document database schema changes\n\n## Performance & Scalability\n\n### Caching Strategy\n- **Redis primary**: Distributed caching for production\n- **Node-cache fallback**: Local caching when Redis unavailable\n- **TTL strategy**: Appropriate cache expiration per data type\n- **Cache invalidation**: Proper invalidation on data changes\n\n### Connection Management\n- **Database**: Prisma connection pooling\n- **WhatsApp**: One connection per instance with lifecycle management\n- **Redis**: Connection pooling and retry logic\n- **External APIs**: Rate limiting and retry with exponential backoff\n\n### Monitoring & Observability\n- **Structured logging**: Pino logger with correlation IDs\n- **Error tracking**: Comprehensive error scenarios\n- **Health checks**: Instance status and connection monitoring\n- **Telemetry**: Usage analytics (non-sensitive data only)\n\n","CLAUDE.md":"# CLAUDE.md\n\nThis file provides comprehensive guidance to Claude AI when working with the Evolution API codebase.\n\n## Project Overview\n\n**Evolution API** is a powerful, production-ready REST API for WhatsApp communication that supports multiple WhatsApp providers:\n- **Baileys** (WhatsApp Web) - Open-source WhatsApp Web client\n- **Meta Business API** - Official WhatsApp Business API\n- **Evolution API** - Custom WhatsApp integration\n\nBuilt with **Node.js 20+**, **TypeScript 5+**, and **Express.js**, it provides extensive integrations with chatbots, CRM systems, and messaging platforms in a **multi-tenant architecture**.\n\n## Common Development Commands\n\n### Build and Run\n```bash\n# Development\nnpm run dev:server    # Run in development with hot reload (tsx watch)\n\n# Production\nnpm run build        # TypeScript check + tsup build\nnpm run start:prod   # Run production build\n\n# Direct execution\nnpm start           # Run with tsx\n```\n\n### Code Quality\n```bash\nnpm run lint        # ESLint with auto-fix\nnpm run lint:check  # ESLint check only\nnpm run commit      # Interactive commit with commitizen\n```\n\n### Database Management\n```bash\n# Set database provider first\nexport DATABASE_PROVIDER=postgresql  # or mysql\n\n# Generate Prisma client (automatically uses DATABASE_PROVIDER env)\nnpm run db:generate\n\n# Deploy migrations (production)\nnpm run db:deploy      # Unix/Mac\nnpm run db:deploy:win  # Windows\n\n# Development migrations (with sync to provider folder)\nnpm run db:migrate:dev      # Unix/Mac\nnpm run db:migrate:dev:win  # Windows\n\n# Open Prisma Studio\nnpm run db:studio\n\n# Development migrations\nnpm run db:migrate:dev      # Unix/Mac\nnpm run db:migrate:dev:win  # Windows\n```\n\n### Testing\n```bash\nnpm test    # Run tests with watch mode\n```\n\n## Architecture Overview\n\n### Core Structure\n- **Multi-tenant SaaS**: Complete instance isolation with per-tenant authentication\n- **Multi-provider database**: PostgreSQL and MySQL via Prisma ORM with provider-specific schemas and migrations\n- **WhatsApp integrations**: Baileys, Meta Business API, and Evolution API with unified interface\n- **Event-driven architecture**: EventEmitter2 for internal events + WebSocket, RabbitMQ, SQS, NATS, Pusher for external events\n- **Microservices pattern**: Modular integrations for chatbots, storage, and external services\n\n### Directory Layout\n```\nsrc/\n├── api/\n│   ├── controllers/     # HTTP route handlers (thin layer)\n│   ├── services/        # Business logic (core functionality)\n│   ├── repository/      # Data access layer (Prisma)\n│   ├── dto/            # Data Transfer Objects (simple classes)\n│   ├── guards/         # Authentication/authorization middleware\n│   ├── integrations/   # External service integrations\n│   │   ├── channel/    # WhatsApp providers (Baileys, Business API, Evolution)\n│   │   ├── chatbot/    # AI/Bot integrations (OpenAI, Dify, Typebot, Chatwoot)\n│   │   ├── event/      # Event systems (WebSocket, RabbitMQ, SQS, NATS, Pusher)\n│   │   └── storage/    # File storage (S3, MinIO)\n│   ├── routes/         # Express route definitions (RouterBroker pattern)\n│   └── types/          # TypeScript type definitions\n├── config/             # Environment and app configuration\n├── cache/             # Redis and local cache implementations\n├── exceptions/        # Custom HTTP exception classes\n├── utils/            # Shared utilities and helpers\n└── validate/         # JSONSchema7 validation schemas\n```\n\n### Key Integration Points\n\n**Channel Integrations** (`src/api/integrations/channel/`):\n- **Baileys**: WhatsApp Web client with QR code authentication\n- **Business API**: Official Meta WhatsApp Business API\n- **Evolution API**: Custom WhatsApp integration\n- Connection lifecycle management per instance with automatic reconnection\n\n**Chatbot Integrations** (`src/api/integrations/chatbot/`):\n- **EvolutionBot**: Native chatbot with trigger system\n- **Chatwoot**: Customer service platform integration\n- **Typebot**: Visual chatbot flow builder\n- **OpenAI**: AI capabilities including GPT and Whisper (audio transcription)\n- **Dify**: AI agent workflow platform\n- **Flowise**: LangChain visual builder\n- **N8N**: Workflow automation platform\n- **EvoAI**: Custom AI integration\n\n**Event Integrations** (`src/api/integrations/event/`):\n- **WebSocket**: Real-time Socket.io connections\n- **RabbitMQ**: Message queue for async processing\n- **Amazon SQS**: Cloud-based message queuing\n- **NATS**: High-performance messaging system\n- **Pusher**: Real-time push notifications\n\n**Storage Integrations** (`src/api/integrations/storage/`):\n- **AWS S3**: Cloud object storage\n- **MinIO**: Self-hosted S3-compatible storage\n- Media file management and URL generation\n\n### Database Schema Management\n- Separate schema files: `postgresql-schema.prisma` and `mysql-schema.prisma`\n- Environment variable `DATABASE_PROVIDER` determines active database\n- Migration folders are provider-specific and auto-selected during deployment\n\n### Authentication & Security\n- **API key-based authentication** via `apikey` header (global or per-instance)\n- **Instance-specific tokens** for WhatsApp connection authentication\n- **Guards system** for route protection and authorization\n- **Input validation** using JSONSchema7 with RouterBroker `dataValidate`\n- **Rate limiting** and security middleware\n- **Webhook signature validation** for external integrations\n\n## Important Implementation Details\n\n### WhatsApp Instance Management\n- Each WhatsApp connection is an \"instance\" with unique name\n- Instance data stored in database with connection state\n- Session persistence in database or file system (configurable)\n- Automatic reconnection handling with exponential backoff\n\n### Message Queue Architecture\n- Supports RabbitMQ, Amazon SQS, and WebSocket for events\n- Event types: message.received, message.sent, connection.update, etc.\n- Configurable per instance which events to send\n\n### Media Handling\n- Local storage or S3/Minio for media files\n- Automatic media download from WhatsApp\n- Media URL generation for external access\n- Support for audio transcription via OpenAI\n\n### Multi-tenancy Support\n- Instance isolation at database level\n- Separate webhook configurations per instance\n- Independent integration settings per instance\n\n## Environment Configuration\n\nKey environment variables are defined in `.env.example`. The system uses a strongly-typed configuration system via `src/config/env.config.ts`.\n\nCritical configurations:\n- `DATABASE_PROVIDER`: postgresql or mysql\n- `DATABASE_CONNECTION_URI`: Database connection string\n- `AUTHENTICATION_API_KEY`: Global API authentication\n- `REDIS_ENABLED`: Enable Redis cache\n- `RABBITMQ_ENABLED`/`SQS_ENABLED`: Message queue options\n\n## Development Guidelines\n\nThe project follows comprehensive development standards defined in `.cursor/rules/`:\n\n### Core Principles\n- **Always respond in Portuguese (PT-BR)** for user communication\n- **Follow established architecture patterns** (Service Layer, RouterBroker, etc.)\n- **Robust error handling** with retry logic and graceful degradation\n- **Multi-database compatibility** (PostgreSQL and MySQL)\n- **Security-first approach** with input validation and rate limiting\n- **Performance optimizations** with Redis caching and connection pooling\n\n### Code Standards\n- **TypeScript strict mode** with full type coverage\n- **JSONSchema7** for input validation (not class-validator)\n- **Conventional Commits** enforced by commitlint\n- **ESLint + Prettier** for code formatting\n- **Service Object pattern** for business logic\n- **RouterBroker pattern** for route handling with `dataValidate`\n\n### Architecture Patterns\n- **Multi-tenant isolation** at database and instance level\n- **Event-driven communication** with EventEmitter2\n- **Microservices integration** pattern for external services\n- **Connection pooling** and lifecycle management\n- **Caching strategy** with Redis primary and Node-cache fallback\n\n## Testing Approach\n\nCurrently, the project has minimal formal testing infrastructure:\n- **Manual testing** is the primary approach\n- **Integration testing** in development environment\n- **No unit test suite** currently implemented\n- Test files can be placed in `test/` directory as `*.test.ts`\n- Run `npm test` for watch mode development testing\n\n### Recommended Testing Strategy\n- Focus on **critical business logic** in services\n- **Mock external dependencies** (WhatsApp APIs, databases)\n- **Integration tests** for API endpoints\n- **Manual testing** for WhatsApp connection flows\n\n## Deployment Considerations\n\n- Docker support with `Dockerfile` and `docker-compose.yaml`\n- Graceful shutdown handling for connections\n- Health check endpoints for monitoring\n- Sentry integration for error tracking\n- Telemetry for usage analytics (non-sensitive data only)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Evolution API - AI Agent Guidelines\n\nThis document provides comprehensive guidelines for AI agents (Claude, GPT, Cursor, etc.) working with the Evolution API codebase.\n\n## Project Overview\n\n**Evolution API** is a production-ready, multi-tenant WhatsApp API platform built with Node.js, TypeScript, and Express.js. It supports multiple WhatsApp providers and extensive integrations with chatbots, CRM systems, and messaging platforms.\n\n## Project Structure & Module Organization\n\n### Core Directories\n- **`src/`** – TypeScript source code with modular architecture\n  - `api/controllers/` – HTTP route handlers (thin layer)\n  - `api/services/` – Business logic (core functionality)\n  - `api/routes/` – Express route definitions (RouterBroker pattern)\n  - `api/integrations/` – External service integrations\n    - `channel/` – WhatsApp providers (Baileys, Business API, Evolution)\n    - `chatbot/` – AI/Bot integrations (OpenAI, Dify, Typebot, Chatwoot)\n    - `event/` – Event systems (WebSocket, RabbitMQ, SQS, NATS, Pusher)\n    - `storage/` – File storage (S3, MinIO)\n  - `dto/` – Data Transfer Objects (simple classes, no decorators)\n  - `guards/` – Authentication/authorization middleware\n  - `types/` – TypeScript type definitions\n  - `repository/` – Data access layer (Prisma)\n- **`prisma/`** – Database schemas and migrations\n  - `postgresql-schema.prisma` / `mysql-schema.prisma` – Provider-specific schemas\n  - `postgresql-migrations/` / `mysql-migrations/` – Provider-specific migrations\n- **`config/`** – Environment and application configuration\n- **`utils/`** – Shared utilities and helper functions\n- **`validate/`** – JSONSchema7 validation schemas\n- **`exceptions/`** – Custom HTTP exception classes\n- **`cache/`** – Redis and local cache implementations\n\n### Build & Deployment\n- **`dist/`** – Build output (do not edit directly)\n- **`public/`** – Static assets and media files\n- **`Docker*`**, **`docker-compose*.yaml`** – Containerization and local development stack\n\n## Build, Test, and Development Commands\n\n### Development Workflow\n```bash\n# Development server with hot reload\nnpm run dev:server\n\n# Direct execution for testing\nnpm start\n\n# Production build and run\nnpm run build\nnpm run start:prod\n```\n\n### Code Quality\n```bash\n# Linting and formatting\nnpm run lint        # ESLint with auto-fix\nnpm run lint:check  # ESLint check only\n\n# Commit with conventional commits\nnpm run commit      # Interactive commit with Commitizen\n```\n\n### Database Management\n```bash\n# Set database provider first (CRITICAL)\nexport DATABASE_PROVIDER=postgresql  # or mysql\n\n# Generate Prisma client\nnpm run db:generate\n\n# Development migrations (with provider sync)\nnpm run db:migrate:dev      # Unix/Mac\nnpm run db:migrate:dev:win  # Windows\n\n# Production deployment\nnpm run db:deploy      # Unix/Mac\nnpm run db:deploy:win  # Windows\n\n# Database tools\nnpm run db:studio      # Open Prisma Studio\n```\n\n### Docker Development\n```bash\n# Start local services (Redis, PostgreSQL, etc.)\ndocker-compose up -d\n\n# Full development stack\ndocker-compose -f docker-compose.dev.yaml up -d\n```\n\n## Coding Standards & Architecture Patterns\n\n### Code Style (Enforced by ESLint + Prettier)\n- **TypeScript strict mode** with full type coverage\n- **2-space indentation**, single quotes, trailing commas\n- **120-character line limit**\n- **Import order** via `simple-import-sort`\n- **File naming**: `feature.kind.ts` (e.g., `whatsapp.baileys.service.ts`)\n- **Naming conventions**:\n  - Classes: `PascalCase`\n  - Functions/variables: `camelCase`\n  - Constants: `UPPER_SNAKE_CASE`\n  - Files: `kebab-case.type.ts`\n\n### Architecture Patterns\n\n#### Service Layer Pattern\n```typescript\nexport class ExampleService {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n  \n  private readonly logger = new Logger('ExampleService');\n  \n  public async create(instance: InstanceDto, data: ExampleDto) {\n    // Business logic here\n    return { example: { ...instance, data } };\n  }\n  \n  public async find(instance: InstanceDto): Promise<ExampleDto | null> {\n    try {\n      const result = await this.waMonitor.waInstances[instance.instanceName].findData();\n      return result || null; // Return null on not found (Evolution pattern)\n    } catch (error) {\n      this.logger.error('Error finding data:', error);\n      return null; // Return null on error (Evolution pattern)\n    }\n  }\n}\n```\n\n#### Controller Pattern (Thin Layer)\n```typescript\nexport class ExampleController {\n  constructor(private readonly exampleService: ExampleService) {}\n  \n  public async createExample(instance: InstanceDto, data: ExampleDto) {\n    return this.exampleService.create(instance, data);\n  }\n}\n```\n\n#### RouterBroker Pattern\n```typescript\nexport class ExampleRouter extends RouterBroker {\n  constructor(...guards: any[]) {\n    super();\n    this.router.post(this.routerPath('create'), ...guards, async (req, res) => {\n      const response = await this.dataValidate<ExampleDto>({\n        request: req,\n        schema: exampleSchema, // JSONSchema7\n        ClassRef: ExampleDto,\n        execute: (instance, data) => controller.createExample(instance, data),\n      });\n      res.status(201).json(response);\n    });\n  }\n}\n```\n\n#### DTO Pattern (Simple Classes)\n```typescript\n// CORRECT - Evolution API pattern (no decorators)\nexport class ExampleDto {\n  name: string;\n  description?: string;\n  enabled: boolean;\n}\n\n// INCORRECT - Don't use class-validator decorators\nexport class BadExampleDto {\n  @IsString() // ❌ Evolution API doesn't use decorators\n  name: string;\n}\n```\n\n#### Validation Pattern (JSONSchema7)\n```typescript\nimport { JSONSchema7 } from 'json-schema';\nimport { v4 } from 'uuid';\n\nexport const exampleSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    name: { type: 'string' },\n    description: { type: 'string' },\n    enabled: { type: 'boolean' },\n  },\n  required: ['name', 'enabled'],\n};\n```\n\n## Multi-Tenant Architecture\n\n### Instance Isolation\n- **CRITICAL**: All operations must be scoped by `instanceName` or `instanceId`\n- **Database queries**: Always include `where: { instanceId: ... }`\n- **Authentication**: Validate instance ownership before operations\n- **Data isolation**: Complete separation between tenant instances\n\n### WhatsApp Instance Management\n```typescript\n// Access instance via WAMonitoringService\nconst waInstance = this.waMonitor.waInstances[instance.instanceName];\nif (!waInstance) {\n  throw new NotFoundException(`Instance ${instance.instanceName} not found`);\n}\n```\n\n## Database Patterns\n\n### Multi-Provider Support\n- **PostgreSQL**: Uses `@db.Integer`, `@db.JsonB`, `@default(now())`\n- **MySQL**: Uses `@db.Int`, `@db.Json`, `@default(now())`\n- **Environment**: Set `DATABASE_PROVIDER=postgresql` or `mysql`\n- **Migrations**: Provider-specific folders auto-selected\n\n### Prisma Repository Pattern\n```typescript\n// Always use PrismaRepository for database operations\nconst result = await this.prismaRepository.instance.findUnique({\n  where: { name: instanceName },\n});\n```\n\n## Integration Patterns\n\n### Channel Integration (WhatsApp Providers)\n- **Baileys**: WhatsApp Web with QR code authentication\n- **Business API**: Official Meta WhatsApp Business API  \n- **Evolution API**: Custom WhatsApp integration\n- **Pattern**: Extend base channel service classes\n\n### Chatbot Integration\n- **Base classes**: Extend `BaseChatbotService` and `BaseChatbotController`\n- **Trigger system**: Support keyword, regex, and advanced triggers\n- **Session management**: Handle conversation state per user\n- **Available integrations**: EvolutionBot, OpenAI, Dify, Typebot, Chatwoot, Flowise, N8N, EvoAI\n\n### Event Integration\n- **Internal events**: EventEmitter2 for application events\n- **External events**: WebSocket, RabbitMQ, SQS, NATS, Pusher\n- **Webhook delivery**: Reliable delivery with retry logic\n\n## Testing Guidelines\n\n### Current State\n- **No formal test suite** currently implemented\n- **Manual testing** is the primary approach\n- **Integration testing** in development environment\n\n### Testing Strategy\n```typescript\n// Place tests in test/ directory as *.test.ts\n// Run: npm test (watches test/all.test.ts)\n\ndescribe('ExampleService', () => {\n  it('should create example', async () => {\n    // Mock external dependencies\n    // Test business logic\n    // Assert expected behavior\n  });\n});\n```\n\n### Recommended Approach\n- Focus on **critical business logic** in services\n- **Mock external dependencies** (WhatsApp APIs, databases)\n- **Integration tests** for API endpoints\n- **Manual testing** for WhatsApp connection flows\n\n## Commit & Pull Request Guidelines\n\n### Conventional Commits (Enforced by commitlint)\n```bash\n# Use interactive commit tool\nnpm run commit\n\n# Commit format: type(scope): subject (max 100 chars)\n# Types: feat, fix, docs, style, refactor, perf, test, chore, ci, build, revert, security\n```\n\n### Examples\n- `feat(api): add WhatsApp message status endpoint`\n- `fix(baileys): resolve connection timeout issue`\n- `docs(readme): update installation instructions`\n- `refactor(service): extract common message validation logic`\n\n### Pull Request Requirements\n- **Clear description** of changes and motivation\n- **Linked issues** if applicable\n- **Migration impact** (specify database provider)\n- **Local testing steps** with screenshots/logs\n- **Breaking changes** clearly documented\n\n## Security & Configuration\n\n### Environment Setup\n```bash\n# Copy example environment file\ncp .env.example .env\n\n# NEVER commit secrets to version control\n# Set DATABASE_PROVIDER before database commands\nexport DATABASE_PROVIDER=postgresql  # or mysql\n```\n\n### Security Best Practices\n- **API key authentication** via `apikey` header\n- **Input validation** with JSONSchema7\n- **Rate limiting** on all endpoints\n- **Webhook signature validation**\n- **Instance-based access control**\n- **Secure defaults** for all configurations\n\n### Vulnerability Reporting\n- See `SECURITY.md` for security vulnerability reporting process\n- Contact: `contato@evolution-api.com`\n\n## Communication Standards\n\n### Language Requirements\n- **User communication**: Always respond in Portuguese (PT-BR)\n- **Code/comments**: English for technical documentation\n- **API responses**: English for consistency\n- **Error messages**: Portuguese for user-facing errors\n\n### Documentation Standards\n- **Inline comments**: Document complex business logic\n- **API documentation**: Document all public endpoints\n- **Integration guides**: Document new integration patterns\n- **Migration guides**: Document database schema changes\n\n## Performance & Scalability\n\n### Caching Strategy\n- **Redis primary**: Distributed caching for production\n- **Node-cache fallback**: Local caching when Redis unavailable\n- **TTL strategy**: Appropriate cache expiration per data type\n- **Cache invalidation**: Proper invalidation on data changes\n\n### Connection Management\n- **Database**: Prisma connection pooling\n- **WhatsApp**: One connection per instance with lifecycle management\n- **Redis**: Connection pooling and retry logic\n- **External APIs**: Rate limiting and retry with exponential backoff\n\n### Monitoring & Observability\n- **Structured logging**: Pino logger with correlation IDs\n- **Error tracking**: Comprehensive error scenarios\n- **Health checks**: Instance status and connection monitoring\n- **Telemetry**: Usage analytics (non-sensitive data only)\n\n","isInternal":false,"tokens":2600,"sizeBytes":11394},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CLAUDE.md\n\nThis file provides comprehensive guidance to Claude AI when working with the Evolution API codebase.\n\n## Project Overview\n\n**Evolution API** is a powerful, production-ready REST API for WhatsApp communication that supports multiple WhatsApp providers:\n- **Baileys** (WhatsApp Web) - Open-source WhatsApp Web client\n- **Meta Business API** - Official WhatsApp Business API\n- **Evolution API** - Custom WhatsApp integration\n\nBuilt with **Node.js 20+**, **TypeScript 5+**, and **Express.js**, it provides extensive integrations with chatbots, CRM systems, and messaging platforms in a **multi-tenant architecture**.\n\n## Common Development Commands\n\n### Build and Run\n```bash\n# Development\nnpm run dev:server    # Run in development with hot reload (tsx watch)\n\n# Production\nnpm run build        # TypeScript check + tsup build\nnpm run start:prod   # Run production build\n\n# Direct execution\nnpm start           # Run with tsx\n```\n\n### Code Quality\n```bash\nnpm run lint        # ESLint with auto-fix\nnpm run lint:check  # ESLint check only\nnpm run commit      # Interactive commit with commitizen\n```\n\n### Database Management\n```bash\n# Set database provider first\nexport DATABASE_PROVIDER=postgresql  # or mysql\n\n# Generate Prisma client (automatically uses DATABASE_PROVIDER env)\nnpm run db:generate\n\n# Deploy migrations (production)\nnpm run db:deploy      # Unix/Mac\nnpm run db:deploy:win  # Windows\n\n# Development migrations (with sync to provider folder)\nnpm run db:migrate:dev      # Unix/Mac\nnpm run db:migrate:dev:win  # Windows\n\n# Open Prisma Studio\nnpm run db:studio\n\n# Development migrations\nnpm run db:migrate:dev      # Unix/Mac\nnpm run db:migrate:dev:win  # Windows\n```\n\n### Testing\n```bash\nnpm test    # Run tests with watch mode\n```\n\n## Architecture Overview\n\n### Core Structure\n- **Multi-tenant SaaS**: Complete instance isolation with per-tenant authentication\n- **Multi-provider database**: PostgreSQL and MySQL via Prisma ORM with provider-specific schemas and migrations\n- **WhatsApp integrations**: Baileys, Meta Business API, and Evolution API with unified interface\n- **Event-driven architecture**: EventEmitter2 for internal events + WebSocket, RabbitMQ, SQS, NATS, Pusher for external events\n- **Microservices pattern**: Modular integrations for chatbots, storage, and external services\n\n### Directory Layout\n```\nsrc/\n├── api/\n│   ├── controllers/     # HTTP route handlers (thin layer)\n│   ├── services/        # Business logic (core functionality)\n│   ├── repository/      # Data access layer (Prisma)\n│   ├── dto/            # Data Transfer Objects (simple classes)\n│   ├── guards/         # Authentication/authorization middleware\n│   ├── integrations/   # External service integrations\n│   │   ├── channel/    # WhatsApp providers (Baileys, Business API, Evolution)\n│   │   ├── chatbot/    # AI/Bot integrations (OpenAI, Dify, Typebot, Chatwoot)\n│   │   ├── event/      # Event systems (WebSocket, RabbitMQ, SQS, NATS, Pusher)\n│   │   └── storage/    # File storage (S3, MinIO)\n│   ├── routes/         # Express route definitions (RouterBroker pattern)\n│   └── types/          # TypeScript type definitions\n├── config/             # Environment and app configuration\n├── cache/             # Redis and local cache implementations\n├── exceptions/        # Custom HTTP exception classes\n├── utils/            # Shared utilities and helpers\n└── validate/         # JSONSchema7 validation schemas\n```\n\n### Key Integration Points\n\n**Channel Integrations** (`src/api/integrations/channel/`):\n- **Baileys**: WhatsApp Web client with QR code authentication\n- **Business API**: Official Meta WhatsApp Business API\n- **Evolution API**: Custom WhatsApp integration\n- Connection lifecycle management per instance with automatic reconnection\n\n**Chatbot Integrations** (`src/api/integrations/chatbot/`):\n- **EvolutionBot**: Native chatbot with trigger system\n- **Chatwoot**: Customer service platform integration\n- **Typebot**: Visual chatbot flow builder\n- **OpenAI**: AI capabilities including GPT and Whisper (audio transcription)\n- **Dify**: AI agent workflow platform\n- **Flowise**: LangChain visual builder\n- **N8N**: Workflow automation platform\n- **EvoAI**: Custom AI integration\n\n**Event Integrations** (`src/api/integrations/event/`):\n- **WebSocket**: Real-time Socket.io connections\n- **RabbitMQ**: Message queue for async processing\n- **Amazon SQS**: Cloud-based message queuing\n- **NATS**: High-performance messaging system\n- **Pusher**: Real-time push notifications\n\n**Storage Integrations** (`src/api/integrations/storage/`):\n- **AWS S3**: Cloud object storage\n- **MinIO**: Self-hosted S3-compatible storage\n- Media file management and URL generation\n\n### Database Schema Management\n- Separate schema files: `postgresql-schema.prisma` and `mysql-schema.prisma`\n- Environment variable `DATABASE_PROVIDER` determines active database\n- Migration folders are provider-specific and auto-selected during deployment\n\n### Authentication & Security\n- **API key-based authentication** via `apikey` header (global or per-instance)\n- **Instance-specific tokens** for WhatsApp connection authentication\n- **Guards system** for route protection and authorization\n- **Input validation** using JSONSchema7 with RouterBroker `dataValidate`\n- **Rate limiting** and security middleware\n- **Webhook signature validation** for external integrations\n\n## Important Implementation Details\n\n### WhatsApp Instance Management\n- Each WhatsApp connection is an \"instance\" with unique name\n- Instance data stored in database with connection state\n- Session persistence in database or file system (configurable)\n- Automatic reconnection handling with exponential backoff\n\n### Message Queue Architecture\n- Supports RabbitMQ, Amazon SQS, and WebSocket for events\n- Event types: message.received, message.sent, connection.update, etc.\n- Configurable per instance which events to send\n\n### Media Handling\n- Local storage or S3/Minio for media files\n- Automatic media download from WhatsApp\n- Media URL generation for external access\n- Support for audio transcription via OpenAI\n\n### Multi-tenancy Support\n- Instance isolation at database level\n- Separate webhook configurations per instance\n- Independent integration settings per instance\n\n## Environment Configuration\n\nKey environment variables are defined in `.env.example`. The system uses a strongly-typed configuration system via `src/config/env.config.ts`.\n\nCritical configurations:\n- `DATABASE_PROVIDER`: postgresql or mysql\n- `DATABASE_CONNECTION_URI`: Database connection string\n- `AUTHENTICATION_API_KEY`: Global API authentication\n- `REDIS_ENABLED`: Enable Redis cache\n- `RABBITMQ_ENABLED`/`SQS_ENABLED`: Message queue options\n\n## Development Guidelines\n\nThe project follows comprehensive development standards defined in `.cursor/rules/`:\n\n### Core Principles\n- **Always respond in Portuguese (PT-BR)** for user communication\n- **Follow established architecture patterns** (Service Layer, RouterBroker, etc.)\n- **Robust error handling** with retry logic and graceful degradation\n- **Multi-database compatibility** (PostgreSQL and MySQL)\n- **Security-first approach** with input validation and rate limiting\n- **Performance optimizations** with Redis caching and connection pooling\n\n### Code Standards\n- **TypeScript strict mode** with full type coverage\n- **JSONSchema7** for input validation (not class-validator)\n- **Conventional Commits** enforced by commitlint\n- **ESLint + Prettier** for code formatting\n- **Service Object pattern** for business logic\n- **RouterBroker pattern** for route handling with `dataValidate`\n\n### Architecture Patterns\n- **Multi-tenant isolation** at database and instance level\n- **Event-driven communication** with EventEmitter2\n- **Microservices integration** pattern for external services\n- **Connection pooling** and lifecycle management\n- **Caching strategy** with Redis primary and Node-cache fallback\n\n## Testing Approach\n\nCurrently, the project has minimal formal testing infrastructure:\n- **Manual testing** is the primary approach\n- **Integration testing** in development environment\n- **No unit test suite** currently implemented\n- Test files can be placed in `test/` directory as `*.test.ts`\n- Run `npm test` for watch mode development testing\n\n### Recommended Testing Strategy\n- Focus on **critical business logic** in services\n- **Mock external dependencies** (WhatsApp APIs, databases)\n- **Integration tests** for API endpoints\n- **Manual testing** for WhatsApp connection flows\n\n## Deployment Considerations\n\n- Docker support with `Dockerfile` and `docker-compose.yaml`\n- Graceful shutdown handling for connections\n- Health check endpoints for monitoring\n- Sentry integration for error tracking\n- Telemetry for usage analytics (non-sensitive data only)","isInternal":false,"tokens":1914,"sizeBytes":8907},{"name":"core-development.mdc","path":".cursor/rules/core-development.mdc","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/core-development.mdc","title":"Cursor Rule: core-development","category":"cursor-rule","format":"mdc","content":"---\ndescription: Core development principles and standards for Evolution API development\nglobs:\nalwaysApply: true\n---\n\n# Evolution API Development Standards\n\n## Cross-References\n- **Project Context**: @project-context.mdc for Evolution API-specific patterns\n- **Specialized Rules**: \n  - @specialized-rules/service-rules.mdc for service layer patterns\n  - @specialized-rules/controller-rules.mdc for controller patterns\n  - @specialized-rules/dto-rules.mdc for DTO validation patterns\n  - @specialized-rules/guard-rules.mdc for authentication/authorization\n  - @specialized-rules/route-rules.mdc for router patterns\n  - @specialized-rules/type-rules.mdc for TypeScript definitions\n  - @specialized-rules/util-rules.mdc for utility functions\n  - @specialized-rules/validate-rules.mdc for validation schemas\n  - @specialized-rules/integration-channel-rules.mdc for channel integrations\n  - @specialized-rules/integration-chatbot-rules.mdc for chatbot integrations\n  - @specialized-rules/integration-storage-rules.mdc for storage integrations\n  - @specialized-rules/integration-event-rules.mdc for event integrations\n- **TypeScript/Node.js**: Node.js 20+ + TypeScript 5+ best practices\n- **Express/Prisma**: Express.js + Prisma ORM patterns\n- **WhatsApp Integrations**: Baileys + Meta Business API patterns\n\n## Senior Engineer Context - Evolution API Platform\n- You are a senior software engineer working on a WhatsApp API platform\n- Focus on Node.js + TypeScript + Express.js full-stack development\n- Specialized in real-time messaging, WhatsApp integrations, and event-driven architecture\n- Apply scalable patterns for multi-tenant API platform\n- Consider WhatsApp integration workflow implications and performance at scale\n\n## Fundamental Principles\n\n### Code Quality Standards\n- **Simplicity First**: Always prefer simple solutions over complex ones\n- **DRY Principle**: Avoid code duplication - check for existing similar functionality before implementing\n- **Single Responsibility**: Each function/class should have one clear purpose\n- **Readable Code**: Write code that tells a story - clear naming and structure\n\n### Problem Resolution Approach\n- **Follow Existing Patterns**: Use established Service patterns, DTOs, and Integration patterns\n- **Event-Driven First**: Leverage EventEmitter2 for event publishing when adding new features\n- **Integration Pattern**: Follow existing WhatsApp integration patterns for new channels\n- **Conservative Changes**: Prefer extending existing services over creating new architecture\n- **Clean Migration**: Remove deprecated patterns when introducing new ones\n- **Incremental Changes**: Break large changes into smaller, testable increments with proper migrations\n\n### File and Function Organization - Node.js/TypeScript Structure\n- **Services**: Keep services focused and under 200 lines\n- **Controllers**: Keep controllers thin - only routing and validation\n- **DTOs**: Use JSONSchema7 for all input validation\n- **Integrations**: Follow `src/api/integrations/` structure for new integrations\n- **Utils**: Extract common functionality into well-named utilities\n- **Types**: Define clear TypeScript interfaces and types\n\n### Code Analysis and Reflection\n- After writing code, deeply reflect on scalability and maintainability\n- Provide 1-2 paragraph analysis of code changes\n- Suggest improvements or next steps based on reflection\n- Consider performance, security, and maintenance implications\n\n## Development Standards\n\n### TypeScript Standards\n- **Strict Mode**: Always use TypeScript strict mode\n- **No Any**: Avoid `any` type - use proper typing\n- **Interfaces**: Define clear contracts with interfaces\n- **Enums**: Use enums for constants and status values\n- **Generics**: Use generics for reusable components\n\n### Error Handling Standards\n- **HTTP Exceptions**: Use appropriate HTTP status codes\n- **Logging**: Structured logging with context\n- **Retry Logic**: Implement retry for external services\n- **Graceful Degradation**: Handle service failures gracefully\n\n### Security Standards\n- **Input Validation**: Validate all inputs with JSONSchema7\n- **Authentication**: Use API keys and JWT tokens\n- **Rate Limiting**: Implement rate limiting for APIs\n- **Data Sanitization**: Sanitize sensitive data in logs\n\n### Performance Standards\n- **Caching**: Use Redis for frequently accessed data\n- **Database**: Optimize Prisma queries with proper indexing\n- **Memory**: Monitor memory usage and implement cleanup\n- **Async**: Use async/await properly with error handling\n\n## Communication Standards\n\n### Language Requirements\n- **User Communication**: Always respond in Portuguese (PT-BR)\n- **Code Comments**: English for technical documentation\n- **API Documentation**: English for consistency\n- **Error Messages**: Portuguese for user-facing errors\n\n### Documentation Standards\n- **Code Comments**: Document complex business logic\n- **API Documentation**: Document all public endpoints\n- **README**: Keep project documentation updated\n- **Changelog**: Document breaking changes\n\n## Quality Assurance\n\n### Testing Standards\n- **Unit Tests**: Test business logic in services\n- **Integration Tests**: Test API endpoints\n- **Mocks**: Mock external dependencies\n- **Coverage**: Aim for 70%+ test coverage\n\n### Code Review Standards\n- **Peer Review**: All code must be reviewed\n- **Automated Checks**: ESLint, Prettier, TypeScript\n- **Security Review**: Check for security vulnerabilities\n- **Performance Review**: Check for performance issues\n\n### Commit Standards (Conventional Commits)\n- **Format**: `type(scope): subject` (max 100 characters)\n- **Types**: \n  - `feat` - New feature\n  - `fix` - Bug fix\n  - `docs` - Documentation changes\n  - `style` - Code style changes (formatting, etc)\n  - `refactor` - Code refactoring\n  - `perf` - Performance improvements\n  - `test` - Adding or updating tests\n  - `chore` - Maintenance tasks\n  - `ci` - CI/CD changes\n  - `build` - Build system changes\n  - `revert` - Reverting changes\n  - `security` - Security fixes\n- **Examples**:\n  - `feat(api): add WhatsApp message status endpoint`\n  - `fix(baileys): resolve connection timeout issue`\n  - `docs(readme): update installation instructions`\n  - `refactor(service): extract common message validation logic`\n- **Tools**: Use `npm run commit` (Commitizen) for guided commits\n- **Validation**: Enforced by commitlint on commit-msg hook\n\n## Evolution API Specific Patterns\n\n### WhatsApp Integration Patterns\n- **Connection Management**: One connection per instance\n- **Event Handling**: Proper event listeners for Baileys\n- **Message Processing**: Queue-based message processing\n- **Error Recovery**: Automatic reconnection logic\n\n### Multi-Database Support\n- **Schema Compatibility**: Support PostgreSQL and MySQL\n- **Migration Sync**: Keep migrations synchronized\n- **Type Safety**: Use Prisma generated types\n- **Connection Pooling**: Proper database connection management\n\n### Cache Strategy\n- **Redis Primary**: Use Redis for distributed caching\n- **Local Fallback**: Node-cache for local fallback\n- **TTL Strategy**: Appropriate TTL for different data types\n- **Cache Invalidation**: Proper cache invalidation patterns\n\n### Event System\n- **EventEmitter2**: Use for internal events\n- **WebSocket**: Socket.io for real-time updates\n- **Queue Systems**: RabbitMQ/SQS for async processing\n- **Webhook Processing**: Proper webhook validation and processing","frontmatter":{"description":"Core development principles and standards for Evolution API development","globs":null,"alwaysApply":true},"isInternal":false,"tokens":1585,"sizeBytes":7388},{"name":"project-context.mdc","path":".cursor/rules/project-context.mdc","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/project-context.mdc","title":"Cursor Rule: project-context","category":"cursor-rule","format":"mdc","content":"---\ndescription: Evolution API project-specific context and constraints\nglobs:\nalwaysApply: true\n---\n\n# Evolution API Project Context\n\n## Cross-References\n- **Core Development**: @core-development.mdc for fundamental development principles\n- **Specialized Rules**: Reference specific specialized rules when working on:\n  - Services: @specialized-rules/service-rules.mdc\n  - Controllers: @specialized-rules/controller-rules.mdc\n  - DTOs: @specialized-rules/dto-rules.mdc\n  - Guards: @specialized-rules/guard-rules.mdc\n  - Routes: @specialized-rules/route-rules.mdc\n  - Types: @specialized-rules/type-rules.mdc\n  - Utils: @specialized-rules/util-rules.mdc\n  - Validation: @specialized-rules/validate-rules.mdc\n  - Channel Integrations: @specialized-rules/integration-channel-rules.mdc\n  - Chatbot Integrations: @specialized-rules/integration-chatbot-rules.mdc\n  - Storage Integrations: @specialized-rules/integration-storage-rules.mdc\n  - Event Integrations: @specialized-rules/integration-event-rules.mdc\n- **TypeScript/Node.js**: Node.js 20+ + TypeScript 5+ backend standards\n- **Express/Prisma**: Express.js + Prisma ORM patterns\n- **WhatsApp Integrations**: Baileys, Meta Business API, and other messaging platforms\n\n## Technology Stack\n- **Backend**: Node.js 20+ + TypeScript 5+ + Express.js\n- **Database**: Prisma ORM (PostgreSQL/MySQL support)\n- **Cache**: Redis + Node-cache for local fallback\n- **Queue**: RabbitMQ + Amazon SQS for message processing\n- **Real-time**: Socket.io for WebSocket connections\n- **Storage**: AWS S3 + Minio for file storage\n- **Validation**: JSONSchema7 for input validation\n- **Logging**: Pino for structured logging\n- **Architecture**: Multi-tenant API with WhatsApp integrations\n\n## Project-Specific Patterns\n\n### WhatsApp Integration Architecture\n- **MANDATORY**: All WhatsApp integrations must follow established patterns\n- **BAILEYS**: Use `whatsapp.baileys.service.ts` patterns for WhatsApp Web\n- **META BUSINESS**: Use `whatsapp.business.service.ts` for official API\n- **CONNECTION MANAGEMENT**: One connection per instance with proper lifecycle\n- **EVENT HANDLING**: Proper event listeners and error handling\n\n### Multi-Database Architecture\n- **CRITICAL**: Support both PostgreSQL and MySQL\n- **SCHEMAS**: Use appropriate schema files (postgresql-schema.prisma / mysql-schema.prisma)\n- **MIGRATIONS**: Keep migrations synchronized between databases\n- **TYPES**: Use database-specific types (@db.JsonB vs @db.Json)\n- **COMPATIBILITY**: Ensure feature parity between databases\n\n### API Integration Workflow\n- **CORE FEATURE**: REST API for WhatsApp communication\n- **COMPLEXITY**: High - involves webhook processing, message routing, and instance management\n- **COMPONENTS**: Instance management, message handling, media processing\n- **INTEGRATIONS**: Baileys, Meta Business API, Chatwoot, Typebot, OpenAI, Dify\n\n### Multi-Tenant Instance Architecture\n- **CRITICAL**: All operations must be scoped by instance\n- **ISOLATION**: Complete data isolation between instances\n- **SECURITY**: Validate instance ownership before operations\n- **SCALING**: Support thousands of concurrent instances\n- **AUTHENTICATION**: API key-based authentication per instance\n\n## Documentation Requirements\n\n### Implementation Documentation\n- **MANDATORY**: Document complex integration patterns\n- **LOCATION**: Use inline comments for business logic\n- **API DOCS**: Document all public endpoints\n- **WEBHOOK DOCS**: Document webhook payloads and signatures\n\n### Change Documentation\n- **CHANGELOG**: Document breaking changes\n- **MIGRATION GUIDES**: Document database migrations\n- **INTEGRATION GUIDES**: Document new integration patterns\n\n## Environment and Security\n\n### Environment Variables\n- **CRITICAL**: Never hardcode sensitive values\n- **VALIDATION**: Validate required environment variables on startup\n- **SECURITY**: Use secure defaults and proper encryption\n- **DOCUMENTATION**: Document all environment variables\n\n### File Organization - Node.js/TypeScript Structure\n- **CONTROLLERS**: Organized by feature (`api/controllers/`)\n- **SERVICES**: Business logic in service classes (`api/services/`)\n- **INTEGRATIONS**: External integrations (`api/integrations/`)\n- **DTOS**: Data transfer objects (`api/dto/`)\n- **TYPES**: TypeScript types (`api/types/`)\n- **UTILS**: Utility functions (`utils/`)\n\n## Integration Points\n\n### WhatsApp Providers\n- **BAILEYS**: WhatsApp Web integration with QR code\n- **META BUSINESS**: Official WhatsApp Business API\n- **CLOUD API**: WhatsApp Cloud API integration\n- **WEBHOOK PROCESSING**: Proper webhook validation and processing\n\n### External Integrations\n- **CHATWOOT**: Customer support platform integration\n- **TYPEBOT**: Chatbot flow integration\n- **OPENAI**: AI-powered chat integration\n- **DIFY**: AI workflow integration\n- **STORAGE**: S3/Minio for media file storage\n\n### Event-Driven Communication\n- **EVENTEMITTER2**: Internal event system\n- **SOCKET.IO**: Real-time WebSocket communication\n- **RABBITMQ**: Message queue for async processing\n- **SQS**: Amazon SQS for cloud-based queuing\n- **WEBHOOKS**: Outbound webhook system\n\n## Development Constraints\n\n### Language Requirements\n- **USER COMMUNICATION**: Always respond in Portuguese (PT-BR)\n- **CODE/COMMENTS**: English for code and technical documentation\n- **API RESPONSES**: English for consistency\n- **ERROR MESSAGES**: Portuguese for user-facing errors\n\n### Performance Constraints\n- **MEMORY**: Efficient memory usage for multiple instances\n- **DATABASE**: Optimized queries with proper indexing\n- **CACHE**: Strategic caching for frequently accessed data\n- **CONNECTIONS**: Proper connection pooling and management\n\n### Security Constraints\n- **AUTHENTICATION**: API key validation for all endpoints\n- **AUTHORIZATION**: Instance-based access control\n- **INPUT VALIDATION**: Validate all inputs with JSONSchema7\n- **RATE LIMITING**: Prevent abuse with rate limiting\n- **WEBHOOK SECURITY**: Validate webhook signatures\n\n## Quality Standards\n- **TYPE SAFETY**: Full TypeScript coverage with strict mode\n- **ERROR HANDLING**: Comprehensive error scenarios with proper logging\n- **TESTING**: Unit and integration tests for critical paths\n- **MONITORING**: Proper logging and error tracking\n- **DOCUMENTATION**: Clear API documentation and code comments\n- **PERFORMANCE**: Optimized for high-throughput message processing\n- **SECURITY**: Secure by default with proper validation\n- **SCALABILITY**: Design for horizontal scaling\n\n## Evolution API Specific Development Patterns\n\n### Instance Management\n- **LIFECYCLE**: Proper instance creation, connection, and cleanup\n- **STATE MANAGEMENT**: Track connection status and health\n- **RECOVERY**: Automatic reconnection and error recovery\n- **MONITORING**: Health checks and status reporting\n\n### Message Processing\n- **QUEUE-BASED**: Use queues for message processing\n- **RETRY LOGIC**: Implement exponential backoff for failures\n- **MEDIA HANDLING**: Proper media upload and processing\n- **WEBHOOK DELIVERY**: Reliable webhook delivery with retries\n\n### Integration Patterns\n- **SERVICE LAYER**: Business logic in service classes\n- **DTO VALIDATION**: Input validation with JSONSchema7\n- **ERROR HANDLING**: Consistent error responses\n- **LOGGING**: Structured logging with correlation IDs\n\n### Database Patterns\n- **PRISMA**: Use Prisma ORM for all database operations\n- **TRANSACTIONS**: Use transactions for multi-step operations\n- **MIGRATIONS**: Proper migration management\n- **INDEXING**: Optimize queries with appropriate indexes","frontmatter":{"description":"Evolution API project-specific context and constraints","globs":null,"alwaysApply":true},"isInternal":false,"tokens":1674,"sizeBytes":7515},{"name":"controller-rules.mdc","path":".cursor/rules/specialized-rules/controller-rules.mdc","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/specialized-rules/controller-rules.mdc","title":"Cursor Rule: controller-rules","category":"cursor-rule","format":"mdc","content":"---\ndescription: Controller patterns for Evolution API\nglobs:\n  - \"src/api/controllers/**/*.ts\"\n  - \"src/api/integrations/**/controllers/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Controller Rules\n\n## Controller Structure Pattern\n\n### Standard Controller Class\n```typescript\nexport class ExampleController {\n  constructor(private readonly exampleService: ExampleService) {}\n\n  public async createExample(instance: InstanceDto, data: ExampleDto) {\n    return this.exampleService.create(instance, data);\n  }\n\n  public async findExample(instance: InstanceDto) {\n    return this.exampleService.find(instance);\n  }\n}\n```\n\n## Dependency Injection Pattern\n\n### Service Injection\n```typescript\n// CORRECT - Evolution API pattern\nexport class ChatController {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n\n  public async whatsappNumber({ instanceName }: InstanceDto, data: WhatsAppNumberDto) {\n    return await this.waMonitor.waInstances[instanceName].getWhatsAppNumbers(data);\n  }\n}\n\n// INCORRECT - Don't inject multiple services when waMonitor is sufficient\nexport class ChatController {\n  constructor(\n    private readonly waMonitor: WAMonitoringService,\n    private readonly prismaRepository: PrismaRepository, // ❌ Unnecessary if waMonitor has access\n    private readonly configService: ConfigService, // ❌ Unnecessary if waMonitor has access\n  ) {}\n}\n```\n\n## Method Signature Pattern\n\n### Instance Parameter Pattern\n```typescript\n// CORRECT - Evolution API pattern (destructuring instanceName)\npublic async fetchCatalog({ instanceName }: InstanceDto, data: getCatalogDto) {\n  return await this.waMonitor.waInstances[instanceName].fetchCatalog(instanceName, data);\n}\n\n// CORRECT - Alternative pattern for full instance (when using services)\npublic async createTemplate(instance: InstanceDto, data: TemplateDto) {\n  return this.templateService.create(instance, data);\n}\n\n// INCORRECT - Don't use generic method names\npublic async methodName(instance: InstanceDto, data: DataDto) { // ❌ Use specific names\n  return this.service.performAction(instance, data);\n}\n```\n\n## WAMonitor Access Pattern\n\n### Direct WAMonitor Usage\n```typescript\n// CORRECT - Standard pattern in controllers\nexport class CallController {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n\n  public async offerCall({ instanceName }: InstanceDto, data: OfferCallDto) {\n    return await this.waMonitor.waInstances[instanceName].offerCall(data);\n  }\n}\n```\n\n## Controller Registration Pattern\n\n### Server Module Registration\n```typescript\n// In server.module.ts\nexport const templateController = new TemplateController(templateService);\nexport const businessController = new BusinessController(waMonitor);\nexport const chatController = new ChatController(waMonitor);\nexport const callController = new CallController(waMonitor);\n```\n\n## Error Handling in Controllers\n\n### Let Services Handle Errors\n```typescript\n// CORRECT - Let service handle errors\npublic async fetchCatalog(instance: InstanceDto, data: getCatalogDto) {\n  return await this.waMonitor.waInstances[instance.instanceName].fetchCatalog(instance.instanceName, data);\n}\n\n// INCORRECT - Don't add try-catch in controllers unless specific handling needed\npublic async fetchCatalog(instance: InstanceDto, data: getCatalogDto) {\n  try {\n    return await this.waMonitor.waInstances[instance.instanceName].fetchCatalog(instance.instanceName, data);\n  } catch (error) {\n    throw error; // ❌ Unnecessary try-catch\n  }\n}\n```\n\n## Complex Controller Pattern\n\n### Instance Controller Pattern\n```typescript\nexport class InstanceController {\n  constructor(\n    private readonly waMonitor: WAMonitoringService,\n    private readonly configService: ConfigService,\n    private readonly prismaRepository: PrismaRepository,\n    private readonly eventEmitter: EventEmitter2,\n    private readonly chatwootService: ChatwootService,\n    private readonly settingsService: SettingsService,\n    private readonly proxyService: ProxyController,\n    private readonly cache: CacheService,\n    private readonly chatwootCache: CacheService,\n    private readonly baileysCache: CacheService,\n    private readonly providerFiles: ProviderFiles,\n  ) {}\n\n  private readonly logger = new Logger('InstanceController');\n\n  // Multiple methods handling different aspects\n  public async createInstance(data: InstanceDto) {\n    // Complex instance creation logic\n  }\n\n  public async deleteInstance({ instanceName }: InstanceDto) {\n    // Complex instance deletion logic\n  }\n}\n```\n\n## Channel Controller Pattern\n\n### Base Channel Controller\n```typescript\nexport class ChannelController {\n  public prismaRepository: PrismaRepository;\n  public waMonitor: WAMonitoringService;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    this.prisma = prismaRepository;\n    this.monitor = waMonitor;\n  }\n\n  // Getters and setters for dependency access\n  public set prisma(prisma: PrismaRepository) {\n    this.prismaRepository = prisma;\n  }\n\n  public get prisma() {\n    return this.prismaRepository;\n  }\n\n  public set monitor(waMonitor: WAMonitoringService) {\n    this.waMonitor = waMonitor;\n  }\n\n  public get monitor() {\n    return this.waMonitor;\n  }\n}\n```\n\n### Extended Channel Controller\n```typescript\nexport class EvolutionController extends ChannelController implements ChannelControllerInterface {\n  private readonly logger = new Logger('EvolutionController');\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n  }\n\n  integrationEnabled: boolean;\n\n  public async receiveWebhook(data: any) {\n    const numberId = data.numberId;\n\n    if (!numberId) {\n      this.logger.error('WebhookService -> receiveWebhookEvolution -> numberId not found');\n      return;\n    }\n\n    const instance = await this.prismaRepository.instance.findFirst({\n      where: { number: numberId },\n    });\n\n    if (!instance) {\n      this.logger.error('WebhookService -> receiveWebhook -> instance not found');\n      return;\n    }\n\n    await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data);\n\n    return {\n      status: 'success',\n    };\n  }\n}\n```\n\n## Chatbot Controller Pattern\n\n### Base Chatbot Controller\n```typescript\nexport abstract class BaseChatbotController<BotType = any, BotData extends BaseChatbotDto = BaseChatbotDto>\n  extends ChatbotController\n  implements ChatbotControllerInterface\n{\n  public readonly logger: Logger;\n  integrationEnabled: boolean;\n  \n  // Abstract methods to be implemented\n  protected abstract readonly integrationName: string;\n  protected abstract processBot(/* parameters */): Promise<void>;\n  protected abstract getFallbackBotId(settings: any): string | undefined;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n  }\n\n  // Base implementation methods\n  public async createBot(instance: InstanceDto, data: BotData) {\n    // Common bot creation logic\n  }\n}\n```\n\n## Method Naming Conventions\n\n### Standard Method Names\n- `create*()` - Create operations\n- `find*()` - Find operations  \n- `fetch*()` - Fetch from external APIs\n- `send*()` - Send operations\n- `receive*()` - Receive webhook/data\n- `handle*()` - Handle specific actions\n- `offer*()` - Offer services (like calls)\n\n## Return Patterns\n\n### Direct Return Pattern\n```typescript\n// CORRECT - Direct return from service\npublic async createTemplate(instance: InstanceDto, data: TemplateDto) {\n  return this.templateService.create(instance, data);\n}\n\n// CORRECT - Direct return from waMonitor\npublic async offerCall({ instanceName }: InstanceDto, data: OfferCallDto) {\n  return await this.waMonitor.waInstances[instanceName].offerCall(data);\n}\n```\n\n## Controller Testing Pattern\n\n### Unit Test Structure\n```typescript\ndescribe('ExampleController', () => {\n  let controller: ExampleController;\n  let service: jest.Mocked<ExampleService>;\n\n  beforeEach(() => {\n    const mockService = {\n      create: jest.fn(),\n      find: jest.fn(),\n    };\n\n    controller = new ExampleController(mockService as any);\n    service = mockService as any;\n  });\n\n  describe('createExample', () => {\n    it('should call service create method', async () => {\n      const instance = { instanceName: 'test' };\n      const data = { test: 'data' };\n      const expectedResult = { success: true };\n\n      service.create.mockResolvedValue(expectedResult);\n\n      const result = await controller.createExample(instance, data);\n\n      expect(service.create).toHaveBeenCalledWith(instance, data);\n      expect(result).toEqual(expectedResult);\n    });\n  });\n});\n```\n\n## Interface Implementation\n\n### Controller Interface Pattern\n```typescript\nexport interface ChannelControllerInterface {\n  integrationEnabled: boolean;\n}\n\nexport interface ChatbotControllerInterface {\n  integrationEnabled: boolean;\n  createBot(instance: InstanceDto, data: any): Promise<any>;\n  findBot(instance: InstanceDto): Promise<any>;\n  // ... other methods\n}\n```\n\n## Controller Organization\n\n### File Naming Convention\n- `*.controller.ts` - Main controllers\n- `*/*.controller.ts` - Integration-specific controllers\n\n### Method Organization\n1. Constructor\n2. Public methods (alphabetical order)\n3. Private methods (if any)\n\n### Import Organization\n```typescript\n// DTOs first\nimport { InstanceDto } from '@api/dto/instance.dto';\nimport { ExampleDto } from '@api/dto/example.dto';\n\n// Services\nimport { ExampleService } from '@api/services/example.service';\n\n// Types\nimport { WAMonitoringService } from '@api/services/monitor.service';\n```","frontmatter":{"description":"Controller patterns for Evolution API","globs":["src/api/controllers/**/*.ts","src/api/integrations/**/controllers/*.ts"],"alwaysApply":false},"isInternal":false,"tokens":2073,"sizeBytes":9567},{"name":"dto-rules.mdc","path":".cursor/rules/specialized-rules/dto-rules.mdc","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/specialized-rules/dto-rules.mdc","title":"Cursor Rule: dto-rules","category":"cursor-rule","format":"mdc","content":"---\ndescription: DTO patterns and validation for Evolution API\nglobs:\n  - \"src/api/dto/**/*.ts\"\n  - \"src/api/integrations/**/dto/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API DTO Rules\n\n## DTO Structure Pattern\n\n### Basic DTO Class\n```typescript\nexport class ExampleDto {\n  name: string;\n  category: string;\n  allowCategoryChange: boolean;\n  language: string;\n  components: any;\n  webhookUrl?: string;\n}\n```\n\n## Inheritance Pattern\n\n### DTO Inheritance\n```typescript\n// CORRECT - Evolution API pattern\nexport class InstanceDto extends IntegrationDto {\n  instanceName: string;\n  instanceId?: string;\n  qrcode?: boolean;\n  businessId?: string;\n  number?: string;\n  integration?: string;\n  token?: string;\n  status?: string;\n  ownerJid?: string;\n  profileName?: string;\n  profilePicUrl?: string;\n  \n  // Settings\n  rejectCall?: boolean;\n  msgCall?: string;\n  groupsIgnore?: boolean;\n  alwaysOnline?: boolean;\n  readMessages?: boolean;\n  readStatus?: boolean;\n  syncFullHistory?: boolean;\n  wavoipToken?: string;\n  \n  // Proxy settings\n  proxyHost?: string;\n  proxyPort?: string;\n  proxyProtocol?: string;\n  proxyUsername?: string;\n  proxyPassword?: string;\n  \n  // Webhook configuration\n  webhook?: {\n    enabled?: boolean;\n    events?: string[];\n    headers?: JsonValue;\n    url?: string;\n    byEvents?: boolean;\n    base64?: boolean;\n  };\n  \n  // Chatwoot integration\n  chatwootAccountId?: string;\n  chatwootConversationPending?: boolean;\n  chatwootAutoCreate?: boolean;\n  chatwootDaysLimitImportMessages?: number;\n  chatwootImportContacts?: boolean;\n  chatwootImportMessages?: boolean;\n  chatwootLogo?: string;\n  chatwootMergeBrazilContacts?: boolean;\n  chatwootNameInbox?: string;\n  chatwootOrganization?: string;\n  chatwootReopenConversation?: boolean;\n  chatwootSignMsg?: boolean;\n  chatwootToken?: string;\n  chatwootUrl?: string;\n}\n```\n\n## Base DTO Pattern\n\n### Base Chatbot DTO\n```typescript\n/**\n * Base DTO for all chatbot integrations\n * Contains common properties shared by all chatbot types\n */\nexport class BaseChatbotDto {\n  enabled?: boolean;\n  description: string;\n  expire?: number;\n  keywordFinish?: string;\n  delayMessage?: number;\n  unknownMessage?: string;\n  listeningFromMe?: boolean;\n  stopBotFromMe?: boolean;\n  keepOpen?: boolean;\n  debounceTime?: number;\n  triggerType: TriggerType;\n  triggerOperator?: TriggerOperator;\n  triggerValue?: string;\n  ignoreJids?: string[];\n  splitMessages?: boolean;\n  timePerChar?: number;\n}\n\n/**\n * Base settings DTO for all chatbot integrations\n */\nexport class BaseChatbotSettingDto {\n  expire?: number;\n  keywordFinish?: string;\n  delayMessage?: number;\n  unknownMessage?: string;\n  listeningFromMe?: boolean;\n  stopBotFromMe?: boolean;\n  keepOpen?: boolean;\n  debounceTime?: number;\n  ignoreJids?: string[];\n  splitMessages?: boolean;\n  timePerChar?: number;\n}\n```\n\n## Message DTO Patterns\n\n### Send Message DTOs\n```typescript\nexport class Metadata {\n  number: string;\n  delay?: number;\n}\n\nexport class SendTextDto extends Metadata {\n  text: string;\n  linkPreview?: boolean;\n  mentionsEveryOne?: boolean;\n  mentioned?: string[];\n}\n\nexport class SendListDto extends Metadata {\n  title: string;\n  description: string;\n  buttonText: string;\n  footerText?: string;\n\n  sections: Section[];\n}\n\nexport class ContactMessage {\n  fullName: string;\n  wuid: string;\n  phoneNumber: string;\n  organization?: string;\n  email?: string;\n  url?: string;\n}\n\nexport class SendTemplateDto extends Metadata {\n  name: string;\n  language: string;\n  components: any;\n}\n```\n\n## Simple DTO Patterns\n\n### Basic DTOs\n```typescript\nexport class NumberDto {\n  number: string;\n}\n\nexport class LabelDto {\n  id?: string;\n  name: string;\n  color: string;\n  predefinedId?: string;\n}\n\nexport class HandleLabelDto {\n  number: string;\n  labelId: string;\n}\n\nexport class ProfileNameDto {\n  name: string;\n}\n\nexport class WhatsAppNumberDto {\n  numbers: string[];\n}\n```\n\n## Complex DTO Patterns\n\n### Business DTOs\n```typescript\nexport class getCatalogDto {\n  number?: string;\n  limit?: number;\n  cursor?: string;\n}\n\nexport class getCollectionsDto {\n  number?: string;\n  limit?: number;\n  cursor?: string;\n}\n\nexport class NumberBusiness {\n  number: string;\n  name?: string;\n  description?: string;\n  email?: string;\n  websites?: string[];\n  latitude?: number;\n  longitude?: number;\n  address?: string;\n  profilehandle?: string;\n}\n```\n\n## Settings DTO Pattern\n\n### Settings Configuration\n```typescript\nexport class SettingsDto {\n  rejectCall?: boolean;\n  msgCall?: string;\n  groupsIgnore?: boolean;\n  alwaysOnline?: boolean;\n  readMessages?: boolean;\n  readStatus?: boolean;\n  syncFullHistory?: boolean;\n  wavoipToken?: string;\n}\n\nexport class ProxyDto {\n  host?: string;\n  port?: string;\n  protocol?: string;\n  username?: string;\n  password?: string;\n}\n```\n\n## Presence DTO Pattern\n\n### WhatsApp Presence\n```typescript\nexport class SetPresenceDto {\n  presence: WAPresence;\n}\n\nexport class SendPresenceDto {\n  number: string;\n  presence: WAPresence;\n}\n```\n\n## DTO Structure (No Decorators)\n\n### Simple DTO Classes (Evolution API Pattern)\n```typescript\n// CORRECT - Evolution API pattern (no decorators)\nexport class ExampleDto {\n  name: string;\n  description?: string;\n  enabled: boolean;\n  items?: string[];\n  timeout?: number;\n}\n\n// INCORRECT - Don't use class-validator decorators\nexport class ValidatedDto {\n  @IsString() // ❌ Evolution API doesn't use decorators\n  name: string;\n}\n```\n\n## Type Safety Patterns\n\n### Prisma Type Integration\n```typescript\nimport { JsonValue } from '@prisma/client/runtime/library';\nimport { WAPresence } from 'baileys';\nimport { TriggerOperator, TriggerType } from '@prisma/client';\n\nexport class TypeSafeDto {\n  presence: WAPresence;\n  triggerType: TriggerType;\n  triggerOperator?: TriggerOperator;\n  metadata?: JsonValue;\n}\n```\n\n## DTO Documentation\n\n### JSDoc Comments\n```typescript\n/**\n * DTO for creating WhatsApp templates\n * Used by Meta Business API integration\n */\nexport class TemplateDto {\n  /** Template name - must be unique */\n  name: string;\n  \n  /** Template category (MARKETING, UTILITY, AUTHENTICATION) */\n  category: string;\n  \n  /** Whether category can be changed after creation */\n  allowCategoryChange: boolean;\n  \n  /** Language code (e.g., 'pt_BR', 'en_US') */\n  language: string;\n  \n  /** Template components (header, body, footer, buttons) */\n  components: any;\n  \n  /** Optional webhook URL for template status updates */\n  webhookUrl?: string;\n}\n```\n\n## DTO Naming Conventions\n\n### Standard Naming Patterns\n- `*Dto` - Data transfer objects\n- `Create*Dto` - Creation DTOs\n- `Update*Dto` - Update DTOs\n- `Send*Dto` - Message sending DTOs\n- `Get*Dto` - Query DTOs\n- `Handle*Dto` - Action DTOs\n\n## File Organization\n\n### DTO File Structure\n```\nsrc/api/dto/\n├── instance.dto.ts          # Main instance DTO\n├── template.dto.ts          # Template management\n├── sendMessage.dto.ts       # Message sending DTOs\n├── chat.dto.ts             # Chat operations\n├── business.dto.ts         # Business API DTOs\n├── group.dto.ts            # Group management\n├── label.dto.ts            # Label management\n├── proxy.dto.ts            # Proxy configuration\n├── settings.dto.ts         # Instance settings\n└── call.dto.ts             # Call operations\n```\n\n## Integration DTO Patterns\n\n### Chatbot Integration DTOs\n```typescript\n// Base for all chatbot DTOs\nexport class BaseChatbotDto {\n  enabled?: boolean;\n  description: string;\n  // ... common properties\n}\n\n// Specific chatbot DTOs extend base\nexport class TypebotDto extends BaseChatbotDto {\n  url: string;\n  typebot: string;\n  // ... typebot-specific properties\n}\n\nexport class OpenaiDto extends BaseChatbotDto {\n  apiKey: string;\n  model: string;\n  // ... openai-specific properties\n}\n```\n\n## DTO Testing Pattern\n\n### DTO Validation Tests\n```typescript\ndescribe('ExampleDto', () => {\n  it('should validate required fields', () => {\n    const dto = new ExampleDto();\n    dto.name = 'test';\n    dto.category = 'MARKETING';\n    dto.allowCategoryChange = true;\n    dto.language = 'pt_BR';\n    dto.components = {};\n\n    expect(dto.name).toBe('test');\n    expect(dto.category).toBe('MARKETING');\n  });\n\n  it('should handle optional fields', () => {\n    const dto = new ExampleDto();\n    dto.name = 'test';\n    dto.category = 'MARKETING';\n    dto.allowCategoryChange = true;\n    dto.language = 'pt_BR';\n    dto.components = {};\n    dto.webhookUrl = 'https://example.com/webhook';\n\n    expect(dto.webhookUrl).toBe('https://example.com/webhook');\n  });\n});\n```\n\n## DTO Transformation\n\n### Request to DTO Mapping (Evolution API Pattern)\n```typescript\n// CORRECT - Evolution API uses RouterBroker dataValidate\nconst response = await this.dataValidate<ExampleDto>({\n  request: req,\n  schema: exampleSchema, // JSONSchema7\n  ClassRef: ExampleDto,\n  execute: (instance, data) => controller.method(instance, data),\n});\n\n// INCORRECT - Don't use class-validator\nconst dto = plainToClass(ExampleDto, req.body); // ❌ Not used in Evolution API\nconst errors = await validate(dto); // ❌ Not used in Evolution API\n```","frontmatter":{"description":"DTO patterns and validation for Evolution API","globs":["src/api/dto/**/*.ts","src/api/integrations/**/dto/*.ts"],"alwaysApply":false},"isInternal":false,"tokens":2220,"sizeBytes":9083},{"name":"guard-rules.mdc","path":".cursor/rules/specialized-rules/guard-rules.mdc","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/specialized-rules/guard-rules.mdc","title":"Cursor Rule: guard-rules","category":"cursor-rule","format":"mdc","content":"---\ndescription: Guard patterns for authentication and authorization in Evolution API\nglobs:\n  - \"src/api/guards/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Guard Rules\n\n## Guard Structure Pattern\n\n### Standard Guard Function\n```typescript\nimport { NextFunction, Request, Response } from 'express';\nimport { Logger } from '@config/logger.config';\nimport { UnauthorizedException, ForbiddenException } from '@exceptions';\n\nconst logger = new Logger('GUARD');\n\nasync function guardFunction(req: Request, _: Response, next: NextFunction) {\n  // Guard logic here\n  \n  if (validationFails) {\n    throw new UnauthorizedException();\n  }\n  \n  return next();\n}\n\nexport const guardName = { guardFunction };\n```\n\n## Authentication Guard Pattern\n\n### API Key Authentication\n```typescript\nasync function apikey(req: Request, _: Response, next: NextFunction) {\n  const env = configService.get<Auth>('AUTHENTICATION').API_KEY;\n  const key = req.get('apikey');\n  const db = configService.get<Database>('DATABASE');\n\n  if (!key) {\n    throw new UnauthorizedException();\n  }\n\n  // Global API key check\n  if (env.KEY === key) {\n    return next();\n  }\n\n  // Special routes handling\n  if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) {\n    throw new ForbiddenException('Missing global api key', 'The global api key must be set');\n  }\n\n  const param = req.params as unknown as InstanceDto;\n\n  try {\n    if (param?.instanceName) {\n      const instance = await prismaRepository.instance.findUnique({\n        where: { name: param.instanceName },\n      });\n      if (instance.token === key) {\n        return next();\n      }\n    } else {\n      if (req.originalUrl.includes('/instance/fetchInstances') && db.SAVE_DATA.INSTANCE) {\n        const instanceByKey = await prismaRepository.instance.findFirst({\n          where: { token: key },\n        });\n        if (instanceByKey) {\n          return next();\n        }\n      }\n    }\n  } catch (error) {\n    logger.error(error);\n  }\n\n  throw new UnauthorizedException();\n}\n\nexport const authGuard = { apikey };\n```\n\n## Instance Validation Guards\n\n### Instance Exists Guard\n```typescript\nasync function getInstance(instanceName: string) {\n  try {\n    const cacheConf = configService.get<CacheConf>('CACHE');\n\n    const exists = !!waMonitor.waInstances[instanceName];\n\n    if (cacheConf.REDIS.ENABLED && cacheConf.REDIS.SAVE_INSTANCES) {\n      const keyExists = await cache.has(instanceName);\n      return exists || keyExists;\n    }\n\n    return exists || (await prismaRepository.instance.findMany({ where: { name: instanceName } })).length > 0;\n  } catch (error) {\n    throw new InternalServerErrorException(error?.toString());\n  }\n}\n\nexport async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) {\n  if (req.originalUrl.includes('/instance/create')) {\n    return next();\n  }\n\n  const param = req.params as unknown as InstanceDto;\n  if (!param?.instanceName) {\n    throw new BadRequestException('\"instanceName\" not provided.');\n  }\n\n  if (!(await getInstance(param.instanceName))) {\n    throw new NotFoundException(`The \"${param.instanceName}\" instance does not exist`);\n  }\n\n  next();\n}\n```\n\n### Instance Logged Guard\n```typescript\nexport async function instanceLoggedGuard(req: Request, _: Response, next: NextFunction) {\n  if (req.originalUrl.includes('/instance/create')) {\n    const instance = req.body as InstanceDto;\n    if (await getInstance(instance.instanceName)) {\n      throw new ForbiddenException(`This name \"${instance.instanceName}\" is already in use.`);\n    }\n\n    if (waMonitor.waInstances[instance.instanceName]) {\n      delete waMonitor.waInstances[instance.instanceName];\n    }\n  }\n\n  next();\n}\n```\n\n## Telemetry Guard Pattern\n\n### Telemetry Collection\n```typescript\nclass Telemetry {\n  public collectTelemetry(req: Request, res: Response, next: NextFunction): void {\n    // Collect telemetry data\n    const telemetryData = {\n      route: req.originalUrl,\n      method: req.method,\n      timestamp: new Date(),\n      userAgent: req.get('User-Agent'),\n    };\n\n    // Send telemetry asynchronously (don't block request)\n    setImmediate(() => {\n      this.sendTelemetry(telemetryData);\n    });\n\n    next();\n  }\n\n  private async sendTelemetry(data: any): Promise<void> {\n    try {\n      // Send telemetry data\n    } catch (error) {\n      // Silently fail - don't affect main request\n    }\n  }\n}\n\nexport default Telemetry;\n```\n\n## Guard Composition Pattern\n\n### Multiple Guards Usage\n```typescript\n// In router setup\nconst guards = [instanceExistsGuard, instanceLoggedGuard, authGuard['apikey']];\n\nrouter\n  .use('/instance', new InstanceRouter(configService, ...guards).router)\n  .use('/message', new MessageRouter(...guards).router)\n  .use('/chat', new ChatRouter(...guards).router);\n```\n\n## Error Handling in Guards\n\n### Proper Exception Throwing\n```typescript\n// CORRECT - Use proper HTTP exceptions\nif (!apiKey) {\n  throw new UnauthorizedException('API key required');\n}\n\nif (instanceExists) {\n  throw new ForbiddenException('Instance already exists');\n}\n\nif (!instanceFound) {\n  throw new NotFoundException('Instance not found');\n}\n\nif (validationFails) {\n  throw new BadRequestException('Invalid request parameters');\n}\n\n// INCORRECT - Don't use generic Error\nif (!apiKey) {\n  throw new Error('API key required'); // ❌ Use specific exceptions\n}\n```\n\n## Configuration Access in Guards\n\n### Config Service Usage\n```typescript\nasync function configAwareGuard(req: Request, _: Response, next: NextFunction) {\n  const authConfig = configService.get<Auth>('AUTHENTICATION');\n  const cacheConfig = configService.get<CacheConf>('CACHE');\n  const dbConfig = configService.get<Database>('DATABASE');\n\n  // Use configuration for guard logic\n  if (authConfig.API_KEY.KEY === providedKey) {\n    return next();\n  }\n\n  throw new UnauthorizedException();\n}\n```\n\n## Database Access in Guards\n\n### Prisma Repository Usage\n```typescript\nasync function databaseGuard(req: Request, _: Response, next: NextFunction) {\n  try {\n    const param = req.params as unknown as InstanceDto;\n    \n    const instance = await prismaRepository.instance.findUnique({\n      where: { name: param.instanceName },\n    });\n\n    if (!instance) {\n      throw new NotFoundException('Instance not found');\n    }\n\n    // Additional validation logic\n    if (instance.status !== 'active') {\n      throw new ForbiddenException('Instance not active');\n    }\n\n    return next();\n  } catch (error) {\n    logger.error('Database guard error:', error);\n    throw new InternalServerErrorException('Database access failed');\n  }\n}\n```\n\n## Cache Integration in Guards\n\n### Cache Service Usage\n```typescript\nasync function cacheAwareGuard(req: Request, _: Response, next: NextFunction) {\n  const cacheConf = configService.get<CacheConf>('CACHE');\n  \n  if (cacheConf.REDIS.ENABLED) {\n    const cached = await cache.get(`guard:${req.params.instanceName}`);\n    if (cached) {\n      // Use cached validation result\n      return next();\n    }\n  }\n\n  // Perform validation and cache result\n  const isValid = await performValidation(req.params.instanceName);\n  \n  if (cacheConf.REDIS.ENABLED) {\n    await cache.set(`guard:${req.params.instanceName}`, isValid, 300); // 5 min TTL\n  }\n\n  if (isValid) {\n    return next();\n  }\n\n  throw new UnauthorizedException();\n}\n```\n\n## Logging in Guards\n\n### Structured Logging\n```typescript\nconst logger = new Logger('GUARD');\n\nasync function loggedGuard(req: Request, _: Response, next: NextFunction) {\n  logger.log(`Guard validation started for ${req.originalUrl}`);\n\n  try {\n    // Guard logic\n    const isValid = await validateRequest(req);\n    \n    if (isValid) {\n      logger.log(`Guard validation successful for ${req.params.instanceName}`);\n      return next();\n    }\n\n    logger.warn(`Guard validation failed for ${req.params.instanceName}`);\n    throw new UnauthorizedException();\n  } catch (error) {\n    logger.error(`Guard validation error: ${error.message}`, error.stack);\n    throw error;\n  }\n}\n```\n\n## Guard Testing Pattern\n\n### Unit Test Structure\n```typescript\ndescribe('authGuard', () => {\n  let req: Partial<Request>;\n  let res: Partial<Response>;\n  let next: NextFunction;\n\n  beforeEach(() => {\n    req = {\n      get: jest.fn(),\n      params: {},\n      originalUrl: '/test',\n    };\n    res = {};\n    next = jest.fn();\n  });\n\n  describe('apikey', () => {\n    it('should pass with valid global API key', async () => {\n      (req.get as jest.Mock).mockReturnValue('valid-global-key');\n      \n      await authGuard.apikey(req as Request, res as Response, next);\n      \n      expect(next).toHaveBeenCalled();\n    });\n\n    it('should throw UnauthorizedException with no API key', async () => {\n      (req.get as jest.Mock).mockReturnValue(undefined);\n      \n      await expect(\n        authGuard.apikey(req as Request, res as Response, next)\n      ).rejects.toThrow(UnauthorizedException);\n    });\n\n    it('should pass with valid instance token', async () => {\n      (req.get as jest.Mock).mockReturnValue('instance-token');\n      req.params = { instanceName: 'test-instance' };\n      \n      // Mock prisma repository\n      jest.spyOn(prismaRepository.instance, 'findUnique').mockResolvedValue({\n        token: 'instance-token',\n      } as any);\n      \n      await authGuard.apikey(req as Request, res as Response, next);\n      \n      expect(next).toHaveBeenCalled();\n    });\n  });\n});\n```\n\n## Guard Performance Considerations\n\n### Efficient Validation\n```typescript\n// CORRECT - Efficient guard with early returns\nasync function efficientGuard(req: Request, _: Response, next: NextFunction) {\n  // Quick checks first\n  if (req.originalUrl.includes('/public')) {\n    return next(); // Skip validation for public routes\n  }\n\n  const apiKey = req.get('apikey');\n  if (!apiKey) {\n    throw new UnauthorizedException(); // Fail fast\n  }\n\n  // More expensive checks only if needed\n  if (apiKey === globalKey) {\n    return next(); // Skip database check\n  }\n\n  // Database check only as last resort\n  const isValid = await validateInDatabase(apiKey);\n  if (isValid) {\n    return next();\n  }\n\n  throw new UnauthorizedException();\n}\n\n// INCORRECT - Inefficient guard\nasync function inefficientGuard(req: Request, _: Response, next: NextFunction) {\n  // Always do expensive database check first\n  const dbResult = await expensiveDatabaseQuery(); // ❌ Expensive operation first\n  \n  const apiKey = req.get('apikey');\n  if (!apiKey && dbResult) { // ❌ Complex logic\n    throw new UnauthorizedException();\n  }\n  \n  next();\n}\n```","frontmatter":{"description":"Guard patterns for authentication and authorization in Evolution API","globs":["src/api/guards/**/*.ts"],"alwaysApply":false},"isInternal":false,"tokens":2365,"sizeBytes":10541},{"name":"integration-channel-rules.mdc","path":".cursor/rules/specialized-rules/integration-channel-rules.mdc","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/specialized-rules/integration-channel-rules.mdc","title":"Cursor Rule: integration-channel-rules","category":"cursor-rule","format":"mdc","content":"---\ndescription: Channel integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/channel/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Channel Integration Rules\n\n## Channel Controller Pattern\n\n### Base Channel Controller\n```typescript\nexport interface ChannelControllerInterface {\n  integrationEnabled: boolean;\n}\n\nexport class ChannelController {\n  public prismaRepository: PrismaRepository;\n  public waMonitor: WAMonitoringService;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    this.prisma = prismaRepository;\n    this.monitor = waMonitor;\n  }\n\n  public set prisma(prisma: PrismaRepository) {\n    this.prismaRepository = prisma;\n  }\n\n  public get prisma() {\n    return this.prismaRepository;\n  }\n\n  public set monitor(waMonitor: WAMonitoringService) {\n    this.waMonitor = waMonitor;\n  }\n\n  public get monitor() {\n    return this.waMonitor;\n  }\n\n  public init(instanceData: InstanceDto, data: ChannelDataType) {\n    if (!instanceData.token && instanceData.integration === Integration.WHATSAPP_BUSINESS) {\n      throw new BadRequestException('token is required');\n    }\n\n    if (instanceData.integration === Integration.WHATSAPP_BUSINESS) {\n      return new BusinessStartupService(/* dependencies */);\n    }\n\n    if (instanceData.integration === Integration.EVOLUTION) {\n      return new EvolutionStartupService(/* dependencies */);\n    }\n\n    if (instanceData.integration === Integration.WHATSAPP_BAILEYS) {\n      return new BaileysStartupService(/* dependencies */);\n    }\n\n    return null;\n  }\n}\n```\n\n### Extended Channel Controller\n```typescript\nexport class EvolutionController extends ChannelController implements ChannelControllerInterface {\n  private readonly logger = new Logger('EvolutionController');\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n  }\n\n  integrationEnabled: boolean;\n\n  public async receiveWebhook(data: any) {\n    const numberId = data.numberId;\n\n    if (!numberId) {\n      this.logger.error('WebhookService -> receiveWebhookEvolution -> numberId not found');\n      return;\n    }\n\n    const instance = await this.prismaRepository.instance.findFirst({\n      where: { number: numberId },\n    });\n\n    if (!instance) {\n      this.logger.error('WebhookService -> receiveWebhook -> instance not found');\n      return;\n    }\n\n    await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data);\n\n    return {\n      status: 'success',\n    };\n  }\n}\n```\n\n## Channel Service Pattern\n\n### Base Channel Service\n```typescript\nexport class ChannelStartupService {\n  constructor(\n    private readonly configService: ConfigService,\n    private readonly eventEmitter: EventEmitter2,\n    private readonly prismaRepository: PrismaRepository,\n    public readonly cache: CacheService,\n    public readonly chatwootCache: CacheService,\n  ) {}\n\n  public readonly logger = new Logger('ChannelStartupService');\n\n  public client: WASocket;\n  public readonly instance: wa.Instance = {};\n  public readonly localChatwoot: wa.LocalChatwoot = {};\n  public readonly localProxy: wa.LocalProxy = {};\n  public readonly localSettings: wa.LocalSettings = {};\n  public readonly localWebhook: wa.LocalWebHook = {};\n\n  public setInstance(instance: InstanceDto) {\n    this.logger.setInstance(instance.instanceName);\n\n    this.instance.name = instance.instanceName;\n    this.instance.id = instance.instanceId;\n    this.instance.integration = instance.integration;\n    this.instance.number = instance.number;\n    this.instance.token = instance.token;\n    this.instance.businessId = instance.businessId;\n\n    if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {\n      this.chatwootService.eventWhatsapp(\n        Events.STATUS_INSTANCE,\n        { instanceName: this.instance.name },\n        {\n          instance: this.instance.name,\n          status: 'created',\n        },\n      );\n    }\n  }\n\n  public set instanceName(name: string) {\n    this.logger.setInstance(name);\n    this.instance.name = name;\n  }\n\n  public get instanceName() {\n    return this.instance.name;\n  }\n}\n```\n\n### Extended Channel Service\n```typescript\nexport class EvolutionStartupService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache);\n  }\n\n  public async sendMessage(data: SendTextDto): Promise<any> {\n    // Evolution-specific message sending logic\n    const response = await this.evolutionApiCall('/send-message', data);\n    return response;\n  }\n\n  public async connectToWhatsapp(data: any): Promise<void> {\n    // Evolution-specific connection logic\n    this.logger.log('Connecting to Evolution API');\n    \n    // Set up webhook listeners\n    this.setupWebhookHandlers();\n    \n    // Initialize connection\n    await this.initializeConnection(data);\n  }\n\n  private async evolutionApiCall(endpoint: string, data: any): Promise<any> {\n    const config = this.configService.get<Evolution>('EVOLUTION');\n    \n    try {\n      const response = await axios.post(`${config.API_URL}${endpoint}`, data, {\n        headers: {\n          'Authorization': `Bearer ${this.instance.token}`,\n          'Content-Type': 'application/json',\n        },\n      });\n      \n      return response.data;\n    } catch (error) {\n      this.logger.error(`Evolution API call failed: ${error.message}`);\n      throw new InternalServerErrorException('Evolution API call failed');\n    }\n  }\n\n  private setupWebhookHandlers(): void {\n    // Set up webhook event handlers\n  }\n\n  private async initializeConnection(data: any): Promise<void> {\n    // Initialize connection with Evolution API\n  }\n}\n```\n\n## Business API Service Pattern\n\n### Meta Business Service\n```typescript\nexport class BusinessStartupService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n    baileysCache: CacheService,\n    providerFiles: ProviderFiles,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache);\n  }\n\n  public async sendMessage(data: SendTextDto): Promise<any> {\n    const businessConfig = this.configService.get<WaBusiness>('WA_BUSINESS');\n    \n    const payload = {\n      messaging_product: 'whatsapp',\n      to: data.number,\n      type: 'text',\n      text: {\n        body: data.text,\n      },\n    };\n\n    try {\n      const response = await axios.post(\n        `${businessConfig.URL}/${businessConfig.VERSION}/${this.instance.businessId}/messages`,\n        payload,\n        {\n          headers: {\n            'Authorization': `Bearer ${this.instance.token}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      return response.data;\n    } catch (error) {\n      this.logger.error(`Business API call failed: ${error.message}`);\n      throw new BadRequestException('Failed to send message via Business API');\n    }\n  }\n\n  public async receiveWebhook(data: any): Promise<void> {\n    // Process incoming webhook from Meta Business API\n    const { entry } = data;\n    \n    for (const entryItem of entry) {\n      const { changes } = entryItem;\n      \n      for (const change of changes) {\n        if (change.field === 'messages') {\n          await this.processMessage(change.value);\n        }\n      }\n    }\n  }\n\n  private async processMessage(messageData: any): Promise<void> {\n    // Process incoming message from Business API\n    const { messages, contacts } = messageData;\n    \n    if (messages) {\n      for (const message of messages) {\n        await this.handleIncomingMessage(message, contacts);\n      }\n    }\n  }\n\n  private async handleIncomingMessage(message: any, contacts: any[]): Promise<void> {\n    // Handle individual message\n    const contact = contacts?.find(c => c.wa_id === message.from);\n    \n    // Emit event for message processing\n    this.eventEmitter.emit(Events.MESSAGES_UPSERT, {\n      instanceName: this.instance.name,\n      message,\n      contact,\n    });\n  }\n}\n```\n\n## Baileys Service Pattern\n\n### Baileys Integration Service\n```typescript\nexport class BaileysStartupService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n    baileysCache: CacheService,\n    providerFiles: ProviderFiles,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache);\n  }\n\n  public async connectToWhatsapp(): Promise<void> {\n    const authPath = path.join(INSTANCE_DIR, this.instance.name);\n    const { state, saveCreds } = await useMultiFileAuthState(authPath);\n\n    this.client = makeWASocket({\n      auth: state,\n      logger: P({ level: 'error' }),\n      printQRInTerminal: false,\n      browser: ['Evolution API', 'Chrome', '4.0.0'],\n      defaultQueryTimeoutMs: 60000,\n    });\n\n    this.setupEventHandlers();\n    this.client.ev.on('creds.update', saveCreds);\n  }\n\n  private setupEventHandlers(): void {\n    this.client.ev.on('connection.update', (update) => {\n      this.handleConnectionUpdate(update);\n    });\n\n    this.client.ev.on('messages.upsert', ({ messages, type }) => {\n      this.handleIncomingMessages(messages, type);\n    });\n\n    this.client.ev.on('messages.update', (updates) => {\n      this.handleMessageUpdates(updates);\n    });\n\n    this.client.ev.on('contacts.upsert', (contacts) => {\n      this.handleContactsUpdate(contacts);\n    });\n\n    this.client.ev.on('chats.upsert', (chats) => {\n      this.handleChatsUpdate(chats);\n    });\n  }\n\n  private async handleConnectionUpdate(update: ConnectionUpdate): Promise<void> {\n    const { connection, lastDisconnect, qr } = update;\n\n    if (qr) {\n      this.instance.qrcode = {\n        count: this.instance.qrcode?.count ? this.instance.qrcode.count + 1 : 1,\n        base64: qr,\n      };\n\n      this.eventEmitter.emit(Events.QRCODE_UPDATED, {\n        instanceName: this.instance.name,\n        qrcode: this.instance.qrcode,\n      });\n    }\n\n    if (connection === 'close') {\n      const shouldReconnect = (lastDisconnect?.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut;\n      \n      if (shouldReconnect) {\n        this.logger.log('Connection closed, reconnecting...');\n        await this.connectToWhatsapp();\n      } else {\n        this.logger.log('Connection closed, logged out');\n        this.eventEmitter.emit(Events.LOGOUT_INSTANCE, {\n          instanceName: this.instance.name,\n        });\n      }\n    }\n\n    if (connection === 'open') {\n      this.logger.log('Connection opened successfully');\n      this.instance.wuid = this.client.user?.id;\n      \n      this.eventEmitter.emit(Events.CONNECTION_UPDATE, {\n        instanceName: this.instance.name,\n        state: 'open',\n      });\n    }\n  }\n\n  public async sendMessage(data: SendTextDto): Promise<any> {\n    const jid = createJid(data.number);\n    \n    const message = {\n      text: data.text,\n    };\n\n    if (data.linkPreview !== undefined) {\n      message.linkPreview = data.linkPreview;\n    }\n\n    if (data.mentionsEveryOne) {\n      // Handle mentions\n    }\n\n    try {\n      const response = await this.client.sendMessage(jid, message);\n      return response;\n    } catch (error) {\n      this.logger.error(`Failed to send message: ${error.message}`);\n      throw new BadRequestException('Failed to send message');\n    }\n  }\n}\n```\n\n## Channel Router Pattern\n\n### Channel Router Structure\n```typescript\nexport class ChannelRouter {\n  public readonly router: Router;\n\n  constructor(configService: any, ...guards: any[]) {\n    this.router = Router();\n\n    this.router.use('/', new EvolutionRouter(configService).router);\n    this.router.use('/', new MetaRouter(configService).router);\n    this.router.use('/baileys', new BaileysRouter(...guards).router);\n  }\n}\n```\n\n### Specific Channel Router\n```typescript\nexport class EvolutionRouter extends RouterBroker {\n  constructor(private readonly configService: ConfigService) {\n    super();\n    this.router\n      .post(this.routerPath('webhook'), async (req, res) => {\n        const response = await evolutionController.receiveWebhook(req.body);\n        return res.status(HttpStatus.OK).json(response);\n      });\n  }\n\n  public readonly router: Router = Router();\n}\n```\n\n## Integration Types\n\n### Channel Data Types\n```typescript\ntype ChannelDataType = {\n  configService: ConfigService;\n  eventEmitter: EventEmitter2;\n  prismaRepository: PrismaRepository;\n  cache: CacheService;\n  chatwootCache: CacheService;\n  baileysCache: CacheService;\n  providerFiles: ProviderFiles;\n};\n\nexport enum Integration {\n  WHATSAPP_BUSINESS = 'WHATSAPP-BUSINESS',\n  WHATSAPP_BAILEYS = 'WHATSAPP-BAILEYS',\n  EVOLUTION = 'EVOLUTION',\n}\n```\n\n## Error Handling in Channels\n\n### Channel-Specific Error Handling\n```typescript\n// CORRECT - Channel-specific error handling\npublic async sendMessage(data: SendTextDto): Promise<any> {\n  try {\n    const response = await this.channelSpecificSend(data);\n    return response;\n  } catch (error) {\n    this.logger.error(`${this.constructor.name} send failed: ${error.message}`);\n    \n    if (error.response?.status === 401) {\n      throw new UnauthorizedException('Invalid token for channel');\n    }\n    \n    if (error.response?.status === 429) {\n      throw new BadRequestException('Rate limit exceeded');\n    }\n    \n    throw new InternalServerErrorException('Channel communication failed');\n  }\n}\n```\n\n## Channel Testing Pattern\n\n### Channel Service Testing\n```typescript\ndescribe('EvolutionStartupService', () => {\n  let service: EvolutionStartupService;\n  let configService: jest.Mocked<ConfigService>;\n  let eventEmitter: jest.Mocked<EventEmitter2>;\n\n  beforeEach(() => {\n    const mockConfig = {\n      get: jest.fn().mockReturnValue({\n        API_URL: 'https://api.evolution.com',\n      }),\n    };\n\n    service = new EvolutionStartupService(\n      mockConfig as any,\n      eventEmitter,\n      prismaRepository,\n      cache,\n      chatwootCache,\n    );\n  });\n\n  describe('sendMessage', () => {\n    it('should send message successfully', async () => {\n      const data = { number: '5511999999999', text: 'Test message' };\n      \n      // Mock axios response\n      jest.spyOn(axios, 'post').mockResolvedValue({\n        data: { success: true, messageId: '123' },\n      });\n\n      const result = await service.sendMessage(data);\n\n      expect(result.success).toBe(true);\n      expect(axios.post).toHaveBeenCalledWith(\n        expect.stringContaining('/send-message'),\n        data,\n        expect.objectContaining({\n          headers: expect.objectContaining({\n            'Authorization': expect.stringContaining('Bearer'),\n          }),\n        })\n      );\n    });\n  });\n});\n```","frontmatter":{"description":"Channel integration patterns for Evolution API","globs":["src/api/integrations/channel/**/*.ts"],"alwaysApply":false},"isInternal":false,"tokens":3295,"sizeBytes":14945},{"name":"integration-chatbot-rules.mdc","path":".cursor/rules/specialized-rules/integration-chatbot-rules.mdc","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/specialized-rules/integration-chatbot-rules.mdc","title":"Cursor Rule: integration-chatbot-rules","category":"cursor-rule","format":"mdc","content":"---\ndescription: Chatbot integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/chatbot/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Chatbot Integration Rules\n\n## Base Chatbot Pattern\n\n### Base Chatbot DTO\n```typescript\n/**\n * Base DTO for all chatbot integrations\n * Contains common properties shared by all chatbot types\n */\nexport class BaseChatbotDto {\n  enabled?: boolean;\n  description: string;\n  expire?: number;\n  keywordFinish?: string;\n  delayMessage?: number;\n  unknownMessage?: string;\n  listeningFromMe?: boolean;\n  stopBotFromMe?: boolean;\n  keepOpen?: boolean;\n  debounceTime?: number;\n  triggerType: TriggerType;\n  triggerOperator?: TriggerOperator;\n  triggerValue?: string;\n  ignoreJids?: string[];\n  splitMessages?: boolean;\n  timePerChar?: number;\n}\n```\n\n### Base Chatbot Controller\n```typescript\nexport abstract class BaseChatbotController<BotType = any, BotData extends BaseChatbotDto = BaseChatbotDto>\n  extends ChatbotController\n  implements ChatbotControllerInterface\n{\n  public readonly logger: Logger;\n  integrationEnabled: boolean;\n  botRepository: any;\n  settingsRepository: any;\n  sessionRepository: any;\n  userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};\n\n  // Abstract methods to be implemented by specific chatbots\n  protected abstract readonly integrationName: string;\n  protected abstract processBot(\n    waInstance: any,\n    remoteJid: string,\n    bot: BotType,\n    session: any,\n    settings: ChatbotSettings,\n    content: string,\n    pushName?: string,\n    msg?: any,\n  ): Promise<void>;\n  protected abstract getFallbackBotId(settings: any): string | undefined;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n    this.sessionRepository = this.prismaRepository.integrationSession;\n  }\n\n  // Base implementation methods\n  public async createBot(instance: InstanceDto, data: BotData) {\n    if (!data.enabled) {\n      throw new BadRequestException(`${this.integrationName} is disabled`);\n    }\n\n    // Common bot creation logic\n    const bot = await this.botRepository.create({\n      data: {\n        ...data,\n        instanceId: instance.instanceId,\n      },\n    });\n\n    return bot;\n  }\n}\n```\n\n### Base Chatbot Service\n```typescript\n/**\n * Base class for all chatbot service implementations\n * Contains common methods shared across different chatbot integrations\n */\nexport abstract class BaseChatbotService<BotType = any, SettingsType = any> {\n  protected readonly logger: Logger;\n  protected readonly waMonitor: WAMonitoringService;\n  protected readonly prismaRepository: PrismaRepository;\n  protected readonly configService?: ConfigService;\n\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    loggerName: string,\n    configService?: ConfigService,\n  ) {\n    this.waMonitor = waMonitor;\n    this.prismaRepository = prismaRepository;\n    this.logger = new Logger(loggerName);\n    this.configService = configService;\n  }\n\n  /**\n   * Check if a message contains an image\n   */\n  protected isImageMessage(content: string): boolean {\n    return content.includes('imageMessage');\n  }\n\n  /**\n   * Extract text content from message\n   */\n  protected getMessageContent(msg: any): string {\n    return getConversationMessage(msg);\n  }\n\n  /**\n   * Send typing indicator\n   */\n  protected async sendTyping(instanceName: string, remoteJid: string): Promise<void> {\n    await this.waMonitor.waInstances[instanceName].sendPresence(remoteJid, 'composing');\n  }\n}\n```\n\n## Typebot Integration Pattern\n\n### Typebot Service\n```typescript\nexport class TypebotService extends BaseChatbotService<TypebotModel, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    configService: ConfigService,\n    prismaRepository: PrismaRepository,\n    private readonly openaiService: OpenaiService,\n  ) {\n    super(waMonitor, prismaRepository, 'TypebotService', configService);\n  }\n\n  public async sendTypebotMessage(\n    instanceName: string,\n    remoteJid: string,\n    typebot: TypebotModel,\n    content: string,\n  ): Promise<void> {\n    try {\n      const response = await axios.post(\n        `${typebot.url}/api/v1/typebots/${typebot.typebot}/startChat`,\n        {\n          message: content,\n          sessionId: `${instanceName}-${remoteJid}`,\n        },\n        {\n          headers: {\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      const { messages } = response.data;\n      \n      for (const message of messages) {\n        await this.processTypebotMessage(instanceName, remoteJid, message);\n      }\n    } catch (error) {\n      this.logger.error(`Typebot API error: ${error.message}`);\n      throw new InternalServerErrorException('Typebot communication failed');\n    }\n  }\n\n  private async processTypebotMessage(\n    instanceName: string,\n    remoteJid: string,\n    message: any,\n  ): Promise<void> {\n    const waInstance = this.waMonitor.waInstances[instanceName];\n    \n    if (message.type === 'text') {\n      await waInstance.sendMessage({\n        number: remoteJid.split('@')[0],\n        text: message.content.richText[0].children[0].text,\n      });\n    }\n    \n    if (message.type === 'image') {\n      await waInstance.sendMessage({\n        number: remoteJid.split('@')[0],\n        mediaMessage: {\n          mediatype: 'image',\n          media: message.content.url,\n        },\n      });\n    }\n  }\n}\n```\n\n## OpenAI Integration Pattern\n\n### OpenAI Service\n```typescript\nexport class OpenaiService extends BaseChatbotService<OpenaiModel, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(waMonitor, prismaRepository, 'OpenaiService', configService);\n  }\n\n  public async sendOpenaiMessage(\n    instanceName: string,\n    remoteJid: string,\n    openai: OpenaiModel,\n    content: string,\n    pushName?: string,\n  ): Promise<void> {\n    try {\n      const openaiConfig = this.configService.get<Openai>('OPENAI');\n      \n      const response = await axios.post(\n        'https://api.openai.com/v1/chat/completions',\n        {\n          model: openai.model || 'gpt-3.5-turbo',\n          messages: [\n            {\n              role: 'system',\n              content: openai.systemMessage || 'You are a helpful assistant.',\n            },\n            {\n              role: 'user',\n              content: content,\n            },\n          ],\n          max_tokens: openai.maxTokens || 1000,\n          temperature: openai.temperature || 0.7,\n        },\n        {\n          headers: {\n            'Authorization': `Bearer ${openai.apiKey || openaiConfig.API_KEY}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      const aiResponse = response.data.choices[0].message.content;\n      \n      await this.waMonitor.waInstances[instanceName].sendMessage({\n        number: remoteJid.split('@')[0],\n        text: aiResponse,\n      });\n    } catch (error) {\n      this.logger.error(`OpenAI API error: ${error.message}`);\n      \n      // Send fallback message\n      await this.waMonitor.waInstances[instanceName].sendMessage({\n        number: remoteJid.split('@')[0],\n        text: openai.unknownMessage || 'Desculpe, não consegui processar sua mensagem.',\n      });\n    }\n  }\n}\n```\n\n## Chatwoot Integration Pattern\n\n### Chatwoot Service\n```typescript\nexport class ChatwootService extends BaseChatbotService<any, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    configService: ConfigService,\n    prismaRepository: PrismaRepository,\n    private readonly chatwootCache: CacheService,\n  ) {\n    super(waMonitor, prismaRepository, 'ChatwootService', configService);\n  }\n\n  public async eventWhatsapp(\n    event: Events,\n    instanceName: { instanceName: string },\n    data: any,\n  ): Promise<void> {\n    const chatwootConfig = this.configService.get<Chatwoot>('CHATWOOT');\n    \n    if (!chatwootConfig.ENABLED) {\n      return;\n    }\n\n    try {\n      const instance = await this.prismaRepository.instance.findUnique({\n        where: { name: instanceName.instanceName },\n      });\n\n      if (!instance?.chatwootAccountId) {\n        return;\n      }\n\n      const webhook = {\n        event,\n        instance: instanceName.instanceName,\n        data,\n        timestamp: new Date().toISOString(),\n      };\n\n      await axios.post(\n        `${instance.chatwootUrl}/api/v1/accounts/${instance.chatwootAccountId}/webhooks`,\n        webhook,\n        {\n          headers: {\n            'Authorization': `Bearer ${instance.chatwootToken}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n    } catch (error) {\n      this.logger.error(`Chatwoot webhook error: ${error.message}`);\n    }\n  }\n\n  public async createConversation(\n    instanceName: string,\n    contact: any,\n    message: any,\n  ): Promise<void> {\n    // Create conversation in Chatwoot\n    const instance = await this.prismaRepository.instance.findUnique({\n      where: { name: instanceName },\n    });\n\n    if (!instance?.chatwootAccountId) {\n      return;\n    }\n\n    try {\n      const conversation = await axios.post(\n        `${instance.chatwootUrl}/api/v1/accounts/${instance.chatwootAccountId}/conversations`,\n        {\n          source_id: contact.id,\n          inbox_id: instance.chatwootInboxId,\n          contact_id: contact.chatwootContactId,\n        },\n        {\n          headers: {\n            'Authorization': `Bearer ${instance.chatwootToken}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      // Cache conversation\n      await this.chatwootCache.set(\n        `conversation:${instanceName}:${contact.id}`,\n        conversation.data,\n        3600\n      );\n    } catch (error) {\n      this.logger.error(`Chatwoot conversation creation error: ${error.message}`);\n    }\n  }\n}\n```\n\n## Dify Integration Pattern\n\n### Dify Service\n```typescript\nexport class DifyService extends BaseChatbotService<DifyModel, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    private readonly openaiService: OpenaiService,\n  ) {\n    super(waMonitor, prismaRepository, 'DifyService', configService);\n  }\n\n  public async sendDifyMessage(\n    instanceName: string,\n    remoteJid: string,\n    dify: DifyModel,\n    content: string,\n  ): Promise<void> {\n    try {\n      const response = await axios.post(\n        `${dify.apiUrl}/v1/chat-messages`,\n        {\n          inputs: {},\n          query: content,\n          user: remoteJid,\n          conversation_id: `${instanceName}-${remoteJid}`,\n          response_mode: 'blocking',\n        },\n        {\n          headers: {\n            'Authorization': `Bearer ${dify.apiKey}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      const aiResponse = response.data.answer;\n      \n      await this.waMonitor.waInstances[instanceName].sendMessage({\n        number: remoteJid.split('@')[0],\n        text: aiResponse,\n      });\n    } catch (error) {\n      this.logger.error(`Dify API error: ${error.message}`);\n      \n      // Fallback to OpenAI if configured\n      if (dify.fallbackOpenai && this.openaiService) {\n        await this.openaiService.sendOpenaiMessage(instanceName, remoteJid, dify.openaiBot, content);\n      }\n    }\n  }\n}\n```\n\n## Chatbot Router Pattern\n\n### Chatbot Router Structure (Evolution API Real Pattern)\n```typescript\nexport class ChatbotRouter {\n  public readonly router: Router;\n\n  constructor(...guards: any[]) {\n    this.router = Router();\n\n    // Real Evolution API chatbot integrations\n    this.router.use('/evolutionBot', new EvolutionBotRouter(...guards).router);\n    this.router.use('/chatwoot', new ChatwootRouter(...guards).router);\n    this.router.use('/typebot', new TypebotRouter(...guards).router);\n    this.router.use('/openai', new OpenaiRouter(...guards).router);\n    this.router.use('/dify', new DifyRouter(...guards).router);\n    this.router.use('/flowise', new FlowiseRouter(...guards).router);\n    this.router.use('/n8n', new N8nRouter(...guards).router);\n    this.router.use('/evoai', new EvoaiRouter(...guards).router);\n  }\n}\n```\n\n## Chatbot Validation Patterns\n\n### Chatbot Schema Validation (Evolution API Pattern)\n```typescript\nimport { JSONSchema7 } from 'json-schema';\nimport { v4 } from 'uuid';\n\nconst isNotEmpty = (...fields: string[]) => {\n  const properties = {};\n  fields.forEach((field) => {\n    properties[field] = {\n      if: { properties: { [field]: { type: 'string' } } },\n      then: { properties: { [field]: { minLength: 1 } } },\n    };\n  });\n\n  return {\n    allOf: Object.values(properties),\n  };\n};\n\nexport const evolutionBotSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    enabled: { type: 'boolean' },\n    description: { type: 'string' },\n    apiUrl: { type: 'string' },\n    apiKey: { type: 'string' },\n    triggerType: { type: 'string', enum: ['all', 'keyword', 'none', 'advanced'] },\n    triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith', 'regex'] },\n    triggerValue: { type: 'string' },\n    expire: { type: 'integer' },\n    keywordFinish: { type: 'string' },\n    delayMessage: { type: 'integer' },\n    unknownMessage: { type: 'string' },\n    listeningFromMe: { type: 'boolean' },\n    stopBotFromMe: { type: 'boolean' },\n    keepOpen: { type: 'boolean' },\n    debounceTime: { type: 'integer' },\n    ignoreJids: { type: 'array', items: { type: 'string' } },\n    splitMessages: { type: 'boolean' },\n    timePerChar: { type: 'integer' },\n  },\n  required: ['enabled', 'apiUrl', 'triggerType'],\n  ...isNotEmpty('enabled', 'apiUrl', 'triggerType'),\n};\n\nfunction validateKeywordTrigger(\n  content: string,\n  operator: TriggerOperator,\n  value: string,\n): boolean {\n  const normalizedContent = content.toLowerCase().trim();\n  const normalizedValue = value.toLowerCase().trim();\n\n  switch (operator) {\n    case TriggerOperator.EQUALS:\n      return normalizedContent === normalizedValue;\n    case TriggerOperator.CONTAINS:\n      return normalizedContent.includes(normalizedValue);\n    case TriggerOperator.STARTS_WITH:\n      return normalizedContent.startsWith(normalizedValue);\n    case TriggerOperator.ENDS_WITH:\n      return normalizedContent.endsWith(normalizedValue);\n    default:\n      return false;\n  }\n}\n```\n\n## Session Management Pattern\n\n### Chatbot Session Handling\n```typescript\nexport class ChatbotSessionManager {\n  constructor(\n    private readonly prismaRepository: PrismaRepository,\n    private readonly cache: CacheService,\n  ) {}\n\n  public async getSession(\n    instanceName: string,\n    remoteJid: string,\n    botId: string,\n  ): Promise<IntegrationSession | null> {\n    const cacheKey = `session:${instanceName}:${remoteJid}:${botId}`;\n    \n    // Try cache first\n    let session = await this.cache.get(cacheKey);\n    if (session) {\n      return session;\n    }\n\n    // Query database\n    session = await this.prismaRepository.integrationSession.findFirst({\n      where: {\n        instanceId: instanceName,\n        remoteJid,\n        botId,\n        status: 'opened',\n      },\n    });\n\n    // Cache result\n    if (session) {\n      await this.cache.set(cacheKey, session, 300); // 5 min TTL\n    }\n\n    return session;\n  }\n\n  public async createSession(\n    instanceName: string,\n    remoteJid: string,\n    botId: string,\n  ): Promise<IntegrationSession> {\n    const session = await this.prismaRepository.integrationSession.create({\n      data: {\n        instanceId: instanceName,\n        remoteJid,\n        botId,\n        status: 'opened',\n        createdAt: new Date(),\n      },\n    });\n\n    // Cache new session\n    const cacheKey = `session:${instanceName}:${remoteJid}:${botId}`;\n    await this.cache.set(cacheKey, session, 300);\n\n    return session;\n  }\n\n  public async closeSession(sessionId: string): Promise<void> {\n    await this.prismaRepository.integrationSession.update({\n      where: { id: sessionId },\n      data: { status: 'closed', updatedAt: new Date() },\n    });\n\n    // Invalidate cache\n    // Note: In a real implementation, you'd need to track cache keys by session ID\n  }\n}\n```","frontmatter":{"description":"Chatbot integration patterns for Evolution API","globs":["src/api/integrations/chatbot/**/*.ts"],"alwaysApply":false},"isInternal":false,"tokens":3798,"sizeBytes":16167},{"name":"integration-event-rules.mdc","path":".cursor/rules/specialized-rules/integration-event-rules.mdc","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/specialized-rules/integration-event-rules.mdc","title":"Cursor Rule: integration-event-rules","category":"cursor-rule","format":"mdc","content":"---\ndescription: Event integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/event/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Event Integration Rules\n\n## Event Manager Pattern\n\n### Event Manager Structure\n```typescript\nimport { PrismaRepository } from '@api/repository/repository.service';\nimport { ConfigService } from '@config/env.config';\nimport { Logger } from '@config/logger.config';\nimport { Server } from 'http';\n\nexport class EventManager {\n  private prismaRepository: PrismaRepository;\n  private configService: ConfigService;\n  private logger = new Logger('EventManager');\n\n  // Event integrations\n  private webhook: WebhookController;\n  private websocket: WebsocketController;\n  private rabbitmq: RabbitmqController;\n  private nats: NatsController;\n  private sqs: SqsController;\n  private pusher: PusherController;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    server?: Server,\n  ) {\n    this.prismaRepository = prismaRepository;\n    this.configService = configService;\n\n    // Initialize event controllers\n    this.webhook = new WebhookController(prismaRepository, configService);\n    this.websocket = new WebsocketController(prismaRepository, configService, server);\n    this.rabbitmq = new RabbitmqController(prismaRepository, configService);\n    this.nats = new NatsController(prismaRepository, configService);\n    this.sqs = new SqsController(prismaRepository, configService);\n    this.pusher = new PusherController(prismaRepository, configService);\n  }\n\n  public async emit(eventData: {\n    instanceName: string;\n    origin: string;\n    event: string;\n    data: Object;\n    serverUrl: string;\n    dateTime: string;\n    sender: string;\n    apiKey?: string;\n    local?: boolean;\n    integration?: string[];\n  }): Promise<void> {\n    this.logger.log(`Emitting event ${eventData.event} for instance ${eventData.instanceName}`);\n\n    // Emit to all configured integrations\n    await Promise.allSettled([\n      this.webhook.emit(eventData),\n      this.websocket.emit(eventData),\n      this.rabbitmq.emit(eventData),\n      this.nats.emit(eventData),\n      this.sqs.emit(eventData),\n      this.pusher.emit(eventData),\n    ]);\n  }\n\n  public async setInstance(instanceName: string, data: any): Promise<any> {\n    const promises = [];\n\n    if (data.websocket) {\n      promises.push(\n        this.websocket.set(instanceName, {\n          websocket: {\n            enabled: true,\n            events: data.websocket?.events,\n          },\n        })\n      );\n    }\n\n    if (data.rabbitmq) {\n      promises.push(\n        this.rabbitmq.set(instanceName, {\n          rabbitmq: {\n            enabled: true,\n            events: data.rabbitmq?.events,\n          },\n        })\n      );\n    }\n\n    if (data.webhook) {\n      promises.push(\n        this.webhook.set(instanceName, {\n          webhook: {\n            enabled: true,\n            events: data.webhook?.events,\n            url: data.webhook?.url,\n            headers: data.webhook?.headers,\n            base64: data.webhook?.base64,\n            byEvents: data.webhook?.byEvents,\n          },\n        })\n      );\n    }\n\n    // Set other integrations...\n\n    await Promise.allSettled(promises);\n  }\n}\n```\n\n## Base Event Controller Pattern\n\n### Abstract Event Controller\n```typescript\nimport { PrismaRepository } from '@api/repository/repository.service';\nimport { ConfigService } from '@config/env.config';\nimport { Logger } from '@config/logger.config';\n\nexport type EmitData = {\n  instanceName: string;\n  origin: string;\n  event: string;\n  data: Object;\n  serverUrl: string;\n  dateTime: string;\n  sender: string;\n  apiKey?: string;\n  local?: boolean;\n  integration?: string[];\n};\n\nexport interface EventControllerInterface {\n  integrationEnabled: boolean;\n  emit(data: EmitData): Promise<void>;\n  set(instanceName: string, data: any): Promise<any>;\n}\n\nexport abstract class EventController implements EventControllerInterface {\n  protected readonly logger: Logger;\n  protected readonly prismaRepository: PrismaRepository;\n  protected readonly configService: ConfigService;\n\n  public integrationEnabled: boolean = false;\n\n  // Available events for all integrations\n  public static readonly events = [\n    'APPLICATION_STARTUP',\n    'INSTANCE_CREATE',\n    'INSTANCE_DELETE',\n    'QRCODE_UPDATED',\n    'CONNECTION_UPDATE',\n    'STATUS_INSTANCE',\n    'MESSAGES_SET',\n    'MESSAGES_UPSERT',\n    'MESSAGES_EDITED',\n    'MESSAGES_UPDATE',\n    'MESSAGES_DELETE',\n    'SEND_MESSAGE',\n    'CONTACTS_SET',\n    'CONTACTS_UPSERT',\n    'CONTACTS_UPDATE',\n    'PRESENCE_UPDATE',\n    'CHATS_SET',\n    'CHATS_UPDATE',\n    'CHATS_UPSERT',\n    'CHATS_DELETE',\n    'GROUPS_UPSERT',\n    'GROUPS_UPDATE',\n    'GROUP_PARTICIPANTS_UPDATE',\n    'CALL',\n    'TYPEBOT_START',\n    'TYPEBOT_CHANGE_STATUS',\n    'LABELS_EDIT',\n    'LABELS_ASSOCIATION',\n    'CREDS_UPDATE',\n    'MESSAGING_HISTORY_SET',\n    'REMOVE_INSTANCE',\n    'LOGOUT_INSTANCE',\n  ];\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    loggerName: string,\n  ) {\n    this.prismaRepository = prismaRepository;\n    this.configService = configService;\n    this.logger = new Logger(loggerName);\n  }\n\n  // Abstract methods to be implemented by specific integrations\n  public abstract emit(data: EmitData): Promise<void>;\n  public abstract set(instanceName: string, data: any): Promise<any>;\n\n  // Helper method to check if event should be processed\n  protected shouldProcessEvent(eventName: string, configuredEvents?: string[]): boolean {\n    if (!configuredEvents || configuredEvents.length === 0) {\n      return true; // Process all events if none specified\n    }\n    return configuredEvents.includes(eventName);\n  }\n\n  // Helper method to get instance configuration\n  protected async getInstanceConfig(instanceName: string): Promise<any> {\n    try {\n      const instance = await this.prismaRepository.instance.findUnique({\n        where: { name: instanceName },\n      });\n      return instance;\n    } catch (error) {\n      this.logger.error(`Failed to get instance config for ${instanceName}:`, error);\n      return null;\n    }\n  }\n}\n```\n\n## Webhook Integration Pattern\n\n### Webhook Controller Implementation\n```typescript\nexport class WebhookController extends EventController {\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(prismaRepository, configService, 'WebhookController');\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.webhook?.enabled) {\n        return;\n      }\n\n      const webhookConfig = instance.webhook;\n      \n      if (!this.shouldProcessEvent(data.event, webhookConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n        server: {\n          version: process.env.npm_package_version,\n          url: data.serverUrl,\n        },\n      };\n\n      // Encode data as base64 if configured\n      if (webhookConfig.base64) {\n        payload.data = Buffer.from(JSON.stringify(payload.data)).toString('base64');\n      }\n\n      const headers = {\n        'Content-Type': 'application/json',\n        'User-Agent': 'Evolution-API-Webhook',\n        ...webhookConfig.headers,\n      };\n\n      if (webhookConfig.byEvents) {\n        // Send to event-specific endpoint\n        const eventUrl = `${webhookConfig.url}/${data.event.toLowerCase()}`;\n        await this.sendWebhook(eventUrl, payload, headers);\n      } else {\n        // Send to main webhook URL\n        await this.sendWebhook(webhookConfig.url, payload, headers);\n      }\n\n      this.logger.log(`Webhook sent for event ${data.event} to instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`Webhook emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const webhookData = data.webhook;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          webhook: webhookData,\n        },\n      });\n\n      this.logger.log(`Webhook configuration set for instance ${instanceName}`);\n      return { webhook: webhookData };\n    } catch (error) {\n      this.logger.error(`Failed to set webhook config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n\n  private async sendWebhook(url: string, payload: any, headers: any): Promise<void> {\n    try {\n      const response = await axios.post(url, payload, {\n        headers,\n        timeout: 30000,\n        maxRedirects: 3,\n      });\n\n      if (response.status >= 200 && response.status < 300) {\n        this.logger.log(`Webhook delivered successfully to ${url}`);\n      } else {\n        this.logger.warn(`Webhook returned status ${response.status} for ${url}`);\n      }\n    } catch (error) {\n      this.logger.error(`Webhook delivery failed to ${url}:`, error.message);\n      \n      // Implement retry logic here if needed\n      if (error.response?.status >= 500) {\n        // Server error - might be worth retrying\n        this.logger.log(`Server error detected, webhook might be retried later`);\n      }\n    }\n  }\n}\n```\n\n## WebSocket Integration Pattern\n\n### WebSocket Controller Implementation\n```typescript\nimport { Server as SocketIOServer } from 'socket.io';\nimport { Server } from 'http';\n\nexport class WebsocketController extends EventController {\n  private io: SocketIOServer;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    server?: Server,\n  ) {\n    super(prismaRepository, configService, 'WebsocketController');\n    \n    if (server) {\n      this.io = new SocketIOServer(server, {\n        cors: {\n          origin: \"*\",\n          methods: [\"GET\", \"POST\"],\n        },\n      });\n\n      this.setupSocketHandlers();\n    }\n  }\n\n  private setupSocketHandlers(): void {\n    this.io.on('connection', (socket) => {\n      this.logger.log(`WebSocket client connected: ${socket.id}`);\n\n      socket.on('join-instance', (instanceName: string) => {\n        socket.join(`instance:${instanceName}`);\n        this.logger.log(`Client ${socket.id} joined instance ${instanceName}`);\n      });\n\n      socket.on('leave-instance', (instanceName: string) => {\n        socket.leave(`instance:${instanceName}`);\n        this.logger.log(`Client ${socket.id} left instance ${instanceName}`);\n      });\n\n      socket.on('disconnect', () => {\n        this.logger.log(`WebSocket client disconnected: ${socket.id}`);\n      });\n    });\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    if (!this.io) {\n      return;\n    }\n\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.websocket?.enabled) {\n        return;\n      }\n\n      const websocketConfig = instance.websocket;\n      \n      if (!this.shouldProcessEvent(data.event, websocketConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n      };\n\n      // Emit to specific instance room\n      this.io.to(`instance:${data.instanceName}`).emit('evolution-event', payload);\n      \n      // Also emit to global room for monitoring\n      this.io.emit('global-event', payload);\n\n      this.logger.log(`WebSocket event ${data.event} emitted for instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`WebSocket emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const websocketData = data.websocket;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          websocket: websocketData,\n        },\n      });\n\n      this.logger.log(`WebSocket configuration set for instance ${instanceName}`);\n      return { websocket: websocketData };\n    } catch (error) {\n      this.logger.error(`Failed to set WebSocket config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n}\n```\n\n## Queue Integration Patterns\n\n### RabbitMQ Controller Implementation\n```typescript\nimport amqp from 'amqplib';\n\nexport class RabbitmqController extends EventController {\n  private connection: amqp.Connection | null = null;\n  private channel: amqp.Channel | null = null;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(prismaRepository, configService, 'RabbitmqController');\n    this.initializeConnection();\n  }\n\n  private async initializeConnection(): Promise<void> {\n    try {\n      const rabbitmqConfig = this.configService.get('RABBITMQ');\n      if (!rabbitmqConfig?.ENABLED) {\n        return;\n      }\n\n      this.connection = await amqp.connect(rabbitmqConfig.URI);\n      this.channel = await this.connection.createChannel();\n\n      // Declare exchange for Evolution API events\n      await this.channel.assertExchange('evolution-events', 'topic', { durable: true });\n\n      this.logger.log('RabbitMQ connection established');\n    } catch (error) {\n      this.logger.error('Failed to initialize RabbitMQ connection:', error);\n    }\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    if (!this.channel) {\n      return;\n    }\n\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.rabbitmq?.enabled) {\n        return;\n      }\n\n      const rabbitmqConfig = instance.rabbitmq;\n      \n      if (!this.shouldProcessEvent(data.event, rabbitmqConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n      };\n\n      const routingKey = `evolution.${data.instanceName}.${data.event.toLowerCase()}`;\n      \n      await this.channel.publish(\n        'evolution-events',\n        routingKey,\n        Buffer.from(JSON.stringify(payload)),\n        {\n          persistent: true,\n          timestamp: Date.now(),\n          messageId: `${data.instanceName}-${Date.now()}`,\n        }\n      );\n\n      this.logger.log(`RabbitMQ message published for event ${data.event} to instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`RabbitMQ emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const rabbitmqData = data.rabbitmq;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          rabbitmq: rabbitmqData,\n        },\n      });\n\n      this.logger.log(`RabbitMQ configuration set for instance ${instanceName}`);\n      return { rabbitmq: rabbitmqData };\n    } catch (error) {\n      this.logger.error(`Failed to set RabbitMQ config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n}\n```\n\n### SQS Controller Implementation\n```typescript\nimport { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';\n\nexport class SqsController extends EventController {\n  private sqsClient: SQSClient | null = null;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(prismaRepository, configService, 'SqsController');\n    this.initializeSQSClient();\n  }\n\n  private initializeSQSClient(): void {\n    try {\n      const sqsConfig = this.configService.get('SQS');\n      if (!sqsConfig?.ENABLED) {\n        return;\n      }\n\n      this.sqsClient = new SQSClient({\n        region: sqsConfig.REGION,\n        credentials: {\n          accessKeyId: sqsConfig.ACCESS_KEY_ID,\n          secretAccessKey: sqsConfig.SECRET_ACCESS_KEY,\n        },\n      });\n\n      this.logger.log('SQS client initialized');\n    } catch (error) {\n      this.logger.error('Failed to initialize SQS client:', error);\n    }\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    if (!this.sqsClient) {\n      return;\n    }\n\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.sqs?.enabled) {\n        return;\n      }\n\n      const sqsConfig = instance.sqs;\n      \n      if (!this.shouldProcessEvent(data.event, sqsConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n      };\n\n      const command = new SendMessageCommand({\n        QueueUrl: sqsConfig.queueUrl,\n        MessageBody: JSON.stringify(payload),\n        MessageAttributes: {\n          event: {\n            DataType: 'String',\n            StringValue: data.event,\n          },\n          instance: {\n            DataType: 'String',\n            StringValue: data.instanceName,\n          },\n        },\n        MessageGroupId: data.instanceName, // For FIFO queues\n        MessageDeduplicationId: `${data.instanceName}-${Date.now()}`, // For FIFO queues\n      });\n\n      await this.sqsClient.send(command);\n\n      this.logger.log(`SQS message sent for event ${data.event} to instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`SQS emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const sqsData = data.sqs;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          sqs: sqsData,\n        },\n      });\n\n      this.logger.log(`SQS configuration set for instance ${instanceName}`);\n      return { sqs: sqsData };\n    } catch (error) {\n      this.logger.error(`Failed to set SQS config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n}\n```\n\n## Event DTO Pattern\n\n### Event Configuration DTO\n```typescript\nimport { JsonValue } from '@prisma/client/runtime/library';\n\nexport class EventDto {\n  webhook?: {\n    enabled?: boolean;\n    events?: string[];\n    url?: string;\n    headers?: JsonValue;\n    byEvents?: boolean;\n    base64?: boolean;\n  };\n\n  websocket?: {\n    enabled?: boolean;\n    events?: string[];\n  };\n\n  sqs?: {\n    enabled?: boolean;\n    events?: string[];\n    queueUrl?: string;\n  };\n\n  rabbitmq?: {\n    enabled?: boolean;\n    events?: string[];\n    exchange?: string;\n  };\n\n  nats?: {\n    enabled?: boolean;\n    events?: string[];\n    subject?: string;\n  };\n\n  pusher?: {\n    enabled?: boolean;\n    appId?: string;\n    key?: string;\n    secret?: string;\n    cluster?: string;\n    useTLS?: boolean;\n    events?: string[];\n  };\n}\n```\n\n## Event Router Pattern\n\n### Event Router Structure\n```typescript\nimport { NatsRouter } from '@api/integrations/event/nats/nats.router';\nimport { PusherRouter } from '@api/integrations/event/pusher/pusher.router';\nimport { RabbitmqRouter } from '@api/integrations/event/rabbitmq/rabbitmq.router';\nimport { SqsRouter } from '@api/integrations/event/sqs/sqs.router';\nimport { WebhookRouter } from '@api/integrations/event/webhook/webhook.router';\nimport { WebsocketRouter } from '@api/integrations/event/websocket/websocket.router';\nimport { Router } from 'express';\n\nexport class EventRouter {\n  public readonly router: Router;\n\n  constructor(configService: any, ...guards: any[]) {\n    this.router = Router();\n\n    this.router.use('/webhook', new WebhookRouter(configService, ...guards).router);\n    this.router.use('/websocket', new WebsocketRouter(...guards).router);\n    this.router.use('/rabbitmq', new RabbitmqRouter(...guards).router);\n    this.router.use('/nats', new NatsRouter(...guards).router);\n    this.router.use('/pusher', new PusherRouter(...guards).router);\n    this.router.use('/sqs', new SqsRouter(...guards).router);\n  }\n}\n```\n\n## Event Validation Schema\n\n### Event Configuration Validation\n```typescript\nimport Joi from 'joi';\nimport { EventController } from '@api/integrations/event/event.controller';\n\nconst eventListSchema = Joi.array().items(\n  Joi.string().valid(...EventController.events)\n).optional();\n\nexport const webhookSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  url: Joi.string().when('enabled', {\n    is: true,\n    then: Joi.required().uri({ scheme: ['http', 'https'] }),\n    otherwise: Joi.optional(),\n  }),\n  events: eventListSchema,\n  headers: Joi.object().pattern(Joi.string(), Joi.string()).optional(),\n  byEvents: Joi.boolean().optional().default(false),\n  base64: Joi.boolean().optional().default(false),\n}).required();\n\nexport const websocketSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  events: eventListSchema,\n}).required();\n\nexport const rabbitmqSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  events: eventListSchema,\n  exchange: Joi.string().optional().default('evolution-events'),\n}).required();\n\nexport const sqsSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  events: eventListSchema,\n  queueUrl: Joi.string().when('enabled', {\n    is: true,\n    then: Joi.required().uri(),\n    otherwise: Joi.optional(),\n  }),\n}).required();\n\nexport const eventSchema = Joi.object({\n  webhook: webhookSchema.optional(),\n  websocket: websocketSchema.optional(),\n  rabbitmq: rabbitmqSchema.optional(),\n  sqs: sqsSchema.optional(),\n  nats: Joi.object({\n    enabled: Joi.boolean().required(),\n    events: eventListSchema,\n    subject: Joi.string().optional().default('evolution.events'),\n  }).optional(),\n  pusher: Joi.object({\n    enabled: Joi.boolean().required(),\n    appId: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    key: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    secret: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    cluster: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    useTLS: Joi.boolean().optional().default(true),\n    events: eventListSchema,\n  }).optional(),\n}).min(1).required();\n```\n\n## Event Testing Pattern\n\n### Event Controller Testing\n```typescript\ndescribe('WebhookController', () => {\n  let controller: WebhookController;\n  let prismaRepository: jest.Mocked<PrismaRepository>;\n  let configService: jest.Mocked<ConfigService>;\n\n  beforeEach(() => {\n    controller = new WebhookController(prismaRepository, configService);\n  });\n\n  describe('emit', () => {\n    it('should send webhook when enabled', async () => {\n      const mockInstance = {\n        webhook: {\n          enabled: true,\n          url: 'https://example.com/webhook',\n          events: ['MESSAGES_UPSERT'],\n        },\n      };\n\n      prismaRepository.instance.findUnique.mockResolvedValue(mockInstance);\n      jest.spyOn(axios, 'post').mockResolvedValue({ status: 200 });\n\n      const eventData = {\n        instanceName: 'test-instance',\n        event: 'MESSAGES_UPSERT',\n        data: { message: 'test' },\n        origin: 'test',\n        serverUrl: 'http://localhost',\n        dateTime: new Date().toISOString(),\n        sender: 'test',\n      };\n\n      await controller.emit(eventData);\n\n      expect(axios.post).toHaveBeenCalledWith(\n        'https://example.com/webhook',\n        expect.objectContaining({\n          event: 'MESSAGES_UPSERT',\n          instance: 'test-instance',\n        }),\n        expect.objectContaining({\n          headers: expect.objectContaining({\n            'Content-Type': 'application/json',\n          }),\n        })\n      );\n    });\n  });\n});\n```","frontmatter":{"description":"Event integration patterns for Evolution API","globs":["src/api/integrations/event/**/*.ts"],"alwaysApply":false},"isInternal":false,"tokens":5260,"sizeBytes":23518},{"name":"integration-storage-rules.mdc","path":".cursor/rules/specialized-rules/integration-storage-rules.mdc","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/specialized-rules/integration-storage-rules.mdc","title":"Cursor Rule: integration-storage-rules","category":"cursor-rule","format":"mdc","content":"---\ndescription: Storage integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/storage/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Storage Integration Rules\n\n## Storage Service Pattern\n\n### Base Storage Service Structure\n```typescript\nimport { InstanceDto } from '@api/dto/instance.dto';\nimport { PrismaRepository } from '@api/repository/repository.service';\nimport { Logger } from '@config/logger.config';\nimport { BadRequestException } from '@exceptions';\n\nexport class StorageService {\n  constructor(private readonly prismaRepository: PrismaRepository) {}\n\n  private readonly logger = new Logger('StorageService');\n\n  public async getMedia(instance: InstanceDto, query?: MediaDto) {\n    try {\n      const where: any = {\n        instanceId: instance.instanceId,\n        ...query,\n      };\n\n      const media = await this.prismaRepository.media.findMany({\n        where,\n        select: {\n          id: true,\n          fileName: true,\n          type: true,\n          mimetype: true,\n          createdAt: true,\n          Message: true,\n        },\n      });\n\n      if (!media || media.length === 0) {\n        throw 'Media not found';\n      }\n\n      return media;\n    } catch (error) {\n      throw new BadRequestException(error);\n    }\n  }\n\n  public async getMediaUrl(instance: InstanceDto, data: MediaDto) {\n    const media = (await this.getMedia(instance, { id: data.id }))[0];\n    const mediaUrl = await this.generateUrl(media.fileName, data.expiry);\n    return {\n      mediaUrl,\n      ...media,\n    };\n  }\n\n  protected abstract generateUrl(fileName: string, expiry?: number): Promise<string>;\n}\n```\n\n## S3/MinIO Integration Pattern\n\n### MinIO Client Setup\n```typescript\nimport { ConfigService, S3 } from '@config/env.config';\nimport { Logger } from '@config/logger.config';\nimport { BadRequestException } from '@exceptions';\nimport * as MinIo from 'minio';\nimport { join } from 'path';\nimport { Readable, Transform } from 'stream';\n\nconst logger = new Logger('S3 Service');\nconst BUCKET = new ConfigService().get<S3>('S3');\n\ninterface Metadata extends MinIo.ItemBucketMetadata {\n  instanceId: string;\n  messageId?: string;\n}\n\nconst minioClient = (() => {\n  if (BUCKET?.ENABLE) {\n    return new MinIo.Client({\n      endPoint: BUCKET.ENDPOINT,\n      port: BUCKET.PORT,\n      useSSL: BUCKET.USE_SSL,\n      accessKey: BUCKET.ACCESS_KEY,\n      secretKey: BUCKET.SECRET_KEY,\n      region: BUCKET.REGION,\n    });\n  }\n})();\n\nconst bucketName = process.env.S3_BUCKET;\n```\n\n### Bucket Management Functions\n```typescript\nconst bucketExists = async (): Promise<boolean> => {\n  if (minioClient) {\n    try {\n      const list = await minioClient.listBuckets();\n      return !!list.find((bucket) => bucket.name === bucketName);\n    } catch (error) {\n      logger.error('Error checking bucket existence:', error);\n      return false;\n    }\n  }\n  return false;\n};\n\nconst setBucketPolicy = async (): Promise<void> => {\n  if (minioClient && bucketName) {\n    try {\n      const policy = {\n        Version: '2012-10-17',\n        Statement: [\n          {\n            Effect: 'Allow',\n            Principal: { AWS: ['*'] },\n            Action: ['s3:GetObject'],\n            Resource: [`arn:aws:s3:::${bucketName}/*`],\n          },\n        ],\n      };\n\n      await minioClient.setBucketPolicy(bucketName, JSON.stringify(policy));\n      logger.log('Bucket policy set successfully');\n    } catch (error) {\n      logger.error('Error setting bucket policy:', error);\n    }\n  }\n};\n\nconst createBucket = async (): Promise<void> => {\n  if (minioClient && bucketName) {\n    try {\n      const exists = await bucketExists();\n      if (!exists) {\n        await minioClient.makeBucket(bucketName, BUCKET.REGION || 'us-east-1');\n        await setBucketPolicy();\n        logger.log(`Bucket ${bucketName} created successfully`);\n      }\n    } catch (error) {\n      logger.error('Error creating bucket:', error);\n    }\n  }\n};\n```\n\n### File Upload Functions\n```typescript\nexport const uploadFile = async (\n  fileName: string,\n  buffer: Buffer,\n  mimetype: string,\n  metadata?: Metadata,\n): Promise<string> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    await createBucket();\n\n    const uploadMetadata = {\n      'Content-Type': mimetype,\n      ...metadata,\n    };\n\n    await minioClient.putObject(bucketName, fileName, buffer, buffer.length, uploadMetadata);\n    \n    logger.log(`File ${fileName} uploaded successfully`);\n    return fileName;\n  } catch (error) {\n    logger.error(`Error uploading file ${fileName}:`, error);\n    throw new BadRequestException(`Failed to upload file: ${error.message}`);\n  }\n};\n\nexport const uploadStream = async (\n  fileName: string,\n  stream: Readable,\n  size: number,\n  mimetype: string,\n  metadata?: Metadata,\n): Promise<string> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    await createBucket();\n\n    const uploadMetadata = {\n      'Content-Type': mimetype,\n      ...metadata,\n    };\n\n    await minioClient.putObject(bucketName, fileName, stream, size, uploadMetadata);\n    \n    logger.log(`Stream ${fileName} uploaded successfully`);\n    return fileName;\n  } catch (error) {\n    logger.error(`Error uploading stream ${fileName}:`, error);\n    throw new BadRequestException(`Failed to upload stream: ${error.message}`);\n  }\n};\n```\n\n### File Download Functions\n```typescript\nexport const getObject = async (fileName: string): Promise<Buffer> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const stream = await minioClient.getObject(bucketName, fileName);\n    const chunks: Buffer[] = [];\n\n    return new Promise((resolve, reject) => {\n      stream.on('data', (chunk) => chunks.push(chunk));\n      stream.on('end', () => resolve(Buffer.concat(chunks)));\n      stream.on('error', reject);\n    });\n  } catch (error) {\n    logger.error(`Error getting object ${fileName}:`, error);\n    throw new BadRequestException(`Failed to get object: ${error.message}`);\n  }\n};\n\nexport const getObjectUrl = async (fileName: string, expiry: number = 3600): Promise<string> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const url = await minioClient.presignedGetObject(bucketName, fileName, expiry);\n    logger.log(`Generated URL for ${fileName} with expiry ${expiry}s`);\n    return url;\n  } catch (error) {\n    logger.error(`Error generating URL for ${fileName}:`, error);\n    throw new BadRequestException(`Failed to generate URL: ${error.message}`);\n  }\n};\n\nexport const getObjectStream = async (fileName: string): Promise<Readable> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const stream = await minioClient.getObject(bucketName, fileName);\n    return stream;\n  } catch (error) {\n    logger.error(`Error getting object stream ${fileName}:`, error);\n    throw new BadRequestException(`Failed to get object stream: ${error.message}`);\n  }\n};\n```\n\n### File Management Functions\n```typescript\nexport const deleteObject = async (fileName: string): Promise<void> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    await minioClient.removeObject(bucketName, fileName);\n    logger.log(`File ${fileName} deleted successfully`);\n  } catch (error) {\n    logger.error(`Error deleting file ${fileName}:`, error);\n    throw new BadRequestException(`Failed to delete file: ${error.message}`);\n  }\n};\n\nexport const listObjects = async (prefix?: string): Promise<MinIo.BucketItem[]> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const objects: MinIo.BucketItem[] = [];\n    const stream = minioClient.listObjects(bucketName, prefix, true);\n\n    return new Promise((resolve, reject) => {\n      stream.on('data', (obj) => objects.push(obj));\n      stream.on('end', () => resolve(objects));\n      stream.on('error', reject);\n    });\n  } catch (error) {\n    logger.error('Error listing objects:', error);\n    throw new BadRequestException(`Failed to list objects: ${error.message}`);\n  }\n};\n\nexport const objectExists = async (fileName: string): Promise<boolean> => {\n  if (!minioClient || !bucketName) {\n    return false;\n  }\n\n  try {\n    await minioClient.statObject(bucketName, fileName);\n    return true;\n  } catch (error) {\n    return false;\n  }\n};\n```\n\n## Storage Controller Pattern\n\n### S3 Controller Implementation\n```typescript\nimport { InstanceDto } from '@api/dto/instance.dto';\nimport { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';\nimport { S3Service } from '@api/integrations/storage/s3/services/s3.service';\n\nexport class S3Controller {\n  constructor(private readonly s3Service: S3Service) {}\n\n  public async getMedia(instance: InstanceDto, data: MediaDto) {\n    return this.s3Service.getMedia(instance, data);\n  }\n\n  public async getMediaUrl(instance: InstanceDto, data: MediaDto) {\n    return this.s3Service.getMediaUrl(instance, data);\n  }\n\n  public async uploadMedia(instance: InstanceDto, data: UploadMediaDto) {\n    return this.s3Service.uploadMedia(instance, data);\n  }\n\n  public async deleteMedia(instance: InstanceDto, data: MediaDto) {\n    return this.s3Service.deleteMedia(instance, data);\n  }\n}\n```\n\n## Storage Router Pattern\n\n### Storage Router Structure\n```typescript\nimport { S3Router } from '@api/integrations/storage/s3/routes/s3.router';\nimport { Router } from 'express';\n\nexport class StorageRouter {\n  public readonly router: Router;\n\n  constructor(...guards: any[]) {\n    this.router = Router();\n\n    this.router.use('/s3', new S3Router(...guards).router);\n    // Add other storage providers here\n    // this.router.use('/gcs', new GCSRouter(...guards).router);\n    // this.router.use('/azure', new AzureRouter(...guards).router);\n  }\n}\n```\n\n### S3 Specific Router\n```typescript\nimport { RouterBroker } from '@api/abstract/abstract.router';\nimport { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';\nimport { s3Schema, s3UrlSchema } from '@api/integrations/storage/s3/validate/s3.schema';\nimport { HttpStatus } from '@api/routes/index.router';\nimport { s3Controller } from '@api/server.module';\nimport { RequestHandler, Router } from 'express';\n\nexport class S3Router extends RouterBroker {\n  constructor(...guards: RequestHandler[]) {\n    super();\n    this.router\n      .post(this.routerPath('getMedia'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<MediaDto>({\n          request: req,\n          schema: s3Schema,\n          ClassRef: MediaDto,\n          execute: (instance, data) => s3Controller.getMedia(instance, data),\n        });\n\n        res.status(HttpStatus.OK).json(response);\n      })\n      .post(this.routerPath('getMediaUrl'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<MediaDto>({\n          request: req,\n          schema: s3UrlSchema,\n          ClassRef: MediaDto,\n          execute: (instance, data) => s3Controller.getMediaUrl(instance, data),\n        });\n\n        res.status(HttpStatus.OK).json(response);\n      })\n      .post(this.routerPath('uploadMedia'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<UploadMediaDto>({\n          request: req,\n          schema: uploadSchema,\n          ClassRef: UploadMediaDto,\n          execute: (instance, data) => s3Controller.uploadMedia(instance, data),\n        });\n\n        res.status(HttpStatus.CREATED).json(response);\n      })\n      .delete(this.routerPath('deleteMedia'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<MediaDto>({\n          request: req,\n          schema: s3Schema,\n          ClassRef: MediaDto,\n          execute: (instance, data) => s3Controller.deleteMedia(instance, data),\n        });\n\n        res.status(HttpStatus.OK).json(response);\n      });\n  }\n\n  public readonly router: Router = Router();\n}\n```\n\n## Storage DTO Pattern\n\n### Media DTO\n```typescript\nexport class MediaDto {\n  id?: string;\n  fileName?: string;\n  type?: string;\n  mimetype?: string;\n  expiry?: number;\n}\n\nexport class UploadMediaDto {\n  fileName: string;\n  mimetype: string;\n  buffer?: Buffer;\n  base64?: string;\n  url?: string;\n  metadata?: {\n    instanceId: string;\n    messageId?: string;\n    contactId?: string;\n    [key: string]: any;\n  };\n}\n```\n\n## Storage Validation Schema\n\n### S3 Validation Schemas\n```typescript\nimport Joi from 'joi';\n\nexport const s3Schema = Joi.object({\n  id: Joi.string().optional(),\n  fileName: Joi.string().optional(),\n  type: Joi.string().optional().valid('image', 'video', 'audio', 'document'),\n  mimetype: Joi.string().optional(),\n  expiry: Joi.number().optional().min(60).max(604800).default(3600), // 1 min to 7 days\n}).min(1).required();\n\nexport const s3UrlSchema = Joi.object({\n  id: Joi.string().required(),\n  expiry: Joi.number().optional().min(60).max(604800).default(3600),\n}).required();\n\nexport const uploadSchema = Joi.object({\n  fileName: Joi.string().required().max(255),\n  mimetype: Joi.string().required(),\n  buffer: Joi.binary().optional(),\n  base64: Joi.string().base64().optional(),\n  url: Joi.string().uri().optional(),\n  metadata: Joi.object({\n    instanceId: Joi.string().required(),\n    messageId: Joi.string().optional(),\n    contactId: Joi.string().optional(),\n  }).optional(),\n}).xor('buffer', 'base64', 'url').required(); // Exactly one of these must be present\n```\n\n## Error Handling in Storage\n\n### Storage-Specific Error Handling\n```typescript\n// CORRECT - Storage-specific error handling\npublic async uploadFile(fileName: string, buffer: Buffer): Promise<string> {\n  try {\n    const result = await this.storageClient.upload(fileName, buffer);\n    return result;\n  } catch (error) {\n    this.logger.error(`Storage upload failed: ${error.message}`);\n    \n    if (error.code === 'NoSuchBucket') {\n      throw new BadRequestException('Storage bucket not found');\n    }\n    \n    if (error.code === 'AccessDenied') {\n      throw new UnauthorizedException('Storage access denied');\n    }\n    \n    if (error.code === 'EntityTooLarge') {\n      throw new BadRequestException('File too large');\n    }\n    \n    throw new InternalServerErrorException('Storage operation failed');\n  }\n}\n```\n\n## Storage Configuration Pattern\n\n### Environment Configuration\n```typescript\nexport interface S3Config {\n  ENABLE: boolean;\n  ENDPOINT: string;\n  PORT: number;\n  USE_SSL: boolean;\n  ACCESS_KEY: string;\n  SECRET_KEY: string;\n  REGION: string;\n  BUCKET: string;\n}\n\n// Usage in service\nconst s3Config = this.configService.get<S3Config>('S3');\nif (!s3Config.ENABLE) {\n  throw new BadRequestException('S3 storage is disabled');\n}\n```\n\n## Storage Testing Pattern\n\n### Storage Service Testing\n```typescript\ndescribe('S3Service', () => {\n  let service: S3Service;\n  let prismaRepository: jest.Mocked<PrismaRepository>;\n\n  beforeEach(() => {\n    service = new S3Service(prismaRepository);\n  });\n\n  describe('getMedia', () => {\n    it('should return media list', async () => {\n      const instance = { instanceId: 'test-instance' };\n      const mockMedia = [\n        { id: '1', fileName: 'test.jpg', type: 'image', mimetype: 'image/jpeg' },\n      ];\n\n      prismaRepository.media.findMany.mockResolvedValue(mockMedia);\n\n      const result = await service.getMedia(instance);\n\n      expect(result).toEqual(mockMedia);\n      expect(prismaRepository.media.findMany).toHaveBeenCalledWith({\n        where: { instanceId: 'test-instance' },\n        select: expect.objectContaining({\n          id: true,\n          fileName: true,\n          type: true,\n          mimetype: true,\n        }),\n      });\n    });\n\n    it('should throw error when no media found', async () => {\n      const instance = { instanceId: 'test-instance' };\n      prismaRepository.media.findMany.mockResolvedValue([]);\n\n      await expect(service.getMedia(instance)).rejects.toThrow(BadRequestException);\n    });\n  });\n});\n```\n\n## Storage Performance Considerations\n\n### Efficient File Handling\n```typescript\n// CORRECT - Stream-based upload for large files\npublic async uploadLargeFile(fileName: string, stream: Readable, size: number): Promise<string> {\n  const uploadStream = new Transform({\n    transform(chunk, encoding, callback) {\n      // Optional: Add compression, encryption, etc.\n      callback(null, chunk);\n    },\n  });\n\n  return new Promise((resolve, reject) => {\n    stream\n      .pipe(uploadStream)\n      .on('error', reject)\n      .on('finish', () => resolve(fileName));\n  });\n}\n\n// INCORRECT - Loading entire file into memory\npublic async uploadLargeFile(fileName: string, filePath: string): Promise<string> {\n  const buffer = fs.readFileSync(filePath); // ❌ Memory intensive for large files\n  return await this.uploadFile(fileName, buffer);\n}\n```","frontmatter":{"description":"Storage integration patterns for Evolution API","globs":["src/api/integrations/storage/**/*.ts"],"alwaysApply":false},"isInternal":false,"tokens":3931,"sizeBytes":16945},{"name":"route-rules.mdc","path":".cursor/rules/specialized-rules/route-rules.mdc","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/specialized-rules/route-rules.mdc","title":"Cursor Rule: route-rules","category":"cursor-rule","format":"mdc","content":"---\ndescription: Router patterns for Evolution API\nglobs:\n  - \"src/api/routes/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Route Rules\n\n## Router Base Pattern\n\n### RouterBroker Extension\n```typescript\nimport { RouterBroker } from '@api/abstract/abstract.router';\nimport { RequestHandler, Router } from 'express';\nimport { HttpStatus } from './index.router';\n\nexport class ExampleRouter extends RouterBroker {\n  constructor(...guards: RequestHandler[]) {\n    super();\n    this.router\n      .get(this.routerPath('findExample'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<ExampleDto>({\n          request: req,\n          schema: null,\n          ClassRef: ExampleDto,\n          execute: (instance) => exampleController.find(instance),\n        });\n\n        return res.status(HttpStatus.OK).json(response);\n      })\n      .post(this.routerPath('createExample'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<ExampleDto>({\n          request: req,\n          schema: exampleSchema,\n          ClassRef: ExampleDto,\n          execute: (instance, data) => exampleController.create(instance, data),\n        });\n\n        return res.status(HttpStatus.CREATED).json(response);\n      });\n  }\n\n  public readonly router: Router = Router();\n}\n```\n\n## Main Router Pattern\n\n### Index Router Structure\n```typescript\nimport { Router } from 'express';\nimport { authGuard } from '@api/guards/auth.guard';\nimport { instanceExistsGuard, instanceLoggedGuard } from '@api/guards/instance.guard';\nimport Telemetry from '@api/guards/telemetry.guard';\n\nenum HttpStatus {\n  OK = 200,\n  CREATED = 201,\n  NOT_FOUND = 404,\n  FORBIDDEN = 403,\n  BAD_REQUEST = 400,\n  UNAUTHORIZED = 401,\n  INTERNAL_SERVER_ERROR = 500,\n}\n\nconst router: Router = Router();\nconst guards = [instanceExistsGuard, instanceLoggedGuard, authGuard['apikey']];\nconst telemetry = new Telemetry();\n\nrouter\n  .use((req, res, next) => telemetry.collectTelemetry(req, res, next))\n  .get('/', async (req, res) => {\n    res.status(HttpStatus.OK).json({\n      status: HttpStatus.OK,\n      message: 'Welcome to the Evolution API, it is working!',\n      version: packageJson.version,\n      clientName: process.env.DATABASE_CONNECTION_CLIENT_NAME,\n      manager: !serverConfig.DISABLE_MANAGER ? `${req.protocol}://${req.get('host')}/manager` : undefined,\n      documentation: `https://doc.evolution-api.com`,\n      whatsappWebVersion: (await fetchLatestWaWebVersion({})).version.join('.'),\n    });\n  })\n  .use('/instance', new InstanceRouter(configService, ...guards).router)\n  .use('/message', new MessageRouter(...guards).router)\n  .use('/chat', new ChatRouter(...guards).router)\n  .use('/business', new BusinessRouter(...guards).router);\n\nexport { HttpStatus, router };\n```\n\n## Data Validation Pattern\n\n### RouterBroker dataValidate Usage\n```typescript\n// CORRECT - Standard validation pattern\n.post(this.routerPath('createTemplate'), ...guards, async (req, res) => {\n  const response = await this.dataValidate<TemplateDto>({\n    request: req,\n    schema: templateSchema,\n    ClassRef: TemplateDto,\n    execute: (instance, data) => templateController.create(instance, data),\n  });\n\n  return res.status(HttpStatus.CREATED).json(response);\n})\n\n// CORRECT - No schema validation (for simple DTOs)\n.get(this.routerPath('findTemplate'), ...guards, async (req, res) => {\n  const response = await this.dataValidate<InstanceDto>({\n    request: req,\n    schema: null,\n    ClassRef: InstanceDto,\n    execute: (instance) => templateController.find(instance),\n  });\n\n  return res.status(HttpStatus.OK).json(response);\n})\n```\n\n## Error Handling in Routes\n\n### Try-Catch Pattern\n```typescript\n// CORRECT - Error handling with utility function\n.post(this.routerPath('getCatalog'), ...guards, async (req, res) => {\n  try {\n    const response = await this.dataValidate<NumberDto>({\n      request: req,\n      schema: catalogSchema,\n      ClassRef: NumberDto,\n      execute: (instance, data) => businessController.fetchCatalog(instance, data),\n    });\n\n    return res.status(HttpStatus.OK).json(response);\n  } catch (error) {\n    // Log error for debugging\n    console.error('Business catalog error:', error);\n\n    // Use utility function to create standardized error response\n    const errorResponse = createMetaErrorResponse(error, 'business_catalog');\n    return res.status(errorResponse.status).json(errorResponse);\n  }\n})\n\n// INCORRECT - Let RouterBroker handle errors (when possible)\n.post(this.routerPath('simpleOperation'), ...guards, async (req, res) => {\n  try {\n    const response = await this.dataValidate<SimpleDto>({\n      request: req,\n      schema: simpleSchema,\n      ClassRef: SimpleDto,\n      execute: (instance, data) => controller.simpleOperation(instance, data),\n    });\n\n    return res.status(HttpStatus.OK).json(response);\n  } catch (error) {\n    throw error; // ❌ Unnecessary - RouterBroker handles this\n  }\n})\n```\n\n## Route Path Pattern\n\n### routerPath Usage\n```typescript\n// CORRECT - Use routerPath for consistent naming\n.get(this.routerPath('findLabels'), ...guards, async (req, res) => {\n  // Implementation\n})\n.post(this.routerPath('handleLabel'), ...guards, async (req, res) => {\n  // Implementation\n})\n\n// INCORRECT - Hardcoded paths\n.get('/labels', ...guards, async (req, res) => { // ❌ Use routerPath\n  // Implementation\n})\n```\n\n## Guard Application Pattern\n\n### Guards Usage\n```typescript\n// CORRECT - Apply guards to protected routes\nexport class ProtectedRouter extends RouterBroker {\n  constructor(...guards: RequestHandler[]) {\n    super();\n    this.router\n      .get(this.routerPath('protectedAction'), ...guards, async (req, res) => {\n        // Protected action\n      })\n      .post(this.routerPath('anotherAction'), ...guards, async (req, res) => {\n        // Another protected action\n      });\n  }\n}\n\n// CORRECT - No guards for public routes\nexport class PublicRouter extends RouterBroker {\n  constructor() {\n    super();\n    this.router\n      .get('/health', async (req, res) => {\n        res.status(HttpStatus.OK).json({ status: 'healthy' });\n      })\n      .get('/version', async (req, res) => {\n        res.status(HttpStatus.OK).json({ version: packageJson.version });\n      });\n  }\n}\n```\n\n## Static File Serving Pattern\n\n### Static Assets Route\n```typescript\n// CORRECT - Secure static file serving\nrouter.get('/assets/*', (req, res) => {\n  const fileName = req.params[0];\n  \n  // Security: Reject paths containing traversal patterns\n  if (!fileName || fileName.includes('..') || fileName.includes('\\\\') || path.isAbsolute(fileName)) {\n    return res.status(403).send('Forbidden');\n  }\n  \n  const basePath = path.join(process.cwd(), 'manager', 'dist');\n  const assetsPath = path.join(basePath, 'assets');\n  const filePath = path.join(assetsPath, fileName);\n  \n  // Security: Ensure the resolved path is within the assets directory\n  const resolvedPath = path.resolve(filePath);\n  const resolvedAssetsPath = path.resolve(assetsPath);\n  \n  if (!resolvedPath.startsWith(resolvedAssetsPath + path.sep) && resolvedPath !== resolvedAssetsPath) {\n    return res.status(403).send('Forbidden');\n  }\n\n  if (fs.existsSync(resolvedPath)) {\n    res.set('Content-Type', mimeTypes.lookup(resolvedPath) || 'text/css');\n    res.send(fs.readFileSync(resolvedPath));\n  } else {\n    res.status(404).send('File not found');\n  }\n});\n```\n\n## Special Route Patterns\n\n### Manager Route Pattern\n```typescript\nexport class ViewsRouter extends RouterBroker {\n  public readonly router: Router;\n\n  constructor() {\n    super();\n    this.router = Router();\n\n    const basePath = path.join(process.cwd(), 'manager', 'dist');\n    const indexPath = path.join(basePath, 'index.html');\n\n    this.router.use(express.static(basePath));\n\n    this.router.get('*', (req, res) => {\n      res.sendFile(indexPath);\n    });\n  }\n}\n```\n\n### Webhook Route Pattern\n```typescript\n// CORRECT - Webhook without guards\n.post('/webhook/evolution', async (req, res) => {\n  const response = await evolutionController.receiveWebhook(req.body);\n  return res.status(HttpStatus.OK).json(response);\n})\n\n// CORRECT - Webhook with signature validation\n.post('/webhook/meta', validateWebhookSignature, async (req, res) => {\n  const response = await metaController.receiveWebhook(req.body);\n  return res.status(HttpStatus.OK).json(response);\n})\n```\n\n## Response Pattern\n\n### Standard Response Format\n```typescript\n// CORRECT - Standard success response\nreturn res.status(HttpStatus.OK).json(response);\n\n// CORRECT - Created response\nreturn res.status(HttpStatus.CREATED).json(response);\n\n// CORRECT - Custom response with additional data\nreturn res.status(HttpStatus.OK).json({\n  ...response,\n  timestamp: new Date().toISOString(),\n  instanceName: req.params.instanceName,\n});\n```\n\n## Route Organization\n\n### File Structure\n```\nsrc/api/routes/\n├── index.router.ts          # Main router with all route registrations\n├── instance.router.ts       # Instance management routes\n├── sendMessage.router.ts    # Message sending routes\n├── chat.router.ts          # Chat operations routes\n├── business.router.ts      # Business API routes\n├── group.router.ts         # Group management routes\n├── label.router.ts         # Label management routes\n├── proxy.router.ts         # Proxy configuration routes\n├── settings.router.ts      # Instance settings routes\n├── template.router.ts      # Template management routes\n├── call.router.ts          # Call operations routes\n└── view.router.ts          # Frontend views routes\n```\n\n## Route Testing Pattern\n\n### Router Testing\n```typescript\ndescribe('ExampleRouter', () => {\n  let app: express.Application;\n  let router: ExampleRouter;\n\n  beforeEach(() => {\n    app = express();\n    router = new ExampleRouter();\n    app.use('/api', router.router);\n    app.use(express.json());\n  });\n\n  describe('GET /findExample', () => {\n    it('should return example data', async () => {\n      const response = await request(app)\n        .get('/api/findExample/test-instance')\n        .set('apikey', 'test-key')\n        .expect(200);\n\n      expect(response.body).toBeDefined();\n      expect(response.body.instanceName).toBe('test-instance');\n    });\n\n    it('should return 401 without API key', async () => {\n      await request(app)\n        .get('/api/findExample/test-instance')\n        .expect(401);\n    });\n  });\n\n  describe('POST /createExample', () => {\n    it('should create example successfully', async () => {\n      const data = {\n        name: 'Test Example',\n        description: 'Test Description',\n      };\n\n      const response = await request(app)\n        .post('/api/createExample/test-instance')\n        .set('apikey', 'test-key')\n        .send(data)\n        .expect(201);\n\n      expect(response.body.name).toBe(data.name);\n    });\n\n    it('should validate required fields', async () => {\n      const data = {\n        description: 'Test Description',\n        // Missing required 'name' field\n      };\n\n      await request(app)\n        .post('/api/createExample/test-instance')\n        .set('apikey', 'test-key')\n        .send(data)\n        .expect(400);\n    });\n  });\n});\n```\n\n## Route Documentation\n\n### JSDoc for Routes\n```typescript\n/**\n * @route GET /api/template/findTemplate/:instanceName\n * @description Find template for instance\n * @param {string} instanceName - Instance name\n * @returns {TemplateDto} Template data\n * @throws {404} Template not found\n * @throws {401} Unauthorized\n */\n.get(this.routerPath('findTemplate'), ...guards, async (req, res) => {\n  // Implementation\n})\n\n/**\n * @route POST /api/template/createTemplate/:instanceName\n * @description Create new template\n * @param {string} instanceName - Instance name\n * @body {TemplateDto} Template data\n * @returns {TemplateDto} Created template\n * @throws {400} Validation error\n * @throws {401} Unauthorized\n */\n.post(this.routerPath('createTemplate'), ...guards, async (req, res) => {\n  // Implementation\n})\n```","frontmatter":{"description":"Router patterns for Evolution API","globs":["src/api/routes/**/*.ts"],"alwaysApply":false},"isInternal":false,"tokens":2711,"sizeBytes":11970},{"name":"service-rules.mdc","path":".cursor/rules/specialized-rules/service-rules.mdc","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/specialized-rules/service-rules.mdc","title":"Cursor Rule: service-rules","category":"cursor-rule","format":"mdc","content":"---\ndescription: Service layer patterns for Evolution API\nglobs:\n  - \"src/api/services/**/*.ts\"\n  - \"src/api/integrations/**/services/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Service Rules\n\n## Service Structure Pattern\n\n### Standard Service Class\n```typescript\nexport class ExampleService {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n\n  private readonly logger = new Logger('ExampleService');\n\n  public async create(instance: InstanceDto, data: ExampleDto) {\n    await this.waMonitor.waInstances[instance.instanceName].setData(data);\n    return { example: { ...instance, data } };\n  }\n\n  public async find(instance: InstanceDto): Promise<ExampleDto> {\n    try {\n      const result = await this.waMonitor.waInstances[instance.instanceName].findData();\n\n      if (Object.keys(result).length === 0) {\n        throw new Error('Data not found');\n      }\n\n      return result;\n    } catch (error) {\n      return null; // Evolution pattern - return null on error\n    }\n  }\n}\n```\n\n## Dependency Injection Pattern\n\n### Constructor Pattern\n```typescript\n// CORRECT - Evolution API pattern\nconstructor(\n  private readonly waMonitor: WAMonitoringService,\n  private readonly prismaRepository: PrismaRepository,\n  private readonly configService: ConfigService,\n) {}\n\n// INCORRECT - Don't use\nconstructor(waMonitor, prismaRepository, configService) {} // ❌ No types\n```\n\n## Logger Pattern\n\n### Standard Logger Usage\n```typescript\n// CORRECT - Evolution API pattern\nprivate readonly logger = new Logger('ServiceName');\n\n// Usage\nthis.logger.log('Operation started');\nthis.logger.error('Operation failed', error);\n\n// INCORRECT\nconsole.log('Operation started'); // ❌ Use Logger\n```\n\n## WAMonitor Integration Pattern\n\n### Instance Access Pattern\n```typescript\n// CORRECT - Standard pattern\npublic async operation(instance: InstanceDto, data: DataDto) {\n  await this.waMonitor.waInstances[instance.instanceName].performAction(data);\n  return { result: { ...instance, data } };\n}\n\n// Instance validation\nconst waInstance = this.waMonitor.waInstances[instance.instanceName];\nif (!waInstance) {\n  throw new NotFoundException('Instance not found');\n}\n```\n\n## Error Handling Pattern\n\n### Try-Catch Pattern\n```typescript\n// CORRECT - Evolution API pattern\npublic async find(instance: InstanceDto): Promise<DataDto> {\n  try {\n    const result = await this.waMonitor.waInstances[instance.instanceName].findData();\n    \n    if (Object.keys(result).length === 0) {\n      throw new Error('Data not found');\n    }\n\n    return result;\n  } catch (error) {\n    this.logger.error('Find operation failed', error);\n    return null; // Return null on error (Evolution pattern)\n  }\n}\n```\n\n## Cache Integration Pattern\n\n### Cache Service Usage\n```typescript\nexport class CacheAwareService {\n  constructor(\n    private readonly cache: CacheService,\n    private readonly chatwootCache: CacheService,\n    private readonly baileysCache: CacheService,\n  ) {}\n\n  public async getCachedData(key: string): Promise<any> {\n    const cached = await this.cache.get(key);\n    if (cached) return cached;\n\n    const data = await this.fetchFromSource(key);\n    await this.cache.set(key, data, 300); // 5 min TTL\n    return data;\n  }\n}\n```\n\n## Integration Service Patterns\n\n### Chatbot Service Base Pattern\n```typescript\nexport class ChatbotService extends BaseChatbotService<BotType, SettingsType> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(waMonitor, prismaRepository, 'ChatbotService', configService);\n  }\n\n  protected async processBot(\n    waInstance: any,\n    remoteJid: string,\n    bot: BotType,\n    session: any,\n    settings: any,\n    content: string,\n  ): Promise<void> {\n    // Implementation\n  }\n}\n```\n\n### Channel Service Pattern\n```typescript\nexport class ChannelService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n    baileysCache: CacheService,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache, baileysCache);\n  }\n\n  public readonly logger = new Logger('ChannelService');\n  public client: WASocket;\n  public readonly instance: wa.Instance = {};\n}\n```\n\n## Service Initialization Pattern\n\n### Service Registration\n```typescript\n// In server.module.ts pattern\nexport const templateService = new TemplateService(\n  waMonitor,\n  prismaRepository,\n  configService,\n);\n\nexport const settingsService = new SettingsService(waMonitor);\n```\n\n## Async Operation Patterns\n\n### Promise Handling\n```typescript\n// CORRECT - Evolution API pattern\npublic async sendMessage(instance: InstanceDto, data: MessageDto) {\n  const waInstance = this.waMonitor.waInstances[instance.instanceName];\n  return await waInstance.sendMessage(data);\n}\n\n// INCORRECT - Don't use .then()\npublic sendMessage(instance: InstanceDto, data: MessageDto) {\n  return this.waMonitor.waInstances[instance.instanceName]\n    .sendMessage(data)\n    .then(result => result); // ❌ Use async/await\n}\n```\n\n## Configuration Access Pattern\n\n### Config Service Usage\n```typescript\n// CORRECT - Evolution API pattern\nconst serverConfig = this.configService.get<HttpServer>('SERVER');\nconst authConfig = this.configService.get<Auth>('AUTHENTICATION');\nconst dbConfig = this.configService.get<Database>('DATABASE');\n\n// Type-safe configuration access\nif (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) {\n  // Chatwoot logic\n}\n```\n\n## Event Emission Pattern\n\n### EventEmitter2 Usage\n```typescript\n// CORRECT - Evolution API pattern\nthis.eventEmitter.emit(Events.INSTANCE_CREATE, {\n  instanceName: instance.name,\n  status: 'created',\n});\n\n// Chatwoot event pattern\nif (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {\n  this.chatwootService.eventWhatsapp(\n    Events.STATUS_INSTANCE,\n    { instanceName: this.instance.name },\n    {\n      instance: this.instance.name,\n      status: 'created',\n    },\n  );\n}\n```\n\n## Service Method Naming\n\n### Standard Method Names\n- `create()` - Create new resource\n- `find()` - Find single resource\n- `findAll()` - Find multiple resources\n- `update()` - Update resource\n- `delete()` - Delete resource\n- `fetch*()` - Fetch from external API\n- `send*()` - Send data/messages\n- `process*()` - Process data\n\n## Service Testing Pattern\n\n### Unit Test Structure\n```typescript\ndescribe('ExampleService', () => {\n  let service: ExampleService;\n  let waMonitor: jest.Mocked<WAMonitoringService>;\n  let prismaRepository: jest.Mocked<PrismaRepository>;\n\n  beforeEach(() => {\n    const mockWaMonitor = {\n      waInstances: {\n        'test-instance': {\n          performAction: jest.fn(),\n        },\n      },\n    };\n\n    service = new ExampleService(\n      mockWaMonitor as any,\n      prismaRepository,\n      configService,\n    );\n  });\n\n  it('should perform action successfully', async () => {\n    const instance = { instanceName: 'test-instance' };\n    const data = { test: 'data' };\n\n    const result = await service.create(instance, data);\n\n    expect(result).toBeDefined();\n    expect(waMonitor.waInstances['test-instance'].performAction).toHaveBeenCalledWith(data);\n  });\n});\n```","frontmatter":{"description":"Service layer patterns for Evolution API","globs":["src/api/services/**/*.ts","src/api/integrations/**/services/*.ts"],"alwaysApply":false},"isInternal":false,"tokens":1681,"sizeBytes":7284},{"name":"type-rules.mdc","path":".cursor/rules/specialized-rules/type-rules.mdc","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/specialized-rules/type-rules.mdc","title":"Cursor Rule: type-rules","category":"cursor-rule","format":"mdc","content":"---\ndescription: Type definitions and interfaces for Evolution API\nglobs:\n  - \"src/api/types/**/*.ts\"\n  - \"src/@types/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Type Rules\n\n## Namespace Pattern\n\n### WhatsApp Types Namespace\n```typescript\n/* eslint-disable @typescript-eslint/no-namespace */\nimport { JsonValue } from '@prisma/client/runtime/library';\nimport { AuthenticationState, WAConnectionState } from 'baileys';\n\nexport declare namespace wa {\n  export type QrCode = {\n    count?: number;\n    pairingCode?: string;\n    base64?: string;\n    code?: string;\n  };\n\n  export type Instance = {\n    id?: string;\n    qrcode?: QrCode;\n    pairingCode?: string;\n    authState?: { state: AuthenticationState; saveCreds: () => void };\n    name?: string;\n    wuid?: string;\n    profileName?: string;\n    profilePictureUrl?: string;\n    token?: string;\n    number?: string;\n    integration?: string;\n    businessId?: string;\n  };\n\n  export type LocalChatwoot = {\n    enabled?: boolean;\n    accountId?: string;\n    token?: string;\n    url?: string;\n    nameInbox?: string;\n    mergeBrazilContacts?: boolean;\n    importContacts?: boolean;\n    importMessages?: boolean;\n    daysLimitImportMessages?: number;\n    organization?: string;\n    logo?: string;\n  };\n\n  export type LocalProxy = {\n    enabled?: boolean;\n    host?: string;\n    port?: string;\n    protocol?: string;\n    username?: string;\n    password?: string;\n  };\n\n  export type LocalSettings = {\n    rejectCall?: boolean;\n    msgCall?: string;\n    groupsIgnore?: boolean;\n    alwaysOnline?: boolean;\n    readMessages?: boolean;\n    readStatus?: boolean;\n    syncFullHistory?: boolean;\n  };\n\n  export type LocalWebHook = {\n    enabled?: boolean;\n    url?: string;\n    events?: string[];\n    headers?: JsonValue;\n    byEvents?: boolean;\n    base64?: boolean;\n  };\n\n  export type StatusMessage = 'ERROR' | 'PENDING' | 'SERVER_ACK' | 'DELIVERY_ACK' | 'READ' | 'DELETED' | 'PLAYED';\n}\n```\n\n## Enum Definitions\n\n### Events Enum\n```typescript\nexport enum Events {\n  APPLICATION_STARTUP = 'application.startup',\n  INSTANCE_CREATE = 'instance.create',\n  INSTANCE_DELETE = 'instance.delete',\n  QRCODE_UPDATED = 'qrcode.updated',\n  CONNECTION_UPDATE = 'connection.update',\n  STATUS_INSTANCE = 'status.instance',\n  MESSAGES_SET = 'messages.set',\n  MESSAGES_UPSERT = 'messages.upsert',\n  MESSAGES_EDITED = 'messages.edited',\n  MESSAGES_UPDATE = 'messages.update',\n  MESSAGES_DELETE = 'messages.delete',\n  SEND_MESSAGE = 'send.message',\n  SEND_MESSAGE_UPDATE = 'send.message.update',\n  CONTACTS_SET = 'contacts.set',\n  CONTACTS_UPSERT = 'contacts.upsert',\n  CONTACTS_UPDATE = 'contacts.update',\n  PRESENCE_UPDATE = 'presence.update',\n  CHATS_SET = 'chats.set',\n  CHATS_UPDATE = 'chats.update',\n  CHATS_UPSERT = 'chats.upsert',\n  CHATS_DELETE = 'chats.delete',\n  GROUPS_UPSERT = 'groups.upsert',\n  GROUPS_UPDATE = 'groups.update',\n  GROUP_PARTICIPANTS_UPDATE = 'group-participants.update',\n  CALL = 'call',\n  TYPEBOT_START = 'typebot.start',\n  TYPEBOT_CHANGE_STATUS = 'typebot.change-status',\n  LABELS_EDIT = 'labels.edit',\n  LABELS_ASSOCIATION = 'labels.association',\n  CREDS_UPDATE = 'creds.update',\n  MESSAGING_HISTORY_SET = 'messaging-history.set',\n  REMOVE_INSTANCE = 'remove.instance',\n  LOGOUT_INSTANCE = 'logout.instance',\n}\n```\n\n### Integration Types\n```typescript\nexport const Integration = {\n  WHATSAPP_BUSINESS: 'WHATSAPP-BUSINESS',\n  WHATSAPP_BAILEYS: 'WHATSAPP-BAILEYS',\n  EVOLUTION: 'EVOLUTION',\n} as const;\n\nexport type IntegrationType = typeof Integration[keyof typeof Integration];\n```\n\n## Constant Arrays\n\n### Message Type Constants\n```typescript\nexport const TypeMediaMessage = [\n  'imageMessage',\n  'documentMessage',\n  'audioMessage',\n  'videoMessage',\n  'stickerMessage',\n  'ptvMessage', // Evolution API includes this\n];\n\nexport const MessageSubtype = [\n  'ephemeralMessage',\n  'documentWithCaptionMessage',\n  'viewOnceMessage',\n  'viewOnceMessageV2',\n];\n\nexport type MediaMessageType = typeof TypeMediaMessage[number];\nexport type MessageSubtypeType = typeof MessageSubtype[number];\n```\n\n## Interface Definitions\n\n### Service Interfaces\n```typescript\nexport interface ServiceInterface {\n  create(instance: InstanceDto, data: any): Promise<any>;\n  find(instance: InstanceDto): Promise<any>;\n  update?(instance: InstanceDto, data: any): Promise<any>;\n  delete?(instance: InstanceDto): Promise<any>;\n}\n\nexport interface ChannelServiceInterface extends ServiceInterface {\n  sendMessage(data: SendMessageDto): Promise<any>;\n  connectToWhatsapp(data?: any): Promise<void>;\n  receiveWebhook?(data: any): Promise<void>;\n}\n\nexport interface ChatbotServiceInterface extends ServiceInterface {\n  processMessage(\n    instanceName: string,\n    remoteJid: string,\n    message: any,\n    pushName?: string,\n  ): Promise<void>;\n}\n```\n\n## Configuration Types\n\n### Environment Configuration Types\n```typescript\nexport interface DatabaseConfig {\n  CONNECTION: {\n    URI: string;\n    DB_PREFIX_NAME: string;\n    CLIENT_NAME?: string;\n  };\n  ENABLED: boolean;\n  SAVE_DATA: {\n    INSTANCE: boolean;\n    NEW_MESSAGE: boolean;\n    MESSAGE_UPDATE: boolean;\n    CONTACTS: boolean;\n    CHATS: boolean;\n  };\n}\n\nexport interface AuthConfig {\n  TYPE: 'apikey' | 'jwt';\n  API_KEY: {\n    KEY: string;\n  };\n  JWT?: {\n    EXPIRIN_IN: number;\n    SECRET: string;\n  };\n}\n\nexport interface CacheConfig {\n  REDIS: {\n    ENABLED: boolean;\n    URI: string;\n    PREFIX_KEY: string;\n    SAVE_INSTANCES: boolean;\n  };\n  LOCAL: {\n    ENABLED: boolean;\n    TTL: number;\n  };\n}\n```\n\n## Message Types\n\n### Message Structure Types\n```typescript\nexport interface MessageContent {\n  text?: string;\n  caption?: string;\n  media?: Buffer | string;\n  mediatype?: 'image' | 'video' | 'audio' | 'document' | 'sticker';\n  fileName?: string;\n  mimetype?: string;\n}\n\nexport interface MessageOptions {\n  delay?: number;\n  presence?: 'unavailable' | 'available' | 'composing' | 'recording' | 'paused';\n  linkPreview?: boolean;\n  mentionsEveryOne?: boolean;\n  mentioned?: string[];\n  quoted?: {\n    key: {\n      remoteJid: string;\n      fromMe: boolean;\n      id: string;\n    };\n    message: any;\n  };\n}\n\nexport interface SendMessageRequest {\n  number: string;\n  content: MessageContent;\n  options?: MessageOptions;\n}\n```\n\n## Webhook Types\n\n### Webhook Payload Types\n```typescript\nexport interface WebhookPayload {\n  event: Events;\n  instance: string;\n  data: any;\n  timestamp: string;\n  server?: {\n    version: string;\n    host: string;\n  };\n}\n\nexport interface WebhookConfig {\n  enabled: boolean;\n  url: string;\n  events: Events[];\n  headers?: Record<string, string>;\n  byEvents?: boolean;\n  base64?: boolean;\n}\n```\n\n## Error Types\n\n### Custom Error Types\n```typescript\nexport interface ApiError {\n  status: number;\n  message: string;\n  error?: string;\n  details?: any;\n  timestamp: string;\n  path: string;\n}\n\nexport interface ValidationError extends ApiError {\n  status: 400;\n  validationErrors: Array<{\n    field: string;\n    message: string;\n    value?: any;\n  }>;\n}\n\nexport interface AuthenticationError extends ApiError {\n  status: 401;\n  message: 'Unauthorized' | 'Invalid API Key' | 'Token Expired';\n}\n```\n\n## Utility Types\n\n### Generic Utility Types\n```typescript\nexport type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;\n\nexport type RequiredFields<T, K extends keyof T> = T & Required<Pick<T, K>>;\n\nexport type DeepPartial<T> = {\n  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];\n};\n\nexport type NonEmptyArray<T> = [T, ...T[]];\n\nexport type StringKeys<T> = {\n  [K in keyof T]: T[K] extends string ? K : never;\n}[keyof T];\n```\n\n## Response Types\n\n### API Response Types\n```typescript\nexport interface ApiResponse<T = any> {\n  success: boolean;\n  data?: T;\n  message?: string;\n  error?: string;\n  timestamp: string;\n}\n\nexport interface PaginatedResponse<T> extends ApiResponse<T[]> {\n  pagination: {\n    page: number;\n    limit: number;\n    total: number;\n    totalPages: number;\n    hasNext: boolean;\n    hasPrev: boolean;\n  };\n}\n\nexport interface InstanceResponse extends ApiResponse {\n  instance: {\n    instanceName: string;\n    status: 'connecting' | 'open' | 'close' | 'qr';\n    qrcode?: string;\n    profileName?: string;\n    profilePicUrl?: string;\n  };\n}\n```\n\n## Integration-Specific Types\n\n### Baileys Types Extension\n```typescript\nimport { WASocket, ConnectionState, DisconnectReason } from 'baileys';\n\nexport interface BaileysInstance {\n  client: WASocket;\n  state: ConnectionState;\n  qrRetry: number;\n  authPath: string;\n}\n\nexport interface BaileysConfig {\n  qrTimeout: number;\n  maxQrRetries: number;\n  authTimeout: number;\n  reconnectInterval: number;\n}\n```\n\n### Business API Types\n```typescript\nexport interface BusinessApiConfig {\n  version: string;\n  baseUrl: string;\n  timeout: number;\n  retries: number;\n}\n\nexport interface BusinessApiMessage {\n  messaging_product: 'whatsapp';\n  to: string;\n  type: 'text' | 'image' | 'document' | 'audio' | 'video' | 'template';\n  text?: {\n    body: string;\n    preview_url?: boolean;\n  };\n  image?: {\n    link?: string;\n    id?: string;\n    caption?: string;\n  };\n  template?: {\n    name: string;\n    language: {\n      code: string;\n    };\n    components?: any[];\n  };\n}\n```\n\n## Type Guards\n\n### Type Guard Functions\n```typescript\nexport function isMediaMessage(message: any): message is MediaMessage {\n  return message && TypeMediaMessage.some(type => message[type]);\n}\n\nexport function isTextMessage(message: any): message is TextMessage {\n  return message && message.conversation;\n}\n\nexport function isValidIntegration(integration: string): integration is IntegrationType {\n  return Object.values(Integration).includes(integration as IntegrationType);\n}\n\nexport function isValidEvent(event: string): event is Events {\n  return Object.values(Events).includes(event as Events);\n}\n```\n\n## Module Augmentation\n\n### Express Request Extension\n```typescript\ndeclare global {\n  namespace Express {\n    interface Request {\n      instanceName?: string;\n      instanceData?: InstanceDto;\n      user?: {\n        id: string;\n        apiKey: string;\n      };\n    }\n  }\n}\n```\n\n## Type Documentation\n\n### JSDoc Type Documentation\n```typescript\n/**\n * WhatsApp instance configuration\n * @interface InstanceConfig\n * @property {string} name - Unique instance name\n * @property {IntegrationType} integration - Integration type\n * @property {string} [token] - API token for business integrations\n * @property {WebhookConfig} [webhook] - Webhook configuration\n * @property {ProxyConfig} [proxy] - Proxy configuration\n */\nexport interface InstanceConfig {\n  name: string;\n  integration: IntegrationType;\n  token?: string;\n  webhook?: WebhookConfig;\n  proxy?: ProxyConfig;\n}\n```","frontmatter":{"description":"Type definitions and interfaces for Evolution API","globs":["src/api/types/**/*.ts","src/@types/**/*.ts"],"alwaysApply":false},"isInternal":false,"tokens":2615,"sizeBytes":10661},{"name":"util-rules.mdc","path":".cursor/rules/specialized-rules/util-rules.mdc","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/specialized-rules/util-rules.mdc","title":"Cursor Rule: util-rules","category":"cursor-rule","format":"mdc","content":"---\ndescription: Utility functions and helpers for Evolution API\nglobs:\n  - \"src/utils/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Utility Rules\n\n## Utility Function Structure\n\n### Standard Utility Pattern\n```typescript\nimport { Logger } from '@config/logger.config';\n\nconst logger = new Logger('UtilityName');\n\nexport function utilityFunction(param: ParamType): ReturnType {\n  try {\n    // Utility logic\n    return result;\n  } catch (error) {\n    logger.error(`Utility function failed: ${error.message}`);\n    throw error;\n  }\n}\n\nexport default utilityFunction;\n```\n\n## Authentication Utilities\n\n### Multi-File Auth State Pattern\n```typescript\nimport { AuthenticationState } from 'baileys';\nimport { CacheService } from '@api/services/cache.service';\nimport fs from 'fs/promises';\nimport path from 'path';\n\nexport default async function useMultiFileAuthStatePrisma(\n  sessionId: string,\n  cache: CacheService,\n): Promise<{\n  state: AuthenticationState;\n  saveCreds: () => Promise<void>;\n}> {\n  const localFolder = path.join(INSTANCE_DIR, sessionId);\n  const localFile = (key: string) => path.join(localFolder, fixFileName(key) + '.json');\n  await fs.mkdir(localFolder, { recursive: true });\n\n  async function writeData(data: any, key: string): Promise<any> {\n    const dataString = JSON.stringify(data, BufferJSON.replacer);\n\n    if (key !== 'creds') {\n      if (process.env.CACHE_REDIS_ENABLED === 'true') {\n        return await cache.hSet(sessionId, key, data);\n      } else {\n        await fs.writeFile(localFile(key), dataString);\n        return;\n      }\n    }\n    await saveKey(sessionId, dataString);\n    return;\n  }\n\n  async function readData(key: string): Promise<any> {\n    try {\n      let rawData;\n\n      if (key !== 'creds') {\n        if (process.env.CACHE_REDIS_ENABLED === 'true') {\n          return await cache.hGet(sessionId, key);\n        } else {\n          if (!(await fileExists(localFile(key)))) return null;\n          rawData = await fs.readFile(localFile(key), { encoding: 'utf-8' });\n          return JSON.parse(rawData, BufferJSON.reviver);\n        }\n      } else {\n        rawData = await getAuthKey(sessionId);\n      }\n\n      const parsedData = JSON.parse(rawData, BufferJSON.reviver);\n      return parsedData;\n    } catch (error) {\n      return null;\n    }\n  }\n\n  async function removeData(key: string): Promise<any> {\n    try {\n      if (key !== 'creds') {\n        if (process.env.CACHE_REDIS_ENABLED === 'true') {\n          return await cache.hDelete(sessionId, key);\n        } else {\n          await fs.unlink(localFile(key));\n        }\n      } else {\n        await deleteAuthKey(sessionId);\n      }\n    } catch (error) {\n      return;\n    }\n  }\n\n  let creds = await readData('creds');\n  if (!creds) {\n    creds = initAuthCreds();\n    await writeData(creds, 'creds');\n  }\n\n  return {\n    state: {\n      creds,\n      keys: {\n        get: async (type, ids) => {\n          const data = {};\n          await Promise.all(\n            ids.map(async (id) => {\n              let value = await readData(`${type}-${id}`);\n              if (type === 'app-state-sync-key' && value) {\n                value = proto.Message.AppStateSyncKeyData.fromObject(value);\n              }\n              data[id] = value;\n            })\n          );\n          return data;\n        },\n        set: async (data) => {\n          const tasks = [];\n          for (const category in data) {\n            for (const id in data[category]) {\n              const value = data[category][id];\n              const key = `${category}-${id}`;\n              tasks.push(value ? writeData(value, key) : removeData(key));\n            }\n          }\n          await Promise.all(tasks);\n        },\n      },\n    },\n    saveCreds: () => writeData(creds, 'creds'),\n  };\n}\n```\n\n## Message Processing Utilities\n\n### Message Content Extraction\n```typescript\nexport const getConversationMessage = (msg: any): string => {\n  const types = getTypeMessage(msg);\n  const messageContent = getMessageContent(types);\n  return messageContent;\n};\n\nconst getTypeMessage = (msg: any): any => {\n  return Object.keys(msg?.message || msg || {})[0];\n};\n\nconst getMessageContent = (type: string, msg?: any): string => {\n  const typeKey = type?.replace('Message', '');\n  \n  const types = {\n    conversation: msg?.message?.conversation,\n    extendedTextMessage: msg?.message?.extendedTextMessage?.text,\n    imageMessage: msg?.message?.imageMessage?.caption || 'Image',\n    videoMessage: msg?.message?.videoMessage?.caption || 'Video',\n    audioMessage: 'Audio',\n    documentMessage: msg?.message?.documentMessage?.caption || 'Document',\n    stickerMessage: 'Sticker',\n    contactMessage: 'Contact',\n    locationMessage: 'Location',\n    liveLocationMessage: 'Live Location',\n    viewOnceMessage: 'View Once',\n    reactionMessage: 'Reaction',\n    pollCreationMessage: 'Poll',\n    pollUpdateMessage: 'Poll Update',\n  };\n\n  let result = types[typeKey] || types[type] || 'Unknown';\n  \n  if (!result || result === 'Unknown') {\n    result = JSON.stringify(msg);\n  }\n\n  return result;\n};\n```\n\n### JID Creation Utility\n```typescript\nexport const createJid = (number: string): string => {\n  if (number.includes('@')) {\n    return number;\n  }\n\n  // Remove any non-numeric characters except +\n  let cleanNumber = number.replace(/[^\\d+]/g, '');\n  \n  // Remove + if present\n  if (cleanNumber.startsWith('+')) {\n    cleanNumber = cleanNumber.substring(1);\n  }\n\n  // Add country code if missing (assuming Brazil as default)\n  if (cleanNumber.length === 11 && cleanNumber.startsWith('11')) {\n    cleanNumber = '55' + cleanNumber;\n  } else if (cleanNumber.length === 10) {\n    cleanNumber = '5511' + cleanNumber;\n  }\n\n  // Determine if it's a group or individual\n  const isGroup = cleanNumber.includes('-');\n  const domain = isGroup ? 'g.us' : 's.whatsapp.net';\n\n  return `${cleanNumber}@${domain}`;\n};\n```\n\n## Cache Utilities\n\n### WhatsApp Number Cache\n```typescript\ninterface ISaveOnWhatsappCacheParams {\n  remoteJid: string;\n  lid?: string;\n}\n\nfunction getAvailableNumbers(remoteJid: string): string[] {\n  const numbersAvailable: string[] = [];\n\n  if (remoteJid.startsWith('+')) {\n    remoteJid = remoteJid.slice(1);\n  }\n\n  const [number, domain] = remoteJid.split('@');\n\n  // Brazilian numbers\n  if (remoteJid.startsWith('55')) {\n    const numberWithDigit =\n      number.slice(4, 5) === '9' && number.length === 13 ? number : `${number.slice(0, 4)}9${number.slice(4)}`;\n    const numberWithoutDigit = number.length === 12 ? number : number.slice(0, 4) + number.slice(5);\n\n    numbersAvailable.push(numberWithDigit);\n    numbersAvailable.push(numberWithoutDigit);\n  }\n  // Mexican/Argentina numbers\n  else if (number.startsWith('52') || number.startsWith('54')) {\n    let prefix = '';\n    if (number.startsWith('52')) {\n      prefix = '1';\n    }\n    if (number.startsWith('54')) {\n      prefix = '9';\n    }\n\n    const numberWithDigit =\n      number.slice(2, 3) === prefix && number.length === 13\n        ? number\n        : `${number.slice(0, 2)}${prefix}${number.slice(2)}`;\n    const numberWithoutDigit = number.length === 12 ? number : number.slice(0, 2) + number.slice(3);\n\n    numbersAvailable.push(numberWithDigit);\n    numbersAvailable.push(numberWithoutDigit);\n  }\n  // Other countries\n  else {\n    numbersAvailable.push(remoteJid);\n  }\n\n  return numbersAvailable.map((number) => `${number}@${domain}`);\n}\n\nexport async function saveOnWhatsappCache(params: ISaveOnWhatsappCacheParams): Promise<void> {\n  const { remoteJid, lid } = params;\n  const db = configService.get<Database>('DATABASE');\n\n  if (!db.SAVE_DATA.CONTACTS) {\n    return;\n  }\n\n  try {\n    const numbersAvailable = getAvailableNumbers(remoteJid);\n    \n    const existingContact = await prismaRepository.contact.findFirst({\n      where: {\n        OR: numbersAvailable.map(number => ({ id: number })),\n      },\n    });\n\n    if (!existingContact) {\n      await prismaRepository.contact.create({\n        data: {\n          id: remoteJid,\n          pushName: '',\n          profilePicUrl: '',\n          isOnWhatsapp: true,\n          lid: lid || null,\n          createdAt: new Date(),\n          updatedAt: new Date(),\n        },\n      });\n    } else {\n      await prismaRepository.contact.update({\n        where: { id: existingContact.id },\n        data: {\n          isOnWhatsapp: true,\n          lid: lid || existingContact.lid,\n          updatedAt: new Date(),\n        },\n      });\n    }\n  } catch (error) {\n    console.error('Error saving WhatsApp cache:', error);\n  }\n}\n```\n\n## Search Utilities\n\n### Advanced Search Operators\n```typescript\nfunction normalizeString(str: string): string {\n  return str\n    .toLowerCase()\n    .normalize('NFD')\n    .replace(/[\\u0300-\\u036f]/g, '');\n}\n\nexport function advancedOperatorsSearch(data: string, query: string): boolean {\n  const normalizedData = normalizeString(data);\n  const normalizedQuery = normalizeString(query);\n\n  // Exact phrase search with quotes\n  if (normalizedQuery.startsWith('\"') && normalizedQuery.endsWith('\"')) {\n    const phrase = normalizedQuery.slice(1, -1);\n    return normalizedData.includes(phrase);\n  }\n\n  // OR operator\n  if (normalizedQuery.includes(' OR ')) {\n    const terms = normalizedQuery.split(' OR ');\n    return terms.some(term => normalizedData.includes(term.trim()));\n  }\n\n  // AND operator (default behavior)\n  if (normalizedQuery.includes(' AND ')) {\n    const terms = normalizedQuery.split(' AND ');\n    return terms.every(term => normalizedData.includes(term.trim()));\n  }\n\n  // NOT operator\n  if (normalizedQuery.startsWith('NOT ')) {\n    const term = normalizedQuery.slice(4);\n    return !normalizedData.includes(term);\n  }\n\n  // Wildcard search\n  if (normalizedQuery.includes('*')) {\n    const regex = new RegExp(normalizedQuery.replace(/\\*/g, '.*'), 'i');\n    return regex.test(normalizedData);\n  }\n\n  // Default: simple contains search\n  return normalizedData.includes(normalizedQuery);\n}\n```\n\n## Proxy Utilities\n\n### Proxy Agent Creation\n```typescript\nimport { HttpsProxyAgent } from 'https-proxy-agent';\nimport { SocksProxyAgent } from 'socks-proxy-agent';\n\ntype Proxy = {\n  host: string;\n  port: string;\n  protocol: 'http' | 'https' | 'socks4' | 'socks5';\n  username?: string;\n  password?: string;\n};\n\nfunction selectProxyAgent(proxyUrl: string): HttpsProxyAgent<string> | SocksProxyAgent {\n  const url = new URL(proxyUrl);\n  \n  if (url.protocol === 'socks4:' || url.protocol === 'socks5:') {\n    return new SocksProxyAgent(proxyUrl);\n  } else {\n    return new HttpsProxyAgent(proxyUrl);\n  }\n}\n\nexport function makeProxyAgent(proxy: Proxy): HttpsProxyAgent<string> | SocksProxyAgent | null {\n  if (!proxy.host || !proxy.port) {\n    return null;\n  }\n\n  let proxyUrl = `${proxy.protocol}://`;\n  \n  if (proxy.username && proxy.password) {\n    proxyUrl += `${proxy.username}:${proxy.password}@`;\n  }\n  \n  proxyUrl += `${proxy.host}:${proxy.port}`;\n\n  try {\n    return selectProxyAgent(proxyUrl);\n  } catch (error) {\n    console.error('Failed to create proxy agent:', error);\n    return null;\n  }\n}\n```\n\n## Telemetry Utilities\n\n### Telemetry Data Collection\n```typescript\nexport interface TelemetryData {\n  route: string;\n  apiVersion: string;\n  timestamp: Date;\n  method?: string;\n  statusCode?: number;\n  responseTime?: number;\n  userAgent?: string;\n  instanceName?: string;\n}\n\nexport const sendTelemetry = async (route: string): Promise<void> => {\n  try {\n    const telemetryData: TelemetryData = {\n      route,\n      apiVersion: packageJson.version,\n      timestamp: new Date(),\n    };\n\n    // Only send telemetry if enabled\n    if (process.env.DISABLE_TELEMETRY === 'true') {\n      return;\n    }\n\n    // Send to telemetry service (implement as needed)\n    await axios.post('https://telemetry.evolution-api.com/collect', telemetryData, {\n      timeout: 5000,\n    });\n  } catch (error) {\n    // Silently fail - don't affect main application\n    console.debug('Telemetry failed:', error.message);\n  }\n};\n```\n\n## Internationalization Utilities\n\n### i18n Setup\n```typescript\nimport { ConfigService, Language } from '@config/env.config';\nimport fs from 'fs';\nimport i18next from 'i18next';\nimport path from 'path';\n\nconst __dirname = path.resolve(process.cwd(), 'src', 'utils');\nconst languages = ['en', 'pt-BR', 'es'];\nconst translationsPath = path.join(__dirname, 'translations');\nconst configService: ConfigService = new ConfigService();\n\nconst resources: any = {};\n\nlanguages.forEach((language) => {\n  const languagePath = path.join(translationsPath, `${language}.json`);\n  if (fs.existsSync(languagePath)) {\n    const translationContent = fs.readFileSync(languagePath, 'utf8');\n    resources[language] = {\n      translation: JSON.parse(translationContent),\n    };\n  }\n});\n\ni18next.init({\n  resources,\n  fallbackLng: 'en',\n  lng: configService.get<Language>('LANGUAGE') || 'pt-BR',\n  interpolation: {\n    escapeValue: false,\n  },\n});\n\nexport const t = i18next.t.bind(i18next);\nexport default i18next;\n```\n\n## Bot Trigger Utilities\n\n### Bot Trigger Matching\n```typescript\nimport { TriggerOperator, TriggerType } from '@prisma/client';\n\nexport function findBotByTrigger(\n  bots: any[],\n  content: string,\n  remoteJid: string,\n): any | null {\n  for (const bot of bots) {\n    if (!bot.enabled) continue;\n    \n    // Check ignore list\n    if (bot.ignoreJids && bot.ignoreJids.includes(remoteJid)) {\n      continue;\n    }\n\n    // Check trigger\n    if (matchesTrigger(content, bot.triggerType, bot.triggerOperator, bot.triggerValue)) {\n      return bot;\n    }\n  }\n\n  return null;\n}\n\nfunction matchesTrigger(\n  content: string,\n  triggerType: TriggerType,\n  triggerOperator: TriggerOperator,\n  triggerValue: string,\n): boolean {\n  const normalizedContent = content.toLowerCase().trim();\n  const normalizedValue = triggerValue.toLowerCase().trim();\n\n  switch (triggerType) {\n    case TriggerType.ALL:\n      return true;\n\n    case TriggerType.KEYWORD:\n      return matchesKeyword(normalizedContent, triggerOperator, normalizedValue);\n\n    case TriggerType.REGEX:\n      try {\n        const regex = new RegExp(triggerValue, 'i');\n        return regex.test(content);\n      } catch {\n        return false;\n      }\n\n    default:\n      return false;\n  }\n}\n\nfunction matchesKeyword(\n  content: string,\n  operator: TriggerOperator,\n  value: string,\n): boolean {\n  switch (operator) {\n    case TriggerOperator.EQUALS:\n      return content === value;\n    case TriggerOperator.CONTAINS:\n      return content.includes(value);\n    case TriggerOperator.STARTS_WITH:\n      return content.startsWith(value);\n    case TriggerOperator.ENDS_WITH:\n      return content.endsWith(value);\n    default:\n      return false;\n  }\n}\n```\n\n## Server Utilities\n\n### Server Status Check\n```typescript\nexport class ServerUP {\n  private static instance: ServerUP;\n  private isServerUp: boolean = false;\n\n  private constructor() {}\n\n  public static getInstance(): ServerUP {\n    if (!ServerUP.instance) {\n      ServerUP.instance = new ServerUP();\n    }\n    return ServerUP.instance;\n  }\n\n  public setServerStatus(status: boolean): void {\n    this.isServerUp = status;\n  }\n\n  public getServerStatus(): boolean {\n    return this.isServerUp;\n  }\n\n  public async waitForServer(timeout: number = 30000): Promise<boolean> {\n    const startTime = Date.now();\n    \n    while (!this.isServerUp && (Date.now() - startTime) < timeout) {\n      await new Promise(resolve => setTimeout(resolve, 100));\n    }\n    \n    return this.isServerUp;\n  }\n}\n```\n\n## Error Response Utilities\n\n### Standardized Error Responses\n```typescript\nexport function createMetaErrorResponse(error: any, context: string) {\n  const timestamp = new Date().toISOString();\n  \n  if (error.response?.data) {\n    return {\n      status: error.response.status || 500,\n      error: {\n        message: error.response.data.error?.message || 'External API error',\n        type: error.response.data.error?.type || 'api_error',\n        code: error.response.data.error?.code || 'unknown_error',\n        context,\n        timestamp,\n      },\n    };\n  }\n\n  return {\n    status: 500,\n    error: {\n      message: error.message || 'Internal server error',\n      type: 'internal_error',\n      code: 'server_error',\n      context,\n      timestamp,\n    },\n  };\n}\n\nexport function createValidationErrorResponse(errors: any[], context: string) {\n  return {\n    status: 400,\n    error: {\n      message: 'Validation failed',\n      type: 'validation_error',\n      code: 'invalid_input',\n      context,\n      details: errors,\n      timestamp: new Date().toISOString(),\n    },\n  };\n}\n```","frontmatter":{"description":"Utility functions and helpers for Evolution API","globs":["src/utils/**/*.ts"],"alwaysApply":false},"isInternal":false,"tokens":3888,"sizeBytes":16474},{"name":"validate-rules.mdc","path":".cursor/rules/specialized-rules/validate-rules.mdc","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/specialized-rules/validate-rules.mdc","title":"Cursor Rule: validate-rules","category":"cursor-rule","format":"mdc","content":"---\ndescription: Validation schemas and patterns for Evolution API\nglobs:\n  - \"src/validate/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Validation Rules\n\n## Validation Schema Structure\n\n### JSONSchema7 Pattern (Evolution API Standard)\n```typescript\nimport { JSONSchema7 } from 'json-schema';\nimport { v4 } from 'uuid';\n\nconst isNotEmpty = (...fields: string[]) => {\n  const properties = {};\n  fields.forEach((field) => {\n    properties[field] = {\n      if: { properties: { [field]: { type: 'string' } } },\n      then: { properties: { [field]: { minLength: 1 } } },\n    };\n  });\n\n  return {\n    allOf: Object.values(properties),\n  };\n};\n\nexport const exampleSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    name: { type: 'string' },\n    description: { type: 'string' },\n    enabled: { type: 'boolean' },\n    settings: {\n      type: 'object',\n      properties: {\n        timeout: { type: 'number', minimum: 1000, maximum: 60000 },\n        retries: { type: 'number', minimum: 0, maximum: 5 },\n      },\n    },\n    tags: {\n      type: 'array',\n      items: { type: 'string' },\n    },\n  },\n  required: ['name', 'enabled'],\n  ...isNotEmpty('name'),\n};\n```\n\n## Message Validation Schemas\n\n### Send Message Validation\n```typescript\nconst numberDefinition = {\n  type: 'string',\n  pattern: '^\\\\d+[\\\\.@\\\\w-]+',\n  description: 'Invalid number',\n};\n\nexport const sendTextSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    number: numberDefinition,\n    text: { type: 'string', minLength: 1, maxLength: 4096 },\n    delay: { type: 'number', minimum: 0, maximum: 60000 },\n    linkPreview: { type: 'boolean' },\n    mentionsEveryOne: { type: 'boolean' },\n    mentioned: {\n      type: 'array',\n      items: { type: 'string' },\n    },\n  },\n  required: ['number', 'text'],\n  ...isNotEmpty('number', 'text'),\n};\n\nexport const sendMediaSchema = Joi.object({\n  number: Joi.string().required().pattern(/^\\d+$/),\n  mediatype: Joi.string().required().valid('image', 'video', 'audio', 'document'),\n  media: Joi.alternatives().try(\n    Joi.string().uri(),\n    Joi.string().base64(),\n  ).required(),\n  caption: Joi.string().optional().max(1024),\n  fileName: Joi.string().optional().max(255),\n  delay: Joi.number().optional().min(0).max(60000),\n}).required();\n\nexport const sendButtonsSchema = Joi.object({\n  number: Joi.string().required().pattern(/^\\d+$/),\n  title: Joi.string().required().max(1024),\n  description: Joi.string().optional().max(1024),\n  footer: Joi.string().optional().max(60),\n  buttons: Joi.array().items(\n    Joi.object({\n      type: Joi.string().required().valid('replyButton', 'urlButton', 'callButton'),\n      displayText: Joi.string().required().max(20),\n      id: Joi.string().when('type', {\n        is: 'replyButton',\n        then: Joi.required().max(256),\n        otherwise: Joi.optional(),\n      }),\n      url: Joi.string().when('type', {\n        is: 'urlButton',\n        then: Joi.required().uri(),\n        otherwise: Joi.optional(),\n      }),\n      phoneNumber: Joi.string().when('type', {\n        is: 'callButton',\n        then: Joi.required().pattern(/^\\+?\\d+$/),\n        otherwise: Joi.optional(),\n      }),\n    })\n  ).min(1).max(3).required(),\n}).required();\n```\n\n## Instance Validation Schemas\n\n### Instance Creation Validation\n```typescript\nexport const instanceSchema = Joi.object({\n  instanceName: Joi.string().required().min(1).max(100).pattern(/^[a-zA-Z0-9_-]+$/),\n  integration: Joi.string().required().valid('WHATSAPP-BAILEYS', 'WHATSAPP-BUSINESS', 'EVOLUTION'),\n  token: Joi.string().when('integration', {\n    is: Joi.valid('WHATSAPP-BUSINESS', 'EVOLUTION'),\n    then: Joi.required().min(10),\n    otherwise: Joi.optional(),\n  }),\n  qrcode: Joi.boolean().optional().default(false),\n  number: Joi.string().optional().pattern(/^\\d+$/),\n  businessId: Joi.string().when('integration', {\n    is: 'WHATSAPP-BUSINESS',\n    then: Joi.required(),\n    otherwise: Joi.optional(),\n  }),\n}).required();\n\nexport const settingsSchema = Joi.object({\n  rejectCall: Joi.boolean().optional(),\n  msgCall: Joi.string().optional().max(500),\n  groupsIgnore: Joi.boolean().optional(),\n  alwaysOnline: Joi.boolean().optional(),\n  readMessages: Joi.boolean().optional(),\n  readStatus: Joi.boolean().optional(),\n  syncFullHistory: Joi.boolean().optional(),\n  wavoipToken: Joi.string().optional(),\n}).optional();\n\nexport const proxySchema = Joi.object({\n  host: Joi.string().required().hostname(),\n  port: Joi.string().required().pattern(/^\\d+$/).custom((value) => {\n    const port = parseInt(value);\n    if (port < 1 || port > 65535) {\n      throw new Error('Port must be between 1 and 65535');\n    }\n    return value;\n  }),\n  protocol: Joi.string().required().valid('http', 'https', 'socks4', 'socks5'),\n  username: Joi.string().optional(),\n  password: Joi.string().optional(),\n}).optional();\n```\n\n## Webhook Validation Schemas\n\n### Webhook Configuration Validation\n```typescript\nexport const webhookSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  url: Joi.string().when('enabled', {\n    is: true,\n    then: Joi.required().uri({ scheme: ['http', 'https'] }),\n    otherwise: Joi.optional(),\n  }),\n  events: Joi.array().items(\n    Joi.string().valid(\n      'APPLICATION_STARTUP',\n      'INSTANCE_CREATE',\n      'INSTANCE_DELETE',\n      'QRCODE_UPDATED',\n      'CONNECTION_UPDATE',\n      'STATUS_INSTANCE',\n      'MESSAGES_SET',\n      'MESSAGES_UPSERT',\n      'MESSAGES_UPDATE',\n      'MESSAGES_DELETE',\n      'CONTACTS_SET',\n      'CONTACTS_UPSERT',\n      'CONTACTS_UPDATE',\n      'CHATS_SET',\n      'CHATS_UPDATE',\n      'CHATS_UPSERT',\n      'CHATS_DELETE',\n      'GROUPS_UPSERT',\n      'GROUPS_UPDATE',\n      'GROUP_PARTICIPANTS_UPDATE',\n      'CALL'\n    )\n  ).min(1).when('enabled', {\n    is: true,\n    then: Joi.required(),\n    otherwise: Joi.optional(),\n  }),\n  headers: Joi.object().pattern(\n    Joi.string(),\n    Joi.string()\n  ).optional(),\n  byEvents: Joi.boolean().optional().default(false),\n  base64: Joi.boolean().optional().default(false),\n}).required();\n```\n\n## Chatbot Validation Schemas\n\n### Base Chatbot Validation\n```typescript\nexport const baseChatbotSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  description: Joi.string().required().min(1).max(500),\n  expire: Joi.number().optional().min(0).max(86400), // 24 hours in seconds\n  keywordFinish: Joi.string().optional().max(100),\n  delayMessage: Joi.number().optional().min(0).max(10000),\n  unknownMessage: Joi.string().optional().max(1000),\n  listeningFromMe: Joi.boolean().optional().default(false),\n  stopBotFromMe: Joi.boolean().optional().default(false),\n  keepOpen: Joi.boolean().optional().default(false),\n  debounceTime: Joi.number().optional().min(0).max(60000),\n  triggerType: Joi.string().required().valid('ALL', 'KEYWORD', 'REGEX'),\n  triggerOperator: Joi.string().when('triggerType', {\n    is: 'KEYWORD',\n    then: Joi.required().valid('EQUALS', 'CONTAINS', 'STARTS_WITH', 'ENDS_WITH'),\n    otherwise: Joi.optional(),\n  }),\n  triggerValue: Joi.string().when('triggerType', {\n    is: Joi.valid('KEYWORD', 'REGEX'),\n    then: Joi.required().min(1).max(500),\n    otherwise: Joi.optional(),\n  }),\n  ignoreJids: Joi.array().items(Joi.string()).optional(),\n  splitMessages: Joi.boolean().optional().default(false),\n  timePerChar: Joi.number().optional().min(10).max(1000).default(100),\n}).required();\n\nexport const typebotSchema = baseChatbotSchema.keys({\n  url: Joi.string().required().uri({ scheme: ['http', 'https'] }),\n  typebot: Joi.string().required().min(1).max(100),\n  apiVersion: Joi.string().optional().valid('v1', 'v2').default('v1'),\n}).required();\n\nexport const openaiSchema = baseChatbotSchema.keys({\n  apiKey: Joi.string().required().min(10),\n  model: Joi.string().optional().valid(\n    'gpt-3.5-turbo',\n    'gpt-3.5-turbo-16k',\n    'gpt-4',\n    'gpt-4-32k',\n    'gpt-4-turbo-preview'\n  ).default('gpt-3.5-turbo'),\n  systemMessage: Joi.string().optional().max(2000),\n  maxTokens: Joi.number().optional().min(1).max(4000).default(1000),\n  temperature: Joi.number().optional().min(0).max(2).default(0.7),\n}).required();\n```\n\n## Business API Validation Schemas\n\n### Template Validation\n```typescript\nexport const templateSchema = Joi.object({\n  name: Joi.string().required().min(1).max(512).pattern(/^[a-z0-9_]+$/),\n  category: Joi.string().required().valid('MARKETING', 'UTILITY', 'AUTHENTICATION'),\n  allowCategoryChange: Joi.boolean().required(),\n  language: Joi.string().required().pattern(/^[a-z]{2}_[A-Z]{2}$/), // e.g., pt_BR, en_US\n  components: Joi.array().items(\n    Joi.object({\n      type: Joi.string().required().valid('HEADER', 'BODY', 'FOOTER', 'BUTTONS'),\n      format: Joi.string().when('type', {\n        is: 'HEADER',\n        then: Joi.valid('TEXT', 'IMAGE', 'VIDEO', 'DOCUMENT'),\n        otherwise: Joi.optional(),\n      }),\n      text: Joi.string().when('type', {\n        is: Joi.valid('HEADER', 'BODY', 'FOOTER'),\n        then: Joi.required().min(1).max(1024),\n        otherwise: Joi.optional(),\n      }),\n      buttons: Joi.array().when('type', {\n        is: 'BUTTONS',\n        then: Joi.items(\n          Joi.object({\n            type: Joi.string().required().valid('QUICK_REPLY', 'URL', 'PHONE_NUMBER'),\n            text: Joi.string().required().min(1).max(25),\n            url: Joi.string().when('type', {\n              is: 'URL',\n              then: Joi.required().uri(),\n              otherwise: Joi.optional(),\n            }),\n            phone_number: Joi.string().when('type', {\n              is: 'PHONE_NUMBER',\n              then: Joi.required().pattern(/^\\+?\\d+$/),\n              otherwise: Joi.optional(),\n            }),\n          })\n        ).min(1).max(10),\n        otherwise: Joi.optional(),\n      }),\n    })\n  ).min(1).required(),\n  webhookUrl: Joi.string().optional().uri({ scheme: ['http', 'https'] }),\n}).required();\n\nexport const catalogSchema = Joi.object({\n  number: Joi.string().optional().pattern(/^\\d+$/),\n  limit: Joi.number().optional().min(1).max(1000).default(10),\n  cursor: Joi.string().optional(),\n}).optional();\n```\n\n## Group Validation Schemas\n\n### Group Management Validation\n```typescript\nexport const createGroupSchema = Joi.object({\n  subject: Joi.string().required().min(1).max(100),\n  description: Joi.string().optional().max(500),\n  participants: Joi.array().items(\n    Joi.string().pattern(/^\\d+$/)\n  ).min(1).max(256).required(),\n  promoteParticipants: Joi.boolean().optional().default(false),\n}).required();\n\nexport const updateGroupSchema = Joi.object({\n  subject: Joi.string().optional().min(1).max(100),\n  description: Joi.string().optional().max(500),\n}).min(1).required();\n\nexport const groupParticipantsSchema = Joi.object({\n  participants: Joi.array().items(\n    Joi.string().pattern(/^\\d+$/)\n  ).min(1).max(50).required(),\n  action: Joi.string().required().valid('add', 'remove', 'promote', 'demote'),\n}).required();\n```\n\n## Label Validation Schemas\n\n### Label Management Validation\n```typescript\nexport const labelSchema = Joi.object({\n  name: Joi.string().required().min(1).max(100),\n  color: Joi.string().required().pattern(/^#[0-9A-Fa-f]{6}$/), // Hex color\n  predefinedId: Joi.string().optional(),\n}).required();\n\nexport const handleLabelSchema = Joi.object({\n  number: Joi.string().required().pattern(/^\\d+$/),\n  labelId: Joi.string().required(),\n  action: Joi.string().required().valid('add', 'remove'),\n}).required();\n```\n\n## Custom Validation Functions\n\n### Phone Number Validation\n```typescript\nexport function validatePhoneNumber(number: string): boolean {\n  // Remove any non-digit characters\n  const cleaned = number.replace(/\\D/g, '');\n  \n  // Check minimum and maximum length\n  if (cleaned.length < 10 || cleaned.length > 15) {\n    return false;\n  }\n  \n  // Check for valid country codes (basic validation)\n  const validCountryCodes = ['1', '7', '20', '27', '30', '31', '32', '33', '34', '36', '39', '40', '41', '43', '44', '45', '46', '47', '48', '49', '51', '52', '53', '54', '55', '56', '57', '58', '60', '61', '62', '63', '64', '65', '66', '81', '82', '84', '86', '90', '91', '92', '93', '94', '95', '98'];\n  \n  // Check if starts with valid country code\n  const startsWithValidCode = validCountryCodes.some(code => cleaned.startsWith(code));\n  \n  return startsWithValidCode;\n}\n\nexport const phoneNumberValidator = Joi.string().custom((value, helpers) => {\n  if (!validatePhoneNumber(value)) {\n    return helpers.error('any.invalid');\n  }\n  return value;\n}, 'Phone number validation');\n```\n\n### Base64 Validation\n```typescript\nexport function validateBase64(base64: string): boolean {\n  try {\n    // Check if it's a valid base64 string\n    const decoded = Buffer.from(base64, 'base64').toString('base64');\n    return decoded === base64;\n  } catch {\n    return false;\n  }\n}\n\nexport const base64Validator = Joi.string().custom((value, helpers) => {\n  if (!validateBase64(value)) {\n    return helpers.error('any.invalid');\n  }\n  return value;\n}, 'Base64 validation');\n```\n\n### URL Validation with Protocol Check\n```typescript\nexport function validateWebhookUrl(url: string): boolean {\n  try {\n    const parsed = new URL(url);\n    return ['http:', 'https:'].includes(parsed.protocol);\n  } catch {\n    return false;\n  }\n}\n\nexport const webhookUrlValidator = Joi.string().custom((value, helpers) => {\n  if (!validateWebhookUrl(value)) {\n    return helpers.error('any.invalid');\n  }\n  return value;\n}, 'Webhook URL validation');\n```\n\n## Validation Error Handling\n\n### Error Message Customization\n```typescript\nexport const validationMessages = {\n  'any.required': 'O campo {#label} é obrigatório',\n  'string.empty': 'O campo {#label} não pode estar vazio',\n  'string.min': 'O campo {#label} deve ter pelo menos {#limit} caracteres',\n  'string.max': 'O campo {#label} deve ter no máximo {#limit} caracteres',\n  'string.pattern.base': 'O campo {#label} possui formato inválido',\n  'number.min': 'O campo {#label} deve ser maior ou igual a {#limit}',\n  'number.max': 'O campo {#label} deve ser menor ou igual a {#limit}',\n  'array.min': 'O campo {#label} deve ter pelo menos {#limit} itens',\n  'array.max': 'O campo {#label} deve ter no máximo {#limit} itens',\n  'any.only': 'O campo {#label} deve ser um dos valores: {#valids}',\n};\n\nexport function formatValidationError(error: Joi.ValidationError): any {\n  return {\n    message: 'Dados de entrada inválidos',\n    details: error.details.map(detail => ({\n      field: detail.path.join('.'),\n      message: detail.message,\n      value: detail.context?.value,\n    })),\n  };\n}\n```\n\n## Schema Composition\n\n### Reusable Schema Components\n```typescript\nexport const commonFields = {\n  instanceName: Joi.string().required().min(1).max(100).pattern(/^[a-zA-Z0-9_-]+$/),\n  number: phoneNumberValidator.required(),\n  delay: Joi.number().optional().min(0).max(60000),\n  enabled: Joi.boolean().optional().default(true),\n};\n\nexport const mediaFields = {\n  mediatype: Joi.string().required().valid('image', 'video', 'audio', 'document'),\n  media: Joi.alternatives().try(\n    Joi.string().uri(),\n    base64Validator,\n  ).required(),\n  caption: Joi.string().optional().max(1024),\n  fileName: Joi.string().optional().max(255),\n};\n\n// Compose schemas using common fields\nexport const quickMessageSchema = Joi.object({\n  ...commonFields,\n  text: Joi.string().required().min(1).max(4096),\n}).required();\n\nexport const quickMediaSchema = Joi.object({\n  ...commonFields,\n  ...mediaFields,\n}).required();\n```","frontmatter":{"description":"Validation schemas and patterns for Evolution API","globs":["src/validate/**/*.ts"],"alwaysApply":false},"isInternal":false,"tokens":4050,"sizeBytes":15376},{"name":"cursor.json","path":".cursor/rules/cursor.json","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/cursor.json","title":"Rules Skill","category":"anthropic-skill","format":"json","content":"{\n  \"version\": \"1.0\",\n  \"description\": \"Cursor IDE configuration for Evolution API project\",\n  \"rules\": {\n    \"general\": {\n      \"max_line_length\": 120,\n      \"indent_size\": 2,\n      \"end_of_line\": \"lf\",\n      \"charset\": \"utf-8\",\n      \"trim_trailing_whitespace\": true,\n      \"insert_final_newline\": true\n    },\n    \"typescript\": {\n      \"quotes\": \"single\",\n      \"semi\": true,\n      \"trailing_comma\": \"es5\",\n      \"bracket_spacing\": true,\n      \"arrow_parens\": \"avoid\",\n      \"print_width\": 120,\n      \"tab_width\": 2,\n      \"use_tabs\": false,\n      \"single_quote\": true,\n      \"end_of_line\": \"lf\",\n      \"strict\": true,\n      \"no_implicit_any\": true,\n      \"strict_null_checks\": true\n    },\n    \"javascript\": {\n      \"quotes\": \"single\",\n      \"semi\": true,\n      \"trailing_comma\": \"es5\",\n      \"bracket_spacing\": true,\n      \"arrow_parens\": \"avoid\",\n      \"print_width\": 120,\n      \"tab_width\": 2,\n      \"use_tabs\": false,\n      \"single_quote\": true,\n      \"end_of_line\": \"lf\",\n      \"style_guide\": \"eslint-airbnb\"\n    },\n    \"json\": {\n      \"tab_width\": 2,\n      \"use_tabs\": false,\n      \"parser\": \"json\"\n    },\n    \"ignore\": {\n      \"files\": [\n        \"node_modules/**\",\n        \"dist/**\",\n        \"build/**\",\n        \".git/**\",\n        \"*.min.js\",\n        \"*.min.css\",\n        \".env\",\n        \".env.*\",\n        \".env.example\",\n        \"coverage/**\",\n        \"*.log\",\n        \"*.lock\",\n        \"pnpm-lock.yaml\",\n        \"package-lock.json\",\n        \"yarn.lock\",\n        \"log/**\",\n        \"tmp/**\",\n        \"instances/**\",\n        \"public/uploads/**\",\n        \"*.dump\",\n        \"*.rdb\",\n        \"*.mmdb\",\n        \".DS_Store\",\n        \"*.swp\",\n        \"*.swo\",\n        \"*.un~\",\n        \".jest-cache\",\n        \".idea/**\",\n        \".vscode/**\",\n        \".yalc/**\",\n        \"yalc.lock\",\n        \"*.local\",\n        \"prisma/migrations/**\",\n        \"prisma/mysql-migrations/**\",\n        \"prisma/postgresql-migrations/**\"\n      ]\n    },\n    \"search\": {\n      \"exclude_patterns\": [\n        \"**/node_modules/**\",\n        \"**/dist/**\",\n        \"**/build/**\",\n        \"**/.git/**\",\n        \"**/coverage/**\",\n        \"**/log/**\",\n        \"**/tmp/**\",\n        \"**/instances/**\",\n        \"**/public/uploads/**\",\n        \"**/*.min.js\",\n        \"**/*.min.css\",\n        \"**/*.log\",\n        \"**/*.lock\",\n        \"**/pnpm-lock.yaml\",\n        \"**/package-lock.json\",\n        \"**/yarn.lock\",\n        \"**/*.dump\",\n        \"**/*.rdb\",\n        \"**/*.mmdb\",\n        \"**/.DS_Store\",\n        \"**/*.swp\",\n        \"**/*.swo\",\n        \"**/*.un~\",\n        \"**/.jest-cache\",\n        \"**/.idea/**\",\n        \"**/.vscode/**\",\n        \"**/.yalc/**\",\n        \"**/yalc.lock\",\n        \"**/*.local\",\n        \"**/prisma/migrations/**\",\n        \"**/prisma/mysql-migrations/**\",\n        \"**/prisma/postgresql-migrations/**\"\n      ]\n    },\n    \"evolution_api\": {\n      \"project_type\": \"nodejs_typescript_api\",\n      \"backend_framework\": \"express_prisma\",\n      \"database\": [\"postgresql\", \"mysql\"],\n      \"cache\": [\"redis\", \"node_cache\"],\n      \"queue\": [\"rabbitmq\", \"sqs\"],\n      \"real_time\": \"socket_io\",\n      \"file_storage\": [\"aws_s3\", \"minio\"],\n      \"validation\": \"class_validator\",\n      \"logging\": \"pino\",\n      \"main_directories\": {\n        \"source\": \"src/\",\n        \"api\": \"src/api/\",\n        \"controllers\": \"src/api/controllers/\",\n        \"services\": \"src/api/services/\",\n        \"integrations\": \"src/api/integrations/\",\n        \"dto\": \"src/api/dto/\",\n        \"types\": \"src/api/types/\",\n        \"guards\": \"src/api/guards/\",\n        \"routes\": \"src/api/routes/\",\n        \"cache\": \"src/cache/\",\n        \"config\": \"src/config/\",\n        \"utils\": \"src/utils/\",\n        \"exceptions\": \"src/exceptions/\",\n        \"validate\": \"src/validate/\",\n        \"prisma\": \"prisma/\",\n        \"tests\": \"test/\",\n        \"docs\": \"docs/\"\n      },\n      \"key_patterns\": [\n        \"whatsapp_integration\",\n        \"multi_database_support\",\n        \"instance_management\",\n        \"event_driven_architecture\",\n        \"service_layer_pattern\",\n        \"dto_validation\",\n        \"webhook_processing\",\n        \"message_queuing\",\n        \"real_time_communication\",\n        \"file_storage_integration\"\n      ],\n      \"whatsapp_integrations\": [\n        \"baileys\",\n        \"meta_business_api\",\n        \"whatsapp_cloud_api\"\n      ],\n      \"external_integrations\": [\n        \"chatwoot\",\n        \"typebot\",\n        \"openai\",\n        \"dify\",\n        \"rabbitmq\",\n        \"sqs\",\n        \"s3\",\n        \"minio\"\n      ]\n    }\n  }\n}\n","frontmatter":{"version":"1.0","description":"Cursor IDE configuration for Evolution API project","rules":{"general":{"max_line_length":120,"indent_size":2,"end_of_line":"lf","charset":"utf-8","trim_trailing_whitespace":true,"insert_final_newline":true},"typescript":{"quotes":"single","semi":true,"trailing_comma":"es5","bracket_spacing":true,"arrow_parens":"avoid","print_width":120,"tab_width":2,"use_tabs":false,"single_quote":true,"end_of_line":"lf","strict":true,"no_implicit_any":true,"strict_null_checks":true},"javascript":{"quotes":"single","semi":true,"trailing_comma":"es5","bracket_spacing":true,"arrow_parens":"avoid","print_width":120,"tab_width":2,"use_tabs":false,"single_quote":true,"end_of_line":"lf","style_guide":"eslint-airbnb"},"json":{"tab_width":2,"use_tabs":false,"parser":"json"},"ignore":{"files":["node_modules/**","dist/**","build/**",".git/**","*.min.js","*.min.css",".env",".env.*",".env.example","coverage/**","*.log","*.lock","pnpm-lock.yaml","package-lock.json","yarn.lock","log/**","tmp/**","instances/**","public/uploads/**","*.dump","*.rdb","*.mmdb",".DS_Store","*.swp","*.swo","*.un~",".jest-cache",".idea/**",".vscode/**",".yalc/**","yalc.lock","*.local","prisma/migrations/**","prisma/mysql-migrations/**","prisma/postgresql-migrations/**"]},"search":{"exclude_patterns":["**/node_modules/**","**/dist/**","**/build/**","**/.git/**","**/coverage/**","**/log/**","**/tmp/**","**/instances/**","**/public/uploads/**","**/*.min.js","**/*.min.css","**/*.log","**/*.lock","**/pnpm-lock.yaml","**/package-lock.json","**/yarn.lock","**/*.dump","**/*.rdb","**/*.mmdb","**/.DS_Store","**/*.swp","**/*.swo","**/*.un~","**/.jest-cache","**/.idea/**","**/.vscode/**","**/.yalc/**","**/yalc.lock","**/*.local","**/prisma/migrations/**","**/prisma/mysql-migrations/**","**/prisma/postgresql-migrations/**"]},"evolution_api":{"project_type":"nodejs_typescript_api","backend_framework":"express_prisma","database":["postgresql","mysql"],"cache":["redis","node_cache"],"queue":["rabbitmq","sqs"],"real_time":"socket_io","file_storage":["aws_s3","minio"],"validation":"class_validator","logging":"pino","main_directories":{"source":"src/","api":"src/api/","controllers":"src/api/controllers/","services":"src/api/services/","integrations":"src/api/integrations/","dto":"src/api/dto/","types":"src/api/types/","guards":"src/api/guards/","routes":"src/api/routes/","cache":"src/cache/","config":"src/config/","utils":"src/utils/","exceptions":"src/exceptions/","validate":"src/validate/","prisma":"prisma/","tests":"test/","docs":"docs/"},"key_patterns":["whatsapp_integration","multi_database_support","instance_management","event_driven_architecture","service_layer_pattern","dto_validation","webhook_processing","message_queuing","real_time_communication","file_storage_integration"],"whatsapp_integrations":["baileys","meta_business_api","whatsapp_cloud_api"],"external_integrations":["chatwoot","typebot","openai","dify","rabbitmq","sqs","s3","minio"]}}},"isInternal":false,"tokens":1168,"sizeBytes":4452},{"name":"README.md","path":".cursor/rules/README.md","rawUrl":"https://raw.githubusercontent.com/EvolutionAPI/evolution-api/HEAD/.cursor/rules/README.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Evolution API Cursor Rules\n\nEste diretório contém as regras e configurações do Cursor IDE para o projeto Evolution API.\n\n## Estrutura dos Arquivos\n\n### Arquivos Principais (alwaysApply: true)\n- **`core-development.mdc`** - Princípios fundamentais de desenvolvimento\n- **`project-context.mdc`** - Contexto específico do projeto Evolution API\n- **`cursor.json`** - Configurações do Cursor IDE\n\n### Regras Especializadas (alwaysApply: false)\nEstas regras são ativadas automaticamente quando você trabalha nos arquivos correspondentes:\n\n#### Camadas da Aplicação\n- **`specialized-rules/service-rules.mdc`** - Padrões para services (`src/api/services/`)\n- **`specialized-rules/controller-rules.mdc`** - Padrões para controllers (`src/api/controllers/`)\n- **`specialized-rules/dto-rules.mdc`** - Padrões para DTOs (`src/api/dto/`)\n- **`specialized-rules/guard-rules.mdc`** - Padrões para guards (`src/api/guards/`)\n- **`specialized-rules/route-rules.mdc`** - Padrões para routers (`src/api/routes/`)\n\n#### Tipos e Validação\n- **`specialized-rules/type-rules.mdc`** - Definições TypeScript (`src/api/types/`)\n- **`specialized-rules/validate-rules.mdc`** - Schemas de validação (`src/validate/`)\n\n#### Utilitários\n- **`specialized-rules/util-rules.mdc`** - Funções utilitárias (`src/utils/`)\n\n#### Integrações\n- **`specialized-rules/integration-channel-rules.mdc`** - Integrações de canal (`src/api/integrations/channel/`)\n- **`specialized-rules/integration-chatbot-rules.mdc`** - Integrações de chatbot (`src/api/integrations/chatbot/`)\n- **`specialized-rules/integration-storage-rules.mdc`** - Integrações de storage (`src/api/integrations/storage/`)\n- **`specialized-rules/integration-event-rules.mdc`** - Integrações de eventos (`src/api/integrations/event/`)\n\n## Como Usar\n\n### Referências Cruzadas\nOs arquivos principais fazem referência aos especializados usando a sintaxe `@specialized-rules/nome-do-arquivo.mdc`. Quando você trabalha em um arquivo específico, o Cursor automaticamente carrega as regras relevantes.\n\n### Exemplo de Uso\nQuando você edita um arquivo em `src/api/services/`, o Cursor automaticamente:\n1. Carrega `core-development.mdc` (sempre ativo)\n2. Carrega `project-context.mdc` (sempre ativo)\n3. Carrega `specialized-rules/service-rules.mdc` (ativado pelo glob pattern)\n\n### Padrões de Código\nCada arquivo de regras contém:\n- **Estruturas padrão** - Como organizar o código\n- **Padrões de nomenclatura** - Convenções de nomes\n- **Exemplos práticos** - Código de exemplo\n- **Anti-padrões** - O que evitar\n- **Testes** - Como testar o código\n- **Padrões de Commit** - Conventional Commits com commitlint\n\n## Configuração do Cursor\n\nO arquivo `cursor.json` contém:\n- Configurações de formatação\n- Padrões de código específicos do Evolution API\n- Diretórios principais do projeto\n- Integrações e tecnologias utilizadas\n\n## Manutenção\n\nPara manter as regras atualizadas:\n1. Analise novos padrões no código\n2. Atualize as regras especializadas correspondentes\n3. Mantenha os exemplos sincronizados com o código real\n4. Documente mudanças significativas\n\n## Tecnologias Cobertas\n\n- **Backend**: Node.js 20+ + TypeScript 5+ + Express.js\n- **Database**: Prisma ORM (PostgreSQL/MySQL)\n- **Cache**: Redis + Node-cache\n- **Queue**: RabbitMQ + Amazon SQS\n- **Real-time**: Socket.io\n- **Storage**: AWS S3 + Minio\n- **Validation**: JSONSchema7\n- **Logging**: Pino\n- **WhatsApp**: Baileys + Meta Business API\n- **Integrations**: Chatwoot, Typebot, OpenAI, Dify\n\n## Estrutura do Projeto\n\n```\nsrc/\n├── api/\n│   ├── controllers/     # Controllers (HTTP handlers)\n│   ├── services/        # Business logic\n│   ├── dto/            # Data Transfer Objects\n│   ├── guards/         # Authentication/Authorization\n│   ├── routes/         # Express routers\n│   ├── types/          # TypeScript definitions\n│   └── integrations/   # External integrations\n│       ├── channel/    # WhatsApp channels (Baileys, Business API)\n│       ├── chatbot/    # Chatbot integrations\n│       ├── event/      # Event integrations\n│       └── storage/    # Storage integrations\n├── cache/              # Cache implementations\n├── config/             # Configuration files\n├── utils/              # Utility functions\n├── validate/           # Validation schemas\n└── exceptions/         # Custom exceptions\n```\n\nEste sistema de regras garante consistência no código e facilita o desenvolvimento seguindo os padrões estabelecidos do Evolution API.\n","isInternal":false,"tokens":1237,"sizeBytes":4624}],"systemPromptSnippet":"<yakaai_skills repo=\"EvolutionAPI/evolution-api\">\n<!-- File: AGENTS.md (Tokens: ~2600 | Category: root-instruction) -->\n# Evolution API - AI Agent Guidelines\n\nThis document provides comprehensive guidelines for AI agents (Claude, GPT, Cursor, etc.) working with the Evolution API codebase.\n\n## Project Overview\n\n**Evolution API** is a production-ready, multi-tenant WhatsApp API platform built with Node.js, TypeScript, and Express.js. It supports multiple WhatsApp providers and extensive integrations with chatbots, CRM systems, and messaging platforms.\n\n## Project Structure & Module Organization\n\n### Core Directories\n- **`src/`** – TypeScript source code with modular architecture\n  - `api/controllers/` – HTTP route handlers (thin layer)\n  - `api/services/` – Business logic (core functionality)\n  - `api/routes/` – Express route definitions (RouterBroker pattern)\n  - `api/integrations/` – External service integrations\n    - `channel/` – WhatsApp providers (Baileys, Business API, Evolution)\n    - `chatbot/` – AI/Bot integrations (OpenAI, Dify, Typebot, Chatwoot)\n    - `event/` – Event systems (WebSocket, RabbitMQ, SQS, NATS, Pusher)\n    - `storage/` – File storage (S3, MinIO)\n  - `dto/` – Data Transfer Objects (simple classes, no decorators)\n  - `guards/` – Authentication/authorization middleware\n  - `types/` – TypeScript type definitions\n  - `repository/` – Data access layer (Prisma)\n- **`prisma/`** – Database schemas and migrations\n  - `postgresql-schema.prisma` / `mysql-schema.prisma` – Provider-specific schemas\n  - `postgresql-migrations/` / `mysql-migrations/` – Provider-specific migrations\n- **`config/`** – Environment and application configuration\n- **`utils/`** – Shared utilities and helper functions\n- **`validate/`** – JSONSchema7 validation schemas\n- **`exceptions/`** – Custom HTTP exception classes\n- **`cache/`** – Redis and local cache implementations\n\n### Build & Deployment\n- **`dist/`** – Build output (do not edit directly)\n- **`public/`** – Static assets and media files\n- **`Docker*`**, **`docker-compose*.yaml`** – Containerization and local development stack\n\n## Build, Test, and Development Commands\n\n### Development Workflow\n```bash\n# Development server with hot reload\nnpm run dev:server\n\n# Direct execution for testing\nnpm start\n\n# Production build and run\nnpm run build\nnpm run start:prod\n```\n\n### Code Quality\n```bash\n# Linting and formatting\nnpm run lint        # ESLint with auto-fix\nnpm run lint:check  # ESLint check only\n\n# Commit with conventional commits\nnpm run commit      # Interactive commit with Commitizen\n```\n\n### Database Management\n```bash\n# Set database provider first (CRITICAL)\nexport DATABASE_PROVIDER=postgresql  # or mysql\n\n# Generate Prisma client\nnpm run db:generate\n\n# Development migrations (with provider sync)\nnpm run db:migrate:dev      # Unix/Mac\nnpm run db:migrate:dev:win  # Windows\n\n# Production deployment\nnpm run db:deploy      # Unix/Mac\nnpm run db:deploy:win  # Windows\n\n# Database tools\nnpm run db:studio      # Open Prisma Studio\n```\n\n### Docker Development\n```bash\n# Start local services (Redis, PostgreSQL, etc.)\ndocker-compose up -d\n\n# Full development stack\ndocker-compose -f docker-compose.dev.yaml up -d\n```\n\n## Coding Standards & Architecture Patterns\n\n### Code Style (Enforced by ESLint + Prettier)\n- **TypeScript strict mode** with full type coverage\n- **2-space indentation**, single quotes, trailing commas\n- **120-character line limit**\n- **Import order** via `simple-import-sort`\n- **File naming**: `feature.kind.ts` (e.g., `whatsapp.baileys.service.ts`)\n- **Naming conventions**:\n  - Classes: `PascalCase`\n  - Functions/variables: `camelCase`\n  - Constants: `UPPER_SNAKE_CASE`\n  - Files: `kebab-case.type.ts`\n\n### Architecture Patterns\n\n#### Service Layer Pattern\n```typescript\nexport class ExampleService {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n  \n  private readonly logger = new Logger('ExampleService');\n  \n  public async create(instance: InstanceDto, data: ExampleDto) {\n    // Business logic here\n    return { example: { ...instance, data } };\n  }\n  \n  public async find(instance: InstanceDto): Promise<ExampleDto | null> {\n    try {\n      const result = await this.waMonitor.waInstances[instance.instanceName].findData();\n      return result || null; // Return null on not found (Evolution pattern)\n    } catch (error) {\n      this.logger.error('Error finding data:', error);\n      return null; // Return null on error (Evolution pattern)\n    }\n  }\n}\n```\n\n#### Controller Pattern (Thin Layer)\n```typescript\nexport class ExampleController {\n  constructor(private readonly exampleService: ExampleService) {}\n  \n  public async createExample(instance: InstanceDto, data: ExampleDto) {\n    return this.exampleService.create(instance, data);\n  }\n}\n```\n\n#### RouterBroker Pattern\n```typescript\nexport class ExampleRouter extends RouterBroker {\n  constructor(...guards: any[]) {\n    super();\n    this.router.post(this.routerPath('create'), ...guards, async (req, res) => {\n      const response = await this.dataValidate<ExampleDto>({\n        request: req,\n        schema: exampleSchema, // JSONSchema7\n        ClassRef: ExampleDto,\n        execute: (instance, data) => controller.createExample(instance, data),\n      });\n      res.status(201).json(response);\n    });\n  }\n}\n```\n\n#### DTO Pattern (Simple Classes)\n```typescript\n// CORRECT - Evolution API pattern (no decorators)\nexport class ExampleDto {\n  name: string;\n  description?: string;\n  enabled: boolean;\n}\n\n// INCORRECT - Don't use class-validator decorators\nexport class BadExampleDto {\n  @IsString() // ❌ Evolution API doesn't use decorators\n  name: string;\n}\n```\n\n#### Validation Pattern (JSONSchema7)\n```typescript\nimport { JSONSchema7 } from 'json-schema';\nimport { v4 } from 'uuid';\n\nexport const exampleSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    name: { type: 'string' },\n    description: { type: 'string' },\n    enabled: { type: 'boolean' },\n  },\n  required: ['name', 'enabled'],\n};\n```\n\n## Multi-Tenant Architecture\n\n### Instance Isolation\n- **CRITICAL**: All operations must be scoped by `instanceName` or `instanceId`\n- **Database queries**: Always include `where: { instanceId: ... }`\n- **Authentication**: Validate instance ownership before operations\n- **Data isolation**: Complete separation between tenant instances\n\n### WhatsApp Instance Management\n```typescript\n// Access instance via WAMonitoringService\nconst waInstance = this.waMonitor.waInstances[instance.instanceName];\nif (!waInstance) {\n  throw new NotFoundException(`Instance ${instance.instanceName} not found`);\n}\n```\n\n## Database Patterns\n\n### Multi-Provider Support\n- **PostgreSQL**: Uses `@db.Integer`, `@db.JsonB`, `@default(now())`\n- **MySQL**: Uses `@db.Int`, `@db.Json`, `@default(now())`\n- **Environment**: Set `DATABASE_PROVIDER=postgresql` or `mysql`\n- **Migrations**: Provider-specific folders auto-selected\n\n### Prisma Repository Pattern\n```typescript\n// Always use PrismaRepository for database operations\nconst result = await this.prismaRepository.instance.findUnique({\n  where: { name: instanceName },\n});\n```\n\n## Integration Patterns\n\n### Channel Integration (WhatsApp Providers)\n- **Baileys**: WhatsApp Web with QR code authentication\n- **Business API**: Official Meta WhatsApp Business API  \n- **Evolution API**: Custom WhatsApp integration\n- **Pattern**: Extend base channel service classes\n\n### Chatbot Integration\n- **Base classes**: Extend `BaseChatbotService` and `BaseChatbotController`\n- **Trigger system**: Support keyword, regex, and advanced triggers\n- **Session management**: Handle conversation state per user\n- **Available integrations**: EvolutionBot, OpenAI, Dify, Typebot, Chatwoot, Flowise, N8N, EvoAI\n\n### Event Integration\n- **Internal events**: EventEmitter2 for application events\n- **External events**: WebSocket, RabbitMQ, SQS, NATS, Pusher\n- **Webhook delivery**: Reliable delivery with retry logic\n\n## Testing Guidelines\n\n### Current State\n- **No formal test suite** currently implemented\n- **Manual testing** is the primary approach\n- **Integration testing** in development environment\n\n### Testing Strategy\n```typescript\n// Place tests in test/ directory as *.test.ts\n// Run: npm test (watches test/all.test.ts)\n\ndescribe('ExampleService', () => {\n  it('should create example', async () => {\n    // Mock external dependencies\n    // Test business logic\n    // Assert expected behavior\n  });\n});\n```\n\n### Recommended Approach\n- Focus on **critical business logic** in services\n- **Mock external dependencies** (WhatsApp APIs, databases)\n- **Integration tests** for API endpoints\n- **Manual testing** for WhatsApp connection flows\n\n## Commit & Pull Request Guidelines\n\n### Conventional Commits (Enforced by commitlint)\n```bash\n# Use interactive commit tool\nnpm run commit\n\n# Commit format: type(scope): subject (max 100 chars)\n# Types: feat, fix, docs, style, refactor, perf, test, chore, ci, build, revert, security\n```\n\n### Examples\n- `feat(api): add WhatsApp message status endpoint`\n- `fix(baileys): resolve connection timeout issue`\n- `docs(readme): update installation instructions`\n- `refactor(service): extract common message validation logic`\n\n### Pull Request Requirements\n- **Clear description** of changes and motivation\n- **Linked issues** if applicable\n- **Migration impact** (specify database provider)\n- **Local testing steps** with screenshots/logs\n- **Breaking changes** clearly documented\n\n## Security & Configuration\n\n### Environment Setup\n```bash\n# Copy example environment file\ncp .env.example .env\n\n# NEVER commit secrets to version control\n# Set DATABASE_PROVIDER before database commands\nexport DATABASE_PROVIDER=postgresql  # or mysql\n```\n\n### Security Best Practices\n- **API key authentication** via `apikey` header\n- **Input validation** with JSONSchema7\n- **Rate limiting** on all endpoints\n- **Webhook signature validation**\n- **Instance-based access control**\n- **Secure defaults** for all configurations\n\n### Vulnerability Reporting\n- See `SECURITY.md` for security vulnerability reporting process\n- Contact: `contato@evolution-api.com`\n\n## Communication Standards\n\n### Language Requirements\n- **User communication**: Always respond in Portuguese (PT-BR)\n- **Code/comments**: English for technical documentation\n- **API responses**: English for consistency\n- **Error messages**: Portuguese for user-facing errors\n\n### Documentation Standards\n- **Inline comments**: Document complex business logic\n- **API documentation**: Document all public endpoints\n- **Integration guides**: Document new integration patterns\n- **Migration guides**: Document database schema changes\n\n## Performance & Scalability\n\n### Caching Strategy\n- **Redis primary**: Distributed caching for production\n- **Node-cache fallback**: Local caching when Redis unavailable\n- **TTL strategy**: Appropriate cache expiration per data type\n- **Cache invalidation**: Proper invalidation on data changes\n\n### Connection Management\n- **Database**: Prisma connection pooling\n- **WhatsApp**: One connection per instance with lifecycle management\n- **Redis**: Connection pooling and retry logic\n- **External APIs**: Rate limiting and retry with exponential backoff\n\n### Monitoring & Observability\n- **Structured logging**: Pino logger with correlation IDs\n- **Error tracking**: Comprehensive error scenarios\n- **Health checks**: Instance status and connection monitoring\n- **Telemetry**: Usage analytics (non-sensitive data only)\n\n<!-- File: CLAUDE.md (Tokens: ~1914 | Category: claude-rule) -->\n# CLAUDE.md\n\nThis file provides comprehensive guidance to Claude AI when working with the Evolution API codebase.\n\n## Project Overview\n\n**Evolution API** is a powerful, production-ready REST API for WhatsApp communication that supports multiple WhatsApp providers:\n- **Baileys** (WhatsApp Web) - Open-source WhatsApp Web client\n- **Meta Business API** - Official WhatsApp Business API\n- **Evolution API** - Custom WhatsApp integration\n\nBuilt with **Node.js 20+**, **TypeScript 5+**, and **Express.js**, it provides extensive integrations with chatbots, CRM systems, and messaging platforms in a **multi-tenant architecture**.\n\n## Common Development Commands\n\n### Build and Run\n```bash\n# Development\nnpm run dev:server    # Run in development with hot reload (tsx watch)\n\n# Production\nnpm run build        # TypeScript check + tsup build\nnpm run start:prod   # Run production build\n\n# Direct execution\nnpm start           # Run with tsx\n```\n\n### Code Quality\n```bash\nnpm run lint        # ESLint with auto-fix\nnpm run lint:check  # ESLint check only\nnpm run commit      # Interactive commit with commitizen\n```\n\n### Database Management\n```bash\n# Set database provider first\nexport DATABASE_PROVIDER=postgresql  # or mysql\n\n# Generate Prisma client (automatically uses DATABASE_PROVIDER env)\nnpm run db:generate\n\n# Deploy migrations (production)\nnpm run db:deploy      # Unix/Mac\nnpm run db:deploy:win  # Windows\n\n# Development migrations (with sync to provider folder)\nnpm run db:migrate:dev      # Unix/Mac\nnpm run db:migrate:dev:win  # Windows\n\n# Open Prisma Studio\nnpm run db:studio\n\n# Development migrations\nnpm run db:migrate:dev      # Unix/Mac\nnpm run db:migrate:dev:win  # Windows\n```\n\n### Testing\n```bash\nnpm test    # Run tests with watch mode\n```\n\n## Architecture Overview\n\n### Core Structure\n- **Multi-tenant SaaS**: Complete instance isolation with per-tenant authentication\n- **Multi-provider database**: PostgreSQL and MySQL via Prisma ORM with provider-specific schemas and migrations\n- **WhatsApp integrations**: Baileys, Meta Business API, and Evolution API with unified interface\n- **Event-driven architecture**: EventEmitter2 for internal events + WebSocket, RabbitMQ, SQS, NATS, Pusher for external events\n- **Microservices pattern**: Modular integrations for chatbots, storage, and external services\n\n### Directory Layout\n```\nsrc/\n├── api/\n│   ├── controllers/     # HTTP route handlers (thin layer)\n│   ├── services/        # Business logic (core functionality)\n│   ├── repository/      # Data access layer (Prisma)\n│   ├── dto/            # Data Transfer Objects (simple classes)\n│   ├── guards/         # Authentication/authorization middleware\n│   ├── integrations/   # External service integrations\n│   │   ├── channel/    # WhatsApp providers (Baileys, Business API, Evolution)\n│   │   ├── chatbot/    # AI/Bot integrations (OpenAI, Dify, Typebot, Chatwoot)\n│   │   ├── event/      # Event systems (WebSocket, RabbitMQ, SQS, NATS, Pusher)\n│   │   └── storage/    # File storage (S3, MinIO)\n│   ├── routes/         # Express route definitions (RouterBroker pattern)\n│   └── types/          # TypeScript type definitions\n├── config/             # Environment and app configuration\n├── cache/             # Redis and local cache implementations\n├── exceptions/        # Custom HTTP exception classes\n├── utils/            # Shared utilities and helpers\n└── validate/         # JSONSchema7 validation schemas\n```\n\n### Key Integration Points\n\n**Channel Integrations** (`src/api/integrations/channel/`):\n- **Baileys**: WhatsApp Web client with QR code authentication\n- **Business API**: Official Meta WhatsApp Business API\n- **Evolution API**: Custom WhatsApp integration\n- Connection lifecycle management per instance with automatic reconnection\n\n**Chatbot Integrations** (`src/api/integrations/chatbot/`):\n- **EvolutionBot**: Native chatbot with trigger system\n- **Chatwoot**: Customer service platform integration\n- **Typebot**: Visual chatbot flow builder\n- **OpenAI**: AI capabilities including GPT and Whisper (audio transcription)\n- **Dify**: AI agent workflow platform\n- **Flowise**: LangChain visual builder\n- **N8N**: Workflow automation platform\n- **EvoAI**: Custom AI integration\n\n**Event Integrations** (`src/api/integrations/event/`):\n- **WebSocket**: Real-time Socket.io connections\n- **RabbitMQ**: Message queue for async processing\n- **Amazon SQS**: Cloud-based message queuing\n- **NATS**: High-performance messaging system\n- **Pusher**: Real-time push notifications\n\n**Storage Integrations** (`src/api/integrations/storage/`):\n- **AWS S3**: Cloud object storage\n- **MinIO**: Self-hosted S3-compatible storage\n- Media file management and URL generation\n\n### Database Schema Management\n- Separate schema files: `postgresql-schema.prisma` and `mysql-schema.prisma`\n- Environment variable `DATABASE_PROVIDER` determines active database\n- Migration folders are provider-specific and auto-selected during deployment\n\n### Authentication & Security\n- **API key-based authentication** via `apikey` header (global or per-instance)\n- **Instance-specific tokens** for WhatsApp connection authentication\n- **Guards system** for route protection and authorization\n- **Input validation** using JSONSchema7 with RouterBroker `dataValidate`\n- **Rate limiting** and security middleware\n- **Webhook signature validation** for external integrations\n\n## Important Implementation Details\n\n### WhatsApp Instance Management\n- Each WhatsApp connection is an \"instance\" with unique name\n- Instance data stored in database with connection state\n- Session persistence in database or file system (configurable)\n- Automatic reconnection handling with exponential backoff\n\n### Message Queue Architecture\n- Supports RabbitMQ, Amazon SQS, and WebSocket for events\n- Event types: message.received, message.sent, connection.update, etc.\n- Configurable per instance which events to send\n\n### Media Handling\n- Local storage or S3/Minio for media files\n- Automatic media download from WhatsApp\n- Media URL generation for external access\n- Support for audio transcription via OpenAI\n\n### Multi-tenancy Support\n- Instance isolation at database level\n- Separate webhook configurations per instance\n- Independent integration settings per instance\n\n## Environment Configuration\n\nKey environment variables are defined in `.env.example`. The system uses a strongly-typed configuration system via `src/config/env.config.ts`.\n\nCritical configurations:\n- `DATABASE_PROVIDER`: postgresql or mysql\n- `DATABASE_CONNECTION_URI`: Database connection string\n- `AUTHENTICATION_API_KEY`: Global API authentication\n- `REDIS_ENABLED`: Enable Redis cache\n- `RABBITMQ_ENABLED`/`SQS_ENABLED`: Message queue options\n\n## Development Guidelines\n\nThe project follows comprehensive development standards defined in `.cursor/rules/`:\n\n### Core Principles\n- **Always respond in Portuguese (PT-BR)** for user communication\n- **Follow established architecture patterns** (Service Layer, RouterBroker, etc.)\n- **Robust error handling** with retry logic and graceful degradation\n- **Multi-database compatibility** (PostgreSQL and MySQL)\n- **Security-first approach** with input validation and rate limiting\n- **Performance optimizations** with Redis caching and connection pooling\n\n### Code Standards\n- **TypeScript strict mode** with full type coverage\n- **JSONSchema7** for input validation (not class-validator)\n- **Conventional Commits** enforced by commitlint\n- **ESLint + Prettier** for code formatting\n- **Service Object pattern** for business logic\n- **RouterBroker pattern** for route handling with `dataValidate`\n\n### Architecture Patterns\n- **Multi-tenant isolation** at database and instance level\n- **Event-driven communication** with EventEmitter2\n- **Microservices integration** pattern for external services\n- **Connection pooling** and lifecycle management\n- **Caching strategy** with Redis primary and Node-cache fallback\n\n## Testing Approach\n\nCurrently, the project has minimal formal testing infrastructure:\n- **Manual testing** is the primary approach\n- **Integration testing** in development environment\n- **No unit test suite** currently implemented\n- Test files can be placed in `test/` directory as `*.test.ts`\n- Run `npm test` for watch mode development testing\n\n### Recommended Testing Strategy\n- Focus on **critical business logic** in services\n- **Mock external dependencies** (WhatsApp APIs, databases)\n- **Integration tests** for API endpoints\n- **Manual testing** for WhatsApp connection flows\n\n## Deployment Considerations\n\n- Docker support with `Dockerfile` and `docker-compose.yaml`\n- Graceful shutdown handling for connections\n- Health check endpoints for monitoring\n- Sentry integration for error tracking\n- Telemetry for usage analytics (non-sensitive data only)\n\n<!-- File: .cursor/rules/core-development.mdc (Tokens: ~1585 | Category: cursor-rule) -->\n---\ndescription: Core development principles and standards for Evolution API development\nglobs:\nalwaysApply: true\n---\n\n# Evolution API Development Standards\n\n## Cross-References\n- **Project Context**: @project-context.mdc for Evolution API-specific patterns\n- **Specialized Rules**: \n  - @specialized-rules/service-rules.mdc for service layer patterns\n  - @specialized-rules/controller-rules.mdc for controller patterns\n  - @specialized-rules/dto-rules.mdc for DTO validation patterns\n  - @specialized-rules/guard-rules.mdc for authentication/authorization\n  - @specialized-rules/route-rules.mdc for router patterns\n  - @specialized-rules/type-rules.mdc for TypeScript definitions\n  - @specialized-rules/util-rules.mdc for utility functions\n  - @specialized-rules/validate-rules.mdc for validation schemas\n  - @specialized-rules/integration-channel-rules.mdc for channel integrations\n  - @specialized-rules/integration-chatbot-rules.mdc for chatbot integrations\n  - @specialized-rules/integration-storage-rules.mdc for storage integrations\n  - @specialized-rules/integration-event-rules.mdc for event integrations\n- **TypeScript/Node.js**: Node.js 20+ + TypeScript 5+ best practices\n- **Express/Prisma**: Express.js + Prisma ORM patterns\n- **WhatsApp Integrations**: Baileys + Meta Business API patterns\n\n## Senior Engineer Context - Evolution API Platform\n- You are a senior software engineer working on a WhatsApp API platform\n- Focus on Node.js + TypeScript + Express.js full-stack development\n- Specialized in real-time messaging, WhatsApp integrations, and event-driven architecture\n- Apply scalable patterns for multi-tenant API platform\n- Consider WhatsApp integration workflow implications and performance at scale\n\n## Fundamental Principles\n\n### Code Quality Standards\n- **Simplicity First**: Always prefer simple solutions over complex ones\n- **DRY Principle**: Avoid code duplication - check for existing similar functionality before implementing\n- **Single Responsibility**: Each function/class should have one clear purpose\n- **Readable Code**: Write code that tells a story - clear naming and structure\n\n### Problem Resolution Approach\n- **Follow Existing Patterns**: Use established Service patterns, DTOs, and Integration patterns\n- **Event-Driven First**: Leverage EventEmitter2 for event publishing when adding new features\n- **Integration Pattern**: Follow existing WhatsApp integration patterns for new channels\n- **Conservative Changes**: Prefer extending existing services over creating new architecture\n- **Clean Migration**: Remove deprecated patterns when introducing new ones\n- **Incremental Changes**: Break large changes into smaller, testable increments with proper migrations\n\n### File and Function Organization - Node.js/TypeScript Structure\n- **Services**: Keep services focused and under 200 lines\n- **Controllers**: Keep controllers thin - only routing and validation\n- **DTOs**: Use JSONSchema7 for all input validation\n- **Integrations**: Follow `src/api/integrations/` structure for new integrations\n- **Utils**: Extract common functionality into well-named utilities\n- **Types**: Define clear TypeScript interfaces and types\n\n### Code Analysis and Reflection\n- After writing code, deeply reflect on scalability and maintainability\n- Provide 1-2 paragraph analysis of code changes\n- Suggest improvements or next steps based on reflection\n- Consider performance, security, and maintenance implications\n\n## Development Standards\n\n### TypeScript Standards\n- **Strict Mode**: Always use TypeScript strict mode\n- **No Any**: Avoid `any` type - use proper typing\n- **Interfaces**: Define clear contracts with interfaces\n- **Enums**: Use enums for constants and status values\n- **Generics**: Use generics for reusable components\n\n### Error Handling Standards\n- **HTTP Exceptions**: Use appropriate HTTP status codes\n- **Logging**: Structured logging with context\n- **Retry Logic**: Implement retry for external services\n- **Graceful Degradation**: Handle service failures gracefully\n\n### Security Standards\n- **Input Validation**: Validate all inputs with JSONSchema7\n- **Authentication**: Use API keys and JWT tokens\n- **Rate Limiting**: Implement rate limiting for APIs\n- **Data Sanitization**: Sanitize sensitive data in logs\n\n### Performance Standards\n- **Caching**: Use Redis for frequently accessed data\n- **Database**: Optimize Prisma queries with proper indexing\n- **Memory**: Monitor memory usage and implement cleanup\n- **Async**: Use async/await properly with error handling\n\n## Communication Standards\n\n### Language Requirements\n- **User Communication**: Always respond in Portuguese (PT-BR)\n- **Code Comments**: English for technical documentation\n- **API Documentation**: English for consistency\n- **Error Messages**: Portuguese for user-facing errors\n\n### Documentation Standards\n- **Code Comments**: Document complex business logic\n- **API Documentation**: Document all public endpoints\n- **README**: Keep project documentation updated\n- **Changelog**: Document breaking changes\n\n## Quality Assurance\n\n### Testing Standards\n- **Unit Tests**: Test business logic in services\n- **Integration Tests**: Test API endpoints\n- **Mocks**: Mock external dependencies\n- **Coverage**: Aim for 70%+ test coverage\n\n### Code Review Standards\n- **Peer Review**: All code must be reviewed\n- **Automated Checks**: ESLint, Prettier, TypeScript\n- **Security Review**: Check for security vulnerabilities\n- **Performance Review**: Check for performance issues\n\n### Commit Standards (Conventional Commits)\n- **Format**: `type(scope): subject` (max 100 characters)\n- **Types**: \n  - `feat` - New feature\n  - `fix` - Bug fix\n  - `docs` - Documentation changes\n  - `style` - Code style changes (formatting, etc)\n  - `refactor` - Code refactoring\n  - `perf` - Performance improvements\n  - `test` - Adding or updating tests\n  - `chore` - Maintenance tasks\n  - `ci` - CI/CD changes\n  - `build` - Build system changes\n  - `revert` - Reverting changes\n  - `security` - Security fixes\n- **Examples**:\n  - `feat(api): add WhatsApp message status endpoint`\n  - `fix(baileys): resolve connection timeout issue`\n  - `docs(readme): update installation instructions`\n  - `refactor(service): extract common message validation logic`\n- **Tools**: Use `npm run commit` (Commitizen) for guided commits\n- **Validation**: Enforced by commitlint on commit-msg hook\n\n## Evolution API Specific Patterns\n\n### WhatsApp Integration Patterns\n- **Connection Management**: One connection per instance\n- **Event Handling**: Proper event listeners for Baileys\n- **Message Processing**: Queue-based message processing\n- **Error Recovery**: Automatic reconnection logic\n\n### Multi-Database Support\n- **Schema Compatibility**: Support PostgreSQL and MySQL\n- **Migration Sync**: Keep migrations synchronized\n- **Type Safety**: Use Prisma generated types\n- **Connection Pooling**: Proper database connection management\n\n### Cache Strategy\n- **Redis Primary**: Use Redis for distributed caching\n- **Local Fallback**: Node-cache for local fallback\n- **TTL Strategy**: Appropriate TTL for different data types\n- **Cache Invalidation**: Proper cache invalidation patterns\n\n### Event System\n- **EventEmitter2**: Use for internal events\n- **WebSocket**: Socket.io for real-time updates\n- **Queue Systems**: RabbitMQ/SQS for async processing\n- **Webhook Processing**: Proper webhook validation and processing\n\n<!-- File: .cursor/rules/project-context.mdc (Tokens: ~1674 | Category: cursor-rule) -->\n---\ndescription: Evolution API project-specific context and constraints\nglobs:\nalwaysApply: true\n---\n\n# Evolution API Project Context\n\n## Cross-References\n- **Core Development**: @core-development.mdc for fundamental development principles\n- **Specialized Rules**: Reference specific specialized rules when working on:\n  - Services: @specialized-rules/service-rules.mdc\n  - Controllers: @specialized-rules/controller-rules.mdc\n  - DTOs: @specialized-rules/dto-rules.mdc\n  - Guards: @specialized-rules/guard-rules.mdc\n  - Routes: @specialized-rules/route-rules.mdc\n  - Types: @specialized-rules/type-rules.mdc\n  - Utils: @specialized-rules/util-rules.mdc\n  - Validation: @specialized-rules/validate-rules.mdc\n  - Channel Integrations: @specialized-rules/integration-channel-rules.mdc\n  - Chatbot Integrations: @specialized-rules/integration-chatbot-rules.mdc\n  - Storage Integrations: @specialized-rules/integration-storage-rules.mdc\n  - Event Integrations: @specialized-rules/integration-event-rules.mdc\n- **TypeScript/Node.js**: Node.js 20+ + TypeScript 5+ backend standards\n- **Express/Prisma**: Express.js + Prisma ORM patterns\n- **WhatsApp Integrations**: Baileys, Meta Business API, and other messaging platforms\n\n## Technology Stack\n- **Backend**: Node.js 20+ + TypeScript 5+ + Express.js\n- **Database**: Prisma ORM (PostgreSQL/MySQL support)\n- **Cache**: Redis + Node-cache for local fallback\n- **Queue**: RabbitMQ + Amazon SQS for message processing\n- **Real-time**: Socket.io for WebSocket connections\n- **Storage**: AWS S3 + Minio for file storage\n- **Validation**: JSONSchema7 for input validation\n- **Logging**: Pino for structured logging\n- **Architecture**: Multi-tenant API with WhatsApp integrations\n\n## Project-Specific Patterns\n\n### WhatsApp Integration Architecture\n- **MANDATORY**: All WhatsApp integrations must follow established patterns\n- **BAILEYS**: Use `whatsapp.baileys.service.ts` patterns for WhatsApp Web\n- **META BUSINESS**: Use `whatsapp.business.service.ts` for official API\n- **CONNECTION MANAGEMENT**: One connection per instance with proper lifecycle\n- **EVENT HANDLING**: Proper event listeners and error handling\n\n### Multi-Database Architecture\n- **CRITICAL**: Support both PostgreSQL and MySQL\n- **SCHEMAS**: Use appropriate schema files (postgresql-schema.prisma / mysql-schema.prisma)\n- **MIGRATIONS**: Keep migrations synchronized between databases\n- **TYPES**: Use database-specific types (@db.JsonB vs @db.Json)\n- **COMPATIBILITY**: Ensure feature parity between databases\n\n### API Integration Workflow\n- **CORE FEATURE**: REST API for WhatsApp communication\n- **COMPLEXITY**: High - involves webhook processing, message routing, and instance management\n- **COMPONENTS**: Instance management, message handling, media processing\n- **INTEGRATIONS**: Baileys, Meta Business API, Chatwoot, Typebot, OpenAI, Dify\n\n### Multi-Tenant Instance Architecture\n- **CRITICAL**: All operations must be scoped by instance\n- **ISOLATION**: Complete data isolation between instances\n- **SECURITY**: Validate instance ownership before operations\n- **SCALING**: Support thousands of concurrent instances\n- **AUTHENTICATION**: API key-based authentication per instance\n\n## Documentation Requirements\n\n### Implementation Documentation\n- **MANDATORY**: Document complex integration patterns\n- **LOCATION**: Use inline comments for business logic\n- **API DOCS**: Document all public endpoints\n- **WEBHOOK DOCS**: Document webhook payloads and signatures\n\n### Change Documentation\n- **CHANGELOG**: Document breaking changes\n- **MIGRATION GUIDES**: Document database migrations\n- **INTEGRATION GUIDES**: Document new integration patterns\n\n## Environment and Security\n\n### Environment Variables\n- **CRITICAL**: Never hardcode sensitive values\n- **VALIDATION**: Validate required environment variables on startup\n- **SECURITY**: Use secure defaults and proper encryption\n- **DOCUMENTATION**: Document all environment variables\n\n### File Organization - Node.js/TypeScript Structure\n- **CONTROLLERS**: Organized by feature (`api/controllers/`)\n- **SERVICES**: Business logic in service classes (`api/services/`)\n- **INTEGRATIONS**: External integrations (`api/integrations/`)\n- **DTOS**: Data transfer objects (`api/dto/`)\n- **TYPES**: TypeScript types (`api/types/`)\n- **UTILS**: Utility functions (`utils/`)\n\n## Integration Points\n\n### WhatsApp Providers\n- **BAILEYS**: WhatsApp Web integration with QR code\n- **META BUSINESS**: Official WhatsApp Business API\n- **CLOUD API**: WhatsApp Cloud API integration\n- **WEBHOOK PROCESSING**: Proper webhook validation and processing\n\n### External Integrations\n- **CHATWOOT**: Customer support platform integration\n- **TYPEBOT**: Chatbot flow integration\n- **OPENAI**: AI-powered chat integration\n- **DIFY**: AI workflow integration\n- **STORAGE**: S3/Minio for media file storage\n\n### Event-Driven Communication\n- **EVENTEMITTER2**: Internal event system\n- **SOCKET.IO**: Real-time WebSocket communication\n- **RABBITMQ**: Message queue for async processing\n- **SQS**: Amazon SQS for cloud-based queuing\n- **WEBHOOKS**: Outbound webhook system\n\n## Development Constraints\n\n### Language Requirements\n- **USER COMMUNICATION**: Always respond in Portuguese (PT-BR)\n- **CODE/COMMENTS**: English for code and technical documentation\n- **API RESPONSES**: English for consistency\n- **ERROR MESSAGES**: Portuguese for user-facing errors\n\n### Performance Constraints\n- **MEMORY**: Efficient memory usage for multiple instances\n- **DATABASE**: Optimized queries with proper indexing\n- **CACHE**: Strategic caching for frequently accessed data\n- **CONNECTIONS**: Proper connection pooling and management\n\n### Security Constraints\n- **AUTHENTICATION**: API key validation for all endpoints\n- **AUTHORIZATION**: Instance-based access control\n- **INPUT VALIDATION**: Validate all inputs with JSONSchema7\n- **RATE LIMITING**: Prevent abuse with rate limiting\n- **WEBHOOK SECURITY**: Validate webhook signatures\n\n## Quality Standards\n- **TYPE SAFETY**: Full TypeScript coverage with strict mode\n- **ERROR HANDLING**: Comprehensive error scenarios with proper logging\n- **TESTING**: Unit and integration tests for critical paths\n- **MONITORING**: Proper logging and error tracking\n- **DOCUMENTATION**: Clear API documentation and code comments\n- **PERFORMANCE**: Optimized for high-throughput message processing\n- **SECURITY**: Secure by default with proper validation\n- **SCALABILITY**: Design for horizontal scaling\n\n## Evolution API Specific Development Patterns\n\n### Instance Management\n- **LIFECYCLE**: Proper instance creation, connection, and cleanup\n- **STATE MANAGEMENT**: Track connection status and health\n- **RECOVERY**: Automatic reconnection and error recovery\n- **MONITORING**: Health checks and status reporting\n\n### Message Processing\n- **QUEUE-BASED**: Use queues for message processing\n- **RETRY LOGIC**: Implement exponential backoff for failures\n- **MEDIA HANDLING**: Proper media upload and processing\n- **WEBHOOK DELIVERY**: Reliable webhook delivery with retries\n\n### Integration Patterns\n- **SERVICE LAYER**: Business logic in service classes\n- **DTO VALIDATION**: Input validation with JSONSchema7\n- **ERROR HANDLING**: Consistent error responses\n- **LOGGING**: Structured logging with correlation IDs\n\n### Database Patterns\n- **PRISMA**: Use Prisma ORM for all database operations\n- **TRANSACTIONS**: Use transactions for multi-step operations\n- **MIGRATIONS**: Proper migration management\n- **INDEXING**: Optimize queries with appropriate indexes\n\n<!-- File: .cursor/rules/specialized-rules/controller-rules.mdc (Tokens: ~2073 | Category: cursor-rule) -->\n---\ndescription: Controller patterns for Evolution API\nglobs:\n  - \"src/api/controllers/**/*.ts\"\n  - \"src/api/integrations/**/controllers/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Controller Rules\n\n## Controller Structure Pattern\n\n### Standard Controller Class\n```typescript\nexport class ExampleController {\n  constructor(private readonly exampleService: ExampleService) {}\n\n  public async createExample(instance: InstanceDto, data: ExampleDto) {\n    return this.exampleService.create(instance, data);\n  }\n\n  public async findExample(instance: InstanceDto) {\n    return this.exampleService.find(instance);\n  }\n}\n```\n\n## Dependency Injection Pattern\n\n### Service Injection\n```typescript\n// CORRECT - Evolution API pattern\nexport class ChatController {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n\n  public async whatsappNumber({ instanceName }: InstanceDto, data: WhatsAppNumberDto) {\n    return await this.waMonitor.waInstances[instanceName].getWhatsAppNumbers(data);\n  }\n}\n\n// INCORRECT - Don't inject multiple services when waMonitor is sufficient\nexport class ChatController {\n  constructor(\n    private readonly waMonitor: WAMonitoringService,\n    private readonly prismaRepository: PrismaRepository, // ❌ Unnecessary if waMonitor has access\n    private readonly configService: ConfigService, // ❌ Unnecessary if waMonitor has access\n  ) {}\n}\n```\n\n## Method Signature Pattern\n\n### Instance Parameter Pattern\n```typescript\n// CORRECT - Evolution API pattern (destructuring instanceName)\npublic async fetchCatalog({ instanceName }: InstanceDto, data: getCatalogDto) {\n  return await this.waMonitor.waInstances[instanceName].fetchCatalog(instanceName, data);\n}\n\n// CORRECT - Alternative pattern for full instance (when using services)\npublic async createTemplate(instance: InstanceDto, data: TemplateDto) {\n  return this.templateService.create(instance, data);\n}\n\n// INCORRECT - Don't use generic method names\npublic async methodName(instance: InstanceDto, data: DataDto) { // ❌ Use specific names\n  return this.service.performAction(instance, data);\n}\n```\n\n## WAMonitor Access Pattern\n\n### Direct WAMonitor Usage\n```typescript\n// CORRECT - Standard pattern in controllers\nexport class CallController {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n\n  public async offerCall({ instanceName }: InstanceDto, data: OfferCallDto) {\n    return await this.waMonitor.waInstances[instanceName].offerCall(data);\n  }\n}\n```\n\n## Controller Registration Pattern\n\n### Server Module Registration\n```typescript\n// In server.module.ts\nexport const templateController = new TemplateController(templateService);\nexport const businessController = new BusinessController(waMonitor);\nexport const chatController = new ChatController(waMonitor);\nexport const callController = new CallController(waMonitor);\n```\n\n## Error Handling in Controllers\n\n### Let Services Handle Errors\n```typescript\n// CORRECT - Let service handle errors\npublic async fetchCatalog(instance: InstanceDto, data: getCatalogDto) {\n  return await this.waMonitor.waInstances[instance.instanceName].fetchCatalog(instance.instanceName, data);\n}\n\n// INCORRECT - Don't add try-catch in controllers unless specific handling needed\npublic async fetchCatalog(instance: InstanceDto, data: getCatalogDto) {\n  try {\n    return await this.waMonitor.waInstances[instance.instanceName].fetchCatalog(instance.instanceName, data);\n  } catch (error) {\n    throw error; // ❌ Unnecessary try-catch\n  }\n}\n```\n\n## Complex Controller Pattern\n\n### Instance Controller Pattern\n```typescript\nexport class InstanceController {\n  constructor(\n    private readonly waMonitor: WAMonitoringService,\n    private readonly configService: ConfigService,\n    private readonly prismaRepository: PrismaRepository,\n    private readonly eventEmitter: EventEmitter2,\n    private readonly chatwootService: ChatwootService,\n    private readonly settingsService: SettingsService,\n    private readonly proxyService: ProxyController,\n    private readonly cache: CacheService,\n    private readonly chatwootCache: CacheService,\n    private readonly baileysCache: CacheService,\n    private readonly providerFiles: ProviderFiles,\n  ) {}\n\n  private readonly logger = new Logger('InstanceController');\n\n  // Multiple methods handling different aspects\n  public async createInstance(data: InstanceDto) {\n    // Complex instance creation logic\n  }\n\n  public async deleteInstance({ instanceName }: InstanceDto) {\n    // Complex instance deletion logic\n  }\n}\n```\n\n## Channel Controller Pattern\n\n### Base Channel Controller\n```typescript\nexport class ChannelController {\n  public prismaRepository: PrismaRepository;\n  public waMonitor: WAMonitoringService;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    this.prisma = prismaRepository;\n    this.monitor = waMonitor;\n  }\n\n  // Getters and setters for dependency access\n  public set prisma(prisma: PrismaRepository) {\n    this.prismaRepository = prisma;\n  }\n\n  public get prisma() {\n    return this.prismaRepository;\n  }\n\n  public set monitor(waMonitor: WAMonitoringService) {\n    this.waMonitor = waMonitor;\n  }\n\n  public get monitor() {\n    return this.waMonitor;\n  }\n}\n```\n\n### Extended Channel Controller\n```typescript\nexport class EvolutionController extends ChannelController implements ChannelControllerInterface {\n  private readonly logger = new Logger('EvolutionController');\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n  }\n\n  integrationEnabled: boolean;\n\n  public async receiveWebhook(data: any) {\n    const numberId = data.numberId;\n\n    if (!numberId) {\n      this.logger.error('WebhookService -> receiveWebhookEvolution -> numberId not found');\n      return;\n    }\n\n    const instance = await this.prismaRepository.instance.findFirst({\n      where: { number: numberId },\n    });\n\n    if (!instance) {\n      this.logger.error('WebhookService -> receiveWebhook -> instance not found');\n      return;\n    }\n\n    await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data);\n\n    return {\n      status: 'success',\n    };\n  }\n}\n```\n\n## Chatbot Controller Pattern\n\n### Base Chatbot Controller\n```typescript\nexport abstract class BaseChatbotController<BotType = any, BotData extends BaseChatbotDto = BaseChatbotDto>\n  extends ChatbotController\n  implements ChatbotControllerInterface\n{\n  public readonly logger: Logger;\n  integrationEnabled: boolean;\n  \n  // Abstract methods to be implemented\n  protected abstract readonly integrationName: string;\n  protected abstract processBot(/* parameters */): Promise<void>;\n  protected abstract getFallbackBotId(settings: any): string | undefined;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n  }\n\n  // Base implementation methods\n  public async createBot(instance: InstanceDto, data: BotData) {\n    // Common bot creation logic\n  }\n}\n```\n\n## Method Naming Conventions\n\n### Standard Method Names\n- `create*()` - Create operations\n- `find*()` - Find operations  \n- `fetch*()` - Fetch from external APIs\n- `send*()` - Send operations\n- `receive*()` - Receive webhook/data\n- `handle*()` - Handle specific actions\n- `offer*()` - Offer services (like calls)\n\n## Return Patterns\n\n### Direct Return Pattern\n```typescript\n// CORRECT - Direct return from service\npublic async createTemplate(instance: InstanceDto, data: TemplateDto) {\n  return this.templateService.create(instance, data);\n}\n\n// CORRECT - Direct return from waMonitor\npublic async offerCall({ instanceName }: InstanceDto, data: OfferCallDto) {\n  return await this.waMonitor.waInstances[instanceName].offerCall(data);\n}\n```\n\n## Controller Testing Pattern\n\n### Unit Test Structure\n```typescript\ndescribe('ExampleController', () => {\n  let controller: ExampleController;\n  let service: jest.Mocked<ExampleService>;\n\n  beforeEach(() => {\n    const mockService = {\n      create: jest.fn(),\n      find: jest.fn(),\n    };\n\n    controller = new ExampleController(mockService as any);\n    service = mockService as any;\n  });\n\n  describe('createExample', () => {\n    it('should call service create method', async () => {\n      const instance = { instanceName: 'test' };\n      const data = { test: 'data' };\n      const expectedResult = { success: true };\n\n      service.create.mockResolvedValue(expectedResult);\n\n      const result = await controller.createExample(instance, data);\n\n      expect(service.create).toHaveBeenCalledWith(instance, data);\n      expect(result).toEqual(expectedResult);\n    });\n  });\n});\n```\n\n## Interface Implementation\n\n### Controller Interface Pattern\n```typescript\nexport interface ChannelControllerInterface {\n  integrationEnabled: boolean;\n}\n\nexport interface ChatbotControllerInterface {\n  integrationEnabled: boolean;\n  createBot(instance: InstanceDto, data: any): Promise<any>;\n  findBot(instance: InstanceDto): Promise<any>;\n  // ... other methods\n}\n```\n\n## Controller Organization\n\n### File Naming Convention\n- `*.controller.ts` - Main controllers\n- `*/*.controller.ts` - Integration-specific controllers\n\n### Method Organization\n1. Constructor\n2. Public methods (alphabetical order)\n3. Private methods (if any)\n\n### Import Organization\n```typescript\n// DTOs first\nimport { InstanceDto } from '@api/dto/instance.dto';\nimport { ExampleDto } from '@api/dto/example.dto';\n\n// Services\nimport { ExampleService } from '@api/services/example.service';\n\n// Types\nimport { WAMonitoringService } from '@api/services/monitor.service';\n```\n\n<!-- File: .cursor/rules/specialized-rules/dto-rules.mdc (Tokens: ~2220 | Category: cursor-rule) -->\n---\ndescription: DTO patterns and validation for Evolution API\nglobs:\n  - \"src/api/dto/**/*.ts\"\n  - \"src/api/integrations/**/dto/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API DTO Rules\n\n## DTO Structure Pattern\n\n### Basic DTO Class\n```typescript\nexport class ExampleDto {\n  name: string;\n  category: string;\n  allowCategoryChange: boolean;\n  language: string;\n  components: any;\n  webhookUrl?: string;\n}\n```\n\n## Inheritance Pattern\n\n### DTO Inheritance\n```typescript\n// CORRECT - Evolution API pattern\nexport class InstanceDto extends IntegrationDto {\n  instanceName: string;\n  instanceId?: string;\n  qrcode?: boolean;\n  businessId?: string;\n  number?: string;\n  integration?: string;\n  token?: string;\n  status?: string;\n  ownerJid?: string;\n  profileName?: string;\n  profilePicUrl?: string;\n  \n  // Settings\n  rejectCall?: boolean;\n  msgCall?: string;\n  groupsIgnore?: boolean;\n  alwaysOnline?: boolean;\n  readMessages?: boolean;\n  readStatus?: boolean;\n  syncFullHistory?: boolean;\n  wavoipToken?: string;\n  \n  // Proxy settings\n  proxyHost?: string;\n  proxyPort?: string;\n  proxyProtocol?: string;\n  proxyUsername?: string;\n  proxyPassword?: string;\n  \n  // Webhook configuration\n  webhook?: {\n    enabled?: boolean;\n    events?: string[];\n    headers?: JsonValue;\n    url?: string;\n    byEvents?: boolean;\n    base64?: boolean;\n  };\n  \n  // Chatwoot integration\n  chatwootAccountId?: string;\n  chatwootConversationPending?: boolean;\n  chatwootAutoCreate?: boolean;\n  chatwootDaysLimitImportMessages?: number;\n  chatwootImportContacts?: boolean;\n  chatwootImportMessages?: boolean;\n  chatwootLogo?: string;\n  chatwootMergeBrazilContacts?: boolean;\n  chatwootNameInbox?: string;\n  chatwootOrganization?: string;\n  chatwootReopenConversation?: boolean;\n  chatwootSignMsg?: boolean;\n  chatwootToken?: string;\n  chatwootUrl?: string;\n}\n```\n\n## Base DTO Pattern\n\n### Base Chatbot DTO\n```typescript\n/**\n * Base DTO for all chatbot integrations\n * Contains common properties shared by all chatbot types\n */\nexport class BaseChatbotDto {\n  enabled?: boolean;\n  description: string;\n  expire?: number;\n  keywordFinish?: string;\n  delayMessage?: number;\n  unknownMessage?: string;\n  listeningFromMe?: boolean;\n  stopBotFromMe?: boolean;\n  keepOpen?: boolean;\n  debounceTime?: number;\n  triggerType: TriggerType;\n  triggerOperator?: TriggerOperator;\n  triggerValue?: string;\n  ignoreJids?: string[];\n  splitMessages?: boolean;\n  timePerChar?: number;\n}\n\n/**\n * Base settings DTO for all chatbot integrations\n */\nexport class BaseChatbotSettingDto {\n  expire?: number;\n  keywordFinish?: string;\n  delayMessage?: number;\n  unknownMessage?: string;\n  listeningFromMe?: boolean;\n  stopBotFromMe?: boolean;\n  keepOpen?: boolean;\n  debounceTime?: number;\n  ignoreJids?: string[];\n  splitMessages?: boolean;\n  timePerChar?: number;\n}\n```\n\n## Message DTO Patterns\n\n### Send Message DTOs\n```typescript\nexport class Metadata {\n  number: string;\n  delay?: number;\n}\n\nexport class SendTextDto extends Metadata {\n  text: string;\n  linkPreview?: boolean;\n  mentionsEveryOne?: boolean;\n  mentioned?: string[];\n}\n\nexport class SendListDto extends Metadata {\n  title: string;\n  description: string;\n  buttonText: string;\n  footerText?: string;\n\n  sections: Section[];\n}\n\nexport class ContactMessage {\n  fullName: string;\n  wuid: string;\n  phoneNumber: string;\n  organization?: string;\n  email?: string;\n  url?: string;\n}\n\nexport class SendTemplateDto extends Metadata {\n  name: string;\n  language: string;\n  components: any;\n}\n```\n\n## Simple DTO Patterns\n\n### Basic DTOs\n```typescript\nexport class NumberDto {\n  number: string;\n}\n\nexport class LabelDto {\n  id?: string;\n  name: string;\n  color: string;\n  predefinedId?: string;\n}\n\nexport class HandleLabelDto {\n  number: string;\n  labelId: string;\n}\n\nexport class ProfileNameDto {\n  name: string;\n}\n\nexport class WhatsAppNumberDto {\n  numbers: string[];\n}\n```\n\n## Complex DTO Patterns\n\n### Business DTOs\n```typescript\nexport class getCatalogDto {\n  number?: string;\n  limit?: number;\n  cursor?: string;\n}\n\nexport class getCollectionsDto {\n  number?: string;\n  limit?: number;\n  cursor?: string;\n}\n\nexport class NumberBusiness {\n  number: string;\n  name?: string;\n  description?: string;\n  email?: string;\n  websites?: string[];\n  latitude?: number;\n  longitude?: number;\n  address?: string;\n  profilehandle?: string;\n}\n```\n\n## Settings DTO Pattern\n\n### Settings Configuration\n```typescript\nexport class SettingsDto {\n  rejectCall?: boolean;\n  msgCall?: string;\n  groupsIgnore?: boolean;\n  alwaysOnline?: boolean;\n  readMessages?: boolean;\n  readStatus?: boolean;\n  syncFullHistory?: boolean;\n  wavoipToken?: string;\n}\n\nexport class ProxyDto {\n  host?: string;\n  port?: string;\n  protocol?: string;\n  username?: string;\n  password?: string;\n}\n```\n\n## Presence DTO Pattern\n\n### WhatsApp Presence\n```typescript\nexport class SetPresenceDto {\n  presence: WAPresence;\n}\n\nexport class SendPresenceDto {\n  number: string;\n  presence: WAPresence;\n}\n```\n\n## DTO Structure (No Decorators)\n\n### Simple DTO Classes (Evolution API Pattern)\n```typescript\n// CORRECT - Evolution API pattern (no decorators)\nexport class ExampleDto {\n  name: string;\n  description?: string;\n  enabled: boolean;\n  items?: string[];\n  timeout?: number;\n}\n\n// INCORRECT - Don't use class-validator decorators\nexport class ValidatedDto {\n  @IsString() // ❌ Evolution API doesn't use decorators\n  name: string;\n}\n```\n\n## Type Safety Patterns\n\n### Prisma Type Integration\n```typescript\nimport { JsonValue } from '@prisma/client/runtime/library';\nimport { WAPresence } from 'baileys';\nimport { TriggerOperator, TriggerType } from '@prisma/client';\n\nexport class TypeSafeDto {\n  presence: WAPresence;\n  triggerType: TriggerType;\n  triggerOperator?: TriggerOperator;\n  metadata?: JsonValue;\n}\n```\n\n## DTO Documentation\n\n### JSDoc Comments\n```typescript\n/**\n * DTO for creating WhatsApp templates\n * Used by Meta Business API integration\n */\nexport class TemplateDto {\n  /** Template name - must be unique */\n  name: string;\n  \n  /** Template category (MARKETING, UTILITY, AUTHENTICATION) */\n  category: string;\n  \n  /** Whether category can be changed after creation */\n  allowCategoryChange: boolean;\n  \n  /** Language code (e.g., 'pt_BR', 'en_US') */\n  language: string;\n  \n  /** Template components (header, body, footer, buttons) */\n  components: any;\n  \n  /** Optional webhook URL for template status updates */\n  webhookUrl?: string;\n}\n```\n\n## DTO Naming Conventions\n\n### Standard Naming Patterns\n- `*Dto` - Data transfer objects\n- `Create*Dto` - Creation DTOs\n- `Update*Dto` - Update DTOs\n- `Send*Dto` - Message sending DTOs\n- `Get*Dto` - Query DTOs\n- `Handle*Dto` - Action DTOs\n\n## File Organization\n\n### DTO File Structure\n```\nsrc/api/dto/\n├── instance.dto.ts          # Main instance DTO\n├── template.dto.ts          # Template management\n├── sendMessage.dto.ts       # Message sending DTOs\n├── chat.dto.ts             # Chat operations\n├── business.dto.ts         # Business API DTOs\n├── group.dto.ts            # Group management\n├── label.dto.ts            # Label management\n├── proxy.dto.ts            # Proxy configuration\n├── settings.dto.ts         # Instance settings\n└── call.dto.ts             # Call operations\n```\n\n## Integration DTO Patterns\n\n### Chatbot Integration DTOs\n```typescript\n// Base for all chatbot DTOs\nexport class BaseChatbotDto {\n  enabled?: boolean;\n  description: string;\n  // ... common properties\n}\n\n// Specific chatbot DTOs extend base\nexport class TypebotDto extends BaseChatbotDto {\n  url: string;\n  typebot: string;\n  // ... typebot-specific properties\n}\n\nexport class OpenaiDto extends BaseChatbotDto {\n  apiKey: string;\n  model: string;\n  // ... openai-specific properties\n}\n```\n\n## DTO Testing Pattern\n\n### DTO Validation Tests\n```typescript\ndescribe('ExampleDto', () => {\n  it('should validate required fields', () => {\n    const dto = new ExampleDto();\n    dto.name = 'test';\n    dto.category = 'MARKETING';\n    dto.allowCategoryChange = true;\n    dto.language = 'pt_BR';\n    dto.components = {};\n\n    expect(dto.name).toBe('test');\n    expect(dto.category).toBe('MARKETING');\n  });\n\n  it('should handle optional fields', () => {\n    const dto = new ExampleDto();\n    dto.name = 'test';\n    dto.category = 'MARKETING';\n    dto.allowCategoryChange = true;\n    dto.language = 'pt_BR';\n    dto.components = {};\n    dto.webhookUrl = 'https://example.com/webhook';\n\n    expect(dto.webhookUrl).toBe('https://example.com/webhook');\n  });\n});\n```\n\n## DTO Transformation\n\n### Request to DTO Mapping (Evolution API Pattern)\n```typescript\n// CORRECT - Evolution API uses RouterBroker dataValidate\nconst response = await this.dataValidate<ExampleDto>({\n  request: req,\n  schema: exampleSchema, // JSONSchema7\n  ClassRef: ExampleDto,\n  execute: (instance, data) => controller.method(instance, data),\n});\n\n// INCORRECT - Don't use class-validator\nconst dto = plainToClass(ExampleDto, req.body); // ❌ Not used in Evolution API\nconst errors = await validate(dto); // ❌ Not used in Evolution API\n```\n\n<!-- File: .cursor/rules/specialized-rules/guard-rules.mdc (Tokens: ~2365 | Category: cursor-rule) -->\n---\ndescription: Guard patterns for authentication and authorization in Evolution API\nglobs:\n  - \"src/api/guards/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Guard Rules\n\n## Guard Structure Pattern\n\n### Standard Guard Function\n```typescript\nimport { NextFunction, Request, Response } from 'express';\nimport { Logger } from '@config/logger.config';\nimport { UnauthorizedException, ForbiddenException } from '@exceptions';\n\nconst logger = new Logger('GUARD');\n\nasync function guardFunction(req: Request, _: Response, next: NextFunction) {\n  // Guard logic here\n  \n  if (validationFails) {\n    throw new UnauthorizedException();\n  }\n  \n  return next();\n}\n\nexport const guardName = { guardFunction };\n```\n\n## Authentication Guard Pattern\n\n### API Key Authentication\n```typescript\nasync function apikey(req: Request, _: Response, next: NextFunction) {\n  const env = configService.get<Auth>('AUTHENTICATION').API_KEY;\n  const key = req.get('apikey');\n  const db = configService.get<Database>('DATABASE');\n\n  if (!key) {\n    throw new UnauthorizedException();\n  }\n\n  // Global API key check\n  if (env.KEY === key) {\n    return next();\n  }\n\n  // Special routes handling\n  if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) {\n    throw new ForbiddenException('Missing global api key', 'The global api key must be set');\n  }\n\n  const param = req.params as unknown as InstanceDto;\n\n  try {\n    if (param?.instanceName) {\n      const instance = await prismaRepository.instance.findUnique({\n        where: { name: param.instanceName },\n      });\n      if (instance.token === key) {\n        return next();\n      }\n    } else {\n      if (req.originalUrl.includes('/instance/fetchInstances') && db.SAVE_DATA.INSTANCE) {\n        const instanceByKey = await prismaRepository.instance.findFirst({\n          where: { token: key },\n        });\n        if (instanceByKey) {\n          return next();\n        }\n      }\n    }\n  } catch (error) {\n    logger.error(error);\n  }\n\n  throw new UnauthorizedException();\n}\n\nexport const authGuard = { apikey };\n```\n\n## Instance Validation Guards\n\n### Instance Exists Guard\n```typescript\nasync function getInstance(instanceName: string) {\n  try {\n    const cacheConf = configService.get<CacheConf>('CACHE');\n\n    const exists = !!waMonitor.waInstances[instanceName];\n\n    if (cacheConf.REDIS.ENABLED && cacheConf.REDIS.SAVE_INSTANCES) {\n      const keyExists = await cache.has(instanceName);\n      return exists || keyExists;\n    }\n\n    return exists || (await prismaRepository.instance.findMany({ where: { name: instanceName } })).length > 0;\n  } catch (error) {\n    throw new InternalServerErrorException(error?.toString());\n  }\n}\n\nexport async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) {\n  if (req.originalUrl.includes('/instance/create')) {\n    return next();\n  }\n\n  const param = req.params as unknown as InstanceDto;\n  if (!param?.instanceName) {\n    throw new BadRequestException('\"instanceName\" not provided.');\n  }\n\n  if (!(await getInstance(param.instanceName))) {\n    throw new NotFoundException(`The \"${param.instanceName}\" instance does not exist`);\n  }\n\n  next();\n}\n```\n\n### Instance Logged Guard\n```typescript\nexport async function instanceLoggedGuard(req: Request, _: Response, next: NextFunction) {\n  if (req.originalUrl.includes('/instance/create')) {\n    const instance = req.body as InstanceDto;\n    if (await getInstance(instance.instanceName)) {\n      throw new ForbiddenException(`This name \"${instance.instanceName}\" is already in use.`);\n    }\n\n    if (waMonitor.waInstances[instance.instanceName]) {\n      delete waMonitor.waInstances[instance.instanceName];\n    }\n  }\n\n  next();\n}\n```\n\n## Telemetry Guard Pattern\n\n### Telemetry Collection\n```typescript\nclass Telemetry {\n  public collectTelemetry(req: Request, res: Response, next: NextFunction): void {\n    // Collect telemetry data\n    const telemetryData = {\n      route: req.originalUrl,\n      method: req.method,\n      timestamp: new Date(),\n      userAgent: req.get('User-Agent'),\n    };\n\n    // Send telemetry asynchronously (don't block request)\n    setImmediate(() => {\n      this.sendTelemetry(telemetryData);\n    });\n\n    next();\n  }\n\n  private async sendTelemetry(data: any): Promise<void> {\n    try {\n      // Send telemetry data\n    } catch (error) {\n      // Silently fail - don't affect main request\n    }\n  }\n}\n\nexport default Telemetry;\n```\n\n## Guard Composition Pattern\n\n### Multiple Guards Usage\n```typescript\n// In router setup\nconst guards = [instanceExistsGuard, instanceLoggedGuard, authGuard['apikey']];\n\nrouter\n  .use('/instance', new InstanceRouter(configService, ...guards).router)\n  .use('/message', new MessageRouter(...guards).router)\n  .use('/chat', new ChatRouter(...guards).router);\n```\n\n## Error Handling in Guards\n\n### Proper Exception Throwing\n```typescript\n// CORRECT - Use proper HTTP exceptions\nif (!apiKey) {\n  throw new UnauthorizedException('API key required');\n}\n\nif (instanceExists) {\n  throw new ForbiddenException('Instance already exists');\n}\n\nif (!instanceFound) {\n  throw new NotFoundException('Instance not found');\n}\n\nif (validationFails) {\n  throw new BadRequestException('Invalid request parameters');\n}\n\n// INCORRECT - Don't use generic Error\nif (!apiKey) {\n  throw new Error('API key required'); // ❌ Use specific exceptions\n}\n```\n\n## Configuration Access in Guards\n\n### Config Service Usage\n```typescript\nasync function configAwareGuard(req: Request, _: Response, next: NextFunction) {\n  const authConfig = configService.get<Auth>('AUTHENTICATION');\n  const cacheConfig = configService.get<CacheConf>('CACHE');\n  const dbConfig = configService.get<Database>('DATABASE');\n\n  // Use configuration for guard logic\n  if (authConfig.API_KEY.KEY === providedKey) {\n    return next();\n  }\n\n  throw new UnauthorizedException();\n}\n```\n\n## Database Access in Guards\n\n### Prisma Repository Usage\n```typescript\nasync function databaseGuard(req: Request, _: Response, next: NextFunction) {\n  try {\n    const param = req.params as unknown as InstanceDto;\n    \n    const instance = await prismaRepository.instance.findUnique({\n      where: { name: param.instanceName },\n    });\n\n    if (!instance) {\n      throw new NotFoundException('Instance not found');\n    }\n\n    // Additional validation logic\n    if (instance.status !== 'active') {\n      throw new ForbiddenException('Instance not active');\n    }\n\n    return next();\n  } catch (error) {\n    logger.error('Database guard error:', error);\n    throw new InternalServerErrorException('Database access failed');\n  }\n}\n```\n\n## Cache Integration in Guards\n\n### Cache Service Usage\n```typescript\nasync function cacheAwareGuard(req: Request, _: Response, next: NextFunction) {\n  const cacheConf = configService.get<CacheConf>('CACHE');\n  \n  if (cacheConf.REDIS.ENABLED) {\n    const cached = await cache.get(`guard:${req.params.instanceName}`);\n    if (cached) {\n      // Use cached validation result\n      return next();\n    }\n  }\n\n  // Perform validation and cache result\n  const isValid = await performValidation(req.params.instanceName);\n  \n  if (cacheConf.REDIS.ENABLED) {\n    await cache.set(`guard:${req.params.instanceName}`, isValid, 300); // 5 min TTL\n  }\n\n  if (isValid) {\n    return next();\n  }\n\n  throw new UnauthorizedException();\n}\n```\n\n## Logging in Guards\n\n### Structured Logging\n```typescript\nconst logger = new Logger('GUARD');\n\nasync function loggedGuard(req: Request, _: Response, next: NextFunction) {\n  logger.log(`Guard validation started for ${req.originalUrl}`);\n\n  try {\n    // Guard logic\n    const isValid = await validateRequest(req);\n    \n    if (isValid) {\n      logger.log(`Guard validation successful for ${req.params.instanceName}`);\n      return next();\n    }\n\n    logger.warn(`Guard validation failed for ${req.params.instanceName}`);\n    throw new UnauthorizedException();\n  } catch (error) {\n    logger.error(`Guard validation error: ${error.message}`, error.stack);\n    throw error;\n  }\n}\n```\n\n## Guard Testing Pattern\n\n### Unit Test Structure\n```typescript\ndescribe('authGuard', () => {\n  let req: Partial<Request>;\n  let res: Partial<Response>;\n  let next: NextFunction;\n\n  beforeEach(() => {\n    req = {\n      get: jest.fn(),\n      params: {},\n      originalUrl: '/test',\n    };\n    res = {};\n    next = jest.fn();\n  });\n\n  describe('apikey', () => {\n    it('should pass with valid global API key', async () => {\n      (req.get as jest.Mock).mockReturnValue('valid-global-key');\n      \n      await authGuard.apikey(req as Request, res as Response, next);\n      \n      expect(next).toHaveBeenCalled();\n    });\n\n    it('should throw UnauthorizedException with no API key', async () => {\n      (req.get as jest.Mock).mockReturnValue(undefined);\n      \n      await expect(\n        authGuard.apikey(req as Request, res as Response, next)\n      ).rejects.toThrow(UnauthorizedException);\n    });\n\n    it('should pass with valid instance token', async () => {\n      (req.get as jest.Mock).mockReturnValue('instance-token');\n      req.params = { instanceName: 'test-instance' };\n      \n      // Mock prisma repository\n      jest.spyOn(prismaRepository.instance, 'findUnique').mockResolvedValue({\n        token: 'instance-token',\n      } as any);\n      \n      await authGuard.apikey(req as Request, res as Response, next);\n      \n      expect(next).toHaveBeenCalled();\n    });\n  });\n});\n```\n\n## Guard Performance Considerations\n\n### Efficient Validation\n```typescript\n// CORRECT - Efficient guard with early returns\nasync function efficientGuard(req: Request, _: Response, next: NextFunction) {\n  // Quick checks first\n  if (req.originalUrl.includes('/public')) {\n    return next(); // Skip validation for public routes\n  }\n\n  const apiKey = req.get('apikey');\n  if (!apiKey) {\n    throw new UnauthorizedException(); // Fail fast\n  }\n\n  // More expensive checks only if needed\n  if (apiKey === globalKey) {\n    return next(); // Skip database check\n  }\n\n  // Database check only as last resort\n  const isValid = await validateInDatabase(apiKey);\n  if (isValid) {\n    return next();\n  }\n\n  throw new UnauthorizedException();\n}\n\n// INCORRECT - Inefficient guard\nasync function inefficientGuard(req: Request, _: Response, next: NextFunction) {\n  // Always do expensive database check first\n  const dbResult = await expensiveDatabaseQuery(); // ❌ Expensive operation first\n  \n  const apiKey = req.get('apikey');\n  if (!apiKey && dbResult) { // ❌ Complex logic\n    throw new UnauthorizedException();\n  }\n  \n  next();\n}\n```\n\n<!-- File: .cursor/rules/specialized-rules/integration-channel-rules.mdc (Tokens: ~3295 | Category: cursor-rule) -->\n---\ndescription: Channel integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/channel/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Channel Integration Rules\n\n## Channel Controller Pattern\n\n### Base Channel Controller\n```typescript\nexport interface ChannelControllerInterface {\n  integrationEnabled: boolean;\n}\n\nexport class ChannelController {\n  public prismaRepository: PrismaRepository;\n  public waMonitor: WAMonitoringService;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    this.prisma = prismaRepository;\n    this.monitor = waMonitor;\n  }\n\n  public set prisma(prisma: PrismaRepository) {\n    this.prismaRepository = prisma;\n  }\n\n  public get prisma() {\n    return this.prismaRepository;\n  }\n\n  public set monitor(waMonitor: WAMonitoringService) {\n    this.waMonitor = waMonitor;\n  }\n\n  public get monitor() {\n    return this.waMonitor;\n  }\n\n  public init(instanceData: InstanceDto, data: ChannelDataType) {\n    if (!instanceData.token && instanceData.integration === Integration.WHATSAPP_BUSINESS) {\n      throw new BadRequestException('token is required');\n    }\n\n    if (instanceData.integration === Integration.WHATSAPP_BUSINESS) {\n      return new BusinessStartupService(/* dependencies */);\n    }\n\n    if (instanceData.integration === Integration.EVOLUTION) {\n      return new EvolutionStartupService(/* dependencies */);\n    }\n\n    if (instanceData.integration === Integration.WHATSAPP_BAILEYS) {\n      return new BaileysStartupService(/* dependencies */);\n    }\n\n    return null;\n  }\n}\n```\n\n### Extended Channel Controller\n```typescript\nexport class EvolutionController extends ChannelController implements ChannelControllerInterface {\n  private readonly logger = new Logger('EvolutionController');\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n  }\n\n  integrationEnabled: boolean;\n\n  public async receiveWebhook(data: any) {\n    const numberId = data.numberId;\n\n    if (!numberId) {\n      this.logger.error('WebhookService -> receiveWebhookEvolution -> numberId not found');\n      return;\n    }\n\n    const instance = await this.prismaRepository.instance.findFirst({\n      where: { number: numberId },\n    });\n\n    if (!instance) {\n      this.logger.error('WebhookService -> receiveWebhook -> instance not found');\n      return;\n    }\n\n    await this.waMonitor.waInstances[instance.name].connectToWhatsapp(data);\n\n    return {\n      status: 'success',\n    };\n  }\n}\n```\n\n## Channel Service Pattern\n\n### Base Channel Service\n```typescript\nexport class ChannelStartupService {\n  constructor(\n    private readonly configService: ConfigService,\n    private readonly eventEmitter: EventEmitter2,\n    private readonly prismaRepository: PrismaRepository,\n    public readonly cache: CacheService,\n    public readonly chatwootCache: CacheService,\n  ) {}\n\n  public readonly logger = new Logger('ChannelStartupService');\n\n  public client: WASocket;\n  public readonly instance: wa.Instance = {};\n  public readonly localChatwoot: wa.LocalChatwoot = {};\n  public readonly localProxy: wa.LocalProxy = {};\n  public readonly localSettings: wa.LocalSettings = {};\n  public readonly localWebhook: wa.LocalWebHook = {};\n\n  public setInstance(instance: InstanceDto) {\n    this.logger.setInstance(instance.instanceName);\n\n    this.instance.name = instance.instanceName;\n    this.instance.id = instance.instanceId;\n    this.instance.integration = instance.integration;\n    this.instance.number = instance.number;\n    this.instance.token = instance.token;\n    this.instance.businessId = instance.businessId;\n\n    if (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {\n      this.chatwootService.eventWhatsapp(\n        Events.STATUS_INSTANCE,\n        { instanceName: this.instance.name },\n        {\n          instance: this.instance.name,\n          status: 'created',\n        },\n      );\n    }\n  }\n\n  public set instanceName(name: string) {\n    this.logger.setInstance(name);\n    this.instance.name = name;\n  }\n\n  public get instanceName() {\n    return this.instance.name;\n  }\n}\n```\n\n### Extended Channel Service\n```typescript\nexport class EvolutionStartupService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache);\n  }\n\n  public async sendMessage(data: SendTextDto): Promise<any> {\n    // Evolution-specific message sending logic\n    const response = await this.evolutionApiCall('/send-message', data);\n    return response;\n  }\n\n  public async connectToWhatsapp(data: any): Promise<void> {\n    // Evolution-specific connection logic\n    this.logger.log('Connecting to Evolution API');\n    \n    // Set up webhook listeners\n    this.setupWebhookHandlers();\n    \n    // Initialize connection\n    await this.initializeConnection(data);\n  }\n\n  private async evolutionApiCall(endpoint: string, data: any): Promise<any> {\n    const config = this.configService.get<Evolution>('EVOLUTION');\n    \n    try {\n      const response = await axios.post(`${config.API_URL}${endpoint}`, data, {\n        headers: {\n          'Authorization': `Bearer ${this.instance.token}`,\n          'Content-Type': 'application/json',\n        },\n      });\n      \n      return response.data;\n    } catch (error) {\n      this.logger.error(`Evolution API call failed: ${error.message}`);\n      throw new InternalServerErrorException('Evolution API call failed');\n    }\n  }\n\n  private setupWebhookHandlers(): void {\n    // Set up webhook event handlers\n  }\n\n  private async initializeConnection(data: any): Promise<void> {\n    // Initialize connection with Evolution API\n  }\n}\n```\n\n## Business API Service Pattern\n\n### Meta Business Service\n```typescript\nexport class BusinessStartupService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n    baileysCache: CacheService,\n    providerFiles: ProviderFiles,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache);\n  }\n\n  public async sendMessage(data: SendTextDto): Promise<any> {\n    const businessConfig = this.configService.get<WaBusiness>('WA_BUSINESS');\n    \n    const payload = {\n      messaging_product: 'whatsapp',\n      to: data.number,\n      type: 'text',\n      text: {\n        body: data.text,\n      },\n    };\n\n    try {\n      const response = await axios.post(\n        `${businessConfig.URL}/${businessConfig.VERSION}/${this.instance.businessId}/messages`,\n        payload,\n        {\n          headers: {\n            'Authorization': `Bearer ${this.instance.token}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      return response.data;\n    } catch (error) {\n      this.logger.error(`Business API call failed: ${error.message}`);\n      throw new BadRequestException('Failed to send message via Business API');\n    }\n  }\n\n  public async receiveWebhook(data: any): Promise<void> {\n    // Process incoming webhook from Meta Business API\n    const { entry } = data;\n    \n    for (const entryItem of entry) {\n      const { changes } = entryItem;\n      \n      for (const change of changes) {\n        if (change.field === 'messages') {\n          await this.processMessage(change.value);\n        }\n      }\n    }\n  }\n\n  private async processMessage(messageData: any): Promise<void> {\n    // Process incoming message from Business API\n    const { messages, contacts } = messageData;\n    \n    if (messages) {\n      for (const message of messages) {\n        await this.handleIncomingMessage(message, contacts);\n      }\n    }\n  }\n\n  private async handleIncomingMessage(message: any, contacts: any[]): Promise<void> {\n    // Handle individual message\n    const contact = contacts?.find(c => c.wa_id === message.from);\n    \n    // Emit event for message processing\n    this.eventEmitter.emit(Events.MESSAGES_UPSERT, {\n      instanceName: this.instance.name,\n      message,\n      contact,\n    });\n  }\n}\n```\n\n## Baileys Service Pattern\n\n### Baileys Integration Service\n```typescript\nexport class BaileysStartupService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n    baileysCache: CacheService,\n    providerFiles: ProviderFiles,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache);\n  }\n\n  public async connectToWhatsapp(): Promise<void> {\n    const authPath = path.join(INSTANCE_DIR, this.instance.name);\n    const { state, saveCreds } = await useMultiFileAuthState(authPath);\n\n    this.client = makeWASocket({\n      auth: state,\n      logger: P({ level: 'error' }),\n      printQRInTerminal: false,\n      browser: ['Evolution API', 'Chrome', '4.0.0'],\n      defaultQueryTimeoutMs: 60000,\n    });\n\n    this.setupEventHandlers();\n    this.client.ev.on('creds.update', saveCreds);\n  }\n\n  private setupEventHandlers(): void {\n    this.client.ev.on('connection.update', (update) => {\n      this.handleConnectionUpdate(update);\n    });\n\n    this.client.ev.on('messages.upsert', ({ messages, type }) => {\n      this.handleIncomingMessages(messages, type);\n    });\n\n    this.client.ev.on('messages.update', (updates) => {\n      this.handleMessageUpdates(updates);\n    });\n\n    this.client.ev.on('contacts.upsert', (contacts) => {\n      this.handleContactsUpdate(contacts);\n    });\n\n    this.client.ev.on('chats.upsert', (chats) => {\n      this.handleChatsUpdate(chats);\n    });\n  }\n\n  private async handleConnectionUpdate(update: ConnectionUpdate): Promise<void> {\n    const { connection, lastDisconnect, qr } = update;\n\n    if (qr) {\n      this.instance.qrcode = {\n        count: this.instance.qrcode?.count ? this.instance.qrcode.count + 1 : 1,\n        base64: qr,\n      };\n\n      this.eventEmitter.emit(Events.QRCODE_UPDATED, {\n        instanceName: this.instance.name,\n        qrcode: this.instance.qrcode,\n      });\n    }\n\n    if (connection === 'close') {\n      const shouldReconnect = (lastDisconnect?.error as Boom)?.output?.statusCode !== DisconnectReason.loggedOut;\n      \n      if (shouldReconnect) {\n        this.logger.log('Connection closed, reconnecting...');\n        await this.connectToWhatsapp();\n      } else {\n        this.logger.log('Connection closed, logged out');\n        this.eventEmitter.emit(Events.LOGOUT_INSTANCE, {\n          instanceName: this.instance.name,\n        });\n      }\n    }\n\n    if (connection === 'open') {\n      this.logger.log('Connection opened successfully');\n      this.instance.wuid = this.client.user?.id;\n      \n      this.eventEmitter.emit(Events.CONNECTION_UPDATE, {\n        instanceName: this.instance.name,\n        state: 'open',\n      });\n    }\n  }\n\n  public async sendMessage(data: SendTextDto): Promise<any> {\n    const jid = createJid(data.number);\n    \n    const message = {\n      text: data.text,\n    };\n\n    if (data.linkPreview !== undefined) {\n      message.linkPreview = data.linkPreview;\n    }\n\n    if (data.mentionsEveryOne) {\n      // Handle mentions\n    }\n\n    try {\n      const response = await this.client.sendMessage(jid, message);\n      return response;\n    } catch (error) {\n      this.logger.error(`Failed to send message: ${error.message}`);\n      throw new BadRequestException('Failed to send message');\n    }\n  }\n}\n```\n\n## Channel Router Pattern\n\n### Channel Router Structure\n```typescript\nexport class ChannelRouter {\n  public readonly router: Router;\n\n  constructor(configService: any, ...guards: any[]) {\n    this.router = Router();\n\n    this.router.use('/', new EvolutionRouter(configService).router);\n    this.router.use('/', new MetaRouter(configService).router);\n    this.router.use('/baileys', new BaileysRouter(...guards).router);\n  }\n}\n```\n\n### Specific Channel Router\n```typescript\nexport class EvolutionRouter extends RouterBroker {\n  constructor(private readonly configService: ConfigService) {\n    super();\n    this.router\n      .post(this.routerPath('webhook'), async (req, res) => {\n        const response = await evolutionController.receiveWebhook(req.body);\n        return res.status(HttpStatus.OK).json(response);\n      });\n  }\n\n  public readonly router: Router = Router();\n}\n```\n\n## Integration Types\n\n### Channel Data Types\n```typescript\ntype ChannelDataType = {\n  configService: ConfigService;\n  eventEmitter: EventEmitter2;\n  prismaRepository: PrismaRepository;\n  cache: CacheService;\n  chatwootCache: CacheService;\n  baileysCache: CacheService;\n  providerFiles: ProviderFiles;\n};\n\nexport enum Integration {\n  WHATSAPP_BUSINESS = 'WHATSAPP-BUSINESS',\n  WHATSAPP_BAILEYS = 'WHATSAPP-BAILEYS',\n  EVOLUTION = 'EVOLUTION',\n}\n```\n\n## Error Handling in Channels\n\n### Channel-Specific Error Handling\n```typescript\n// CORRECT - Channel-specific error handling\npublic async sendMessage(data: SendTextDto): Promise<any> {\n  try {\n    const response = await this.channelSpecificSend(data);\n    return response;\n  } catch (error) {\n    this.logger.error(`${this.constructor.name} send failed: ${error.message}`);\n    \n    if (error.response?.status === 401) {\n      throw new UnauthorizedException('Invalid token for channel');\n    }\n    \n    if (error.response?.status === 429) {\n      throw new BadRequestException('Rate limit exceeded');\n    }\n    \n    throw new InternalServerErrorException('Channel communication failed');\n  }\n}\n```\n\n## Channel Testing Pattern\n\n### Channel Service Testing\n```typescript\ndescribe('EvolutionStartupService', () => {\n  let service: EvolutionStartupService;\n  let configService: jest.Mocked<ConfigService>;\n  let eventEmitter: jest.Mocked<EventEmitter2>;\n\n  beforeEach(() => {\n    const mockConfig = {\n      get: jest.fn().mockReturnValue({\n        API_URL: 'https://api.evolution.com',\n      }),\n    };\n\n    service = new EvolutionStartupService(\n      mockConfig as any,\n      eventEmitter,\n      prismaRepository,\n      cache,\n      chatwootCache,\n    );\n  });\n\n  describe('sendMessage', () => {\n    it('should send message successfully', async () => {\n      const data = { number: '5511999999999', text: 'Test message' };\n      \n      // Mock axios response\n      jest.spyOn(axios, 'post').mockResolvedValue({\n        data: { success: true, messageId: '123' },\n      });\n\n      const result = await service.sendMessage(data);\n\n      expect(result.success).toBe(true);\n      expect(axios.post).toHaveBeenCalledWith(\n        expect.stringContaining('/send-message'),\n        data,\n        expect.objectContaining({\n          headers: expect.objectContaining({\n            'Authorization': expect.stringContaining('Bearer'),\n          }),\n        })\n      );\n    });\n  });\n});\n```\n\n<!-- File: .cursor/rules/specialized-rules/integration-chatbot-rules.mdc (Tokens: ~3798 | Category: cursor-rule) -->\n---\ndescription: Chatbot integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/chatbot/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Chatbot Integration Rules\n\n## Base Chatbot Pattern\n\n### Base Chatbot DTO\n```typescript\n/**\n * Base DTO for all chatbot integrations\n * Contains common properties shared by all chatbot types\n */\nexport class BaseChatbotDto {\n  enabled?: boolean;\n  description: string;\n  expire?: number;\n  keywordFinish?: string;\n  delayMessage?: number;\n  unknownMessage?: string;\n  listeningFromMe?: boolean;\n  stopBotFromMe?: boolean;\n  keepOpen?: boolean;\n  debounceTime?: number;\n  triggerType: TriggerType;\n  triggerOperator?: TriggerOperator;\n  triggerValue?: string;\n  ignoreJids?: string[];\n  splitMessages?: boolean;\n  timePerChar?: number;\n}\n```\n\n### Base Chatbot Controller\n```typescript\nexport abstract class BaseChatbotController<BotType = any, BotData extends BaseChatbotDto = BaseChatbotDto>\n  extends ChatbotController\n  implements ChatbotControllerInterface\n{\n  public readonly logger: Logger;\n  integrationEnabled: boolean;\n  botRepository: any;\n  settingsRepository: any;\n  sessionRepository: any;\n  userMessageDebounce: { [key: string]: { message: string; timeoutId: NodeJS.Timeout } } = {};\n\n  // Abstract methods to be implemented by specific chatbots\n  protected abstract readonly integrationName: string;\n  protected abstract processBot(\n    waInstance: any,\n    remoteJid: string,\n    bot: BotType,\n    session: any,\n    settings: ChatbotSettings,\n    content: string,\n    pushName?: string,\n    msg?: any,\n  ): Promise<void>;\n  protected abstract getFallbackBotId(settings: any): string | undefined;\n\n  constructor(prismaRepository: PrismaRepository, waMonitor: WAMonitoringService) {\n    super(prismaRepository, waMonitor);\n    this.sessionRepository = this.prismaRepository.integrationSession;\n  }\n\n  // Base implementation methods\n  public async createBot(instance: InstanceDto, data: BotData) {\n    if (!data.enabled) {\n      throw new BadRequestException(`${this.integrationName} is disabled`);\n    }\n\n    // Common bot creation logic\n    const bot = await this.botRepository.create({\n      data: {\n        ...data,\n        instanceId: instance.instanceId,\n      },\n    });\n\n    return bot;\n  }\n}\n```\n\n### Base Chatbot Service\n```typescript\n/**\n * Base class for all chatbot service implementations\n * Contains common methods shared across different chatbot integrations\n */\nexport abstract class BaseChatbotService<BotType = any, SettingsType = any> {\n  protected readonly logger: Logger;\n  protected readonly waMonitor: WAMonitoringService;\n  protected readonly prismaRepository: PrismaRepository;\n  protected readonly configService?: ConfigService;\n\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    loggerName: string,\n    configService?: ConfigService,\n  ) {\n    this.waMonitor = waMonitor;\n    this.prismaRepository = prismaRepository;\n    this.logger = new Logger(loggerName);\n    this.configService = configService;\n  }\n\n  /**\n   * Check if a message contains an image\n   */\n  protected isImageMessage(content: string): boolean {\n    return content.includes('imageMessage');\n  }\n\n  /**\n   * Extract text content from message\n   */\n  protected getMessageContent(msg: any): string {\n    return getConversationMessage(msg);\n  }\n\n  /**\n   * Send typing indicator\n   */\n  protected async sendTyping(instanceName: string, remoteJid: string): Promise<void> {\n    await this.waMonitor.waInstances[instanceName].sendPresence(remoteJid, 'composing');\n  }\n}\n```\n\n## Typebot Integration Pattern\n\n### Typebot Service\n```typescript\nexport class TypebotService extends BaseChatbotService<TypebotModel, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    configService: ConfigService,\n    prismaRepository: PrismaRepository,\n    private readonly openaiService: OpenaiService,\n  ) {\n    super(waMonitor, prismaRepository, 'TypebotService', configService);\n  }\n\n  public async sendTypebotMessage(\n    instanceName: string,\n    remoteJid: string,\n    typebot: TypebotModel,\n    content: string,\n  ): Promise<void> {\n    try {\n      const response = await axios.post(\n        `${typebot.url}/api/v1/typebots/${typebot.typebot}/startChat`,\n        {\n          message: content,\n          sessionId: `${instanceName}-${remoteJid}`,\n        },\n        {\n          headers: {\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      const { messages } = response.data;\n      \n      for (const message of messages) {\n        await this.processTypebotMessage(instanceName, remoteJid, message);\n      }\n    } catch (error) {\n      this.logger.error(`Typebot API error: ${error.message}`);\n      throw new InternalServerErrorException('Typebot communication failed');\n    }\n  }\n\n  private async processTypebotMessage(\n    instanceName: string,\n    remoteJid: string,\n    message: any,\n  ): Promise<void> {\n    const waInstance = this.waMonitor.waInstances[instanceName];\n    \n    if (message.type === 'text') {\n      await waInstance.sendMessage({\n        number: remoteJid.split('@')[0],\n        text: message.content.richText[0].children[0].text,\n      });\n    }\n    \n    if (message.type === 'image') {\n      await waInstance.sendMessage({\n        number: remoteJid.split('@')[0],\n        mediaMessage: {\n          mediatype: 'image',\n          media: message.content.url,\n        },\n      });\n    }\n  }\n}\n```\n\n## OpenAI Integration Pattern\n\n### OpenAI Service\n```typescript\nexport class OpenaiService extends BaseChatbotService<OpenaiModel, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(waMonitor, prismaRepository, 'OpenaiService', configService);\n  }\n\n  public async sendOpenaiMessage(\n    instanceName: string,\n    remoteJid: string,\n    openai: OpenaiModel,\n    content: string,\n    pushName?: string,\n  ): Promise<void> {\n    try {\n      const openaiConfig = this.configService.get<Openai>('OPENAI');\n      \n      const response = await axios.post(\n        'https://api.openai.com/v1/chat/completions',\n        {\n          model: openai.model || 'gpt-3.5-turbo',\n          messages: [\n            {\n              role: 'system',\n              content: openai.systemMessage || 'You are a helpful assistant.',\n            },\n            {\n              role: 'user',\n              content: content,\n            },\n          ],\n          max_tokens: openai.maxTokens || 1000,\n          temperature: openai.temperature || 0.7,\n        },\n        {\n          headers: {\n            'Authorization': `Bearer ${openai.apiKey || openaiConfig.API_KEY}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      const aiResponse = response.data.choices[0].message.content;\n      \n      await this.waMonitor.waInstances[instanceName].sendMessage({\n        number: remoteJid.split('@')[0],\n        text: aiResponse,\n      });\n    } catch (error) {\n      this.logger.error(`OpenAI API error: ${error.message}`);\n      \n      // Send fallback message\n      await this.waMonitor.waInstances[instanceName].sendMessage({\n        number: remoteJid.split('@')[0],\n        text: openai.unknownMessage || 'Desculpe, não consegui processar sua mensagem.',\n      });\n    }\n  }\n}\n```\n\n## Chatwoot Integration Pattern\n\n### Chatwoot Service\n```typescript\nexport class ChatwootService extends BaseChatbotService<any, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    configService: ConfigService,\n    prismaRepository: PrismaRepository,\n    private readonly chatwootCache: CacheService,\n  ) {\n    super(waMonitor, prismaRepository, 'ChatwootService', configService);\n  }\n\n  public async eventWhatsapp(\n    event: Events,\n    instanceName: { instanceName: string },\n    data: any,\n  ): Promise<void> {\n    const chatwootConfig = this.configService.get<Chatwoot>('CHATWOOT');\n    \n    if (!chatwootConfig.ENABLED) {\n      return;\n    }\n\n    try {\n      const instance = await this.prismaRepository.instance.findUnique({\n        where: { name: instanceName.instanceName },\n      });\n\n      if (!instance?.chatwootAccountId) {\n        return;\n      }\n\n      const webhook = {\n        event,\n        instance: instanceName.instanceName,\n        data,\n        timestamp: new Date().toISOString(),\n      };\n\n      await axios.post(\n        `${instance.chatwootUrl}/api/v1/accounts/${instance.chatwootAccountId}/webhooks`,\n        webhook,\n        {\n          headers: {\n            'Authorization': `Bearer ${instance.chatwootToken}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n    } catch (error) {\n      this.logger.error(`Chatwoot webhook error: ${error.message}`);\n    }\n  }\n\n  public async createConversation(\n    instanceName: string,\n    contact: any,\n    message: any,\n  ): Promise<void> {\n    // Create conversation in Chatwoot\n    const instance = await this.prismaRepository.instance.findUnique({\n      where: { name: instanceName },\n    });\n\n    if (!instance?.chatwootAccountId) {\n      return;\n    }\n\n    try {\n      const conversation = await axios.post(\n        `${instance.chatwootUrl}/api/v1/accounts/${instance.chatwootAccountId}/conversations`,\n        {\n          source_id: contact.id,\n          inbox_id: instance.chatwootInboxId,\n          contact_id: contact.chatwootContactId,\n        },\n        {\n          headers: {\n            'Authorization': `Bearer ${instance.chatwootToken}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      // Cache conversation\n      await this.chatwootCache.set(\n        `conversation:${instanceName}:${contact.id}`,\n        conversation.data,\n        3600\n      );\n    } catch (error) {\n      this.logger.error(`Chatwoot conversation creation error: ${error.message}`);\n    }\n  }\n}\n```\n\n## Dify Integration Pattern\n\n### Dify Service\n```typescript\nexport class DifyService extends BaseChatbotService<DifyModel, any> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    private readonly openaiService: OpenaiService,\n  ) {\n    super(waMonitor, prismaRepository, 'DifyService', configService);\n  }\n\n  public async sendDifyMessage(\n    instanceName: string,\n    remoteJid: string,\n    dify: DifyModel,\n    content: string,\n  ): Promise<void> {\n    try {\n      const response = await axios.post(\n        `${dify.apiUrl}/v1/chat-messages`,\n        {\n          inputs: {},\n          query: content,\n          user: remoteJid,\n          conversation_id: `${instanceName}-${remoteJid}`,\n          response_mode: 'blocking',\n        },\n        {\n          headers: {\n            'Authorization': `Bearer ${dify.apiKey}`,\n            'Content-Type': 'application/json',\n          },\n        }\n      );\n\n      const aiResponse = response.data.answer;\n      \n      await this.waMonitor.waInstances[instanceName].sendMessage({\n        number: remoteJid.split('@')[0],\n        text: aiResponse,\n      });\n    } catch (error) {\n      this.logger.error(`Dify API error: ${error.message}`);\n      \n      // Fallback to OpenAI if configured\n      if (dify.fallbackOpenai && this.openaiService) {\n        await this.openaiService.sendOpenaiMessage(instanceName, remoteJid, dify.openaiBot, content);\n      }\n    }\n  }\n}\n```\n\n## Chatbot Router Pattern\n\n### Chatbot Router Structure (Evolution API Real Pattern)\n```typescript\nexport class ChatbotRouter {\n  public readonly router: Router;\n\n  constructor(...guards: any[]) {\n    this.router = Router();\n\n    // Real Evolution API chatbot integrations\n    this.router.use('/evolutionBot', new EvolutionBotRouter(...guards).router);\n    this.router.use('/chatwoot', new ChatwootRouter(...guards).router);\n    this.router.use('/typebot', new TypebotRouter(...guards).router);\n    this.router.use('/openai', new OpenaiRouter(...guards).router);\n    this.router.use('/dify', new DifyRouter(...guards).router);\n    this.router.use('/flowise', new FlowiseRouter(...guards).router);\n    this.router.use('/n8n', new N8nRouter(...guards).router);\n    this.router.use('/evoai', new EvoaiRouter(...guards).router);\n  }\n}\n```\n\n## Chatbot Validation Patterns\n\n### Chatbot Schema Validation (Evolution API Pattern)\n```typescript\nimport { JSONSchema7 } from 'json-schema';\nimport { v4 } from 'uuid';\n\nconst isNotEmpty = (...fields: string[]) => {\n  const properties = {};\n  fields.forEach((field) => {\n    properties[field] = {\n      if: { properties: { [field]: { type: 'string' } } },\n      then: { properties: { [field]: { minLength: 1 } } },\n    };\n  });\n\n  return {\n    allOf: Object.values(properties),\n  };\n};\n\nexport const evolutionBotSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    enabled: { type: 'boolean' },\n    description: { type: 'string' },\n    apiUrl: { type: 'string' },\n    apiKey: { type: 'string' },\n    triggerType: { type: 'string', enum: ['all', 'keyword', 'none', 'advanced'] },\n    triggerOperator: { type: 'string', enum: ['equals', 'contains', 'startsWith', 'endsWith', 'regex'] },\n    triggerValue: { type: 'string' },\n    expire: { type: 'integer' },\n    keywordFinish: { type: 'string' },\n    delayMessage: { type: 'integer' },\n    unknownMessage: { type: 'string' },\n    listeningFromMe: { type: 'boolean' },\n    stopBotFromMe: { type: 'boolean' },\n    keepOpen: { type: 'boolean' },\n    debounceTime: { type: 'integer' },\n    ignoreJids: { type: 'array', items: { type: 'string' } },\n    splitMessages: { type: 'boolean' },\n    timePerChar: { type: 'integer' },\n  },\n  required: ['enabled', 'apiUrl', 'triggerType'],\n  ...isNotEmpty('enabled', 'apiUrl', 'triggerType'),\n};\n\nfunction validateKeywordTrigger(\n  content: string,\n  operator: TriggerOperator,\n  value: string,\n): boolean {\n  const normalizedContent = content.toLowerCase().trim();\n  const normalizedValue = value.toLowerCase().trim();\n\n  switch (operator) {\n    case TriggerOperator.EQUALS:\n      return normalizedContent === normalizedValue;\n    case TriggerOperator.CONTAINS:\n      return normalizedContent.includes(normalizedValue);\n    case TriggerOperator.STARTS_WITH:\n      return normalizedContent.startsWith(normalizedValue);\n    case TriggerOperator.ENDS_WITH:\n      return normalizedContent.endsWith(normalizedValue);\n    default:\n      return false;\n  }\n}\n```\n\n## Session Management Pattern\n\n### Chatbot Session Handling\n```typescript\nexport class ChatbotSessionManager {\n  constructor(\n    private readonly prismaRepository: PrismaRepository,\n    private readonly cache: CacheService,\n  ) {}\n\n  public async getSession(\n    instanceName: string,\n    remoteJid: string,\n    botId: string,\n  ): Promise<IntegrationSession | null> {\n    const cacheKey = `session:${instanceName}:${remoteJid}:${botId}`;\n    \n    // Try cache first\n    let session = await this.cache.get(cacheKey);\n    if (session) {\n      return session;\n    }\n\n    // Query database\n    session = await this.prismaRepository.integrationSession.findFirst({\n      where: {\n        instanceId: instanceName,\n        remoteJid,\n        botId,\n        status: 'opened',\n      },\n    });\n\n    // Cache result\n    if (session) {\n      await this.cache.set(cacheKey, session, 300); // 5 min TTL\n    }\n\n    return session;\n  }\n\n  public async createSession(\n    instanceName: string,\n    remoteJid: string,\n    botId: string,\n  ): Promise<IntegrationSession> {\n    const session = await this.prismaRepository.integrationSession.create({\n      data: {\n        instanceId: instanceName,\n        remoteJid,\n        botId,\n        status: 'opened',\n        createdAt: new Date(),\n      },\n    });\n\n    // Cache new session\n    const cacheKey = `session:${instanceName}:${remoteJid}:${botId}`;\n    await this.cache.set(cacheKey, session, 300);\n\n    return session;\n  }\n\n  public async closeSession(sessionId: string): Promise<void> {\n    await this.prismaRepository.integrationSession.update({\n      where: { id: sessionId },\n      data: { status: 'closed', updatedAt: new Date() },\n    });\n\n    // Invalidate cache\n    // Note: In a real implementation, you'd need to track cache keys by session ID\n  }\n}\n```\n\n<!-- File: .cursor/rules/specialized-rules/integration-event-rules.mdc (Tokens: ~5260 | Category: cursor-rule) -->\n---\ndescription: Event integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/event/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Event Integration Rules\n\n## Event Manager Pattern\n\n### Event Manager Structure\n```typescript\nimport { PrismaRepository } from '@api/repository/repository.service';\nimport { ConfigService } from '@config/env.config';\nimport { Logger } from '@config/logger.config';\nimport { Server } from 'http';\n\nexport class EventManager {\n  private prismaRepository: PrismaRepository;\n  private configService: ConfigService;\n  private logger = new Logger('EventManager');\n\n  // Event integrations\n  private webhook: WebhookController;\n  private websocket: WebsocketController;\n  private rabbitmq: RabbitmqController;\n  private nats: NatsController;\n  private sqs: SqsController;\n  private pusher: PusherController;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    server?: Server,\n  ) {\n    this.prismaRepository = prismaRepository;\n    this.configService = configService;\n\n    // Initialize event controllers\n    this.webhook = new WebhookController(prismaRepository, configService);\n    this.websocket = new WebsocketController(prismaRepository, configService, server);\n    this.rabbitmq = new RabbitmqController(prismaRepository, configService);\n    this.nats = new NatsController(prismaRepository, configService);\n    this.sqs = new SqsController(prismaRepository, configService);\n    this.pusher = new PusherController(prismaRepository, configService);\n  }\n\n  public async emit(eventData: {\n    instanceName: string;\n    origin: string;\n    event: string;\n    data: Object;\n    serverUrl: string;\n    dateTime: string;\n    sender: string;\n    apiKey?: string;\n    local?: boolean;\n    integration?: string[];\n  }): Promise<void> {\n    this.logger.log(`Emitting event ${eventData.event} for instance ${eventData.instanceName}`);\n\n    // Emit to all configured integrations\n    await Promise.allSettled([\n      this.webhook.emit(eventData),\n      this.websocket.emit(eventData),\n      this.rabbitmq.emit(eventData),\n      this.nats.emit(eventData),\n      this.sqs.emit(eventData),\n      this.pusher.emit(eventData),\n    ]);\n  }\n\n  public async setInstance(instanceName: string, data: any): Promise<any> {\n    const promises = [];\n\n    if (data.websocket) {\n      promises.push(\n        this.websocket.set(instanceName, {\n          websocket: {\n            enabled: true,\n            events: data.websocket?.events,\n          },\n        })\n      );\n    }\n\n    if (data.rabbitmq) {\n      promises.push(\n        this.rabbitmq.set(instanceName, {\n          rabbitmq: {\n            enabled: true,\n            events: data.rabbitmq?.events,\n          },\n        })\n      );\n    }\n\n    if (data.webhook) {\n      promises.push(\n        this.webhook.set(instanceName, {\n          webhook: {\n            enabled: true,\n            events: data.webhook?.events,\n            url: data.webhook?.url,\n            headers: data.webhook?.headers,\n            base64: data.webhook?.base64,\n            byEvents: data.webhook?.byEvents,\n          },\n        })\n      );\n    }\n\n    // Set other integrations...\n\n    await Promise.allSettled(promises);\n  }\n}\n```\n\n## Base Event Controller Pattern\n\n### Abstract Event Controller\n```typescript\nimport { PrismaRepository } from '@api/repository/repository.service';\nimport { ConfigService } from '@config/env.config';\nimport { Logger } from '@config/logger.config';\n\nexport type EmitData = {\n  instanceName: string;\n  origin: string;\n  event: string;\n  data: Object;\n  serverUrl: string;\n  dateTime: string;\n  sender: string;\n  apiKey?: string;\n  local?: boolean;\n  integration?: string[];\n};\n\nexport interface EventControllerInterface {\n  integrationEnabled: boolean;\n  emit(data: EmitData): Promise<void>;\n  set(instanceName: string, data: any): Promise<any>;\n}\n\nexport abstract class EventController implements EventControllerInterface {\n  protected readonly logger: Logger;\n  protected readonly prismaRepository: PrismaRepository;\n  protected readonly configService: ConfigService;\n\n  public integrationEnabled: boolean = false;\n\n  // Available events for all integrations\n  public static readonly events = [\n    'APPLICATION_STARTUP',\n    'INSTANCE_CREATE',\n    'INSTANCE_DELETE',\n    'QRCODE_UPDATED',\n    'CONNECTION_UPDATE',\n    'STATUS_INSTANCE',\n    'MESSAGES_SET',\n    'MESSAGES_UPSERT',\n    'MESSAGES_EDITED',\n    'MESSAGES_UPDATE',\n    'MESSAGES_DELETE',\n    'SEND_MESSAGE',\n    'CONTACTS_SET',\n    'CONTACTS_UPSERT',\n    'CONTACTS_UPDATE',\n    'PRESENCE_UPDATE',\n    'CHATS_SET',\n    'CHATS_UPDATE',\n    'CHATS_UPSERT',\n    'CHATS_DELETE',\n    'GROUPS_UPSERT',\n    'GROUPS_UPDATE',\n    'GROUP_PARTICIPANTS_UPDATE',\n    'CALL',\n    'TYPEBOT_START',\n    'TYPEBOT_CHANGE_STATUS',\n    'LABELS_EDIT',\n    'LABELS_ASSOCIATION',\n    'CREDS_UPDATE',\n    'MESSAGING_HISTORY_SET',\n    'REMOVE_INSTANCE',\n    'LOGOUT_INSTANCE',\n  ];\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    loggerName: string,\n  ) {\n    this.prismaRepository = prismaRepository;\n    this.configService = configService;\n    this.logger = new Logger(loggerName);\n  }\n\n  // Abstract methods to be implemented by specific integrations\n  public abstract emit(data: EmitData): Promise<void>;\n  public abstract set(instanceName: string, data: any): Promise<any>;\n\n  // Helper method to check if event should be processed\n  protected shouldProcessEvent(eventName: string, configuredEvents?: string[]): boolean {\n    if (!configuredEvents || configuredEvents.length === 0) {\n      return true; // Process all events if none specified\n    }\n    return configuredEvents.includes(eventName);\n  }\n\n  // Helper method to get instance configuration\n  protected async getInstanceConfig(instanceName: string): Promise<any> {\n    try {\n      const instance = await this.prismaRepository.instance.findUnique({\n        where: { name: instanceName },\n      });\n      return instance;\n    } catch (error) {\n      this.logger.error(`Failed to get instance config for ${instanceName}:`, error);\n      return null;\n    }\n  }\n}\n```\n\n## Webhook Integration Pattern\n\n### Webhook Controller Implementation\n```typescript\nexport class WebhookController extends EventController {\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(prismaRepository, configService, 'WebhookController');\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.webhook?.enabled) {\n        return;\n      }\n\n      const webhookConfig = instance.webhook;\n      \n      if (!this.shouldProcessEvent(data.event, webhookConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n        server: {\n          version: process.env.npm_package_version,\n          url: data.serverUrl,\n        },\n      };\n\n      // Encode data as base64 if configured\n      if (webhookConfig.base64) {\n        payload.data = Buffer.from(JSON.stringify(payload.data)).toString('base64');\n      }\n\n      const headers = {\n        'Content-Type': 'application/json',\n        'User-Agent': 'Evolution-API-Webhook',\n        ...webhookConfig.headers,\n      };\n\n      if (webhookConfig.byEvents) {\n        // Send to event-specific endpoint\n        const eventUrl = `${webhookConfig.url}/${data.event.toLowerCase()}`;\n        await this.sendWebhook(eventUrl, payload, headers);\n      } else {\n        // Send to main webhook URL\n        await this.sendWebhook(webhookConfig.url, payload, headers);\n      }\n\n      this.logger.log(`Webhook sent for event ${data.event} to instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`Webhook emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const webhookData = data.webhook;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          webhook: webhookData,\n        },\n      });\n\n      this.logger.log(`Webhook configuration set for instance ${instanceName}`);\n      return { webhook: webhookData };\n    } catch (error) {\n      this.logger.error(`Failed to set webhook config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n\n  private async sendWebhook(url: string, payload: any, headers: any): Promise<void> {\n    try {\n      const response = await axios.post(url, payload, {\n        headers,\n        timeout: 30000,\n        maxRedirects: 3,\n      });\n\n      if (response.status >= 200 && response.status < 300) {\n        this.logger.log(`Webhook delivered successfully to ${url}`);\n      } else {\n        this.logger.warn(`Webhook returned status ${response.status} for ${url}`);\n      }\n    } catch (error) {\n      this.logger.error(`Webhook delivery failed to ${url}:`, error.message);\n      \n      // Implement retry logic here if needed\n      if (error.response?.status >= 500) {\n        // Server error - might be worth retrying\n        this.logger.log(`Server error detected, webhook might be retried later`);\n      }\n    }\n  }\n}\n```\n\n## WebSocket Integration Pattern\n\n### WebSocket Controller Implementation\n```typescript\nimport { Server as SocketIOServer } from 'socket.io';\nimport { Server } from 'http';\n\nexport class WebsocketController extends EventController {\n  private io: SocketIOServer;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n    server?: Server,\n  ) {\n    super(prismaRepository, configService, 'WebsocketController');\n    \n    if (server) {\n      this.io = new SocketIOServer(server, {\n        cors: {\n          origin: \"*\",\n          methods: [\"GET\", \"POST\"],\n        },\n      });\n\n      this.setupSocketHandlers();\n    }\n  }\n\n  private setupSocketHandlers(): void {\n    this.io.on('connection', (socket) => {\n      this.logger.log(`WebSocket client connected: ${socket.id}`);\n\n      socket.on('join-instance', (instanceName: string) => {\n        socket.join(`instance:${instanceName}`);\n        this.logger.log(`Client ${socket.id} joined instance ${instanceName}`);\n      });\n\n      socket.on('leave-instance', (instanceName: string) => {\n        socket.leave(`instance:${instanceName}`);\n        this.logger.log(`Client ${socket.id} left instance ${instanceName}`);\n      });\n\n      socket.on('disconnect', () => {\n        this.logger.log(`WebSocket client disconnected: ${socket.id}`);\n      });\n    });\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    if (!this.io) {\n      return;\n    }\n\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.websocket?.enabled) {\n        return;\n      }\n\n      const websocketConfig = instance.websocket;\n      \n      if (!this.shouldProcessEvent(data.event, websocketConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n      };\n\n      // Emit to specific instance room\n      this.io.to(`instance:${data.instanceName}`).emit('evolution-event', payload);\n      \n      // Also emit to global room for monitoring\n      this.io.emit('global-event', payload);\n\n      this.logger.log(`WebSocket event ${data.event} emitted for instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`WebSocket emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const websocketData = data.websocket;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          websocket: websocketData,\n        },\n      });\n\n      this.logger.log(`WebSocket configuration set for instance ${instanceName}`);\n      return { websocket: websocketData };\n    } catch (error) {\n      this.logger.error(`Failed to set WebSocket config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n}\n```\n\n## Queue Integration Patterns\n\n### RabbitMQ Controller Implementation\n```typescript\nimport amqp from 'amqplib';\n\nexport class RabbitmqController extends EventController {\n  private connection: amqp.Connection | null = null;\n  private channel: amqp.Channel | null = null;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(prismaRepository, configService, 'RabbitmqController');\n    this.initializeConnection();\n  }\n\n  private async initializeConnection(): Promise<void> {\n    try {\n      const rabbitmqConfig = this.configService.get('RABBITMQ');\n      if (!rabbitmqConfig?.ENABLED) {\n        return;\n      }\n\n      this.connection = await amqp.connect(rabbitmqConfig.URI);\n      this.channel = await this.connection.createChannel();\n\n      // Declare exchange for Evolution API events\n      await this.channel.assertExchange('evolution-events', 'topic', { durable: true });\n\n      this.logger.log('RabbitMQ connection established');\n    } catch (error) {\n      this.logger.error('Failed to initialize RabbitMQ connection:', error);\n    }\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    if (!this.channel) {\n      return;\n    }\n\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.rabbitmq?.enabled) {\n        return;\n      }\n\n      const rabbitmqConfig = instance.rabbitmq;\n      \n      if (!this.shouldProcessEvent(data.event, rabbitmqConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n      };\n\n      const routingKey = `evolution.${data.instanceName}.${data.event.toLowerCase()}`;\n      \n      await this.channel.publish(\n        'evolution-events',\n        routingKey,\n        Buffer.from(JSON.stringify(payload)),\n        {\n          persistent: true,\n          timestamp: Date.now(),\n          messageId: `${data.instanceName}-${Date.now()}`,\n        }\n      );\n\n      this.logger.log(`RabbitMQ message published for event ${data.event} to instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`RabbitMQ emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const rabbitmqData = data.rabbitmq;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          rabbitmq: rabbitmqData,\n        },\n      });\n\n      this.logger.log(`RabbitMQ configuration set for instance ${instanceName}`);\n      return { rabbitmq: rabbitmqData };\n    } catch (error) {\n      this.logger.error(`Failed to set RabbitMQ config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n}\n```\n\n### SQS Controller Implementation\n```typescript\nimport { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';\n\nexport class SqsController extends EventController {\n  private sqsClient: SQSClient | null = null;\n\n  constructor(\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(prismaRepository, configService, 'SqsController');\n    this.initializeSQSClient();\n  }\n\n  private initializeSQSClient(): void {\n    try {\n      const sqsConfig = this.configService.get('SQS');\n      if (!sqsConfig?.ENABLED) {\n        return;\n      }\n\n      this.sqsClient = new SQSClient({\n        region: sqsConfig.REGION,\n        credentials: {\n          accessKeyId: sqsConfig.ACCESS_KEY_ID,\n          secretAccessKey: sqsConfig.SECRET_ACCESS_KEY,\n        },\n      });\n\n      this.logger.log('SQS client initialized');\n    } catch (error) {\n      this.logger.error('Failed to initialize SQS client:', error);\n    }\n  }\n\n  public async emit(data: EmitData): Promise<void> {\n    if (!this.sqsClient) {\n      return;\n    }\n\n    try {\n      const instance = await this.getInstanceConfig(data.instanceName);\n      if (!instance?.sqs?.enabled) {\n        return;\n      }\n\n      const sqsConfig = instance.sqs;\n      \n      if (!this.shouldProcessEvent(data.event, sqsConfig.events)) {\n        return;\n      }\n\n      const payload = {\n        event: data.event,\n        instance: data.instanceName,\n        data: data.data,\n        timestamp: data.dateTime,\n        sender: data.sender,\n      };\n\n      const command = new SendMessageCommand({\n        QueueUrl: sqsConfig.queueUrl,\n        MessageBody: JSON.stringify(payload),\n        MessageAttributes: {\n          event: {\n            DataType: 'String',\n            StringValue: data.event,\n          },\n          instance: {\n            DataType: 'String',\n            StringValue: data.instanceName,\n          },\n        },\n        MessageGroupId: data.instanceName, // For FIFO queues\n        MessageDeduplicationId: `${data.instanceName}-${Date.now()}`, // For FIFO queues\n      });\n\n      await this.sqsClient.send(command);\n\n      this.logger.log(`SQS message sent for event ${data.event} to instance ${data.instanceName}`);\n    } catch (error) {\n      this.logger.error(`SQS emission failed for ${data.instanceName}:`, error);\n    }\n  }\n\n  public async set(instanceName: string, data: any): Promise<any> {\n    try {\n      const sqsData = data.sqs;\n      \n      await this.prismaRepository.instance.update({\n        where: { name: instanceName },\n        data: {\n          sqs: sqsData,\n        },\n      });\n\n      this.logger.log(`SQS configuration set for instance ${instanceName}`);\n      return { sqs: sqsData };\n    } catch (error) {\n      this.logger.error(`Failed to set SQS config for ${instanceName}:`, error);\n      throw error;\n    }\n  }\n}\n```\n\n## Event DTO Pattern\n\n### Event Configuration DTO\n```typescript\nimport { JsonValue } from '@prisma/client/runtime/library';\n\nexport class EventDto {\n  webhook?: {\n    enabled?: boolean;\n    events?: string[];\n    url?: string;\n    headers?: JsonValue;\n    byEvents?: boolean;\n    base64?: boolean;\n  };\n\n  websocket?: {\n    enabled?: boolean;\n    events?: string[];\n  };\n\n  sqs?: {\n    enabled?: boolean;\n    events?: string[];\n    queueUrl?: string;\n  };\n\n  rabbitmq?: {\n    enabled?: boolean;\n    events?: string[];\n    exchange?: string;\n  };\n\n  nats?: {\n    enabled?: boolean;\n    events?: string[];\n    subject?: string;\n  };\n\n  pusher?: {\n    enabled?: boolean;\n    appId?: string;\n    key?: string;\n    secret?: string;\n    cluster?: string;\n    useTLS?: boolean;\n    events?: string[];\n  };\n}\n```\n\n## Event Router Pattern\n\n### Event Router Structure\n```typescript\nimport { NatsRouter } from '@api/integrations/event/nats/nats.router';\nimport { PusherRouter } from '@api/integrations/event/pusher/pusher.router';\nimport { RabbitmqRouter } from '@api/integrations/event/rabbitmq/rabbitmq.router';\nimport { SqsRouter } from '@api/integrations/event/sqs/sqs.router';\nimport { WebhookRouter } from '@api/integrations/event/webhook/webhook.router';\nimport { WebsocketRouter } from '@api/integrations/event/websocket/websocket.router';\nimport { Router } from 'express';\n\nexport class EventRouter {\n  public readonly router: Router;\n\n  constructor(configService: any, ...guards: any[]) {\n    this.router = Router();\n\n    this.router.use('/webhook', new WebhookRouter(configService, ...guards).router);\n    this.router.use('/websocket', new WebsocketRouter(...guards).router);\n    this.router.use('/rabbitmq', new RabbitmqRouter(...guards).router);\n    this.router.use('/nats', new NatsRouter(...guards).router);\n    this.router.use('/pusher', new PusherRouter(...guards).router);\n    this.router.use('/sqs', new SqsRouter(...guards).router);\n  }\n}\n```\n\n## Event Validation Schema\n\n### Event Configuration Validation\n```typescript\nimport Joi from 'joi';\nimport { EventController } from '@api/integrations/event/event.controller';\n\nconst eventListSchema = Joi.array().items(\n  Joi.string().valid(...EventController.events)\n).optional();\n\nexport const webhookSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  url: Joi.string().when('enabled', {\n    is: true,\n    then: Joi.required().uri({ scheme: ['http', 'https'] }),\n    otherwise: Joi.optional(),\n  }),\n  events: eventListSchema,\n  headers: Joi.object().pattern(Joi.string(), Joi.string()).optional(),\n  byEvents: Joi.boolean().optional().default(false),\n  base64: Joi.boolean().optional().default(false),\n}).required();\n\nexport const websocketSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  events: eventListSchema,\n}).required();\n\nexport const rabbitmqSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  events: eventListSchema,\n  exchange: Joi.string().optional().default('evolution-events'),\n}).required();\n\nexport const sqsSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  events: eventListSchema,\n  queueUrl: Joi.string().when('enabled', {\n    is: true,\n    then: Joi.required().uri(),\n    otherwise: Joi.optional(),\n  }),\n}).required();\n\nexport const eventSchema = Joi.object({\n  webhook: webhookSchema.optional(),\n  websocket: websocketSchema.optional(),\n  rabbitmq: rabbitmqSchema.optional(),\n  sqs: sqsSchema.optional(),\n  nats: Joi.object({\n    enabled: Joi.boolean().required(),\n    events: eventListSchema,\n    subject: Joi.string().optional().default('evolution.events'),\n  }).optional(),\n  pusher: Joi.object({\n    enabled: Joi.boolean().required(),\n    appId: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    key: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    secret: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    cluster: Joi.string().when('enabled', { is: true, then: Joi.required() }),\n    useTLS: Joi.boolean().optional().default(true),\n    events: eventListSchema,\n  }).optional(),\n}).min(1).required();\n```\n\n## Event Testing Pattern\n\n### Event Controller Testing\n```typescript\ndescribe('WebhookController', () => {\n  let controller: WebhookController;\n  let prismaRepository: jest.Mocked<PrismaRepository>;\n  let configService: jest.Mocked<ConfigService>;\n\n  beforeEach(() => {\n    controller = new WebhookController(prismaRepository, configService);\n  });\n\n  describe('emit', () => {\n    it('should send webhook when enabled', async () => {\n      const mockInstance = {\n        webhook: {\n          enabled: true,\n          url: 'https://example.com/webhook',\n          events: ['MESSAGES_UPSERT'],\n        },\n      };\n\n      prismaRepository.instance.findUnique.mockResolvedValue(mockInstance);\n      jest.spyOn(axios, 'post').mockResolvedValue({ status: 200 });\n\n      const eventData = {\n        instanceName: 'test-instance',\n        event: 'MESSAGES_UPSERT',\n        data: { message: 'test' },\n        origin: 'test',\n        serverUrl: 'http://localhost',\n        dateTime: new Date().toISOString(),\n        sender: 'test',\n      };\n\n      await controller.emit(eventData);\n\n      expect(axios.post).toHaveBeenCalledWith(\n        'https://example.com/webhook',\n        expect.objectContaining({\n          event: 'MESSAGES_UPSERT',\n          instance: 'test-instance',\n        }),\n        expect.objectContaining({\n          headers: expect.objectContaining({\n            'Content-Type': 'application/json',\n          }),\n        })\n      );\n    });\n  });\n});\n```\n\n<!-- File: .cursor/rules/specialized-rules/integration-storage-rules.mdc (Tokens: ~3931 | Category: cursor-rule) -->\n---\ndescription: Storage integration patterns for Evolution API\nglobs:\n  - \"src/api/integrations/storage/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Storage Integration Rules\n\n## Storage Service Pattern\n\n### Base Storage Service Structure\n```typescript\nimport { InstanceDto } from '@api/dto/instance.dto';\nimport { PrismaRepository } from '@api/repository/repository.service';\nimport { Logger } from '@config/logger.config';\nimport { BadRequestException } from '@exceptions';\n\nexport class StorageService {\n  constructor(private readonly prismaRepository: PrismaRepository) {}\n\n  private readonly logger = new Logger('StorageService');\n\n  public async getMedia(instance: InstanceDto, query?: MediaDto) {\n    try {\n      const where: any = {\n        instanceId: instance.instanceId,\n        ...query,\n      };\n\n      const media = await this.prismaRepository.media.findMany({\n        where,\n        select: {\n          id: true,\n          fileName: true,\n          type: true,\n          mimetype: true,\n          createdAt: true,\n          Message: true,\n        },\n      });\n\n      if (!media || media.length === 0) {\n        throw 'Media not found';\n      }\n\n      return media;\n    } catch (error) {\n      throw new BadRequestException(error);\n    }\n  }\n\n  public async getMediaUrl(instance: InstanceDto, data: MediaDto) {\n    const media = (await this.getMedia(instance, { id: data.id }))[0];\n    const mediaUrl = await this.generateUrl(media.fileName, data.expiry);\n    return {\n      mediaUrl,\n      ...media,\n    };\n  }\n\n  protected abstract generateUrl(fileName: string, expiry?: number): Promise<string>;\n}\n```\n\n## S3/MinIO Integration Pattern\n\n### MinIO Client Setup\n```typescript\nimport { ConfigService, S3 } from '@config/env.config';\nimport { Logger } from '@config/logger.config';\nimport { BadRequestException } from '@exceptions';\nimport * as MinIo from 'minio';\nimport { join } from 'path';\nimport { Readable, Transform } from 'stream';\n\nconst logger = new Logger('S3 Service');\nconst BUCKET = new ConfigService().get<S3>('S3');\n\ninterface Metadata extends MinIo.ItemBucketMetadata {\n  instanceId: string;\n  messageId?: string;\n}\n\nconst minioClient = (() => {\n  if (BUCKET?.ENABLE) {\n    return new MinIo.Client({\n      endPoint: BUCKET.ENDPOINT,\n      port: BUCKET.PORT,\n      useSSL: BUCKET.USE_SSL,\n      accessKey: BUCKET.ACCESS_KEY,\n      secretKey: BUCKET.SECRET_KEY,\n      region: BUCKET.REGION,\n    });\n  }\n})();\n\nconst bucketName = process.env.S3_BUCKET;\n```\n\n### Bucket Management Functions\n```typescript\nconst bucketExists = async (): Promise<boolean> => {\n  if (minioClient) {\n    try {\n      const list = await minioClient.listBuckets();\n      return !!list.find((bucket) => bucket.name === bucketName);\n    } catch (error) {\n      logger.error('Error checking bucket existence:', error);\n      return false;\n    }\n  }\n  return false;\n};\n\nconst setBucketPolicy = async (): Promise<void> => {\n  if (minioClient && bucketName) {\n    try {\n      const policy = {\n        Version: '2012-10-17',\n        Statement: [\n          {\n            Effect: 'Allow',\n            Principal: { AWS: ['*'] },\n            Action: ['s3:GetObject'],\n            Resource: [`arn:aws:s3:::${bucketName}/*`],\n          },\n        ],\n      };\n\n      await minioClient.setBucketPolicy(bucketName, JSON.stringify(policy));\n      logger.log('Bucket policy set successfully');\n    } catch (error) {\n      logger.error('Error setting bucket policy:', error);\n    }\n  }\n};\n\nconst createBucket = async (): Promise<void> => {\n  if (minioClient && bucketName) {\n    try {\n      const exists = await bucketExists();\n      if (!exists) {\n        await minioClient.makeBucket(bucketName, BUCKET.REGION || 'us-east-1');\n        await setBucketPolicy();\n        logger.log(`Bucket ${bucketName} created successfully`);\n      }\n    } catch (error) {\n      logger.error('Error creating bucket:', error);\n    }\n  }\n};\n```\n\n### File Upload Functions\n```typescript\nexport const uploadFile = async (\n  fileName: string,\n  buffer: Buffer,\n  mimetype: string,\n  metadata?: Metadata,\n): Promise<string> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    await createBucket();\n\n    const uploadMetadata = {\n      'Content-Type': mimetype,\n      ...metadata,\n    };\n\n    await minioClient.putObject(bucketName, fileName, buffer, buffer.length, uploadMetadata);\n    \n    logger.log(`File ${fileName} uploaded successfully`);\n    return fileName;\n  } catch (error) {\n    logger.error(`Error uploading file ${fileName}:`, error);\n    throw new BadRequestException(`Failed to upload file: ${error.message}`);\n  }\n};\n\nexport const uploadStream = async (\n  fileName: string,\n  stream: Readable,\n  size: number,\n  mimetype: string,\n  metadata?: Metadata,\n): Promise<string> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    await createBucket();\n\n    const uploadMetadata = {\n      'Content-Type': mimetype,\n      ...metadata,\n    };\n\n    await minioClient.putObject(bucketName, fileName, stream, size, uploadMetadata);\n    \n    logger.log(`Stream ${fileName} uploaded successfully`);\n    return fileName;\n  } catch (error) {\n    logger.error(`Error uploading stream ${fileName}:`, error);\n    throw new BadRequestException(`Failed to upload stream: ${error.message}`);\n  }\n};\n```\n\n### File Download Functions\n```typescript\nexport const getObject = async (fileName: string): Promise<Buffer> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const stream = await minioClient.getObject(bucketName, fileName);\n    const chunks: Buffer[] = [];\n\n    return new Promise((resolve, reject) => {\n      stream.on('data', (chunk) => chunks.push(chunk));\n      stream.on('end', () => resolve(Buffer.concat(chunks)));\n      stream.on('error', reject);\n    });\n  } catch (error) {\n    logger.error(`Error getting object ${fileName}:`, error);\n    throw new BadRequestException(`Failed to get object: ${error.message}`);\n  }\n};\n\nexport const getObjectUrl = async (fileName: string, expiry: number = 3600): Promise<string> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const url = await minioClient.presignedGetObject(bucketName, fileName, expiry);\n    logger.log(`Generated URL for ${fileName} with expiry ${expiry}s`);\n    return url;\n  } catch (error) {\n    logger.error(`Error generating URL for ${fileName}:`, error);\n    throw new BadRequestException(`Failed to generate URL: ${error.message}`);\n  }\n};\n\nexport const getObjectStream = async (fileName: string): Promise<Readable> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const stream = await minioClient.getObject(bucketName, fileName);\n    return stream;\n  } catch (error) {\n    logger.error(`Error getting object stream ${fileName}:`, error);\n    throw new BadRequestException(`Failed to get object stream: ${error.message}`);\n  }\n};\n```\n\n### File Management Functions\n```typescript\nexport const deleteObject = async (fileName: string): Promise<void> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    await minioClient.removeObject(bucketName, fileName);\n    logger.log(`File ${fileName} deleted successfully`);\n  } catch (error) {\n    logger.error(`Error deleting file ${fileName}:`, error);\n    throw new BadRequestException(`Failed to delete file: ${error.message}`);\n  }\n};\n\nexport const listObjects = async (prefix?: string): Promise<MinIo.BucketItem[]> => {\n  if (!minioClient || !bucketName) {\n    throw new BadRequestException('S3 storage not configured');\n  }\n\n  try {\n    const objects: MinIo.BucketItem[] = [];\n    const stream = minioClient.listObjects(bucketName, prefix, true);\n\n    return new Promise((resolve, reject) => {\n      stream.on('data', (obj) => objects.push(obj));\n      stream.on('end', () => resolve(objects));\n      stream.on('error', reject);\n    });\n  } catch (error) {\n    logger.error('Error listing objects:', error);\n    throw new BadRequestException(`Failed to list objects: ${error.message}`);\n  }\n};\n\nexport const objectExists = async (fileName: string): Promise<boolean> => {\n  if (!minioClient || !bucketName) {\n    return false;\n  }\n\n  try {\n    await minioClient.statObject(bucketName, fileName);\n    return true;\n  } catch (error) {\n    return false;\n  }\n};\n```\n\n## Storage Controller Pattern\n\n### S3 Controller Implementation\n```typescript\nimport { InstanceDto } from '@api/dto/instance.dto';\nimport { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';\nimport { S3Service } from '@api/integrations/storage/s3/services/s3.service';\n\nexport class S3Controller {\n  constructor(private readonly s3Service: S3Service) {}\n\n  public async getMedia(instance: InstanceDto, data: MediaDto) {\n    return this.s3Service.getMedia(instance, data);\n  }\n\n  public async getMediaUrl(instance: InstanceDto, data: MediaDto) {\n    return this.s3Service.getMediaUrl(instance, data);\n  }\n\n  public async uploadMedia(instance: InstanceDto, data: UploadMediaDto) {\n    return this.s3Service.uploadMedia(instance, data);\n  }\n\n  public async deleteMedia(instance: InstanceDto, data: MediaDto) {\n    return this.s3Service.deleteMedia(instance, data);\n  }\n}\n```\n\n## Storage Router Pattern\n\n### Storage Router Structure\n```typescript\nimport { S3Router } from '@api/integrations/storage/s3/routes/s3.router';\nimport { Router } from 'express';\n\nexport class StorageRouter {\n  public readonly router: Router;\n\n  constructor(...guards: any[]) {\n    this.router = Router();\n\n    this.router.use('/s3', new S3Router(...guards).router);\n    // Add other storage providers here\n    // this.router.use('/gcs', new GCSRouter(...guards).router);\n    // this.router.use('/azure', new AzureRouter(...guards).router);\n  }\n}\n```\n\n### S3 Specific Router\n```typescript\nimport { RouterBroker } from '@api/abstract/abstract.router';\nimport { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';\nimport { s3Schema, s3UrlSchema } from '@api/integrations/storage/s3/validate/s3.schema';\nimport { HttpStatus } from '@api/routes/index.router';\nimport { s3Controller } from '@api/server.module';\nimport { RequestHandler, Router } from 'express';\n\nexport class S3Router extends RouterBroker {\n  constructor(...guards: RequestHandler[]) {\n    super();\n    this.router\n      .post(this.routerPath('getMedia'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<MediaDto>({\n          request: req,\n          schema: s3Schema,\n          ClassRef: MediaDto,\n          execute: (instance, data) => s3Controller.getMedia(instance, data),\n        });\n\n        res.status(HttpStatus.OK).json(response);\n      })\n      .post(this.routerPath('getMediaUrl'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<MediaDto>({\n          request: req,\n          schema: s3UrlSchema,\n          ClassRef: MediaDto,\n          execute: (instance, data) => s3Controller.getMediaUrl(instance, data),\n        });\n\n        res.status(HttpStatus.OK).json(response);\n      })\n      .post(this.routerPath('uploadMedia'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<UploadMediaDto>({\n          request: req,\n          schema: uploadSchema,\n          ClassRef: UploadMediaDto,\n          execute: (instance, data) => s3Controller.uploadMedia(instance, data),\n        });\n\n        res.status(HttpStatus.CREATED).json(response);\n      })\n      .delete(this.routerPath('deleteMedia'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<MediaDto>({\n          request: req,\n          schema: s3Schema,\n          ClassRef: MediaDto,\n          execute: (instance, data) => s3Controller.deleteMedia(instance, data),\n        });\n\n        res.status(HttpStatus.OK).json(response);\n      });\n  }\n\n  public readonly router: Router = Router();\n}\n```\n\n## Storage DTO Pattern\n\n### Media DTO\n```typescript\nexport class MediaDto {\n  id?: string;\n  fileName?: string;\n  type?: string;\n  mimetype?: string;\n  expiry?: number;\n}\n\nexport class UploadMediaDto {\n  fileName: string;\n  mimetype: string;\n  buffer?: Buffer;\n  base64?: string;\n  url?: string;\n  metadata?: {\n    instanceId: string;\n    messageId?: string;\n    contactId?: string;\n    [key: string]: any;\n  };\n}\n```\n\n## Storage Validation Schema\n\n### S3 Validation Schemas\n```typescript\nimport Joi from 'joi';\n\nexport const s3Schema = Joi.object({\n  id: Joi.string().optional(),\n  fileName: Joi.string().optional(),\n  type: Joi.string().optional().valid('image', 'video', 'audio', 'document'),\n  mimetype: Joi.string().optional(),\n  expiry: Joi.number().optional().min(60).max(604800).default(3600), // 1 min to 7 days\n}).min(1).required();\n\nexport const s3UrlSchema = Joi.object({\n  id: Joi.string().required(),\n  expiry: Joi.number().optional().min(60).max(604800).default(3600),\n}).required();\n\nexport const uploadSchema = Joi.object({\n  fileName: Joi.string().required().max(255),\n  mimetype: Joi.string().required(),\n  buffer: Joi.binary().optional(),\n  base64: Joi.string().base64().optional(),\n  url: Joi.string().uri().optional(),\n  metadata: Joi.object({\n    instanceId: Joi.string().required(),\n    messageId: Joi.string().optional(),\n    contactId: Joi.string().optional(),\n  }).optional(),\n}).xor('buffer', 'base64', 'url').required(); // Exactly one of these must be present\n```\n\n## Error Handling in Storage\n\n### Storage-Specific Error Handling\n```typescript\n// CORRECT - Storage-specific error handling\npublic async uploadFile(fileName: string, buffer: Buffer): Promise<string> {\n  try {\n    const result = await this.storageClient.upload(fileName, buffer);\n    return result;\n  } catch (error) {\n    this.logger.error(`Storage upload failed: ${error.message}`);\n    \n    if (error.code === 'NoSuchBucket') {\n      throw new BadRequestException('Storage bucket not found');\n    }\n    \n    if (error.code === 'AccessDenied') {\n      throw new UnauthorizedException('Storage access denied');\n    }\n    \n    if (error.code === 'EntityTooLarge') {\n      throw new BadRequestException('File too large');\n    }\n    \n    throw new InternalServerErrorException('Storage operation failed');\n  }\n}\n```\n\n## Storage Configuration Pattern\n\n### Environment Configuration\n```typescript\nexport interface S3Config {\n  ENABLE: boolean;\n  ENDPOINT: string;\n  PORT: number;\n  USE_SSL: boolean;\n  ACCESS_KEY: string;\n  SECRET_KEY: string;\n  REGION: string;\n  BUCKET: string;\n}\n\n// Usage in service\nconst s3Config = this.configService.get<S3Config>('S3');\nif (!s3Config.ENABLE) {\n  throw new BadRequestException('S3 storage is disabled');\n}\n```\n\n## Storage Testing Pattern\n\n### Storage Service Testing\n```typescript\ndescribe('S3Service', () => {\n  let service: S3Service;\n  let prismaRepository: jest.Mocked<PrismaRepository>;\n\n  beforeEach(() => {\n    service = new S3Service(prismaRepository);\n  });\n\n  describe('getMedia', () => {\n    it('should return media list', async () => {\n      const instance = { instanceId: 'test-instance' };\n      const mockMedia = [\n        { id: '1', fileName: 'test.jpg', type: 'image', mimetype: 'image/jpeg' },\n      ];\n\n      prismaRepository.media.findMany.mockResolvedValue(mockMedia);\n\n      const result = await service.getMedia(instance);\n\n      expect(result).toEqual(mockMedia);\n      expect(prismaRepository.media.findMany).toHaveBeenCalledWith({\n        where: { instanceId: 'test-instance' },\n        select: expect.objectContaining({\n          id: true,\n          fileName: true,\n          type: true,\n          mimetype: true,\n        }),\n      });\n    });\n\n    it('should throw error when no media found', async () => {\n      const instance = { instanceId: 'test-instance' };\n      prismaRepository.media.findMany.mockResolvedValue([]);\n\n      await expect(service.getMedia(instance)).rejects.toThrow(BadRequestException);\n    });\n  });\n});\n```\n\n## Storage Performance Considerations\n\n### Efficient File Handling\n```typescript\n// CORRECT - Stream-based upload for large files\npublic async uploadLargeFile(fileName: string, stream: Readable, size: number): Promise<string> {\n  const uploadStream = new Transform({\n    transform(chunk, encoding, callback) {\n      // Optional: Add compression, encryption, etc.\n      callback(null, chunk);\n    },\n  });\n\n  return new Promise((resolve, reject) => {\n    stream\n      .pipe(uploadStream)\n      .on('error', reject)\n      .on('finish', () => resolve(fileName));\n  });\n}\n\n// INCORRECT - Loading entire file into memory\npublic async uploadLargeFile(fileName: string, filePath: string): Promise<string> {\n  const buffer = fs.readFileSync(filePath); // ❌ Memory intensive for large files\n  return await this.uploadFile(fileName, buffer);\n}\n```\n\n<!-- File: .cursor/rules/specialized-rules/route-rules.mdc (Tokens: ~2711 | Category: cursor-rule) -->\n---\ndescription: Router patterns for Evolution API\nglobs:\n  - \"src/api/routes/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Route Rules\n\n## Router Base Pattern\n\n### RouterBroker Extension\n```typescript\nimport { RouterBroker } from '@api/abstract/abstract.router';\nimport { RequestHandler, Router } from 'express';\nimport { HttpStatus } from './index.router';\n\nexport class ExampleRouter extends RouterBroker {\n  constructor(...guards: RequestHandler[]) {\n    super();\n    this.router\n      .get(this.routerPath('findExample'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<ExampleDto>({\n          request: req,\n          schema: null,\n          ClassRef: ExampleDto,\n          execute: (instance) => exampleController.find(instance),\n        });\n\n        return res.status(HttpStatus.OK).json(response);\n      })\n      .post(this.routerPath('createExample'), ...guards, async (req, res) => {\n        const response = await this.dataValidate<ExampleDto>({\n          request: req,\n          schema: exampleSchema,\n          ClassRef: ExampleDto,\n          execute: (instance, data) => exampleController.create(instance, data),\n        });\n\n        return res.status(HttpStatus.CREATED).json(response);\n      });\n  }\n\n  public readonly router: Router = Router();\n}\n```\n\n## Main Router Pattern\n\n### Index Router Structure\n```typescript\nimport { Router } from 'express';\nimport { authGuard } from '@api/guards/auth.guard';\nimport { instanceExistsGuard, instanceLoggedGuard } from '@api/guards/instance.guard';\nimport Telemetry from '@api/guards/telemetry.guard';\n\nenum HttpStatus {\n  OK = 200,\n  CREATED = 201,\n  NOT_FOUND = 404,\n  FORBIDDEN = 403,\n  BAD_REQUEST = 400,\n  UNAUTHORIZED = 401,\n  INTERNAL_SERVER_ERROR = 500,\n}\n\nconst router: Router = Router();\nconst guards = [instanceExistsGuard, instanceLoggedGuard, authGuard['apikey']];\nconst telemetry = new Telemetry();\n\nrouter\n  .use((req, res, next) => telemetry.collectTelemetry(req, res, next))\n  .get('/', async (req, res) => {\n    res.status(HttpStatus.OK).json({\n      status: HttpStatus.OK,\n      message: 'Welcome to the Evolution API, it is working!',\n      version: packageJson.version,\n      clientName: process.env.DATABASE_CONNECTION_CLIENT_NAME,\n      manager: !serverConfig.DISABLE_MANAGER ? `${req.protocol}://${req.get('host')}/manager` : undefined,\n      documentation: `https://doc.evolution-api.com`,\n      whatsappWebVersion: (await fetchLatestWaWebVersion({})).version.join('.'),\n    });\n  })\n  .use('/instance', new InstanceRouter(configService, ...guards).router)\n  .use('/message', new MessageRouter(...guards).router)\n  .use('/chat', new ChatRouter(...guards).router)\n  .use('/business', new BusinessRouter(...guards).router);\n\nexport { HttpStatus, router };\n```\n\n## Data Validation Pattern\n\n### RouterBroker dataValidate Usage\n```typescript\n// CORRECT - Standard validation pattern\n.post(this.routerPath('createTemplate'), ...guards, async (req, res) => {\n  const response = await this.dataValidate<TemplateDto>({\n    request: req,\n    schema: templateSchema,\n    ClassRef: TemplateDto,\n    execute: (instance, data) => templateController.create(instance, data),\n  });\n\n  return res.status(HttpStatus.CREATED).json(response);\n})\n\n// CORRECT - No schema validation (for simple DTOs)\n.get(this.routerPath('findTemplate'), ...guards, async (req, res) => {\n  const response = await this.dataValidate<InstanceDto>({\n    request: req,\n    schema: null,\n    ClassRef: InstanceDto,\n    execute: (instance) => templateController.find(instance),\n  });\n\n  return res.status(HttpStatus.OK).json(response);\n})\n```\n\n## Error Handling in Routes\n\n### Try-Catch Pattern\n```typescript\n// CORRECT - Error handling with utility function\n.post(this.routerPath('getCatalog'), ...guards, async (req, res) => {\n  try {\n    const response = await this.dataValidate<NumberDto>({\n      request: req,\n      schema: catalogSchema,\n      ClassRef: NumberDto,\n      execute: (instance, data) => businessController.fetchCatalog(instance, data),\n    });\n\n    return res.status(HttpStatus.OK).json(response);\n  } catch (error) {\n    // Log error for debugging\n    console.error('Business catalog error:', error);\n\n    // Use utility function to create standardized error response\n    const errorResponse = createMetaErrorResponse(error, 'business_catalog');\n    return res.status(errorResponse.status).json(errorResponse);\n  }\n})\n\n// INCORRECT - Let RouterBroker handle errors (when possible)\n.post(this.routerPath('simpleOperation'), ...guards, async (req, res) => {\n  try {\n    const response = await this.dataValidate<SimpleDto>({\n      request: req,\n      schema: simpleSchema,\n      ClassRef: SimpleDto,\n      execute: (instance, data) => controller.simpleOperation(instance, data),\n    });\n\n    return res.status(HttpStatus.OK).json(response);\n  } catch (error) {\n    throw error; // ❌ Unnecessary - RouterBroker handles this\n  }\n})\n```\n\n## Route Path Pattern\n\n### routerPath Usage\n```typescript\n// CORRECT - Use routerPath for consistent naming\n.get(this.routerPath('findLabels'), ...guards, async (req, res) => {\n  // Implementation\n})\n.post(this.routerPath('handleLabel'), ...guards, async (req, res) => {\n  // Implementation\n})\n\n// INCORRECT - Hardcoded paths\n.get('/labels', ...guards, async (req, res) => { // ❌ Use routerPath\n  // Implementation\n})\n```\n\n## Guard Application Pattern\n\n### Guards Usage\n```typescript\n// CORRECT - Apply guards to protected routes\nexport class ProtectedRouter extends RouterBroker {\n  constructor(...guards: RequestHandler[]) {\n    super();\n    this.router\n      .get(this.routerPath('protectedAction'), ...guards, async (req, res) => {\n        // Protected action\n      })\n      .post(this.routerPath('anotherAction'), ...guards, async (req, res) => {\n        // Another protected action\n      });\n  }\n}\n\n// CORRECT - No guards for public routes\nexport class PublicRouter extends RouterBroker {\n  constructor() {\n    super();\n    this.router\n      .get('/health', async (req, res) => {\n        res.status(HttpStatus.OK).json({ status: 'healthy' });\n      })\n      .get('/version', async (req, res) => {\n        res.status(HttpStatus.OK).json({ version: packageJson.version });\n      });\n  }\n}\n```\n\n## Static File Serving Pattern\n\n### Static Assets Route\n```typescript\n// CORRECT - Secure static file serving\nrouter.get('/assets/*', (req, res) => {\n  const fileName = req.params[0];\n  \n  // Security: Reject paths containing traversal patterns\n  if (!fileName || fileName.includes('..') || fileName.includes('\\\\') || path.isAbsolute(fileName)) {\n    return res.status(403).send('Forbidden');\n  }\n  \n  const basePath = path.join(process.cwd(), 'manager', 'dist');\n  const assetsPath = path.join(basePath, 'assets');\n  const filePath = path.join(assetsPath, fileName);\n  \n  // Security: Ensure the resolved path is within the assets directory\n  const resolvedPath = path.resolve(filePath);\n  const resolvedAssetsPath = path.resolve(assetsPath);\n  \n  if (!resolvedPath.startsWith(resolvedAssetsPath + path.sep) && resolvedPath !== resolvedAssetsPath) {\n    return res.status(403).send('Forbidden');\n  }\n\n  if (fs.existsSync(resolvedPath)) {\n    res.set('Content-Type', mimeTypes.lookup(resolvedPath) || 'text/css');\n    res.send(fs.readFileSync(resolvedPath));\n  } else {\n    res.status(404).send('File not found');\n  }\n});\n```\n\n## Special Route Patterns\n\n### Manager Route Pattern\n```typescript\nexport class ViewsRouter extends RouterBroker {\n  public readonly router: Router;\n\n  constructor() {\n    super();\n    this.router = Router();\n\n    const basePath = path.join(process.cwd(), 'manager', 'dist');\n    const indexPath = path.join(basePath, 'index.html');\n\n    this.router.use(express.static(basePath));\n\n    this.router.get('*', (req, res) => {\n      res.sendFile(indexPath);\n    });\n  }\n}\n```\n\n### Webhook Route Pattern\n```typescript\n// CORRECT - Webhook without guards\n.post('/webhook/evolution', async (req, res) => {\n  const response = await evolutionController.receiveWebhook(req.body);\n  return res.status(HttpStatus.OK).json(response);\n})\n\n// CORRECT - Webhook with signature validation\n.post('/webhook/meta', validateWebhookSignature, async (req, res) => {\n  const response = await metaController.receiveWebhook(req.body);\n  return res.status(HttpStatus.OK).json(response);\n})\n```\n\n## Response Pattern\n\n### Standard Response Format\n```typescript\n// CORRECT - Standard success response\nreturn res.status(HttpStatus.OK).json(response);\n\n// CORRECT - Created response\nreturn res.status(HttpStatus.CREATED).json(response);\n\n// CORRECT - Custom response with additional data\nreturn res.status(HttpStatus.OK).json({\n  ...response,\n  timestamp: new Date().toISOString(),\n  instanceName: req.params.instanceName,\n});\n```\n\n## Route Organization\n\n### File Structure\n```\nsrc/api/routes/\n├── index.router.ts          # Main router with all route registrations\n├── instance.router.ts       # Instance management routes\n├── sendMessage.router.ts    # Message sending routes\n├── chat.router.ts          # Chat operations routes\n├── business.router.ts      # Business API routes\n├── group.router.ts         # Group management routes\n├── label.router.ts         # Label management routes\n├── proxy.router.ts         # Proxy configuration routes\n├── settings.router.ts      # Instance settings routes\n├── template.router.ts      # Template management routes\n├── call.router.ts          # Call operations routes\n└── view.router.ts          # Frontend views routes\n```\n\n## Route Testing Pattern\n\n### Router Testing\n```typescript\ndescribe('ExampleRouter', () => {\n  let app: express.Application;\n  let router: ExampleRouter;\n\n  beforeEach(() => {\n    app = express();\n    router = new ExampleRouter();\n    app.use('/api', router.router);\n    app.use(express.json());\n  });\n\n  describe('GET /findExample', () => {\n    it('should return example data', async () => {\n      const response = await request(app)\n        .get('/api/findExample/test-instance')\n        .set('apikey', 'test-key')\n        .expect(200);\n\n      expect(response.body).toBeDefined();\n      expect(response.body.instanceName).toBe('test-instance');\n    });\n\n    it('should return 401 without API key', async () => {\n      await request(app)\n        .get('/api/findExample/test-instance')\n        .expect(401);\n    });\n  });\n\n  describe('POST /createExample', () => {\n    it('should create example successfully', async () => {\n      const data = {\n        name: 'Test Example',\n        description: 'Test Description',\n      };\n\n      const response = await request(app)\n        .post('/api/createExample/test-instance')\n        .set('apikey', 'test-key')\n        .send(data)\n        .expect(201);\n\n      expect(response.body.name).toBe(data.name);\n    });\n\n    it('should validate required fields', async () => {\n      const data = {\n        description: 'Test Description',\n        // Missing required 'name' field\n      };\n\n      await request(app)\n        .post('/api/createExample/test-instance')\n        .set('apikey', 'test-key')\n        .send(data)\n        .expect(400);\n    });\n  });\n});\n```\n\n## Route Documentation\n\n### JSDoc for Routes\n```typescript\n/**\n * @route GET /api/template/findTemplate/:instanceName\n * @description Find template for instance\n * @param {string} instanceName - Instance name\n * @returns {TemplateDto} Template data\n * @throws {404} Template not found\n * @throws {401} Unauthorized\n */\n.get(this.routerPath('findTemplate'), ...guards, async (req, res) => {\n  // Implementation\n})\n\n/**\n * @route POST /api/template/createTemplate/:instanceName\n * @description Create new template\n * @param {string} instanceName - Instance name\n * @body {TemplateDto} Template data\n * @returns {TemplateDto} Created template\n * @throws {400} Validation error\n * @throws {401} Unauthorized\n */\n.post(this.routerPath('createTemplate'), ...guards, async (req, res) => {\n  // Implementation\n})\n```\n\n<!-- File: .cursor/rules/specialized-rules/service-rules.mdc (Tokens: ~1681 | Category: cursor-rule) -->\n---\ndescription: Service layer patterns for Evolution API\nglobs:\n  - \"src/api/services/**/*.ts\"\n  - \"src/api/integrations/**/services/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Service Rules\n\n## Service Structure Pattern\n\n### Standard Service Class\n```typescript\nexport class ExampleService {\n  constructor(private readonly waMonitor: WAMonitoringService) {}\n\n  private readonly logger = new Logger('ExampleService');\n\n  public async create(instance: InstanceDto, data: ExampleDto) {\n    await this.waMonitor.waInstances[instance.instanceName].setData(data);\n    return { example: { ...instance, data } };\n  }\n\n  public async find(instance: InstanceDto): Promise<ExampleDto> {\n    try {\n      const result = await this.waMonitor.waInstances[instance.instanceName].findData();\n\n      if (Object.keys(result).length === 0) {\n        throw new Error('Data not found');\n      }\n\n      return result;\n    } catch (error) {\n      return null; // Evolution pattern - return null on error\n    }\n  }\n}\n```\n\n## Dependency Injection Pattern\n\n### Constructor Pattern\n```typescript\n// CORRECT - Evolution API pattern\nconstructor(\n  private readonly waMonitor: WAMonitoringService,\n  private readonly prismaRepository: PrismaRepository,\n  private readonly configService: ConfigService,\n) {}\n\n// INCORRECT - Don't use\nconstructor(waMonitor, prismaRepository, configService) {} // ❌ No types\n```\n\n## Logger Pattern\n\n### Standard Logger Usage\n```typescript\n// CORRECT - Evolution API pattern\nprivate readonly logger = new Logger('ServiceName');\n\n// Usage\nthis.logger.log('Operation started');\nthis.logger.error('Operation failed', error);\n\n// INCORRECT\nconsole.log('Operation started'); // ❌ Use Logger\n```\n\n## WAMonitor Integration Pattern\n\n### Instance Access Pattern\n```typescript\n// CORRECT - Standard pattern\npublic async operation(instance: InstanceDto, data: DataDto) {\n  await this.waMonitor.waInstances[instance.instanceName].performAction(data);\n  return { result: { ...instance, data } };\n}\n\n// Instance validation\nconst waInstance = this.waMonitor.waInstances[instance.instanceName];\nif (!waInstance) {\n  throw new NotFoundException('Instance not found');\n}\n```\n\n## Error Handling Pattern\n\n### Try-Catch Pattern\n```typescript\n// CORRECT - Evolution API pattern\npublic async find(instance: InstanceDto): Promise<DataDto> {\n  try {\n    const result = await this.waMonitor.waInstances[instance.instanceName].findData();\n    \n    if (Object.keys(result).length === 0) {\n      throw new Error('Data not found');\n    }\n\n    return result;\n  } catch (error) {\n    this.logger.error('Find operation failed', error);\n    return null; // Return null on error (Evolution pattern)\n  }\n}\n```\n\n## Cache Integration Pattern\n\n### Cache Service Usage\n```typescript\nexport class CacheAwareService {\n  constructor(\n    private readonly cache: CacheService,\n    private readonly chatwootCache: CacheService,\n    private readonly baileysCache: CacheService,\n  ) {}\n\n  public async getCachedData(key: string): Promise<any> {\n    const cached = await this.cache.get(key);\n    if (cached) return cached;\n\n    const data = await this.fetchFromSource(key);\n    await this.cache.set(key, data, 300); // 5 min TTL\n    return data;\n  }\n}\n```\n\n## Integration Service Patterns\n\n### Chatbot Service Base Pattern\n```typescript\nexport class ChatbotService extends BaseChatbotService<BotType, SettingsType> {\n  constructor(\n    waMonitor: WAMonitoringService,\n    prismaRepository: PrismaRepository,\n    configService: ConfigService,\n  ) {\n    super(waMonitor, prismaRepository, 'ChatbotService', configService);\n  }\n\n  protected async processBot(\n    waInstance: any,\n    remoteJid: string,\n    bot: BotType,\n    session: any,\n    settings: any,\n    content: string,\n  ): Promise<void> {\n    // Implementation\n  }\n}\n```\n\n### Channel Service Pattern\n```typescript\nexport class ChannelService extends ChannelStartupService {\n  constructor(\n    configService: ConfigService,\n    eventEmitter: EventEmitter2,\n    prismaRepository: PrismaRepository,\n    cache: CacheService,\n    chatwootCache: CacheService,\n    baileysCache: CacheService,\n  ) {\n    super(configService, eventEmitter, prismaRepository, cache, chatwootCache, baileysCache);\n  }\n\n  public readonly logger = new Logger('ChannelService');\n  public client: WASocket;\n  public readonly instance: wa.Instance = {};\n}\n```\n\n## Service Initialization Pattern\n\n### Service Registration\n```typescript\n// In server.module.ts pattern\nexport const templateService = new TemplateService(\n  waMonitor,\n  prismaRepository,\n  configService,\n);\n\nexport const settingsService = new SettingsService(waMonitor);\n```\n\n## Async Operation Patterns\n\n### Promise Handling\n```typescript\n// CORRECT - Evolution API pattern\npublic async sendMessage(instance: InstanceDto, data: MessageDto) {\n  const waInstance = this.waMonitor.waInstances[instance.instanceName];\n  return await waInstance.sendMessage(data);\n}\n\n// INCORRECT - Don't use .then()\npublic sendMessage(instance: InstanceDto, data: MessageDto) {\n  return this.waMonitor.waInstances[instance.instanceName]\n    .sendMessage(data)\n    .then(result => result); // ❌ Use async/await\n}\n```\n\n## Configuration Access Pattern\n\n### Config Service Usage\n```typescript\n// CORRECT - Evolution API pattern\nconst serverConfig = this.configService.get<HttpServer>('SERVER');\nconst authConfig = this.configService.get<Auth>('AUTHENTICATION');\nconst dbConfig = this.configService.get<Database>('DATABASE');\n\n// Type-safe configuration access\nif (this.configService.get<Chatwoot>('CHATWOOT').ENABLED) {\n  // Chatwoot logic\n}\n```\n\n## Event Emission Pattern\n\n### EventEmitter2 Usage\n```typescript\n// CORRECT - Evolution API pattern\nthis.eventEmitter.emit(Events.INSTANCE_CREATE, {\n  instanceName: instance.name,\n  status: 'created',\n});\n\n// Chatwoot event pattern\nif (this.configService.get<Chatwoot>('CHATWOOT').ENABLED && this.localChatwoot?.enabled) {\n  this.chatwootService.eventWhatsapp(\n    Events.STATUS_INSTANCE,\n    { instanceName: this.instance.name },\n    {\n      instance: this.instance.name,\n      status: 'created',\n    },\n  );\n}\n```\n\n## Service Method Naming\n\n### Standard Method Names\n- `create()` - Create new resource\n- `find()` - Find single resource\n- `findAll()` - Find multiple resources\n- `update()` - Update resource\n- `delete()` - Delete resource\n- `fetch*()` - Fetch from external API\n- `send*()` - Send data/messages\n- `process*()` - Process data\n\n## Service Testing Pattern\n\n### Unit Test Structure\n```typescript\ndescribe('ExampleService', () => {\n  let service: ExampleService;\n  let waMonitor: jest.Mocked<WAMonitoringService>;\n  let prismaRepository: jest.Mocked<PrismaRepository>;\n\n  beforeEach(() => {\n    const mockWaMonitor = {\n      waInstances: {\n        'test-instance': {\n          performAction: jest.fn(),\n        },\n      },\n    };\n\n    service = new ExampleService(\n      mockWaMonitor as any,\n      prismaRepository,\n      configService,\n    );\n  });\n\n  it('should perform action successfully', async () => {\n    const instance = { instanceName: 'test-instance' };\n    const data = { test: 'data' };\n\n    const result = await service.create(instance, data);\n\n    expect(result).toBeDefined();\n    expect(waMonitor.waInstances['test-instance'].performAction).toHaveBeenCalledWith(data);\n  });\n});\n```\n\n<!-- File: .cursor/rules/specialized-rules/type-rules.mdc (Tokens: ~2615 | Category: cursor-rule) -->\n---\ndescription: Type definitions and interfaces for Evolution API\nglobs:\n  - \"src/api/types/**/*.ts\"\n  - \"src/@types/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Type Rules\n\n## Namespace Pattern\n\n### WhatsApp Types Namespace\n```typescript\n/* eslint-disable @typescript-eslint/no-namespace */\nimport { JsonValue } from '@prisma/client/runtime/library';\nimport { AuthenticationState, WAConnectionState } from 'baileys';\n\nexport declare namespace wa {\n  export type QrCode = {\n    count?: number;\n    pairingCode?: string;\n    base64?: string;\n    code?: string;\n  };\n\n  export type Instance = {\n    id?: string;\n    qrcode?: QrCode;\n    pairingCode?: string;\n    authState?: { state: AuthenticationState; saveCreds: () => void };\n    name?: string;\n    wuid?: string;\n    profileName?: string;\n    profilePictureUrl?: string;\n    token?: string;\n    number?: string;\n    integration?: string;\n    businessId?: string;\n  };\n\n  export type LocalChatwoot = {\n    enabled?: boolean;\n    accountId?: string;\n    token?: string;\n    url?: string;\n    nameInbox?: string;\n    mergeBrazilContacts?: boolean;\n    importContacts?: boolean;\n    importMessages?: boolean;\n    daysLimitImportMessages?: number;\n    organization?: string;\n    logo?: string;\n  };\n\n  export type LocalProxy = {\n    enabled?: boolean;\n    host?: string;\n    port?: string;\n    protocol?: string;\n    username?: string;\n    password?: string;\n  };\n\n  export type LocalSettings = {\n    rejectCall?: boolean;\n    msgCall?: string;\n    groupsIgnore?: boolean;\n    alwaysOnline?: boolean;\n    readMessages?: boolean;\n    readStatus?: boolean;\n    syncFullHistory?: boolean;\n  };\n\n  export type LocalWebHook = {\n    enabled?: boolean;\n    url?: string;\n    events?: string[];\n    headers?: JsonValue;\n    byEvents?: boolean;\n    base64?: boolean;\n  };\n\n  export type StatusMessage = 'ERROR' | 'PENDING' | 'SERVER_ACK' | 'DELIVERY_ACK' | 'READ' | 'DELETED' | 'PLAYED';\n}\n```\n\n## Enum Definitions\n\n### Events Enum\n```typescript\nexport enum Events {\n  APPLICATION_STARTUP = 'application.startup',\n  INSTANCE_CREATE = 'instance.create',\n  INSTANCE_DELETE = 'instance.delete',\n  QRCODE_UPDATED = 'qrcode.updated',\n  CONNECTION_UPDATE = 'connection.update',\n  STATUS_INSTANCE = 'status.instance',\n  MESSAGES_SET = 'messages.set',\n  MESSAGES_UPSERT = 'messages.upsert',\n  MESSAGES_EDITED = 'messages.edited',\n  MESSAGES_UPDATE = 'messages.update',\n  MESSAGES_DELETE = 'messages.delete',\n  SEND_MESSAGE = 'send.message',\n  SEND_MESSAGE_UPDATE = 'send.message.update',\n  CONTACTS_SET = 'contacts.set',\n  CONTACTS_UPSERT = 'contacts.upsert',\n  CONTACTS_UPDATE = 'contacts.update',\n  PRESENCE_UPDATE = 'presence.update',\n  CHATS_SET = 'chats.set',\n  CHATS_UPDATE = 'chats.update',\n  CHATS_UPSERT = 'chats.upsert',\n  CHATS_DELETE = 'chats.delete',\n  GROUPS_UPSERT = 'groups.upsert',\n  GROUPS_UPDATE = 'groups.update',\n  GROUP_PARTICIPANTS_UPDATE = 'group-participants.update',\n  CALL = 'call',\n  TYPEBOT_START = 'typebot.start',\n  TYPEBOT_CHANGE_STATUS = 'typebot.change-status',\n  LABELS_EDIT = 'labels.edit',\n  LABELS_ASSOCIATION = 'labels.association',\n  CREDS_UPDATE = 'creds.update',\n  MESSAGING_HISTORY_SET = 'messaging-history.set',\n  REMOVE_INSTANCE = 'remove.instance',\n  LOGOUT_INSTANCE = 'logout.instance',\n}\n```\n\n### Integration Types\n```typescript\nexport const Integration = {\n  WHATSAPP_BUSINESS: 'WHATSAPP-BUSINESS',\n  WHATSAPP_BAILEYS: 'WHATSAPP-BAILEYS',\n  EVOLUTION: 'EVOLUTION',\n} as const;\n\nexport type IntegrationType = typeof Integration[keyof typeof Integration];\n```\n\n## Constant Arrays\n\n### Message Type Constants\n```typescript\nexport const TypeMediaMessage = [\n  'imageMessage',\n  'documentMessage',\n  'audioMessage',\n  'videoMessage',\n  'stickerMessage',\n  'ptvMessage', // Evolution API includes this\n];\n\nexport const MessageSubtype = [\n  'ephemeralMessage',\n  'documentWithCaptionMessage',\n  'viewOnceMessage',\n  'viewOnceMessageV2',\n];\n\nexport type MediaMessageType = typeof TypeMediaMessage[number];\nexport type MessageSubtypeType = typeof MessageSubtype[number];\n```\n\n## Interface Definitions\n\n### Service Interfaces\n```typescript\nexport interface ServiceInterface {\n  create(instance: InstanceDto, data: any): Promise<any>;\n  find(instance: InstanceDto): Promise<any>;\n  update?(instance: InstanceDto, data: any): Promise<any>;\n  delete?(instance: InstanceDto): Promise<any>;\n}\n\nexport interface ChannelServiceInterface extends ServiceInterface {\n  sendMessage(data: SendMessageDto): Promise<any>;\n  connectToWhatsapp(data?: any): Promise<void>;\n  receiveWebhook?(data: any): Promise<void>;\n}\n\nexport interface ChatbotServiceInterface extends ServiceInterface {\n  processMessage(\n    instanceName: string,\n    remoteJid: string,\n    message: any,\n    pushName?: string,\n  ): Promise<void>;\n}\n```\n\n## Configuration Types\n\n### Environment Configuration Types\n```typescript\nexport interface DatabaseConfig {\n  CONNECTION: {\n    URI: string;\n    DB_PREFIX_NAME: string;\n    CLIENT_NAME?: string;\n  };\n  ENABLED: boolean;\n  SAVE_DATA: {\n    INSTANCE: boolean;\n    NEW_MESSAGE: boolean;\n    MESSAGE_UPDATE: boolean;\n    CONTACTS: boolean;\n    CHATS: boolean;\n  };\n}\n\nexport interface AuthConfig {\n  TYPE: 'apikey' | 'jwt';\n  API_KEY: {\n    KEY: string;\n  };\n  JWT?: {\n    EXPIRIN_IN: number;\n    SECRET: string;\n  };\n}\n\nexport interface CacheConfig {\n  REDIS: {\n    ENABLED: boolean;\n    URI: string;\n    PREFIX_KEY: string;\n    SAVE_INSTANCES: boolean;\n  };\n  LOCAL: {\n    ENABLED: boolean;\n    TTL: number;\n  };\n}\n```\n\n## Message Types\n\n### Message Structure Types\n```typescript\nexport interface MessageContent {\n  text?: string;\n  caption?: string;\n  media?: Buffer | string;\n  mediatype?: 'image' | 'video' | 'audio' | 'document' | 'sticker';\n  fileName?: string;\n  mimetype?: string;\n}\n\nexport interface MessageOptions {\n  delay?: number;\n  presence?: 'unavailable' | 'available' | 'composing' | 'recording' | 'paused';\n  linkPreview?: boolean;\n  mentionsEveryOne?: boolean;\n  mentioned?: string[];\n  quoted?: {\n    key: {\n      remoteJid: string;\n      fromMe: boolean;\n      id: string;\n    };\n    message: any;\n  };\n}\n\nexport interface SendMessageRequest {\n  number: string;\n  content: MessageContent;\n  options?: MessageOptions;\n}\n```\n\n## Webhook Types\n\n### Webhook Payload Types\n```typescript\nexport interface WebhookPayload {\n  event: Events;\n  instance: string;\n  data: any;\n  timestamp: string;\n  server?: {\n    version: string;\n    host: string;\n  };\n}\n\nexport interface WebhookConfig {\n  enabled: boolean;\n  url: string;\n  events: Events[];\n  headers?: Record<string, string>;\n  byEvents?: boolean;\n  base64?: boolean;\n}\n```\n\n## Error Types\n\n### Custom Error Types\n```typescript\nexport interface ApiError {\n  status: number;\n  message: string;\n  error?: string;\n  details?: any;\n  timestamp: string;\n  path: string;\n}\n\nexport interface ValidationError extends ApiError {\n  status: 400;\n  validationErrors: Array<{\n    field: string;\n    message: string;\n    value?: any;\n  }>;\n}\n\nexport interface AuthenticationError extends ApiError {\n  status: 401;\n  message: 'Unauthorized' | 'Invalid API Key' | 'Token Expired';\n}\n```\n\n## Utility Types\n\n### Generic Utility Types\n```typescript\nexport type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;\n\nexport type RequiredFields<T, K extends keyof T> = T & Required<Pick<T, K>>;\n\nexport type DeepPartial<T> = {\n  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];\n};\n\nexport type NonEmptyArray<T> = [T, ...T[]];\n\nexport type StringKeys<T> = {\n  [K in keyof T]: T[K] extends string ? K : never;\n}[keyof T];\n```\n\n## Response Types\n\n### API Response Types\n```typescript\nexport interface ApiResponse<T = any> {\n  success: boolean;\n  data?: T;\n  message?: string;\n  error?: string;\n  timestamp: string;\n}\n\nexport interface PaginatedResponse<T> extends ApiResponse<T[]> {\n  pagination: {\n    page: number;\n    limit: number;\n    total: number;\n    totalPages: number;\n    hasNext: boolean;\n    hasPrev: boolean;\n  };\n}\n\nexport interface InstanceResponse extends ApiResponse {\n  instance: {\n    instanceName: string;\n    status: 'connecting' | 'open' | 'close' | 'qr';\n    qrcode?: string;\n    profileName?: string;\n    profilePicUrl?: string;\n  };\n}\n```\n\n## Integration-Specific Types\n\n### Baileys Types Extension\n```typescript\nimport { WASocket, ConnectionState, DisconnectReason } from 'baileys';\n\nexport interface BaileysInstance {\n  client: WASocket;\n  state: ConnectionState;\n  qrRetry: number;\n  authPath: string;\n}\n\nexport interface BaileysConfig {\n  qrTimeout: number;\n  maxQrRetries: number;\n  authTimeout: number;\n  reconnectInterval: number;\n}\n```\n\n### Business API Types\n```typescript\nexport interface BusinessApiConfig {\n  version: string;\n  baseUrl: string;\n  timeout: number;\n  retries: number;\n}\n\nexport interface BusinessApiMessage {\n  messaging_product: 'whatsapp';\n  to: string;\n  type: 'text' | 'image' | 'document' | 'audio' | 'video' | 'template';\n  text?: {\n    body: string;\n    preview_url?: boolean;\n  };\n  image?: {\n    link?: string;\n    id?: string;\n    caption?: string;\n  };\n  template?: {\n    name: string;\n    language: {\n      code: string;\n    };\n    components?: any[];\n  };\n}\n```\n\n## Type Guards\n\n### Type Guard Functions\n```typescript\nexport function isMediaMessage(message: any): message is MediaMessage {\n  return message && TypeMediaMessage.some(type => message[type]);\n}\n\nexport function isTextMessage(message: any): message is TextMessage {\n  return message && message.conversation;\n}\n\nexport function isValidIntegration(integration: string): integration is IntegrationType {\n  return Object.values(Integration).includes(integration as IntegrationType);\n}\n\nexport function isValidEvent(event: string): event is Events {\n  return Object.values(Events).includes(event as Events);\n}\n```\n\n## Module Augmentation\n\n### Express Request Extension\n```typescript\ndeclare global {\n  namespace Express {\n    interface Request {\n      instanceName?: string;\n      instanceData?: InstanceDto;\n      user?: {\n        id: string;\n        apiKey: string;\n      };\n    }\n  }\n}\n```\n\n## Type Documentation\n\n### JSDoc Type Documentation\n```typescript\n/**\n * WhatsApp instance configuration\n * @interface InstanceConfig\n * @property {string} name - Unique instance name\n * @property {IntegrationType} integration - Integration type\n * @property {string} [token] - API token for business integrations\n * @property {WebhookConfig} [webhook] - Webhook configuration\n * @property {ProxyConfig} [proxy] - Proxy configuration\n */\nexport interface InstanceConfig {\n  name: string;\n  integration: IntegrationType;\n  token?: string;\n  webhook?: WebhookConfig;\n  proxy?: ProxyConfig;\n}\n```\n\n<!-- File: .cursor/rules/specialized-rules/util-rules.mdc (Tokens: ~3888 | Category: cursor-rule) -->\n---\ndescription: Utility functions and helpers for Evolution API\nglobs:\n  - \"src/utils/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Utility Rules\n\n## Utility Function Structure\n\n### Standard Utility Pattern\n```typescript\nimport { Logger } from '@config/logger.config';\n\nconst logger = new Logger('UtilityName');\n\nexport function utilityFunction(param: ParamType): ReturnType {\n  try {\n    // Utility logic\n    return result;\n  } catch (error) {\n    logger.error(`Utility function failed: ${error.message}`);\n    throw error;\n  }\n}\n\nexport default utilityFunction;\n```\n\n## Authentication Utilities\n\n### Multi-File Auth State Pattern\n```typescript\nimport { AuthenticationState } from 'baileys';\nimport { CacheService } from '@api/services/cache.service';\nimport fs from 'fs/promises';\nimport path from 'path';\n\nexport default async function useMultiFileAuthStatePrisma(\n  sessionId: string,\n  cache: CacheService,\n): Promise<{\n  state: AuthenticationState;\n  saveCreds: () => Promise<void>;\n}> {\n  const localFolder = path.join(INSTANCE_DIR, sessionId);\n  const localFile = (key: string) => path.join(localFolder, fixFileName(key) + '.json');\n  await fs.mkdir(localFolder, { recursive: true });\n\n  async function writeData(data: any, key: string): Promise<any> {\n    const dataString = JSON.stringify(data, BufferJSON.replacer);\n\n    if (key !== 'creds') {\n      if (process.env.CACHE_REDIS_ENABLED === 'true') {\n        return await cache.hSet(sessionId, key, data);\n      } else {\n        await fs.writeFile(localFile(key), dataString);\n        return;\n      }\n    }\n    await saveKey(sessionId, dataString);\n    return;\n  }\n\n  async function readData(key: string): Promise<any> {\n    try {\n      let rawData;\n\n      if (key !== 'creds') {\n        if (process.env.CACHE_REDIS_ENABLED === 'true') {\n          return await cache.hGet(sessionId, key);\n        } else {\n          if (!(await fileExists(localFile(key)))) return null;\n          rawData = await fs.readFile(localFile(key), { encoding: 'utf-8' });\n          return JSON.parse(rawData, BufferJSON.reviver);\n        }\n      } else {\n        rawData = await getAuthKey(sessionId);\n      }\n\n      const parsedData = JSON.parse(rawData, BufferJSON.reviver);\n      return parsedData;\n    } catch (error) {\n      return null;\n    }\n  }\n\n  async function removeData(key: string): Promise<any> {\n    try {\n      if (key !== 'creds') {\n        if (process.env.CACHE_REDIS_ENABLED === 'true') {\n          return await cache.hDelete(sessionId, key);\n        } else {\n          await fs.unlink(localFile(key));\n        }\n      } else {\n        await deleteAuthKey(sessionId);\n      }\n    } catch (error) {\n      return;\n    }\n  }\n\n  let creds = await readData('creds');\n  if (!creds) {\n    creds = initAuthCreds();\n    await writeData(creds, 'creds');\n  }\n\n  return {\n    state: {\n      creds,\n      keys: {\n        get: async (type, ids) => {\n          const data = {};\n          await Promise.all(\n            ids.map(async (id) => {\n              let value = await readData(`${type}-${id}`);\n              if (type === 'app-state-sync-key' && value) {\n                value = proto.Message.AppStateSyncKeyData.fromObject(value);\n              }\n              data[id] = value;\n            })\n          );\n          return data;\n        },\n        set: async (data) => {\n          const tasks = [];\n          for (const category in data) {\n            for (const id in data[category]) {\n              const value = data[category][id];\n              const key = `${category}-${id}`;\n              tasks.push(value ? writeData(value, key) : removeData(key));\n            }\n          }\n          await Promise.all(tasks);\n        },\n      },\n    },\n    saveCreds: () => writeData(creds, 'creds'),\n  };\n}\n```\n\n## Message Processing Utilities\n\n### Message Content Extraction\n```typescript\nexport const getConversationMessage = (msg: any): string => {\n  const types = getTypeMessage(msg);\n  const messageContent = getMessageContent(types);\n  return messageContent;\n};\n\nconst getTypeMessage = (msg: any): any => {\n  return Object.keys(msg?.message || msg || {})[0];\n};\n\nconst getMessageContent = (type: string, msg?: any): string => {\n  const typeKey = type?.replace('Message', '');\n  \n  const types = {\n    conversation: msg?.message?.conversation,\n    extendedTextMessage: msg?.message?.extendedTextMessage?.text,\n    imageMessage: msg?.message?.imageMessage?.caption || 'Image',\n    videoMessage: msg?.message?.videoMessage?.caption || 'Video',\n    audioMessage: 'Audio',\n    documentMessage: msg?.message?.documentMessage?.caption || 'Document',\n    stickerMessage: 'Sticker',\n    contactMessage: 'Contact',\n    locationMessage: 'Location',\n    liveLocationMessage: 'Live Location',\n    viewOnceMessage: 'View Once',\n    reactionMessage: 'Reaction',\n    pollCreationMessage: 'Poll',\n    pollUpdateMessage: 'Poll Update',\n  };\n\n  let result = types[typeKey] || types[type] || 'Unknown';\n  \n  if (!result || result === 'Unknown') {\n    result = JSON.stringify(msg);\n  }\n\n  return result;\n};\n```\n\n### JID Creation Utility\n```typescript\nexport const createJid = (number: string): string => {\n  if (number.includes('@')) {\n    return number;\n  }\n\n  // Remove any non-numeric characters except +\n  let cleanNumber = number.replace(/[^\\d+]/g, '');\n  \n  // Remove + if present\n  if (cleanNumber.startsWith('+')) {\n    cleanNumber = cleanNumber.substring(1);\n  }\n\n  // Add country code if missing (assuming Brazil as default)\n  if (cleanNumber.length === 11 && cleanNumber.startsWith('11')) {\n    cleanNumber = '55' + cleanNumber;\n  } else if (cleanNumber.length === 10) {\n    cleanNumber = '5511' + cleanNumber;\n  }\n\n  // Determine if it's a group or individual\n  const isGroup = cleanNumber.includes('-');\n  const domain = isGroup ? 'g.us' : 's.whatsapp.net';\n\n  return `${cleanNumber}@${domain}`;\n};\n```\n\n## Cache Utilities\n\n### WhatsApp Number Cache\n```typescript\ninterface ISaveOnWhatsappCacheParams {\n  remoteJid: string;\n  lid?: string;\n}\n\nfunction getAvailableNumbers(remoteJid: string): string[] {\n  const numbersAvailable: string[] = [];\n\n  if (remoteJid.startsWith('+')) {\n    remoteJid = remoteJid.slice(1);\n  }\n\n  const [number, domain] = remoteJid.split('@');\n\n  // Brazilian numbers\n  if (remoteJid.startsWith('55')) {\n    const numberWithDigit =\n      number.slice(4, 5) === '9' && number.length === 13 ? number : `${number.slice(0, 4)}9${number.slice(4)}`;\n    const numberWithoutDigit = number.length === 12 ? number : number.slice(0, 4) + number.slice(5);\n\n    numbersAvailable.push(numberWithDigit);\n    numbersAvailable.push(numberWithoutDigit);\n  }\n  // Mexican/Argentina numbers\n  else if (number.startsWith('52') || number.startsWith('54')) {\n    let prefix = '';\n    if (number.startsWith('52')) {\n      prefix = '1';\n    }\n    if (number.startsWith('54')) {\n      prefix = '9';\n    }\n\n    const numberWithDigit =\n      number.slice(2, 3) === prefix && number.length === 13\n        ? number\n        : `${number.slice(0, 2)}${prefix}${number.slice(2)}`;\n    const numberWithoutDigit = number.length === 12 ? number : number.slice(0, 2) + number.slice(3);\n\n    numbersAvailable.push(numberWithDigit);\n    numbersAvailable.push(numberWithoutDigit);\n  }\n  // Other countries\n  else {\n    numbersAvailable.push(remoteJid);\n  }\n\n  return numbersAvailable.map((number) => `${number}@${domain}`);\n}\n\nexport async function saveOnWhatsappCache(params: ISaveOnWhatsappCacheParams): Promise<void> {\n  const { remoteJid, lid } = params;\n  const db = configService.get<Database>('DATABASE');\n\n  if (!db.SAVE_DATA.CONTACTS) {\n    return;\n  }\n\n  try {\n    const numbersAvailable = getAvailableNumbers(remoteJid);\n    \n    const existingContact = await prismaRepository.contact.findFirst({\n      where: {\n        OR: numbersAvailable.map(number => ({ id: number })),\n      },\n    });\n\n    if (!existingContact) {\n      await prismaRepository.contact.create({\n        data: {\n          id: remoteJid,\n          pushName: '',\n          profilePicUrl: '',\n          isOnWhatsapp: true,\n          lid: lid || null,\n          createdAt: new Date(),\n          updatedAt: new Date(),\n        },\n      });\n    } else {\n      await prismaRepository.contact.update({\n        where: { id: existingContact.id },\n        data: {\n          isOnWhatsapp: true,\n          lid: lid || existingContact.lid,\n          updatedAt: new Date(),\n        },\n      });\n    }\n  } catch (error) {\n    console.error('Error saving WhatsApp cache:', error);\n  }\n}\n```\n\n## Search Utilities\n\n### Advanced Search Operators\n```typescript\nfunction normalizeString(str: string): string {\n  return str\n    .toLowerCase()\n    .normalize('NFD')\n    .replace(/[\\u0300-\\u036f]/g, '');\n}\n\nexport function advancedOperatorsSearch(data: string, query: string): boolean {\n  const normalizedData = normalizeString(data);\n  const normalizedQuery = normalizeString(query);\n\n  // Exact phrase search with quotes\n  if (normalizedQuery.startsWith('\"') && normalizedQuery.endsWith('\"')) {\n    const phrase = normalizedQuery.slice(1, -1);\n    return normalizedData.includes(phrase);\n  }\n\n  // OR operator\n  if (normalizedQuery.includes(' OR ')) {\n    const terms = normalizedQuery.split(' OR ');\n    return terms.some(term => normalizedData.includes(term.trim()));\n  }\n\n  // AND operator (default behavior)\n  if (normalizedQuery.includes(' AND ')) {\n    const terms = normalizedQuery.split(' AND ');\n    return terms.every(term => normalizedData.includes(term.trim()));\n  }\n\n  // NOT operator\n  if (normalizedQuery.startsWith('NOT ')) {\n    const term = normalizedQuery.slice(4);\n    return !normalizedData.includes(term);\n  }\n\n  // Wildcard search\n  if (normalizedQuery.includes('*')) {\n    const regex = new RegExp(normalizedQuery.replace(/\\*/g, '.*'), 'i');\n    return regex.test(normalizedData);\n  }\n\n  // Default: simple contains search\n  return normalizedData.includes(normalizedQuery);\n}\n```\n\n## Proxy Utilities\n\n### Proxy Agent Creation\n```typescript\nimport { HttpsProxyAgent } from 'https-proxy-agent';\nimport { SocksProxyAgent } from 'socks-proxy-agent';\n\ntype Proxy = {\n  host: string;\n  port: string;\n  protocol: 'http' | 'https' | 'socks4' | 'socks5';\n  username?: string;\n  password?: string;\n};\n\nfunction selectProxyAgent(proxyUrl: string): HttpsProxyAgent<string> | SocksProxyAgent {\n  const url = new URL(proxyUrl);\n  \n  if (url.protocol === 'socks4:' || url.protocol === 'socks5:') {\n    return new SocksProxyAgent(proxyUrl);\n  } else {\n    return new HttpsProxyAgent(proxyUrl);\n  }\n}\n\nexport function makeProxyAgent(proxy: Proxy): HttpsProxyAgent<string> | SocksProxyAgent | null {\n  if (!proxy.host || !proxy.port) {\n    return null;\n  }\n\n  let proxyUrl = `${proxy.protocol}://`;\n  \n  if (proxy.username && proxy.password) {\n    proxyUrl += `${proxy.username}:${proxy.password}@`;\n  }\n  \n  proxyUrl += `${proxy.host}:${proxy.port}`;\n\n  try {\n    return selectProxyAgent(proxyUrl);\n  } catch (error) {\n    console.error('Failed to create proxy agent:', error);\n    return null;\n  }\n}\n```\n\n## Telemetry Utilities\n\n### Telemetry Data Collection\n```typescript\nexport interface TelemetryData {\n  route: string;\n  apiVersion: string;\n  timestamp: Date;\n  method?: string;\n  statusCode?: number;\n  responseTime?: number;\n  userAgent?: string;\n  instanceName?: string;\n}\n\nexport const sendTelemetry = async (route: string): Promise<void> => {\n  try {\n    const telemetryData: TelemetryData = {\n      route,\n      apiVersion: packageJson.version,\n      timestamp: new Date(),\n    };\n\n    // Only send telemetry if enabled\n    if (process.env.DISABLE_TELEMETRY === 'true') {\n      return;\n    }\n\n    // Send to telemetry service (implement as needed)\n    await axios.post('https://telemetry.evolution-api.com/collect', telemetryData, {\n      timeout: 5000,\n    });\n  } catch (error) {\n    // Silently fail - don't affect main application\n    console.debug('Telemetry failed:', error.message);\n  }\n};\n```\n\n## Internationalization Utilities\n\n### i18n Setup\n```typescript\nimport { ConfigService, Language } from '@config/env.config';\nimport fs from 'fs';\nimport i18next from 'i18next';\nimport path from 'path';\n\nconst __dirname = path.resolve(process.cwd(), 'src', 'utils');\nconst languages = ['en', 'pt-BR', 'es'];\nconst translationsPath = path.join(__dirname, 'translations');\nconst configService: ConfigService = new ConfigService();\n\nconst resources: any = {};\n\nlanguages.forEach((language) => {\n  const languagePath = path.join(translationsPath, `${language}.json`);\n  if (fs.existsSync(languagePath)) {\n    const translationContent = fs.readFileSync(languagePath, 'utf8');\n    resources[language] = {\n      translation: JSON.parse(translationContent),\n    };\n  }\n});\n\ni18next.init({\n  resources,\n  fallbackLng: 'en',\n  lng: configService.get<Language>('LANGUAGE') || 'pt-BR',\n  interpolation: {\n    escapeValue: false,\n  },\n});\n\nexport const t = i18next.t.bind(i18next);\nexport default i18next;\n```\n\n## Bot Trigger Utilities\n\n### Bot Trigger Matching\n```typescript\nimport { TriggerOperator, TriggerType } from '@prisma/client';\n\nexport function findBotByTrigger(\n  bots: any[],\n  content: string,\n  remoteJid: string,\n): any | null {\n  for (const bot of bots) {\n    if (!bot.enabled) continue;\n    \n    // Check ignore list\n    if (bot.ignoreJids && bot.ignoreJids.includes(remoteJid)) {\n      continue;\n    }\n\n    // Check trigger\n    if (matchesTrigger(content, bot.triggerType, bot.triggerOperator, bot.triggerValue)) {\n      return bot;\n    }\n  }\n\n  return null;\n}\n\nfunction matchesTrigger(\n  content: string,\n  triggerType: TriggerType,\n  triggerOperator: TriggerOperator,\n  triggerValue: string,\n): boolean {\n  const normalizedContent = content.toLowerCase().trim();\n  const normalizedValue = triggerValue.toLowerCase().trim();\n\n  switch (triggerType) {\n    case TriggerType.ALL:\n      return true;\n\n    case TriggerType.KEYWORD:\n      return matchesKeyword(normalizedContent, triggerOperator, normalizedValue);\n\n    case TriggerType.REGEX:\n      try {\n        const regex = new RegExp(triggerValue, 'i');\n        return regex.test(content);\n      } catch {\n        return false;\n      }\n\n    default:\n      return false;\n  }\n}\n\nfunction matchesKeyword(\n  content: string,\n  operator: TriggerOperator,\n  value: string,\n): boolean {\n  switch (operator) {\n    case TriggerOperator.EQUALS:\n      return content === value;\n    case TriggerOperator.CONTAINS:\n      return content.includes(value);\n    case TriggerOperator.STARTS_WITH:\n      return content.startsWith(value);\n    case TriggerOperator.ENDS_WITH:\n      return content.endsWith(value);\n    default:\n      return false;\n  }\n}\n```\n\n## Server Utilities\n\n### Server Status Check\n```typescript\nexport class ServerUP {\n  private static instance: ServerUP;\n  private isServerUp: boolean = false;\n\n  private constructor() {}\n\n  public static getInstance(): ServerUP {\n    if (!ServerUP.instance) {\n      ServerUP.instance = new ServerUP();\n    }\n    return ServerUP.instance;\n  }\n\n  public setServerStatus(status: boolean): void {\n    this.isServerUp = status;\n  }\n\n  public getServerStatus(): boolean {\n    return this.isServerUp;\n  }\n\n  public async waitForServer(timeout: number = 30000): Promise<boolean> {\n    const startTime = Date.now();\n    \n    while (!this.isServerUp && (Date.now() - startTime) < timeout) {\n      await new Promise(resolve => setTimeout(resolve, 100));\n    }\n    \n    return this.isServerUp;\n  }\n}\n```\n\n## Error Response Utilities\n\n### Standardized Error Responses\n```typescript\nexport function createMetaErrorResponse(error: any, context: string) {\n  const timestamp = new Date().toISOString();\n  \n  if (error.response?.data) {\n    return {\n      status: error.response.status || 500,\n      error: {\n        message: error.response.data.error?.message || 'External API error',\n        type: error.response.data.error?.type || 'api_error',\n        code: error.response.data.error?.code || 'unknown_error',\n        context,\n        timestamp,\n      },\n    };\n  }\n\n  return {\n    status: 500,\n    error: {\n      message: error.message || 'Internal server error',\n      type: 'internal_error',\n      code: 'server_error',\n      context,\n      timestamp,\n    },\n  };\n}\n\nexport function createValidationErrorResponse(errors: any[], context: string) {\n  return {\n    status: 400,\n    error: {\n      message: 'Validation failed',\n      type: 'validation_error',\n      code: 'invalid_input',\n      context,\n      details: errors,\n      timestamp: new Date().toISOString(),\n    },\n  };\n}\n```\n\n<!-- File: .cursor/rules/specialized-rules/validate-rules.mdc (Tokens: ~4050 | Category: cursor-rule) -->\n---\ndescription: Validation schemas and patterns for Evolution API\nglobs:\n  - \"src/validate/**/*.ts\"\nalwaysApply: false\n---\n\n# Evolution API Validation Rules\n\n## Validation Schema Structure\n\n### JSONSchema7 Pattern (Evolution API Standard)\n```typescript\nimport { JSONSchema7 } from 'json-schema';\nimport { v4 } from 'uuid';\n\nconst isNotEmpty = (...fields: string[]) => {\n  const properties = {};\n  fields.forEach((field) => {\n    properties[field] = {\n      if: { properties: { [field]: { type: 'string' } } },\n      then: { properties: { [field]: { minLength: 1 } } },\n    };\n  });\n\n  return {\n    allOf: Object.values(properties),\n  };\n};\n\nexport const exampleSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    name: { type: 'string' },\n    description: { type: 'string' },\n    enabled: { type: 'boolean' },\n    settings: {\n      type: 'object',\n      properties: {\n        timeout: { type: 'number', minimum: 1000, maximum: 60000 },\n        retries: { type: 'number', minimum: 0, maximum: 5 },\n      },\n    },\n    tags: {\n      type: 'array',\n      items: { type: 'string' },\n    },\n  },\n  required: ['name', 'enabled'],\n  ...isNotEmpty('name'),\n};\n```\n\n## Message Validation Schemas\n\n### Send Message Validation\n```typescript\nconst numberDefinition = {\n  type: 'string',\n  pattern: '^\\\\d+[\\\\.@\\\\w-]+',\n  description: 'Invalid number',\n};\n\nexport const sendTextSchema: JSONSchema7 = {\n  $id: v4(),\n  type: 'object',\n  properties: {\n    number: numberDefinition,\n    text: { type: 'string', minLength: 1, maxLength: 4096 },\n    delay: { type: 'number', minimum: 0, maximum: 60000 },\n    linkPreview: { type: 'boolean' },\n    mentionsEveryOne: { type: 'boolean' },\n    mentioned: {\n      type: 'array',\n      items: { type: 'string' },\n    },\n  },\n  required: ['number', 'text'],\n  ...isNotEmpty('number', 'text'),\n};\n\nexport const sendMediaSchema = Joi.object({\n  number: Joi.string().required().pattern(/^\\d+$/),\n  mediatype: Joi.string().required().valid('image', 'video', 'audio', 'document'),\n  media: Joi.alternatives().try(\n    Joi.string().uri(),\n    Joi.string().base64(),\n  ).required(),\n  caption: Joi.string().optional().max(1024),\n  fileName: Joi.string().optional().max(255),\n  delay: Joi.number().optional().min(0).max(60000),\n}).required();\n\nexport const sendButtonsSchema = Joi.object({\n  number: Joi.string().required().pattern(/^\\d+$/),\n  title: Joi.string().required().max(1024),\n  description: Joi.string().optional().max(1024),\n  footer: Joi.string().optional().max(60),\n  buttons: Joi.array().items(\n    Joi.object({\n      type: Joi.string().required().valid('replyButton', 'urlButton', 'callButton'),\n      displayText: Joi.string().required().max(20),\n      id: Joi.string().when('type', {\n        is: 'replyButton',\n        then: Joi.required().max(256),\n        otherwise: Joi.optional(),\n      }),\n      url: Joi.string().when('type', {\n        is: 'urlButton',\n        then: Joi.required().uri(),\n        otherwise: Joi.optional(),\n      }),\n      phoneNumber: Joi.string().when('type', {\n        is: 'callButton',\n        then: Joi.required().pattern(/^\\+?\\d+$/),\n        otherwise: Joi.optional(),\n      }),\n    })\n  ).min(1).max(3).required(),\n}).required();\n```\n\n## Instance Validation Schemas\n\n### Instance Creation Validation\n```typescript\nexport const instanceSchema = Joi.object({\n  instanceName: Joi.string().required().min(1).max(100).pattern(/^[a-zA-Z0-9_-]+$/),\n  integration: Joi.string().required().valid('WHATSAPP-BAILEYS', 'WHATSAPP-BUSINESS', 'EVOLUTION'),\n  token: Joi.string().when('integration', {\n    is: Joi.valid('WHATSAPP-BUSINESS', 'EVOLUTION'),\n    then: Joi.required().min(10),\n    otherwise: Joi.optional(),\n  }),\n  qrcode: Joi.boolean().optional().default(false),\n  number: Joi.string().optional().pattern(/^\\d+$/),\n  businessId: Joi.string().when('integration', {\n    is: 'WHATSAPP-BUSINESS',\n    then: Joi.required(),\n    otherwise: Joi.optional(),\n  }),\n}).required();\n\nexport const settingsSchema = Joi.object({\n  rejectCall: Joi.boolean().optional(),\n  msgCall: Joi.string().optional().max(500),\n  groupsIgnore: Joi.boolean().optional(),\n  alwaysOnline: Joi.boolean().optional(),\n  readMessages: Joi.boolean().optional(),\n  readStatus: Joi.boolean().optional(),\n  syncFullHistory: Joi.boolean().optional(),\n  wavoipToken: Joi.string().optional(),\n}).optional();\n\nexport const proxySchema = Joi.object({\n  host: Joi.string().required().hostname(),\n  port: Joi.string().required().pattern(/^\\d+$/).custom((value) => {\n    const port = parseInt(value);\n    if (port < 1 || port > 65535) {\n      throw new Error('Port must be between 1 and 65535');\n    }\n    return value;\n  }),\n  protocol: Joi.string().required().valid('http', 'https', 'socks4', 'socks5'),\n  username: Joi.string().optional(),\n  password: Joi.string().optional(),\n}).optional();\n```\n\n## Webhook Validation Schemas\n\n### Webhook Configuration Validation\n```typescript\nexport const webhookSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  url: Joi.string().when('enabled', {\n    is: true,\n    then: Joi.required().uri({ scheme: ['http', 'https'] }),\n    otherwise: Joi.optional(),\n  }),\n  events: Joi.array().items(\n    Joi.string().valid(\n      'APPLICATION_STARTUP',\n      'INSTANCE_CREATE',\n      'INSTANCE_DELETE',\n      'QRCODE_UPDATED',\n      'CONNECTION_UPDATE',\n      'STATUS_INSTANCE',\n      'MESSAGES_SET',\n      'MESSAGES_UPSERT',\n      'MESSAGES_UPDATE',\n      'MESSAGES_DELETE',\n      'CONTACTS_SET',\n      'CONTACTS_UPSERT',\n      'CONTACTS_UPDATE',\n      'CHATS_SET',\n      'CHATS_UPDATE',\n      'CHATS_UPSERT',\n      'CHATS_DELETE',\n      'GROUPS_UPSERT',\n      'GROUPS_UPDATE',\n      'GROUP_PARTICIPANTS_UPDATE',\n      'CALL'\n    )\n  ).min(1).when('enabled', {\n    is: true,\n    then: Joi.required(),\n    otherwise: Joi.optional(),\n  }),\n  headers: Joi.object().pattern(\n    Joi.string(),\n    Joi.string()\n  ).optional(),\n  byEvents: Joi.boolean().optional().default(false),\n  base64: Joi.boolean().optional().default(false),\n}).required();\n```\n\n## Chatbot Validation Schemas\n\n### Base Chatbot Validation\n```typescript\nexport const baseChatbotSchema = Joi.object({\n  enabled: Joi.boolean().required(),\n  description: Joi.string().required().min(1).max(500),\n  expire: Joi.number().optional().min(0).max(86400), // 24 hours in seconds\n  keywordFinish: Joi.string().optional().max(100),\n  delayMessage: Joi.number().optional().min(0).max(10000),\n  unknownMessage: Joi.string().optional().max(1000),\n  listeningFromMe: Joi.boolean().optional().default(false),\n  stopBotFromMe: Joi.boolean().optional().default(false),\n  keepOpen: Joi.boolean().optional().default(false),\n  debounceTime: Joi.number().optional().min(0).max(60000),\n  triggerType: Joi.string().required().valid('ALL', 'KEYWORD', 'REGEX'),\n  triggerOperator: Joi.string().when('triggerType', {\n    is: 'KEYWORD',\n    then: Joi.required().valid('EQUALS', 'CONTAINS', 'STARTS_WITH', 'ENDS_WITH'),\n    otherwise: Joi.optional(),\n  }),\n  triggerValue: Joi.string().when('triggerType', {\n    is: Joi.valid('KEYWORD', 'REGEX'),\n    then: Joi.required().min(1).max(500),\n    otherwise: Joi.optional(),\n  }),\n  ignoreJids: Joi.array().items(Joi.string()).optional(),\n  splitMessages: Joi.boolean().optional().default(false),\n  timePerChar: Joi.number().optional().min(10).max(1000).default(100),\n}).required();\n\nexport const typebotSchema = baseChatbotSchema.keys({\n  url: Joi.string().required().uri({ scheme: ['http', 'https'] }),\n  typebot: Joi.string().required().min(1).max(100),\n  apiVersion: Joi.string().optional().valid('v1', 'v2').default('v1'),\n}).required();\n\nexport const openaiSchema = baseChatbotSchema.keys({\n  apiKey: Joi.string().required().min(10),\n  model: Joi.string().optional().valid(\n    'gpt-3.5-turbo',\n    'gpt-3.5-turbo-16k',\n    'gpt-4',\n    'gpt-4-32k',\n    'gpt-4-turbo-preview'\n  ).default('gpt-3.5-turbo'),\n  systemMessage: Joi.string().optional().max(2000),\n  maxTokens: Joi.number().optional().min(1).max(4000).default(1000),\n  temperature: Joi.number().optional().min(0).max(2).default(0.7),\n}).required();\n```\n\n## Business API Validation Schemas\n\n### Template Validation\n```typescript\nexport const templateSchema = Joi.object({\n  name: Joi.string().required().min(1).max(512).pattern(/^[a-z0-9_]+$/),\n  category: Joi.string().required().valid('MARKETING', 'UTILITY', 'AUTHENTICATION'),\n  allowCategoryChange: Joi.boolean().required(),\n  language: Joi.string().required().pattern(/^[a-z]{2}_[A-Z]{2}$/), // e.g., pt_BR, en_US\n  components: Joi.array().items(\n    Joi.object({\n      type: Joi.string().required().valid('HEADER', 'BODY', 'FOOTER', 'BUTTONS'),\n      format: Joi.string().when('type', {\n        is: 'HEADER',\n        then: Joi.valid('TEXT', 'IMAGE', 'VIDEO', 'DOCUMENT'),\n        otherwise: Joi.optional(),\n      }),\n      text: Joi.string().when('type', {\n        is: Joi.valid('HEADER', 'BODY', 'FOOTER'),\n        then: Joi.required().min(1).max(1024),\n        otherwise: Joi.optional(),\n      }),\n      buttons: Joi.array().when('type', {\n        is: 'BUTTONS',\n        then: Joi.items(\n          Joi.object({\n            type: Joi.string().required().valid('QUICK_REPLY', 'URL', 'PHONE_NUMBER'),\n            text: Joi.string().required().min(1).max(25),\n            url: Joi.string().when('type', {\n              is: 'URL',\n              then: Joi.required().uri(),\n              otherwise: Joi.optional(),\n            }),\n            phone_number: Joi.string().when('type', {\n              is: 'PHONE_NUMBER',\n              then: Joi.required().pattern(/^\\+?\\d+$/),\n              otherwise: Joi.optional(),\n            }),\n          })\n        ).min(1).max(10),\n        otherwise: Joi.optional(),\n      }),\n    })\n  ).min(1).required(),\n  webhookUrl: Joi.string().optional().uri({ scheme: ['http', 'https'] }),\n}).required();\n\nexport const catalogSchema = Joi.object({\n  number: Joi.string().optional().pattern(/^\\d+$/),\n  limit: Joi.number().optional().min(1).max(1000).default(10),\n  cursor: Joi.string().optional(),\n}).optional();\n```\n\n## Group Validation Schemas\n\n### Group Management Validation\n```typescript\nexport const createGroupSchema = Joi.object({\n  subject: Joi.string().required().min(1).max(100),\n  description: Joi.string().optional().max(500),\n  participants: Joi.array().items(\n    Joi.string().pattern(/^\\d+$/)\n  ).min(1).max(256).required(),\n  promoteParticipants: Joi.boolean().optional().default(false),\n}).required();\n\nexport const updateGroupSchema = Joi.object({\n  subject: Joi.string().optional().min(1).max(100),\n  description: Joi.string().optional().max(500),\n}).min(1).required();\n\nexport const groupParticipantsSchema = Joi.object({\n  participants: Joi.array().items(\n    Joi.string().pattern(/^\\d+$/)\n  ).min(1).max(50).required(),\n  action: Joi.string().required().valid('add', 'remove', 'promote', 'demote'),\n}).required();\n```\n\n## Label Validation Schemas\n\n### Label Management Validation\n```typescript\nexport const labelSchema = Joi.object({\n  name: Joi.string().required().min(1).max(100),\n  color: Joi.string().required().pattern(/^#[0-9A-Fa-f]{6}$/), // Hex color\n  predefinedId: Joi.string().optional(),\n}).required();\n\nexport const handleLabelSchema = Joi.object({\n  number: Joi.string().required().pattern(/^\\d+$/),\n  labelId: Joi.string().required(),\n  action: Joi.string().required().valid('add', 'remove'),\n}).required();\n```\n\n## Custom Validation Functions\n\n### Phone Number Validation\n```typescript\nexport function validatePhoneNumber(number: string): boolean {\n  // Remove any non-digit characters\n  const cleaned = number.replace(/\\D/g, '');\n  \n  // Check minimum and maximum length\n  if (cleaned.length < 10 || cleaned.length > 15) {\n    return false;\n  }\n  \n  // Check for valid country codes (basic validation)\n  const validCountryCodes = ['1', '7', '20', '27', '30', '31', '32', '33', '34', '36', '39', '40', '41', '43', '44', '45', '46', '47', '48', '49', '51', '52', '53', '54', '55', '56', '57', '58', '60', '61', '62', '63', '64', '65', '66', '81', '82', '84', '86', '90', '91', '92', '93', '94', '95', '98'];\n  \n  // Check if starts with valid country code\n  const startsWithValidCode = validCountryCodes.some(code => cleaned.startsWith(code));\n  \n  return startsWithValidCode;\n}\n\nexport const phoneNumberValidator = Joi.string().custom((value, helpers) => {\n  if (!validatePhoneNumber(value)) {\n    return helpers.error('any.invalid');\n  }\n  return value;\n}, 'Phone number validation');\n```\n\n### Base64 Validation\n```typescript\nexport function validateBase64(base64: string): boolean {\n  try {\n    // Check if it's a valid base64 string\n    const decoded = Buffer.from(base64, 'base64').toString('base64');\n    return decoded === base64;\n  } catch {\n    return false;\n  }\n}\n\nexport const base64Validator = Joi.string().custom((value, helpers) => {\n  if (!validateBase64(value)) {\n    return helpers.error('any.invalid');\n  }\n  return value;\n}, 'Base64 validation');\n```\n\n### URL Validation with Protocol Check\n```typescript\nexport function validateWebhookUrl(url: string): boolean {\n  try {\n    const parsed = new URL(url);\n    return ['http:', 'https:'].includes(parsed.protocol);\n  } catch {\n    return false;\n  }\n}\n\nexport const webhookUrlValidator = Joi.string().custom((value, helpers) => {\n  if (!validateWebhookUrl(value)) {\n    return helpers.error('any.invalid');\n  }\n  return value;\n}, 'Webhook URL validation');\n```\n\n## Validation Error Handling\n\n### Error Message Customization\n```typescript\nexport const validationMessages = {\n  'any.required': 'O campo {#label} é obrigatório',\n  'string.empty': 'O campo {#label} não pode estar vazio',\n  'string.min': 'O campo {#label} deve ter pelo menos {#limit} caracteres',\n  'string.max': 'O campo {#label} deve ter no máximo {#limit} caracteres',\n  'string.pattern.base': 'O campo {#label} possui formato inválido',\n  'number.min': 'O campo {#label} deve ser maior ou igual a {#limit}',\n  'number.max': 'O campo {#label} deve ser menor ou igual a {#limit}',\n  'array.min': 'O campo {#label} deve ter pelo menos {#limit} itens',\n  'array.max': 'O campo {#label} deve ter no máximo {#limit} itens',\n  'any.only': 'O campo {#label} deve ser um dos valores: {#valids}',\n};\n\nexport function formatValidationError(error: Joi.ValidationError): any {\n  return {\n    message: 'Dados de entrada inválidos',\n    details: error.details.map(detail => ({\n      field: detail.path.join('.'),\n      message: detail.message,\n      value: detail.context?.value,\n    })),\n  };\n}\n```\n\n## Schema Composition\n\n### Reusable Schema Components\n```typescript\nexport const commonFields = {\n  instanceName: Joi.string().required().min(1).max(100).pattern(/^[a-zA-Z0-9_-]+$/),\n  number: phoneNumberValidator.required(),\n  delay: Joi.number().optional().min(0).max(60000),\n  enabled: Joi.boolean().optional().default(true),\n};\n\nexport const mediaFields = {\n  mediatype: Joi.string().required().valid('image', 'video', 'audio', 'document'),\n  media: Joi.alternatives().try(\n    Joi.string().uri(),\n    base64Validator,\n  ).required(),\n  caption: Joi.string().optional().max(1024),\n  fileName: Joi.string().optional().max(255),\n};\n\n// Compose schemas using common fields\nexport const quickMessageSchema = Joi.object({\n  ...commonFields,\n  text: Joi.string().required().min(1).max(4096),\n}).required();\n\nexport const quickMediaSchema = Joi.object({\n  ...commonFields,\n  ...mediaFields,\n}).required();\n```\n\n<!-- File: .cursor/rules/cursor.json (Tokens: ~1168 | Category: anthropic-skill) -->\n{\n  \"version\": \"1.0\",\n  \"description\": \"Cursor IDE configuration for Evolution API project\",\n  \"rules\": {\n    \"general\": {\n      \"max_line_length\": 120,\n      \"indent_size\": 2,\n      \"end_of_line\": \"lf\",\n      \"charset\": \"utf-8\",\n      \"trim_trailing_whitespace\": true,\n      \"insert_final_newline\": true\n    },\n    \"typescript\": {\n      \"quotes\": \"single\",\n      \"semi\": true,\n      \"trailing_comma\": \"es5\",\n      \"bracket_spacing\": true,\n      \"arrow_parens\": \"avoid\",\n      \"print_width\": 120,\n      \"tab_width\": 2,\n      \"use_tabs\": false,\n      \"single_quote\": true,\n      \"end_of_line\": \"lf\",\n      \"strict\": true,\n      \"no_implicit_any\": true,\n      \"strict_null_checks\": true\n    },\n    \"javascript\": {\n      \"quotes\": \"single\",\n      \"semi\": true,\n      \"trailing_comma\": \"es5\",\n      \"bracket_spacing\": true,\n      \"arrow_parens\": \"avoid\",\n      \"print_width\": 120,\n      \"tab_width\": 2,\n      \"use_tabs\": false,\n      \"single_quote\": true,\n      \"end_of_line\": \"lf\",\n      \"style_guide\": \"eslint-airbnb\"\n    },\n    \"json\": {\n      \"tab_width\": 2,\n      \"use_tabs\": false,\n      \"parser\": \"json\"\n    },\n    \"ignore\": {\n      \"files\": [\n        \"node_modules/**\",\n        \"dist/**\",\n        \"build/**\",\n        \".git/**\",\n        \"*.min.js\",\n        \"*.min.css\",\n        \".env\",\n        \".env.*\",\n        \".env.example\",\n        \"coverage/**\",\n        \"*.log\",\n        \"*.lock\",\n        \"pnpm-lock.yaml\",\n        \"package-lock.json\",\n        \"yarn.lock\",\n        \"log/**\",\n        \"tmp/**\",\n        \"instances/**\",\n        \"public/uploads/**\",\n        \"*.dump\",\n        \"*.rdb\",\n        \"*.mmdb\",\n        \".DS_Store\",\n        \"*.swp\",\n        \"*.swo\",\n        \"*.un~\",\n        \".jest-cache\",\n        \".idea/**\",\n        \".vscode/**\",\n        \".yalc/**\",\n        \"yalc.lock\",\n        \"*.local\",\n        \"prisma/migrations/**\",\n        \"prisma/mysql-migrations/**\",\n        \"prisma/postgresql-migrations/**\"\n      ]\n    },\n    \"search\": {\n      \"exclude_patterns\": [\n        \"**/node_modules/**\",\n        \"**/dist/**\",\n        \"**/build/**\",\n        \"**/.git/**\",\n        \"**/coverage/**\",\n        \"**/log/**\",\n        \"**/tmp/**\",\n        \"**/instances/**\",\n        \"**/public/uploads/**\",\n        \"**/*.min.js\",\n        \"**/*.min.css\",\n        \"**/*.log\",\n        \"**/*.lock\",\n        \"**/pnpm-lock.yaml\",\n        \"**/package-lock.json\",\n        \"**/yarn.lock\",\n        \"**/*.dump\",\n        \"**/*.rdb\",\n        \"**/*.mmdb\",\n        \"**/.DS_Store\",\n        \"**/*.swp\",\n        \"**/*.swo\",\n        \"**/*.un~\",\n        \"**/.jest-cache\",\n        \"**/.idea/**\",\n        \"**/.vscode/**\",\n        \"**/.yalc/**\",\n        \"**/yalc.lock\",\n        \"**/*.local\",\n        \"**/prisma/migrations/**\",\n        \"**/prisma/mysql-migrations/**\",\n        \"**/prisma/postgresql-migrations/**\"\n      ]\n    },\n    \"evolution_api\": {\n      \"project_type\": \"nodejs_typescript_api\",\n      \"backend_framework\": \"express_prisma\",\n      \"database\": [\"postgresql\", \"mysql\"],\n      \"cache\": [\"redis\", \"node_cache\"],\n      \"queue\": [\"rabbitmq\", \"sqs\"],\n      \"real_time\": \"socket_io\",\n      \"file_storage\": [\"aws_s3\", \"minio\"],\n      \"validation\": \"class_validator\",\n      \"logging\": \"pino\",\n      \"main_directories\": {\n        \"source\": \"src/\",\n        \"api\": \"src/api/\",\n        \"controllers\": \"src/api/controllers/\",\n        \"services\": \"src/api/services/\",\n        \"integrations\": \"src/api/integrations/\",\n        \"dto\": \"src/api/dto/\",\n        \"types\": \"src/api/types/\",\n        \"guards\": \"src/api/guards/\",\n        \"routes\": \"src/api/routes/\",\n        \"cache\": \"src/cache/\",\n        \"config\": \"src/config/\",\n        \"utils\": \"src/utils/\",\n        \"exceptions\": \"src/exceptions/\",\n        \"validate\": \"src/validate/\",\n        \"prisma\": \"prisma/\",\n        \"tests\": \"test/\",\n        \"docs\": \"docs/\"\n      },\n      \"key_patterns\": [\n        \"whatsapp_integration\",\n        \"multi_database_support\",\n        \"instance_management\",\n        \"event_driven_architecture\",\n        \"service_layer_pattern\",\n        \"dto_validation\",\n        \"webhook_processing\",\n        \"message_queuing\",\n        \"real_time_communication\",\n        \"file_storage_integration\"\n      ],\n      \"whatsapp_integrations\": [\n        \"baileys\",\n        \"meta_business_api\",\n        \"whatsapp_cloud_api\"\n      ],\n      \"external_integrations\": [\n        \"chatwoot\",\n        \"typebot\",\n        \"openai\",\n        \"dify\",\n        \"rabbitmq\",\n        \"sqs\",\n        \"s3\",\n        \"minio\"\n      ]\n    }\n  }\n}\n\n<!-- File: .cursor/rules/README.md (Tokens: ~1237 | Category: anthropic-skill) -->\n# Evolution API Cursor Rules\n\nEste diretório contém as regras e configurações do Cursor IDE para o projeto Evolution API.\n\n## Estrutura dos Arquivos\n\n### Arquivos Principais (alwaysApply: true)\n- **`core-development.mdc`** - Princípios fundamentais de desenvolvimento\n- **`project-context.mdc`** - Contexto específico do projeto Evolution API\n- **`cursor.json`** - Configurações do Cursor IDE\n\n### Regras Especializadas (alwaysApply: false)\nEstas regras são ativadas automaticamente quando você trabalha nos arquivos correspondentes:\n\n#### Camadas da Aplicação\n- **`specialized-rules/service-rules.mdc`** - Padrões para services (`src/api/services/`)\n- **`specialized-rules/controller-rules.mdc`** - Padrões para controllers (`src/api/controllers/`)\n- **`specialized-rules/dto-rules.mdc`** - Padrões para DTOs (`src/api/dto/`)\n- **`specialized-rules/guard-rules.mdc`** - Padrões para guards (`src/api/guards/`)\n- **`specialized-rules/route-rules.mdc`** - Padrões para routers (`src/api/routes/`)\n\n#### Tipos e Validação\n- **`specialized-rules/type-rules.mdc`** - Definições TypeScript (`src/api/types/`)\n- **`specialized-rules/validate-rules.mdc`** - Schemas de validação (`src/validate/`)\n\n#### Utilitários\n- **`specialized-rules/util-rules.mdc`** - Funções utilitárias (`src/utils/`)\n\n#### Integrações\n- **`specialized-rules/integration-channel-rules.mdc`** - Integrações de canal (`src/api/integrations/channel/`)\n- **`specialized-rules/integration-chatbot-rules.mdc`** - Integrações de chatbot (`src/api/integrations/chatbot/`)\n- **`specialized-rules/integration-storage-rules.mdc`** - Integrações de storage (`src/api/integrations/storage/`)\n- **`specialized-rules/integration-event-rules.mdc`** - Integrações de eventos (`src/api/integrations/event/`)\n\n## Como Usar\n\n### Referências Cruzadas\nOs arquivos principais fazem referência aos especializados usando a sintaxe `@specialized-rules/nome-do-arquivo.mdc`. Quando você trabalha em um arquivo específico, o Cursor automaticamente carrega as regras relevantes.\n\n### Exemplo de Uso\nQuando você edita um arquivo em `src/api/services/`, o Cursor automaticamente:\n1. Carrega `core-development.mdc` (sempre ativo)\n2. Carrega `project-context.mdc` (sempre ativo)\n3. Carrega `specialized-rules/service-rules.mdc` (ativado pelo glob pattern)\n\n### Padrões de Código\nCada arquivo de regras contém:\n- **Estruturas padrão** - Como organizar o código\n- **Padrões de nomenclatura** - Convenções de nomes\n- **Exemplos práticos** - Código de exemplo\n- **Anti-padrões** - O que evitar\n- **Testes** - Como testar o código\n- **Padrões de Commit** - Conventional Commits com commitlint\n\n## Configuração do Cursor\n\nO arquivo `cursor.json` contém:\n- Configurações de formatação\n- Padrões de código específicos do Evolution API\n- Diretórios principais do projeto\n- Integrações e tecnologias utilizadas\n\n## Manutenção\n\nPara manter as regras atualizadas:\n1. Analise novos padrões no código\n2. Atualize as regras especializadas correspondentes\n3. Mantenha os exemplos sincronizados com o código real\n4. Documente mudanças significativas\n\n## Tecnologias Cobertas\n\n- **Backend**: Node.js 20+ + TypeScript 5+ + Express.js\n- **Database**: Prisma ORM (PostgreSQL/MySQL)\n- **Cache**: Redis + Node-cache\n- **Queue**: RabbitMQ + Amazon SQS\n- **Real-time**: Socket.io\n- **Storage**: AWS S3 + Minio\n- **Validation**: JSONSchema7\n- **Logging**: Pino\n- **WhatsApp**: Baileys + Meta Business API\n- **Integrations**: Chatwoot, Typebot, OpenAI, Dify\n\n## Estrutura do Projeto\n\n```\nsrc/\n├── api/\n│   ├── controllers/     # Controllers (HTTP handlers)\n│   ├── services/        # Business logic\n│   ├── dto/            # Data Transfer Objects\n│   ├── guards/         # Authentication/Authorization\n│   ├── routes/         # Express routers\n│   ├── types/          # TypeScript definitions\n│   └── integrations/   # External integrations\n│       ├── channel/    # WhatsApp channels (Baileys, Business API)\n│       ├── chatbot/    # Chatbot integrations\n│       ├── event/      # Event integrations\n│       └── storage/    # Storage integrations\n├── cache/              # Cache implementations\n├── config/             # Configuration files\n├── utils/              # Utility functions\n├── validate/           # Validation schemas\n└── exceptions/         # Custom exceptions\n```\n\nEste sistema de regras garante consistência no código e facilita o desenvolvimento seguindo os padrões estabelecidos do Evolution API.\n</yakaai_skills>"}