fix: trustProxies (assets Filament https) + noms exos localisés (name_i18n) + /api/stats
Build & Deploy / build (push) Successful in 18s
Build & Deploy / build (push) Successful in 18s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,9 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->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.
|
// Active le flow Sanctum SPA : cookie de session + protection CSRF pour les requêtes API du front.
|
||||||
$middleware->statefulApi();
|
$middleware->statefulApi();
|
||||||
// Pas de route web "login" nommée (SPA) : rediriger les invités vers le front plutôt que planter.
|
// Pas de route web "login" nommée (SPA) : rediriger les invités vers le front plutôt que planter.
|
||||||
|
|||||||
+5
-4
@@ -10,9 +10,10 @@ services:
|
|||||||
- "192.168.1.136:8111:80"
|
- "192.168.1.136:8111:80"
|
||||||
labels:
|
labels:
|
||||||
- homepage.group=Développement
|
- homepage.group=Développement
|
||||||
- homepage.name=StreakFit API
|
- homepage.name=StreakFit — Admin
|
||||||
- homepage.icon=mdi-fire
|
- homepage.icon=mdi-cog
|
||||||
- homepage.href=https://api.streakfit.nfteam.ovh
|
- homepage.href=https://streakfit.nfteam.ovh/admin
|
||||||
- homepage.description=API cross-training (Laravel OSDD / Octane)
|
- homepage.description=Back-office Filament (Laravel OSDD / Octane)
|
||||||
|
- homepage.weight=2
|
||||||
- com.centurylinklabs.watchtower.enable=true
|
- com.centurylinklabs.watchtower.enable=true
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
+23
@@ -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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Functional\Exercises\Http\Controllers\ExercisesController;
|
use Functional\Exercises\Http\Controllers\ExercisesController;
|
||||||
|
use Functional\Exercises\Http\Controllers\StatsController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
// Routes here are wrapped in the 'api' middleware group with the 'api'
|
// 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/facets', [ExercisesController::class, 'facets']);
|
||||||
Route::get('/exercises', [ExercisesController::class, 'index']);
|
Route::get('/exercises', [ExercisesController::class, 'index']);
|
||||||
Route::get('/exercises/{exercise}', [ExercisesController::class, 'show']);
|
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 [
|
return [
|
||||||
'id' => $this->id,
|
'id' => $this->id,
|
||||||
'name' => $this->name,
|
'name' => $this->resource->localizedName($l),
|
||||||
'slug' => $this->slug,
|
'slug' => $this->slug,
|
||||||
'body_part' => Exercise::localizeTerm($this->body_part, $l),
|
'body_part' => Exercise::localizeTerm($this->body_part, $l),
|
||||||
'equipment' => Exercise::localizeTerm($this->equipment, $l),
|
'equipment' => Exercise::localizeTerm($this->equipment, $l),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class ExerciseResource extends JsonResource
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => $this->id,
|
'id' => $this->id,
|
||||||
'name' => $this->name,
|
'name' => $model->localizedName($l),
|
||||||
'slug' => $this->slug,
|
'slug' => $this->slug,
|
||||||
'body_part' => $model::localizeTerm($this->body_part, $l),
|
'body_part' => $model::localizeTerm($this->body_part, $l),
|
||||||
'category' => $model::localizeTerm($this->category, $l),
|
'category' => $model::localizeTerm($this->category, $l),
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class Exercise extends Model
|
|||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $fillable = [
|
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',
|
'target', 'muscle_group', 'secondary_muscles', 'instructions',
|
||||||
'image_path', 'gif_path', 'attribution',
|
'image_path', 'gif_path', 'attribution',
|
||||||
];
|
];
|
||||||
@@ -30,6 +30,7 @@ class Exercise extends Model
|
|||||||
protected $casts = [
|
protected $casts = [
|
||||||
'secondary_muscles' => 'array',
|
'secondary_muscles' => 'array',
|
||||||
'instructions' => 'array',
|
'instructions' => 'array',
|
||||||
|
'name_i18n' => 'array',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $appends = ['image_url', 'gif_url'];
|
protected $appends = ['image_url', 'gif_url'];
|
||||||
@@ -82,6 +83,14 @@ class Exercise extends Model
|
|||||||
return [];
|
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. */
|
/** Traduit un terme du vocabulaire contrôlé (zone/équipement/muscle) selon la locale. Repli = anglais. */
|
||||||
public static function localizeTerm(?string $value, string $locale): ?string
|
public static function localizeTerm(?string $value, string $locale): ?string
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user