188 lines
5.6 KiB
PHP
188 lines
5.6 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace App\Console\Commands;
|
||
|
|
|
||
|
|
use App\Models\BodyPart;
|
||
|
|
use App\Models\Equipment;
|
||
|
|
use App\Models\Exercise;
|
||
|
|
use App\Models\Muscle;
|
||
|
|
use App\Services\ExerciseDb\ApiService;
|
||
|
|
use Illuminate\Console\Command;
|
||
|
|
use Illuminate\Support\Str;
|
||
|
|
|
||
|
|
class FetchExerciseDbDataCommand extends Command
|
||
|
|
{
|
||
|
|
/**
|
||
|
|
* The name and signature of the console command.
|
||
|
|
*
|
||
|
|
* @var string
|
||
|
|
*/
|
||
|
|
protected $signature = 'fetch:exercise';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The console command description.
|
||
|
|
*
|
||
|
|
* @var string
|
||
|
|
*/
|
||
|
|
protected $description = 'Fetch all exercises from ExerciseDB API and store them locally.';
|
||
|
|
|
||
|
|
private ApiService $apiService;
|
||
|
|
|
||
|
|
public function __construct(ApiService $apiService)
|
||
|
|
{
|
||
|
|
$this->apiService = $apiService;
|
||
|
|
|
||
|
|
parent::__construct();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Execute the console command.
|
||
|
|
*/
|
||
|
|
public function handle(): int
|
||
|
|
{
|
||
|
|
$page = 1;
|
||
|
|
$stored = 0;
|
||
|
|
$updated = 0;
|
||
|
|
|
||
|
|
$this->info('Fetching exercises from ExerciseDB...');
|
||
|
|
|
||
|
|
while (true) {
|
||
|
|
$results = $this->apiService->fetch($page);
|
||
|
|
|
||
|
|
$results = $results['data'];
|
||
|
|
|
||
|
|
$count = count($results);
|
||
|
|
if ($count === 0) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
[$s, $u] = $this->process($results);
|
||
|
|
$stored += $s;
|
||
|
|
$updated += $u;
|
||
|
|
|
||
|
|
$this->info("Processed page {$page} with {$count} items.");
|
||
|
|
|
||
|
|
if ($count < ApiService::PER_PAGE) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
$page++;
|
||
|
|
sleep(5);
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->info("Done. Created {$stored}, updated {$updated} exercises.");
|
||
|
|
|
||
|
|
return self::SUCCESS;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param array<int, array<string, mixed>> $results
|
||
|
|
* @return array{int,int} [created, updated]
|
||
|
|
*/
|
||
|
|
private function process(array $results): array
|
||
|
|
{
|
||
|
|
$created = 0;
|
||
|
|
$updated = 0;
|
||
|
|
|
||
|
|
foreach ($results as $item) {
|
||
|
|
$exerciseDbId = (string)($item['exerciseId'] ?? '');
|
||
|
|
if ($exerciseDbId === '') {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
$name = (string)($item['name'] ?? '');
|
||
|
|
$gifUrl = (string)($item['gifUrl'] ?? '');
|
||
|
|
|
||
|
|
$instructions = $item['instructions'] ?? [];
|
||
|
|
if (is_array($instructions)) {
|
||
|
|
$instructionsText = implode("\n", array_filter(array_map('strval', $instructions)));
|
||
|
|
} else {
|
||
|
|
$instructionsText = (string)$instructions;
|
||
|
|
}
|
||
|
|
|
||
|
|
$slug = Str::slug($name) ?: $exerciseDbId;
|
||
|
|
|
||
|
|
$values = [
|
||
|
|
'name' => $name,
|
||
|
|
'slug' => $slug,
|
||
|
|
'gif_url' => $gifUrl,
|
||
|
|
'instructions' => $instructionsText,
|
||
|
|
];
|
||
|
|
|
||
|
|
$existing = Exercise::where('exercise_db_id', $exerciseDbId)->first();
|
||
|
|
$exercise = Exercise::updateOrCreate(
|
||
|
|
['exercise_db_id' => $exerciseDbId],
|
||
|
|
$values,
|
||
|
|
);
|
||
|
|
|
||
|
|
if ($existing) { $updated++; } else { $created++; }
|
||
|
|
|
||
|
|
// Relations
|
||
|
|
// Body part (string)
|
||
|
|
$bodyPartVal = $item['bodyPart'] ?? null;
|
||
|
|
$bodyPartIds = [];
|
||
|
|
if ($bodyPartVal) {
|
||
|
|
$bodyParts = is_array($bodyPartVal) ? $bodyPartVal : [$bodyPartVal];
|
||
|
|
foreach ($bodyParts as $bp) {
|
||
|
|
$bpName = (string)$bp;
|
||
|
|
$bpSlug = Str::slug($bpName);
|
||
|
|
$bpModel = BodyPart::firstOrCreate(['slug' => $bpSlug], ['name' => $bpName]);
|
||
|
|
$bodyPartIds[] = $bpModel->id;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if ($bodyPartIds) {
|
||
|
|
$exercise->bodyParts()->sync($bodyPartIds);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Target muscle (string)
|
||
|
|
$targetVal = $item['target'] ?? null;
|
||
|
|
$targetIds = [];
|
||
|
|
if ($targetVal) {
|
||
|
|
$targets = is_array($targetVal) ? $targetVal : [$targetVal];
|
||
|
|
foreach ($targets as $tm) {
|
||
|
|
$mName = (string)$tm;
|
||
|
|
$mSlug = Str::slug($mName);
|
||
|
|
$mModel = Muscle::firstOrCreate(['slug' => $mSlug], ['name' => $mName]);
|
||
|
|
$targetIds[] = $mModel->id;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if ($targetIds) {
|
||
|
|
$exercise->targetMuscles()->sync($targetIds);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Secondary muscles (array)
|
||
|
|
$secVal = $item['secondaryMuscles'] ?? [];
|
||
|
|
$secondaryIds = [];
|
||
|
|
if (!is_array($secVal)) { $secVal = [$secVal]; }
|
||
|
|
foreach ($secVal as $sm) {
|
||
|
|
$smName = (string)$sm;
|
||
|
|
if ($smName === '') { continue; }
|
||
|
|
$smSlug = Str::slug($smName);
|
||
|
|
$mModel = Muscle::firstOrCreate(['slug' => $smSlug], ['name' => $smName]);
|
||
|
|
$secondaryIds[] = $mModel->id;
|
||
|
|
}
|
||
|
|
if ($secondaryIds) {
|
||
|
|
$exercise->secondaryMuscles()->sync($secondaryIds);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Equipment (string or array depending on source variant)
|
||
|
|
$equipVal = $item['equipment'] ?? null;
|
||
|
|
$equipmentIds = [];
|
||
|
|
if ($equipVal) {
|
||
|
|
$equipments = is_array($equipVal) ? $equipVal : [$equipVal];
|
||
|
|
foreach ($equipments as $eq) {
|
||
|
|
$eqName = (string)$eq;
|
||
|
|
$eqSlug = Str::slug($eqName);
|
||
|
|
$eqModel = Equipment::firstOrCreate(['slug' => $eqSlug], ['name' => $eqName]);
|
||
|
|
$equipmentIds[] = $eqModel->id;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if ($equipmentIds) {
|
||
|
|
$exercise->equipments()->sync($equipmentIds);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return [$created, $updated];
|
||
|
|
}
|
||
|
|
}
|