From e192df49e7546e581cf968c8002ce57f17be8f64 Mon Sep 17 00:00:00 2001 From: Maxime Adler Date: Thu, 17 Apr 2025 12:49:31 +0200 Subject: [PATCH 01/11] Add game and game detail dtos --- .../GameIdeas.BlazorApp/Helpers/GameHelper.cs | 2 +- .../Components/GameCreationForm.razor.cs | 2 +- .../Pages/Games/Components/GameValidation.cs | 2 +- .../Pages/Games/Gateways/GameGateway.cs | 2 +- .../Pages/Games/Gateways/IGameGateway.cs | 2 +- .../GameIdeas.Shared/Dto/GameDetailDto.cs | 20 +++++++++++++++++++ src/GameIdeas/GameIdeas.Shared/Dto/GameDto.cs | 14 ++----------- .../GameIdeas.Shared/Dto/SortPropertyDto.cs | 2 +- .../Controllers/GameController.cs | 8 ++++---- .../GameIdeas.WebAPI/Profiles/GameProfile.cs | 14 ++++++++++--- .../GameIdeas.WebAPI/Services/GameService.cs | 12 +++++------ .../Services/Interfaces/IGameService.cs | 6 +++--- 12 files changed, 52 insertions(+), 34 deletions(-) create mode 100644 src/GameIdeas/GameIdeas.Shared/Dto/GameDetailDto.cs diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Helpers/GameHelper.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Helpers/GameHelper.cs index 8f830c2..b737d80 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Helpers/GameHelper.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Helpers/GameHelper.cs @@ -4,7 +4,7 @@ namespace GameIdeas.BlazorApp.Helpers; public static class GameHelper { - public static void WriteTrackingDto(GameDto game) + public static void WriteTrackingDto(GameDetailDto game) { game.CreationUserId = 100000; game.CreationDate = DateTime.Now; diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameCreationForm.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameCreationForm.razor.cs index 358d442..895fb72 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameCreationForm.razor.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameCreationForm.razor.cs @@ -18,7 +18,7 @@ public partial class GameCreationForm [Parameter] public CategoriesDto? Categories { get; set; } [Parameter] public EventCallback OnSubmit { get; set; } - private GameDto GameDto = new(); + private GameDetailDto GameDto = new(); private EditContext? EditContext; private readonly SelectTheme Theme = SelectTheme.Creation; private readonly SliderParams SliderParams = new() { Gap = 1, Min = 1, Max = 5 }; diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameValidation.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameValidation.cs index 12eff58..34de4ed 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameValidation.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameValidation.cs @@ -4,7 +4,7 @@ using GameIdeas.Shared.Dto; namespace GameIdeas.BlazorApp.Pages.Games.Components; -public class GameValidation : AbstractValidator +public class GameValidation : AbstractValidator { public GameValidation() { diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/GameGateway.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/GameGateway.cs index 25da217..ff68e56 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/GameGateway.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/GameGateway.cs @@ -8,7 +8,7 @@ namespace GameIdeas.BlazorApp.Pages.Games.Gateways; public class GameGateway(IHttpClientService httpClientService) : IGameGateway { - public async Task CreateGame(GameDto game) + public async Task CreateGame(GameDetailDto game) { try { diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/IGameGateway.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/IGameGateway.cs index 387d846..645a988 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/IGameGateway.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/IGameGateway.cs @@ -5,6 +5,6 @@ namespace GameIdeas.BlazorApp.Pages.Games.Gateways; public interface IGameGateway { Task FetchCategories(); - Task CreateGame(GameDto game); + Task CreateGame(GameDetailDto game); Task> FetchGames(PaggingDto pagging); } diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/GameDetailDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/GameDetailDto.cs new file mode 100644 index 0000000..6510480 --- /dev/null +++ b/src/GameIdeas/GameIdeas.Shared/Dto/GameDetailDto.cs @@ -0,0 +1,20 @@ +namespace GameIdeas.Shared.Dto; + +public class GameDetailDto +{ + public int? Id { get; set; } + public string? Title { get; set; } + public DateTime? ReleaseDate { get; set; } + public DateTime? CreationDate { get; set; } + public int CreationUserId { get; set; } + public DateTime? ModificationDate { get; set; } + public int? ModificationUserId { get; set; } + public double? StorageSpace { get; set; } + public string? Description { get; set; } + public int Interest { get; set; } = 3; + public List? Platforms { get; set; } + public List? Properties { get; set; } + public List? Tags { get; set; } + public List? Publishers { get; set; } + public List? Developers { get; set; } +} \ No newline at end of file diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/GameDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/GameDto.cs index dd2afe0..aa87436 100644 --- a/src/GameIdeas/GameIdeas.Shared/Dto/GameDto.cs +++ b/src/GameIdeas/GameIdeas.Shared/Dto/GameDto.cs @@ -5,18 +5,8 @@ public class GameDto public int? Id { get; set; } public string? Title { get; set; } public DateTime? ReleaseDate { get; set; } - public DateTime? CreationDate { get; set; } - public UserDto? CreationUser { get; set; } - public int CreationUserId { get; set; } - public DateTime? ModificationDate { get; set; } - public UserDto? ModificationUser { get; set; } - public int? ModificationUserId { get; set; } + public IEnumerable? Platforms { get; set; } + public IEnumerable? Tags { get; set; } public double? StorageSpace { get; set; } - public string? Description { get; set; } public int Interest { get; set; } = 3; - public List? Platforms { get; set; } - public List? Properties { get; set; } - public List? Tags { get; set; } - public List? Publishers { get; set; } - public List? Developers { get; set; } } \ No newline at end of file diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/SortPropertyDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/SortPropertyDto.cs index 5f54704..9d6343e 100644 --- a/src/GameIdeas/GameIdeas.Shared/Dto/SortPropertyDto.cs +++ b/src/GameIdeas/GameIdeas.Shared/Dto/SortPropertyDto.cs @@ -2,6 +2,6 @@ public class SortPropertyDto { - public Func? SortProperty { get; set; } + public Func? SortProperty { get; set; } public string Label { get; set; } = string.Empty; } diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs index 0db432b..ba68265 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs @@ -12,7 +12,7 @@ public class GameController(IGameService gameService, ILoggerFactory loggerFacto private readonly ILogger logger = loggerFactory.CreateLogger(); [HttpGet] - public async Task>> GetGames([FromQuery] PaggingDto pagging) + public async Task>> SearchGames([FromQuery] PaggingDto pagging) { try { @@ -26,7 +26,7 @@ public class GameController(IGameService gameService, ILoggerFactory loggerFacto } [HttpGet("{id:int}")] - public async Task> GetGameById(int id) + public async Task> GetGameById(int id) { try { @@ -40,7 +40,7 @@ public class GameController(IGameService gameService, ILoggerFactory loggerFacto } [HttpPost("Create")] - public async Task> CreateGame([FromBody] GameDto game) + public async Task> CreateGame([FromBody] GameDetailDto game) { try { @@ -55,7 +55,7 @@ public class GameController(IGameService gameService, ILoggerFactory loggerFacto } [HttpPut("Update")] - public async Task> UpdateGame([FromBody] GameDto game) + public async Task> UpdateGame([FromBody] GameDetailDto game) { try { diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs index e87e187..e0e5d73 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs @@ -8,16 +8,14 @@ public class GameProfile : Profile { public GameProfile() { - CreateMap() + CreateMap() .ForMember(d => d.Id, o => o.MapFrom(s => s.Id)) .ForMember(d => d.Title, o => o.MapFrom(s => s.Title)) .ForMember(d => d.ReleaseDate, o => o.MapFrom(s => s.ReleaseDate)) .ForMember(d => d.CreationDate, o => o.MapFrom(s => s.CreationDate)) .ForMember(d => d.CreationUserId, o => o.MapFrom(s => s.CreationUserId)) - .ForMember(d => d.CreationUser, o => o.MapFrom(s => s.CreationUser)) .ForMember(d => d.ModificationDate, o => o.MapFrom(s => s.ModificationDate)) .ForMember(d => d.ModificationUserId, o => o.MapFrom(s => s.ModificationUserId)) - .ForMember(d => d.ModificationUser, o => o.MapFrom(s => s.ModificationUser)) .ForMember(d => d.StorageSpace, o => o.MapFrom(s => s.StorageSpace)) .ForMember(d => d.Description, o => o.MapFrom(s => s.Description)) .ForMember(d => d.Interest, o => o.MapFrom(s => s.Interest)) @@ -27,5 +25,15 @@ public class GameProfile : Profile .ForMember(d => d.Publishers, o => o.MapFrom(s => s.GamePublishers.Select(p => p.Publisher))) .ForMember(d => d.Developers, o => o.MapFrom(s => s.GameDevelopers.Select(gd => gd.Developer))) .ReverseMap(); + + CreateMap() + .ForMember(d => d.Id, o => o.MapFrom(s => s.Id)) + .ForMember(d => d.Title, o => o.MapFrom(s => s.Title)) + .ForMember(d => d.ReleaseDate, o => o.MapFrom(s => s.ReleaseDate)) + .ForMember(d => d.Platforms, o => o.MapFrom(s => s.GamePlatforms.Select(p => new PlatformDto() { Id = p.Platform.Id, Label = p.Platform.Label, Url = p.Url }))) + .ForMember(d => d.Tags, o => o.MapFrom(s => s.GameTags.Select(t => t.Tag))) + .ForMember(d => d.StorageSpace, o => o.MapFrom(s => s.StorageSpace)) + .ForMember(d => d.Interest, o => o.MapFrom(s => s.Interest)) + .ReverseMap(); } } diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs index a00b216..91625ce 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs @@ -21,17 +21,17 @@ public class GameService(GameIdeasContext context, IMapper mapper) : IGameServic return mapper.Map>(games); } - public async Task GetGameById(int gameId) + public async Task GetGameById(int gameId) { var game = await SelectGames() .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); + : mapper.Map(game); } - public async Task CreateGame(GameDto gameDto) + public async Task CreateGame(GameDetailDto gameDto) { var gameToCreate = mapper.Map(gameDto); @@ -46,10 +46,10 @@ public class GameService(GameIdeasContext context, IMapper mapper) : IGameServic await context.SaveChangesAsync(); - return mapper.Map(gameToCreate); + return mapper.Map(gameToCreate); } - public async Task UpdateGame(GameDto gameDto) + public async Task UpdateGame(GameDetailDto gameDto) { if (await context.Games.CountAsync(g => g.Id == gameDto.Id) == 0) { @@ -67,7 +67,7 @@ public class GameService(GameIdeasContext context, IMapper mapper) : IGameServic context.Games.Update(gameToUpdate); await context.SaveChangesAsync(); - return mapper.Map(gameToUpdate); + return mapper.Map(gameToUpdate); } public async Task DeleteGame(int gameId) diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Interfaces/IGameService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Interfaces/IGameService.cs index efc11cc..0b3e974 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Interfaces/IGameService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Interfaces/IGameService.cs @@ -5,8 +5,8 @@ namespace GameIdeas.WebAPI.Services.Interfaces; public interface IGameService { Task> GetGames(PaggingDto pagging); - Task GetGameById(int gameId); - Task CreateGame(GameDto gameDto); - Task UpdateGame(GameDto gameDto); + Task GetGameById(int gameId); + Task CreateGame(GameDetailDto gameDto); + Task UpdateGame(GameDetailDto gameDto); Task DeleteGame(int gameId); } -- 2.39.5 From 33cdfa2841fd23b8064dd69ea19c817f16dd4ccc Mon Sep 17 00:00:00 2001 From: Maxime Adler Date: Thu, 17 Apr 2025 16:28:31 +0200 Subject: [PATCH 02/11] Send game filter dtos --- .../GameIdeas.BlazorApp/Helpers/UrlHelper.cs | 38 +++++++++++++++++++ .../Games/Filter/AdvancedGameFilter.razor.cs | 4 +- .../Pages/Games/Filter/GameFilter.razor | 1 + .../Pages/Games/Filter/GameFilter.razor.cs | 12 +++--- .../Pages/Games/Filter/GameFilterParams.cs | 19 ++++++++++ .../Pages/Games/Game.razor | 4 +- .../Pages/Games/Game.razor.cs | 27 ++++++++++++- .../Pages/Games/Gateways/GameGateway.cs | 22 ++++++++++- .../Pages/Games/Gateways/IGameGateway.cs | 2 +- .../Shared/Constants/Endpoints.cs | 6 +-- .../Constants/GlobalConstants.cs | 2 +- .../GameIdeas.Shared/Dto/GameFilterDto.cs | 18 +++++---- .../GameIdeas.Shared/Dto/PaggingDto.cs | 7 ---- .../GameIdeas.Shared/Dto/SortPropertyDto.cs | 2 +- .../Controllers/GameController.cs | 4 +- .../GameIdeas.WebAPI/Services/GameService.cs | 7 ++-- .../Services/Interfaces/IGameService.cs | 2 +- 17 files changed, 137 insertions(+), 40 deletions(-) create mode 100644 src/GameIdeas/Client/GameIdeas.BlazorApp/Helpers/UrlHelper.cs create mode 100644 src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilterParams.cs delete mode 100644 src/GameIdeas/GameIdeas.Shared/Dto/PaggingDto.cs diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Helpers/UrlHelper.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Helpers/UrlHelper.cs new file mode 100644 index 0000000..2ffd849 --- /dev/null +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Helpers/UrlHelper.cs @@ -0,0 +1,38 @@ +using System.Collections; + +namespace GameIdeas.BlazorApp.Helpers; + +public static class UrlHelper +{ + public static string BuildUrlParams(object? model) + { + if (model == null) + return string.Empty; + + var properties = model.GetType().GetProperties(); + var queryParams = properties + .Select(p => + { + var value = p.GetValue(model); + + switch (value) + { + case null: + return null; + case DateTime dateTime: + return $"{p.Name}={Uri.EscapeDataString(dateTime.ToString("yyyy-MM-dd HH:mm:ss"))}"; + } + + if (value is IEnumerable enumerable and not string) + { + var items = enumerable.Cast() + .Select(item => $"{p.Name}={Uri.EscapeDataString(item?.ToString() ?? string.Empty)}"); + return string.Join("&", items); + } + + return $"{p.Name}={Uri.EscapeDataString(value.ToString() ?? string.Empty)}"; + }) + .ToArray(); + return string.Join("&", queryParams.Where(p => p != null)); + } +} diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/AdvancedGameFilter.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/AdvancedGameFilter.razor.cs index 9447fe8..be03b6b 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/AdvancedGameFilter.razor.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/AdvancedGameFilter.razor.cs @@ -6,9 +6,9 @@ namespace GameIdeas.BlazorApp.Pages.Games.Filter; public partial class AdvancedGameFilter { - [Parameter] public GameFilterDto GameFilter { get; set; } = new(); + [Parameter] public GameFilterParams GameFilter { get; set; } = new(); [Parameter] public CategoriesDto? Categories { get; set; } - [Parameter] public EventCallback GameFilterChanged { get; set; } + [Parameter] public EventCallback GameFilterChanged { get; set; } private readonly SelectTheme Theme = SelectTheme.AdvancedFilter; diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor index 0f2b88a..f864301 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor @@ -31,6 +31,7 @@
diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor.cs index c5f2c1e..749f0f5 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor.cs @@ -10,8 +10,8 @@ namespace GameIdeas.BlazorApp.Pages.Games.Filter; public partial class GameFilter { - [Parameter] public GameFilterDto Value { get; set; } = new(); - [Parameter] public EventCallback ValueChanged { get; set; } + [Parameter] public GameFilterParams Value { get; set; } = new(); + [Parameter] public EventCallback ValueChanged { get; set; } [Parameter] public DisplayType DisplayType { get; set; } [Parameter] public EventCallback DisplayTypeChanged { get; set; } [Parameter] public CategoriesDto? Categories { get; set; } @@ -23,8 +23,10 @@ public partial class GameFilter ]; private readonly List GameProperties = [ - new() { SortProperty = game => game.Title!, Label = "Titre" }, - new() { SortProperty = game => game.ReleaseDate!, Label = "Date de parution" } + new() { PropertyName = nameof(GameDetailDto.Title), Label = "Titre" }, + new() { PropertyName = nameof(GameDetailDto.ReleaseDate), Label = "Date de parution" }, + new() { PropertyName = nameof(GameDetailDto.StorageSpace), Label = "Espace de stockage" }, + new() { PropertyName = nameof(GameDetailDto.Interest), Label = "Interêt" } ]; private SelectParams SelectParams = new(); @@ -39,7 +41,7 @@ public partial class GameFilter DefaultHeaders = SortTypes.Where(h => h.SortType == SortType.Ascending).ToList(), Items = GameProperties, GetItemLabel = item => item.Label, - DefaultItems = GameProperties.Where(p => p.Label == "Titre").ToList() + DefaultItems = GameProperties.Where(p => p.PropertyName == nameof(GameDetailDto.Title)).ToList() }; } diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilterParams.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilterParams.cs new file mode 100644 index 0000000..1fc1f1b --- /dev/null +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilterParams.cs @@ -0,0 +1,19 @@ + +namespace GameIdeas.Shared.Dto; + +public class GameFilterParams +{ + public SortTypeDto? SortType { get; set; } + public SortPropertyDto? SortProperty { get; set; } + public string? Title { get; set; } + public List? Platforms { get; set; } + public List? Properties { get; set; } + public List? Tags { get; set; } + public List? Publishers { get; set; } + public List? Developers { get; set; } + public int MinInterest { get; set; } = 1; + public int MaxInterest { get; set; } = 5; + public List? ReleaseYears { get; set; } + public int? MinStorageSize { get; set; } + public int? MaxStorageSize { get; set; } +} diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor index 8841706..d5011b5 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor @@ -14,7 +14,7 @@ + Value=GameFilter ValueChanged="HandleFilterChanged" />
@@ -36,7 +36,7 @@
- + diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor.cs index f586315..198eeba 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor.cs @@ -11,14 +11,16 @@ public partial class Game [Inject] private IGameGateway GameGateway { get; set; } = default!; private DisplayType DisplayType = DisplayType.List; - private GameFilterDto GameFilter = new(); + private GameFilterParams GameFilter = new(); private Popup? ManualAddPopup; private bool IsLoading = false; private CategoriesDto? Categories; private IEnumerable GamesDto = []; + private int CurrentPage; protected override async Task OnInitializedAsync() { + CurrentPage = 1; await HandleFetchDatas(); await base.OnInitializedAsync(); } @@ -46,7 +48,28 @@ public partial class Game IsLoading = true; Categories = await GameGateway.FetchCategories(); - GamesDto = await GameGateway.FetchGames(new PaggingDto() { CurrentPage = 1, NumberPerPage = 50 }); + GamesDto = await GameGateway.FetchGames(GameFilter, CurrentPage); + } + catch (Exception) + { + throw; + } + finally + { + IsLoading = false; + } + } + private async Task HandleFilterChanged(GameFilterParams args) + { + GameFilter = args; + + + + try + { + IsLoading = true; + + GamesDto = await GameGateway.FetchGames(GameFilter, CurrentPage); } catch (Exception) { diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/GameGateway.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/GameGateway.cs index ff68e56..0cbf89b 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/GameGateway.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/GameGateway.cs @@ -34,11 +34,29 @@ public class GameGateway(IHttpClientService httpClientService) : IGameGateway } } - public async Task> FetchGames(PaggingDto pagging) + public async Task> FetchGames(GameFilterParams filterParams, int currentPage) { try { - var result = await httpClientService.FetchDataAsync>(Endpoints.Game.Fetch(pagging)); + GameFilterDto filter = new() + { + CurrentPage = currentPage, + Title = filterParams.Title, + MaxInterest = filterParams.MaxInterest, + MinInterest = filterParams.MinInterest, + MaxStorageSize = filterParams.MaxStorageSize, + MinStorageSize = filterParams.MinStorageSize, + ReleaseYears = filterParams.ReleaseYears, + DeveloperIds = filterParams.Developers?.Select(d => d.Id ?? 0).ToList(), + PublisherIds = filterParams.Publishers?.Select(d => d.Id ?? 0).ToList(), + PlatformIds = filterParams.Platforms?.Select(d => d.Id ?? 0).ToList(), + PropertyIds = filterParams.Properties?.Select(d => d.Id ?? 0).ToList(), + TagIds = filterParams.Tags?.Select(d => d.Id ?? 0).ToList(), + SortType = filterParams.SortType?.SortType, + SortPropertyName = filterParams.SortProperty?.PropertyName + }; + + var result = await httpClientService.FetchDataAsync>(Endpoints.Game.Fetch(filter)); return result ?? throw new InvalidOperationException(ResourcesKey.ErrorFetchGames); } catch (Exception) diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/IGameGateway.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/IGameGateway.cs index 645a988..cbb8439 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/IGameGateway.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/IGameGateway.cs @@ -6,5 +6,5 @@ public interface IGameGateway { Task FetchCategories(); Task CreateGame(GameDetailDto game); - Task> FetchGames(PaggingDto pagging); + Task> FetchGames(GameFilterParams filter, int currentPage); } diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Endpoints.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Endpoints.cs index 3e6289d..0665ffa 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Endpoints.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Endpoints.cs @@ -1,4 +1,5 @@ -using GameIdeas.Shared.Dto; +using GameIdeas.BlazorApp.Helpers; +using GameIdeas.Shared.Dto; namespace GameIdeas.BlazorApp.Shared.Constants; @@ -7,8 +8,7 @@ public static class Endpoints public static class Game { public static readonly string Create = "api/Game/Create"; - public static string Fetch(PaggingDto pagging) => - $"api/Game?{nameof(pagging.CurrentPage)}={pagging.CurrentPage}&{nameof(pagging.NumberPerPage)}={pagging.NumberPerPage}"; + public static string Fetch(GameFilterDto filter) => $"api/Game?{UrlHelper.BuildUrlParams(filter)}"; } public static class Category diff --git a/src/GameIdeas/GameIdeas.Shared/Constants/GlobalConstants.cs b/src/GameIdeas/GameIdeas.Shared/Constants/GlobalConstants.cs index 373c8b2..ff8ddf9 100644 --- a/src/GameIdeas/GameIdeas.Shared/Constants/GlobalConstants.cs +++ b/src/GameIdeas/GameIdeas.Shared/Constants/GlobalConstants.cs @@ -2,6 +2,6 @@ public class GlobalConstants { - + public static int NUMBER_PER_PAGE = 50; } \ No newline at end of file diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/GameFilterDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/GameFilterDto.cs index d2922c2..7c1fcb2 100644 --- a/src/GameIdeas/GameIdeas.Shared/Dto/GameFilterDto.cs +++ b/src/GameIdeas/GameIdeas.Shared/Dto/GameFilterDto.cs @@ -1,16 +1,18 @@ - +using GameIdeas.Shared.Enum; + namespace GameIdeas.Shared.Dto; public class GameFilterDto { - public SortTypeDto? SortType { get; set; } - public SortPropertyDto? SortProperty { get; set; } + public int CurrentPage { get; set; } = 1; + public SortType? SortType { get; set; } + public string? SortPropertyName { get; set; } public string? Title { get; set; } - public List? Platforms { get; set; } - public List? Properties { get; set; } - public List? Tags { get; set; } - public List? Publishers { get; set; } - public List? Developers { get; set; } + public List? PlatformIds { get; set; } + public List? PropertyIds { get; set; } + public List? TagIds { get; set; } + public List? PublisherIds { get; set; } + public List? DeveloperIds { get; set; } public int MinInterest { get; set; } = 1; public int MaxInterest { get; set; } = 5; public List? ReleaseYears { get; set; } diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/PaggingDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/PaggingDto.cs deleted file mode 100644 index c006966..0000000 --- a/src/GameIdeas/GameIdeas.Shared/Dto/PaggingDto.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace GameIdeas.Shared.Dto; - -public class PaggingDto -{ - public int CurrentPage { get; set; } - public int NumberPerPage { get; set; } -} diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/SortPropertyDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/SortPropertyDto.cs index 9d6343e..1261e67 100644 --- a/src/GameIdeas/GameIdeas.Shared/Dto/SortPropertyDto.cs +++ b/src/GameIdeas/GameIdeas.Shared/Dto/SortPropertyDto.cs @@ -2,6 +2,6 @@ public class SortPropertyDto { - public Func? SortProperty { get; set; } + public string PropertyName { get; set; } = string.Empty; public string Label { get; set; } = string.Empty; } diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs index ba68265..fd0e9a6 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs @@ -12,11 +12,11 @@ public class GameController(IGameService gameService, ILoggerFactory loggerFacto private readonly ILogger logger = loggerFactory.CreateLogger(); [HttpGet] - public async Task>> SearchGames([FromQuery] PaggingDto pagging) + public async Task>> SearchGames([FromQuery] GameFilterDto filter) { try { - return Ok(await gameService.GetGames(pagging)); + return Ok(await gameService.GetGames(filter)); } catch (Exception e) { diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs index 91625ce..b3afab7 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs @@ -1,4 +1,5 @@ using AutoMapper; +using GameIdeas.Shared.Constants; using GameIdeas.Shared.Dto; using GameIdeas.Shared.Exceptions; using GameIdeas.Shared.Model; @@ -10,12 +11,12 @@ namespace GameIdeas.WebAPI.Services; public class GameService(GameIdeasContext context, IMapper mapper) : IGameService { - public async Task> GetGames(PaggingDto pagging) + public async Task> GetGames(GameFilterDto filter) { var games = await SelectGames() .OrderBy(g => g.Title) - .Skip((pagging.CurrentPage - 1) * pagging.NumberPerPage) - .Take(pagging.NumberPerPage) + .Skip((filter.CurrentPage - 1) * GlobalConstants.NUMBER_PER_PAGE) + .Take(GlobalConstants.NUMBER_PER_PAGE) .ToListAsync(); return mapper.Map>(games); diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Interfaces/IGameService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Interfaces/IGameService.cs index 0b3e974..ff2749d 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Interfaces/IGameService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Interfaces/IGameService.cs @@ -4,7 +4,7 @@ namespace GameIdeas.WebAPI.Services.Interfaces; public interface IGameService { - Task> GetGames(PaggingDto pagging); + Task> GetGames(GameFilterDto filter); Task GetGameById(int gameId); Task CreateGame(GameDetailDto gameDto); Task UpdateGame(GameDetailDto gameDto); -- 2.39.5 From 8aa585f2510b52fc1fc0f030d5bffcf7a3022c34 Mon Sep 17 00:00:00 2001 From: Egamorf Date: Thu, 17 Apr 2025 21:18:40 +0200 Subject: [PATCH 03/11] Add js for end text --- .../Pages/Games/Game.razor | 2 +- .../Pages/Games/Game.razor.cs | 31 ++++++------------- .../Components/Search/SearchInput.razor | 6 ++-- .../Components/Search/SearchInput.razor.cs | 11 +++++-- .../Components/Search/SearchInput.razor.js | 19 ++++++++++++ .../GameIdeas.BlazorApp/wwwroot/index.html | 2 +- 6 files changed, 40 insertions(+), 31 deletions(-) create mode 100644 src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.js diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor index d5011b5..369b8bf 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor @@ -40,5 +40,5 @@ - + diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor.cs index 198eeba..4299b9f 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor.cs @@ -18,12 +18,13 @@ public partial class Game private IEnumerable GamesDto = []; private int CurrentPage; - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { CurrentPage = 1; - await HandleFetchDatas(); - await base.OnInitializedAsync(); + await HandleFetchDatas(firstRender); + await base.OnAfterRenderAsync(firstRender); } + private void HandleAddClicked(AddType addType) { switch (addType) @@ -41,13 +42,15 @@ public partial class Game { ManualAddPopup?.Close(); } - private async Task HandleFetchDatas() + private async Task HandleFetchDatas(bool loadCategories = false) { try { IsLoading = true; - Categories = await GameGateway.FetchCategories(); + if (loadCategories) + Categories = await GameGateway.FetchCategories(); + GamesDto = await GameGateway.FetchGames(GameFilter, CurrentPage); } catch (Exception) @@ -62,22 +65,6 @@ public partial class Game private async Task HandleFilterChanged(GameFilterParams args) { GameFilter = args; - - - - try - { - IsLoading = true; - - GamesDto = await GameGateway.FetchGames(GameFilter, CurrentPage); - } - catch (Exception) - { - throw; - } - finally - { - IsLoading = false; - } + await HandleFetchDatas(false); } } \ No newline at end of file diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor index 886e722..1fe279c 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor @@ -3,14 +3,13 @@
@@ -25,5 +24,4 @@ @GetSearchIcon()
- - + \ No newline at end of file diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.cs index fd0f42e..97a2e29 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.cs @@ -1,11 +1,13 @@ using GameIdeas.BlazorApp.Shared.Constants; using GameIdeas.Shared.Constants; using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; namespace GameIdeas.BlazorApp.Shared.Components.Search; public partial class SearchInput { + [Inject] private IJSRuntime Js { get; set; } = default!; [Parameter] public string? Text { get; set; } [Parameter] public string? Placeholder { get; set; } [Parameter] public bool IsDisable { get; set; } @@ -17,17 +19,20 @@ public partial class SearchInput private ElementReference InputText; - protected override void OnInitialized() + protected override async Task OnAfterRenderAsync(bool firstRender) { Text = string.Empty; + await Js.InvokeVoidAsync("addStopWriteListener", DotNetObjectReference.Create(this)); + await base.OnAfterRenderAsync(firstRender); } public void SetText(string str) { Text = str; } - - private async Task HandleTextChanged() + + [JSInvokable] + public async Task HandleTextEnd() { await TextChanged.InvokeAsync(Text); } diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.js b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.js new file mode 100644 index 0000000..d9ba0d9 --- /dev/null +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.js @@ -0,0 +1,19 @@ +function debounce(func, delay, dotNetReference) { + let timeoutId; + return function () { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + func.apply(this, dotNetReference); + }, delay); + }; +} + +function handleInputStop(dotNetReference) { + console.log("L'utilisateur a arrêté de taper."); + //dotNetReference.invokeMethodAsync("HandleTextEnd"); +} + +function addStopWriteListener(dotNetReference) { + const input = document.getElementById('searchInput'); + input.addEventListener('input', debounce(handleInputStop, 1000, dotNetReference)); // 500ms après la dernière frappe +} \ No newline at end of file diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/wwwroot/index.html b/src/GameIdeas/Client/GameIdeas.BlazorApp/wwwroot/index.html index bc8684e..2b4ad65 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/wwwroot/index.html +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/wwwroot/index.html @@ -27,7 +27,7 @@ - + -- 2.39.5 From 97f78b1d744e3418d3d3621e7d5eb2ad31838e80 Mon Sep 17 00:00:00 2001 From: Egamorf Date: Fri, 18 Apr 2025 00:49:35 +0200 Subject: [PATCH 04/11] Fix input search trigger text changed --- .../Components/Search/SearchInput.razor | 5 ++-- .../Components/Search/SearchInput.razor.cs | 24 +++++++++++-------- .../Components/Search/SearchInput.razor.js | 19 --------------- .../GameIdeas.BlazorApp/wwwroot/index.html | 1 - 4 files changed, 17 insertions(+), 32 deletions(-) delete mode 100644 src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.js diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor index 1fe279c..5c1e7f6 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor @@ -2,14 +2,15 @@ @using GameIdeas.Shared.Constants
-
diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.cs index 97a2e29..d9e250a 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.cs @@ -1,13 +1,10 @@ using GameIdeas.BlazorApp.Shared.Constants; -using GameIdeas.Shared.Constants; using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; namespace GameIdeas.BlazorApp.Shared.Components.Search; public partial class SearchInput { - [Inject] private IJSRuntime Js { get; set; } = default!; [Parameter] public string? Text { get; set; } [Parameter] public string? Placeholder { get; set; } [Parameter] public bool IsDisable { get; set; } @@ -17,13 +14,20 @@ public partial class SearchInput [Parameter] public EventCallback FocusIn { get; set; } [Parameter] public SearchInputIcon Icon { get; set; } - private ElementReference InputText; + private System.Timers.Timer? Timer; - protected override async Task OnAfterRenderAsync(bool firstRender) + protected override void OnInitialized() { Text = string.Empty; - await Js.InvokeVoidAsync("addStopWriteListener", DotNetObjectReference.Create(this)); - await base.OnAfterRenderAsync(firstRender); + Timer = new() + { + Interval = 1000, + AutoReset = false, + }; + + Timer.Elapsed += async (_, _) => await TextChanged.InvokeAsync(Text); + + base.OnInitialized(); } public void SetText(string str) @@ -31,10 +35,10 @@ public partial class SearchInput Text = str; } - [JSInvokable] - public async Task HandleTextEnd() + private void HandleTextChanged() { - await TextChanged.InvokeAsync(Text); + Timer?.Stop(); + Timer?.Start(); } private async Task HandleClearClicked() diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.js b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.js deleted file mode 100644 index d9ba0d9..0000000 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Search/SearchInput.razor.js +++ /dev/null @@ -1,19 +0,0 @@ -function debounce(func, delay, dotNetReference) { - let timeoutId; - return function () { - clearTimeout(timeoutId); - timeoutId = setTimeout(() => { - func.apply(this, dotNetReference); - }, delay); - }; -} - -function handleInputStop(dotNetReference) { - console.log("L'utilisateur a arrêté de taper."); - //dotNetReference.invokeMethodAsync("HandleTextEnd"); -} - -function addStopWriteListener(dotNetReference) { - const input = document.getElementById('searchInput'); - input.addEventListener('input', debounce(handleInputStop, 1000, dotNetReference)); // 500ms après la dernière frappe -} \ No newline at end of file diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/wwwroot/index.html b/src/GameIdeas/Client/GameIdeas.BlazorApp/wwwroot/index.html index 2b4ad65..4400e66 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/wwwroot/index.html +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/wwwroot/index.html @@ -27,7 +27,6 @@
- -- 2.39.5 From 5889216dfa0d57c66b9e4f3d6f0f99ab1d7f93b0 Mon Sep 17 00:00:00 2001 From: Egamorf Date: Fri, 18 Apr 2025 01:01:19 +0200 Subject: [PATCH 05/11] Preprare for add filter --- .../Pages/Games/Game.razor.cs | 6 +-- .../GameIdeas.WebAPI/Services/GameService.cs | 42 ++++++++++++------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor.cs index 4299b9f..45c0dfc 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Game.razor.cs @@ -18,11 +18,11 @@ public partial class Game private IEnumerable GamesDto = []; private int CurrentPage; - protected override async Task OnAfterRenderAsync(bool firstRender) + protected override async Task OnInitializedAsync() { CurrentPage = 1; - await HandleFetchDatas(firstRender); - await base.OnAfterRenderAsync(firstRender); + await HandleFetchDatas(true); + await base.OnInitializedAsync(); } private void HandleAddClicked(AddType addType) diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs index b3afab7..b19d43b 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs @@ -13,9 +13,19 @@ public class GameService(GameIdeasContext context, IMapper mapper) : IGameServic { public async Task> GetGames(GameFilterDto filter) { - var games = await SelectGames() - .OrderBy(g => g.Title) - .Skip((filter.CurrentPage - 1) * GlobalConstants.NUMBER_PER_PAGE) + 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(); @@ -24,7 +34,14 @@ public class GameService(GameIdeasContext context, IMapper mapper) : IGameServic public async Task GetGameById(int gameId) { - var game = await SelectGames() + 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 @@ -81,17 +98,14 @@ public class GameService(GameIdeasContext context, IMapper mapper) : IGameServic return await context.SaveChangesAsync() != 0; } - private IQueryable SelectGames() + private void ApplyOrder(ref IQueryable query, GameFilterDto filter) { - return 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) - .AsQueryable(); + + } + + private void ApplyFilter(ref IQueryable query, GameFilterDto filter) + { + } private async Task HandlePlatformsCreation(IEnumerable? categoriesToCreate, int gameId) -- 2.39.5 From b66bb911f10f3862736813e862225cb1a49d551d Mon Sep 17 00:00:00 2001 From: Maxime Adler Date: Fri, 18 Apr 2025 15:20:13 +0200 Subject: [PATCH 06/11] Add search for games and orders --- .../Pages/Games/Filter/GameFilter.razor.cs | 9 +- .../Controllers/GameController.cs | 17 +-- .../Server/GameIdeas.WebAPI/Program.cs | 6 +- .../{ => Categories}/CategoryService.cs | 2 +- .../ICategoryService.cs | 0 .../Services/Games/GameReadService.cs | 100 ++++++++++++++++++ .../GameWriteService.cs} | 53 +--------- .../Services/Games/IGameReadService.cs | 9 ++ .../IGameWriteService.cs} | 6 +- 9 files changed, 132 insertions(+), 70 deletions(-) rename src/GameIdeas/Server/GameIdeas.WebAPI/Services/{ => Categories}/CategoryService.cs (95%) rename src/GameIdeas/Server/GameIdeas.WebAPI/Services/{Interfaces => Categories}/ICategoryService.cs (100%) create mode 100644 src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameReadService.cs rename src/GameIdeas/Server/GameIdeas.WebAPI/Services/{GameService.cs => Games/GameWriteService.cs} (70%) create mode 100644 src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/IGameReadService.cs rename src/GameIdeas/Server/GameIdeas.WebAPI/Services/{Interfaces/IGameService.cs => Games/IGameWriteService.cs} (50%) diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor.cs index 749f0f5..21b236f 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor.cs @@ -4,7 +4,6 @@ using GameIdeas.BlazorApp.Shared.Models; using GameIdeas.Shared.Dto; using GameIdeas.Shared.Enum; using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Forms; namespace GameIdeas.BlazorApp.Pages.Games.Filter; @@ -23,10 +22,10 @@ public partial class GameFilter ]; private readonly List GameProperties = [ - new() { PropertyName = nameof(GameDetailDto.Title), Label = "Titre" }, - new() { PropertyName = nameof(GameDetailDto.ReleaseDate), Label = "Date de parution" }, - new() { PropertyName = nameof(GameDetailDto.StorageSpace), Label = "Espace de stockage" }, - new() { PropertyName = nameof(GameDetailDto.Interest), Label = "Interêt" } + new() { PropertyName = nameof(GameIdeas.Shared.Model.Game.Title), Label = "Titre" }, + new() { PropertyName = nameof(GameIdeas.Shared.Model.Game.ReleaseDate), Label = "Date de parution" }, + new() { PropertyName = nameof(GameIdeas.Shared.Model.Game.StorageSpace), Label = "Espace de stockage" }, + new() { PropertyName = nameof(GameIdeas.Shared.Model.Game.Interest), Label = "Interêt" } ]; private SelectParams SelectParams = new(); diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs index fd0e9a6..9b0e15f 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs @@ -1,5 +1,5 @@ using GameIdeas.Shared.Dto; -using GameIdeas.WebAPI.Services.Interfaces; +using GameIdeas.WebAPI.Services.Games; using Microsoft.AspNetCore.Mvc; namespace GameIdeas.WebAPI.Controllers; @@ -7,7 +7,10 @@ namespace GameIdeas.WebAPI.Controllers; [ApiController] [Route("api/[controller]")] -public class GameController(IGameService gameService, ILoggerFactory loggerFactory) : Controller +public class GameController( + IGameReadService gameReadService, + IGameWriteService gameWriteService, + ILoggerFactory loggerFactory) : Controller { private readonly ILogger logger = loggerFactory.CreateLogger(); @@ -16,7 +19,7 @@ public class GameController(IGameService gameService, ILoggerFactory loggerFacto { try { - return Ok(await gameService.GetGames(filter)); + return Ok(await gameReadService.GetGames(filter)); } catch (Exception e) { @@ -30,7 +33,7 @@ public class GameController(IGameService gameService, ILoggerFactory loggerFacto { try { - return Ok(await gameService.GetGameById(id)); + return Ok(await gameReadService.GetGameById(id)); } catch (Exception e) { @@ -44,7 +47,7 @@ public class GameController(IGameService gameService, ILoggerFactory loggerFacto { try { - var gameResult = await gameService.CreateGame(game); + var gameResult = await gameWriteService.CreateGame(game); return Created("/Create", gameResult.Id); } catch (Exception e) @@ -59,7 +62,7 @@ public class GameController(IGameService gameService, ILoggerFactory loggerFacto { try { - var gameResult = await gameService.UpdateGame(game); + var gameResult = await gameWriteService.UpdateGame(game); return Created($"/Update", gameResult.Id); } catch (Exception e) @@ -74,7 +77,7 @@ public class GameController(IGameService gameService, ILoggerFactory loggerFacto { try { - return Ok(await gameService.DeleteGame(id)); + return Ok(await gameWriteService.DeleteGame(id)); } catch (Exception e) { diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs index e78e427..6cfe582 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs @@ -1,7 +1,8 @@ using GameIdeas.Resources; using GameIdeas.WebAPI.Context; using GameIdeas.WebAPI.Profiles; -using GameIdeas.WebAPI.Services; +using GameIdeas.WebAPI.Services.Categories; +using GameIdeas.WebAPI.Services.Games; using GameIdeas.WebAPI.Services.Interfaces; using Microsoft.EntityFrameworkCore; using System.Text.Json.Serialization; @@ -32,7 +33,8 @@ services.AddDbContext(dbContextOptions); services.AddSingleton(); services.AddSingleton(); -services.AddScoped(); +services.AddScoped(); +services.AddScoped(); services.AddScoped(); services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies()); diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/CategoryService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Categories/CategoryService.cs similarity index 95% rename from src/GameIdeas/Server/GameIdeas.WebAPI/Services/CategoryService.cs rename to src/GameIdeas/Server/GameIdeas.WebAPI/Services/Categories/CategoryService.cs index f3650a1..4d7c05d 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/CategoryService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Categories/CategoryService.cs @@ -4,7 +4,7 @@ using GameIdeas.WebAPI.Context; using GameIdeas.WebAPI.Services.Interfaces; using Microsoft.EntityFrameworkCore; -namespace GameIdeas.WebAPI.Services; +namespace GameIdeas.WebAPI.Services.Categories; public class CategoryService(GameIdeasContext context, IMapper mapper) : ICategoryService { diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Interfaces/ICategoryService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Categories/ICategoryService.cs similarity index 100% rename from src/GameIdeas/Server/GameIdeas.WebAPI/Services/Interfaces/ICategoryService.cs rename to src/GameIdeas/Server/GameIdeas.WebAPI/Services/Categories/ICategoryService.cs diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameReadService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameReadService.cs new file mode 100644 index 0000000..7e7e77a --- /dev/null +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameReadService.cs @@ -0,0 +1,100 @@ +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(); + + 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.Title != null) + { + return; + } + + 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 = query.OrderBy(lambda.Compile()).AsQueryable(); + } + else + { + query = query.OrderByDescending(lambda.Compile()).AsQueryable(); + } + } + } + + private static void ApplyFilter(ref IQueryable query, GameFilterDto filter) + { + if (filter.Title != null) + { + var keywords = filter.Title + .Split([' '], StringSplitOptions.RemoveEmptyEntries) + .Select(k => k.Trim()) + .ToArray(); + + query = query + .Where(game => keywords.All( + kw => game.Title.IndexOf(kw, StringComparison.OrdinalIgnoreCase) >= 0 + )) + .OrderBy(game => + keywords.Min(kw => + game.Title.IndexOf(kw, StringComparison.OrdinalIgnoreCase) + ) + ) + .ThenBy(game => game.Title.Length); + + return; + } + } +} diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameWriteService.cs similarity index 70% rename from src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs rename to src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameWriteService.cs index b19d43b..b1b05b1 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameWriteService.cs @@ -4,51 +4,12 @@ using GameIdeas.Shared.Dto; using GameIdeas.Shared.Exceptions; using GameIdeas.Shared.Model; using GameIdeas.WebAPI.Context; -using GameIdeas.WebAPI.Services.Interfaces; using Microsoft.EntityFrameworkCore; -namespace GameIdeas.WebAPI.Services; +namespace GameIdeas.WebAPI.Services.Games; -public class GameService(GameIdeasContext context, IMapper mapper) : IGameService +public class GameWriteService(GameIdeasContext context, IMapper mapper) : IGameWriteService { - 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(); - - 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); - } - public async Task CreateGame(GameDetailDto gameDto) { var gameToCreate = mapper.Map(gameDto); @@ -98,16 +59,6 @@ public class GameService(GameIdeasContext context, IMapper mapper) : IGameServic return await context.SaveChangesAsync() != 0; } - private void ApplyOrder(ref IQueryable query, GameFilterDto filter) - { - - } - - private void ApplyFilter(ref IQueryable query, GameFilterDto filter) - { - - } - private async Task HandlePlatformsCreation(IEnumerable? categoriesToCreate, int gameId) { if (categoriesToCreate != null) diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/IGameReadService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/IGameReadService.cs new file mode 100644 index 0000000..4c814d3 --- /dev/null +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/IGameReadService.cs @@ -0,0 +1,9 @@ +using GameIdeas.Shared.Dto; + +namespace GameIdeas.WebAPI.Services.Games; + +public interface IGameReadService +{ + Task> GetGames(GameFilterDto filter); + Task GetGameById(int gameId); +} diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Interfaces/IGameService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/IGameWriteService.cs similarity index 50% rename from src/GameIdeas/Server/GameIdeas.WebAPI/Services/Interfaces/IGameService.cs rename to src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/IGameWriteService.cs index ff2749d..d7fcc92 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Interfaces/IGameService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/IGameWriteService.cs @@ -1,11 +1,9 @@ using GameIdeas.Shared.Dto; -namespace GameIdeas.WebAPI.Services.Interfaces; +namespace GameIdeas.WebAPI.Services.Games; -public interface IGameService +public interface IGameWriteService { - Task> GetGames(GameFilterDto filter); - Task GetGameById(int gameId); Task CreateGame(GameDetailDto gameDto); Task UpdateGame(GameDetailDto gameDto); Task DeleteGame(int gameId); -- 2.39.5 From be63fa1cafa5ff400d1956b6072bb47a9fb3da0d Mon Sep 17 00:00:00 2001 From: Egamorf Date: Fri, 18 Apr 2025 19:27:14 +0200 Subject: [PATCH 07/11] Arrange game read service --- .../GameIdeas.WebAPI/Services/Games/GameReadService.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameReadService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameReadService.cs index 7e7e77a..34975d0 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameReadService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameReadService.cs @@ -85,13 +85,11 @@ public class GameReadService(GameIdeasContext context, IMapper mapper) : IGameRe query = query .Where(game => keywords.All( - kw => game.Title.IndexOf(kw, StringComparison.OrdinalIgnoreCase) >= 0 + kw => game.Title.Contains(kw, StringComparison.OrdinalIgnoreCase) + )) + .OrderBy(game => keywords.Min(kw => + game.Title.IndexOf(kw, StringComparison.OrdinalIgnoreCase) )) - .OrderBy(game => - keywords.Min(kw => - game.Title.IndexOf(kw, StringComparison.OrdinalIgnoreCase) - ) - ) .ThenBy(game => game.Title.Length); return; -- 2.39.5 From 7fd9639eed4f9a4a364c2acabbc1d07eaab51112 Mon Sep 17 00:00:00 2001 From: Egamorf Date: Sat, 19 Apr 2025 20:16:41 +0200 Subject: [PATCH 08/11] Apply sort and game filter --- .../Pages/Games/Filter/GameFilter.razor | 4 +- .../Pages/Games/Filter/GameFilter.razor.cs | 22 ++++---- .../Pages/Games/Game.razor.cs | 7 +++ .../Pages/Games/Header/GameHeader.razor.cs | 3 +- .../Components/Select/Models/SelectParams.cs | 2 - .../Shared/Components/Select/Select.razor.cs | 10 ---- .../CreateStaticResourceKey.cs | 8 +++ .../GameIdeas.WebAPI/Files/GameIdeas.fr.json | 7 ++- .../Services/Games/GameReadService.cs | 52 +++++++++++-------- 9 files changed, 63 insertions(+), 52 deletions(-) diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor index f864301..d128e8f 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/GameFilter.razor @@ -7,8 +7,8 @@ @using GameIdeas.Shared.Dto
-