userModel = new UserModel(); $this->integrationsModel = new IntegrationsModel(); $this->messagesModel = new UnipileMessagesModel(); $this->unipileClient = new UnipileClient(); } public function __invoke(ServerRequestInterface $request) { $userId = (int) ($request->getAttribute('user_id') ?? 0); $userEmail = (string) ($request->getAttribute('user_email') ?? ''); $userRole = mb_strtolower(trim((string) ($request->getAttribute('user_role') ?? ''))); $body = json_decode((string) $request->getBody(), true) ?: []; $conversationId = (int) ($body['conversation_id'] ?? 0); $text = trim((string) ($body['text'] ?? '')); if ($userId <= 0) { return Payload::fail('Unauthorized: Missing authenticated user', [], 'E_VALIDATE', 401); } $validator = (new Validator(['conversation_id' => $conversationId, 'text' => $text])) ->required('conversation_id')->intRange('conversation_id', 1) ->required('text')->maxLength('text', 4000); if ($validator->fails()) { return Payload::fail($validator->firstError(), [], 'E_VALIDATE', 400); } if (!$this->unipileClient->isConfigured()) { return Payload::fail('Unipile is not configured', [], 'E_GENERIC', 500); } try { $companyId = $this->userModel->getCompanyIdByUserId($userId); if ($companyId === null) { return Payload::fail('User not found', [], 'E_NOT_FOUND', 404); } $conversation = $this->messagesModel->getConversationForSending($companyId, $conversationId); if ($conversation === null) { return Payload::fail('Conversation not found', [], 'E_NOT_FOUND', 404); } if (!$this->canSend($companyId, $userEmail, $userRole, $conversation)) { return Payload::fail('Forbidden: insufficient permissions', [], 'E_FORBIDDEN', 403); } if (!(bool) ($conversation['integration_is_connected'] ?? false)) { return Payload::fail('Integration is not connected', [], 'E_VALIDATE', 400); } $chatId = (string) ($conversation['conversation_external_id'] ?? ''); if ($chatId === '') { return Payload::fail('Conversation is missing external chat id', [], 'E_VALIDATE', 400); } $response = $this->unipileClient->sendTextMessage($chatId, $text); $message = $this->messagesModel->createOutboundLocalMessage($conversationId, $response, $text); $this->messagesModel->updateConversationAfterOutbound($conversationId, $text); return Payload::ok([ 'message_id' => (int) ($message['message_id'] ?? 0), 'unipile' => $response, ]); } catch (\Throwable $e) { Logger::error('Failed to send WhatsApp message through Unipile', ['error' => $e->getMessage()]); return Payload::fail('Failed to send message', [], 'E_GENERIC', 500); } } private function canSend(int $companyId, string $userEmail, string $userRole, array $conversation): bool { if (in_array($userRole, [Roles::ADMIN, Roles::MANAGER], true)) { return true; } if ($userRole !== Roles::OPERATOR) { return false; } $operatorId = $this->integrationsModel->getOperatorIdByUserEmail($companyId, $userEmail); if ($operatorId <= 0) { return false; } return (int) ($conversation['operator_id'] ?? 0) === $operatorId; } }