The 2018 stack (Angular 5.2 / CLI 1.7, PHP, MySQL) had not been touched since
July 2018. Rebuilt rather than upgraded in place: the frontend was 17 major
versions behind, and of ~16,700 lines of PHP only ~150 were application logic —
the rest was four near-identical vendored copies of php-crud-api plus
class.upload.php.
Backend — ASP.NET Core 10, EF Core, SQLite
* ASP.NET Core Identity (PBKDF2) + JWT bearer auth
* Clean REST API replacing php-crud-api's filter[]/transform query syntax
* Box art uploads re-encoded to WebP via SkiaSharp
* Imports the 105 games recovered from the 2018 dump on first run
Frontend — Angular 22, zoneless, signals, Material 22
* Standalone components, lazy routes, functional guards and interceptor
* Vitest replaces Karma/Jasmine; fonts and icons bundled, no CDN calls
* No provideAnimations: @angular/animations is deprecated in v22 and
Material no longer depends on it (pinned by a test)
Docker
* Multi-stage builds for both services, non-root at runtime
* nginx serves the SPA and reverse-proxies the API, so everything is
same-origin; one volume holds the database, uploads and DP keys
Security issues in the old code, not carried across:
* Two endpoints exposed unauthenticated CRUD over every table
* The client chose whose rows to read (filter[]=userId,eq,N); ownership now
comes from the JWT subject server-side
* Login was hardcoded to a single username
* crypt() with one global salt, silently truncating passwords to 8 chars
* JWT secret was the literal string "testing", tokens never expired
* Token travelled in the query string rather than a header
* Uploads were anonymous with the path built from the client filename
* Access-Control-Allow-Origin: *
The live MySQL password committed in 2018 remains in git history and must be
rotated independently of this change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
51 lines
1.8 KiB
Docker
51 lines
1.8 KiB
Docker
# syntax=docker/dockerfile:1
|
|
|
|
# ---- build ----------------------------------------------------------------
|
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
|
WORKDIR /src
|
|
|
|
# Restore against the project file alone so the layer caches across code edits.
|
|
COPY src/LudosData.Api/LudosData.Api.csproj src/LudosData.Api/
|
|
RUN dotnet restore src/LudosData.Api/LudosData.Api.csproj
|
|
|
|
COPY src/ src/
|
|
RUN dotnet publish src/LudosData.Api/LudosData.Api.csproj \
|
|
-c Release \
|
|
-o /app/publish \
|
|
--no-restore \
|
|
/p:UseAppHost=false
|
|
|
|
# ---- runtime --------------------------------------------------------------
|
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
|
|
WORKDIR /app
|
|
|
|
# The runtime image ships neither curl nor wget, so the container healthcheck
|
|
# below has nothing to probe with unless one is added.
|
|
RUN apt-get update \
|
|
&& apt-get install -y --no-install-recommends curl \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# Run as the non-root user the base image already ships with.
|
|
ENV ASPNETCORE_HTTP_PORTS=8080 \
|
|
DOTNET_RUNNING_IN_CONTAINER=true \
|
|
ConnectionStrings__Default="Data Source=/data/ludos.db" \
|
|
Uploads__RootPath=/data/uploads
|
|
|
|
COPY --from=build /app/publish .
|
|
|
|
# Writable mount point for the SQLite file and uploaded art. Declared as a volume
|
|
# so an unmounted run still persists for the life of the container rather than
|
|
# failing to open the database.
|
|
RUN mkdir -p /data/uploads && chown -R $APP_UID:$APP_UID /data
|
|
VOLUME ["/data"]
|
|
|
|
USER $APP_UID
|
|
EXPOSE 8080
|
|
|
|
# Probes the app's own health endpoint, so "healthy" means it is actually
|
|
# serving requests rather than merely that the process exists.
|
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=15s --retries=3 \
|
|
CMD curl -fsS http://localhost:8080/health || exit 1
|
|
|
|
ENTRYPOINT ["dotnet", "LudosData.Api.dll"]
|