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); + } } }