Add detail page and fetch game detail

This commit is contained in:
2025-05-02 22:04:42 +02:00
parent f3c9e1d9da
commit 9fdd25172c
29 changed files with 961 additions and 189 deletions

View File

@@ -0,0 +1,59 @@
@page "/Detail/{GameId:int}"
@using GameIdeas.BlazorApp.Shared.Components.Header
@layout MainLayout
<HeaderGameIdeas>
</HeaderGameIdeas>
<div class="detail-container">
<h1 class="header-1 expand-col-2">@Game.Title</h1>
<div class="description">@Game.Description</div>
<div class="medias grd-col-2"></div>
<div class="properties-tags expand-col-2">
<h2 class="header-2"></h2>
<div class="category-container">
@foreach (var property in Game.Properties ?? [])
{
<div class="pill prop-tag">@property.Label</div>
}
</div>
<h2 class="header-2"></h2>
<div class="category-container">
@foreach (var property in Game.Tags ?? [])
{
<div class="pill prop-tag">@property.Label</div>
}
</div>
</div>
<div class="how-long-to-beat expand-col-2">
</div>
<div class="additional-informations">
<h2 class="header-2">@ResourcesKey.About</h2>
<div class="more-information">
</div>
<div class="information">
<span class="body-sm">@ResourcesKey.Platforms</span>
@foreach (var platform in Game.Platforms ?? [])
{
<a class="pill" href="@platform.Url">@platform.Label</a>
}
</div>
<div class="information">
<span class="body-sm">@ResourcesKey.Platforms</span>
<span class="pill">@Game.Developer</span>
</div>
<div class="information">
<span class="body-sm">@ResourcesKey.Platforms</span>
<span class="pill">@Game.Publisher</span>
</div>
</div>
<div class="files grd-col-2">
</div>
</div>

View File

@@ -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();
}
}

View File

@@ -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
@@ -23,14 +24,14 @@
<div class="input-game">
<div class="label">@ResourcesKey.Developers :</div>
<SelectSearch TItem="DeveloperDto" Theme="Theme" GetLabel="@(i => i.Name)" QuickAdd=true
Items="Categories?.Developers" @bind-Values=GameDto.Developers
AddItem="@(str => new DeveloperDto() { Name = str })" />
Items="Categories?.Developers" ValuesChanged="HandleDeveloperChanged"
AddItem="@(str => new DeveloperDto() { Name = str })" SelectType="SelectType.Single" />
</div>
<div class="input-game">
<div class="label">@ResourcesKey.Publishers :</div>
<SelectSearch TItem="PublisherDto" Theme="Theme" GetLabel="@(i => i.Name)" QuickAdd=true
Items="Categories?.Publishers" @bind-Values=GameDto.Publishers
AddItem="@(str => new PublisherDto() { Name = str })" />
Items="Categories?.Publishers" ValuesChanged="HandlePublisherChanged"
AddItem="@(str => new PublisherDto() { Name = str })" SelectType="SelectType.Single" />
</div>
</div>
<div class="container">

View File

@@ -75,4 +75,12 @@ public partial class GameCreationForm
StateHasChanged();
}
}
private void HandlePublisherChanged(List<PublisherDto> pubs)
{
GameDto.Publisher = pubs.FirstOrDefault();
}
private void HandleDeveloperChanged(List<DeveloperDto> devs)
{
GameDto.Developer = devs.FirstOrDefault();
}
}

View File

@@ -1,5 +1,4 @@
@page "/"
@using GameIdeas.BlazorApp.Layouts
@using GameIdeas.BlazorApp.Pages.Games.Components
@using GameIdeas.BlazorApp.Pages.Games.Filter
@using GameIdeas.BlazorApp.Shared.Components

View File

@@ -62,4 +62,18 @@ public class GameGateway(IHttpClientService httpClientService) : IGameGateway
throw new GameNotFoundException(ResourcesKey.ErrorFetchGames);
}
}
public async Task<GameDetailDto> GetGameById(int gameId)
{
try
{
var result = await httpClientService.FetchDataAsync<GameDetailDto>(Endpoints.Game.FetchById(gameId));
return result ?? throw new InvalidOperationException(ResourcesKey.ErrorFetchGames);
}
catch (Exception)
{
throw new CategoryNotFoundException(ResourcesKey.ErrorFetchGames);
}
}
}

View File

@@ -8,4 +8,5 @@ public interface IGameGateway
Task<CategoriesDto> FetchCategories();
Task<int> CreateGame(GameDetailDto game);
Task<IEnumerable<GameDto>> FetchGames(GameFilterParams filter, int currentPage);
Task<GameDetailDto> GetGameById(int gameId);
}

View File

@@ -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

View File

@@ -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

View File

@@ -7,3 +7,4 @@
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using GameIdeas.Resources
@using GameIdeas.BlazorApp.Layouts

View File

@@ -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
@@ -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.");
}

View File

@@ -15,6 +15,6 @@ public class GameDetailDto
public List<PlatformDto>? Platforms { get; set; }
public List<PropertyDto>? Properties { get; set; }
public List<TagDto>? Tags { get; set; }
public List<PublisherDto>? Publishers { get; set; }
public List<DeveloperDto>? Developers { get; set; }
public PublisherDto? Publisher { get; set; }
public DeveloperDto? Developer { get; set; }
}

View File

@@ -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; }
}

View File

@@ -4,11 +4,11 @@ public partial class Developer
{
public Developer()
{
GameDevelopers = new HashSet<GameDeveloper>();
Games = new HashSet<Game>();
}
public int Id { get; set; }
public string Name { get; set; } = null!;
public virtual ICollection<GameDeveloper> GameDevelopers { get; set; }
public virtual ICollection<Game> Games { get; set; }
}

View File

@@ -7,8 +7,6 @@ public partial class Game
GamePlatforms = new HashSet<GamePlatform>();
GameProperties = new HashSet<GameProperty>();
GameTags = new HashSet<GameTag>();
GamePublishers = new HashSet<GamePublisher>();
GameDevelopers = new HashSet<GameDeveloper>();
}
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<GamePlatform> GamePlatforms { get; set; }
public virtual ICollection<GameProperty> GameProperties { get; set; }
public virtual ICollection<GameTag> GameTags { get; set; }
public virtual ICollection<GamePublisher> GamePublishers { get; set; }
public virtual ICollection<GameDeveloper> GameDevelopers { get; set; }
}

View File

@@ -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!;
}

View File

@@ -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!;
}

View File

@@ -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<GamePlatform> GamePlatforms { get; set; }

View File

@@ -4,11 +4,11 @@ public partial class Publisher
{
public Publisher()
{
GamePublishers = new HashSet<GamePublisher>();
Games = new HashSet<Game>();
}
public int Id { get; set; }
public string Name { get; set; } = null!;
public virtual ICollection<GamePublisher> GamePublishers { get; set; }
public virtual ICollection<Game> Games { get; set; }
}

View File

@@ -19,10 +19,8 @@ public class GameIdeasContext : IdentityDbContext<User>
public virtual DbSet<Publisher> Publishers { get; set; } = null!;
public virtual DbSet<Tag> Tags { get; set; } = null!;
public virtual DbSet<Game> Games { get; set; } = null!;
public virtual DbSet<GameDeveloper> GameDevelopers { get; set; } = null!;
public virtual DbSet<GamePlatform> GamePlatforms { get; set; } = null!;
public virtual DbSet<GameProperty> GameProperties { get; set; } = null!;
public virtual DbSet<GamePublisher> GamePublishers { get; set; } = null!;
public virtual DbSet<GameTag> GameTags { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
@@ -76,6 +74,10 @@ public class GameIdeasContext : IdentityDbContext<User>
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<User>
.WithMany(p => p.ModificationGames)
.HasForeignKey(d => d.ModificationUserId)
.OnDelete(DeleteBehavior.ClientSetNull);
});
modelBuilder.Entity<GameDeveloper>(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<GamePlatform>(entity =>
@@ -145,23 +140,6 @@ public class GameIdeasContext : IdentityDbContext<User>
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<GamePublisher>(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<GameTag>(entity =>
{
entity.ToTable("GameTag");

View File

@@ -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"
}

View File

@@ -0,0 +1,616 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
NpgsqlPropertyBuilderExtensions.HasIdentityOptions(b.Property<int>("Id"), 100000L, null, null, null, null, null);
b.Property<DateTime>("CreationDate")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp without time zone")
.HasDefaultValueSql("now()");
b.Property<string>("CreationUserId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<int?>("DeveloperId")
.HasColumnType("integer");
b.Property<int>("Interest")
.HasColumnType("integer");
b.Property<DateTime?>("ModificationDate")
.HasColumnType("timestamp without time zone");
b.Property<string>("ModificationUserId")
.HasColumnType("text");
b.Property<int?>("PublisherId")
.HasColumnType("integer");
b.Property<DateTime?>("ReleaseDate")
.HasColumnType("timestamp without time zone");
b.Property<double?>("StorageSpace")
.HasColumnType("double precision");
b.Property<string>("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<int>("GameId")
.HasColumnType("integer");
b.Property<int>("PlatformId")
.HasColumnType("integer");
b.Property<string>("Url")
.HasColumnType("text");
b.HasKey("GameId", "PlatformId");
b.HasIndex("PlatformId");
b.ToTable("GamePlatform", (string)null);
});
modelBuilder.Entity("GameIdeas.Shared.Model.GameProperty", b =>
{
b.Property<int>("GameId")
.HasColumnType("integer");
b.Property<int>("PropertyId")
.HasColumnType("integer");
b.HasKey("GameId", "PropertyId");
b.HasIndex("PropertyId");
b.ToTable("GameProperty", (string)null);
});
modelBuilder.Entity("GameIdeas.Shared.Model.GameTag", b =>
{
b.Property<int>("GameId")
.HasColumnType("integer");
b.Property<int>("TagId")
.HasColumnType("integer");
b.HasKey("GameId", "TagId");
b.HasIndex("TagId");
b.ToTable("GameTag", (string)null);
});
modelBuilder.Entity("GameIdeas.Shared.Model.Platform", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("IconUrl")
.HasColumnType("text");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("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<string>("Id")
.HasColumnType("text");
b.Property<int>("AccessFailedCount")
.HasColumnType("integer");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("boolean");
b.Property<bool>("LockoutEnabled")
.HasColumnType("boolean");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("timestamp with time zone");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("PasswordHash")
.HasColumnType("text");
b.Property<string>("PhoneNumber")
.HasColumnType("text");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("boolean");
b.Property<string>("SecurityStamp")
.HasColumnType("text");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("boolean");
b.Property<string>("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<string>("Id")
.HasColumnType("text");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("text");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<string>("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<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("text");
b.Property<string>("ClaimValue")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("ProviderKey")
.HasColumnType("text");
b.Property<string>("ProviderDisplayName")
.HasColumnType("text");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("RoleId")
.HasColumnType("text");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("text");
b.Property<string>("LoginProvider")
.HasColumnType("text");
b.Property<string>("Name")
.HasColumnType("text");
b.Property<string>("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<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("GameIdeas.Shared.Model.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("GameIdeas.Shared.Model.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", 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
}
}
}

View File

@@ -0,0 +1,152 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace GameIdeas.WebAPI.Migrations
{
/// <inheritdoc />
public partial class OneManyCategories : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "GameDeveloper");
migrationBuilder.DropTable(
name: "GamePublisher");
migrationBuilder.AddColumn<string>(
name: "IconUrl",
table: "Platform",
type: "text",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "DeveloperId",
table: "Game",
type: "integer",
nullable: true);
migrationBuilder.AddColumn<int>(
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");
}
/// <inheritdoc />
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<int>(type: "integer", nullable: false),
DeveloperId = table.Column<int>(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<int>(type: "integer", nullable: false),
PublisherId = table.Column<int>(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");
}
}
}

View File

@@ -63,6 +63,9 @@ namespace GameIdeas.WebAPI.Migrations
b.Property<string>("Description")
.HasColumnType("text");
b.Property<int?>("DeveloperId")
.HasColumnType("integer");
b.Property<int>("Interest")
.HasColumnType("integer");
@@ -72,6 +75,9 @@ namespace GameIdeas.WebAPI.Migrations
b.Property<string>("ModificationUserId")
.HasColumnType("text");
b.Property<int?>("PublisherId")
.HasColumnType("integer");
b.Property<DateTime?>("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<int>("GameId")
.HasColumnType("integer");
b.Property<int>("DeveloperId")
.HasColumnType("integer");
b.HasKey("GameId", "DeveloperId");
b.HasIndex("DeveloperId");
b.ToTable("GameDeveloper", (string)null);
});
modelBuilder.Entity("GameIdeas.Shared.Model.GamePlatform", b =>
{
b.Property<int>("GameId")
@@ -142,21 +137,6 @@ namespace GameIdeas.WebAPI.Migrations
b.ToTable("GameProperty", (string)null);
});
modelBuilder.Entity("GameIdeas.Shared.Model.GamePublisher", b =>
{
b.Property<int>("GameId")
.HasColumnType("integer");
b.Property<int>("PublisherId")
.HasColumnType("integer");
b.HasKey("GameId", "PublisherId");
b.HasIndex("PublisherId");
b.ToTable("GamePublisher", (string)null);
});
modelBuilder.Entity("GameIdeas.Shared.Model.GameTag", b =>
{
b.Property<int>("GameId")
@@ -180,6 +160,9 @@ namespace GameIdeas.WebAPI.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("IconUrl")
.HasColumnType("text");
b.Property<string>("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 =>

View File

@@ -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<PublisherDto, GamePublisher>()
.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<DeveloperDto, GameDeveloper>()
.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()

View File

@@ -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))
.ForMember(d => d.Publisher!.Id, o => o.MapFrom(s => s.PublisherId))
.ForMember(d => d.Developer, o => o.MapFrom(s => s.Developer))
.ForMember(d => d.Developer!.Id, o => o.MapFrom(s => s.DeveloperId))
.ReverseMap();
CreateMap<Game, GameDto>()
.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))

View File

@@ -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)

View File

@@ -14,14 +14,15 @@ public class GameWriteService(GameIdeasContext context, IMapper mapper) : IGameW
{
var gameToCreate = mapper.Map<Game>(gameDto);
await HandlePublisherCreation(gameDto.Publisher);
await HandleDeveloperCreation(gameDto.Developer);
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 +38,12 @@ public class GameWriteService(GameIdeasContext context, IMapper mapper) : IGameW
var gameToUpdate = mapper.Map<Game>(gameDto);
await HandlePublisherCreation(gameDto.Publisher);
await HandleDeveloperCreation(gameDto.Developer);
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 +109,23 @@ public class GameWriteService(GameIdeasContext context, IMapper mapper) : IGameW
}
}
private async Task HandlePublishersCreation(IEnumerable<PublisherDto>? categoriesToCreate, int gameId)
private async Task HandlePublisherCreation(PublisherDto? categoriesToCreate)
{
if (categoriesToCreate != null)
{
var gps = mapper.Map<ICollection<GamePublisher>>(categoriesToCreate);
foreach (var gp in gps)
{
gp.GameId = gameId;
}
context.Publishers.AttachRange(gps.Select(gp => gp.Publisher));
await context.GamePublishers.AddRangeAsync(gps);
var pub = mapper.Map<Publisher>(categoriesToCreate);
context.Publishers.Attach(pub);
await context.Publishers.AddAsync(pub);
}
}
private async Task HandleDevelopersCreation(IEnumerable<DeveloperDto>? categoriesToCreate, int gameId)
private async Task HandleDeveloperCreation(DeveloperDto? categoriesToCreate)
{
if (categoriesToCreate != null)
{
var gds = mapper.Map<ICollection<GameDeveloper>>(categoriesToCreate);
foreach (var gd in gds)
{
gd.GameId = gameId;
}
context.Developers.AttachRange(gds.Select(gd => gd.Developer));
await context.GameDevelopers.AddRangeAsync(gds);
var dev = mapper.Map<Developer>(categoriesToCreate);
context.Developers.Attach(dev);
await context.Developers.AddAsync(dev);
}
}
}