diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..b0b20fb
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,18 @@
+# ======================
+# Database Configuration
+# ======================
+
+# SQL Server Connection Components
+# These are used to build connection strings dynamically
+DB_SERVER=sqlserver,1433
+DB_NAME=Biergarten
+DB_USER=sa
+DB_PASSWORD=YourStrong!Passw0rd
+
+# ======================
+# JWT Configuration
+# ======================
+
+# JWT Secret for signing tokens (generate using: openssl rand -base64 32)
+JWT_SECRET=128490218jfklsdajfdsa90f8sd0fid0safasr31jl2k1j4AFSDR
+
diff --git a/.gitignore b/.gitignore
index b06b40a..ecede2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -486,4 +486,7 @@ FodyWeavers.xsd
database
-.env.*
\ No newline at end of file
+.env
+.env.dev
+.env.test
+.env.prod
diff --git a/docker-compose.db.yaml b/docker-compose.db.yaml
index 5db74df..4c359b3 100644
--- a/docker-compose.db.yaml
+++ b/docker-compose.db.yaml
@@ -1,25 +1,27 @@
services:
- sqlserver:
+ sqlserver:
+ env_file: ".env.dev"
image: mcr.microsoft.com/mssql/server:2022-latest
platform: linux/amd64
container_name: dev-env-sqlserver
environment:
ACCEPT_EULA: "Y"
- SA_PASSWORD: "${SA_PASSWORD}"
+ SA_PASSWORD: "${DB_PASSWORD}"
MSSQL_PID: "Express"
ports:
- "1433:1433"
volumes:
- sqlserverdata-dev:/var/opt/mssql
healthcheck:
- test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P '${SA_PASSWORD}' -C -Q 'SELECT 1' || exit 1"]
+ test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P '${DB_PASSWORD}' -C -Q 'SELECT 1' || exit 1"]
interval: 10s
timeout: 5s
retries: 12
start_period: 30s
networks:
- devnet
- database.migrations:
+ database.migrations:
+ env_file: ".env.dev"
image: database.migrations
container_name: dev-env-database-migrations
depends_on:
@@ -33,14 +35,17 @@ services:
APP_UID: 1000
environment:
DOTNET_RUNNING_IN_CONTAINER: "true"
- DB_CONNECTION_STRING: "${DB_CONNECTION_STRING}"
- MASTER_DB_CONNECTION_STRING: "${MASTER_DB_CONNECTION_STRING}"
+ DB_SERVER: "${DB_SERVER}"
+ DB_NAME: "${DB_NAME}"
+ DB_USER: "${DB_USER}"
+ DB_PASSWORD: "${DB_PASSWORD}"
CLEAR_DATABASE: "true"
restart: "no"
networks:
- devnet
- database.seed:
+database.seed:
+ env_file: ".env.dev"
image: database.seed
container_name: dev-env-database-seed
depends_on:
@@ -54,11 +59,13 @@ services:
APP_UID: 1000
environment:
DOTNET_RUNNING_IN_CONTAINER: "true"
- DB_CONNECTION_STRING: "${DB_CONNECTION_STRING}"
+ DB_SERVER: "${DB_SERVER}"
+ DB_NAME: "${DB_NAME}"
+ DB_USER: "${DB_USER}"
+ DB_PASSWORD: "${DB_PASSWORD}"
restart: "no"
networks:
- devnet
-
volumes:
sqlserverdata-dev:
driver: local
diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml
index 26eedfc..f764ee0 100644
--- a/docker-compose.dev.yaml
+++ b/docker-compose.dev.yaml
@@ -1,18 +1,19 @@
services:
sqlserver:
+ env_file: ".env.dev"
image: mcr.microsoft.com/mssql/server:2022-latest
platform: linux/amd64
container_name: dev-env-sqlserver
environment:
ACCEPT_EULA: "Y"
- SA_PASSWORD: "${SA_PASSWORD}"
+ SA_PASSWORD: "${DB_PASSWORD}"
MSSQL_PID: "Express"
ports:
- "1433:1433"
volumes:
- sqlserverdata-dev:/var/opt/mssql
healthcheck:
- test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P '${SA_PASSWORD}' -C -Q 'SELECT 1' || exit 1"]
+ test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P '${DB_PASSWORD}' -C -Q 'SELECT 1' || exit 1"]
interval: 10s
timeout: 5s
retries: 12
@@ -20,6 +21,7 @@ services:
networks:
- devnet
database.migrations:
+ env_file: ".env.dev"
image: database.migrations
container_name: dev-env-database-migrations
depends_on:
@@ -33,14 +35,17 @@ services:
APP_UID: 1000
environment:
DOTNET_RUNNING_IN_CONTAINER: "true"
- DB_CONNECTION_STRING: "${DB_CONNECTION_STRING}"
- MASTER_DB_CONNECTION_STRING: "${MASTER_DB_CONNECTION_STRING}"
+ DB_SERVER: "${DB_SERVER}"
+ DB_NAME: "${DB_NAME}"
+ DB_USER: "${DB_USER}"
+ DB_PASSWORD: "${DB_PASSWORD}"
CLEAR_DATABASE: "true"
restart: "no"
networks:
- devnet
database.seed:
+ env_file: ".env.dev"
image: database.seed
container_name: dev-env-database-seed
depends_on:
@@ -54,12 +59,16 @@ services:
APP_UID: 1000
environment:
DOTNET_RUNNING_IN_CONTAINER: "true"
- DB_CONNECTION_STRING: "${DB_CONNECTION_STRING}"
+ DB_SERVER: "${DB_SERVER}"
+ DB_NAME: "${DB_NAME}"
+ DB_USER: "${DB_USER}"
+ DB_PASSWORD: "${DB_PASSWORD}"
restart: "no"
networks:
- devnet
api.core:
+ env_file: ".env.dev"
image: api.core
container_name: dev-env-api-core
depends_on:
@@ -78,7 +87,10 @@ services:
ASPNETCORE_ENVIRONMENT: "Development"
ASPNETCORE_URLS: "http://0.0.0.0:8080"
DOTNET_RUNNING_IN_CONTAINER: "true"
- DB_CONNECTION_STRING: "${DB_CONNECTION_STRING}"
+ DB_SERVER: "${DB_SERVER}"
+ DB_NAME: "${DB_NAME}"
+ DB_USER: "${DB_USER}"
+ DB_PASSWORD: "${DB_PASSWORD}"
JWT_SECRET: "${JWT_SECRET}"
restart: unless-stopped
networks:
diff --git a/docker-compose.prod.yaml b/docker-compose.prod.yaml
index eccbf98..d41d268 100644
--- a/docker-compose.prod.yaml
+++ b/docker-compose.prod.yaml
@@ -1,16 +1,17 @@
services:
sqlserver:
+ env_file: ".env.prod"
image: mcr.microsoft.com/mssql/server:2022-latest
platform: linux/amd64
container_name: prod-env-sqlserver
environment:
ACCEPT_EULA: "Y"
- SA_PASSWORD: "${SA_PASSWORD}"
+ SA_PASSWORD: "${DB_PASSWORD}"
MSSQL_PID: "Express"
volumes:
- sqlserverdata-prod:/var/opt/mssql
healthcheck:
- test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P '${SA_PASSWORD}' -C -Q 'SELECT 1' || exit 1"]
+ test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P '${DB_PASSWORD}' -C -Q 'SELECT 1' || exit 1"]
interval: 10s
timeout: 5s
retries: 12
@@ -19,6 +20,7 @@ services:
- prodnet
database.migrations:
+ env_file: ".env.prod"
image: database.migrations
container_name: prod-env-database-migrations
depends_on:
@@ -32,13 +34,16 @@ services:
APP_UID: 1000
environment:
DOTNET_RUNNING_IN_CONTAINER: "true"
- DB_CONNECTION_STRING: "${DB_CONNECTION_STRING}"
- MASTER_DB_CONNECTION_STRING: "${MASTER_DB_CONNECTION_STRING}"
+ DB_SERVER: "${DB_SERVER}"
+ DB_NAME: "${DB_NAME}"
+ DB_USER: "${DB_USER}"
+ DB_PASSWORD: "${DB_PASSWORD}"
restart: "no"
networks:
- prodnet
api.core:
+ env_file: ".env.prod"
image: api.core
container_name: prod-env-api-core
depends_on:
@@ -57,8 +62,10 @@ services:
ASPNETCORE_ENVIRONMENT: "Production"
ASPNETCORE_URLS: "http://0.0.0.0:8080"
DOTNET_RUNNING_IN_CONTAINER: "true"
- MASTER_DB_CONNECTION_STRING: "${MASTER_DB_CONNECTION_STRING}"
- DB_CONNECTION_STRING: "${DB_CONNECTION_STRING}"
+ DB_SERVER: "${DB_SERVER}"
+ DB_NAME: "${DB_NAME}"
+ DB_USER: "${DB_USER}"
+ DB_PASSWORD: "${DB_PASSWORD}"
JWT_SECRET: "${JWT_SECRET}"
restart: unless-stopped
networks:
diff --git a/docker-compose.test.yaml b/docker-compose.test.yaml
index 08e93b7..17b2cf2 100644
--- a/docker-compose.test.yaml
+++ b/docker-compose.test.yaml
@@ -1,17 +1,18 @@
services:
sqlserver:
+ env_file: ".env.test"
image: mcr.microsoft.com/mssql/server:2022-latest
platform: linux/amd64
container_name: test-env-sqlserver
environment:
ACCEPT_EULA: "Y"
- SA_PASSWORD: "${SA_PASSWORD}"
+ SA_PASSWORD: "${DB_PASSWORD}"
MSSQL_PID: "Express"
DOTNET_RUNNING_IN_CONTAINER: "true"
volumes:
- sqlserverdata-test:/var/opt/mssql
healthcheck:
- test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P '${SA_PASSWORD}' -C -Q 'SELECT 1' || exit 1"]
+ test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P '${DB_PASSWORD}' -C -Q 'SELECT 1' || exit 1"]
interval: 10s
timeout: 5s
retries: 12
@@ -20,6 +21,7 @@ services:
- testnet
database.migrations:
+ env_file: ".env.test"
image: database.migrations
container_name: test-env-database-migrations
depends_on:
@@ -33,14 +35,17 @@ services:
APP_UID: 1000
environment:
DOTNET_RUNNING_IN_CONTAINER: "true"
- DB_CONNECTION_STRING: "${TEST_DB_CONNECTION_STRING}"
- MASTER_DB_CONNECTION_STRING: "${TEST_MASTER_DB_CONNECTION_STRING}"
+ DB_SERVER: "${DB_SERVER}"
+ DB_NAME: "${DB_NAME}"
+ DB_USER: "${DB_USER}"
+ DB_PASSWORD: "${DB_PASSWORD}"
CLEAR_DATABASE: "true"
restart: "no"
networks:
- testnet
database.seed:
+ env_file: ".env.test"
image: database.seed
container_name: test-env-database-seed
depends_on:
@@ -54,12 +59,16 @@ services:
APP_UID: 1000
environment:
DOTNET_RUNNING_IN_CONTAINER: "true"
- DB_CONNECTION_STRING: "${TEST_DB_CONNECTION_STRING}"
+ DB_SERVER: "${DB_SERVER}"
+ DB_NAME: "${DB_NAME}"
+ DB_USER: "${DB_USER}"
+ DB_PASSWORD: "${DB_PASSWORD}"
restart: "no"
networks:
- testnet
api.specs:
+ env_file: ".env.test"
image: api.specs
container_name: test-env-api-specs
depends_on:
@@ -72,7 +81,10 @@ services:
BUILD_CONFIGURATION: Release
environment:
DOTNET_RUNNING_IN_CONTAINER: "true"
- DB_CONNECTION_STRING: "${TEST_DB_CONNECTION_STRING}"
+ DB_SERVER: "${DB_SERVER}"
+ DB_NAME: "${DB_NAME}"
+ DB_USER: "${DB_USER}"
+ DB_PASSWORD: "${DB_PASSWORD}"
JWT_SECRET: "${JWT_SECRET}"
volumes:
- ./test-results:/app/test-results
@@ -81,6 +93,7 @@ services:
- testnet
repository.tests:
+ env_file: ".env.test"
image: repository.tests
container_name: test-env-repository-tests
depends_on:
@@ -93,8 +106,6 @@ services:
BUILD_CONFIGURATION: Release
environment:
DOTNET_RUNNING_IN_CONTAINER: "true"
- DB_CONNECTION_STRING: "${TEST_DB_CONNECTION_STRING}"
- JWT_SECRET: "${JWT_SECRET}"
volumes:
- ./test-results:/app/test-results
restart: "no"
diff --git a/src/Core/API/API.Specs/Dockerfile b/src/Core/API/API.Specs/Dockerfile
index 7929db8..97139c9 100644
--- a/src/Core/API/API.Specs/Dockerfile
+++ b/src/Core/API/API.Specs/Dockerfile
@@ -16,4 +16,4 @@ WORKDIR /src
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
RUN mkdir -p /app/test-results
WORKDIR /src/API/API.Specs
-ENTRYPOINT ["dotnet", "test", "API.Specs.csproj", "-c", "Release", "--no-build", "--no-restore", "--logger", "trx;LogFileName=/app/test-results/test-results.trx"]
+ENTRYPOINT ["dotnet", "test", "API.Specs.csproj", "-c", "Release", "--logger", "trx;LogFileName=/app/test-results/test-results.trx"]
diff --git a/src/Core/API/API.Specs/TestApiFactory.cs b/src/Core/API/API.Specs/TestApiFactory.cs
index 36e7820..15da2c8 100644
--- a/src/Core/API/API.Specs/TestApiFactory.cs
+++ b/src/Core/API/API.Specs/TestApiFactory.cs
@@ -10,11 +10,6 @@ namespace API.Specs
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Testing");
-
- builder.ConfigureAppConfiguration((context, configBuilder) =>
- {
- var connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING");
- });
}
}
}
diff --git a/src/Core/Database/Database.Migrations/Program.cs b/src/Core/Database/Database.Migrations/Program.cs
index 86281cb..af80640 100644
--- a/src/Core/Database/Database.Migrations/Program.cs
+++ b/src/Core/Database/Database.Migrations/Program.cs
@@ -7,8 +7,39 @@ namespace Database.Migrations;
public static class Program
{
- private static readonly string? connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING");
- private static readonly string? masterConnectionString = Environment.GetEnvironmentVariable("MASTER_DB_CONNECTION_STRING");
+ private static string BuildConnectionString(string? databaseName = null)
+ {
+ var server = Environment.GetEnvironmentVariable("DB_SERVER")
+ ?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
+
+ var dbName = databaseName
+ ?? Environment.GetEnvironmentVariable("DB_NAME")
+ ?? throw new InvalidOperationException("DB_NAME environment variable is not set");
+
+ var user = Environment.GetEnvironmentVariable("DB_USER")
+ ?? throw new InvalidOperationException("DB_USER environment variable is not set");
+
+ var password = Environment.GetEnvironmentVariable("DB_PASSWORD")
+ ?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
+
+ var trustServerCertificate = Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE")
+ ?? "True";
+
+ var builder = new SqlConnectionStringBuilder
+ {
+ DataSource = server,
+ InitialCatalog = dbName,
+ UserID = user,
+ Password = password,
+ TrustServerCertificate = bool.Parse(trustServerCertificate),
+ Encrypt = true
+ };
+
+ return builder.ConnectionString;
+ }
+
+ private static readonly string connectionString = BuildConnectionString();
+ private static readonly string masterConnectionString = BuildConnectionString("master");
private static bool DeployMigrations()
{
diff --git a/src/Core/Database/Database.Seed/Program.cs b/src/Core/Database/Database.Seed/Program.cs
index 0fb1138..e7fff96 100644
--- a/src/Core/Database/Database.Seed/Program.cs
+++ b/src/Core/Database/Database.Seed/Program.cs
@@ -3,9 +3,39 @@ using Microsoft.Data.SqlClient;
using DbUp;
using System.Reflection;
+string BuildConnectionString()
+{
+ var server = Environment.GetEnvironmentVariable("DB_SERVER")
+ ?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
+
+ var dbName = Environment.GetEnvironmentVariable("DB_NAME")
+ ?? throw new InvalidOperationException("DB_NAME environment variable is not set");
+
+ var user = Environment.GetEnvironmentVariable("DB_USER")
+ ?? throw new InvalidOperationException("DB_USER environment variable is not set");
+
+ var password = Environment.GetEnvironmentVariable("DB_PASSWORD")
+ ?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
+
+ var trustServerCertificate = Environment.GetEnvironmentVariable("DB_TRUST_SERVER_CERTIFICATE")
+ ?? "True";
+
+ var builder = new SqlConnectionStringBuilder
+ {
+ DataSource = server,
+ InitialCatalog = dbName,
+ UserID = user,
+ Password = password,
+ TrustServerCertificate = bool.Parse(trustServerCertificate),
+ Encrypt = true
+ };
+
+ return builder.ConnectionString;
+}
+
try
{
- var connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING");
+ var connectionString = BuildConnectionString();
Console.WriteLine("Attempting to connect to database...");
diff --git a/src/Core/Repository/Repository.Core/Sql/DefaultSqlConnectionFactory.cs b/src/Core/Repository/Repository.Core/Sql/DefaultSqlConnectionFactory.cs
index eb8abad..b38f25d 100644
--- a/src/Core/Repository/Repository.Core/Sql/DefaultSqlConnectionFactory.cs
+++ b/src/Core/Repository/Repository.Core/Sql/DefaultSqlConnectionFactory.cs
@@ -7,11 +7,36 @@ namespace DataAccessLayer.Sql
{
public class DefaultSqlConnectionFactory(IConfiguration configuration) : ISqlConnectionFactory
{
- private readonly string _connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING")
- ?? configuration.GetConnectionString("Default")
- ?? throw new InvalidOperationException(
- "Database connection string not configured. Set DB_CONNECTION_STRING env var or ConnectionStrings:Default."
- );
+ private readonly string _connectionString = GetConnectionString(configuration);
+
+ private static string GetConnectionString(IConfiguration configuration)
+ {
+ // Check for full connection string first
+ var fullConnectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING");
+ if (!string.IsNullOrEmpty(fullConnectionString))
+ {
+ return fullConnectionString;
+ }
+
+ // Try to build from individual environment variables (preferred method for Docker)
+ try
+ {
+ return SqlConnectionStringHelper.BuildConnectionString();
+ }
+ catch (InvalidOperationException)
+ {
+ // Fall back to configuration-based connection string if env vars are not set
+ var connString = configuration.GetConnectionString("Default");
+ if (!string.IsNullOrEmpty(connString))
+ {
+ return connString;
+ }
+
+ throw new InvalidOperationException(
+ "Database connection string not configured. Set DB_CONNECTION_STRING or DB_SERVER, DB_NAME, DB_USER, DB_PASSWORD env vars or ConnectionStrings:Default."
+ );
+ }
+ }
public DbConnection CreateConnection()
{
diff --git a/src/Core/Repository/Repository.Core/Sql/SqlConnectionStringHelper.cs b/src/Core/Repository/Repository.Core/Sql/SqlConnectionStringHelper.cs
new file mode 100644
index 0000000..907c82f
--- /dev/null
+++ b/src/Core/Repository/Repository.Core/Sql/SqlConnectionStringHelper.cs
@@ -0,0 +1,50 @@
+using Microsoft.Data.SqlClient;
+
+namespace DataAccessLayer.Sql
+{
+ public static class SqlConnectionStringHelper
+ {
+ ///
+ /// Builds a SQL Server connection string from environment variables.
+ /// Expects DB_SERVER, DB_NAME, DB_USER, DB_PASSWORD, and DB_TRUST_SERVER_CERTIFICATE.
+ ///
+ /// Optional override for the database name. If null, uses DB_NAME env var.
+ /// A properly formatted SQL Server connection string.
+ public static string BuildConnectionString(string? databaseName = null)
+ {
+ var server = Environment.GetEnvironmentVariable("DB_SERVER")
+ ?? throw new InvalidOperationException("DB_SERVER environment variable is not set");
+
+ var dbName = databaseName
+ ?? Environment.GetEnvironmentVariable("DB_NAME")
+ ?? throw new InvalidOperationException("DB_NAME environment variable is not set");
+
+ var user = Environment.GetEnvironmentVariable("DB_USER")
+ ?? throw new InvalidOperationException("DB_USER environment variable is not set");
+
+ var password = Environment.GetEnvironmentVariable("DB_PASSWORD")
+ ?? throw new InvalidOperationException("DB_PASSWORD environment variable is not set");
+
+ var builder = new SqlConnectionStringBuilder
+ {
+ DataSource = server,
+ InitialCatalog = dbName,
+ UserID = user,
+ Password = password,
+ TrustServerCertificate = true,
+ Encrypt = true
+ };
+
+ return builder.ConnectionString;
+ }
+
+ ///
+ /// Builds a connection string to the master database using environment variables.
+ ///
+ /// A connection string for the master database.
+ public static string BuildMasterConnectionString()
+ {
+ return BuildConnectionString("master");
+ }
+ }
+}
diff --git a/src/Core/Repository/Repository.Tests/Dockerfile b/src/Core/Repository/Repository.Tests/Dockerfile
index 6d5db48..4b83399 100644
--- a/src/Core/Repository/Repository.Tests/Dockerfile
+++ b/src/Core/Repository/Repository.Tests/Dockerfile
@@ -11,4 +11,4 @@ RUN dotnet build "./Repository.Tests.csproj" -c $BUILD_CONFIGURATION -o /app/bui
FROM build AS final
RUN mkdir -p /app/test-results
WORKDIR /src/Repository/Repository.Tests
-ENTRYPOINT ["dotnet", "test", "./Repository.Tests.csproj", "-c", "Release", "--no-build", "--logger", "trx;LogFileName=/app/test-results/repository-tests.trx"]
+ENTRYPOINT ["dotnet", "test", "./Repository.Tests.csproj", "-c", "Release", "--logger", "trx;LogFileName=/app/test-results/repository-tests.trx"]