From f0e934d17afaa8c4251bb536bbf32de3cab758e6 Mon Sep 17 00:00:00 2001 From: Nano Taboada <87288+nanotaboada@users.noreply.github.com> Date: Sun, 21 Dec 2025 19:26:03 -0300 Subject: [PATCH 1/2] feat: add 15 substitute players to database seed data Added GetSubstitutes() and GetSubstitutesWithId() methods to PlayerData utility, following the same pattern as Starting 11. Created SeedSubstitutes migration to seed 15 substitute players with squad numbers 1, 2, 4, 5, 6, 8, 12, 14, 15, 16, 17, 18, 21, 22, 25. Database now contains 26 total players: 11 starting (Starting11=true) and 15 substitutes (Starting11=false). All squad numbers verified conflict-free. Additional improvements: - Fixed run-migrations-and-copy-database.sh to use storage/ path - Updated copilot-instructions.md with correct database paths - Removed obsolete Data/players-sqlite3.db file Closes #332 --- .github/copilot-instructions.md | 8 +- scripts/run-migrations-and-copy-database.sh | 4 +- ...20251221220614_SeedSubstitutes.Designer.cs | 69 +++ .../20251221220614_SeedSubstitutes.cs | 62 +++ .../PlayerDbContextModelSnapshot.cs | 2 +- .../Utilities/PlayerData.cs | 413 ++++++++++++++++++ .../storage/players-sqlite3.db | Bin 28672 -> 28672 bytes 7 files changed, 551 insertions(+), 7 deletions(-) create mode 100644 src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20251221220614_SeedSubstitutes.Designer.cs create mode 100644 src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20251221220614_SeedSubstitutes.cs diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index c95da48..73c4711 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -297,7 +297,7 @@ dotnet ef database update --project src/Dotnet.Samples.AspNetCore.WebApi **Important**: The `run-migrations-and-copy-database.sh` script: - Resets the placeholder database file - Runs all migrations -- Copies the generated database from `bin/Debug/net8.0/Data/` to `Data/` +- Copies the generated database from `bin/Debug/net8.0/storage/` to `storage/` - Requires `dotnet ef` CLI tool installed globally ### Docker Operations @@ -325,9 +325,9 @@ docker compose down -v ## 🚨 Common Issues & Workarounds ### Database Path Issues -- **SQLite database location**: `storage/players-sqlite3.db` relative to binary output -- **Container storage**: `/storage/players-sqlite3.db` (mounted volume) -- **Environment variable**: `STORAGE_PATH` can override the default path in containers +- **Development**: `storage/players-sqlite3.db` (source, copied to `bin/Debug/net8.0/storage/` during build) +- **Container**: Pre-seeded database copied from image `/app/hold/` to volume `/storage/` on first run +- **Runtime**: Application uses `AppContext.BaseDirectory/storage/players-sqlite3.db` ### Validation Patterns - **FluentValidation** runs in the validator class for input format/structure diff --git a/scripts/run-migrations-and-copy-database.sh b/scripts/run-migrations-and-copy-database.sh index f86a26d..fe83197 100755 --- a/scripts/run-migrations-and-copy-database.sh +++ b/scripts/run-migrations-and-copy-database.sh @@ -5,8 +5,8 @@ DATA_FILE="players-sqlite3.db" PROJECT_ROOT_PATH="src/Dotnet.Samples.AspNetCore.WebApi" PROJECT_BASE_PATH="$PROJECT_ROOT_PATH/bin/Debug/net8.0" -SOURCE_FILE_PATH="$PROJECT_BASE_PATH/Data/$DATA_FILE" -TARGET_FILE_PATH="$PROJECT_ROOT_PATH/Data/$DATA_FILE" +SOURCE_FILE_PATH="$PROJECT_BASE_PATH/storage/$DATA_FILE" +TARGET_FILE_PATH="$PROJECT_ROOT_PATH/storage/$DATA_FILE" log() { local emoji=$1 diff --git a/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20251221220614_SeedSubstitutes.Designer.cs b/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20251221220614_SeedSubstitutes.Designer.cs new file mode 100644 index 0000000..f7f7b89 --- /dev/null +++ b/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20251221220614_SeedSubstitutes.Designer.cs @@ -0,0 +1,69 @@ +// +using System; +using Dotnet.Samples.AspNetCore.WebApi.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Dotnet.Samples.AspNetCore.WebApi.Migrations +{ + [DbContext(typeof(PlayerDbContext))] + [Migration("20251221220614_SeedSubstitutes")] + partial class SeedSubstitutes + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.11"); + + modelBuilder.Entity("Dotnet.Samples.AspNetCore.WebApi.Models.Player", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AbbrPosition") + .HasColumnType("TEXT"); + + b.Property("DateOfBirth") + .HasColumnType("TEXT"); + + b.Property("FirstName") + .HasColumnType("TEXT"); + + b.Property("LastName") + .HasColumnType("TEXT"); + + b.Property("League") + .HasColumnType("TEXT"); + + b.Property("MiddleName") + .HasColumnType("TEXT"); + + b.Property("Position") + .HasColumnType("TEXT"); + + b.Property("SquadNumber") + .HasColumnType("INTEGER"); + + b.Property("Starting11") + .HasColumnType("INTEGER"); + + b.Property("Team") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SquadNumber") + .IsUnique(); + + b.ToTable("Players"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20251221220614_SeedSubstitutes.cs b/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20251221220614_SeedSubstitutes.cs new file mode 100644 index 0000000..b4e4ffc --- /dev/null +++ b/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/20251221220614_SeedSubstitutes.cs @@ -0,0 +1,62 @@ +using Dotnet.Samples.AspNetCore.WebApi.Utilities; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Dotnet.Samples.AspNetCore.WebApi.Migrations +{ + /// + public partial class SeedSubstitutes : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + var substitutes = PlayerData.GetSubstitutesWithId(); + + foreach (var player in substitutes) + { + migrationBuilder.InsertData( + table: "Players", + columns: + [ + "Id", + "FirstName", + "MiddleName", + "LastName", + "DateOfBirth", + "SquadNumber", + "Position", + "AbbrPosition", + "Team", + "League", + "Starting11" + ], + values: new object[] + { + player.Id, + player.FirstName, + player.MiddleName, + player.LastName, + player.DateOfBirth, + player.SquadNumber, + player.Position, + player.AbbrPosition, + player.Team, + player.League, + player.Starting11 + } + ); + } + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + var substitutes = PlayerData.GetSubstitutesWithId(); + foreach (var player in substitutes) + { + migrationBuilder.DeleteData(table: "Players", keyColumn: "Id", keyValue: player.Id); + } + } + } +} diff --git a/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/PlayerDbContextModelSnapshot.cs b/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/PlayerDbContextModelSnapshot.cs index 79ae23e..d1c92e8 100644 --- a/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/PlayerDbContextModelSnapshot.cs +++ b/src/Dotnet.Samples.AspNetCore.WebApi/Migrations/PlayerDbContextModelSnapshot.cs @@ -15,7 +15,7 @@ partial class PlayerDbContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "9.0.4"); + modelBuilder.HasAnnotation("ProductVersion", "9.0.11"); modelBuilder.Entity("Dotnet.Samples.AspNetCore.WebApi.Models.Player", b => { diff --git a/src/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerData.cs b/src/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerData.cs index 968e8f4..d760765 100644 --- a/src/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerData.cs +++ b/src/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerData.cs @@ -311,6 +311,419 @@ public static List MakeStarting11WithId() ]; } + public static List GetSubstitutes() + { + return + [ + new() + { + FirstName = "Franco", + MiddleName = "Daniel", + LastName = "Armani", + DateOfBirth = new DateTime(1986, 10, 16, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 1, + Position = Position.Goalkeeper.Text, + AbbrPosition = Position.Goalkeeper.Abbr, + Team = "River Plate", + League = "Copa de la Liga", + Starting11 = false, + }, + new() + { + FirstName = "Gerónimo", + LastName = "Rulli", + DateOfBirth = new DateTime(1992, 5, 20, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 12, + Position = Position.Goalkeeper.Text, + AbbrPosition = Position.Goalkeeper.Abbr, + Team = "Ajax Amsterdam", + League = "Eredivisie", + Starting11 = false, + }, + new() + { + FirstName = "Juan", + MiddleName = "Marcos", + LastName = "Foyth", + DateOfBirth = new DateTime(1998, 1, 12, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 2, + Position = Position.RightBack.Text, + AbbrPosition = Position.RightBack.Abbr, + Team = "Villareal", + League = "La Liga", + Starting11 = false, + }, + new() + { + FirstName = "Gonzalo", + MiddleName = "Ariel", + LastName = "Montiel", + DateOfBirth = new DateTime(1997, 1, 1, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 4, + Position = Position.RightBack.Text, + AbbrPosition = Position.RightBack.Abbr, + Team = "Nottingham Forrest", + League = "Premier League", + Starting11 = false, + }, + new() + { + FirstName = "Germán", + MiddleName = "Alejo", + LastName = "Pezzella", + DateOfBirth = new DateTime(1991, 6, 27, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 6, + Position = Position.CentreBack.Text, + AbbrPosition = Position.CentreBack.Abbr, + Team = "Real Betis Balompié", + League = "La Liga", + Starting11 = false, + }, + new() + { + FirstName = "Marcos", + MiddleName = "Javier", + LastName = "Acuña", + DateOfBirth = new DateTime(1991, 10, 28, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 8, + Position = Position.LeftBack.Text, + AbbrPosition = Position.LeftBack.Abbr, + Team = "Sevilla FC", + League = "La Liga", + Starting11 = false, + }, + new() + { + FirstName = "Lisandro", + LastName = "Martínez", + DateOfBirth = new DateTime(1998, 1, 18, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 25, + Position = Position.CentreBack.Text, + AbbrPosition = Position.CentreBack.Abbr, + Team = "Manchester United", + League = "Premier League", + Starting11 = false, + }, + new() + { + FirstName = "Leandro", + MiddleName = "Daniel", + LastName = "Paredes", + DateOfBirth = new DateTime(1994, 6, 29, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 5, + Position = Position.DefensiveMidfield.Text, + AbbrPosition = Position.DefensiveMidfield.Abbr, + Team = "AS Roma", + League = "Serie A", + Starting11 = false, + }, + new() + { + FirstName = "Exequiel", + MiddleName = "Alejandro", + LastName = "Palacios", + DateOfBirth = new DateTime(1998, 10, 5, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 14, + Position = Position.CentralMidfield.Text, + AbbrPosition = Position.CentralMidfield.Abbr, + Team = "Bayer 04 Leverkusen", + League = "Bundesliga", + Starting11 = false, + }, + new() + { + FirstName = "Alejandro", + MiddleName = "Darío", + LastName = "Gómez", + DateOfBirth = new DateTime(1988, 2, 15, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 17, + Position = Position.LeftWinger.Text, + AbbrPosition = Position.LeftWinger.Abbr, + Team = "AC Monza", + League = "Serie A", + Starting11 = false, + }, + new() + { + FirstName = "Guido", + LastName = "Rodríguez", + DateOfBirth = new DateTime(1994, 4, 12, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 18, + Position = Position.DefensiveMidfield.Text, + AbbrPosition = Position.DefensiveMidfield.Abbr, + Team = "Real Betis Balompié", + League = "La Liga", + Starting11 = false, + }, + new() + { + FirstName = "Ángel", + MiddleName = "Martín", + LastName = "Correa", + DateOfBirth = new DateTime(1995, 3, 9, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 15, + Position = Position.RightWinger.Text, + AbbrPosition = Position.RightWinger.Abbr, + Team = "Atlético Madrid", + League = "La Liga", + Starting11 = false, + }, + new() + { + FirstName = "Thiago", + MiddleName = "Ezequiel", + LastName = "Almada", + DateOfBirth = new DateTime(2001, 4, 26, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 16, + Position = Position.AttackingMidfield.Text, + AbbrPosition = Position.AttackingMidfield.Abbr, + Team = "Atlanta United FC", + League = "Major League Soccer", + Starting11 = false, + }, + new() + { + FirstName = "Paulo", + MiddleName = "Exequiel", + LastName = "Dybala", + DateOfBirth = new DateTime(1993, 11, 15, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 21, + Position = Position.SecondStriker.Text, + AbbrPosition = Position.SecondStriker.Abbr, + Team = "AS Roma", + League = "Serie A", + Starting11 = false, + }, + new() + { + FirstName = "Lautaro", + MiddleName = "Javier", + LastName = "Martínez", + DateOfBirth = new DateTime(1997, 8, 22, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 22, + Position = Position.CentreForward.Text, + AbbrPosition = Position.CentreForward.Abbr, + Team = "Inter Milan", + League = "Serie A", + Starting11 = false, + } + ]; + } + + public static List GetSubstitutesWithId() + { + return + [ + new() + { + Id = Guid.Parse("b1f8a5d3-2c4e-4a6b-8d9f-1e3c5a7b9d2f"), + FirstName = "Franco", + MiddleName = "Daniel", + LastName = "Armani", + DateOfBirth = new DateTime(1986, 10, 16, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 1, + Position = Position.Goalkeeper.Text, + AbbrPosition = Position.Goalkeeper.Abbr, + Team = "River Plate", + League = "Copa de la Liga", + Starting11 = false, + }, + new() + { + Id = Guid.Parse("c2e9b6f4-3d5f-4b7c-9e0a-2f4d6b8c0e3a"), + FirstName = "Gerónimo", + LastName = "Rulli", + DateOfBirth = new DateTime(1992, 5, 20, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 12, + Position = Position.Goalkeeper.Text, + AbbrPosition = Position.Goalkeeper.Abbr, + Team = "Ajax Amsterdam", + League = "Eredivisie", + Starting11 = false, + }, + new() + { + Id = Guid.Parse("d3f0c7e5-4e6a-5c8d-0f1b-3a5e7c9d1f4b"), + FirstName = "Juan", + MiddleName = "Marcos", + LastName = "Foyth", + DateOfBirth = new DateTime(1998, 1, 12, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 2, + Position = Position.RightBack.Text, + AbbrPosition = Position.RightBack.Abbr, + Team = "Villareal", + League = "La Liga", + Starting11 = false, + }, + new() + { + Id = Guid.Parse("e4a1d8f6-5f7b-6d9e-1a2c-4b6d8e0a2c5d"), + FirstName = "Gonzalo", + MiddleName = "Ariel", + LastName = "Montiel", + DateOfBirth = new DateTime(1997, 1, 1, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 4, + Position = Position.RightBack.Text, + AbbrPosition = Position.RightBack.Abbr, + Team = "Nottingham Forrest", + League = "Premier League", + Starting11 = false, + }, + new() + { + Id = Guid.Parse("f5b2e9a7-6a8c-7e0f-2b3d-5c7e9a1b3d6e"), + FirstName = "Germán", + MiddleName = "Alejo", + LastName = "Pezzella", + DateOfBirth = new DateTime(1991, 6, 27, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 6, + Position = Position.CentreBack.Text, + AbbrPosition = Position.CentreBack.Abbr, + Team = "Real Betis Balompié", + League = "La Liga", + Starting11 = false, + }, + new() + { + Id = Guid.Parse("a6c3f0b8-7b9d-8f1a-3c4e-6d8f0b2c4e7f"), + FirstName = "Marcos", + MiddleName = "Javier", + LastName = "Acuña", + DateOfBirth = new DateTime(1991, 10, 28, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 8, + Position = Position.LeftBack.Text, + AbbrPosition = Position.LeftBack.Abbr, + Team = "Sevilla FC", + League = "La Liga", + Starting11 = false, + }, + new() + { + Id = Guid.Parse("b7d4a1c9-8c0e-9a2b-4d5f-7e9a1c3d5f8a"), + FirstName = "Lisandro", + LastName = "Martínez", + DateOfBirth = new DateTime(1998, 1, 18, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 25, + Position = Position.CentreBack.Text, + AbbrPosition = Position.CentreBack.Abbr, + Team = "Manchester United", + League = "Premier League", + Starting11 = false, + }, + new() + { + Id = Guid.Parse("c8e5b2d0-9d1f-0b3c-5e6a-8f0b2d4e6a9b"), + FirstName = "Leandro", + MiddleName = "Daniel", + LastName = "Paredes", + DateOfBirth = new DateTime(1994, 6, 29, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 5, + Position = Position.DefensiveMidfield.Text, + AbbrPosition = Position.DefensiveMidfield.Abbr, + Team = "AS Roma", + League = "Serie A", + Starting11 = false, + }, + new() + { + Id = Guid.Parse("d9f6c3e1-0e2a-1c4d-6f7b-9a1c3e5f7b0c"), + FirstName = "Exequiel", + MiddleName = "Alejandro", + LastName = "Palacios", + DateOfBirth = new DateTime(1998, 10, 5, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 14, + Position = Position.CentralMidfield.Text, + AbbrPosition = Position.CentralMidfield.Abbr, + Team = "Bayer 04 Leverkusen", + League = "Bundesliga", + Starting11 = false, + }, + new() + { + Id = Guid.Parse("e0a7d4f2-1f3b-2d5e-7a8c-0b2d4f6a8c1d"), + FirstName = "Alejandro", + MiddleName = "Darío", + LastName = "Gómez", + DateOfBirth = new DateTime(1988, 2, 15, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 17, + Position = Position.LeftWinger.Text, + AbbrPosition = Position.LeftWinger.Abbr, + Team = "AC Monza", + League = "Serie A", + Starting11 = false, + }, + new() + { + Id = Guid.Parse("f1b8e5a3-2a4c-3e6f-8b9d-1c3e5a7b9d2e"), + FirstName = "Guido", + LastName = "Rodríguez", + DateOfBirth = new DateTime(1994, 4, 12, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 18, + Position = Position.DefensiveMidfield.Text, + AbbrPosition = Position.DefensiveMidfield.Abbr, + Team = "Real Betis Balompié", + League = "La Liga", + Starting11 = false, + }, + new() + { + Id = Guid.Parse("a2c9f6b4-3b5d-4f7a-9c0e-2d4f6b8c0e3f"), + FirstName = "Ángel", + MiddleName = "Martín", + LastName = "Correa", + DateOfBirth = new DateTime(1995, 3, 9, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 15, + Position = Position.RightWinger.Text, + AbbrPosition = Position.RightWinger.Abbr, + Team = "Atlético Madrid", + League = "La Liga", + Starting11 = false, + }, + new() + { + Id = Guid.Parse("b3d0a7c5-4c6e-5a8b-0d1f-3e5a7c9d1f4a"), + FirstName = "Thiago", + MiddleName = "Ezequiel", + LastName = "Almada", + DateOfBirth = new DateTime(2001, 4, 26, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 16, + Position = Position.AttackingMidfield.Text, + AbbrPosition = Position.AttackingMidfield.Abbr, + Team = "Atlanta United FC", + League = "Major League Soccer", + Starting11 = false, + }, + new() + { + Id = Guid.Parse("c4e1b8d6-5d7f-6b9c-1e2a-4f6d8e0a2c5b"), + FirstName = "Paulo", + MiddleName = "Exequiel", + LastName = "Dybala", + DateOfBirth = new DateTime(1993, 11, 15, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 21, + Position = Position.SecondStriker.Text, + AbbrPosition = Position.SecondStriker.Abbr, + Team = "AS Roma", + League = "Serie A", + Starting11 = false, + }, + new() + { + Id = Guid.Parse("d5f2c9e7-6e8a-7c0d-2f3b-5a7e9a1b3d6c"), + FirstName = "Lautaro", + MiddleName = "Javier", + LastName = "Martínez", + DateOfBirth = new DateTime(1997, 8, 22, 0, 0, 0, DateTimeKind.Utc), + SquadNumber = 22, + Position = Position.CentreForward.Text, + AbbrPosition = Position.CentreForward.Abbr, + Team = "Inter Milan", + League = "Serie A", + Starting11 = false, + } + ]; + } + /// /// The purpose of this method is to demonstrate the capabilities of /// System.Text.Json.JsonSerializer diff --git a/src/Dotnet.Samples.AspNetCore.WebApi/storage/players-sqlite3.db b/src/Dotnet.Samples.AspNetCore.WebApi/storage/players-sqlite3.db index c3208ada7b0e48185b3811e24e3188f76d9dff3a..9d38e8c55a3a3b07291d425b677aa209e56889eb 100644 GIT binary patch delta 3014 zcma)8O>7&-6(&W=rmZDMlr6~=VQ5t$ZPj4xo!#Yf=|hVBRqc?4Kw2?!Xp9LtlGa-8 zTFa$sD@Hd0iXtdbAb^1$>Rwvp&>YgnP!DMj$$@AuMGrj`Y7a$(G$~M^O^O1^rI)^; zB+3rZlmPKKhsSyEeee6e;j4$~R}a%~f3&bnWGoX=mah@%nyOxrE3#JApOvI*l9WrO zQW^AG3jG;9Mz8G`rgqVluVgaUi?Sq_i7dlM(ujJy$ymGDd$}8XaW7`w>J{kl{z2~FxozQJf4Wn<wC9J;J+|HRm~Y>I znferJE0_>W%EQ_-P1Xv7w%Cj#Z;M6ErqL7|VTT^q-J7~3tQDbdk|u?#N)`r_jEYsp zs--zFt!`q;CN5SSnB1zuQ8jLEdUPXnc8@9>euuWHEJ*}QDwefjP8GR$5dPkyqL8`*Q(PyVIRmhn>4#|IOnrdNab> zLJ>e%DqJpQVdoW?SH^_1P`1kst{A$7C364+9EolC><$guQD{@>BV6h4cX%thjwSfr zDJ2Ruwtj*_?UrGQ^)T2?hKvv{3Tu}aE-PtVP!_W`U=M@BaykEqrP^3?D<%dJ@cH2= zStleMHn0j@YKo8tkxWtJh_L*$djN>%%s7 zFK}I!_%gUKU==X3ggF&qdd0$JM>DYQ4v>Ij)vU2&6awJfDvcQ2bhHr-%cpFZcxofb zfS`8$bMi<}0~jfAg{8vP<*W%ifVr!fvkoATO{{`b;DIMt3LJH#=5+_VCJ>f_fs}bvfrvM&Y6;T1?z?ra6SOhu1I$&5GS2ziEml#+9 zyTD)!90NF3T&Ud&yGO=kw0ix=Tn*5HgyqvFvYZB(w@@>iEZOmVpMoGyM39;S+~ zO9K%A>R^1}hn#g4M{=OmU8Pu>davpbtZXLvr4gE#1Zv zSOiv#vUBW^=8YZ=j&|UN_v20I;D`aIb};s22QL8h3GkHq$KdbeK>Mjd#pT7U1<0G4 z3kJx!rkWKCSHTb9K~n8w12h2p{(j(f!bZ>cJt!!H=&&peM;2B>>TfZ2mqjbj8+YhE z(dcl-wQ0uzg?O<4uE$;xj;MBSv9R=P)+E5^vJKA138osFiFNP;c#0GYo&)ZDt{VY+ z!Ew0OjHmn~4e|v#a>zd-@8zCR|{3`xy8i@b3ev+r&S4pS_zDH&+M) ze Date: Mon, 22 Dec 2025 09:25:22 -0300 Subject: [PATCH 2/2] chore: improve documentation and fix Team name typos - Clarify database workflow in copilot-instructions.md with detailed explanation of migration process and three use cases - Update Data/ folder description in README.md structure diagram - Add XML documentation comments for PlayerData methods - Fix team name typos - Add log function documentation in run-migrations script --- .github/copilot-instructions.md | 25 +++++++++++++++---- README.md | 2 +- scripts/run-migrations-and-copy-database.sh | 4 ++- .../Utilities/PlayerData.cs | 22 ++++++++++++---- 4 files changed, 41 insertions(+), 12 deletions(-) mode change 100755 => 100644 scripts/run-migrations-and-copy-database.sh diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 73c4711..a38a9fa 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -294,11 +294,26 @@ dotnet ef database update --project src/Dotnet.Samples.AspNetCore.WebApi ./scripts/run-migrations-and-copy-database.sh ``` -**Important**: The `run-migrations-and-copy-database.sh` script: -- Resets the placeholder database file -- Runs all migrations -- Copies the generated database from `bin/Debug/net8.0/storage/` to `storage/` -- Requires `dotnet ef` CLI tool installed globally +**Database Workflow Explained:** + +The project maintains a **pre-seeded database** at `storage/players-sqlite3.db` to support three use cases: +1. **Clone & Run** - Developers can clone the repo and run immediately without manual DB setup +2. **Recreate from Scratch** - Use the script to rebuild the database with all migrations +3. **Docker** - Container gets a copy of the pre-seeded database on first startup + +**How `run-migrations-and-copy-database.sh` works:** +1. **Creates empty file** at `storage/players-sqlite3.db` (version-controlled source location) +2. **Runs migrations** via `dotnet ef database update` + - EF Core uses `AppContext.BaseDirectory/storage/players-sqlite3.db` + - During migration, `AppContext.BaseDirectory` = `bin/Debug/net8.0/` + - EF Core creates/updates database at `bin/Debug/net8.0/storage/players-sqlite3.db` + - Applies all migrations: creates schema, seeds 26 players (11 starting + 15 substitutes) +3. **Copies migration-applied database** from `bin/Debug/net8.0/storage/` back to `storage/` + - Updates the version-controlled database with latest schema + seed data + - This file is included in git and copied to build output via `.csproj` configuration + +**Requirements:** +- `dotnet ef` CLI tool installed globally (`dotnet tool install --global dotnet-ef`) ### Docker Operations ```bash diff --git a/README.md b/README.md index fba09c6..37551d3 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ src/Dotnet.Samples.AspNetCore.WebApi/ │ ├── Player.cs │ ├── PlayerRequestModel.cs │ └── PlayerResponseModel.cs -├── Data/ # EF Core DbContext +├── Data/ # EF Core DbContext and migrations │ └── PlayerDbContext.cs ├── Mappings/ # AutoMapper profiles │ └── PlayerMappingProfile.cs diff --git a/scripts/run-migrations-and-copy-database.sh b/scripts/run-migrations-and-copy-database.sh old mode 100755 new mode 100644 index fe83197..83631d1 --- a/scripts/run-migrations-and-copy-database.sh +++ b/scripts/run-migrations-and-copy-database.sh @@ -8,6 +8,8 @@ PROJECT_BASE_PATH="$PROJECT_ROOT_PATH/bin/Debug/net8.0" SOURCE_FILE_PATH="$PROJECT_BASE_PATH/storage/$DATA_FILE" TARGET_FILE_PATH="$PROJECT_ROOT_PATH/storage/$DATA_FILE" +# log prints a formatted log message prefixed by an emoji and timestamp, followed by the log level and message. +# Parameters: emoji — emoji or symbol to prefix the entry; level — severity or level label; message — the log text. log() { local emoji=$1 local level=$2 @@ -60,4 +62,4 @@ if [ -f "$TARGET_FILE_PATH" ]; then else log "⚠️" "WARNING" "Something went wrong. The destination file was not found." exit 1 -fi +fi \ No newline at end of file diff --git a/src/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerData.cs b/src/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerData.cs index d760765..1b986e6 100644 --- a/src/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerData.cs +++ b/src/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerData.cs @@ -1,4 +1,4 @@ -using System.Text.Json; +using System.Text.Json; using Dotnet.Samples.AspNetCore.WebApi.Enums; using Dotnet.Samples.AspNetCore.WebApi.Models; @@ -153,6 +153,10 @@ public static List MakeStarting11() ]; } + /// + /// Create a predefined starting eleven of players where each player has a fixed GUID identifier and full profile data. + /// + /// A list of 11 Player instances representing the starting lineup; each entry includes a predefined Id (Guid) and populated fields such as name, date of birth, squad number, position (and abbreviation), team, league, and Starting11 set to true. public static List MakeStarting11WithId() { return @@ -311,6 +315,10 @@ public static List MakeStarting11WithId() ]; } + /// + /// Create a predefined list of 15 substitute players with full profile data but without Id values. + /// + /// A list of 15 Player instances representing substitute players; each entry includes populated fields such as name, date of birth, squad number, position (and abbreviation), team, league, and Starting11 set to false. Id values are not assigned. public static List GetSubstitutes() { return @@ -349,7 +357,7 @@ public static List GetSubstitutes() SquadNumber = 2, Position = Position.RightBack.Text, AbbrPosition = Position.RightBack.Abbr, - Team = "Villareal", + Team = "Villarreal", League = "La Liga", Starting11 = false, }, @@ -362,7 +370,7 @@ public static List GetSubstitutes() SquadNumber = 4, Position = Position.RightBack.Text, AbbrPosition = Position.RightBack.Abbr, - Team = "Nottingham Forrest", + Team = "Nottingham Forest", League = "Premier League", Starting11 = false, }, @@ -510,6 +518,10 @@ public static List GetSubstitutes() ]; } + /// + /// Create a predefined list of 15 substitute players where each player has a fixed GUID identifier and full profile data. + /// + /// A list of 15 Player instances representing substitute players; each entry includes a predefined Id (Guid) and populated fields such as name, date of birth, squad number, position (and abbreviation), team, league, and Starting11 set to false. public static List GetSubstitutesWithId() { return @@ -551,7 +563,7 @@ public static List GetSubstitutesWithId() SquadNumber = 2, Position = Position.RightBack.Text, AbbrPosition = Position.RightBack.Abbr, - Team = "Villareal", + Team = "Villarreal", League = "La Liga", Starting11 = false, }, @@ -565,7 +577,7 @@ public static List GetSubstitutesWithId() SquadNumber = 4, Position = Position.RightBack.Text, AbbrPosition = Position.RightBack.Abbr, - Team = "Nottingham Forrest", + Team = "Nottingham Forest", League = "Premier League", Starting11 = false, },