146 lines
5.4 KiB
PHP
146 lines
5.4 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Exercise;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\View\View;
|
|
|
|
class ExerciseController extends Controller
|
|
{
|
|
public function index(Request $request): View
|
|
{
|
|
$q = trim((string) $request->query('q', ''));
|
|
$exercises = Exercise::query()
|
|
->when($q !== '', function ($qb) use ($q) {
|
|
$qb->where('name', 'like', "%{$q}%")
|
|
->orWhere('slug', 'like', "%{$q}%");
|
|
})
|
|
->orderBy('name')
|
|
->paginate(20)
|
|
->withQueryString();
|
|
|
|
return view('admin.exercises.index', compact('exercises', 'q'));
|
|
}
|
|
|
|
public function create(): View
|
|
{
|
|
return view('admin.exercises.create');
|
|
}
|
|
|
|
public function store(Request $request): RedirectResponse
|
|
{
|
|
$data = $request->validate([
|
|
'name' => ['required', 'string', 'max:255'],
|
|
'slug' => ['nullable', 'string', 'max:255', 'unique:exercises,slug'],
|
|
'gif_url' => ['nullable', 'string', 'max:2048'],
|
|
'poster_image' => ['nullable', 'string', 'max:2048'],
|
|
'poster_image_file' => ['nullable', 'image', 'mimes:jpeg,jpg,png,webp,avif,gif', 'max:8192'],
|
|
'instructions' => ['nullable', 'string'],
|
|
]);
|
|
|
|
$name = trim($data['name']);
|
|
$slug = trim((string) ($data['slug'] ?? ''));
|
|
if ($slug === '') {
|
|
$slug = $this->uniqueSlug($name);
|
|
}
|
|
|
|
// Determine poster image value (uploaded file takes precedence)
|
|
$posterImageValue = $data['poster_image'] ?? null;
|
|
if ($request->hasFile('poster_image_file')) {
|
|
$path = $request->file('poster_image_file')->store('exercises/posters', 'public');
|
|
$posterImageValue = $path; // store relative path; use Storage::url to display
|
|
}
|
|
|
|
Exercise::create([
|
|
'name' => $name,
|
|
'slug' => $slug,
|
|
'gif_url' => $data['gif_url'] ?? null,
|
|
'poster_image' => $posterImageValue,
|
|
'instructions' => $data['instructions'] ?? null,
|
|
]);
|
|
|
|
return redirect()->route('admin.exercises.index')->with('status', 'Exercise created');
|
|
}
|
|
|
|
public function edit(Exercise $exercise): View
|
|
{
|
|
return view('admin.exercises.edit', compact('exercise'));
|
|
}
|
|
|
|
public function update(Request $request, Exercise $exercise): RedirectResponse
|
|
{
|
|
$data = $request->validate([
|
|
'name' => ['required', 'string', 'max:255'],
|
|
'slug' => ['nullable', 'string', 'max:255', 'unique:exercises,slug,' . $exercise->id],
|
|
'gif_url' => ['nullable', 'string', 'max:2048'],
|
|
'poster_image' => ['nullable', 'string', 'max:2048'],
|
|
'poster_image_file' => ['nullable', 'image', 'mimes:jpeg,jpg,png,webp,avif,gif', 'max:8192'],
|
|
'instructions' => ['nullable', 'string'],
|
|
]);
|
|
|
|
$name = trim($data['name']);
|
|
$slug = trim((string) ($data['slug'] ?? ''));
|
|
if ($slug === '') {
|
|
// only (re)generate if empty
|
|
$slug = $this->uniqueSlug($name, $exercise->id);
|
|
}
|
|
|
|
// Determine poster image value for update (uploaded file > provided URL > keep existing)
|
|
$posterImageValue = $data['poster_image'] ?? $exercise->poster_image;
|
|
if ($request->hasFile('poster_image_file')) {
|
|
$path = $request->file('poster_image_file')->store('exercises/posters', 'public');
|
|
$posterImageValue = $path;
|
|
}
|
|
|
|
$exercise->update([
|
|
'name' => $name,
|
|
'slug' => $slug,
|
|
'gif_url' => $data['gif_url'] ?? null,
|
|
'poster_image' => $posterImageValue,
|
|
'instructions' => $data['instructions'] ?? null,
|
|
]);
|
|
|
|
return redirect()->route('admin.exercises.index')->with('status', 'Exercise updated');
|
|
}
|
|
|
|
public function destroy(Exercise $exercise): RedirectResponse
|
|
{
|
|
// Safely remove related data to avoid FK constraint errors
|
|
DB::transaction(function () use ($exercise) {
|
|
// Detach pivot relations if defined
|
|
try { $exercise->bodyParts()->detach(); } catch (\Throwable $e) {}
|
|
try { $exercise->targetMuscles()->detach(); } catch (\Throwable $e) {}
|
|
try { $exercise->secondaryMuscles()->detach(); } catch (\Throwable $e) {}
|
|
try { $exercise->equipments()->detach(); } catch (\Throwable $e) {}
|
|
try { $exercise->favoritedBy()->detach(); } catch (\Throwable $e) {}
|
|
// Delete aliases and workout_exercises if relations exist
|
|
try { $exercise->aliases()->delete(); } catch (\Throwable $e) {}
|
|
try { $exercise->workoutExercises()->delete(); } catch (\Throwable $e) {}
|
|
|
|
$exercise->delete();
|
|
});
|
|
|
|
return redirect()->route('admin.exercises.index')->with('status', 'Exercise deleted');
|
|
}
|
|
|
|
protected function uniqueSlug(string $name, ?int $ignoreId = null): string
|
|
{
|
|
$base = Str::slug($name);
|
|
if ($base === '') { $base = 'exercise'; }
|
|
$slug = $base;
|
|
$i = 1;
|
|
while (Exercise::where('slug', $slug)
|
|
->when($ignoreId, fn($q)=>$q->where('id','!=',$ignoreId))
|
|
->exists()) {
|
|
$slug = $base . '-' . (++$i);
|
|
}
|
|
return $slug;
|
|
}
|
|
}
|