using AutoMapper; using GameIdeas.Shared.Constants; using GameIdeas.Shared.Dto; using GameIdeas.Shared.Exceptions; using GameIdeas.Shared.Model; using GameIdeas.WebAPI.Context; using Microsoft.EntityFrameworkCore; using System.Linq.Expressions; namespace GameIdeas.WebAPI.Services.Games; public class GameReadService(GameIdeasContext context, IMapper mapper) : IGameReadService { public async Task> GetGames(GameFilterDto filter) { var query = context.Games .Include(g => g.GamePlatforms).ThenInclude(gp => gp.Platform) .Include(g => g.GameProperties) .Include(g => g.GameTags).ThenInclude(gt => gt.Tag) .Include(g => g.GamePublishers) .Include(g => g.GameDevelopers) .AsQueryable(); ApplyFilter(ref query, filter); ApplyOrder(ref query, filter); var games = await query.Skip((filter.CurrentPage - 1) * GlobalConstants.NUMBER_PER_PAGE) .Take(GlobalConstants.NUMBER_PER_PAGE) .ToListAsync(); if (!string.IsNullOrWhiteSpace(filter.Title)) { games = ApplySearchGameFilter(games, filter).ToList(); } return mapper.Map>(games); } public async Task GetGameById(int gameId) { var game = await context.Games .Include(g => g.CreationUser) .Include(g => g.ModificationUser) .Include(g => g.GamePlatforms).ThenInclude(p => p.Platform) .Include(g => g.GameProperties).ThenInclude(p => p.Property) .Include(g => g.GameTags).ThenInclude(p => p.Tag) .Include(g => g.GamePublishers).ThenInclude(p => p.Publisher) .Include(g => g.GameDevelopers).ThenInclude(p => p.Developer) .FirstOrDefaultAsync(g => g.Id == gameId); return game == null ? throw new NotFoundException($"[{typeof(Game).FullName}] with ID {gameId} has not been found in context") : mapper.Map(game); } private static void ApplyOrder(ref IQueryable query, GameFilterDto filter) { if (filter.SortType != null && filter.SortPropertyName != null) { var param = Expression.Parameter(typeof(Game), "x"); Expression propertyAccess = Expression.PropertyOrField(param, filter.SortPropertyName); var converted = Expression.Convert(propertyAccess, typeof(object)); var lambda = Expression.Lambda>(converted, param); if (filter.SortType == Shared.Enum.SortType.Ascending) { query = Queryable.OrderBy(query, lambda); } else { query = Queryable.OrderByDescending(query, lambda); } } } private static void ApplyFilter(ref IQueryable query, GameFilterDto filter) { if (filter.PlatformIds != null) { query = query.Where(game => filter.PlatformIds.All(plat => game.GamePlatforms.Any(gp => gp.PlatformId == plat))); } } private static IEnumerable ApplySearchGameFilter(IEnumerable query, GameFilterDto filter) { var keywords = filter.Title? .Split([' '], StringSplitOptions.RemoveEmptyEntries) .Select(k => k.Trim()) .ToArray() ?? []; query = query .Where(game => keywords.All( kw => game.Title.Contains(kw, StringComparison.OrdinalIgnoreCase) )) .OrderBy(game => keywords.Min(kw => game.Title.IndexOf(kw, StringComparison.OrdinalIgnoreCase) )) .ThenBy(game => game.Title.Length); return query; } }