From 7d474860c180d57ef5ce33dbd1e28658a70c6d94 Mon Sep 17 00:00:00 2001 From: alt-potato Date: Fri, 24 Oct 2025 11:51:47 -0400 Subject: [PATCH 1/5] Modify frontend docker setup to use SSR --- Project.App/Project.Client/Dockerfile | 43 ++++++++++++++------------- compose.yml | 2 +- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/Project.App/Project.Client/Dockerfile b/Project.App/Project.Client/Dockerfile index b79be7f..093678d 100644 --- a/Project.App/Project.Client/Dockerfile +++ b/Project.App/Project.Client/Dockerfile @@ -13,34 +13,35 @@ COPY . . # build RUN npm run build -FROM nginx:alpine AS run +# --- run frontend from nginx server (static) --- +# FROM nginx:alpine AS run -# copy nginx config (if anything other than the default is required) -# COPY nginx.conf /etc/nginx/conf.d/default.conf +# # copy nginx config (if anything other than the default is required) +# # COPY nginx.conf /etc/nginx/conf.d/default.conf -# copy built files from build -COPY --from=build /src/out /usr/share/nginx/html +# # copy built files from build +# COPY --from=build /src/out /usr/share/nginx/html -# according to richard this does nothing :/ -EXPOSE 80 +# # according to richard this does nothing :/ +# EXPOSE 80 -# run (in the foreground)! hopefully. -CMD ["nginx", "-g", "daemon off;"] +# # run (in the foreground)! hopefully. +# CMD ["nginx", "-g", "daemon off;"] # --- run frontend from node server (if SSR is required) --- -# FROM node:24-alpine AS run +FROM node:24-alpine AS run -# # set working directory -# WORKDIR /app +# set working directory +WORKDIR /app -# # copy built files from build -# COPY --from=build /src/.next ./.next -# # copy static files -# COPY --from=build /src/public ./public +# copy built files from build +COPY --from=build /src/.next ./.next +# copy static files +COPY --from=build /src/public ./public -# # install dependencies (without dev deps) -# COPY package.json package-lock.json ./ -# RUN npm ci --omit=dev +# install dependencies (without dev deps) +COPY package.json package-lock.json ./ +RUN npm ci --omit=dev -# # run! (on node server) -# CMD ["npm", "run", "start"] +# run! (on node server) +CMD ["npm", "run", "start"] diff --git a/compose.yml b/compose.yml index 870b679..4aa94b0 100644 --- a/compose.yml +++ b/compose.yml @@ -4,7 +4,7 @@ services: build: context: ./Project.App/Project.Client ports: - - 8080:80 + - 8080:3000 depends_on: - backend From bd9510df80a4564f6537cfe80468ce8bdd15456b Mon Sep 17 00:00:00 2001 From: alt-potato Date: Fri, 24 Oct 2025 13:19:53 -0400 Subject: [PATCH 2/5] Update migrations --- ...51024170113_ImplementBlackjack.Designer.cs | 247 ++++++++++++++++++ .../20251024170113_ImplementBlackjack.cs | 145 ++++++++++ .../Migrations/AppDbContextModelSnapshot.cs | 30 ++- 3 files changed, 411 insertions(+), 11 deletions(-) create mode 100644 Project.App/Project.Api/Migrations/20251024170113_ImplementBlackjack.Designer.cs create mode 100644 Project.App/Project.Api/Migrations/20251024170113_ImplementBlackjack.cs diff --git a/Project.App/Project.Api/Migrations/20251024170113_ImplementBlackjack.Designer.cs b/Project.App/Project.Api/Migrations/20251024170113_ImplementBlackjack.Designer.cs new file mode 100644 index 0000000..31bc64a --- /dev/null +++ b/Project.App/Project.Api/Migrations/20251024170113_ImplementBlackjack.Designer.cs @@ -0,0 +1,247 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Project.Api.Data; + +#nullable disable + +namespace Project.Api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20251024170113_ImplementBlackjack")] + partial class ImplementBlackjack + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Project.Api.Models.Hand", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Bet") + .HasColumnType("bigint"); + + b.Property("HandNumber") + .HasColumnType("int"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("RoomPlayerId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RoomPlayerId"); + + b.ToTable("Hands"); + }); + + modelBuilder.Entity("Project.Api.Models.Room", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreatedAt") + .HasColumnType("datetimeoffset"); + + b.Property("DeckId") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("EndedAt") + .HasColumnType("datetimeoffset"); + + b.Property("GameConfig") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("GameMode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("GameState") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("HostId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsPublic") + .HasColumnType("bit"); + + b.Property("MaxPlayers") + .HasColumnType("int"); + + b.Property("MinPlayers") + .HasColumnType("int"); + + b.Property("Round") + .HasColumnType("int"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .IsRequired() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("rowversion"); + + b.Property("StartedAt") + .HasColumnType("datetimeoffset"); + + b.Property("State") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("HostId"); + + b.ToTable("Rooms"); + }); + + modelBuilder.Entity("Project.Api.Models.RoomPlayer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Balance") + .HasColumnType("bigint"); + + b.Property("BalanceDelta") + .HasColumnType("bigint"); + + b.Property("Role") + .HasColumnType("int"); + + b.Property("RoomId") + .HasColumnType("uniqueidentifier"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("RoomId", "UserId") + .IsUnique() + .HasDatabaseName("IX_RoomPlayer_RoomId_UserId_Unique"); + + b.ToTable("RoomPlayers"); + }); + + modelBuilder.Entity("Project.Api.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AvatarUrl") + .HasMaxLength(512) + .HasColumnType("nvarchar(512)"); + + b.Property("Balance") + .HasColumnType("float"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("nvarchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Project.Api.Models.Hand", b => + { + b.HasOne("Project.Api.Models.RoomPlayer", "RoomPlayer") + .WithMany("Hands") + .HasForeignKey("RoomPlayerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RoomPlayer"); + }); + + modelBuilder.Entity("Project.Api.Models.Room", b => + { + b.HasOne("Project.Api.Models.User", "Host") + .WithMany() + .HasForeignKey("HostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Host"); + }); + + modelBuilder.Entity("Project.Api.Models.RoomPlayer", b => + { + b.HasOne("Project.Api.Models.Room", "Room") + .WithMany("RoomPlayers") + .HasForeignKey("RoomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Project.Api.Models.User", "User") + .WithMany("RoomPlayers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Room"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Project.Api.Models.Room", b => + { + b.Navigation("RoomPlayers"); + }); + + modelBuilder.Entity("Project.Api.Models.RoomPlayer", b => + { + b.Navigation("Hands"); + }); + + modelBuilder.Entity("Project.Api.Models.User", b => + { + b.Navigation("RoomPlayers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Project.App/Project.Api/Migrations/20251024170113_ImplementBlackjack.cs b/Project.App/Project.Api/Migrations/20251024170113_ImplementBlackjack.cs new file mode 100644 index 0000000..4627c7e --- /dev/null +++ b/Project.App/Project.Api/Migrations/20251024170113_ImplementBlackjack.cs @@ -0,0 +1,145 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Project.Api.Migrations +{ + /// + public partial class ImplementBlackjack : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_RoomPlayers_RoomId", + table: "RoomPlayers"); + + migrationBuilder.AlterColumn( + name: "StartedAt", + table: "Rooms", + type: "datetimeoffset", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "EndedAt", + table: "Rooms", + type: "datetimeoffset", + nullable: true, + oldClrType: typeof(DateTime), + oldType: "datetime2", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "DeckId", + table: "Rooms", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(int), + oldType: "int"); + + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "Rooms", + type: "datetimeoffset", + nullable: false, + oldClrType: typeof(DateTime), + oldType: "datetime2"); + + migrationBuilder.AlterColumn( + name: "Bet", + table: "Hands", + type: "bigint", + nullable: false, + oldClrType: typeof(int), + oldType: "int"); + + migrationBuilder.AddColumn( + name: "HandNumber", + table: "Hands", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.CreateIndex( + name: "IX_Users_Email", + table: "Users", + column: "Email", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_RoomPlayer_RoomId_UserId_Unique", + table: "RoomPlayers", + columns: new[] { "RoomId", "UserId" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Users_Email", + table: "Users"); + + migrationBuilder.DropIndex( + name: "IX_RoomPlayer_RoomId_UserId_Unique", + table: "RoomPlayers"); + + migrationBuilder.DropColumn( + name: "HandNumber", + table: "Hands"); + + migrationBuilder.AlterColumn( + name: "StartedAt", + table: "Rooms", + type: "datetime2", + nullable: true, + oldClrType: typeof(DateTimeOffset), + oldType: "datetimeoffset", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "EndedAt", + table: "Rooms", + type: "datetime2", + nullable: true, + oldClrType: typeof(DateTimeOffset), + oldType: "datetimeoffset", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "DeckId", + table: "Rooms", + type: "int", + nullable: false, + defaultValue: 0, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "CreatedAt", + table: "Rooms", + type: "datetime2", + nullable: false, + oldClrType: typeof(DateTimeOffset), + oldType: "datetimeoffset"); + + migrationBuilder.AlterColumn( + name: "Bet", + table: "Hands", + type: "int", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint"); + + migrationBuilder.CreateIndex( + name: "IX_RoomPlayers_RoomId", + table: "RoomPlayers", + column: "RoomId"); + } + } +} diff --git a/Project.App/Project.Api/Migrations/AppDbContextModelSnapshot.cs b/Project.App/Project.Api/Migrations/AppDbContextModelSnapshot.cs index 28a7d4a..29cafb4 100644 --- a/Project.App/Project.Api/Migrations/AppDbContextModelSnapshot.cs +++ b/Project.App/Project.Api/Migrations/AppDbContextModelSnapshot.cs @@ -28,7 +28,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("Bet") + b.Property("Bet") + .HasColumnType("bigint"); + + b.Property("HandNumber") .HasColumnType("int"); b.Property("Order") @@ -50,18 +53,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType("uniqueidentifier"); - b.Property("CreatedAt") - .HasColumnType("datetime2"); + b.Property("CreatedAt") + .HasColumnType("datetimeoffset"); - b.Property("DeckId") - .HasColumnType("int"); + b.Property("DeckId") + .HasColumnType("nvarchar(max)"); b.Property("Description") .HasMaxLength(500) .HasColumnType("nvarchar(500)"); - b.Property("EndedAt") - .HasColumnType("datetime2"); + b.Property("EndedAt") + .HasColumnType("datetimeoffset"); b.Property("GameConfig") .IsRequired() @@ -100,8 +103,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAddOrUpdate() .HasColumnType("rowversion"); - b.Property("StartedAt") - .HasColumnType("datetime2"); + b.Property("StartedAt") + .HasColumnType("datetimeoffset"); b.Property("State") .IsRequired() @@ -140,10 +143,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); - b.HasIndex("RoomId"); - b.HasIndex("UserId"); + b.HasIndex("RoomId", "UserId") + .IsUnique() + .HasDatabaseName("IX_RoomPlayer_RoomId_UserId_Unique"); + b.ToTable("RoomPlayers"); }); @@ -172,6 +177,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("Email") + .IsUnique(); + b.ToTable("Users"); }); From a23f0cc0893fb1c9898a7405f9318088d06d1e79 Mon Sep 17 00:00:00 2001 From: alt-potato Date: Fri, 24 Oct 2025 13:20:03 -0400 Subject: [PATCH 3/5] Fix https issue --- Project.App/Project.Api/Program.cs | 3 +++ compose.yml | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Project.App/Project.Api/Program.cs b/Project.App/Project.Api/Program.cs index 647300f..a6ec932 100644 --- a/Project.App/Project.Api/Program.cs +++ b/Project.App/Project.Api/Program.cs @@ -29,6 +29,9 @@ public static void Main(string[] args) reloadOnChange: true ); + // env files should override adminsettings.json + builder.Configuration.AddEnvironmentVariables(); + // configure Serilog Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() diff --git a/compose.yml b/compose.yml index 4aa94b0..59be8da 100644 --- a/compose.yml +++ b/compose.yml @@ -7,17 +7,29 @@ services: - 8080:3000 depends_on: - backend - + backend: build: context: ./Project.App/Project.Api environment: # binds to ConnectionStrings:DefaultConnection - ConnectionStrings__DefaultConnection=Server=db;Database=DevitoDB;User Id=SA;Password=ThisIsTheBestPassword!!1!;TrustServerCertificate=True; + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=https://+:7069;http://+:5131 + - ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx + - ASPNETCORE_Kestrel__Certificates__Default__Password=devcertpass ports: - - 5131:5131 + - 5131:5131 # http + - 7069:7069 # https depends_on: - db + volumes: + # dev certificate directory, assumes windows machine + # first run `dotnet dev-certs https -ep %USERPROFILE%\.aspnet\https\aspnetapp.pfx -p devcertpass` + # then run `dotnet dev-certs https --trust` + # see: https://learn.microsoft.com/en-us/aspnet/core/security/docker-https?view=aspnetcore-9.0 + # - ${APPDATA}/ASP.NET/https:/root/.aspnet/https:ro + - ${USERPROFILE}/.aspnet/https:/https/ db: image: mcr.microsoft.com/mssql/server:2022-latest @@ -27,7 +39,7 @@ services: - SA_PASSWORD=ThisIsTheBestPassword!!1! - MSSQL_PID=Developer # set to developer cuz we're developing :) - MSSQL_DATABASE=DevitoDB - ports: + ports: - 1433:1433 volumes: - db_data:/var/opt/mssql From 8c67ee03065acfae8e5bae50ca16c738d812e9a1 Mon Sep 17 00:00:00 2001 From: alt-potato Date: Fri, 24 Oct 2025 13:26:30 -0400 Subject: [PATCH 4/5] Fix default Next.js port --- Project.App/Project.Api/Program.cs | 2 +- compose.yml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Project.App/Project.Api/Program.cs b/Project.App/Project.Api/Program.cs index a6ec932..619fcba 100644 --- a/Project.App/Project.Api/Program.cs +++ b/Project.App/Project.Api/Program.cs @@ -238,7 +238,7 @@ IWebHostEnvironment environment >(); // fetch the full user JSON from Google's userinfo endpoint - var request = new System.Net.Http.HttpRequestMessage( + var request = new HttpRequestMessage( System.Net.Http.HttpMethod.Get, ctx.Options.UserInformationEndpoint ); diff --git a/compose.yml b/compose.yml index 59be8da..367e97c 100644 --- a/compose.yml +++ b/compose.yml @@ -4,7 +4,7 @@ services: build: context: ./Project.App/Project.Client ports: - - 8080:3000 + - 3000:3000 depends_on: - backend @@ -28,7 +28,6 @@ services: # first run `dotnet dev-certs https -ep %USERPROFILE%\.aspnet\https\aspnetapp.pfx -p devcertpass` # then run `dotnet dev-certs https --trust` # see: https://learn.microsoft.com/en-us/aspnet/core/security/docker-https?view=aspnetcore-9.0 - # - ${APPDATA}/ASP.NET/https:/root/.aspnet/https:ro - ${USERPROFILE}/.aspnet/https:/https/ db: From 1e4de8b5db297e86932212bce19bd9fc5cdff94a Mon Sep 17 00:00:00 2001 From: alt-potato Date: Fri, 24 Oct 2025 13:29:10 -0400 Subject: [PATCH 5/5] Update dev certificate comment for clarity --- compose.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compose.yml b/compose.yml index 367e97c..f656a70 100644 --- a/compose.yml +++ b/compose.yml @@ -24,9 +24,10 @@ services: depends_on: - db volumes: - # dev certificate directory, assumes windows machine - # first run `dotnet dev-certs https -ep %USERPROFILE%\.aspnet\https\aspnetapp.pfx -p devcertpass` - # then run `dotnet dev-certs https --trust` + # dev certificate directory (assumes windows machine) + # to create the certificate: + # > `dotnet dev-certs https -ep %USERPROFILE%\.aspnet\https\aspnetapp.pfx -p devcertpass` + # > `dotnet dev-certs https --trust` # see: https://learn.microsoft.com/en-us/aspnet/core/security/docker-https?view=aspnetcore-9.0 - ${USERPROFILE}/.aspnet/https:/https/