From 69a3570a605a6fa743476cc0ece166da18ed50f9 Mon Sep 17 00:00:00 2001 From: Egamorf Date: Thu, 10 Apr 2025 01:27:46 +0200 Subject: [PATCH 1/7] Add dto and services --- .../Pages/Games/Filter/GameFilter.razor.cs | 4 +- .../GameIdeas.Shared/Dto/DeveloperDto.cs | 7 +++ src/GameIdeas/GameIdeas.Shared/Dto/GameDto.cs | 17 +++++- .../GameIdeas.Shared/Dto/GameFilterDto.cs | 18 ++++++ .../GameIdeas.Shared/Dto/LoginDto.cs | 6 +- .../GameIdeas.Shared/Dto/PlatformDto.cs | 8 +++ .../GameIdeas.Shared/Dto/PropertyDto.cs | 7 +++ .../GameIdeas.Shared/Dto/PublisherDto.cs | 7 +++ src/GameIdeas/GameIdeas.Shared/Dto/TagDto.cs | 8 +++ src/GameIdeas/GameIdeas.Shared/Dto/UserDto.cs | 13 ++++ src/GameIdeas/GameIdeas.Shared/Enum/Role.cs | 8 +++ .../GameIdeas.Shared/Model/Platform.cs | 2 +- .../Controllers/GameController.cs | 17 ++++++ .../GameIdeas.WebAPI/GameIdeas.WebAPI.csproj | 1 + ... 20250409225125_InitialCreate.Designer.cs} | 4 +- ...ate.cs => 20250409225125_InitialCreate.cs} | 2 +- .../GameIdeasContextModelSnapshot.cs | 2 +- .../Profiles/CategoryProfile.cs | 36 +++++++++++ .../GameIdeas.WebAPI/Profiles/GameProfile.cs | 31 ++++++++++ .../GameIdeas.WebAPI/Profiles/UserProfile.cs | 22 +++++++ .../Server/GameIdeas.WebAPI/Program.cs | 8 +++ .../GameIdeas.WebAPI/Services/GameService.cs | 60 +++++++++++++++++++ .../GameIdeas.WebAPI/Services/UserService.cs | 5 ++ 23 files changed, 283 insertions(+), 10 deletions(-) create mode 100644 src/GameIdeas/GameIdeas.Shared/Dto/DeveloperDto.cs create mode 100644 src/GameIdeas/GameIdeas.Shared/Dto/GameFilterDto.cs create mode 100644 src/GameIdeas/GameIdeas.Shared/Dto/PlatformDto.cs create mode 100644 src/GameIdeas/GameIdeas.Shared/Dto/PropertyDto.cs create mode 100644 src/GameIdeas/GameIdeas.Shared/Dto/PublisherDto.cs create mode 100644 src/GameIdeas/GameIdeas.Shared/Dto/TagDto.cs create mode 100644 src/GameIdeas/GameIdeas.Shared/Dto/UserDto.cs create mode 100644 src/GameIdeas/GameIdeas.Shared/Enum/Role.cs create mode 100644 src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs rename src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/{20250409210640_InitialCreate.Designer.cs => 20250409225125_InitialCreate.Designer.cs} (99%) rename src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/{20250409210640_InitialCreate.cs => 20250409225125_InitialCreate.cs} (99%) create mode 100644 src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/CategoryProfile.cs create mode 100644 src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs create mode 100644 src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/UserProfile.cs create mode 100644 src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs create mode 100644 src/GameIdeas/Server/GameIdeas.WebAPI/Services/UserService.cs 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 7fda92f..49d7461 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 @@ -21,7 +21,7 @@ public partial class GameFilter ]; private readonly IEnumerable>> GameProperties = [ - new() { Item = game => game?.Name, Label = "Nom", IsSelected = true }, + new() { Item = game => game?.Title, Label = "Nom", IsSelected = true }, new() { Item = game => game?.ReleaseDate, Label = "Date de parution" } ]; @@ -40,7 +40,7 @@ public partial class GameFilter ]; private EditContext? EditContext; - private SliderRangeParams SliderRangeParams = + private readonly SliderRangeParams SliderRangeParams = new() { Min = 1, ValueMin = 1, ValueMax = 5, Max = 5 }; protected override void OnInitialized() diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/DeveloperDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/DeveloperDto.cs new file mode 100644 index 0000000..61cf754 --- /dev/null +++ b/src/GameIdeas/GameIdeas.Shared/Dto/DeveloperDto.cs @@ -0,0 +1,7 @@ +namespace GameIdeas.Shared.Dto; + +public class DeveloperDto +{ + public int? Id { get; set; } + public string? Name { get; set; } +} diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/GameDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/GameDto.cs index 293a672..e0fca59 100644 --- a/src/GameIdeas/GameIdeas.Shared/Dto/GameDto.cs +++ b/src/GameIdeas/GameIdeas.Shared/Dto/GameDto.cs @@ -2,6 +2,19 @@ public class GameDto { - public string? Name { get; set; } - public string? ReleaseDate { get; set; } + 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 DateTime? ModificationDate { get; set; } + public UserDto? ModificationUser { get; set; } + public double? StorageSpace { get; set; } + public string? Description { get; set; } + public int? Interest { get; set; } + public IEnumerable? Platforms { get; set; } + public IEnumerable? Properties { get; set; } + public IEnumerable? Tags { get; set; } + public IEnumerable? Publishers { get; set; } + public IEnumerable? Developers { get; set; } } \ No newline at end of file diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/GameFilterDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/GameFilterDto.cs new file mode 100644 index 0000000..eae8c05 --- /dev/null +++ b/src/GameIdeas/GameIdeas.Shared/Dto/GameFilterDto.cs @@ -0,0 +1,18 @@ +namespace GameIdeas.Shared.Dto; + +public class GameFilterDto +{ + public IEnumerable? Platforms { get; set; } + public string? Name { get; set; } + public IEnumerable? Tags { get; set; } + public IEnumerable? Properties { get; set; } + public int? MinInterest { get; set; } + public int? MaxInterest { get; set; } + public IEnumerable? ReleaseYears { get; set; } + public IEnumerable? PublisherIds { get; set; } + public IEnumerable? DeveloperIds { get; set; } + public IEnumerable? CreationUserIds { get; set; } + public IEnumerable? ModificationUserIds { get; set; } + public int CurrentPage { get; set; } + public int NumberPerPage { get; set; } +} diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/LoginDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/LoginDto.cs index 61ea528..33c81a7 100644 --- a/src/GameIdeas/GameIdeas.Shared/Dto/LoginDto.cs +++ b/src/GameIdeas/GameIdeas.Shared/Dto/LoginDto.cs @@ -1,7 +1,11 @@ -namespace GameIdeas.Shared.Dto; +using GameIdeas.Shared.Enum; + +namespace GameIdeas.Shared.Dto; public class LoginDto { + public int? Id { get; set; } public string? Username { get; set; } public string? Password { get; set; } + public Role? Role { get; set; } } diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/PlatformDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/PlatformDto.cs new file mode 100644 index 0000000..472f1cc --- /dev/null +++ b/src/GameIdeas/GameIdeas.Shared/Dto/PlatformDto.cs @@ -0,0 +1,8 @@ +namespace GameIdeas.Shared.Dto; + +public class PlatformDto +{ + public int? Id { get; set; } + public string? Label { get; set; } + public string? Url { get; set; } +} diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/PropertyDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/PropertyDto.cs new file mode 100644 index 0000000..0841c91 --- /dev/null +++ b/src/GameIdeas/GameIdeas.Shared/Dto/PropertyDto.cs @@ -0,0 +1,7 @@ +namespace GameIdeas.Shared.Dto; + +public class PropertyDto +{ + public int? Id { get; set; } + public string? Label { get; set; } +} diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/PublisherDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/PublisherDto.cs new file mode 100644 index 0000000..4e97bc9 --- /dev/null +++ b/src/GameIdeas/GameIdeas.Shared/Dto/PublisherDto.cs @@ -0,0 +1,7 @@ +namespace GameIdeas.Shared.Dto; + +public class PublisherDto +{ + public int? Id { get; set; } + public string? Name { get; set; } +} diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/TagDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/TagDto.cs new file mode 100644 index 0000000..a959e21 --- /dev/null +++ b/src/GameIdeas/GameIdeas.Shared/Dto/TagDto.cs @@ -0,0 +1,8 @@ + +namespace GameIdeas.Shared.Dto; + +public class TagDto +{ + public int? Id { get; set; } + public string? Label { get; set; } +} diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/UserDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/UserDto.cs new file mode 100644 index 0000000..39d52ca --- /dev/null +++ b/src/GameIdeas/GameIdeas.Shared/Dto/UserDto.cs @@ -0,0 +1,13 @@ +using GameIdeas.Shared.Enum; + +namespace GameIdeas.Shared.Dto; + +public class UserDto +{ + public int? Id { get; set; } + public string? Username { get; set; } + public string? Password { get; set; } + public Role? Role { get; set; } + public IEnumerable? CreationGames { get; set; } + public IEnumerable? ModificationGames { get; set; } +} diff --git a/src/GameIdeas/GameIdeas.Shared/Enum/Role.cs b/src/GameIdeas/GameIdeas.Shared/Enum/Role.cs new file mode 100644 index 0000000..25ed04c --- /dev/null +++ b/src/GameIdeas/GameIdeas.Shared/Enum/Role.cs @@ -0,0 +1,8 @@ +namespace GameIdeas.Shared.Enum; + +public enum Role +{ + Guest = 1, + Member = 2, + Administrator = 3 +} diff --git a/src/GameIdeas/GameIdeas.Shared/Model/Platform.cs b/src/GameIdeas/GameIdeas.Shared/Model/Platform.cs index a4c643c..b1557c9 100644 --- a/src/GameIdeas/GameIdeas.Shared/Model/Platform.cs +++ b/src/GameIdeas/GameIdeas.Shared/Model/Platform.cs @@ -8,7 +8,7 @@ public partial class Platform } public int Id { get; set; } - public string Libelle { get; set; } = null!; + public string Label { get; set; } = null!; public virtual ICollection GamePlatforms { get; set; } diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs new file mode 100644 index 0000000..ae31a44 --- /dev/null +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs @@ -0,0 +1,17 @@ +using GameIdeas.Shared.Dto; +using GameIdeas.WebAPI.Services; +using Microsoft.AspNetCore.Mvc; + +namespace GameIdeas.WebAPI.Controllers; + +[ApiController] +[Route("api/[controller]")] + +public class GameController(GameService gameService) : Controller +{ + [HttpGet("Search")] + public async Task> FetchGames([FromQuery] GameFilterDto filter) + { + return await gameService.SearchGames(filter); + } +} diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj b/src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj index cb257d4..9a3748c 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj @@ -11,6 +11,7 @@ + diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250409210640_InitialCreate.Designer.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250409225125_InitialCreate.Designer.cs similarity index 99% rename from src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250409210640_InitialCreate.Designer.cs rename to src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250409225125_InitialCreate.Designer.cs index 8a8bf38..4e323db 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250409210640_InitialCreate.Designer.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250409225125_InitialCreate.Designer.cs @@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace GameIdeas.WebAPI.Migrations { [DbContext(typeof(GameIdeasContext))] - [Migration("20250409210640_InitialCreate")] + [Migration("20250409225125_InitialCreate")] partial class InitialCreate { /// @@ -176,7 +176,7 @@ namespace GameIdeas.WebAPI.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.Property("Libelle") + b.Property("Label") .IsRequired() .HasColumnType("text"); diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250409210640_InitialCreate.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250409225125_InitialCreate.cs similarity index 99% rename from src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250409210640_InitialCreate.cs rename to src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250409225125_InitialCreate.cs index 338fcf5..b4bb5ab 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250409210640_InitialCreate.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250409225125_InitialCreate.cs @@ -31,7 +31,7 @@ namespace GameIdeas.WebAPI.Migrations { Id = table.Column(type: "integer", nullable: false) .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - Libelle = table.Column(type: "text", nullable: false) + Label = table.Column(type: "text", nullable: false) }, constraints: table => { diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/GameIdeasContextModelSnapshot.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/GameIdeasContextModelSnapshot.cs index d142826..630e7fb 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/GameIdeasContextModelSnapshot.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/GameIdeasContextModelSnapshot.cs @@ -173,7 +173,7 @@ namespace GameIdeas.WebAPI.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - b.Property("Libelle") + b.Property("Label") .IsRequired() .HasColumnType("text"); diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/CategoryProfile.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/CategoryProfile.cs new file mode 100644 index 0000000..2763b8b --- /dev/null +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/CategoryProfile.cs @@ -0,0 +1,36 @@ +using AutoMapper; +using GameIdeas.Shared.Dto; +using GameIdeas.Shared.Model; + +namespace GameIdeas.WebAPI.Profiles; + +public class CategoryProfile : Profile +{ + public CategoryProfile() + { + CreateMap() + .ForMember(d => d.Id, o => o.MapFrom(s => s.Id)) + .ForMember(d => d.Label, o => o.MapFrom(s => s.Label)) + .ReverseMap(); + + CreateMap() + .ForMember(d => d.Id, o => o.MapFrom(s => s.Id)) + .ForMember(d => d.Label, o => o.MapFrom(s => s.Label)) + .ReverseMap(); + + CreateMap() + .ForMember(d => d.Id, o => o.MapFrom(s => s.Id)) + .ForMember(d => d.Label, o => o.MapFrom(s => s.Label)) + .ReverseMap(); + + CreateMap() + .ForMember(d => d.Id, o => o.MapFrom(s => s.Id)) + .ForMember(d => d.Name, o => o.MapFrom(s => s.Name)) + .ReverseMap(); + + CreateMap() + .ForMember(d => d.Id, o => o.MapFrom(s => s.Id)) + .ForMember(d => d.Name, o => o.MapFrom(s => s.Name)) + .ReverseMap(); + } +} diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs new file mode 100644 index 0000000..a0e8d12 --- /dev/null +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs @@ -0,0 +1,31 @@ +using AutoMapper; +using GameIdeas.Shared.Dto; +using GameIdeas.Shared.Model; + +namespace GameIdeas.WebAPI.Profiles; + +public class GameProfile : Profile +{ + public GameProfile() + { + 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)) + .ForPath(d => d.CreationUser!.Id, 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)) + .ForPath(d => d.ModificationUser!.Id, 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)) + .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.Properties, o => o.MapFrom(s => s.GameProperties.Select(p => p.Property))) + .ForMember(d => d.Tags, o => o.MapFrom(s => s.GameTags.Select(t => t.Tag))) + .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(); + } +} diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/UserProfile.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/UserProfile.cs new file mode 100644 index 0000000..f0868d7 --- /dev/null +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/UserProfile.cs @@ -0,0 +1,22 @@ +using AutoMapper; +using GameIdeas.Shared.Dto; +using GameIdeas.Shared.Enum; +using GameIdeas.Shared.Model; + +namespace GameIdeas.WebAPI.Profiles; + +public class UserProfile : Profile +{ + public UserProfile() + { + CreateMap() + .ForMember(d => d.Id, o => o.MapFrom(s => s.Id)) + .ForMember(d => d.Username, o => o.MapFrom(s => s.Username)) + .ForMember(d => d.Password, o => o.MapFrom(s => s.Password)) + .ForMember(d => d.Role, o => o.MapFrom(s => s.Role)) + .ForMember(d => d.CreationGames, o => o.MapFrom(s => s.CreationGames)) + .ForMember(d => d.ModificationGames, o => o.MapFrom(s => s.ModificationGames)) + .ReverseMap(); + + } +} diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs index 6ce21a4..6486ff7 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs @@ -1,5 +1,7 @@ using GameIdeas.Resources; using GameIdeas.WebAPI.Context; +using GameIdeas.WebAPI.Profiles; +using GameIdeas.WebAPI.Services; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using System.Data; @@ -32,6 +34,12 @@ services.AddDbContext(dbContextOptions); services.AddSingleton(); services.AddSingleton(); +services.AddScoped(); + +services.AddAutoMapper(typeof(GameProfile).Assembly); +services.AddAutoMapper(typeof(UserProfile).Assembly); +services.AddAutoMapper(typeof(CategoryProfile).Assembly); + services.AddControllers(); // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi services.AddOpenApi(); diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs new file mode 100644 index 0000000..9ff1772 --- /dev/null +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs @@ -0,0 +1,60 @@ +using AutoMapper; +using GameIdeas.Shared.Dto; +using GameIdeas.Shared.Enum; +using GameIdeas.Shared.Model; +using GameIdeas.WebAPI.Context; +using Microsoft.EntityFrameworkCore; + +namespace GameIdeas.WebAPI.Services; + +public class GameService(GameIdeasContext context, IMapper mapper) +{ + public async Task> SearchGames(GameFilterDto filter) + { + var query = 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(); + + ApplyFilter(ref query); + + var games = await query + .OrderBy(g => g.Title) + .Skip(filter.CurrentPage * filter.NumberPerPage) + .Take(filter.NumberPerPage) + .ToListAsync(); + + List gamesTest = [ new () + { + Id = 1, + Title = "Test", + CreationDate = DateTime.Today - TimeSpan.FromHours(-12), + CreationUser = new User() { Id = 1, Username = "Test", Role = (int)Role.Administrator }, + CreationUserId = 1, + Description = "Test", + GameDevelopers = [ new() { Developer = new Developer() { Id = 1, Name = "THQNordic"} }], + GamePlatforms = [ new() { Platform = new Platform() { Id = 1, Label = "Steam" }, Url = "steam.com?app=55554131" }], + GameProperties = [ new () { Property = new Property() { Id = 1, Label = "Coop"} }], + GamePublishers = [ new () { Publisher = new Publisher() { Id = 1, Name = "Take-Two"} }], + GameTags = [ new () { Tag = new Tag() { Id = 1, Label = "RPG" } }], + Interest = 5, + ModificationDate = DateTime.Today , + ModificationUser = new User() { Id = 2, Username = "Test 2", Role = (int)Role.Member }, + ModificationUserId = 2, + ReleaseDate = new DateTime(2025, 2, 1), + StorageSpace = 40 + } ]; + + return mapper.Map>(gamesTest); + } + + private void ApplyFilter(ref IQueryable query) + { + + } +} diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/UserService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/UserService.cs new file mode 100644 index 0000000..bfb43fb --- /dev/null +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/UserService.cs @@ -0,0 +1,5 @@ +namespace GameIdeas.WebAPI.Services; + +public class UserService +{ +} -- 2.39.5 From bdec8f401a6f1b5230149d174bbd3d3d75cb045f Mon Sep 17 00:00:00 2001 From: Egamorf Date: Thu, 10 Apr 2025 01:27:54 +0200 Subject: [PATCH 2/7] remove log --- src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs index 6486ff7..b4e1040 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs @@ -24,8 +24,7 @@ Action dbContextOptions = options => { npgOption.CommandTimeout(60); npgOption.MigrationsAssembly("GameIdeas.WebAPI"); - }) - .LogTo(Console.WriteLine); + }); }; // Add services to the container. -- 2.39.5 From 8654379fbb7041c5359f99f0498061c8c89bc6cb Mon Sep 17 00:00:00 2001 From: Egamorf Date: Thu, 10 Apr 2025 01:30:44 +0200 Subject: [PATCH 3/7] refactor Program cs --- src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs index b4e1040..4c0d6ea 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs @@ -3,17 +3,14 @@ using GameIdeas.WebAPI.Context; using GameIdeas.WebAPI.Profiles; using GameIdeas.WebAPI.Services; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using System.Data; -using System.Data.Common; var builder = WebApplication.CreateBuilder(args); var services = builder.Services; #if DEBUG -Load("../../../../.env"); +LoadEnvironmentVariable("../../../../.env"); #else - Load(".env"); +LoadEnvironmentVariable(".env"); #endif Action dbContextOptions = options => @@ -92,7 +89,7 @@ string GetConnectionString() return $"Host={host};Username={login};Password={pass};Database={database}"; } -static void Load(string filePath) +static void LoadEnvironmentVariable(string filePath) { if (!File.Exists(filePath)) return; -- 2.39.5 From 797484525bd33946cf686ff3266f8ddbe73179f1 Mon Sep 17 00:00:00 2001 From: Maxime Adler Date: Thu, 10 Apr 2025 15:18:44 +0200 Subject: [PATCH 4/7] Add Create and Update --- .../Games/{GamesBase.razor => GameBase.razor} | 4 +- .../{GamesBase.razor.cs => GameBase.razor.cs} | 2 +- ...GamesBase.razor.css => GameBase.razor.css} | 0 .../{GamesHeader.razor => GameHeader.razor} | 0 ...mesHeader.razor.cs => GameHeader.razor.cs} | 2 +- ...sHeader.razor.css => GameHeader.razor.css} | 0 .../GameIdeas.Shared/Dto/GameFilterDto.cs | 2 - .../GameIdeas.Shared/Dto/PaggingDto.cs | 7 ++ .../Exceptions/NotFoundException.cs | 7 ++ .../Controllers/GameController.cs | 2 +- .../GameIdeas.WebAPI/Services/GameService.cs | 95 +++++++++++++------ 11 files changed, 84 insertions(+), 37 deletions(-) rename src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/{GamesBase.razor => GameBase.razor} (94%) rename src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/{GamesBase.razor.cs => GameBase.razor.cs} (87%) rename src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/{GamesBase.razor.css => GameBase.razor.css} (100%) rename src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/{GamesHeader.razor => GameHeader.razor} (100%) rename src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/{GamesHeader.razor.cs => GameHeader.razor.cs} (95%) rename src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/{GamesHeader.razor.css => GameHeader.razor.css} (100%) create mode 100644 src/GameIdeas/GameIdeas.Shared/Dto/PaggingDto.cs create mode 100644 src/GameIdeas/GameIdeas.Shared/Exceptions/NotFoundException.cs diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GamesBase.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GameBase.razor similarity index 94% rename from src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GamesBase.razor rename to src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GameBase.razor index 3da8b3f..dd434a7 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GamesBase.razor +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GameBase.razor @@ -9,10 +9,10 @@ @ResourcesKey.GamesIdeas - + - +
diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GamesBase.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GameBase.razor.cs similarity index 87% rename from src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GamesBase.razor.cs rename to src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GameBase.razor.cs index 0fcb9fa..60ab45d 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GamesBase.razor.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GameBase.razor.cs @@ -3,7 +3,7 @@ using GameIdeas.BlazorApp.Shared.Models; namespace GameIdeas.BlazorApp.Pages.Games; -public partial class GamesBase () +public partial class GameBase () { private DisplayType DisplayType = DisplayType.List; private GameFilterParams GameFilterParams = new(); diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GamesBase.razor.css b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GameBase.razor.css similarity index 100% rename from src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GamesBase.razor.css rename to src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/GameBase.razor.css diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/GamesHeader.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/GameHeader.razor similarity index 100% rename from src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/GamesHeader.razor rename to src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/GameHeader.razor diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/GamesHeader.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/GameHeader.razor.cs similarity index 95% rename from src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/GamesHeader.razor.cs rename to src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/GameHeader.razor.cs index ee0a45e..a854eaf 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/GamesHeader.razor.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/GameHeader.razor.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Components; namespace GameIdeas.BlazorApp.Pages.Games.Header; -public partial class GamesHeader : ComponentBase +public partial class GameHeader : ComponentBase { [Parameter] public EventCallback AddTypeChanged { get; set; } [Parameter] public RenderFragment? ChildContent { get; set; } diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/GamesHeader.razor.css b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/GameHeader.razor.css similarity index 100% rename from src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/GamesHeader.razor.css rename to src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Header/GameHeader.razor.css diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/GameFilterDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/GameFilterDto.cs index eae8c05..25ba67c 100644 --- a/src/GameIdeas/GameIdeas.Shared/Dto/GameFilterDto.cs +++ b/src/GameIdeas/GameIdeas.Shared/Dto/GameFilterDto.cs @@ -13,6 +13,4 @@ public class GameFilterDto public IEnumerable? DeveloperIds { get; set; } public IEnumerable? CreationUserIds { get; set; } public IEnumerable? ModificationUserIds { get; set; } - public int CurrentPage { get; set; } - public int NumberPerPage { get; set; } } diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/PaggingDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/PaggingDto.cs new file mode 100644 index 0000000..c006966 --- /dev/null +++ b/src/GameIdeas/GameIdeas.Shared/Dto/PaggingDto.cs @@ -0,0 +1,7 @@ +namespace GameIdeas.Shared.Dto; + +public class PaggingDto +{ + public int CurrentPage { get; set; } + public int NumberPerPage { get; set; } +} diff --git a/src/GameIdeas/GameIdeas.Shared/Exceptions/NotFoundException.cs b/src/GameIdeas/GameIdeas.Shared/Exceptions/NotFoundException.cs new file mode 100644 index 0000000..7467e55 --- /dev/null +++ b/src/GameIdeas/GameIdeas.Shared/Exceptions/NotFoundException.cs @@ -0,0 +1,7 @@ +namespace GameIdeas.Shared.Exceptions; + +public class NotFoundException : Exception +{ + public NotFoundException(string msg, Exception? innerException = null) : base(msg, innerException) + { } +} diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs index ae31a44..382fb85 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs @@ -12,6 +12,6 @@ public class GameController(GameService gameService) : Controller [HttpGet("Search")] public async Task> FetchGames([FromQuery] GameFilterDto filter) { - return await gameService.SearchGames(filter); + return await gameService.GetGames(filter); } } diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs index 9ff1772..de768e3 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs @@ -1,6 +1,6 @@ using AutoMapper; using GameIdeas.Shared.Dto; -using GameIdeas.Shared.Enum; +using GameIdeas.Shared.Exceptions; using GameIdeas.Shared.Model; using GameIdeas.WebAPI.Context; using Microsoft.EntityFrameworkCore; @@ -9,7 +9,7 @@ namespace GameIdeas.WebAPI.Services; public class GameService(GameIdeasContext context, IMapper mapper) { - public async Task> SearchGames(GameFilterDto filter) + public async Task> GetGames(PaggingDto pagging) { var query = context.Games .Include(g => g.CreationUser) @@ -21,40 +21,75 @@ public class GameService(GameIdeasContext context, IMapper mapper) .Include(g => g.GameDevelopers).ThenInclude(p => p.Developer) .AsQueryable(); - ApplyFilter(ref query); - var games = await query .OrderBy(g => g.Title) - .Skip(filter.CurrentPage * filter.NumberPerPage) - .Take(filter.NumberPerPage) + .Skip(pagging.CurrentPage * pagging.NumberPerPage) + .Take(pagging.NumberPerPage) .ToListAsync(); - List gamesTest = [ new () - { - Id = 1, - Title = "Test", - CreationDate = DateTime.Today - TimeSpan.FromHours(-12), - CreationUser = new User() { Id = 1, Username = "Test", Role = (int)Role.Administrator }, - CreationUserId = 1, - Description = "Test", - GameDevelopers = [ new() { Developer = new Developer() { Id = 1, Name = "THQNordic"} }], - GamePlatforms = [ new() { Platform = new Platform() { Id = 1, Label = "Steam" }, Url = "steam.com?app=55554131" }], - GameProperties = [ new () { Property = new Property() { Id = 1, Label = "Coop"} }], - GamePublishers = [ new () { Publisher = new Publisher() { Id = 1, Name = "Take-Two"} }], - GameTags = [ new () { Tag = new Tag() { Id = 1, Label = "RPG" } }], - Interest = 5, - ModificationDate = DateTime.Today , - ModificationUser = new User() { Id = 2, Username = "Test 2", Role = (int)Role.Member }, - ModificationUserId = 2, - ReleaseDate = new DateTime(2025, 2, 1), - StorageSpace = 40 - } ]; - - return mapper.Map>(gamesTest); + return mapper.Map>(games); } - private void ApplyFilter(ref IQueryable query) + 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); + + if (game == null) + { + throw new NotFoundException($"[{typeof(Game).Name}] with ID {gameId} has not been found in context"); + } + + return mapper.Map(game); + } + + public async Task CreateGame(GameDto gameDto) + { + var game = mapper.Map(gameDto); + + context.GameDevelopers.AttachRange(game.GameDevelopers); + context.Developers.AttachRange(game.GameDevelopers.Select(gd => gd.Developer)); + context.GamePlatforms.AttachRange(game.GamePlatforms); + context.Platforms.AttachRange(game.GamePlatforms.Select(gp => gp.Platform)); + context.GameProperties.AttachRange(game.GameProperties); + context.Properties.AttachRange(game.GameProperties.Select(gp => gp.Property)); + context.GamePublishers.AttachRange(game.GamePublishers); + context.Publishers.AttachRange(game.GamePublishers.Select(gp => gp.Publisher)); + context.GameTags.AttachRange(game.GameTags); + context.Tags.AttachRange(game.GameTags.Select(gt => gt.Tag)); + + await context.Games.AddAsync(game); + await context.SaveChangesAsync(); + + return mapper.Map(game); + } + + public async Task UpdateGame(GameDto gameDto) + { + var gameToUpdate = mapper.Map(gameDto); + + context.GameDevelopers.AttachRange(gameToUpdate.GameDevelopers); + context.Developers.AttachRange(gameToUpdate.GameDevelopers.Select(gd => gd.Developer)); + context.GamePlatforms.AttachRange(gameToUpdate.GamePlatforms); + context.Platforms.AttachRange(gameToUpdate.GamePlatforms.Select(gp => gp.Platform)); + context.GameProperties.AttachRange(gameToUpdate.GameProperties); + context.Properties.AttachRange(gameToUpdate.GameProperties.Select(gp => gp.Property)); + context.GamePublishers.AttachRange(gameToUpdate.GamePublishers); + context.Publishers.AttachRange(gameToUpdate.GamePublishers.Select(gp => gp.Publisher)); + context.GameTags.AttachRange(gameToUpdate.GameTags); + context.Tags.AttachRange(gameToUpdate.GameTags.Select(gt => gt.Tag)); + + + context.Games.Update(gameToUpdate); + await context.SaveChangesAsync(); + + return mapper.Map(gameToUpdate); } } -- 2.39.5 From b545da19b77c0e297eaf16f0877d43de4a0fef33 Mon Sep 17 00:00:00 2001 From: Maxime Adler Date: Thu, 10 Apr 2025 15:21:13 +0200 Subject: [PATCH 5/7] Fix message --- .../GameIdeas.Shared/Exceptions/NotFoundException.cs | 6 +----- .../GameIdeas.WebAPI/Controllers/GameController.cs | 4 ++-- .../Server/GameIdeas.WebAPI/Services/GameService.cs | 9 +++------ 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/GameIdeas/GameIdeas.Shared/Exceptions/NotFoundException.cs b/src/GameIdeas/GameIdeas.Shared/Exceptions/NotFoundException.cs index 7467e55..027b57d 100644 --- a/src/GameIdeas/GameIdeas.Shared/Exceptions/NotFoundException.cs +++ b/src/GameIdeas/GameIdeas.Shared/Exceptions/NotFoundException.cs @@ -1,7 +1,3 @@ namespace GameIdeas.Shared.Exceptions; -public class NotFoundException : Exception -{ - public NotFoundException(string msg, Exception? innerException = null) : base(msg, innerException) - { } -} +public class NotFoundException(string msg, Exception? innerException = null) : Exception(msg, innerException); diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs index 382fb85..44458e4 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs @@ -10,8 +10,8 @@ namespace GameIdeas.WebAPI.Controllers; public class GameController(GameService gameService) : Controller { [HttpGet("Search")] - public async Task> FetchGames([FromQuery] GameFilterDto filter) + public async Task> FetchGames([FromQuery] PaggingDto pagging) { - return await gameService.GetGames(filter); + return await gameService.GetGames(pagging); } } diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs index de768e3..98674f6 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs @@ -42,12 +42,9 @@ public class GameService(GameIdeasContext context, IMapper mapper) .Include(g => g.GameDevelopers).ThenInclude(p => p.Developer) .FirstOrDefaultAsync(g => g.Id == gameId); - if (game == null) - { - throw new NotFoundException($"[{typeof(Game).Name}] with ID {gameId} has not been found in context"); - } - - return mapper.Map(game); + return game == null + ? throw new NotFoundException($"[{typeof(Game).Name}] with ID {gameId} has not been found in context") + : mapper.Map(game); } public async Task CreateGame(GameDto gameDto) -- 2.39.5 From 5d655d8b4b5385086d449d353af5be172244361a Mon Sep 17 00:00:00 2001 From: Maxime Adler Date: Thu, 10 Apr 2025 15:41:39 +0200 Subject: [PATCH 6/7] restore test value --- .../GameIdeas.WebAPI/Services/GameService.cs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs index 98674f6..3743d8b 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs @@ -1,9 +1,11 @@ using AutoMapper; using GameIdeas.Shared.Dto; +using GameIdeas.Shared.Enum; using GameIdeas.Shared.Exceptions; using GameIdeas.Shared.Model; using GameIdeas.WebAPI.Context; using Microsoft.EntityFrameworkCore; +using System.Data.SqlTypes; namespace GameIdeas.WebAPI.Services; @@ -27,7 +29,26 @@ public class GameService(GameIdeasContext context, IMapper mapper) .Take(pagging.NumberPerPage) .ToListAsync(); - return mapper.Map>(games); + List gameTest = [ new() { Id = 1, + Title = "Test", + CreationDate = DateTime.Today - TimeSpan.FromHours(-12), + CreationUser = new User() { Id = 1, Username = "Test", Role = (int)Role.Administrator }, + CreationUserId = 1, + Description = "Test", + GameDevelopers = [new() { Developer = new Developer() { Id = 1, Name = "THQNordic" } }], + GamePlatforms = [new() { Platform = new Platform() { Id = 1, Label = "Steam" }, Url = "steam.com?app=55554131" }], + GameProperties = [new() { Property = new Property() { Id = 1, Label = "Coop" } }], + GamePublishers = [new() { Publisher = new Publisher() { Id = 1, Name = "Take-Two" } }], + GameTags = [new() { Tag = new Tag() { Id = 1, Label = "RPG" } }], + Interest = 5, + ModificationDate = DateTime.Today , + ModificationUser = new User() { Id = 2, Username = "Test 2", Role = (int)Role.Member }, + ModificationUserId = 2, + ReleaseDate = new DateTime(2025, 2, 1), + StorageSpace = 40 + } ]; + + return mapper.Map>(gameTest); } public async Task GetGameById(int gameId) -- 2.39.5 From 2f9457fc7e8e7220caa9b3c5f6731223ff988bf7 Mon Sep 17 00:00:00 2001 From: Egamorf Date: Sat, 12 Apr 2025 21:32:10 +0200 Subject: [PATCH 7/7] Complete CRUD for games --- src/GameIdeas/GameIdeas.Shared/Dto/GameDto.cs | 2 + src/GameIdeas/GameIdeas.Shared/Dto/UserDto.cs | 2 - .../Context/GameIdeasContext.cs | 5 +- .../Controllers/GameController.cs | 74 ++++++- .../Profiles/CategoryProfile.cs | 73 ++++-- .../GameIdeas.WebAPI/Profiles/GameProfile.cs | 4 +- .../GameIdeas.WebAPI/Profiles/UserProfile.cs | 2 - .../Server/GameIdeas.WebAPI/Program.cs | 3 + .../GameIdeas.WebAPI/Services/GameService.cs | 209 +++++++++++------- 9 files changed, 276 insertions(+), 98 deletions(-) diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/GameDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/GameDto.cs index e0fca59..44e685b 100644 --- a/src/GameIdeas/GameIdeas.Shared/Dto/GameDto.cs +++ b/src/GameIdeas/GameIdeas.Shared/Dto/GameDto.cs @@ -7,8 +7,10 @@ public class GameDto 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 double? StorageSpace { get; set; } public string? Description { get; set; } public int? Interest { get; set; } diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/UserDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/UserDto.cs index 39d52ca..d98d15d 100644 --- a/src/GameIdeas/GameIdeas.Shared/Dto/UserDto.cs +++ b/src/GameIdeas/GameIdeas.Shared/Dto/UserDto.cs @@ -8,6 +8,4 @@ public class UserDto public string? Username { get; set; } public string? Password { get; set; } public Role? Role { get; set; } - public IEnumerable? CreationGames { get; set; } - public IEnumerable? ModificationGames { get; set; } } diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Context/GameIdeasContext.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Context/GameIdeasContext.cs index 87f4024..30d329f 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Context/GameIdeasContext.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Context/GameIdeasContext.cs @@ -7,7 +7,10 @@ public class GameIdeasContext : DbContext { public GameIdeasContext(DbContextOptions option) : base(option) - { } + { + AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); + AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true); + } public virtual DbSet Users { get; set; } = null!; public virtual DbSet Developers { get; set; } = null!; diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs index 44458e4..127c645 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/GameController.cs @@ -7,11 +7,77 @@ namespace GameIdeas.WebAPI.Controllers; [ApiController] [Route("api/[controller]")] -public class GameController(GameService gameService) : Controller +public class GameController(GameService gameService, ILoggerFactory loggerFactory) : Controller { - [HttpGet("Search")] - public async Task> FetchGames([FromQuery] PaggingDto pagging) + private readonly ILogger logger = loggerFactory.CreateLogger(); + + [HttpGet] + public async Task>> SearchGames([FromQuery] PaggingDto pagging) { - return await gameService.GetGames(pagging); + try + { + return Ok(await gameService.GetGames(pagging)); + } + catch (Exception e) + { + logger.LogError(e, "Internal error while search games"); + return StatusCode(500, e.Message); + } + } + + [HttpGet("{id:int}")] + public async Task> GetGameById(int id) + { + try + { + return Ok(await gameService.GetGameById(id)); + } + catch (Exception e) + { + logger.LogError(e, "Internal error while get game with id {id}", id); + return StatusCode(500, e.Message); + } + } + + [HttpPost("Create")] + public async Task> CreateGame([FromBody] GameDto game) + { + try + { + return Created("/Create", await gameService.CreateGame(game)); + } + catch (Exception e) + { + logger.LogError(e, "Internal error while create game"); + return StatusCode(500, e.Message); + } + } + + [HttpPut("Update")] + public async Task> UpdateGame([FromBody] GameDto game) + { + try + { + return Created($"/Update", await gameService.UpdateGame(game)); + } + catch (Exception e) + { + logger.LogError(e, "Internal error while update game"); + return StatusCode(500, e.Message); + } + } + + [HttpDelete("Delete/{id:int}")] + public async Task> DeleteGame(int id) + { + try + { + return Ok(await gameService.DeleteGame(id)); + } + catch (Exception e) + { + logger.LogError(e, "Internal error while delete game"); + return StatusCode(500, e.Message); + } } } diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/CategoryProfile.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/CategoryProfile.cs index 2763b8b..5ad0516 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/CategoryProfile.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/CategoryProfile.cs @@ -8,29 +8,76 @@ public class CategoryProfile : Profile { public CategoryProfile() { - CreateMap() + CreatePlaformMap(); + CreatePropertyMap(); + CreateTagMap(); + CreateDeveloperMap(); + CreatePublisherMap(); + } + + private void CreatePublisherMap() + { + CreateMap() .ForMember(d => d.Id, o => o.MapFrom(s => s.Id)) - .ForMember(d => d.Label, o => o.MapFrom(s => s.Label)) + .ForMember(d => d.Name, o => o.MapFrom(s => s.Name)) .ReverseMap(); - CreateMap() - .ForMember(d => d.Id, o => o.MapFrom(s => s.Id)) - .ForMember(d => d.Label, o => o.MapFrom(s => s.Label)) - .ReverseMap(); - - CreateMap() - .ForMember(d => d.Id, o => o.MapFrom(s => s.Id)) - .ForMember(d => d.Label, o => o.MapFrom(s => s.Label)) - .ReverseMap(); + CreateMap() + .ForMember(d => d.PublisherId, o => o.MapFrom(s => s.Id)) + .ForPath(d => d.Publisher.Id, o => o.MapFrom(s => s.Id)) + .ForPath(d => d.Publisher.Name, o => o.MapFrom(s => s.Name)); + } + private void CreateDeveloperMap() + { CreateMap() .ForMember(d => d.Id, o => o.MapFrom(s => s.Id)) .ForMember(d => d.Name, o => o.MapFrom(s => s.Name)) .ReverseMap(); - CreateMap() + CreateMap() + .ForMember(d => d.DeveloperId, o => o.MapFrom(s => s.Id)) + .ForPath(d => d.Developer.Id, o => o.MapFrom(s => s.Id)) + .ForPath(d => d.Developer.Name, o => o.MapFrom(s => s.Name)); + } + + private void CreateTagMap() + { + CreateMap() .ForMember(d => d.Id, o => o.MapFrom(s => s.Id)) - .ForMember(d => d.Name, o => o.MapFrom(s => s.Name)) + .ForMember(d => d.Label, o => o.MapFrom(s => s.Label)) .ReverseMap(); + + CreateMap() + .ForMember(d => d.TagId, o => o.MapFrom(s => s.Id)) + .ForPath(d => d.Tag.Id, o => o.MapFrom(s => s.Id)) + .ForPath(d => d.Tag.Label, o => o.MapFrom(s => s.Label)); + } + + private void CreatePropertyMap() + { + CreateMap() + .ForMember(d => d.Id, o => o.MapFrom(s => s.Id)) + .ForMember(d => d.Label, o => o.MapFrom(s => s.Label)) + .ReverseMap(); + + CreateMap() + .ForMember(d => d.PropertyId, o => o.MapFrom(s => s.Id)) + .ForPath(d => d.Property.Id, o => o.MapFrom(s => s.Id)) + .ForPath(d => d.Property.Label, o => o.MapFrom(s => s.Label)); + } + + private void CreatePlaformMap() + { + CreateMap() + .ForMember(d => d.Id, o => o.MapFrom(s => s.Id)) + .ForMember(d => d.Label, o => o.MapFrom(s => s.Label)) + .ReverseMap(); + + CreateMap() + .ForMember(d => d.PlatformId, o => o.MapFrom(s => s.Id)) + .ForPath(d => d.Platform.Id, o => o.MapFrom(s => s.Id)) + .ForPath(d => d.Platform.Label, o => o.MapFrom(s => s.Label)) + .ForMember(d => d.Url, o => o.MapFrom(s => s.Url)); } } diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs index a0e8d12..e87e187 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs @@ -13,10 +13,10 @@ public class GameProfile : Profile .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)) - .ForPath(d => d.CreationUser!.Id, o => o.MapFrom(s => s.CreationUserId)) + .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)) - .ForPath(d => d.ModificationUser!.Id, o => o.MapFrom(s => s.ModificationUserId)) + .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)) diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/UserProfile.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/UserProfile.cs index f0868d7..851b059 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/UserProfile.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/UserProfile.cs @@ -14,8 +14,6 @@ public class UserProfile : Profile .ForMember(d => d.Username, o => o.MapFrom(s => s.Username)) .ForMember(d => d.Password, o => o.MapFrom(s => s.Password)) .ForMember(d => d.Role, o => o.MapFrom(s => s.Role)) - .ForMember(d => d.CreationGames, o => o.MapFrom(s => s.CreationGames)) - .ForMember(d => d.ModificationGames, o => o.MapFrom(s => s.ModificationGames)) .ReverseMap(); } diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs index 4c0d6ea..9f6a079 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs @@ -3,6 +3,7 @@ using GameIdeas.WebAPI.Context; using GameIdeas.WebAPI.Profiles; using GameIdeas.WebAPI.Services; using Microsoft.EntityFrameworkCore; +using System.Text.Json.Serialization; var builder = WebApplication.CreateBuilder(args); var services = builder.Services; @@ -37,6 +38,7 @@ services.AddAutoMapper(typeof(UserProfile).Assembly); services.AddAutoMapper(typeof(CategoryProfile).Assembly); services.AddControllers(); + // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi services.AddOpenApi(); @@ -44,6 +46,7 @@ services.AddCors(option => option.AddDefaultPolicy(policy => policy.WithOrigins("http://localhost:5172", "http://localhost:7060") .AllowAnyHeader() .WithMethods("GET", "POST", "PUT", "DELETE"))); + var app = builder.Build(); await LoadTranslations(); diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs index 3743d8b..d3c073f 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/GameService.cs @@ -1,11 +1,9 @@ using AutoMapper; using GameIdeas.Shared.Dto; -using GameIdeas.Shared.Enum; using GameIdeas.Shared.Exceptions; using GameIdeas.Shared.Model; using GameIdeas.WebAPI.Context; using Microsoft.EntityFrameworkCore; -using System.Data.SqlTypes; namespace GameIdeas.WebAPI.Services; @@ -13,7 +11,77 @@ public class GameService(GameIdeasContext context, IMapper mapper) { public async Task> GetGames(PaggingDto pagging) { - var query = context.Games + var games = await SelectGames() + .OrderBy(g => g.Title) + .Skip((pagging.CurrentPage - 1) * pagging.NumberPerPage) + .Take(pagging.NumberPerPage) + .ToListAsync(); + + return mapper.Map>(games); + } + + 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); + } + + public async Task CreateGame(GameDto gameDto) + { + var gameToCreate = mapper.Map(gameDto); + + await context.Games.AddAsync(gameToCreate); + await context.SaveChangesAsync(); + + await HandlePlatformsCreation(gameDto.Platforms, gameToCreate.Id); + await HandlePropertiesCreation(gameDto.Properties, gameToCreate.Id); + await HandleTagsCreation(gameDto.Tags, gameToCreate.Id); + await HandlePublishersCreation(gameDto.Publishers, gameToCreate.Id); + await HandleDevelopersCreation(gameDto.Developers, gameToCreate.Id); + + await context.SaveChangesAsync(); + + return mapper.Map(gameToCreate); + } + + public async Task UpdateGame(GameDto gameDto) + { + if (await context.Games.CountAsync(g => g.Id == gameDto.Id) == 0) + { + throw new NotFoundException($"[{typeof(Game).FullName}] with ID {gameDto.Id} has not been found in context"); + } + + var gameToUpdate = mapper.Map(gameDto); + + await HandlePlatformsCreation(gameDto.Platforms, gameToUpdate.Id); + await HandlePropertiesCreation(gameDto.Properties, gameToUpdate.Id); + await HandleTagsCreation(gameDto.Tags, gameToUpdate.Id); + await HandlePublishersCreation(gameDto.Publishers, gameToUpdate.Id); + await HandleDevelopersCreation(gameDto.Developers, gameToUpdate.Id); + + context.Games.Update(gameToUpdate); + await context.SaveChangesAsync(); + + return mapper.Map(gameToUpdate); + } + + public async Task DeleteGame(int gameId) + { + var gameToRemove = await context.Games + .FirstOrDefaultAsync(g => g.Id == gameId) + ?? throw new NotFoundException($"[{typeof(Game).FullName}] with ID {gameId} has not been found in context"); + + context.Games.Remove(gameToRemove); + return await context.SaveChangesAsync() != 0; + } + + private IQueryable SelectGames() + { + return context.Games .Include(g => g.CreationUser) .Include(g => g.ModificationUser) .Include(g => g.GamePlatforms).ThenInclude(p => p.Platform) @@ -22,92 +90,85 @@ public class GameService(GameIdeasContext context, IMapper mapper) .Include(g => g.GamePublishers).ThenInclude(p => p.Publisher) .Include(g => g.GameDevelopers).ThenInclude(p => p.Developer) .AsQueryable(); - - var games = await query - .OrderBy(g => g.Title) - .Skip(pagging.CurrentPage * pagging.NumberPerPage) - .Take(pagging.NumberPerPage) - .ToListAsync(); - - List gameTest = [ new() { Id = 1, - Title = "Test", - CreationDate = DateTime.Today - TimeSpan.FromHours(-12), - CreationUser = new User() { Id = 1, Username = "Test", Role = (int)Role.Administrator }, - CreationUserId = 1, - Description = "Test", - GameDevelopers = [new() { Developer = new Developer() { Id = 1, Name = "THQNordic" } }], - GamePlatforms = [new() { Platform = new Platform() { Id = 1, Label = "Steam" }, Url = "steam.com?app=55554131" }], - GameProperties = [new() { Property = new Property() { Id = 1, Label = "Coop" } }], - GamePublishers = [new() { Publisher = new Publisher() { Id = 1, Name = "Take-Two" } }], - GameTags = [new() { Tag = new Tag() { Id = 1, Label = "RPG" } }], - Interest = 5, - ModificationDate = DateTime.Today , - ModificationUser = new User() { Id = 2, Username = "Test 2", Role = (int)Role.Member }, - ModificationUserId = 2, - ReleaseDate = new DateTime(2025, 2, 1), - StorageSpace = 40 - } ]; - - return mapper.Map>(gameTest); } - public async Task GetGameById(int gameId) + private async Task HandlePlatformsCreation(IEnumerable? categoriesToCreate, 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); + if (categoriesToCreate != null) + { + var gps = mapper.Map>(categoriesToCreate); - return game == null - ? throw new NotFoundException($"[{typeof(Game).Name}] with ID {gameId} has not been found in context") - : mapper.Map(game); + foreach (var gp in gps) + { + gp.GameId = gameId; + } + + context.Platforms.AttachRange(gps.Select(gp => gp.Platform)); + await context.GamePlatforms.AddRangeAsync(gps); + } } - public async Task CreateGame(GameDto gameDto) + private async Task HandlePropertiesCreation(IEnumerable? categoriesToCreate, int gameId) { - var game = mapper.Map(gameDto); + if (categoriesToCreate != null) + { + var gps = mapper.Map>(categoriesToCreate); - context.GameDevelopers.AttachRange(game.GameDevelopers); - context.Developers.AttachRange(game.GameDevelopers.Select(gd => gd.Developer)); - context.GamePlatforms.AttachRange(game.GamePlatforms); - context.Platforms.AttachRange(game.GamePlatforms.Select(gp => gp.Platform)); - context.GameProperties.AttachRange(game.GameProperties); - context.Properties.AttachRange(game.GameProperties.Select(gp => gp.Property)); - context.GamePublishers.AttachRange(game.GamePublishers); - context.Publishers.AttachRange(game.GamePublishers.Select(gp => gp.Publisher)); - context.GameTags.AttachRange(game.GameTags); - context.Tags.AttachRange(game.GameTags.Select(gt => gt.Tag)); + foreach (var gp in gps) + { + gp.GameId = gameId; + } - await context.Games.AddAsync(game); - await context.SaveChangesAsync(); - - return mapper.Map(game); + context.Properties.AttachRange(gps.Select(gp => gp.Property)); + await context.GameProperties.AddRangeAsync(gps); + } } - public async Task UpdateGame(GameDto gameDto) + private async Task HandleTagsCreation(IEnumerable? categoriesToCreate, int gameId) { - var gameToUpdate = mapper.Map(gameDto); + if (categoriesToCreate != null) + { + var gts = mapper.Map>(categoriesToCreate); - context.GameDevelopers.AttachRange(gameToUpdate.GameDevelopers); - context.Developers.AttachRange(gameToUpdate.GameDevelopers.Select(gd => gd.Developer)); - context.GamePlatforms.AttachRange(gameToUpdate.GamePlatforms); - context.Platforms.AttachRange(gameToUpdate.GamePlatforms.Select(gp => gp.Platform)); - context.GameProperties.AttachRange(gameToUpdate.GameProperties); - context.Properties.AttachRange(gameToUpdate.GameProperties.Select(gp => gp.Property)); - context.GamePublishers.AttachRange(gameToUpdate.GamePublishers); - context.Publishers.AttachRange(gameToUpdate.GamePublishers.Select(gp => gp.Publisher)); - context.GameTags.AttachRange(gameToUpdate.GameTags); - context.Tags.AttachRange(gameToUpdate.GameTags.Select(gt => gt.Tag)); + foreach (var gt in gts) + { + gt.GameId = gameId; + } + context.Tags.AttachRange(gts.Select(gt => gt.Tag)); + await context.GameTags.AddRangeAsync(gts); + } + } - context.Games.Update(gameToUpdate); - await context.SaveChangesAsync(); + private async Task HandlePublishersCreation(IEnumerable? categoriesToCreate, int gameId) + { + if (categoriesToCreate != null) + { + var gps = mapper.Map>(categoriesToCreate); - return mapper.Map(gameToUpdate); + foreach (var gp in gps) + { + gp.GameId = gameId; + } + + context.Publishers.AttachRange(gps.Select(gp => gp.Publisher)); + await context.GamePublishers.AddRangeAsync(gps); + } + } + + private async Task HandleDevelopersCreation(IEnumerable? categoriesToCreate, int gameId) + { + if (categoriesToCreate != null) + { + var gds = mapper.Map>(categoriesToCreate); + + foreach (var gd in gds) + { + gd.GameId = gameId; + } + + context.Developers.AttachRange(gds.Select(gd => gd.Developer)); + await context.GameDevelopers.AddRangeAsync(gds); + } } } -- 2.39.5