i18n vocabulaire exos (fr/pt-BR) + Web Push rappels de série
Build & Deploy / build (push) Successful in 17s

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-19 03:46:43 +02:00
co-authored by Claude Opus 4.8
parent af32669931
commit fa3e59704b
23 changed files with 954 additions and 31 deletions
+10 -2
View File
@@ -1,7 +1,15 @@
<?php
use Functional\Users\Http\Controllers\AuthController;
use Functional\Users\Http\Controllers\PushController;
use Illuminate\Support\Facades\Route;
// Le préfixe api/ est ajouté par UsersServiceProvider::withRouting() : GET /api/me.
Route::get('me', [AuthController::class, 'me'])->middleware('auth:sanctum');
// Le préfixe api/ est ajouté par UsersServiceProvider::withRouting().
Route::middleware('auth:sanctum')->group(function () {
Route::get('me', [AuthController::class, 'me']);
// Web Push (rappels de série pilotés par le back)
Route::post('push/subscribe', [PushController::class, 'subscribe']);
Route::post('push/unsubscribe', [PushController::class, 'unsubscribe']);
Route::post('push/test', [PushController::class, 'test']);
});
@@ -85,6 +85,10 @@ class AuthController
'longest' => $streak->longest_count,
'freezes' => $streak->freezes_available,
],
'push' => [
'vapid_public_key' => config('webpush.vapid.public_key'),
'enabled' => $user->pushSubscriptions()->exists(),
],
]);
}
@@ -0,0 +1,50 @@
<?php
namespace Functional\Users\Http\Controllers;
use Functional\Users\Notifications\PushTestNotification;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
/**
* Abonnement Web Push (PWA). Le front envoie sa subscription ; on la stocke sur l'utilisateur.
* L'envoi réel des rappels est piloté par le back (commande planifiée) cf couche streaks.
*/
class PushController
{
/** POST /api/push/subscribe — enregistre/actualise l'abonnement push du navigateur. */
public function subscribe(Request $request): JsonResponse
{
$data = $request->validate([
'endpoint' => ['required', 'string'],
'keys.p256dh' => ['required', 'string'],
'keys.auth' => ['required', 'string'],
]);
$request->user()->updatePushSubscription(
$data['endpoint'],
$data['keys']['p256dh'],
$data['keys']['auth'],
);
return response()->json(['status' => 'subscribed']);
}
/** POST /api/push/unsubscribe — supprime l'abonnement (désactivation des rappels). */
public function unsubscribe(Request $request): Response
{
$request->validate(['endpoint' => ['required', 'string']]);
$request->user()->deletePushSubscription($request->input('endpoint'));
return response()->noContent();
}
/** POST /api/push/test — envoie une notif de test (bouton "tester" côté profil). */
public function test(Request $request): JsonResponse
{
$request->user()->notify(new PushTestNotification());
return response()->json(['status' => 'sent']);
}
}
+2 -1
View File
@@ -14,6 +14,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use NotificationChannels\WebPush\HasPushSubscriptions;
/**
* @property string $locale
@@ -27,7 +28,7 @@ use Laravel\Sanctum\HasApiTokens;
#[UseFactory(UserFactory::class)]
class User extends Authenticatable implements FilamentUser
{
use HasApiTokens, HasFactory, Notifiable;
use HasApiTokens, HasFactory, HasPushSubscriptions, Notifiable;
/** Accès au back-office Filament (à affiner : rôle admin plus tard). */
public function canAccessPanel(Panel $panel): bool
@@ -0,0 +1,28 @@
<?php
namespace Functional\Users\Notifications;
use Illuminate\Notifications\Notification;
use NotificationChannels\WebPush\WebPushChannel;
use NotificationChannels\WebPush\WebPushMessage;
/** Notification de test envoyée quand l'utilisateur active/teste les rappels push. */
class PushTestNotification extends Notification
{
/** @return array<int,string> */
public function via(object $notifiable): array
{
return [WebPushChannel::class];
}
public function toWebPush(object $notifiable): WebPushMessage
{
return (new WebPushMessage())
->title('🔥 StreakFit')
->body('Notifications activées — on te préviendra pour garder ta flamme !')
->icon('/pwa-192.png')
->badge('/pwa-192.png')
->data(['url' => '/'])
->options(['TTL' => 3600]);
}
}