Files
DailyMeals/MealPlan.Api/Controllers/DietController.cs
Piotr Kus f15bb7a916 * Extended functionalities
* Added different UIs
2026-06-24 21:43:40 +02:00

285 lines
13 KiB
C#

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;
/// <summary>Daily intake summary with consumed totals, goals, and remaining amounts.</summary>
[HttpGet("summary")]
[ProducesResponseType(typeof(DailySummaryDto), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<MealConsumptionDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<DietSuggestionDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<IActionResult> 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<DrinkCatalogItemDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> GetDrinkCatalog(CancellationToken ct)
{
var catalog = await _dietService.GetDrinkCatalogAsync(ct);
return Ok(catalog);
}
[HttpGet("drinks")]
[ProducesResponseType(typeof(IReadOnlyList<DrinkConsumptionDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<DietReminderDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<DueReminderDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<HistoryDayDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<RecentMealDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<IActionResult> 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<FavoriteRecipeDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<FavoriteCatalogItemDto>), StatusCodes.Status200OK)]
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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();
}
}