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
+
+
+
+
+
+
+
+
+
+
+
@Game.Description
+
+
+
+
+
+
+
+
+ @foreach (var property in Game.Properties ?? [])
+ {
+
@property.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 @@
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);
}
}
}