diff --git a/server/src/Questeloper.Application/Hero/Queries/Extensions.cs b/server/src/Questeloper.Application/Hero/Queries/Extensions.cs index a637106..882553b 100644 --- a/server/src/Questeloper.Application/Hero/Queries/Extensions.cs +++ b/server/src/Questeloper.Application/Hero/Queries/Extensions.cs @@ -1,4 +1,6 @@ -namespace Questeloper.Application.Hero.Queries; +using Questeloper.Domain.Entities; + +namespace Questeloper.Application.Hero.Queries; internal static class Extensions { @@ -9,5 +11,8 @@ public static GetHeroResponse ToResponse(this Domain.Entities.Hero hero) => hero.HeroClass.ClassName.Value, hero.Experience.ExperiencePoints, hero.HealthPoints.Points, - hero.ManaPoints.Points); + hero.ManaPoints.Points); + + public static GetHeroClassesResponse ToResponse(this HeroClass heroClass) => + new GetHeroClassesResponse(heroClass.ClassName.Value); } \ No newline at end of file diff --git a/server/src/Questeloper.Domain/Entities/Hero.cs b/server/src/Questeloper.Domain/Entities/Hero.cs index 71224b8..9fc6b2e 100644 --- a/server/src/Questeloper.Domain/Entities/Hero.cs +++ b/server/src/Questeloper.Domain/Entities/Hero.cs @@ -6,11 +6,13 @@ public class Hero : EntityBase { public HeroName HeroName { get; private set; } public Level Level { get; private set; } - public HeroClass HeroClass { get; private set; } public Experience Experience { get; private set; } public HealthPoints HealthPoints { get; private set; } public ManaPoints ManaPoints { get; private set; } + // Relations + public int HeroClassId { get; set; } + public HeroClass HeroClass { get; private set; } public ICollection Battles { get; set; } public Hero() diff --git a/server/src/Questeloper.Domain/Entities/HeroClass.cs b/server/src/Questeloper.Domain/Entities/HeroClass.cs index 02d9a83..70d3f26 100644 --- a/server/src/Questeloper.Domain/Entities/HeroClass.cs +++ b/server/src/Questeloper.Domain/Entities/HeroClass.cs @@ -5,6 +5,13 @@ namespace Questeloper.Domain.Entities; public class HeroClass : EntityBase { public HeroClassName ClassName { get; private set; } + + // Relations + public ICollection Heroes { get; set; } + + public HeroClass() + { + } public HeroClass(string className) { diff --git a/server/src/Questeloper.Domain/Entities/Question.cs b/server/src/Questeloper.Domain/Entities/Question.cs index 227e657..8d9a57f 100644 --- a/server/src/Questeloper.Domain/Entities/Question.cs +++ b/server/src/Questeloper.Domain/Entities/Question.cs @@ -5,7 +5,8 @@ namespace Questeloper.Domain.Entities; public abstract class Question : EntityBase { public QuestionContent? Content { get; set; } - + + // Relations public int EnemyId { get; set; } public Enemy Enemy { get; set; } public ICollection Categories { get; set; } diff --git a/server/src/Questeloper.Infrastructure/Persistence/DatabaseSeeders/DatabaseInitializer.cs b/server/src/Questeloper.Infrastructure/Persistence/DatabaseSeeders/DatabaseInitializer.cs index e0f62d0..687870c 100644 --- a/server/src/Questeloper.Infrastructure/Persistence/DatabaseSeeders/DatabaseInitializer.cs +++ b/server/src/Questeloper.Infrastructure/Persistence/DatabaseSeeders/DatabaseInitializer.cs @@ -1,23 +1,30 @@ -using Microsoft.EntityFrameworkCore; +using MediatR; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Questeloper.Domain.Abstractions; namespace Questeloper.Infrastructure.Persistence.DatabaseSeeders; -internal sealed class DatabaseInitializer(IServiceProvider serviceProvider, IClock clock) : IHostedService +internal sealed class DatabaseInitializer(IServiceProvider serviceProvider, IClock clock, + ILogger logger, ILoggerFactory loggerFactory) : IHostedService { public async Task StartAsync(CancellationToken cancellationToken) { + logger.LogInformation("Starting database initializer..."); + using var scope = serviceProvider.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); await dbContext.Database.MigrateAsync(cancellationToken); - var heroSeeder = new QuesteloperDataSeeder(dbContext, clock); + var heroSeeder = new QuesteloperDataSeeder(dbContext, loggerFactory.CreateLogger(), clock); await heroSeeder.SeedAsync(); await dbContext.SaveChangesAsync(cancellationToken); + + logger.LogInformation("Database initialisation was succesfull!"); } public async Task StopAsync(CancellationToken cancellationToken) => await Task.CompletedTask; diff --git a/server/src/Questeloper.Infrastructure/Persistence/DatabaseSeeders/QuesteloperDataSeeder.cs b/server/src/Questeloper.Infrastructure/Persistence/DatabaseSeeders/QuesteloperDataSeeder.cs index 9a3a72e..20d37dd 100644 --- a/server/src/Questeloper.Infrastructure/Persistence/DatabaseSeeders/QuesteloperDataSeeder.cs +++ b/server/src/Questeloper.Infrastructure/Persistence/DatabaseSeeders/QuesteloperDataSeeder.cs @@ -1,16 +1,17 @@ using Bogus; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using Questeloper.Domain.Abstractions; using Questeloper.Domain.Entities; using Questeloper.Domain.ValueObjects; namespace Questeloper.Infrastructure.Persistence.DatabaseSeeders; -internal sealed class QuesteloperDataSeeder(QuesteloperDbContext questeloperDbContext, IClock clock) +internal sealed class QuesteloperDataSeeder(QuesteloperDbContext questeloperDbContext, ILogger logger, IClock clock) { private readonly string[] HeroClasses = { "Front-End Developer", "Back-End Developer", "Tester" }; -internal async Task SeedAsync() + internal async Task SeedAsync() { if (!questeloperDbContext.HeroClasses.Any()) await GenerateHeroClasses(); if (!questeloperDbContext.Heroes.Any()) await GenerateHeroes(); @@ -20,33 +21,55 @@ internal async Task SeedAsync() if (!questeloperDbContext.Users.Any()) await GenerateUsers(); } + #region Generators + private async Task GenerateHeroClasses() { - var heroeClassesFaker = new Faker() - .RuleFor(x => x.ClassName, x => - new HeroClassName(x.PickRandom(HeroClasses))); + logger.LogInformation("Generating hero classes..."); + + var heroClassesToSeed = new List(); + + foreach (var className in HeroClasses) + { + var heroeClassesFaker = new Faker() + .RuleFor(x => x.ClassName, x => new HeroClassName(className)); + + heroClassesToSeed.Add(heroeClassesFaker); + } - var heroClassesToSeed = heroeClassesFaker.Generate(2); await questeloperDbContext.HeroClasses.AddRangeAsync(heroClassesToSeed); + await questeloperDbContext.SaveChangesAsync(); + + logger.LogInformation("Generating hero classes succesful!"); } private async Task GenerateHeroes() - { + { + logger.LogInformation("Generating heroes..."); + + var heroClassIds = await questeloperDbContext.HeroClasses.Select(x => x.Id).ToListAsync(); + var heroesFaker = new Faker() .RuleFor(x => x.Level, x => new Level(x.Random.Int(1, 100))) .RuleFor(x => x.HeroName, x => new HeroName(x.Person.UserName)) .RuleFor(x => x.Experience, x => new Experience(x.Random.Int(1, 10000))) .RuleFor(x => x.HealthPoints, x => new HealthPoints(x.Random.Int(1, 100))) .RuleFor(x => x.ManaPoints, x => new ManaPoints(x.Random.Int(1, 100))) + .RuleFor(x => x.HeroClassId, x => x.PickRandom(heroClassIds)) .RuleFor(x => x.HeroClass, x => new HeroClass(x.PickRandom(HeroClasses))); var heroesToSeed = heroesFaker.Generate(2); await questeloperDbContext.Heroes.AddRangeAsync(heroesToSeed); + await questeloperDbContext.SaveChangesAsync(); + + logger.LogInformation("Generating heroes succesful!"); } private async Task GenerateCategories() { + logger.LogInformation("Generating categories..."); + var categoriesFaker = new Faker() .RuleFor(x => x.CategoryName, x => new CategoryName(x.Lorem.Sentence(3))); @@ -54,10 +77,14 @@ private async Task GenerateCategories() var categoriesToSeed = categoriesFaker.Generate(5); await questeloperDbContext.Categories.AddRangeAsync(categoriesToSeed); await questeloperDbContext.SaveChangesAsync(); + + logger.LogInformation("Generating categories succesful!"); } private async Task GenerateQuestions() { + logger.LogInformation("Generating questions..."); + var categories = await questeloperDbContext.Categories.ToListAsync(); var enemyIds = await questeloperDbContext.Enemies.Select(x => x.Id).ToListAsync(); @@ -87,10 +114,14 @@ private async Task GenerateQuestions() await Task.WhenAll(addMultipleQuestionsTask, addTextAnswerQuestionsTask); await questeloperDbContext.SaveChangesAsync(); + + logger.LogInformation("Generating questions succesfull!"); } private async Task GenerateEnemies() { + logger.LogInformation("Generating enemies..."); + var enemiesFaker = new Faker() .RuleFor(x => x.Name, x => new EnemyName(x.Lorem.Word())) .RuleFor(x => x.HealthPoints, x => new HealthPoints(x.Random.Int(1, 100))) @@ -100,10 +131,14 @@ private async Task GenerateEnemies() await questeloperDbContext.Enemies.AddRangeAsync(enemiesToSeed); await questeloperDbContext.SaveChangesAsync(); + + logger.LogInformation("Generating enemies succesfull!"); } private async Task GenerateUsers() { + logger.LogInformation("Generating users..."); + var usersFaker = new Faker() .RuleFor(x => x.NickName, x => new NickName(x.Person.UserName)) .RuleFor(x => x.EmailAddress, x => new EmailAddress(x.Person.Email)) @@ -116,5 +151,9 @@ private async Task GenerateUsers() await questeloperDbContext.Users.AddRangeAsync(usersToSeed); await questeloperDbContext.SaveChangesAsync(); + + logger.LogInformation("Generating users succesfull!"); } + + # endregion } \ No newline at end of file diff --git a/server/src/Questeloper.Infrastructure/Persistence/EntityConfigurations/HeroConfiguration.cs b/server/src/Questeloper.Infrastructure/Persistence/EntityConfigurations/HeroConfiguration.cs index 25b1fd2..eb08546 100644 --- a/server/src/Questeloper.Infrastructure/Persistence/EntityConfigurations/HeroConfiguration.cs +++ b/server/src/Questeloper.Infrastructure/Persistence/EntityConfigurations/HeroConfiguration.cs @@ -28,12 +28,6 @@ public void Configure(EntityTypeBuilder builder) e => new Level(e)) .HasMaxLength(100); - builder - .Property(h => h.HeroClass) - .HasConversion(h => h.Value, - h => new HeroClass(h)) - .IsRequired(); - builder .Property(h => h.HealthPoints) .HasConversion(h => h.Points, @@ -45,5 +39,10 @@ public void Configure(EntityTypeBuilder builder) .HasConversion(h => h.Points, h => new ManaPoints(h)) .IsRequired(); + + builder + .HasOne(h => h.HeroClass) + .WithMany(c => c.Heroes) + .HasForeignKey(e => e.HeroClassId); } } \ No newline at end of file diff --git a/server/src/Questeloper.Infrastructure/Persistence/Migrations/20250201131656_HeroClassCorrections.Designer.cs b/server/src/Questeloper.Infrastructure/Persistence/Migrations/20250201131656_HeroClassCorrections.Designer.cs new file mode 100644 index 0000000..0b39a38 --- /dev/null +++ b/server/src/Questeloper.Infrastructure/Persistence/Migrations/20250201131656_HeroClassCorrections.Designer.cs @@ -0,0 +1,369 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Questeloper.Infrastructure.Persistence; + +#nullable disable + +namespace Questeloper.Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(QuesteloperDbContext))] + [Migration("20250201131656_HeroClassCorrections")] + partial class HeroClassCorrections + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("CategoryQuestion", b => + { + b.Property("CategoriesId") + .HasColumnType("integer"); + + b.Property("QuestionsId") + .HasColumnType("integer"); + + b.HasKey("CategoriesId", "QuestionsId"); + + b.HasIndex("QuestionsId"); + + b.ToTable("CategoryQuestion"); + }); + + modelBuilder.Entity("Questeloper.Domain.Entities.Battle", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("BattleDate") + .HasColumnType("timestamp with time zone"); + + b.Property("EnemyId") + .HasColumnType("integer"); + + b.Property("EnemyId1") + .HasColumnType("integer"); + + b.Property("HeroId") + .HasColumnType("integer"); + + b.Property("HeroId1") + .HasColumnType("integer"); + + b.Property("Result") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("EnemyId"); + + b.HasIndex("EnemyId1"); + + b.HasIndex("HeroId"); + + b.HasIndex("HeroId1"); + + b.ToTable("Battles"); + }); + + modelBuilder.Entity("Questeloper.Domain.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CategoryName") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("Questeloper.Domain.Entities.Enemy", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("HealthPoints") + .HasColumnType("integer"); + + b.Property("Level") + .HasMaxLength(100) + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("Enemies"); + }); + + modelBuilder.Entity("Questeloper.Domain.Entities.Hero", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Experience") + .HasColumnType("integer"); + + b.Property("HealthPoints") + .HasColumnType("integer"); + + b.Property("HeroClassId") + .HasColumnType("integer"); + + b.Property("HeroName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Level") + .HasMaxLength(100) + .HasColumnType("integer"); + + b.Property("ManaPoints") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("HeroClassId"); + + b.ToTable("Heroes"); + }); + + modelBuilder.Entity("Questeloper.Domain.Entities.HeroClass", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClassName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("HeroClasses"); + }); + + modelBuilder.Entity("Questeloper.Domain.Entities.Question", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("EnemyId") + .HasColumnType("integer"); + + b.Property("QuestionType") + .IsRequired() + .HasMaxLength(34) + .HasColumnType("character varying(34)"); + + b.HasKey("Id"); + + b.HasIndex("EnemyId"); + + b.ToTable("Questions"); + + b.HasDiscriminator("QuestionType").HasValue("Question"); + + b.UseTphMappingStrategy(); + }); + + modelBuilder.Entity("Questeloper.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EmailAddress") + .IsRequired() + .HasColumnType("text"); + + b.Property("FirstName") + .HasColumnType("text"); + + b.Property("HashedPassword") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastName") + .HasColumnType("text"); + + b.Property("NickName") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Questeloper.Domain.Entities.MultipleChoiceQuestion", b => + { + b.HasBaseType("Questeloper.Domain.Entities.Question"); + + b.Property("CorrectAnswer") + .IsRequired() + .HasColumnType("text"); + + b.Property("OptionA") + .IsRequired() + .HasColumnType("text"); + + b.Property("OptionB") + .IsRequired() + .HasColumnType("text"); + + b.Property("OptionC") + .IsRequired() + .HasColumnType("text"); + + b.Property("OptionD") + .IsRequired() + .HasColumnType("text"); + + b.HasDiscriminator().HasValue("MultipleChoiceQuestion"); + }); + + modelBuilder.Entity("Questeloper.Domain.Entities.TextAnswerQuestion", b => + { + b.HasBaseType("Questeloper.Domain.Entities.Question"); + + b.Property("CorrectAnswer") + .IsRequired() + .HasColumnType("text"); + + b.ToTable("Questions", t => + { + t.Property("CorrectAnswer") + .HasColumnName("TextAnswerQuestion_CorrectAnswer"); + }); + + b.HasDiscriminator().HasValue("TextAnswerQuestion"); + }); + + modelBuilder.Entity("CategoryQuestion", b => + { + b.HasOne("Questeloper.Domain.Entities.Category", null) + .WithMany() + .HasForeignKey("CategoriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Questeloper.Domain.Entities.Question", null) + .WithMany() + .HasForeignKey("QuestionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Questeloper.Domain.Entities.Battle", b => + { + b.HasOne("Questeloper.Domain.Entities.Enemy", "Enemy") + .WithMany() + .HasForeignKey("EnemyId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Questeloper.Domain.Entities.Enemy", null) + .WithMany("Battles") + .HasForeignKey("EnemyId1"); + + b.HasOne("Questeloper.Domain.Entities.Hero", "Hero") + .WithMany() + .HasForeignKey("HeroId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Questeloper.Domain.Entities.Hero", null) + .WithMany("Battles") + .HasForeignKey("HeroId1"); + + b.Navigation("Enemy"); + + b.Navigation("Hero"); + }); + + modelBuilder.Entity("Questeloper.Domain.Entities.Hero", b => + { + b.HasOne("Questeloper.Domain.Entities.HeroClass", "HeroClass") + .WithMany("Heroes") + .HasForeignKey("HeroClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HeroClass"); + }); + + modelBuilder.Entity("Questeloper.Domain.Entities.Question", b => + { + b.HasOne("Questeloper.Domain.Entities.Enemy", "Enemy") + .WithMany("Questions") + .HasForeignKey("EnemyId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.Navigation("Enemy"); + }); + + modelBuilder.Entity("Questeloper.Domain.Entities.Enemy", b => + { + b.Navigation("Battles"); + + b.Navigation("Questions"); + }); + + modelBuilder.Entity("Questeloper.Domain.Entities.Hero", b => + { + b.Navigation("Battles"); + }); + + modelBuilder.Entity("Questeloper.Domain.Entities.HeroClass", b => + { + b.Navigation("Heroes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/server/src/Questeloper.Infrastructure/Persistence/Migrations/20250201131656_HeroClassCorrections.cs b/server/src/Questeloper.Infrastructure/Persistence/Migrations/20250201131656_HeroClassCorrections.cs new file mode 100644 index 0000000..a7aaf5f --- /dev/null +++ b/server/src/Questeloper.Infrastructure/Persistence/Migrations/20250201131656_HeroClassCorrections.cs @@ -0,0 +1,78 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Questeloper.Infrastructure.Persistence.Migrations +{ + /// + public partial class HeroClassCorrections : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "HeroClass", + table: "Heroes"); + + migrationBuilder.AddColumn( + name: "HeroClassId", + table: "Heroes", + type: "integer", + nullable: false, + defaultValue: 0); + + migrationBuilder.CreateTable( + name: "HeroClasses", + columns: table => new + { + Id = table.Column(type: "integer", nullable: false) + .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), + ClassName = table.Column(type: "character varying(255)", maxLength: 255, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_HeroClasses", x => x.Id); + }); + + migrationBuilder.CreateIndex( + name: "IX_Heroes_HeroClassId", + table: "Heroes", + column: "HeroClassId"); + + migrationBuilder.AddForeignKey( + name: "FK_Heroes_HeroClasses_HeroClassId", + table: "Heroes", + column: "HeroClassId", + principalTable: "HeroClasses", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Heroes_HeroClasses_HeroClassId", + table: "Heroes"); + + migrationBuilder.DropTable( + name: "HeroClasses"); + + migrationBuilder.DropIndex( + name: "IX_Heroes_HeroClassId", + table: "Heroes"); + + migrationBuilder.DropColumn( + name: "HeroClassId", + table: "Heroes"); + + migrationBuilder.AddColumn( + name: "HeroClass", + table: "Heroes", + type: "text", + nullable: false, + defaultValue: ""); + } + } +} diff --git a/server/src/Questeloper.Infrastructure/Persistence/Migrations/QuesteloperDbContextModelSnapshot.cs b/server/src/Questeloper.Infrastructure/Persistence/Migrations/QuesteloperDbContextModelSnapshot.cs index ac81b50..ac10658 100644 --- a/server/src/Questeloper.Infrastructure/Persistence/Migrations/QuesteloperDbContextModelSnapshot.cs +++ b/server/src/Questeloper.Infrastructure/Persistence/Migrations/QuesteloperDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.2") + .HasAnnotation("ProductVersion", "8.0.10") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -132,9 +132,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("HealthPoints") .HasColumnType("integer"); - b.Property("HeroClassConfiguration") - .IsRequired() - .HasColumnType("text"); + b.Property("HeroClassId") + .HasColumnType("integer"); b.Property("HeroName") .IsRequired() @@ -150,9 +149,29 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("HeroClassId"); + b.ToTable("Heroes"); }); + modelBuilder.Entity("Questeloper.Domain.Entities.HeroClass", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClassName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("Id"); + + b.ToTable("HeroClasses"); + }); + modelBuilder.Entity("Questeloper.Domain.Entities.Question", b => { b.Property("Id") @@ -303,6 +322,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("Hero"); }); + modelBuilder.Entity("Questeloper.Domain.Entities.Hero", b => + { + b.HasOne("Questeloper.Domain.Entities.HeroClass", "HeroClass") + .WithMany("Heroes") + .HasForeignKey("HeroClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HeroClass"); + }); + modelBuilder.Entity("Questeloper.Domain.Entities.Question", b => { b.HasOne("Questeloper.Domain.Entities.Enemy", "Enemy") @@ -325,6 +355,11 @@ protected override void BuildModel(ModelBuilder modelBuilder) { b.Navigation("Battles"); }); + + modelBuilder.Entity("Questeloper.Domain.Entities.HeroClass", b => + { + b.Navigation("Heroes"); + }); #pragma warning restore 612, 618 } } diff --git a/server/src/Questeloper.Infrastructure/Persistence/Repositories/HeroRepository.cs b/server/src/Questeloper.Infrastructure/Persistence/Repositories/HeroRepository.cs index 0602fb0..31df702 100644 --- a/server/src/Questeloper.Infrastructure/Persistence/Repositories/HeroRepository.cs +++ b/server/src/Questeloper.Infrastructure/Persistence/Repositories/HeroRepository.cs @@ -10,7 +10,10 @@ internal sealed class HeroRepository(QuesteloperDbContext questeloperDbContext) await questeloperDbContext.Heroes.FirstOrDefaultAsync(x => x.Id == id); public async Task> GetHeroesAsync() => - await questeloperDbContext.Heroes.ToListAsync(); + await questeloperDbContext.Heroes + .Include(h => h.HeroClass) + .AsNoTracking() + .ToListAsync(); public async Task CreateHeroAsync(Hero hero) => await questeloperDbContext.Heroes.AddAsync(hero); diff --git a/server/tests/Questeloper.Tests.Unit/Hero/Commands/CreateHeroCommandHandlerTests.cs b/server/tests/Questeloper.Tests.Unit/Hero/Commands/CreateHeroCommandHandlerTests.cs index 9edaca1..17a90c1 100644 --- a/server/tests/Questeloper.Tests.Unit/Hero/Commands/CreateHeroCommandHandlerTests.cs +++ b/server/tests/Questeloper.Tests.Unit/Hero/Commands/CreateHeroCommandHandlerTests.cs @@ -36,7 +36,7 @@ public async Task CreateHeroCommandHandler_ForValidCommand_ShouldCreateHeroAndIn await _heroRepository .Received(1) .CreateHeroAsync(Arg.Is(h => - h.HeroName == command.Name && h.HeroClass.Value == command.HeroClass)); + h.HeroName == command.Name && h.HeroName.Value == command.HeroClass)); await _heroRepository.Received(1).CompleteAsync(); result.Should().Be(expectedHeroId); diff --git a/server/tests/Questeloper.Tests.Unit/Hero/Queries/GetHeroQueryHandlerTests.cs b/server/tests/Questeloper.Tests.Unit/Hero/Queries/GetHeroQueryHandlerTests.cs index 91a1066..24c684f 100644 --- a/server/tests/Questeloper.Tests.Unit/Hero/Queries/GetHeroQueryHandlerTests.cs +++ b/server/tests/Questeloper.Tests.Unit/Hero/Queries/GetHeroQueryHandlerTests.cs @@ -36,7 +36,7 @@ public async Task GetHeroQueryHandler_ForValidQuery_ShouldReturnCorrectGetHeroRe //Assert result.Should().NotBeNull(); result.Should().BeOfType(); - result.HeroClass.Should().Be(hero.HeroClass.Value); + result.HeroClass.Should().Be(hero.HeroName.Value); result.Name.Should().Be(hero.HeroName.Value); result.Level.Should().Be(hero.Level.LevelValue); result.Experience.Should().Be(hero.Experience.ExperiencePoints); diff --git a/tools/scripts/AddMigration.ps1 b/tools/scripts/AddMigration.ps1 index b7a843c..fc8bec0 100644 --- a/tools/scripts/AddMigration.ps1 +++ b/tools/scripts/AddMigration.ps1 @@ -10,6 +10,10 @@ if ([string]::IsNullOrWhiteSpace($migrationName)) { dotnet ef migrations add $migrationName -o ".\Persistence\Migrations" --startup-project "../Questeloper.Api" Write-Host "Migration '$migrationName' added successfully." + + Write-Host "Updating database..." + dotnet ef database update + Write-Host "Database updated!" } catch { Write-Host "An error occurred while adding the migration: $_" } diff --git a/tools/scripts/DatabaseUpdate.ps1 b/tools/scripts/DatabaseUpdate.ps1 new file mode 100644 index 0000000..ce47d41 --- /dev/null +++ b/tools/scripts/DatabaseUpdate.ps1 @@ -0,0 +1,12 @@ +try { + # Set the location to the Infrastructure directory + Set-Location -Path "../../server/src/Questeloper.Infrastructure" + + Write-Host "Updating database..." + + dotnet ef database update --startup-project "../Questeloper.Api" + + Write-Host "Database updated!" +} catch { + Write-Host "An error occurred while updating database: $_" +}