middleware('auth'); } public function index() { $workouts = Auth::user()->workouts()->with('assignedByTrainer')->latest()->paginate(10); return view('workouts.index', compact('workouts')); } public function community(Request $request) { $q = trim((string) $request->query('q', '')); $workouts = Workout::query() ->where('is_public', true) ->with(['user:id,name', 'items']) ->when($q !== '', fn ($qb) => $qb->where('title', 'like', "%{$q}%")) ->latest() ->paginate(12) ->withQueryString(); return view('workouts.community', [ 'workouts' => $workouts, 'filters' => ['q' => $q], ]); } public function togglePublic(Workout $workout) { if ($workout->user_id !== Auth::id()) { abort(403); } $workout->is_public = !$workout->is_public; $workout->save(); return back()->with('status', $workout->is_public ? 'Workout is now public' : 'Workout is now private'); } public function copy(Workout $workout) { if (!$workout->is_public && $workout->user_id !== Auth::id()) { abort(403); } $workout->loadMissing('items'); $copy = Auth::user()->workouts()->create([ 'title' => $workout->title . ' (copy)', 'notes' => $workout->notes, 'estimated_minutes' => $workout->estimated_minutes, ]); foreach ($workout->items as $item) { $copy->items()->create([ 'exercise_id' => $item->exercise_id, 'position' => $item->position, 'sets' => $item->sets, 'reps' => $item->reps, 'weight' => $item->weight, 'rest_seconds' => $item->rest_seconds, 'notes' => $item->notes, ]); } return redirect()->route('workouts.show', $copy)->with('status', 'Workout copied to your library'); } public function create() { // Simple create form (builder-like) return view('workouts.create'); } public function store(Request $request) { $data = $request->validate([ 'title' => 'required|string|max:255', 'notes' => 'nullable|string', 'estimated_minutes' => 'nullable|integer|min:1|max:600', ]); $workout = Auth::user()->workouts()->create($data); return redirect()->route('workouts.show', $workout)->with('status', 'Workout created'); } public function show(Workout $workout) { $this->authorizeView($workout); $workout->load(['items.exercise', 'assignedByTrainer', 'user:id,name']); $exercises = Exercise::orderBy('name')->limit(20)->get(['id','name','slug']); return view('workouts.show', compact('workout', 'exercises')); } public function searchExercises(Request $request, Workout $workout) { $this->authorizeWorkout($workout); $q = trim((string) $request->query('q', '')); $exercises = Exercise::query() ->with(['aliases' => fn ($rel) => $rel->where('user_id', Auth::id())]) ->when($q !== '', fn ($qb) => $qb->where('name', 'like', "%{$q}%")) ->orderBy('name') ->limit(20) ->get(['id', 'name']) ->map(fn ($e) => ['id' => $e->id, 'name' => $e->display_name]); return response()->json($exercises); } // Legacy builder route -> redirect to create public function builder() { return redirect()->route('workouts.create'); } public function addExercise(Request $request, Workout $workout) { $this->authorizeWorkout($workout); $data = $request->validate([ 'exercise_id' => 'required|exists:exercises,id', 'position' => 'nullable|integer|min:1', 'sets' => 'nullable|integer|min:1|max:20', 'reps' => 'nullable|integer|min:1|max:200', 'weight' => 'nullable|numeric|min:0|max:999.99', 'rest_seconds' => 'nullable|integer|min:0|max:600', 'notes' => 'nullable|string', ]); $nextPos = ($workout->items()->max('position') ?? 0) + 1; $data['position'] = $data['position'] ?? $nextPos; $item = $workout->items()->create($data); return back()->with('status', 'Exercise added to workout'); } public function updateExercise(Request $request, Workout $workout, WorkoutExercise $item) { $this->authorizeWorkout($workout); if ($item->workout_id !== $workout->id) { abort(404); } $data = $request->validate([ 'position' => 'nullable|integer|min:1', 'sets' => 'nullable|integer|min:1|max:20', 'reps' => 'nullable|integer|min:1|max:200', 'weight' => 'nullable|numeric|min:0|max:999.99', 'rest_seconds' => 'nullable|integer|min:0|max:600', 'notes' => 'nullable|string', ]); $item->update($data); return back()->with('status', 'Exercise updated'); } public function removeExercise(Workout $workout, WorkoutExercise $item) { $this->authorizeWorkout($workout); if ($item->workout_id !== $workout->id) { abort(404); } $item->delete(); return back()->with('status', 'Exercise removed'); } public function destroy(Workout $workout) { // Deletion stays strictly owner-only, even for a trainer-assigned workout. if ($workout->user_id !== Auth::id()) { abort(403); } $workout->delete(); return redirect()->route('workouts.index')->with('status', 'Workout deleted'); } // Allows the owner, or the trainer who assigned this workout, to view and edit it. // Session/completion data is intentionally not exposed to the trainer here - only // the workout structure they authored. protected function authorizeWorkout(Workout $workout): void { if ($workout->user_id === Auth::id()) { return; } if ($workout->assigned_by_trainer_id && $workout->assigned_by_trainer_id === Auth::id()) { return; } abort(403); } // Read-only: anyone can view a public workout (to decide whether to copy it), // in addition to everyone authorizeWorkout() already allows. protected function authorizeView(Workout $workout): void { if ($workout->is_public) { return; } $this->authorizeWorkout($workout); } }