Initial commit
This commit is contained in:
107
MealPlan.Api/Services/RecipeService.cs
Normal file
107
MealPlan.Api/Services/RecipeService.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using MealPlan.Api.Data;
|
||||
using MealPlan.Api.DTOs.Recipe;
|
||||
using MealPlan.Api.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace MealPlan.Api.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Read-only recipe queries. All filtering is done with EF Core LINQ (parameterized);
|
||||
/// no raw SQL is used anywhere.
|
||||
/// </summary>
|
||||
public class RecipeService : IRecipeService
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
|
||||
public RecipeService(AppDbContext db) => _db = db;
|
||||
|
||||
public async Task<IReadOnlyList<RecipeListItemDto>> GetRecipesAsync(
|
||||
int? category, string? search, CancellationToken ct = default)
|
||||
{
|
||||
var query = _db.Recipes.AsNoTracking().Where(r => r.IsActive);
|
||||
|
||||
if (category is >= 0 and <= 3)
|
||||
{
|
||||
query = query.Where(r => r.MealCategory == category);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var term = search.Trim();
|
||||
query = query.Where(r => EF.Functions.ILike(r.Name, $"%{term}%"));
|
||||
}
|
||||
|
||||
return await query
|
||||
.OrderBy(r => r.Name)
|
||||
.Select(r => new RecipeListItemDto
|
||||
{
|
||||
Id = r.Id,
|
||||
Name = r.Name,
|
||||
MealCategory = r.MealCategory,
|
||||
Calories = r.Calories,
|
||||
PrepTimeMinutes = r.PrepTimeMinutes,
|
||||
})
|
||||
.ToListAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<RecipeDetailDto?> GetRecipeByIdAsync(int id, CancellationToken ct = default)
|
||||
{
|
||||
return await _db.Recipes
|
||||
.AsNoTracking()
|
||||
.Where(r => r.Id == id && r.IsActive)
|
||||
.Select(r => new RecipeDetailDto
|
||||
{
|
||||
Id = r.Id,
|
||||
Name = r.Name,
|
||||
MealCategory = r.MealCategory,
|
||||
Calories = r.Calories,
|
||||
Protein = r.Protein,
|
||||
Fat = r.Fat,
|
||||
Carbs = r.Carbs,
|
||||
PrepTimeMinutes = r.PrepTimeMinutes,
|
||||
Ingredients = r.Ingredients
|
||||
.OrderBy(i => i.SortOrder)
|
||||
.ThenBy(i => i.Id)
|
||||
.Select(i => new IngredientDto
|
||||
{
|
||||
Id = i.Id,
|
||||
Name = i.Name,
|
||||
AmountGrams = i.AmountGrams,
|
||||
Unit = i.Unit,
|
||||
SortOrder = i.SortOrder,
|
||||
})
|
||||
.ToList(),
|
||||
Steps = r.Steps
|
||||
.OrderBy(s => s.StepNumber)
|
||||
.Select(s => new RecipeStepDto
|
||||
{
|
||||
Id = s.Id,
|
||||
StepNumber = s.StepNumber,
|
||||
Description = s.Description,
|
||||
})
|
||||
.ToList(),
|
||||
})
|
||||
.FirstOrDefaultAsync(ct);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<CategoryCountDto>> GetCategoriesAsync(CancellationToken ct = default)
|
||||
{
|
||||
var counts = await _db.Recipes
|
||||
.AsNoTracking()
|
||||
.Where(r => r.IsActive)
|
||||
.GroupBy(r => r.MealCategory)
|
||||
.Select(g => new { Category = g.Key, Count = g.Count() })
|
||||
.ToListAsync(ct);
|
||||
|
||||
// Always return all four categories, even when a category has zero recipes.
|
||||
return Enum.GetValues<MealCategory>()
|
||||
.Select(category => new CategoryCountDto
|
||||
{
|
||||
Category = (int)category,
|
||||
Name = category.ToString(),
|
||||
Count = counts.FirstOrDefault(c => c.Category == (int)category)?.Count ?? 0,
|
||||
})
|
||||
.OrderBy(c => c.Category)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user