diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Detail/GameDetail.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Detail/GameDetail.razor new file mode 100644 index 0000000..6350c31 --- /dev/null +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Detail/GameDetail.razor @@ -0,0 +1,98 @@ +@page "/Detail/{GameId:int}" +@using GameIdeas.BlazorApp.Helpers +@using GameIdeas.BlazorApp.Shared.Components.Header +@using GameIdeas.BlazorApp.Shared.Components.Interest +@using GameIdeas.BlazorApp.Shared.Constants +@layout MainLayout + + + + + +
+
+ @Icons.Back +

@Game.Title

+ +
+ + +
+ @Game.Description + +
+
+ +
+
+

@ResourcesKey.Properties

+
+ @foreach (var property in Game.Properties ?? []) + { +
@property.Label
+ } +
+
+
+

@ResourcesKey.Tags

+
+ @foreach (var property in Game.Tags ?? []) + { +
@property.Label
+ } +
+
+
+ +
+
+

@ResourcesKey.About

+
+ @if (Game.ReleaseDate != null) + { +
+ @ResourcesKey.ReleaseDate + @Game.ReleaseDate?.ToShortDateString() +
+ } + + @if (Game.StorageSpace != null) + { +
+ @ResourcesKey.StorageSize + @GameHelper.GetFormatedStorageSpace(Game.StorageSpace) +
+ } + + @if (Game.Developer != null) + { +
+ @ResourcesKey.Developer + @Game.Developer?.Name +
+ } + + @if (Game.Publisher != null) + { +
+ @ResourcesKey.Publisher + @Game.Publisher?.Name +
+ } +
+
+ +
+

@ResourcesKey.Platforms

+
+ @foreach (var platform in Game.Platforms ?? []) + { + @platform.Label + } +
+
+
+ +
+
+
\ No newline at end of file diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Detail/GameDetail.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Detail/GameDetail.razor.cs new file mode 100644 index 0000000..035976c --- /dev/null +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Detail/GameDetail.razor.cs @@ -0,0 +1,19 @@ +using GameIdeas.BlazorApp.Pages.Games.Gateways; +using GameIdeas.Shared.Dto; +using Microsoft.AspNetCore.Components; + +namespace GameIdeas.BlazorApp.Pages.Detail; + +public partial class GameDetail +{ + [Inject] private IGameGateway GameGateway { get; set; } = default!; + [Parameter] public int GameId { get; set; } + + private GameDetailDto Game = new(); + + protected override async Task OnInitializedAsync() + { + Game = await GameGateway.GetGameById(GameId); + await base.OnInitializedAsync(); + } +} \ No newline at end of file diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Detail/GameDetail.razor.css b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Detail/GameDetail.razor.css new file mode 100644 index 0000000..8c8695b --- /dev/null +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Detail/GameDetail.razor.css @@ -0,0 +1,103 @@ +.detail-container, .properties-tags { + display: grid; + grid-gap: 20px; +} + +.flex { + display: flex; + gap: 8px; + align-items: center; +} + +.section { + padding: 20px 100px; +} + +.col-2 { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 40px; +} + +.first-larger { + grid-template-columns: 3fr 2fr; +} + +.dark { + background: rgb(0, 0, 0, 0.4) +} + +.header-1, .header-2 { + width: 100%; +} + +.header-2 { + margin-bottom: 20px; +} + +.pills, .informations { + display: flex; + flex-wrap: wrap; + gap: 34px; +} + +.additional-informations, .platforms { + padding: 20px; + background: var(--input-secondary); + box-shadow: var(--drop-shadow); + border-radius: var(--big-radius); +} + +.pill { + width: fit-content; + height: 24px; + padding: 0 6px; + background: rgb(255, 255, 255, 0.2); + border-radius: var(--small-radius); + align-content: center; +} + +.platform-pill { + color: var(--violet); + cursor: pointer; + text-decoration: none; +} + + .platform-pill:hover { + text-decoration: underline; + } + +.square-button { + height: 28px; + min-height: 28px; + width: 28px; + min-width: 28px; + border-radius: var(--small-radius); + background: var(--input-primary); + overflow: hidden; + cursor: pointer; +} + + .square-button ::deep svg { + fill: var(--white); + } + + .square-button:hover ::deep svg { + background: var(--input-selected); + } + +@media screen and (max-width: 1000px) { + .section { + padding: 20px; + } +} + +@media screen and (max-width: 700px) { + .col-2 { + grid-template-columns: 1fr; + } + + .platforms { + grid-row: 1; + } +} \ No newline at end of file diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameCreationForm.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameCreationForm.razor index 103208d..c9e1028 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameCreationForm.razor +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameCreationForm.razor @@ -1,5 +1,6 @@ @using Blazored.FluentValidation @using GameIdeas.BlazorApp.Shared.Components.CircleLoader +@using GameIdeas.BlazorApp.Shared.Components.Select.Models @using GameIdeas.BlazorApp.Shared.Components.SelectSearch @using GameIdeas.BlazorApp.Shared.Components.Slider @using GameIdeas.Shared.Dto @@ -21,16 +22,16 @@
-
@ResourcesKey.Developers :
+
@ResourcesKey.Developer :
+ Items="Categories?.Developers" ValuesChanged="HandleDeveloperChanged" + AddItem="@(str => new DeveloperDto() { Name = str })" SelectType="SelectType.Single" />
-
@ResourcesKey.Publishers :
+
@ResourcesKey.Publisher :
+ Items="Categories?.Publishers" ValuesChanged="HandlePublisherChanged" + AddItem="@(str => new PublisherDto() { Name = str })" SelectType="SelectType.Single" />
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 9685dc5..24ec5e9 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 @@ -75,4 +75,12 @@ public partial class GameCreationForm StateHasChanged(); } } + private void HandlePublisherChanged(List pubs) + { + GameDto.Publisher = pubs.FirstOrDefault(); + } + private void HandleDeveloperChanged(List devs) + { + GameDto.Developer = devs.FirstOrDefault(); + } } \ No newline at end of file diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameRow.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameRow.razor index b31104c..180382d 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameRow.razor +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameRow.razor @@ -1,11 +1,12 @@ @using GameIdeas.BlazorApp.Helpers +@using GameIdeas.BlazorApp.Shared.Components.Interest @using GameIdeas.BlazorApp.Shared.Constants @inherits GameBase
- @GameDto.Title + @GameDto.Title @(GameDto.ReleaseDate?.ToShortDateString() ?? @ResourcesKey.Unknown) @@ -30,12 +31,7 @@ @GameHelper.GetFormatedStorageSpace(GameDto.StorageSpace) -
- - @GameDto.Interest - - /5 -
+
\ No newline at end of file diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameRow.razor.css b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameRow.razor.css index 3adfb08..14d3ae6 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameRow.razor.css +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Components/GameRow.razor.css @@ -11,7 +11,7 @@ overflow: hidden; } -.row * { +.row > * { max-height: 64px; height: fit-content; padding: 6px 0; @@ -39,7 +39,7 @@ background: var(--input-selected); } -.release-date, .storage, .max-value { +.release-date, .storage { color: rgb(184, 184, 184); } @@ -50,7 +50,7 @@ background: rgb(255, 255, 255, 0.2); border-radius: var(--small-radius); align-content: center; -} +} .platforms, .tags { display: flex; @@ -80,16 +80,6 @@ fill: var(--white); } -.value { - font-size: 24px; - font-weight: bold; -} - -.max-value { - position: absolute; - transform: translate(2px, 10px); -} - @media screen and (max-width: 1000px) { .row { grid-template-columns: 48px 3fr 2fr 3fr 30px 30px; diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/AdvancedGameFilter.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/AdvancedGameFilter.razor index 66d3f6f..e111326 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/AdvancedGameFilter.razor +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Filter/AdvancedGameFilter.razor @@ -16,10 +16,10 @@ - - GetGameById(int gameId) + { + try + { + var result = await httpClientService.FetchDataAsync(Endpoints.Game.FetchById(gameId)); + + return result ?? throw new InvalidOperationException(ResourcesKey.ErrorFetchGames); + } + catch (Exception) + { + throw new CategoryNotFoundException(ResourcesKey.ErrorFetchGames); + } + } } 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 d78155f..9bc4fc4 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/IGameGateway.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Games/Gateways/IGameGateway.cs @@ -8,4 +8,5 @@ public interface IGameGateway Task FetchCategories(); Task CreateGame(GameDetailDto game); Task> FetchGames(GameFilterParams filter, int currentPage); + Task GetGameById(int gameId); } diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor index dbf9d6b..0fbc631 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor @@ -1,5 +1,4 @@ @page "/Users" -@using GameIdeas.BlazorApp.Layouts @using GameIdeas.BlazorApp.Pages.Users.Components @using GameIdeas.BlazorApp.Shared.Components.Header @using GameIdeas.BlazorApp.Shared.Components.Popup diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Interest/Interest.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Interest/Interest.razor new file mode 100644 index 0000000..14a8238 --- /dev/null +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Interest/Interest.razor @@ -0,0 +1,9 @@ +@using GameIdeas.BlazorApp.Helpers + +
+ + @Value + + /5 +
+ diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Interest/Interest.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Interest/Interest.razor.cs new file mode 100644 index 0000000..b65b0d8 --- /dev/null +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Interest/Interest.razor.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Components; + +namespace GameIdeas.BlazorApp.Shared.Components.Interest; + +public partial class Interest +{ + [Parameter] public int Value { get; set; } +} \ No newline at end of file diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Interest/Interest.razor.css b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Interest/Interest.razor.css new file mode 100644 index 0000000..a2e45ac --- /dev/null +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Components/Interest/Interest.razor.css @@ -0,0 +1,16 @@ +.interest { + position: relative; + display: flex; +} + +.value { + align-content: center; + font-size: 24px; + font-weight: bold; +} + +.max-value { + align-content: center; + transform: translate(2px, 10px); + color: rgb(184, 184, 184); +} \ No newline at end of file diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Endpoints.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Endpoints.cs index 53c7180..c16d19b 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Endpoints.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Endpoints.cs @@ -9,6 +9,7 @@ public static class Endpoints { public const string Create = "api/Game/Create"; public static string Fetch(GameFilterDto filter) => $"api/Game?{UrlHelper.BuildUrlParams(filter)}"; + public static string FetchById(int gameId) => $"api/Game/{gameId}"; } public static class Category diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Icons.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Icons.cs index 2892160..582fe1d 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Icons.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Icons.cs @@ -39,4 +39,8 @@ public static class Icons public readonly static MarkupString Check = new(OpenBraket + "" + CloseBraket); + + public readonly static MarkupString Back = new(OpenBraket + + "" + + CloseBraket); } diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/_Imports.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/_Imports.razor index 587e1a1..55bd956 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/_Imports.razor +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/_Imports.razor @@ -7,3 +7,4 @@ @using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.JSInterop @using GameIdeas.Resources +@using GameIdeas.BlazorApp.Layouts diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/wwwroot/css/app.css b/src/GameIdeas/Client/GameIdeas.BlazorApp/wwwroot/css/app.css index 1f9a59a..c443f39 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/wwwroot/css/app.css +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/wwwroot/css/app.css @@ -132,6 +132,32 @@ code { outline: none; } +.expand-col-2 { + grid-column: 1 / 3; +} + +.expand-row-2 { + grid-row: 1 / 3; +} + +.body-sm { + color: #ccc +} + +.body-lg { + font-weight: 400; + font-size: 14px; +} + +.header-1, .header-2, span, a { + display: block; + color: var(--white); + margin: 0; + padding: 0; + text-decoration: none; +} + + @keyframes fade-in { 0% {opacity: 0} 100% {opacity: 1} diff --git a/src/GameIdeas/GameIdeas.Resources/CreateStaticResourceKey.cs b/src/GameIdeas/GameIdeas.Resources/CreateStaticResourceKey.cs index db3cccb..62ae033 100644 --- a/src/GameIdeas/GameIdeas.Resources/CreateStaticResourceKey.cs +++ b/src/GameIdeas/GameIdeas.Resources/CreateStaticResourceKey.cs @@ -17,8 +17,8 @@ public class Translations (TranslationService translationService) public string Research => translationService.Translate(nameof(Research)); public string Platforms => translationService.Translate(nameof(Platforms)); public string Tags => translationService.Translate(nameof(Tags)); - public string Publishers => translationService.Translate(nameof(Publishers)); - public string Developers => translationService.Translate(nameof(Developers)); + public string Publisher => translationService.Translate(nameof(Publisher)); + public string Developer => translationService.Translate(nameof(Developer)); public string StorageSize => translationService.Translate(nameof(StorageSize)); public string StorageSizeMo => translationService.Translate(nameof(StorageSizeMo)); public string LastModification => translationService.Translate(nameof(LastModification)); @@ -64,6 +64,8 @@ public class Translations (TranslationService translationService) public string Cancel => translationService.Translate(nameof(Cancel)); public string Confirm => translationService.Translate(nameof(Confirm)); public string ConfirmDeleteDescription => translationService.Translate(nameof(ConfirmDeleteDescription)); + public string Informations => translationService.Translate(nameof(Informations)); + public string About => translationService.Translate(nameof(About)); } public static class ResourcesKey @@ -89,8 +91,8 @@ public static class ResourcesKey public static string Research => _instance?.Research ?? throw new InvalidOperationException("ResourcesKey.Research is not initialized."); public static string Platforms => _instance?.Platforms ?? throw new InvalidOperationException("ResourcesKey.Platforms is not initialized."); public static string Tags => _instance?.Tags ?? throw new InvalidOperationException("ResourcesKey.Tags is not initialized."); - public static string Publishers => _instance?.Publishers ?? throw new InvalidOperationException("ResourcesKey.Publishers is not initialized."); - public static string Developers => _instance?.Developers ?? throw new InvalidOperationException("ResourcesKey.Developers is not initialized."); + public static string Publisher => _instance?.Publisher ?? throw new InvalidOperationException("ResourcesKey.Publisher is not initialized."); + public static string Developer => _instance?.Developer ?? throw new InvalidOperationException("ResourcesKey.Developer is not initialized."); public static string StorageSize => _instance?.StorageSize ?? throw new InvalidOperationException("ResourcesKey.StorageSize is not initialized."); public static string StorageSizeMo => _instance?.StorageSizeMo ?? throw new InvalidOperationException("ResourcesKey.StorageSizeMo is not initialized."); public static string LastModification => _instance?.LastModification ?? throw new InvalidOperationException("ResourcesKey.LastModification is not initialized."); @@ -136,4 +138,6 @@ public static class ResourcesKey public static string Cancel => _instance?.Cancel ?? throw new InvalidOperationException("ResourcesKey.Cancel is not initialized."); public static string Confirm => _instance?.Confirm ?? throw new InvalidOperationException("ResourcesKey.Confirm is not initialized."); public static string ConfirmDeleteDescription => _instance?.ConfirmDeleteDescription ?? throw new InvalidOperationException("ResourcesKey.ConfirmDeleteDescription is not initialized."); + public static string Informations => _instance?.Informations ?? throw new InvalidOperationException("ResourcesKey.Informations is not initialized."); + public static string About => _instance?.About ?? throw new InvalidOperationException("ResourcesKey.About is not initialized."); } \ No newline at end of file diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/GameDetailDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/GameDetailDto.cs index 78e5548..24b6d01 100644 --- a/src/GameIdeas/GameIdeas.Shared/Dto/GameDetailDto.cs +++ b/src/GameIdeas/GameIdeas.Shared/Dto/GameDetailDto.cs @@ -15,6 +15,6 @@ public class GameDetailDto 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 PublisherDto? Publisher { get; set; } + public DeveloperDto? Developer { get; set; } } \ No newline at end of file diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/PlatformDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/PlatformDto.cs index 472f1cc..2d2a418 100644 --- a/src/GameIdeas/GameIdeas.Shared/Dto/PlatformDto.cs +++ b/src/GameIdeas/GameIdeas.Shared/Dto/PlatformDto.cs @@ -5,4 +5,5 @@ public class PlatformDto public int? Id { get; set; } public string? Label { get; set; } public string? Url { get; set; } + public string? IconUrl { get; set; } } diff --git a/src/GameIdeas/GameIdeas.Shared/Model/Developer.cs b/src/GameIdeas/GameIdeas.Shared/Model/Developer.cs index 97c7fdd..5d70413 100644 --- a/src/GameIdeas/GameIdeas.Shared/Model/Developer.cs +++ b/src/GameIdeas/GameIdeas.Shared/Model/Developer.cs @@ -4,11 +4,11 @@ public partial class Developer { public Developer() { - GameDevelopers = new HashSet(); + Games = new HashSet(); } public int Id { get; set; } public string Name { get; set; } = null!; - public virtual ICollection GameDevelopers { get; set; } + public virtual ICollection Games { get; set; } } diff --git a/src/GameIdeas/GameIdeas.Shared/Model/Game.cs b/src/GameIdeas/GameIdeas.Shared/Model/Game.cs index 680a7cc..fee8508 100644 --- a/src/GameIdeas/GameIdeas.Shared/Model/Game.cs +++ b/src/GameIdeas/GameIdeas.Shared/Model/Game.cs @@ -7,8 +7,6 @@ public partial class Game GamePlatforms = new HashSet(); GameProperties = new HashSet(); GameTags = new HashSet(); - GamePublishers = new HashSet(); - GameDevelopers = new HashSet(); } public int Id { get; set; } @@ -21,14 +19,16 @@ public partial class Game public double? StorageSpace { get; set; } public string? Description { get; set; } public int Interest { get; set; } + public int? PublisherId { get; set; } + public int? DeveloperId { get; set; } public virtual User CreationUser { get; set; } = null!; public virtual User? ModificationUser { get; set; } + public virtual Publisher? Publisher { get; set; } + public virtual Developer? Developer { get; set; } public virtual ICollection GamePlatforms { get; set; } public virtual ICollection GameProperties { get; set; } public virtual ICollection GameTags { get; set; } - public virtual ICollection GamePublishers { get; set; } - public virtual ICollection GameDevelopers { get; set; } } \ No newline at end of file diff --git a/src/GameIdeas/GameIdeas.Shared/Model/GameDeveloper.cs b/src/GameIdeas/GameIdeas.Shared/Model/GameDeveloper.cs deleted file mode 100644 index a4d4c56..0000000 --- a/src/GameIdeas/GameIdeas.Shared/Model/GameDeveloper.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace GameIdeas.Shared.Model; - -public partial class GameDeveloper -{ - public int GameId { get; set; } - public int DeveloperId { get; set; } - - public virtual Game Game { get; set; } = null!; - public virtual Developer Developer { get; set; } = null!; -} diff --git a/src/GameIdeas/GameIdeas.Shared/Model/GamePublisher.cs b/src/GameIdeas/GameIdeas.Shared/Model/GamePublisher.cs deleted file mode 100644 index fa255fd..0000000 --- a/src/GameIdeas/GameIdeas.Shared/Model/GamePublisher.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace GameIdeas.Shared.Model; - -public partial class GamePublisher -{ - public int GameId { get; set; } - public int PublisherId { get; set; } - - public virtual Game Game { get; set; } = null!; - public virtual Publisher Publisher { get; set; } = null!; -} diff --git a/src/GameIdeas/GameIdeas.Shared/Model/Platform.cs b/src/GameIdeas/GameIdeas.Shared/Model/Platform.cs index b1557c9..decde48 100644 --- a/src/GameIdeas/GameIdeas.Shared/Model/Platform.cs +++ b/src/GameIdeas/GameIdeas.Shared/Model/Platform.cs @@ -9,6 +9,7 @@ public partial class Platform public int Id { get; set; } public string Label { get; set; } = null!; + public string? IconUrl { get; set; } public virtual ICollection GamePlatforms { get; set; } diff --git a/src/GameIdeas/GameIdeas.Shared/Model/Publisher.cs b/src/GameIdeas/GameIdeas.Shared/Model/Publisher.cs index 1bf0c9c..bae4c71 100644 --- a/src/GameIdeas/GameIdeas.Shared/Model/Publisher.cs +++ b/src/GameIdeas/GameIdeas.Shared/Model/Publisher.cs @@ -4,11 +4,11 @@ public partial class Publisher { public Publisher() { - GamePublishers = new HashSet(); + Games = new HashSet(); } public int Id { get; set; } public string Name { get; set; } = null!; - public virtual ICollection GamePublishers { get; set; } + public virtual ICollection Games { get; set; } } diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Context/GameIdeasContext.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Context/GameIdeasContext.cs index 836b915..ece2f8c 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Context/GameIdeasContext.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Context/GameIdeasContext.cs @@ -19,10 +19,8 @@ public class GameIdeasContext : IdentityDbContext public virtual DbSet Publishers { get; set; } = null!; public virtual DbSet Tags { get; set; } = null!; public virtual DbSet Games { get; set; } = null!; - public virtual DbSet GameDevelopers { get; set; } = null!; public virtual DbSet GamePlatforms { get; set; } = null!; public virtual DbSet GameProperties { get; set; } = null!; - public virtual DbSet GamePublishers { get; set; } = null!; public virtual DbSet GameTags { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -76,6 +74,10 @@ public class GameIdeasContext : IdentityDbContext entity.HasIndex(e => e.ModificationUserId); + entity.HasIndex(e => e.PublisherId); + + entity.HasIndex(e => e.DeveloperId); + entity.Property(e => e.CreationDate) .HasDefaultValueSql("now()"); @@ -92,23 +94,16 @@ public class GameIdeasContext : IdentityDbContext .WithMany(p => p.ModificationGames) .HasForeignKey(d => d.ModificationUserId) .OnDelete(DeleteBehavior.ClientSetNull); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("GameDeveloper"); - - entity.HasKey(e => new { e.GameId, e.DeveloperId }); - - entity.HasOne(d => d.Game) - .WithMany(p => p.GameDevelopers) - .HasForeignKey(d => d.GameId) - .OnDelete(DeleteBehavior.Cascade); entity.HasOne(d => d.Developer) - .WithMany(p => p.GameDevelopers) + .WithMany(p => p.Games) .HasForeignKey(d => d.DeveloperId) - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.ClientSetNull); + + entity.HasOne(d => d.Publisher) + .WithMany(p => p.Games) + .HasForeignKey(d => d.PublisherId) + .OnDelete(DeleteBehavior.ClientSetNull); }); modelBuilder.Entity(entity => @@ -145,23 +140,6 @@ public class GameIdeasContext : IdentityDbContext .OnDelete(DeleteBehavior.Cascade); }); - modelBuilder.Entity(entity => - { - entity.ToTable("GamePublisher"); - - entity.HasKey(e => new { e.GameId, e.PublisherId }); - - entity.HasOne(d => d.Game) - .WithMany(p => p.GamePublishers) - .HasForeignKey(d => d.GameId) - .OnDelete(DeleteBehavior.Cascade); - - entity.HasOne(d => d.Publisher) - .WithMany(p => p.GamePublishers) - .HasForeignKey(d => d.PublisherId) - .OnDelete(DeleteBehavior.Cascade); - }); - modelBuilder.Entity(entity => { entity.ToTable("GameTag"); diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Files/GameIdeas.fr.json b/src/GameIdeas/Server/GameIdeas.WebAPI/Files/GameIdeas.fr.json index 43a1047..ec9317f 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Files/GameIdeas.fr.json +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Files/GameIdeas.fr.json @@ -13,8 +13,8 @@ "Research": "Rechercher", "Platforms": "Plateformes", "Tags": "Genres", - "Publishers": "Editeurs", - "Developers": "Développeurs", + "Publisher": "Editeur", + "Developer": "Développeur", "StorageSize": "Taille d'espace", "StorageSizeMo": "Taille d'espace en Mo", "LastModification": "Dernière modifications", @@ -59,5 +59,7 @@ "ErrorDeleteUser": "Erreur lors de la suppression d'un utilisateur", "Cancel": "Annuler", "Confirm": "Confirmer", - "ConfirmDeleteDescription": "Êtes-vous sur de vouloir supprimer cet élément ?" + "ConfirmDeleteDescription": "Êtes-vous sur de vouloir supprimer cet élément ?", + "Informations": "Informations", + "About": "À propos" } \ No newline at end of file diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250502200035_One-Many-Categories.Designer.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250502200035_One-Many-Categories.Designer.cs new file mode 100644 index 0000000..a61a96a --- /dev/null +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250502200035_One-Many-Categories.Designer.cs @@ -0,0 +1,616 @@ +// +using System; +using GameIdeas.WebAPI.Context; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace GameIdeas.WebAPI.Migrations +{ + [DbContext(typeof(GameIdeasContext))] + [Migration("20250502200035_One-Many-Categories")] + partial class OneManyCategories + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("GameIdeas.Shared.Model.Developer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Developer", (string)null); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.Game", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + NpgsqlPropertyBuilderExtensions.HasIdentityOptions(b.Property("Id"), 100000L, null, null, null, null, null); + + b.Property("CreationDate") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp without time zone") + .HasDefaultValueSql("now()"); + + b.Property("CreationUserId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("DeveloperId") + .HasColumnType("integer"); + + b.Property("Interest") + .HasColumnType("integer"); + + b.Property("ModificationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ModificationUserId") + .HasColumnType("text"); + + b.Property("PublisherId") + .HasColumnType("integer"); + + b.Property("ReleaseDate") + .HasColumnType("timestamp without time zone"); + + b.Property("StorageSpace") + .HasColumnType("double precision"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CreationUserId"); + + b.HasIndex("DeveloperId"); + + b.HasIndex("ModificationUserId"); + + b.HasIndex("PublisherId"); + + b.HasIndex("Title") + .IsUnique(); + + b.ToTable("Game", (string)null); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.GamePlatform", b => + { + b.Property("GameId") + .HasColumnType("integer"); + + b.Property("PlatformId") + .HasColumnType("integer"); + + b.Property("Url") + .HasColumnType("text"); + + b.HasKey("GameId", "PlatformId"); + + b.HasIndex("PlatformId"); + + b.ToTable("GamePlatform", (string)null); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.GameProperty", b => + { + b.Property("GameId") + .HasColumnType("integer"); + + b.Property("PropertyId") + .HasColumnType("integer"); + + b.HasKey("GameId", "PropertyId"); + + b.HasIndex("PropertyId"); + + b.ToTable("GameProperty", (string)null); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.GameTag", b => + { + b.Property("GameId") + .HasColumnType("integer"); + + b.Property("TagId") + .HasColumnType("integer"); + + b.HasKey("GameId", "TagId"); + + b.HasIndex("TagId"); + + b.ToTable("GameTag", (string)null); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.Platform", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("IconUrl") + .HasColumnType("text"); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Label") + .IsUnique(); + + b.ToTable("Platform", (string)null); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.Property", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Label") + .IsUnique(); + + b.ToTable("Property", (string)null); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.Publisher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Publisher", (string)null); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Label") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Label") + .IsUnique(); + + b.ToTable("Tag", (string)null); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.User", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.Game", b => + { + b.HasOne("GameIdeas.Shared.Model.User", "CreationUser") + .WithMany("CreationGames") + .HasForeignKey("CreationUserId") + .IsRequired(); + + b.HasOne("GameIdeas.Shared.Model.Developer", "Developer") + .WithMany("Games") + .HasForeignKey("DeveloperId"); + + b.HasOne("GameIdeas.Shared.Model.User", "ModificationUser") + .WithMany("ModificationGames") + .HasForeignKey("ModificationUserId"); + + b.HasOne("GameIdeas.Shared.Model.Publisher", "Publisher") + .WithMany("Games") + .HasForeignKey("PublisherId"); + + b.Navigation("CreationUser"); + + b.Navigation("Developer"); + + b.Navigation("ModificationUser"); + + b.Navigation("Publisher"); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.GamePlatform", b => + { + b.HasOne("GameIdeas.Shared.Model.Game", "Game") + .WithMany("GamePlatforms") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GameIdeas.Shared.Model.Platform", "Platform") + .WithMany("GamePlatforms") + .HasForeignKey("PlatformId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Game"); + + b.Navigation("Platform"); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.GameProperty", b => + { + b.HasOne("GameIdeas.Shared.Model.Game", "Game") + .WithMany("GameProperties") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GameIdeas.Shared.Model.Property", "Property") + .WithMany("GameProperties") + .HasForeignKey("PropertyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Game"); + + b.Navigation("Property"); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.GameTag", b => + { + b.HasOne("GameIdeas.Shared.Model.Game", "Game") + .WithMany("GameTags") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GameIdeas.Shared.Model.Tag", "Tag") + .WithMany("GameTags") + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Game"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("GameIdeas.Shared.Model.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("GameIdeas.Shared.Model.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GameIdeas.Shared.Model.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("GameIdeas.Shared.Model.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.Developer", b => + { + b.Navigation("Games"); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.Game", b => + { + b.Navigation("GamePlatforms"); + + b.Navigation("GameProperties"); + + b.Navigation("GameTags"); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.Platform", b => + { + b.Navigation("GamePlatforms"); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.Property", b => + { + b.Navigation("GameProperties"); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.Publisher", b => + { + b.Navigation("Games"); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.Tag", b => + { + b.Navigation("GameTags"); + }); + + modelBuilder.Entity("GameIdeas.Shared.Model.User", b => + { + b.Navigation("CreationGames"); + + b.Navigation("ModificationGames"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250502200035_One-Many-Categories.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250502200035_One-Many-Categories.cs new file mode 100644 index 0000000..14f15c8 --- /dev/null +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250502200035_One-Many-Categories.cs @@ -0,0 +1,152 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace GameIdeas.WebAPI.Migrations +{ + /// + public partial class OneManyCategories : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "GameDeveloper"); + + migrationBuilder.DropTable( + name: "GamePublisher"); + + migrationBuilder.AddColumn( + name: "IconUrl", + table: "Platform", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "DeveloperId", + table: "Game", + type: "integer", + nullable: true); + + migrationBuilder.AddColumn( + name: "PublisherId", + table: "Game", + type: "integer", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_Game_DeveloperId", + table: "Game", + column: "DeveloperId"); + + migrationBuilder.CreateIndex( + name: "IX_Game_PublisherId", + table: "Game", + column: "PublisherId"); + + migrationBuilder.AddForeignKey( + name: "FK_Game_Developer_DeveloperId", + table: "Game", + column: "DeveloperId", + principalTable: "Developer", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_Game_Publisher_PublisherId", + table: "Game", + column: "PublisherId", + principalTable: "Publisher", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Game_Developer_DeveloperId", + table: "Game"); + + migrationBuilder.DropForeignKey( + name: "FK_Game_Publisher_PublisherId", + table: "Game"); + + migrationBuilder.DropIndex( + name: "IX_Game_DeveloperId", + table: "Game"); + + migrationBuilder.DropIndex( + name: "IX_Game_PublisherId", + table: "Game"); + + migrationBuilder.DropColumn( + name: "IconUrl", + table: "Platform"); + + migrationBuilder.DropColumn( + name: "DeveloperId", + table: "Game"); + + migrationBuilder.DropColumn( + name: "PublisherId", + table: "Game"); + + migrationBuilder.CreateTable( + name: "GameDeveloper", + columns: table => new + { + GameId = table.Column(type: "integer", nullable: false), + DeveloperId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GameDeveloper", x => new { x.GameId, x.DeveloperId }); + table.ForeignKey( + name: "FK_GameDeveloper_Developer_DeveloperId", + column: x => x.DeveloperId, + principalTable: "Developer", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_GameDeveloper_Game_GameId", + column: x => x.GameId, + principalTable: "Game", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "GamePublisher", + columns: table => new + { + GameId = table.Column(type: "integer", nullable: false), + PublisherId = table.Column(type: "integer", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_GamePublisher", x => new { x.GameId, x.PublisherId }); + table.ForeignKey( + name: "FK_GamePublisher_Game_GameId", + column: x => x.GameId, + principalTable: "Game", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_GamePublisher_Publisher_PublisherId", + column: x => x.PublisherId, + principalTable: "Publisher", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_GameDeveloper_DeveloperId", + table: "GameDeveloper", + column: "DeveloperId"); + + migrationBuilder.CreateIndex( + name: "IX_GamePublisher_PublisherId", + table: "GamePublisher", + column: "PublisherId"); + } + } +} diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/GameIdeasContextModelSnapshot.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/GameIdeasContextModelSnapshot.cs index bab569b..951f3c5 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/GameIdeasContextModelSnapshot.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/GameIdeasContextModelSnapshot.cs @@ -63,6 +63,9 @@ namespace GameIdeas.WebAPI.Migrations b.Property("Description") .HasColumnType("text"); + b.Property("DeveloperId") + .HasColumnType("integer"); + b.Property("Interest") .HasColumnType("integer"); @@ -72,6 +75,9 @@ namespace GameIdeas.WebAPI.Migrations b.Property("ModificationUserId") .HasColumnType("text"); + b.Property("PublisherId") + .HasColumnType("integer"); + b.Property("ReleaseDate") .HasColumnType("timestamp without time zone"); @@ -86,29 +92,18 @@ namespace GameIdeas.WebAPI.Migrations b.HasIndex("CreationUserId"); + b.HasIndex("DeveloperId"); + b.HasIndex("ModificationUserId"); + b.HasIndex("PublisherId"); + b.HasIndex("Title") .IsUnique(); b.ToTable("Game", (string)null); }); - modelBuilder.Entity("GameIdeas.Shared.Model.GameDeveloper", b => - { - b.Property("GameId") - .HasColumnType("integer"); - - b.Property("DeveloperId") - .HasColumnType("integer"); - - b.HasKey("GameId", "DeveloperId"); - - b.HasIndex("DeveloperId"); - - b.ToTable("GameDeveloper", (string)null); - }); - modelBuilder.Entity("GameIdeas.Shared.Model.GamePlatform", b => { b.Property("GameId") @@ -142,21 +137,6 @@ namespace GameIdeas.WebAPI.Migrations b.ToTable("GameProperty", (string)null); }); - modelBuilder.Entity("GameIdeas.Shared.Model.GamePublisher", b => - { - b.Property("GameId") - .HasColumnType("integer"); - - b.Property("PublisherId") - .HasColumnType("integer"); - - b.HasKey("GameId", "PublisherId"); - - b.HasIndex("PublisherId"); - - b.ToTable("GamePublisher", (string)null); - }); - modelBuilder.Entity("GameIdeas.Shared.Model.GameTag", b => { b.Property("GameId") @@ -180,6 +160,9 @@ namespace GameIdeas.WebAPI.Migrations NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + b.Property("IconUrl") + .HasColumnType("text"); + b.Property("Label") .IsRequired() .HasColumnType("text"); @@ -455,32 +438,25 @@ namespace GameIdeas.WebAPI.Migrations .HasForeignKey("CreationUserId") .IsRequired(); + b.HasOne("GameIdeas.Shared.Model.Developer", "Developer") + .WithMany("Games") + .HasForeignKey("DeveloperId"); + b.HasOne("GameIdeas.Shared.Model.User", "ModificationUser") .WithMany("ModificationGames") .HasForeignKey("ModificationUserId"); + b.HasOne("GameIdeas.Shared.Model.Publisher", "Publisher") + .WithMany("Games") + .HasForeignKey("PublisherId"); + b.Navigation("CreationUser"); - b.Navigation("ModificationUser"); - }); - - modelBuilder.Entity("GameIdeas.Shared.Model.GameDeveloper", b => - { - b.HasOne("GameIdeas.Shared.Model.Developer", "Developer") - .WithMany("GameDevelopers") - .HasForeignKey("DeveloperId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("GameIdeas.Shared.Model.Game", "Game") - .WithMany("GameDevelopers") - .HasForeignKey("GameId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - b.Navigation("Developer"); - b.Navigation("Game"); + b.Navigation("ModificationUser"); + + b.Navigation("Publisher"); }); modelBuilder.Entity("GameIdeas.Shared.Model.GamePlatform", b => @@ -521,25 +497,6 @@ namespace GameIdeas.WebAPI.Migrations b.Navigation("Property"); }); - modelBuilder.Entity("GameIdeas.Shared.Model.GamePublisher", b => - { - b.HasOne("GameIdeas.Shared.Model.Game", "Game") - .WithMany("GamePublishers") - .HasForeignKey("GameId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("GameIdeas.Shared.Model.Publisher", "Publisher") - .WithMany("GamePublishers") - .HasForeignKey("PublisherId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.Navigation("Game"); - - b.Navigation("Publisher"); - }); - modelBuilder.Entity("GameIdeas.Shared.Model.GameTag", b => { b.HasOne("GameIdeas.Shared.Model.Game", "Game") @@ -612,19 +569,15 @@ namespace GameIdeas.WebAPI.Migrations modelBuilder.Entity("GameIdeas.Shared.Model.Developer", b => { - b.Navigation("GameDevelopers"); + b.Navigation("Games"); }); modelBuilder.Entity("GameIdeas.Shared.Model.Game", b => { - b.Navigation("GameDevelopers"); - b.Navigation("GamePlatforms"); b.Navigation("GameProperties"); - b.Navigation("GamePublishers"); - b.Navigation("GameTags"); }); @@ -640,7 +593,7 @@ namespace GameIdeas.WebAPI.Migrations modelBuilder.Entity("GameIdeas.Shared.Model.Publisher", b => { - b.Navigation("GamePublishers"); + b.Navigation("Games"); }); modelBuilder.Entity("GameIdeas.Shared.Model.Tag", b => diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/CategoryProfile.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/CategoryProfile.cs index 5ad0516..e4bf5da 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/CategoryProfile.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/CategoryProfile.cs @@ -21,11 +21,6 @@ public class CategoryProfile : Profile .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.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() @@ -34,11 +29,6 @@ public class CategoryProfile : Profile .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.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() diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs index e0e5d73..407d188 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Profiles/GameProfile.cs @@ -19,18 +19,20 @@ public class GameProfile : Profile .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.Platforms, o => o.MapFrom(s => s.GamePlatforms.Select(p => new PlatformDto() { Id = p.Platform.Id, Label = p.Platform.Label, Url = p.Url, IconUrl = p.Platform.IconUrl }))) .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))) + .ForMember(d => d.Publisher, o => o.MapFrom(s => s.Publisher)) + .ForPath(d => d.Publisher!.Id, o => o.MapFrom(s => s.PublisherId)) + .ForMember(d => d.Developer, o => o.MapFrom(s => s.Developer)) + .ForPath(d => d.Developer!.Id, o => o.MapFrom(s => s.DeveloperId)) .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.Platforms, o => o.MapFrom(s => s.GamePlatforms.Select(p => new PlatformDto() { Id = p.Platform.Id, Label = p.Platform.Label, Url = p.Url, IconUrl = p.Platform.IconUrl }))) .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)) diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameReadService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameReadService.cs index 6b2a3b2..aefaf0d 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameReadService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameReadService.cs @@ -18,8 +18,8 @@ public class GameReadService(GameIdeasContext context, IMapper mapper, ICategory .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) + .Include(g => g.Publisher) + .Include(g => g.Developer) .AsQueryable(); ApplyFilter(ref query, filter); @@ -43,8 +43,8 @@ public class GameReadService(GameIdeasContext context, IMapper mapper, ICategory .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) + .Include(g => g.Publisher) + .Include(g => g.Developer) .FirstOrDefaultAsync(g => g.Id == gameId); return game == null @@ -94,14 +94,14 @@ public class GameReadService(GameIdeasContext context, IMapper mapper, ICategory if (filter.PublisherIds != null) { - query = query.Where(game => filter.PublisherIds.All(pub => - game.GamePublishers.Any(gp => gp.PublisherId == pub))); + query = query.Where(game => game.Publisher != null && + filter.PublisherIds.Contains(game.Publisher!.Id)); } if (filter.DeveloperIds != null) { - query = query.Where(game => filter.DeveloperIds.All(dev => - game.GameDevelopers.Any(gd => gd.DeveloperId == dev))); + query = query.Where(game => game.Developer != null && + filter.DeveloperIds.Contains(game.Developer.Id)); } if (filter.MinInterest != null) diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameWriteService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameWriteService.cs index b1b05b1..4681b75 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameWriteService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Games/GameWriteService.cs @@ -1,5 +1,4 @@ using AutoMapper; -using GameIdeas.Shared.Constants; using GameIdeas.Shared.Dto; using GameIdeas.Shared.Exceptions; using GameIdeas.Shared.Model; @@ -14,14 +13,13 @@ public class GameWriteService(GameIdeasContext context, IMapper mapper) : IGameW { var gameToCreate = mapper.Map(gameDto); + HandleDeveloperPublisherCreation(gameToCreate); 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(); @@ -37,11 +35,10 @@ public class GameWriteService(GameIdeasContext context, IMapper mapper) : IGameW var gameToUpdate = mapper.Map(gameDto); + HandleDeveloperPublisherCreation(gameToUpdate); 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(); @@ -107,35 +104,16 @@ public class GameWriteService(GameIdeasContext context, IMapper mapper) : IGameW } } - private async Task HandlePublishersCreation(IEnumerable? categoriesToCreate, int gameId) + private void HandleDeveloperPublisherCreation(Game? game) { - if (categoriesToCreate != null) + if (game?.Publisher != null) { - var gps = mapper.Map>(categoriesToCreate); - - foreach (var gp in gps) - { - gp.GameId = gameId; - } - - context.Publishers.AttachRange(gps.Select(gp => gp.Publisher)); - await context.GamePublishers.AddRangeAsync(gps); + context.Publishers.Attach(game.Publisher); } - } - private async Task HandleDevelopersCreation(IEnumerable? categoriesToCreate, int gameId) - { - if (categoriesToCreate != null) + if (game?.Developer != 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); + context.Developers.Attach(game.Developer); } } }