diff --git a/.env b/.env new file mode 100644 index 0000000..a7c723d --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +MSSQL_SA_PASSWORD=YOUR_STRONG_!Passw0rd +MSSQL_PORT=1433 +REDIS_PASSWORD=YOUR_STRONG_!Passw0rd +REDIS_PORT=6379 +MONGO_PORT=27017 \ No newline at end of file diff --git a/README.md b/README.md index c4f993c..99cfce3 100644 --- a/README.md +++ b/README.md @@ -68,10 +68,17 @@ After cloning the repository to the desired folder, run the command in the termi dotnet clean Shop.sln --nologo /tl && dotnet build Shop.sln --nologo /tl ``` +Set passwords in the `.env` file: + +```yaml +MSSQL_SA_PASSWORD=YOUR_STRONG_!Passw0rd +REDIS_PASSWORD=YOUR_STRONG_!Passw0rd +``` + Next step, run the command in the terminal: ```csharp -docker-compose up --build --abort-on-container-exit --remove-orphans +docker-compose up --build ``` Now just open the url in the browser: diff --git a/Shop.sln b/Shop.sln index 8934e76..977a952 100644 --- a/Shop.sln +++ b/Shop.sln @@ -35,6 +35,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution nuget.config = nuget.config README.md = README.md requests.http = requests.http + .env = .env EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shop.Query", "src\Shop.Query\Shop.Query.csproj", "{C2A82AAD-0348-4B29-B7E7-E36509EE068C}" diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 7b0f437..5d6129f 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -1,10 +1,5 @@ services: - shop-webapi: - environment: - - ASPNETCORE_ENVIRONMENT=Docker - - ASPNETCORE_URLS=http://+:80 - ports: - - "80" + shop-api: volumes: - ~/.aspnet/https:/root/.aspnet/https:ro - ~/.microsoft/usersecrets:/root/.microsoft/usersecrets:ro diff --git a/docker-compose.yml b/docker-compose.yml index b255803..3146722 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,80 +1,72 @@ -networks: - app-network: - driver: bridge - services: - # ASP.NET Core Web API Application - shop-webapi: - image: ${DOCKER_REGISTRY-}shop-webapi - container_name: shop-webapi - restart: always - build: - context: . - dockerfile: src/Shop.PublicApi/Dockerfile - expose: - - 80 - links: - - shop-sql-server - - shop-mongo-server - - shop-redis - networks: - - app-network - depends_on: - - shop-sql-server - - shop-mongo-server - - shop-redis - - # SQL Server + # Microsoft SQL Server + # https://hub.docker.com/r/microsoft/mssql-server/ shop-sql-server: - image: mcr.microsoft.com/azure-sql-edge:latest - cap_add: ["SYS_PTRACE"] - hostname: mssql + image: mcr.microsoft.com/mssql/server:2022-latest container_name: shop-sql-server - restart: always - expose: - - 1433 - ports: - - "1433:1433" environment: - ACCEPT_EULA=Y - MSSQL_SA_PASSWORD=${MSSQL_SA_PASSWORD} - - MSSQL_PID=Developer - - MSSQL_COLLATION=Latin1_General_CI_AI - networks: - - app-network + - MSSQL_PID=Express + - MSSQL_COLLATION=SQL_Latin1_General_CP1_CI_AS + ports: + - "${MSSQL_PORT}:${MSSQL_PORT}" volumes: - - "./data-mssql:/var/opt/mssql/data" + - sql_data:/var/opt/mssql + networks: + - shop-network - # MongoDB Server + # MongoDB + # https://hub.docker.com/_/mongo/ shop-mongo-server: image: mongo:latest container_name: shop-mongo-server - restart: always - expose: - - 27017 ports: - - "27017:27017" + - "${MONGO_PORT}:${MONGO_PORT}" networks: - - app-network + - shop-network - # Redis Cache Server - shop-redis: + # Redis Cache + # https://hub.docker.com/_/redis/ + shop-redis-server: image: redis:latest - container_name: shop-redis + container_name: shop-redis-server command: redis-server --requirepass ${REDIS_PASSWORD} - restart: always - expose: - - 6379 ports: - - "6379:6379" + - "${REDIS_PORT}:${REDIS_PORT}" networks: - - app-network + - shop-network volumes: - - "./data-mongo:/data/db" + - redis_data:/data + # Shop API Container (NET Core) + shop-api: + image: shop-api:latest + container_name: shop-api + build: + context: . + dockerfile: src/Shop.PublicApi/Dockerfile + env_file: + - ./.env + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ConnectionStrings__SqlConnection=Server=shop-sql-server,${MSSQL_PORT};Database=ShopContext;User Id=sa;Password=${MSSQL_SA_PASSWORD};TrustServerCertificate=True; + - ConnectionStrings__NoSqlConnection=mongodb://shop-mongo-server:${MONGO_PORT} + - ConnectionStrings__CacheConnection=shop-redis-server:${REDIS_PORT},password=${REDIS_PASSWORD},abortConnect=false + ports: + - "5000:80" + depends_on: + - shop-sql-server + - shop-mongo-server + - shop-redis-server + networks: + - shop-network + +# Define volumes for persistent storage volumes: - data-mssql: - external: true + sql_data: + redis_data: - data-mongo: - external: true +networks: + shop-network: + driver: bridge diff --git a/src/Shop.PublicApi/Dockerfile b/src/Shop.PublicApi/Dockerfile index 3c176da..8bf2173 100644 --- a/src/Shop.PublicApi/Dockerfile +++ b/src/Shop.PublicApi/Dockerfile @@ -1,7 +1,7 @@ FROM mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim AS base WORKDIR /app -EXPOSE 80 +EXPOSE 80 ENV ASPNETCORE_URLS=http://+:80 # Specifies whether .NET welcome and telemetry messages are displayed on the first run. @@ -10,6 +10,9 @@ ENV DOTNET_NOLOGO=1 # Specifies whether data about the .NET tools usage is collected and sent to Microsoft. ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 +# Defines the build configuration to use at build time. The default is 'Release' +ARG BUILD_CONFIGURATION=Release + FROM mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim AS build WORKDIR /src COPY ["Directory.Packages.props", "."] @@ -24,10 +27,10 @@ COPY ["src/Shop.Query/Shop.Query.csproj", "src/Shop.Query/"] RUN dotnet restore "src/Shop.PublicApi/Shop.PublicApi.csproj" COPY . . WORKDIR "/src/src/Shop.PublicApi" -RUN dotnet build "Shop.PublicApi.csproj" --configuration Release --no-restore --nologo --output /app/build +RUN dotnet build "Shop.PublicApi.csproj" --configuration $BUILD_CONFIGURATION --no-restore --nologo --output /app/build FROM build AS publish -RUN dotnet publish "Shop.PublicApi.csproj" --configuration Release --no-restore --nologo --output /app/publish +RUN dotnet publish "Shop.PublicApi.csproj" --configuration $BUILD_CONFIGURATION --no-restore --nologo --output /app/publish FROM base AS final WORKDIR /app diff --git a/src/Shop.PublicApi/Extensions/WebApplicationExtensions.cs b/src/Shop.PublicApi/Extensions/WebApplicationExtensions.cs index 8b3fdd2..656610d 100644 --- a/src/Shop.PublicApi/Extensions/WebApplicationExtensions.cs +++ b/src/Shop.PublicApi/Extensions/WebApplicationExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using AutoMapper; using Microsoft.AspNetCore.Builder; @@ -12,7 +13,7 @@ namespace Shop.PublicApi.Extensions; internal static class WebApplicationExtensions { - public static async Task RunAppAsync(this WebApplication app) + public static async Task ConfigureAndRunAppAsync(this WebApplication app) { await using var serviceScope = app.Services.CreateAsyncScope(); @@ -62,8 +63,20 @@ internal static class WebApplicationExtensions app.Logger.LogInformation("----- {DbName}: checking if there are any pending migrations...", dbName); + var allMigrations = dbContext.Database.GetMigrations(); + + app.Logger.LogInformation("----- {DbName}: {MigrationsCount} migrations were found", dbName, allMigrations.Count()); + + var appliedMigrations = await dbContext.Database.GetAppliedMigrationsAsync(); + + app.Logger.LogInformation("----- {DbName}: {AppliedMigrationsCount} applied migrations were found", dbName, appliedMigrations.Count()); + + var pendingMigrations = allMigrations.Except(appliedMigrations).ToList(); + + app.Logger.LogInformation("----- {DbName}: {PendingMigrationsCount} pending migrations were found", dbName, pendingMigrations.Count); + // Check if there are any pending migrations for the context. - if (dbContext.Database.HasPendingModelChanges()) + if (pendingMigrations.Count > 0) { app.Logger.LogInformation("----- {DbName}: creating and migrating the database...", dbName); diff --git a/src/Shop.PublicApi/Program.cs b/src/Shop.PublicApi/Program.cs index befe877..3838d0f 100644 --- a/src/Shop.PublicApi/Program.cs +++ b/src/Shop.PublicApi/Program.cs @@ -139,4 +139,4 @@ app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); -await app.RunAppAsync(); \ No newline at end of file +await app.ConfigureAndRunAppAsync(); \ No newline at end of file diff --git a/src/Shop.PublicApi/appsettings.Development.json b/src/Shop.PublicApi/appsettings.Development.json index 4203d47..189cb36 100644 --- a/src/Shop.PublicApi/appsettings.Development.json +++ b/src/Shop.PublicApi/appsettings.Development.json @@ -9,11 +9,6 @@ "Microsoft.AspNetCore.DataProtection": "Information" } }, - "ConnectionStrings": { - "SqlConnection": "", - "NoSqlConnection": "", - "CacheConnection": "" - }, "CacheOptions": { "AbsoluteExpirationInHours": 2, "SlidingExpirationInSeconds": 60 diff --git a/src/Shop.PublicApi/appsettings.Docker.json b/src/Shop.PublicApi/appsettings.Docker.json deleted file mode 100644 index 4203d47..0000000 --- a/src/Shop.PublicApi/appsettings.Docker.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.AspNetCore": "Information", - "Microsoft.Hosting.Lifetime": "Information", - "Microsoft.EntityFrameworkCore": "Information", - "Microsoft.AspNetCore.DataProtection": "Information" - } - }, - "ConnectionStrings": { - "SqlConnection": "", - "NoSqlConnection": "", - "CacheConnection": "" - }, - "CacheOptions": { - "AbsoluteExpirationInHours": 2, - "SlidingExpirationInSeconds": 60 - } -}