feat: notifications on comments
This commit is contained in:
parent
cd38d88fe7
commit
49419c1e7b
|
|
@ -6,6 +6,7 @@ use App\Http\Requests\PostReviewsRequest;
|
||||||
use App\Http\Resources\PostReviewsResource;
|
use App\Http\Resources\PostReviewsResource;
|
||||||
use App\Models\MealPosts;
|
use App\Models\MealPosts;
|
||||||
use App\Models\PostReviews;
|
use App\Models\PostReviews;
|
||||||
|
use App\Notifications\MealPostCommentedNotification;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||||
|
|
@ -49,7 +50,10 @@ class PostReviewsController extends Controller
|
||||||
'user_id' => $userId,
|
'user_id' => $userId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (new PostReviewsResource($postReview->load('user:id,name,avatar_url')))
|
$postReview->load('user:id,name,avatar_url');
|
||||||
|
$this->notifyMealOwner($mealPost, $postReview, $userId);
|
||||||
|
|
||||||
|
return (new PostReviewsResource($postReview))
|
||||||
->response()
|
->response()
|
||||||
->setStatusCode(201);
|
->setStatusCode(201);
|
||||||
}
|
}
|
||||||
|
|
@ -97,4 +101,23 @@ class PostReviewsController extends Controller
|
||||||
404,
|
404,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function notifyMealOwner(MealPosts $mealPost, PostReviews $postReview, string $reviewerId): void
|
||||||
|
{
|
||||||
|
if ($mealPost->user_id === $reviewerId || blank($postReview->comment)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mealPost->loadMissing('user');
|
||||||
|
$owner = $mealPost->user;
|
||||||
|
|
||||||
|
if (! $owner) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$owner->notify(
|
||||||
|
(new MealPostCommentedNotification($postReview))
|
||||||
|
->locale($owner->preferredLocale())
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications;
|
||||||
|
|
||||||
|
use App\Models\PostReviews;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class MealPostCommentedNotification extends Notification
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public function __construct(private readonly PostReviews $postReview) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
|
public function via(object $notifiable): array
|
||||||
|
{
|
||||||
|
return ['expo'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toExpoPush(object $notifiable): array
|
||||||
|
{
|
||||||
|
$this->postReview->loadMissing(
|
||||||
|
'mealPost:id,title,user_id',
|
||||||
|
'user:id,name',
|
||||||
|
);
|
||||||
|
|
||||||
|
$mealPost = $this->postReview->mealPost;
|
||||||
|
$mealPostId = $mealPost?->getKey() ?? $this->postReview->meal_post_id;
|
||||||
|
$mealTitle = Str::limit((string) ($mealPost?->title ?: __('api.notifications.your_meal')), 60);
|
||||||
|
$commenterName = $this->postReview->user?->name ?: __('api.notifications.someone');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => __('api.notifications.meal_post_commented_title'),
|
||||||
|
'body' => __('api.notifications.meal_post_commented_body', [
|
||||||
|
'meal' => $mealTitle,
|
||||||
|
'name' => $commenterName,
|
||||||
|
]),
|
||||||
|
'sound' => 'default',
|
||||||
|
'channelId' => 'default',
|
||||||
|
'data' => [
|
||||||
|
'url' => "bowly://meals/{$mealPostId}",
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,10 @@ return [
|
||||||
'notifications' => [
|
'notifications' => [
|
||||||
'test_title' => 'Test',
|
'test_title' => 'Test',
|
||||||
'test_body' => 'Test notification from the Laravel API.',
|
'test_body' => 'Test notification from the Laravel API.',
|
||||||
|
'meal_post_commented_title' => 'New comment',
|
||||||
|
'meal_post_commented_body' => ':name commented on your meal ":meal".',
|
||||||
|
'someone' => 'Someone',
|
||||||
|
'your_meal' => 'your meal',
|
||||||
'moderation_threshold_title' => 'Reports need review',
|
'moderation_threshold_title' => 'Reports need review',
|
||||||
'moderation_threshold_body' => ':count reports received for :type.',
|
'moderation_threshold_body' => ':count reports received for :type.',
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,10 @@ return [
|
||||||
'notifications' => [
|
'notifications' => [
|
||||||
'test_title' => 'Test',
|
'test_title' => 'Test',
|
||||||
'test_body' => 'Notification test depuis Laravel API.',
|
'test_body' => 'Notification test depuis Laravel API.',
|
||||||
|
'meal_post_commented_title' => 'Nouveau commentaire',
|
||||||
|
'meal_post_commented_body' => ':name a commenté votre plat ":meal".',
|
||||||
|
'someone' => 'Quelqu’un',
|
||||||
|
'your_meal' => 'votre plat',
|
||||||
'moderation_threshold_title' => 'Signalements à vérifier',
|
'moderation_threshold_title' => 'Signalements à vérifier',
|
||||||
'moderation_threshold_body' => ':count signalements reçus pour :type.',
|
'moderation_threshold_body' => ':count signalements reçus pour :type.',
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ use App\Models\MealPosts;
|
||||||
use App\Models\PostReviews;
|
use App\Models\PostReviews;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Http\Client\Request;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
use Laravel\Sanctum\Sanctum;
|
use Laravel\Sanctum\Sanctum;
|
||||||
|
|
||||||
uses(RefreshDatabase::class);
|
uses(RefreshDatabase::class);
|
||||||
|
|
@ -40,6 +42,87 @@ it('creates one review per user for a meal post', function () {
|
||||||
expect(PostReviews::query()->count())->toBe(1);
|
expect(PostReviews::query()->count())->toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sends a push notification to the meal owner when another user comments on their meal', function () {
|
||||||
|
Http::fake([
|
||||||
|
'https://exp.host/*' => Http::response([
|
||||||
|
'data' => [
|
||||||
|
['status' => 'ok', 'id' => 'ticket-one'],
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$owner = User::factory()->create();
|
||||||
|
$owner->deviceTokens()->create([
|
||||||
|
'expo_push_token' => 'ExponentPushToken[owner]',
|
||||||
|
'platform' => 'ios',
|
||||||
|
]);
|
||||||
|
$reviewer = User::factory()->create(['name' => 'Alex']);
|
||||||
|
$mealPost = MealPosts::factory()
|
||||||
|
->for($owner, 'user')
|
||||||
|
->create(['title' => 'Recovery bowl']);
|
||||||
|
|
||||||
|
Sanctum::actingAs($reviewer);
|
||||||
|
|
||||||
|
$this->postJson("/api/meal-posts/{$mealPost->id}/reviews", [
|
||||||
|
'rating' => 5,
|
||||||
|
'comment' => 'Très bon repas.',
|
||||||
|
])->assertCreated();
|
||||||
|
|
||||||
|
Http::assertSent(fn (Request $request) => $request->url() === 'https://exp.host/--/api/v2/push/send'
|
||||||
|
&& $request->data() === [
|
||||||
|
[
|
||||||
|
'to' => 'ExponentPushToken[owner]',
|
||||||
|
'title' => 'New comment',
|
||||||
|
'body' => 'Alex commented on your meal "Recovery bowl".',
|
||||||
|
'sound' => 'default',
|
||||||
|
'channelId' => 'default',
|
||||||
|
'data' => [
|
||||||
|
'url' => "bowly://meals/{$mealPost->id}",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not send a push notification for rating-only reviews', function () {
|
||||||
|
Http::fake();
|
||||||
|
|
||||||
|
$owner = User::factory()->create();
|
||||||
|
$owner->deviceTokens()->create([
|
||||||
|
'expo_push_token' => 'ExponentPushToken[owner]',
|
||||||
|
'platform' => 'ios',
|
||||||
|
]);
|
||||||
|
$reviewer = User::factory()->create();
|
||||||
|
$mealPost = MealPosts::factory()->for($owner, 'user')->create();
|
||||||
|
|
||||||
|
Sanctum::actingAs($reviewer);
|
||||||
|
|
||||||
|
$this->postJson("/api/meal-posts/{$mealPost->id}/reviews", [
|
||||||
|
'rating' => 5,
|
||||||
|
])->assertCreated();
|
||||||
|
|
||||||
|
Http::assertNothingSent();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not notify the owner when they comment on their own meal', function () {
|
||||||
|
Http::fake();
|
||||||
|
|
||||||
|
$owner = User::factory()->create();
|
||||||
|
$owner->deviceTokens()->create([
|
||||||
|
'expo_push_token' => 'ExponentPushToken[owner]',
|
||||||
|
'platform' => 'ios',
|
||||||
|
]);
|
||||||
|
$mealPost = MealPosts::factory()->for($owner, 'user')->create();
|
||||||
|
|
||||||
|
Sanctum::actingAs($owner);
|
||||||
|
|
||||||
|
$this->postJson("/api/meal-posts/{$mealPost->id}/reviews", [
|
||||||
|
'rating' => 5,
|
||||||
|
'comment' => 'Note personnelle.',
|
||||||
|
])->assertCreated();
|
||||||
|
|
||||||
|
Http::assertNothingSent();
|
||||||
|
});
|
||||||
|
|
||||||
it('lists reviews for a single meal post', function () {
|
it('lists reviews for a single meal post', function () {
|
||||||
$user = User::factory()->create();
|
$user = User::factory()->create();
|
||||||
$mealPost = MealPosts::factory()->create();
|
$mealPost = MealPosts::factory()->create();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue