Kaynağa Gözat

fix: unipile now connects whatsapp

EduLascala 1 hafta önce
ebeveyn
işleme
8304adde65

+ 27 - 2
libs/Database.php

@@ -14,14 +14,34 @@ namespace Libs;
 final class Database
 {
     private static ?\PDO $connection = null;
+    private static float $lastCheckedAt = 0.0;
+
+    /**
+     * Intervalo mínimo entre validações de "liveness" da conexão.
+     * Dentro de uma mesma requisição (que faz várias queries em sequência),
+     * evita repetir o round-trip de "SELECT 1" a cada chamada de pdo().
+     */
+    private const HEALTHCHECK_INTERVAL_SECONDS = 2.0;
 
     public static function pdo(): \PDO
     {
-        if (self::$connection instanceof \PDO && self::isAlive(self::$connection)) {
-            return self::$connection;
+        if (self::$connection instanceof \PDO) {
+            $now = microtime(true);
+
+            // Só revalida a conexão se passou o intervalo mínimo desde a
+            // última checagem; caso contrário reaproveita direto.
+            if ($now - self::$lastCheckedAt < self::HEALTHCHECK_INTERVAL_SECONDS) {
+                return self::$connection;
+            }
+
+            if (self::isAlive(self::$connection)) {
+                self::$lastCheckedAt = $now;
+                return self::$connection;
+            }
         }
 
         self::$connection = self::connect();
+        self::$lastCheckedAt = microtime(true);
 
         return self::$connection;
     }
@@ -65,6 +85,11 @@ final class Database
                 \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
                 \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
                 \PDO::ATTR_PERSISTENT => true,
+                // Emula prepares no cliente: evita o round-trip extra de
+                // Parse/Describe server-side a cada query. Com o banco remoto
+                // (~160ms de latência por ida-e-volta), isso reduz o custo de
+                // cada query de ~3 round-trips para 1.
+                \PDO::ATTR_EMULATE_PREPARES => true,
             ]);
         } catch (\PDOException $e) {
             Logger::error('Failed to connect to database', [

+ 29 - 5
models/DashboardOverviewModel.php

@@ -36,7 +36,7 @@ class DashboardOverviewModel
         $sentiment = strtolower((string) ($queryParams['sentiment'] ?? 'all'));
         $volumeView = strtolower((string) ($queryParams['volume_view'] ?? 'day'));
 
-        if (!in_array($period, ['today', 'yesterday', 'week'], true)) {
+        if (!in_array($period, ['today', 'yesterday', 'week', 'all_time'], true)) {
             $period = 'week';
         }
 
@@ -59,6 +59,18 @@ class DashboardOverviewModel
 
     private function resolveDateRange(string $period): array
     {
+        // "all_time" remove o recorte temporal usando uma faixa larga o
+        // suficiente para abranger todos os registros, mantendo as queries
+        // (que sempre filtram por data) inalteradas.
+        if ($period === 'all_time') {
+            return [
+                'start_date' => '1970-01-01',
+                'end_date' => '9999-12-31',
+                'start_datetime' => '1970-01-01 00:00:00',
+                'end_exclusive_datetime' => '9999-12-31 00:00:00',
+            ];
+        }
+
         $today = new \DateTimeImmutable('today');
 
         if ($period === 'today') {
@@ -87,18 +99,30 @@ class DashboardOverviewModel
             FROM client
             WHERE company_id = :company_id
               AND client_deleted_at = 'infinity'
-              AND client_is_registered = TRUE"
+              AND client_is_registered = TRUE
+              AND client_created_at >= :start_datetime
+              AND client_created_at < :end_exclusive_datetime"
         );
-        $registeredStmt->execute(['company_id' => $companyId]);
+        $registeredStmt->execute([
+            'company_id' => $companyId,
+            'start_datetime' => $range['start_datetime'],
+            'end_exclusive_datetime' => $range['end_exclusive_datetime'],
+        ]);
 
         $unregisteredStmt = $this->pdo->prepare(
             "SELECT COUNT(*)
             FROM client
             WHERE company_id = :company_id
               AND client_deleted_at = 'infinity'
-              AND client_is_registered = FALSE"
+              AND client_is_registered = FALSE
+              AND client_created_at >= :start_datetime
+              AND client_created_at < :end_exclusive_datetime"
         );
-        $unregisteredStmt->execute(['company_id' => $companyId]);
+        $unregisteredStmt->execute([
+            'company_id' => $companyId,
+            'start_datetime' => $range['start_datetime'],
+            'end_exclusive_datetime' => $range['end_exclusive_datetime'],
+        ]);
 
         $activeOperatorsSql = "SELECT COUNT(*)
             FROM operator

+ 0 - 1
models/InteractionDetailsModel.php

@@ -92,7 +92,6 @@ class InteractionDetailsModel
                 message_sent_at
             FROM message
             WHERE conversation_id = :conversation_id
-              AND message_deleted_at = 'infinity'
               AND message_deleted = FALSE
               AND message_hidden = FALSE
               AND message_is_event = FALSE

+ 12 - 0
models/InteractionsModel.php

@@ -89,6 +89,18 @@ class InteractionsModel
             "c.company_id = :company_id",
             "c.conversation_deleted_at = 'infinity'",
             "cl.client_deleted_at = 'infinity'",
+            // Oculta conversas sem nenhuma mensagem visível, evitando exibir
+            // conversas "vazias" na listagem. Verifica apenas os flags de
+            // visibilidade da mensagem (não o sentinela de soft-delete
+            // message_deleted_at, que o seed atual não preenche com 'infinity').
+            "EXISTS (
+                SELECT 1
+                FROM message m
+                WHERE m.conversation_id = c.conversation_id
+                  AND m.message_deleted = FALSE
+                  AND m.message_hidden = FALSE
+                  AND m.message_is_event = FALSE
+            )",
         ];
         $params = ['company_id' => $companyId];
 

+ 1 - 1
routes/Dispatcher.php

@@ -59,7 +59,7 @@ final class Dispatcher
         $app->get('/v1/analytics/sentiment/dashboard', $auth, \Controllers\AnalyticsSentimentDashboardController::class);
         $app->get('/v1/personas/overview', $auth, \Controllers\PersonasOverviewController::class);
         $app->get('/v1/evolution/overview', $auth, \Controllers\EvolutionOverviewController::class);
-        $app->get('/v1/executive/dashboard', $auth, \Controllers\ExecutiveDashboardController::class);
+        $app->get('/v1/executive/dashboard', $auth, new RoleMiddleware(Roles::ADMIN), \Controllers\ExecutiveDashboardController::class);
 
         // SLA.
         $app->get('/v1/sla/configs', $auth, \Controllers\SlaConfigsController::class);