Initial commit

This commit is contained in:
2026-06-04 06:24:56 +02:00
commit 282f4864c4
111 changed files with 8083 additions and 0 deletions

View 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();
}
}