integer('per_page', 15), 100)); $mealPosts = MealPosts::query() ->where('user_id', $request->user()->getKey()) ->with('user:id,name,avatar_url') ->latest('eaten_at') ->paginate($perPage) ->withQueryString(); return MealPostsResource::collection($mealPosts); } public function stats(MealPostStatsRequest $request): JsonResponse { $validated = $request->validated(); $period = $validated['period'] ?? 'day'; $referenceDate = CarbonImmutable::createFromFormat('!Y-m-d', $validated['date'] ?? now()->toDateString()); [$startDate, $endDate] = match ($period) { 'week' => [ $referenceDate->startOfWeek(CarbonInterface::MONDAY), $referenceDate->endOfWeek(CarbonInterface::SUNDAY), ], default => [ $referenceDate->startOfDay(), $referenceDate->endOfDay(), ], }; $mealPosts = MealPosts::query() ->where('user_id', $request->user()->getKey()) ->whereBetween('eaten_at', [$startDate, $endDate]) ->get(['calories', 'proteins', 'carbs', 'fats', 'eaten_at']); return response()->json([ 'data' => [ 'period' => $period, 'date' => $referenceDate->toDateString(), 'startDate' => $startDate->toDateString(), 'endDate' => $endDate->toDateString(), 'totals' => $this->getNutritionTotals($mealPosts), 'days' => $this->getDailyNutritionTotals($mealPosts, $startDate, $endDate), ], ]); } public function store(MealPostsRequest $request): JsonResponse { $mealPost = MealPosts::create([ ...$this->getValidatedDataWithImageUrl($request), 'user_id' => $request->user()->getKey(), ]); $mealPost->setRelation('user', $request->user()); return (new MealPostsResource($mealPost)) ->response() ->setStatusCode(201); } public function show(Request $request, MealPosts $mealPost): MealPostsResource { $this->abortIfNotOwner($request, $mealPost); return new MealPostsResource($mealPost->loadMissing('user:id,name,avatar_url')); } public function update(MealPostsRequest $request, MealPosts $mealPost): MealPostsResource { $this->abortIfNotOwner($request, $mealPost); $mealPost->update($this->getValidatedDataWithImageUrl($request)); return new MealPostsResource($mealPost->refresh()->loadMissing('user:id,name,avatar_url')); } public function destroy(Request $request, MealPosts $mealPost): Response { $this->abortIfNotOwner($request, $mealPost); $mealPost->delete(); return response()->noContent(); } private function abortIfNotOwner(Request $request, MealPosts $mealPost): void { abort_unless($request->user(), 401); abort_unless($mealPost->user_id === $request->user()->getKey(), 404); } private function getValidatedDataWithImageUrl(MealPostsRequest $request): array { $data = $request->validated(); if ($request->hasFile('image')) { $path = $request->file('image')->store('meal-posts', 'public'); $data['image_url'] = asset(Storage::url($path)); } unset($data['image']); return $data; } private function getDailyNutritionTotals(Collection $mealPosts, CarbonImmutable $startDate, CarbonImmutable $endDate): array { $days = []; for ($date = $startDate; $date->lte($endDate); $date = $date->addDay()) { $day = $date->toDateString(); $dayMealPosts = $mealPosts->filter( fn (MealPosts $mealPost): bool => $mealPost->eaten_at->toDateString() === $day ); $days[] = [ 'date' => $day, 'totals' => $this->getNutritionTotals($dayMealPosts), ]; } return $days; } private function getNutritionTotals(Collection $mealPosts): array { return [ 'calories' => (int) $mealPosts->sum(fn (MealPosts $mealPost): int => (int) ($mealPost->calories ?? 0)), 'proteins' => round((float) $mealPosts->sum(fn (MealPosts $mealPost): float => (float) ($mealPost->proteins ?? 0)), 2), 'carbs' => round((float) $mealPosts->sum(fn (MealPosts $mealPost): float => (float) ($mealPost->carbs ?? 0)), 2), 'fats' => round((float) $mealPosts->sum(fn (MealPosts $mealPost): float => (float) ($mealPost->fats ?? 0)), 2), 'mealsCount' => $mealPosts->count(), ]; } }