fix: trustProxies (assets Filament https) + noms exos localisés (name_i18n) + /api/stats
Build & Deploy / build (push) Successful in 18s

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-19 03:56:11 +02:00
co-authored by Claude Opus 4.8
parent fa3e59704b
commit b46cee7eb9
8 changed files with 69 additions and 7 deletions
+3
View File
@@ -12,6 +12,9 @@ return Application::configure(basePath: dirname(__DIR__))
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
// Derrière NPM (reverse-proxy) : faire confiance au proxy pour X-Forwarded-Proto=https
// (sinon Laravel génère des URLs http -> mixed-content, casse les assets Filament).
$middleware->trustProxies(at: '*');
// Active le flow Sanctum SPA : cookie de session + protection CSRF pour les requêtes API du front.
$middleware->statefulApi();
// Pas de route web "login" nommée (SPA) : rediriger les invités vers le front plutôt que planter.
+5 -4
View File
@@ -10,9 +10,10 @@ services:
- "192.168.1.136:8111:80"
labels:
- homepage.group=Développement
- homepage.name=StreakFit API
- homepage.icon=mdi-fire
- homepage.href=https://api.streakfit.nfteam.ovh
- homepage.description=API cross-training (Laravel OSDD / Octane)
- homepage.name=StreakFit — Admin
- homepage.icon=mdi-cog
- homepage.href=https://streakfit.nfteam.ovh/admin
- homepage.description=Back-office Filament (Laravel OSDD / Octane)
- homepage.weight=2
- com.centurylinklabs.watchtower.enable=true
restart: unless-stopped
@@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('exercises', function (Blueprint $table) {
// Traductions du nom : { fr: "...", "pt-BR": "..." }. Anglais = colonne name (canonique).
$table->json('name_i18n')->nullable()->after('name');
});
}
public function down(): void
{
Schema::table('exercises', function (Blueprint $table) {
$table->dropColumn('name_i18n');
});
}
};
+4
View File
@@ -1,6 +1,7 @@
<?php
use Functional\Exercises\Http\Controllers\ExercisesController;
use Functional\Exercises\Http\Controllers\StatsController;
use Illuminate\Support\Facades\Route;
// Routes here are wrapped in the 'api' middleware group with the 'api'
@@ -12,3 +13,6 @@ use Illuminate\Support\Facades\Route;
Route::get('/exercises/facets', [ExercisesController::class, 'facets']);
Route::get('/exercises', [ExercisesController::class, 'index']);
Route::get('/exercises/{exercise}', [ExercisesController::class, 'show']);
// Stats publiques agrégées (widget Homepage).
Route::get('/stats', StatsController::class);
@@ -0,0 +1,22 @@
<?php
namespace Functional\Exercises\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
/**
* Stats publiques agrégées (widget Homepage). Compteurs seulement, aucune donnée perso.
*/
class StatsController
{
public function __invoke(): JsonResponse
{
return response()->json([
'exercises' => DB::table('exercises')->count(),
'users' => DB::table('users')->count(),
'workouts' => DB::table('workout_sessions')->where('status', 'completed')->count(),
'active_streaks' => DB::table('streaks')->where('current_count', '>', 0)->count(),
]);
}
}
@@ -20,7 +20,7 @@ class ExerciseListResource extends JsonResource
return [
'id' => $this->id,
'name' => $this->name,
'name' => $this->resource->localizedName($l),
'slug' => $this->slug,
'body_part' => Exercise::localizeTerm($this->body_part, $l),
'equipment' => Exercise::localizeTerm($this->equipment, $l),
@@ -22,7 +22,7 @@ class ExerciseResource extends JsonResource
return [
'id' => $this->id,
'name' => $this->name,
'name' => $model->localizedName($l),
'slug' => $this->slug,
'body_part' => $model::localizeTerm($this->body_part, $l),
'category' => $model::localizeTerm($this->category, $l),
+10 -1
View File
@@ -22,7 +22,7 @@ class Exercise extends Model
use HasFactory;
protected $fillable = [
'external_id', 'name', 'slug', 'category', 'body_part', 'equipment',
'external_id', 'name', 'name_i18n', 'slug', 'category', 'body_part', 'equipment',
'target', 'muscle_group', 'secondary_muscles', 'instructions',
'image_path', 'gif_path', 'attribution',
];
@@ -30,6 +30,7 @@ class Exercise extends Model
protected $casts = [
'secondary_muscles' => 'array',
'instructions' => 'array',
'name_i18n' => 'array',
];
protected $appends = ['image_url', 'gif_url'];
@@ -82,6 +83,14 @@ class Exercise extends Model
return [];
}
/** Nom localisé (name_i18n) avec repli sur le nom anglais canonique. */
public function localizedName(string $locale): string
{
$map = $this->name_i18n ?? [];
return $map[$locale] ?? $map[substr($locale, 0, 2)] ?? $this->name;
}
/** Traduit un terme du vocabulaire contrôlé (zone/équipement/muscle) selon la locale. Repli = anglais. */
public static function localizeTerm(?string $value, string $locale): ?string
{