using MealPlan.Api.Extensions; using MealPlan.Api.DTOs.Diet; using MealPlan.Api.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace MealPlan.Api.Controllers; [ApiController] [Route("api/diet")] [Authorize] public class DietController : ControllerBase { private readonly IDietTrackingService _dietService; public DietController(IDietTrackingService dietService) => _dietService = dietService; /// Daily intake summary with consumed totals, goals, and remaining amounts. [HttpGet("summary")] [ProducesResponseType(typeof(DailySummaryDto), StatusCodes.Status200OK)] public async Task GetSummary([FromQuery] DateOnly? date, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var targetDate = date ?? DateOnly.FromDateTime(DateTime.UtcNow); var summary = await _dietService.GetDailySummaryAsync(userId, targetDate, ct); return Ok(summary); } [HttpGet("goals")] [ProducesResponseType(typeof(UserDailyGoalsDto), StatusCodes.Status200OK)] public async Task GetGoals(CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var goals = await _dietService.GetGoalsAsync(userId, ct); return Ok(goals ?? new UserDailyGoalsDto()); } [HttpPut("goals")] [ProducesResponseType(typeof(UserDailyGoalsDto), StatusCodes.Status200OK)] public async Task UpsertGoals([FromBody] UserDailyGoalsDto dto, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var goals = await _dietService.UpsertGoalsAsync(userId, dto, ct); return Ok(goals); } [HttpGet("meals")] [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] public async Task GetMeals([FromQuery] DateOnly? date, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var targetDate = date ?? DateOnly.FromDateTime(DateTime.UtcNow); var meals = await _dietService.GetMealsAsync(userId, targetDate, ct); return Ok(meals); } [HttpPost("meals")] [ProducesResponseType(typeof(MealConsumptionDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task LogMeal([FromBody] LogMealDto dto, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var meal = await _dietService.LogMealAsync(userId, dto, ct); if (meal is null) { return dto.RecipeId.HasValue ? NotFound(new { error = $"Recipe {dto.RecipeId} was not found." }) : BadRequest(new { error = "Invalid meal log payload." }); } return CreatedAtAction(nameof(GetMeals), new { date = meal.LogDate }, meal); } [HttpPost("meals/preview")] [ProducesResponseType(typeof(MealPreviewDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task PreviewMeal([FromQuery] DateOnly? date, [FromBody] LogMealDto dto, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var targetDate = date ?? DateOnly.FromDateTime(DateTime.UtcNow); var preview = await _dietService.PreviewMealAsync(userId, targetDate, dto, ct); return preview is null ? BadRequest(new { error = "Could not preview meal macros." }) : Ok(preview); } [HttpGet("suggestions")] [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] public async Task GetSuggestions([FromQuery] DateOnly? date, [FromQuery] int limit = 5, CancellationToken ct = default) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var targetDate = date ?? DateOnly.FromDateTime(DateTime.UtcNow); var suggestions = await _dietService.GetMealSuggestionsAsync(userId, targetDate, limit, ct); return Ok(suggestions); } [HttpDelete("meals/{id:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task DeleteMeal(int id, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var deleted = await _dietService.DeleteMealAsync(userId, id, ct); return deleted ? NoContent() : NotFound(new { error = $"Meal log {id} was not found." }); } [HttpGet("drinks/catalog")] [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] public async Task GetDrinkCatalog(CancellationToken ct) { var catalog = await _dietService.GetDrinkCatalogAsync(ct); return Ok(catalog); } [HttpGet("drinks")] [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] public async Task GetDrinks([FromQuery] DateOnly? date, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var targetDate = date ?? DateOnly.FromDateTime(DateTime.UtcNow); var drinks = await _dietService.GetDrinksAsync(userId, targetDate, ct); return Ok(drinks); } [HttpPost("drinks")] [ProducesResponseType(typeof(DrinkConsumptionDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task LogDrink([FromBody] LogDrinkDto dto, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var drink = await _dietService.LogDrinkAsync(userId, dto, ct); if (drink is null) { return NotFound(new { error = "Drink catalog item was not found." }); } return CreatedAtAction(nameof(GetDrinks), new { date = drink.LogDate }, drink); } [HttpDelete("drinks/{id:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task DeleteDrink(int id, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var deleted = await _dietService.DeleteDrinkAsync(userId, id, ct); return deleted ? NoContent() : NotFound(new { error = $"Drink log {id} was not found." }); } [HttpGet("reminders")] [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] public async Task GetReminders(CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var reminders = await _dietService.GetRemindersAsync(userId, ct); return Ok(reminders); } [HttpGet("reminders/due")] [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] public async Task GetDueReminders( [FromQuery] DateOnly? date, [FromQuery] TimeOnly? time, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var due = await _dietService.GetDueRemindersAsync(userId, date, time, ct); return Ok(due); } [HttpPost("reminders")] [ProducesResponseType(typeof(DietReminderDto), StatusCodes.Status201Created)] public async Task CreateReminder([FromBody] CreateDietReminderDto dto, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var reminder = await _dietService.CreateReminderAsync(userId, dto, ct); return CreatedAtAction(nameof(GetReminders), reminder); } [HttpPut("reminders/{id:int}")] [ProducesResponseType(typeof(DietReminderDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task UpdateReminder(int id, [FromBody] UpdateDietReminderDto dto, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var reminder = await _dietService.UpdateReminderAsync(userId, id, dto, ct); return reminder is null ? NotFound(new { error = $"Reminder {id} was not found." }) : Ok(reminder); } [HttpDelete("reminders/{id:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task DeleteReminder(int id, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var deleted = await _dietService.DeleteReminderAsync(userId, id, ct); return deleted ? NoContent() : NotFound(new { error = $"Reminder {id} was not found." }); } [HttpGet("history")] [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] public async Task GetHistory([FromQuery] DateOnly from, [FromQuery] DateOnly to, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var history = await _dietService.GetHistoryAsync(userId, from, to, ct); return Ok(history); } [HttpGet("meals/recent")] [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] public async Task GetRecentMeals([FromQuery] int limit = 10, CancellationToken ct = default) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var meals = await _dietService.GetRecentMealsAsync(userId, limit, ct); return Ok(meals); } [HttpPost("meals/copy-yesterday")] [ProducesResponseType(typeof(CopyMealsResultDto), StatusCodes.Status200OK)] public async Task CopyYesterdayMeals([FromQuery] DateOnly? date, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var targetDate = date ?? DateOnly.FromDateTime(DateTime.UtcNow); var result = await _dietService.CopyYesterdayMealsAsync(userId, targetDate, ct); return Ok(result); } [HttpGet("favorites/recipes")] [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] public async Task GetFavoriteRecipes(CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; return Ok(await _dietService.GetFavoriteRecipesAsync(userId, ct)); } [HttpPost("favorites/recipes/{recipeId:int}")] [ProducesResponseType(typeof(FavoriteRecipeDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task AddFavoriteRecipe(int recipeId, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var fav = await _dietService.AddFavoriteRecipeAsync(userId, recipeId, ct); return fav is null ? NotFound() : Ok(fav); } [HttpDelete("favorites/recipes/{recipeId:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task RemoveFavoriteRecipe(int recipeId, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var deleted = await _dietService.RemoveFavoriteRecipeAsync(userId, recipeId, ct); return deleted ? NoContent() : NotFound(); } [HttpGet("favorites/catalog")] [ProducesResponseType(typeof(IReadOnlyList), StatusCodes.Status200OK)] public async Task GetFavoriteCatalogItems(CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; return Ok(await _dietService.GetFavoriteCatalogItemsAsync(userId, ct)); } [HttpPost("favorites/catalog/{catalogItemId:int}")] [ProducesResponseType(typeof(FavoriteCatalogItemDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task AddFavoriteCatalogItem(int catalogItemId, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var fav = await _dietService.AddFavoriteCatalogItemAsync(userId, catalogItemId, ct); return fav is null ? NotFound() : Ok(fav); } [HttpDelete("favorites/catalog/{catalogItemId:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task RemoveFavoriteCatalogItem(int catalogItemId, CancellationToken ct) { if (User.RequireUserId(out var userId) is { } unauthorized) return unauthorized; var deleted = await _dietService.RemoveFavoriteCatalogItemAsync(userId, catalogItemId, ct); return deleted ? NoContent() : NotFound(); } }