using MealPlan.Api.DTOs.Recipe; using MealPlan.Api.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace MealPlan.Api.Controllers; /// Endpoints for creating recipes manually and importing from Excel. [ApiController] [Route("api/recipes")] [Authorize] public class RecipeManagementController : ControllerBase { private readonly IRecipeManagementService _managementService; private readonly IRecipeExcelService _excelService; public RecipeManagementController( IRecipeManagementService managementService, IRecipeExcelService excelService) { _managementService = managementService; _excelService = excelService; } /// Creates a new recipe with ingredients and steps. [HttpPost] [ProducesResponseType(typeof(RecipeDetailDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task Create([FromBody] CreateRecipeDto dto, CancellationToken ct) { var recipe = await _managementService.CreateRecipeAsync(dto, ct); return CreatedAtAction( nameof(RecipesController.GetRecipe), "Recipes", new { id = recipe.Id }, recipe); } /// Updates an existing recipe (replaces ingredients and steps). [HttpPut("{id:int}")] [ProducesResponseType(typeof(RecipeDetailDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task Update(int id, [FromBody] CreateRecipeDto dto, CancellationToken ct) { var recipe = await _managementService.UpdateRecipeAsync(id, dto, ct); return recipe is null ? NotFound(new { error = $"Recipe {id} was not found." }) : Ok(recipe); } /// Downloads an Excel template for bulk recipe import. [HttpGet("import/template")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] public IActionResult DownloadTemplate() { var bytes = _excelService.GenerateTemplate(); return File( bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "DailyMeals-recipe-import-template.xlsx"); } /// Imports recipes from an uploaded .xlsx file (Recipes + Ingredients + Steps sheets). [HttpPost("import/excel")] [RequestSizeLimit(10_000_000)] [ProducesResponseType(typeof(RecipeImportResultDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public async Task ImportExcel(IFormFile? file, CancellationToken ct) { if (file is null || file.Length == 0) { return BadRequest(new { error = "No file uploaded." }); } if (!file.FileName.EndsWith(".xlsx", StringComparison.OrdinalIgnoreCase)) { return BadRequest(new { error = "Only .xlsx files are supported." }); } await using var stream = file.OpenReadStream(); var result = await _excelService.ImportAsync(stream, ct); return Ok(result); } /// Recalculates recipe macros from linked catalog ingredients (all active recipes). [HttpPost("recalculate-macros")] [ProducesResponseType(typeof(RecipeMacroRecalcResultDto), StatusCodes.Status200OK)] public async Task RecalculateMacros(CancellationToken ct) { var result = await _managementService.RecalculateAllMacrosFromCatalogAsync(ct); return Ok(result); } }