From f716a205ce573b8e4e88aba2eefc79c546a7b8d4 Mon Sep 17 00:00:00 2001 From: Leon Morival Date: Tue, 26 May 2026 11:55:39 +0200 Subject: [PATCH] feat: engagment notifications --- .env.example | 1 + app/Http/Controllers/StravaController.php | 6 ++--- .../MealPostCommentedNotification.php | 3 ++- config/app.php | 2 ++ config/deploy.yml | 1 + docker-compose.prod.yml | 14 ------------ lang/en/api.php | 22 +++++++++++++++++++ lang/fr/api.php | 22 +++++++++++++++++++ routes/api.php | 1 - routes/console.php | 4 ++++ tests/Feature/PostReviewsControllerTest.php | 4 +++- tests/Feature/ScheduledAuthCleanupTest.php | 5 +++-- tests/Feature/StravaConnectionTest.php | 4 +++- 13 files changed, 65 insertions(+), 24 deletions(-) diff --git a/.env.example b/.env.example index fbb5c45..be1220c 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,7 @@ APP_ENV=local APP_KEY= APP_DEBUG=true APP_URL=http://localhost +APP_MOBILE_SCHEME=bowly APP_PORT=8003 APP_LOCALE=fr APP_FALLBACK_LOCALE=en diff --git a/app/Http/Controllers/StravaController.php b/app/Http/Controllers/StravaController.php index 6b586e8..e9c63d4 100644 --- a/app/Http/Controllers/StravaController.php +++ b/app/Http/Controllers/StravaController.php @@ -7,6 +7,7 @@ use App\Http\Requests\StravaCallbackRequest; use App\Http\Requests\StravaSyncRequest; use App\Models\StravaConnection; use App\Models\WorkoutSessions; +use App\Services\MobileDeepLink; use App\Services\StravaClient; use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\RequestException; @@ -40,10 +41,7 @@ class StravaController extends Controller ->filter(fn ($value): bool => $value !== null && $value !== '') ->all(); - $query = http_build_query($params, '', '&', PHP_QUERY_RFC3986); - $url = 'bowly://strava/callback'.($query ? "?{$query}" : ''); - - return redirect()->away($url); + return redirect()->away(MobileDeepLink::to('strava/callback', $params)); } public function callback(StravaCallbackRequest $request, StravaClient $strava): JsonResponse diff --git a/app/Notifications/MealPostCommentedNotification.php b/app/Notifications/MealPostCommentedNotification.php index aa9a7d8..cb0f0b2 100644 --- a/app/Notifications/MealPostCommentedNotification.php +++ b/app/Notifications/MealPostCommentedNotification.php @@ -3,6 +3,7 @@ namespace App\Notifications; use App\Models\PostReviews; +use App\Services\MobileDeepLink; use Illuminate\Bus\Queueable; use Illuminate\Notifications\Notification; use Illuminate\Support\Str; @@ -42,7 +43,7 @@ class MealPostCommentedNotification extends Notification 'sound' => 'default', 'channelId' => 'default', 'data' => [ - 'url' => "bowly://meals/{$mealPostId}", + 'url' => MobileDeepLink::to("meals/{$mealPostId}"), ], ]; } diff --git a/config/app.php b/config/app.php index 753cb70..c44c1a1 100644 --- a/config/app.php +++ b/config/app.php @@ -54,6 +54,8 @@ return [ 'url' => env('APP_URL', 'http://localhost'), + 'mobile_scheme' => env('APP_MOBILE_SCHEME', 'bowly'), + /* |-------------------------------------------------------------------------- | Application Timezone diff --git a/config/deploy.yml b/config/deploy.yml index 9f60b6b..362c3be 100644 --- a/config/deploy.yml +++ b/config/deploy.yml @@ -60,6 +60,7 @@ env: APP_DEBUG: "false" APP_NAME: "Bowly" APP_URL: https://bemeal.leonmorival.com + APP_MOBILE_SCHEME: bowly SERVER_NAME: ":80" APP_RUNTIME: "Laravel\\FrankenPHP\\Runtime" FILESYSTEM_DISK: s3 diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 95aca54..b54cbe5 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -37,17 +37,6 @@ services: networks: - internal - meilisearch: - image: getmeili/meilisearch:latest - container_name: bemeal-meilisearch - restart: unless-stopped - environment: - MEILI_NO_ANALYTICS: "true" - MEILI_MASTER_KEY: "${MEILISEARCH_KEY:?MEILISEARCH_KEY is required}" - volumes: - - meilisearch-data:/meili_data - networks: - - internal volumes: pgsql-data: @@ -57,9 +46,6 @@ volumes: redis-data: name: bemeal_redis_data - meilisearch-data: - name: bemeal_meilisearch_data - networks: internal: external: true diff --git a/lang/en/api.php b/lang/en/api.php index b360204..21d1f3f 100644 --- a/lang/en/api.php +++ b/lang/en/api.php @@ -38,6 +38,28 @@ return [ 'your_meal' => 'your meal', 'moderation_threshold_title' => 'Reports need review', 'moderation_threshold_body' => ':count reports received for :type.', + 'engagement_reminders' => [ + 'publish_meal' => [ + 'title' => 'Your next meal is waiting', + 'body' => "Do not hesitate to publish today's meal on Bowli.", + ], + 'meal_photo' => [ + 'title' => 'A plate worth sharing?', + 'body' => 'Take a quick photo and keep track of your meal.', + ], + 'evening_checkin' => [ + 'title' => 'Small Bowli reminder', + 'body' => "Add today's meal to keep your habits on track.", + ], + 'community_inspiration' => [ + 'title' => 'Inspire the community', + 'body' => 'Publish your meal and give other users new ideas.', + ], + 'workout_checkin' => [ + 'title' => 'Meal or workout?', + 'body' => 'If you moved today, add your workout in Bowli too.', + ], + ], ], 'reports' => [ 'created' => 'Report sent. Thanks, our team will review this content.', diff --git a/lang/fr/api.php b/lang/fr/api.php index 3732f12..58c992c 100644 --- a/lang/fr/api.php +++ b/lang/fr/api.php @@ -38,6 +38,28 @@ return [ 'your_meal' => 'votre plat', 'moderation_threshold_title' => 'Signalements à vérifier', 'moderation_threshold_body' => ':count signalements reçus pour :type.', + 'engagement_reminders' => [ + 'publish_meal' => [ + 'title' => 'Ton prochain plat attend', + 'body' => 'N’hésite pas à publier ton repas du jour sur Bowli.', + ], + 'meal_photo' => [ + 'title' => 'Une assiette à partager ?', + 'body' => 'Prends une photo rapide et garde une trace de ton repas.', + ], + 'evening_checkin' => [ + 'title' => 'Petit rappel Bowli', + 'body' => 'Ajoute ton plat du jour pour suivre tes habitudes sans perdre le fil.', + ], + 'community_inspiration' => [ + 'title' => 'Inspire la communauté', + 'body' => 'Publie ton plat et donne des idées aux autres utilisateurs.', + ], + 'workout_checkin' => [ + 'title' => 'Repas ou workout ?', + 'body' => 'Si tu as bougé aujourd’hui, ajoute aussi ta séance dans Bowli.', + ], + ], ], 'reports' => [ 'created' => 'Signalement envoyé. Merci, notre équipe va examiner ce contenu.', diff --git a/routes/api.php b/routes/api.php index 940f6c2..a57376e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -53,7 +53,6 @@ Route::prefix('legal-documents')->group(function (): void { Route::middleware(['auth:sanctum', 'verified', 'not_suspended'])->group(function (): void { Route::post('device-tokens', [DeviceTokenController::class, 'store'])->middleware('throttle:device-token'); Route::delete('device-tokens', [DeviceTokenController::class, 'destroy'])->middleware('throttle:device-token'); - Route::get('test-notifs', [DeviceTokenController::class, 'notifs'])->middleware('throttle:notifications'); }); // Workouts diff --git a/routes/console.php b/routes/console.php index 519c2b2..0228a2e 100644 --- a/routes/console.php +++ b/routes/console.php @@ -25,3 +25,7 @@ Schedule::command('meal-posts:generate-ai') ->daily() ->withoutOverlapping(55) ->onOneServer(); +Schedule::command('notifications:send-engagement-reminders') + ->dailyAt('12:30') + ->withoutOverlapping(30) + ->onOneServer(); diff --git a/tests/Feature/PostReviewsControllerTest.php b/tests/Feature/PostReviewsControllerTest.php index c87399b..45b202e 100644 --- a/tests/Feature/PostReviewsControllerTest.php +++ b/tests/Feature/PostReviewsControllerTest.php @@ -43,6 +43,8 @@ it('creates one review per user for a meal post', function () { }); it('sends a push notification to the meal owner when another user comments on their meal', function () { + config()->set('app.mobile_scheme', 'dailymeal'); + Http::fake([ 'https://exp.host/*' => Http::response([ 'data' => [ @@ -77,7 +79,7 @@ it('sends a push notification to the meal owner when another user comments on th 'sound' => 'default', 'channelId' => 'default', 'data' => [ - 'url' => "bowly://meals/{$mealPost->id}", + 'url' => "dailymeal://meals/{$mealPost->id}", ], ], ]); diff --git a/tests/Feature/ScheduledAuthCleanupTest.php b/tests/Feature/ScheduledAuthCleanupTest.php index 8da742c..d82116e 100644 --- a/tests/Feature/ScheduledAuthCleanupTest.php +++ b/tests/Feature/ScheduledAuthCleanupTest.php @@ -2,7 +2,7 @@ use Illuminate\Support\Facades\Artisan; -it('schedules expired auth and stale device token cleanup commands', function () { +it('schedules maintenance and engagement commands', function () { Artisan::call('schedule:list', [ '--json' => true, ]); @@ -13,5 +13,6 @@ it('schedules expired auth and stale device token cleanup commands', function () expect($commands) ->toContain('php artisan sanctum:prune-expired --hours=24') ->toContain('php artisan auth:clear-resets') - ->toContain('php artisan device-tokens:prune-stale --days=180'); + ->toContain('php artisan device-tokens:prune-stale --days=180') + ->toContain('php artisan notifications:send-engagement-reminders'); }); diff --git a/tests/Feature/StravaConnectionTest.php b/tests/Feature/StravaConnectionTest.php index a5d878d..4841b8f 100644 --- a/tests/Feature/StravaConnectionTest.php +++ b/tests/Feature/StravaConnectionTest.php @@ -38,6 +38,8 @@ it('returns a strava authorization url for the api callback', function () { }); it('redirects the strava web callback to the mobile app', function () { + config()->set('app.mobile_scheme', 'dailymeal'); + $query = http_build_query([ 'code' => 'authorization-code', 'scope' => 'read activity:read', @@ -46,7 +48,7 @@ it('redirects the strava web callback to the mobile app', function () { $this ->get("/strava/callback?{$query}") - ->assertRedirect("bowly://strava/callback?{$query}"); + ->assertRedirect("dailymeal://strava/callback?{$query}"); }); it('stores strava tokens from an authorization code', function () {