feat(chat): журнал разговоров переезжает с Jivo на свой чат

jivo_chat_id -> chat_id + добавлены source/user_id/ip (спека
2026-07-13-own-chat-widget-design §5). Историческая миграция создания
таблицы не тронута.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-07-13 06:45:54 +03:00
parent 9cd12f179f
commit 197e771a4f
4 changed files with 57 additions and 9 deletions
+5 -5
View File
@@ -56,7 +56,7 @@ class ProcessJivoMessageJob implements ShouldQueue
// Память разговора — ДО записи текущего сообщения (иначе оно попадёт в историю).
// Служебные хвосты «👉 Показать на портале: …» из прошлых ответов LLM не нужны.
$history = BotDialog::query()
->where('jivo_chat_id', $this->chatId)
->where('chat_id', $this->chatId)
->orderByDesc('id')
->limit(self::HISTORY_LIMIT)
->get(['direction', 'message'])
@@ -69,7 +69,7 @@ class ProcessJivoMessageJob implements ShouldQueue
->all();
BotDialog::create([
'jivo_chat_id' => $this->chatId,
'chat_id' => $this->chatId,
'direction' => 'in',
'message' => $this->text,
'created_at' => now(),
@@ -85,7 +85,7 @@ class ProcessJivoMessageJob implements ShouldQueue
app(JivoBotClient::class)->sendMessage($this->chatId, $this->clientId, $answer->text);
BotDialog::create([
'jivo_chat_id' => $this->chatId,
'chat_id' => $this->chatId,
'direction' => 'out',
'message' => $answer->text,
'matched_chunks' => $answer->matchedChunkIds,
@@ -99,7 +99,7 @@ class ProcessJivoMessageJob implements ShouldQueue
private function awaitsContact(): bool
{
$lastOut = BotDialog::query()
->where('jivo_chat_id', $this->chatId)
->where('chat_id', $this->chatId)
->where('direction', 'out')
->latest('id')
->value('message');
@@ -124,7 +124,7 @@ class ProcessJivoMessageJob implements ShouldQueue
}
$rows = BotDialog::query()
->where('jivo_chat_id', $this->chatId)
->where('chat_id', $this->chatId)
->orderByDesc('id')
->limit(8)
->get(['direction', 'message'])
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
/**
* Свой чат вместо Jivo (спека 2026-07-13-own-chat-widget-design §5).
* Журнал диалогов перестаёт быть журналом Jivo: номер разговора теперь наш,
* добавляем откуда пришёл (portal/landing), кто клиент (если вошёл) и адрес.
* На проде таблицы нет (бот там не стоит) переименование безопасно.
*/
return new class extends Migration
{
public function up(): void
{
DB::statement('ALTER TABLE bot_dialogs RENAME COLUMN jivo_chat_id TO chat_id');
DB::statement("ALTER TABLE bot_dialogs ADD COLUMN IF NOT EXISTS source VARCHAR(16) NOT NULL DEFAULT 'portal'");
DB::statement('ALTER TABLE bot_dialogs ADD COLUMN IF NOT EXISTS user_id BIGINT');
DB::statement('ALTER TABLE bot_dialogs ADD COLUMN IF NOT EXISTS ip VARCHAR(45)');
// Опрос окошка: «дай реплики этого разговора после id N».
DB::statement('CREATE INDEX IF NOT EXISTS idx_bot_dialogs_chat_seq ON bot_dialogs (chat_id, id)');
}
public function down(): void
{
DB::statement('DROP INDEX IF EXISTS idx_bot_dialogs_chat_seq');
DB::statement('ALTER TABLE bot_dialogs DROP COLUMN IF EXISTS ip');
DB::statement('ALTER TABLE bot_dialogs DROP COLUMN IF EXISTS user_id');
DB::statement('ALTER TABLE bot_dialogs DROP COLUMN IF EXISTS source');
DB::statement('ALTER TABLE bot_dialogs RENAME COLUMN chat_id TO jivo_chat_id');
}
};
+17 -3
View File
@@ -9,13 +9,13 @@ uses(RefreshDatabase::class);
it('bot_dialogs принимает запись входа и выхода', function () {
DB::table('bot_dialogs')->insert([
'jivo_chat_id' => 'chat-1',
'chat_id' => 'chat-1',
'direction' => 'in',
'message' => 'что такое проект?',
'created_at' => now(),
]);
DB::table('bot_dialogs')->insert([
'jivo_chat_id' => 'chat-1',
'chat_id' => 'chat-1',
'direction' => 'out',
'message' => 'Проект — это…',
'matched_chunks' => json_encode([1, 2]),
@@ -24,5 +24,19 @@ it('bot_dialogs принимает запись входа и выхода', fun
'created_at' => now(),
]);
expect(DB::table('bot_dialogs')->where('jivo_chat_id', 'chat-1')->count())->toBe(2);
expect(DB::table('bot_dialogs')->where('chat_id', 'chat-1')->count())->toBe(2);
});
it('журнал разговоров хранит номер разговора, откуда пришёл и кто клиент', function () {
$columns = DB::select("
SELECT column_name FROM information_schema.columns
WHERE table_name = 'bot_dialogs'
");
$names = array_map(fn ($c) => $c->column_name, $columns);
expect($names)->toContain('chat_id')
->and($names)->toContain('source')
->and($names)->toContain('user_id')
->and($names)->toContain('ip')
->and($names)->not->toContain('jivo_chat_id');
});
+1 -1
View File
@@ -33,7 +33,7 @@ it('наша часть тракта (без сети) укладывается
$latencies = [];
for ($i = 0; $i < 10; $i++) {
(new ProcessJivoMessageJob("chat-{$i}", 'c', 'что такое проект?'))->handle();
$latencies[] = (int) BotDialog::where('jivo_chat_id', "chat-{$i}")
$latencies[] = (int) BotDialog::where('chat_id', "chat-{$i}")
->where('direction', 'out')->value('latency_ms');
}