From 4d3fdf890e95e5cdd9b8f6e9fe71db57e52c8b2e Mon Sep 17 00:00:00 2001 From: Michela Date: Sun, 5 Apr 2026 13:16:39 +0200 Subject: [PATCH] Subscriber can be temporary paused and restored By API --- app/Http/Controllers/SubscriberController.php | 88 +++++++++++++++++++ app/Jobs/SendMessageJob.php | 9 ++ config/cache.php | 4 +- routes/api.php | 2 + tests/Feature/SubscriberTest.php | 67 ++++++++++++++ tests/Integration/MessageTest.php | 17 ++++ 6 files changed, 186 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/SubscriberController.php b/app/Http/Controllers/SubscriberController.php index 9f13691..29afb34 100644 --- a/app/Http/Controllers/SubscriberController.php +++ b/app/Http/Controllers/SubscriberController.php @@ -6,6 +6,8 @@ use App\Http\Requests\SubscriberRequest; use App\Http\Resources\SubscriberResource; use App\Http\Repositories\RepositoryInterface; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Config; class SubscriberController extends Controller { @@ -232,4 +234,90 @@ public function getSubscriber($id) } return new SubscriberResource($subscriber); } + + /** + * @OA\Get( + * path="/api/subscribers/{id}/pause", + * summary="Pauses a subscriber from receiving messages", + * tags={"subscribers"}, + * security={{"passport":{}}}, + * description="Use to pauses a subscriber from receiving messages for 1 hour", + * operationId="SubscriberController.pauseSubscriber", + * @OA\Parameter( + * in="path", + * required=true, + * description="Subscriber id", + * name="id", + * @OA\Schema( + * type="integer", + * minimum=1 + * ) + * ), + * @OA\Response( + * response=200, + * ref="#/components/responses/Success200" + * ), + * @OA\Response( + * response=404, + * ref="#/components/responses/Error404" + * ) + * ) + * + * Response to route /api/subscribers/{id}/pause + * + * @param int $id + * + */ + public function pauseSubscriber($id) + { + $subscriber = $this->subscriberRepository->find($id);; + if (!$subscriber) { + return response()->error404(__('messages.Subscriber') . $id); + } + Cache::put(Config::get('cache.publisher_paused_key_prefix') . $id, true, 3600); + return response()->success204(); + } + + /** + * @OA\Get( + * path="/api/subscribers/{id}/restore", + * summary="Restore a subscriber from receiving messages", + * tags={"subscribers"}, + * security={{"passport":{}}}, + * description="Use to restore a subscriber from receiving messages", + * operationId="SubscriberController.restoreSubscriber", + * @OA\Parameter( + * in="path", + * required=true, + * description="Subscriber id", + * name="id", + * @OA\Schema( + * type="integer", + * minimum=1 + * ) + * ), + * @OA\Response( + * response=200, + * ref="#/components/responses/Success200" + * ), + * @OA\Response( + * response=404, + * ref="#/components/responses/Error404" + * ) + * ) + * + * Response to route /api/subscribers/{id}/restore + * + * @param int $id + * + */ + public function restoreSubscriber($id) + { + $subscriber = $this->subscriberRepository->find($id);; + if (!$subscriber) { + return response()->error404(__('messages.Subscriber') . $id); + } + Cache::forget(Config::get('cache.publisher_paused_key_prefix') . $id); + return response()->success204(); + } } diff --git a/app/Jobs/SendMessageJob.php b/app/Jobs/SendMessageJob.php index aeae17e..120cd9d 100644 --- a/app/Jobs/SendMessageJob.php +++ b/app/Jobs/SendMessageJob.php @@ -11,6 +11,8 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Config; class SendMessageJob implements ShouldQueue { @@ -75,6 +77,13 @@ public function handle(GuzzleService $guzzleService) "authentication" => $this->event->channelSubscribe->authentication, ]); + $pausedKey = Config::get('cache.publisher_paused_key_prefix') . $this->event->channelSubscribe->subscriber_id; + if (Cache::has($pausedKey)) { + Log::warning("Message paused"); + $this->fail("Subscriber Paused"); + return; + } + $this->startTime = microtime(true); switch ($this->event->channelSubscribe->authentication) { case Authentication::BASIC: diff --git a/config/cache.php b/config/cache.php index 910bd25..3925193 100644 --- a/config/cache.php +++ b/config/cache.php @@ -104,6 +104,8 @@ */ 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache:'), - 'channel_key_prefix' => "channel." + 'channel_key_prefix' => "channel.", + 'publisher_paused_key_prefix' => "publisher_paused." + ]; diff --git a/routes/api.php b/routes/api.php index 8787ab0..f899b55 100644 --- a/routes/api.php +++ b/routes/api.php @@ -26,6 +26,8 @@ Route::get('/', 'SubscriberController@getList'); Route::post('/', 'SubscriberController@store'); Route::get('{id}', 'SubscriberController@getSubscriber')->where('id', '[0-9]+'); + Route::get('{id}/pause', 'SubscriberController@pauseSubscriber')->where('id', '[0-9]+'); + Route::get('{id}/restore', 'SubscriberController@restoreSubscriber')->where('id', '[0-9]+'); Route::delete('{id}', 'SubscriberController@destroy')->where('id', '[0-9]+'); Route::get('{id}/channels', 'ChannelSubscribeController@getChannelSubscribe')->where('id', '[0-9]+'); diff --git a/tests/Feature/SubscriberTest.php b/tests/Feature/SubscriberTest.php index f01ca5c..33822f8 100644 --- a/tests/Feature/SubscriberTest.php +++ b/tests/Feature/SubscriberTest.php @@ -6,6 +6,9 @@ use App\Models\Subscriber; use App\Http\Repositories\SubscriberRepository; use Tests\TestCaseWithoutMiddleware; +use Illuminate\Http\Response; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Config; class SubscriberTest extends TestCaseWithoutMiddleware { @@ -169,4 +172,68 @@ public function testDestroy() $response = $this->json('DELETE', '/api/subscribers/1'); $response->assertStatus(200); } + + public function testPause() + { + Cache::spy(); + $subscriber = factory(Subscriber::class)->make(); + $subscriber->id = 1; + $mock = Mockery::mock(SubscriberRepository::class)->makePartial() + ->shouldReceive([ + "find" => $subscriber, + ]) + ->withAnyArgs() + ->once() + ->getMock(); + $this->app->instance('App\Http\Repositories\SubscriberRepository', $mock); + $response = $this->json('GET', '/api/subscribers/1/pause'); + $response->assertStatus(Response::HTTP_NO_CONTENT); + Cache::shouldHaveReceived('put')->with(Config::get('cache.publisher_paused_key_prefix') . 1, true, 3600); + } + + public function testPauseNotFound() + { + $mock = Mockery::mock(SubscriberRepository::class)->makePartial() + ->shouldReceive([ + "find" => null, + ]) + ->withAnyArgs() + ->once() + ->getMock(); + $this->app->instance('App\Http\Repositories\SubscriberRepository', $mock); + $response = $this->json('GET', '/api/subscribers/1/pause'); + $response->assertStatus(Response::HTTP_NOT_FOUND); + } + + public function testRestore() + { + Cache::spy(); + $subscriber = factory(Subscriber::class)->make(); + $subscriber->id = 1; + $mock = Mockery::mock(SubscriberRepository::class)->makePartial() + ->shouldReceive([ + "find" => $subscriber, + ]) + ->withAnyArgs() + ->once() + ->getMock(); + $this->app->instance('App\Http\Repositories\SubscriberRepository', $mock); + $response = $this->json('GET', '/api/subscribers/1/restore'); + $response->assertStatus(Response::HTTP_NO_CONTENT); + Cache::shouldHaveReceived('forget')->with(Config::get('cache.publisher_paused_key_prefix') . 1); + } + + public function testRestoreNotFound() + { + $mock = Mockery::mock(SubscriberRepository::class)->makePartial() + ->shouldReceive([ + "find" => null, + ]) + ->withAnyArgs() + ->once() + ->getMock(); + $this->app->instance('App\Http\Repositories\SubscriberRepository', $mock); + $response = $this->json('GET', '/api/subscribers/1/restore'); + $response->assertStatus(Response::HTTP_NOT_FOUND); + } } diff --git a/tests/Integration/MessageTest.php b/tests/Integration/MessageTest.php index b96d7ff..81c11b5 100644 --- a/tests/Integration/MessageTest.php +++ b/tests/Integration/MessageTest.php @@ -2,7 +2,9 @@ namespace Tests\Integration; +use App\Events\SendMessageEvent; use Mockery; +use App\Models\Message; use App\Models\Channel; use App\Models\Publisher; use Illuminate\Support\Str; @@ -13,6 +15,8 @@ use Illuminate\Foundation\Testing\TestCase; use Illuminate\Support\Facades\Queue; use Tests\CreatesApplication; +use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Config; class MessageTest extends TestCase { @@ -191,4 +195,17 @@ public function testBasicAuthFailedAuth() $response = $this->json('POST', '/api/sendMessage/' . Str::random(10), [], $authorization); $response->assertStatus(401); } + + public function testSendMessagePaused() + { + $channel = factory(Channel::class)->create(); + $publisher = factory(Publisher::class)->create(['password' => bcrypt(self::PUBLISHER_PASSWORD)]); + factory(ChannelPublish::class)->create(['channel_id' => $channel->id, 'publisher_id' => $publisher->id]); + $channelSubscribe = factory(ChannelSubscribe::class)->create(['channel_id' => $channel->id]); + Cache::put(Config::get('cache.publisher_paused_key_prefix') . $channelSubscribe->subscriber_id, true, 3600); + $job = (new SendMessageJob(new SendMessageEvent(new Message("hello"), "http://prova.com", $channelSubscribe, "low", "test", "test")))->withFakeQueueInteractions(); + + $job->handle(new GuzzleService()); + $job->assertFailed(); + } }