From 98a7afd574eb4397e2aac67d4c15bf4fe6ddb4ea Mon Sep 17 00:00:00 2001 From: Stevy Endaman Date: Thu, 10 Oct 2024 00:39:28 +0200 Subject: [PATCH 1/3] feat: LAR 10 Send a Telegram notification for articles that are submitted but neither approved nor declined. --- .phpunit.cache/test-results | 1 + .../Commands/NotifyPendingArticles.php | 26 +++++++++ app/Console/Kernel.php | 3 +- .../PendingArticlesNotification.php | 58 +++++++++++++++++++ lang/en.json | 6 +- lang/fr.json | 6 +- tests/Feature/NotifyPendingArticlesTest.php | 58 +++++++++++++++++++ tests/Integration/DiscussionTest.php | 7 +-- tests/Integration/ThreadTest.php | 7 +-- 9 files changed, 157 insertions(+), 15 deletions(-) create mode 100644 .phpunit.cache/test-results create mode 100644 app/Console/Commands/NotifyPendingArticles.php create mode 100644 app/Notifications/PendingArticlesNotification.php create mode 100644 tests/Feature/NotifyPendingArticlesTest.php diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results new file mode 100644 index 00000000..a6c46b0e --- /dev/null +++ b/.phpunit.cache/test-results @@ -0,0 +1 @@ +{"version":"pest_2.35.0","defects":[],"times":{"P\\Tests\\Feature\\NotifyPendingArticlesTest::__pest_evaluable_it_will_send_a_notification_when_there_are_pending_articles":0.207,"P\\Tests\\Feature\\NotifyPendingArticlesTest::__pest_evaluable_it_will_not_send_a_notification_when_there_are_no_pending_articles":0.061,"P\\Tests\\Integration\\ChannelTest::__pest_evaluable_channel_can_have_children":0.137,"P\\Tests\\Integration\\ChannelTest::__pest_evaluable_child_channel_can_be_a_parent":0.015,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_it_can_find_by_slug":0.046,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_it_can_give_an_excerpt_of_its_body":0.057,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_html_in_excerpts_is_markdown_converted":0.016,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_it_can_have_many_tags":0.024,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_it_records_activity_when_a_discussion_is_created":0.025,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_it_generates_a_slug_when_valid_url_characters_provided":0.007,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_it_generates_a_unique_slug_when_valid_url_characters_provided":0.017,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_it_generates_a_slug_when_invalid_url_characters_provided":0.011,"P\\Tests\\Integration\\ReplyTest::__pest_evaluable_it_records_activity_when_a_reply_is_send":0.004,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_can_find_by_slug":0.013,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_can_give_an_excerpt_of_its_body":0.006,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_html_in_excerpts_is_markdown_converted":0.017,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_can_have_many_channels":0.003,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_records_activity_when_a_thread_is_created":0.003,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_its_conversation_is_old_when_the_oldest_reply_was_six_months_ago":0.049,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_its_conversation_is_old_when_there_are_no_replies_but_the_creation_date_was_six_months_ago":0.021,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_we_can_mark_and_unmark_a_reply_as_the_solution":0.065,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_can_retrieve_the_latest_threads_in_a_correct_order":0.053,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_can_retrieve_only_resolved_threads":0.056,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_can_retrieve_only_active_threads":0.052,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_generates_a_slug_when_valid_url_characters_provided":0.008,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_generates_a_unique_slug_when_valid_url_characters_provided":0.019,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_generates_a_slug_when_invalid_url_characters_provided":0.012,"P\\Tests\\Feature\\Cleanup\\DeleteOldUnverifiedUsersTest::__pest_evaluable_it_will_delete_unverified_users_after_some_days":0.387,"P\\Tests\\Feature\\Cleanup\\DeleteOldUnverifiedUsersTest::__pest_evaluable_it_will_not_delete_verified_users":0.014,"P\\Tests\\Feature\\UserActivitiesTest::__pest_evaluable_it_records_activity_when_an_article_is_created":0.002,"P\\Tests\\Feature\\UserActivitiesTest::__pest_evaluable_it_get_feed_from_any_user":0.002}} \ No newline at end of file diff --git a/app/Console/Commands/NotifyPendingArticles.php b/app/Console/Commands/NotifyPendingArticles.php new file mode 100644 index 00000000..b34572da --- /dev/null +++ b/app/Console/Commands/NotifyPendingArticles.php @@ -0,0 +1,26 @@ +get(); + + if ($pendingArticles->isNotEmpty()) { + $notifiable->notify(new PendingArticlesNotification($pendingArticles)); + } + } +} \ No newline at end of file diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index ad9992d9..4f357d45 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -23,6 +23,7 @@ protected function schedule(Schedule $schedule): void $schedule->command('lcm:post-article-to-telegram')->everyFourHours(); $schedule->command('lcm:send-unverified-mails')->weeklyOn(1, '8:00'); $schedule->command('sitemap:generate')->daily(); + $schedule->command('lcm:notify-pending-articles')->everyTwoDays(); } } @@ -30,4 +31,4 @@ protected function commands(): void { $this->load(__DIR__.'/Commands'); } -} +} \ No newline at end of file diff --git a/app/Notifications/PendingArticlesNotification.php b/app/Notifications/PendingArticlesNotification.php new file mode 100644 index 00000000..74f2acad --- /dev/null +++ b/app/Notifications/PendingArticlesNotification.php @@ -0,0 +1,58 @@ +content(); + + return TelegramMessage::create() + ->to(config('services.telegram-bot-api.channel')) + ->content($message); + } + + private function content(): string + { + $message = __("Pending approval articles:\n"); + foreach ($this->pendingArticles as $article) { + $url = route('articles.show', $article->slug); + + $message .= __("• Title: [:title](:url)\n", [ + 'title' => $article->title, + 'url' => $url, + ]); + + $message .= __("• By: [@:username](:profile_url)\n", [ + 'username' => $article->user?->username, + 'profile_url' => route('profile', $article->user?->username), + ]); + + $message .= __("• Submitted on: :date\n\n", [ + 'date' => $article->submitted_at->translatedFormat('d/m/Y'), + ]); + } + + return $message; + } +} \ No newline at end of file diff --git a/lang/en.json b/lang/en.json index a41ce909..b5079794 100644 --- a/lang/en.json +++ b/lang/en.json @@ -3,5 +3,9 @@ "The given :attribute has appeared in a data leak. Please choose a different :attribute.": "The given :attribute has appeared in a data leak. Please choose a different :attribute.", "Verify Email Address": "Verify Email Address", "Please click the button below to verify your email address.": "Please click the button below to verify your email address.", - "If you did not create an account, no further action is required.": "If you did not create an account, no further action is required." + "If you did not create an account, no further action is required.": "If you did not create an account, no further action is required.", + "Pending approval articles:" : "Pending approval articles:", + "• Title: [:title](:url)" : "• Title: [:title](:url)", + "• By: [@:username](:profile_url)" : "• By: [@:username](:profile_url)", + "• Submitted on: :date" : "• Submitted on: :date" } diff --git a/lang/fr.json b/lang/fr.json index bbd27e39..5fa54319 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -3,5 +3,9 @@ "The given :attribute has appeared in a data leak. Please choose a different :attribute.": "Le champ :attribute donné est apparu dans une fuite de données. Veuillez choisir un autre :attribute.", "Verify Email Address": "Vérifier l'adresse e-mail", "Please click the button below to verify your email address.": "Veuillez cliquer sur le bouton ci-dessous pour vérifier votre adresse email.", - "If you did not create an account, no further action is required.": "Si vous n'avez pas créé de compte, aucune autre action n'est requise." + "If you did not create an account, no further action is required.": "Si vous n'avez pas créé de compte, aucune autre action n'est requise.", + "Pending approval articles:" : "Articles soumis en attente d'approbation:", + "• Title: [:title](:url)" : "• Titre: [:title](:url)", + "• By: [@:username](:profile_url)" : "• Par: [@:username](:profile_url)", + "• Submitted on: :date" : "• Soumis le: :date" } diff --git a/tests/Feature/NotifyPendingArticlesTest.php b/tests/Feature/NotifyPendingArticlesTest.php new file mode 100644 index 00000000..fb067f5e --- /dev/null +++ b/tests/Feature/NotifyPendingArticlesTest.php @@ -0,0 +1,58 @@ + Notification::fake()); + +it('will send a notification when there are pending articles', function (): void { + Article::factory()->createMany([ + [ + 'submitted_at' => now(), + ], + [ + 'submitted_at' => now()->subDay(), + 'approved_at' => now(), + ], + [ + 'submitted_at' => now()->subDay(), + 'declined_at' => now(), + ], + ]); + + $this->assertDatabaseCount('articles', 3); + + $this->artisan(NotifyPendingArticles::class)->assertExitCode(0); + + Notification::assertSentTo( + new AnonymousNotifiable(), + PendingArticlesNotification::class, + fn($notification) => $notification->pendingArticles->count() === 1 + ); + + Notification::assertCount(1); +}); + +it('will not send a notification when there are no pending articles', function (): void { + Article::factory()->createMany([ + [ + 'submitted_at' => now()->subDay(), + 'approved_at' => now(), + ], + [ + 'submitted_at' => now()->subDay(), + 'declined_at' => now(), + ], + ]); + + $this->assertDatabaseCount('articles', 2); + $this->artisan(NotifyPendingArticles::class)->assertExitCode(0); + + Notification::assertNothingSent(); + Notification::assertCount(0); +}); \ No newline at end of file diff --git a/tests/Integration/DiscussionTest.php b/tests/Integration/DiscussionTest.php index b9ec428e..28bd459b 100644 --- a/tests/Integration/DiscussionTest.php +++ b/tests/Integration/DiscussionTest.php @@ -5,11 +5,6 @@ use App\Models\Activity; use App\Models\Discussion; use App\Models\Tag; -use Illuminate\Foundation\Testing\DatabaseMigrations; -use Illuminate\Foundation\Testing\RefreshDatabase; - -uses(RefreshDatabase::class); -uses(DatabaseMigrations::class); it('can find by slug', function (): void { Discussion::factory()->create(['slug' => 'foo']); @@ -74,4 +69,4 @@ // When providing a slug with invalid url characters, a random 5 character string is returned. expect($discussion->slug())->toMatch('/\w{5}/'); -}); +}); \ No newline at end of file diff --git a/tests/Integration/ThreadTest.php b/tests/Integration/ThreadTest.php index 71a0c0cd..0aaefeaa 100644 --- a/tests/Integration/ThreadTest.php +++ b/tests/Integration/ThreadTest.php @@ -8,11 +8,6 @@ use App\Models\Thread; use App\Models\User; use Carbon\Carbon; -use Illuminate\Foundation\Testing\DatabaseMigrations; -use Illuminate\Foundation\Testing\RefreshDatabase; - -uses(RefreshDatabase::class); -uses(DatabaseMigrations::class); it('can find by slug', function (): void { Thread::factory()->create(['slug' => 'foo']); @@ -192,4 +187,4 @@ function createActiveThread(): Thread $reply->save(); return $thread; -} +} \ No newline at end of file From 4c3e813adcc48f2a63ec0ae5e3b13a639a41a51b Mon Sep 17 00:00:00 2001 From: Stevy Endaman Date: Thu, 10 Oct 2024 14:38:42 +0200 Subject: [PATCH 2/3] refactor: (LAR-10) refactoring some file --- .phpunit.cache/test-results | 1 - .../PendingArticlesNotification.php | 26 +++++++------------ lang/en.json | 6 ++--- lang/fr.json | 6 ++--- tests/Feature/NotifyPendingArticlesTest.php | 9 ------- 5 files changed, 14 insertions(+), 34 deletions(-) delete mode 100644 .phpunit.cache/test-results diff --git a/.phpunit.cache/test-results b/.phpunit.cache/test-results deleted file mode 100644 index a6c46b0e..00000000 --- a/.phpunit.cache/test-results +++ /dev/null @@ -1 +0,0 @@ -{"version":"pest_2.35.0","defects":[],"times":{"P\\Tests\\Feature\\NotifyPendingArticlesTest::__pest_evaluable_it_will_send_a_notification_when_there_are_pending_articles":0.207,"P\\Tests\\Feature\\NotifyPendingArticlesTest::__pest_evaluable_it_will_not_send_a_notification_when_there_are_no_pending_articles":0.061,"P\\Tests\\Integration\\ChannelTest::__pest_evaluable_channel_can_have_children":0.137,"P\\Tests\\Integration\\ChannelTest::__pest_evaluable_child_channel_can_be_a_parent":0.015,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_it_can_find_by_slug":0.046,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_it_can_give_an_excerpt_of_its_body":0.057,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_html_in_excerpts_is_markdown_converted":0.016,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_it_can_have_many_tags":0.024,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_it_records_activity_when_a_discussion_is_created":0.025,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_it_generates_a_slug_when_valid_url_characters_provided":0.007,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_it_generates_a_unique_slug_when_valid_url_characters_provided":0.017,"P\\Tests\\Integration\\DiscussionTest::__pest_evaluable_it_generates_a_slug_when_invalid_url_characters_provided":0.011,"P\\Tests\\Integration\\ReplyTest::__pest_evaluable_it_records_activity_when_a_reply_is_send":0.004,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_can_find_by_slug":0.013,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_can_give_an_excerpt_of_its_body":0.006,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_html_in_excerpts_is_markdown_converted":0.017,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_can_have_many_channels":0.003,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_records_activity_when_a_thread_is_created":0.003,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_its_conversation_is_old_when_the_oldest_reply_was_six_months_ago":0.049,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_its_conversation_is_old_when_there_are_no_replies_but_the_creation_date_was_six_months_ago":0.021,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_we_can_mark_and_unmark_a_reply_as_the_solution":0.065,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_can_retrieve_the_latest_threads_in_a_correct_order":0.053,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_can_retrieve_only_resolved_threads":0.056,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_can_retrieve_only_active_threads":0.052,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_generates_a_slug_when_valid_url_characters_provided":0.008,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_generates_a_unique_slug_when_valid_url_characters_provided":0.019,"P\\Tests\\Integration\\ThreadTest::__pest_evaluable_it_generates_a_slug_when_invalid_url_characters_provided":0.012,"P\\Tests\\Feature\\Cleanup\\DeleteOldUnverifiedUsersTest::__pest_evaluable_it_will_delete_unverified_users_after_some_days":0.387,"P\\Tests\\Feature\\Cleanup\\DeleteOldUnverifiedUsersTest::__pest_evaluable_it_will_not_delete_verified_users":0.014,"P\\Tests\\Feature\\UserActivitiesTest::__pest_evaluable_it_records_activity_when_an_article_is_created":0.002,"P\\Tests\\Feature\\UserActivitiesTest::__pest_evaluable_it_get_feed_from_any_user":0.002}} \ No newline at end of file diff --git a/app/Notifications/PendingArticlesNotification.php b/app/Notifications/PendingArticlesNotification.php index 74f2acad..9288cd45 100644 --- a/app/Notifications/PendingArticlesNotification.php +++ b/app/Notifications/PendingArticlesNotification.php @@ -34,23 +34,17 @@ public function toTelegram(): TelegramMessage private function content(): string { - $message = __("Pending approval articles:\n"); + $message = __("Pending approval articles:\n\n"); foreach ($this->pendingArticles as $article) { - $url = route('articles.show', $article->slug); - - $message .= __("• Title: [:title](:url)\n", [ - 'title' => $article->title, - 'url' => $url, - ]); - - $message .= __("• By: [@:username](:profile_url)\n", [ - 'username' => $article->user?->username, - 'profile_url' => route('profile', $article->user?->username), - ]); - - $message .= __("• Submitted on: :date\n\n", [ - 'date' => $article->submitted_at->translatedFormat('d/m/Y'), - ]); + $message .= __( + "[@:username](:profile_url) submitted the article [:title](:url) on: :date\n\n", [ + 'username' => $article->user?->username, + 'profile_url' => route('profile', $article->user?->username), + 'title' => $article->title, + 'url' => route('articles.show', $article->slug), + 'date' => $article->submitted_at->translatedFormat('d/m/Y') + ] + ); } return $message; diff --git a/lang/en.json b/lang/en.json index b5079794..4ebcf659 100644 --- a/lang/en.json +++ b/lang/en.json @@ -5,7 +5,5 @@ "Please click the button below to verify your email address.": "Please click the button below to verify your email address.", "If you did not create an account, no further action is required.": "If you did not create an account, no further action is required.", "Pending approval articles:" : "Pending approval articles:", - "• Title: [:title](:url)" : "• Title: [:title](:url)", - "• By: [@:username](:profile_url)" : "• By: [@:username](:profile_url)", - "• Submitted on: :date" : "• Submitted on: :date" -} + "[@:username](:profile_url) submitted the article [:title](:url) on: :date" : "[@:username](:profile_url) submitted the article [:title](:url) on: :date" +} \ No newline at end of file diff --git a/lang/fr.json b/lang/fr.json index 5fa54319..083b53b1 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -5,7 +5,5 @@ "Please click the button below to verify your email address.": "Veuillez cliquer sur le bouton ci-dessous pour vérifier votre adresse email.", "If you did not create an account, no further action is required.": "Si vous n'avez pas créé de compte, aucune autre action n'est requise.", "Pending approval articles:" : "Articles soumis en attente d'approbation:", - "• Title: [:title](:url)" : "• Titre: [:title](:url)", - "• By: [@:username](:profile_url)" : "• Par: [@:username](:profile_url)", - "• Submitted on: :date" : "• Soumis le: :date" -} + "[@:username](:profile_url) submitted the article [:title](:url) on: :date" : "[@:username](:profile_url) a soumit l'article [:title](:url) le: :date" +} \ No newline at end of file diff --git a/tests/Feature/NotifyPendingArticlesTest.php b/tests/Feature/NotifyPendingArticlesTest.php index fb067f5e..7e260cba 100644 --- a/tests/Feature/NotifyPendingArticlesTest.php +++ b/tests/Feature/NotifyPendingArticlesTest.php @@ -4,8 +4,6 @@ use App\Console\Commands\NotifyPendingArticles; use App\Models\Article; -use App\Notifications\PendingArticlesNotification; -use Illuminate\Notifications\AnonymousNotifiable; use Illuminate\Support\Facades\Notification; beforeEach(fn() => Notification::fake()); @@ -26,15 +24,8 @@ ]); $this->assertDatabaseCount('articles', 3); - $this->artisan(NotifyPendingArticles::class)->assertExitCode(0); - Notification::assertSentTo( - new AnonymousNotifiable(), - PendingArticlesNotification::class, - fn($notification) => $notification->pendingArticles->count() === 1 - ); - Notification::assertCount(1); }); From 16dd864f8b1d2531b1dba76d83af5ba4822218e6 Mon Sep 17 00:00:00 2001 From: Stevy Endaman Date: Thu, 10 Oct 2024 14:46:16 +0200 Subject: [PATCH 3/3] fix: (LAR-10) fix php stan error --- app/Console/Kernel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 4f357d45..3ee86670 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -23,7 +23,7 @@ protected function schedule(Schedule $schedule): void $schedule->command('lcm:post-article-to-telegram')->everyFourHours(); $schedule->command('lcm:send-unverified-mails')->weeklyOn(1, '8:00'); $schedule->command('sitemap:generate')->daily(); - $schedule->command('lcm:notify-pending-articles')->everyTwoDays(); + $schedule->command('lcm:notify-pending-articles')->days(2); } }