From b4c4900635031213e8d436c46e4fa0bbd4a2e3f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Mon, 13 Jul 2026 07:17:38 +0300 Subject: [PATCH] =?UTF-8?q?feat(chat):=20=D0=BE=D0=BF=D1=80=D0=BE=D1=81=20?= =?UTF-8?q?=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D0=BE=D0=B2=20=D0=B8=20=D0=B2?= =?UTF-8?q?=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Http/Controllers/Api/ChatController.php | 25 +++++++++++++++ app/routes/web.php | 2 ++ app/tests/Feature/Bot/ChatEndpointTest.php | 31 +++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/app/app/Http/Controllers/Api/ChatController.php b/app/app/Http/Controllers/Api/ChatController.php index 3a125cc2..1273416a 100644 --- a/app/app/Http/Controllers/Api/ChatController.php +++ b/app/app/Http/Controllers/Api/ChatController.php @@ -47,4 +47,29 @@ class ChatController extends Controller return response()->json(['message_id' => (int) $row->id]); } + + /** + * Опрос окошка: «что нового в разговоре после реплики N?». + * after=0 → вся переписка (клиент перезагрузил страницу и вернулся). + */ + public function messages(Request $request, string $chat): JsonResponse + { + $after = max(0, (int) $request->query('after', '0')); + + $rows = BotDialog::query() + ->where('chat_id', $chat) + ->where('id', '>', $after) + ->orderBy('id') + ->limit(100) + ->get(['id', 'direction', 'message', 'created_at']); + + return response()->json([ + 'messages' => $rows->map(fn (BotDialog $d) => [ + 'id' => (int) $d->id, + 'direction' => $d->direction, + 'text' => $d->message, + 'at' => $d->created_at?->toIso8601String(), + ])->all(), + ]); + } } diff --git a/app/routes/web.php b/app/routes/web.php index 1140cca2..4b471637 100644 --- a/app/routes/web.php +++ b/app/routes/web.php @@ -386,6 +386,8 @@ Route::post('/api/webhook/supplier/{secret}', 'App\Http\Controllers\Api\Supplier // Свой чат: окошко на портале и лендинге (спека 2026-07-13-own-chat-widget-design §5). // Публичный адрес — лендинг открыт всему интернету; защита от накрутки — ChatRateLimiter. Route::post('/api/chat/message', 'App\Http\Controllers\Api\ChatController@send'); +Route::get('/api/chat/{chat}/messages', 'App\Http\Controllers\Api\ChatController@messages') + ->where('chat', '[a-f0-9]{32}'); // Платёжный webhook (ЮKassa). Публичный, под маской api/webhook/* → CSRF-exempt. // Подлинность — server-to-server сверкой статуса (не доверяем телу). Plan billing-yookassa Task 7. diff --git a/app/tests/Feature/Bot/ChatEndpointTest.php b/app/tests/Feature/Bot/ChatEndpointTest.php index 00ab4dc1..1e0733b5 100644 --- a/app/tests/Feature/Bot/ChatEndpointTest.php +++ b/app/tests/Feature/Bot/ChatEndpointTest.php @@ -44,3 +44,34 @@ it('не принимает кривой номер разговора', functio 'text' => 'привет', ])->assertStatus(422); }); + +it('отдаёт ответ бота, появившийся после сообщения клиента', function () { + $chat = str_repeat('c', 32); + $in = BotDialog::create(['chat_id' => $chat, 'direction' => 'in', 'message' => 'привет', 'created_at' => now()]); + $out = BotDialog::create(['chat_id' => $chat, 'direction' => 'out', 'message' => 'Здравствуйте!', 'created_at' => now()]); + + $response = $this->getJson("/api/chat/{$chat}/messages?after={$in->id}"); + + $response->assertOk(); + expect($response->json('messages'))->toHaveCount(1) + ->and($response->json('messages.0.direction'))->toBe('out') + ->and($response->json('messages.0.text'))->toBe('Здравствуйте!') + ->and($response->json('messages.0.id'))->toBe((int) $out->id); +}); + +it('отдаёт всю переписку разговора, когда окошко открыли заново', function () { + $chat = str_repeat('d', 32); + BotDialog::create(['chat_id' => $chat, 'direction' => 'in', 'message' => 'вопрос', 'created_at' => now()]); + BotDialog::create(['chat_id' => $chat, 'direction' => 'out', 'message' => 'ответ', 'created_at' => now()]); + + $response = $this->getJson("/api/chat/{$chat}/messages?after=0"); + + expect($response->json('messages'))->toHaveCount(2); +}); + +it('не отдаёт чужой разговор', function () { + $mine = str_repeat('e', 32); + BotDialog::create(['chat_id' => str_repeat('f', 32), 'direction' => 'out', 'message' => 'чужое', 'created_at' => now()]); + + expect($this->getJson("/api/chat/{$mine}/messages?after=0")->json('messages'))->toBe([]); +});