Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ coverage*.xml
!.env.example
appsettings.*.json
!appsettings.json
!appsettings.Development.json

# YAML config - production secrets must never be committed
OrderMonitor_ENV.production.yml
OrderMonitor_ENV.staging.yml

# Secrets
*.pem
Expand Down
46 changes: 46 additions & 0 deletions OrderMonitor_ENV.development.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# OrderMonitor API - Development Environment Configuration
# Non-sensitive values for local development.
# Sensitive values (passwords, keys) should be set as OS environment variables.

# ===== Database Configuration =====
Database__Provider: "sqlserver"
Database__ConnectionString: "Server=localhost,1433;Database=PrinterPix_BO_Live;User Id=sa;Password={ENCRYPTED};TrustServerCertificate=True;ApplicationIntent=ReadOnly;"
Database__EncryptedPassword: "" # Set via env var: Database__EncryptedPassword
Database__EncryptionKey: "" # Set via env var: Database__EncryptionKey
Database__MaxPoolSize: "50"
Database__CommandTimeout: "30"

# ===== SMTP Configuration =====
SmtpSettings__Host: "pod51017.outlook.com"
SmtpSettings__Port: "587"
SmtpSettings__Username: "backoffice@printerpix.com"
SmtpSettings__Password: "" # Set via env var: SmtpSettings__Password
SmtpSettings__FromEmail: "backoffice@printerpix.com"
SmtpSettings__UseSsl: "true"

# ===== Alert Configuration =====
Alerts__Enabled: "true"
Alerts__Recipients: "ranganathan.e@syncoms.com"
Alerts__SubjectPrefix: "[Order Monitor DEV]"

# ===== Scanner Configuration =====
Scanner__Enabled: "true"
Scanner__IntervalMinutes: "15"
Scanner__BatchSize: "100"

# ===== Business Hours =====
BusinessHours__Timezone: "Europe/London"
BusinessHours__StartHour: "0"
BusinessHours__EndHour: "0"
BusinessHours__Holidays: "2026-01-01,2026-04-03,2026-04-06,2026-05-04,2026-05-25,2026-08-31,2026-12-25,2026-12-28"

# ===== Swagger =====
Swagger__Enabled: "true"

# ===== Logging =====
Logging__LogLevel__Default: "Debug"
Logging__LogLevel__Microsoft.AspNetCore: "Information"
Logging__LogLevel__OrderMonitor: "Debug"

# ===== Application =====
ASPNETCORE_ENVIRONMENT: "Development"
25 changes: 25 additions & 0 deletions OrderMonitor_ENV.secrets.yml.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# OrderMonitor API - Secrets Configuration Template
# =================================================
# IMPORTANT: Never commit this file with real values!
# Copy to OrderMonitor_ENV.secrets.yml and fill with actual Base64-encoded values.
# This file is for reference only.
#
# How to Base64 encode:
# Linux/Mac: echo -n "your-value" | base64
# PowerShell: [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("your-value"))

# ===== Database Credentials =====
# Connection string with password placeholder
Database__ConnectionString: ""
# AES-encrypted database password (Base64)
Database__EncryptedPassword: ""
# AES-256 encryption key (exactly 32 characters)
Database__EncryptionKey: ""

# ===== SMTP Credentials =====
# Encrypted SMTP password (Base64 AES-encrypted)
SmtpSettings__Password: ""

# ===== Alert Recipients =====
# Comma-separated email addresses
Alerts__Recipients: ""
65 changes: 65 additions & 0 deletions OrderMonitor_ENV.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# OrderMonitor API - Environment Configuration
# This file contains ALL configuration values for the application.
# Override precedence: K8s Secrets > ConfigMap > OS env vars > this file
#
# Key naming: Use double-underscore (__) as hierarchy separator.
# Example: Database__ConnectionString maps to IConfiguration["Database:ConnectionString"]

# ===== Database Configuration =====
Database__Provider: "sqlserver" # sqlserver | mysql | postgresql
Database__ConnectionString: "" # Full connection string (without password if using EncryptedPassword)
Database__EncryptedPassword: "" # Base64-encoded AES-encrypted password
Database__EncryptionKey: "" # AES-256 key for decrypting EncryptedPassword (32 chars)
Database__MaxPoolSize: "100"
Database__CommandTimeout: "30"

# ===== SMTP Configuration =====
SmtpSettings__Host: ""
SmtpSettings__Port: "587"
SmtpSettings__Username: ""
SmtpSettings__Password: "" # Encrypted SMTP password
SmtpSettings__FromEmail: ""
SmtpSettings__UseSsl: "true"

# ===== Alert Configuration =====
Alerts__Enabled: "true"
Alerts__Recipients: "" # Comma-separated email addresses
Alerts__SubjectPrefix: "[Order Monitor]"

# ===== Scanner Configuration =====
Scanner__Enabled: "true"
Scanner__IntervalMinutes: "15"
Scanner__BatchSize: "1000"

# ===== Status Thresholds =====
StatusThresholds__PrepStatuses__MinStatusId: "3001"
StatusThresholds__PrepStatuses__MaxStatusId: "3910"
StatusThresholds__PrepStatuses__ThresholdHours: "6"
StatusThresholds__FacilityStatuses__MinStatusId: "4001"
StatusThresholds__FacilityStatuses__MaxStatusId: "5830"
StatusThresholds__FacilityStatuses__ThresholdHours: "48"

# ===== Business Hours =====
BusinessHours__Timezone: "Europe/London"
BusinessHours__StartHour: "0" # 0 = full 24-hour days
BusinessHours__EndHour: "0"
BusinessHours__Holidays: "" # Comma-separated yyyy-MM-dd dates

# ===== Health Check =====
HealthCheck__Path: "/health"
HealthCheck__IncludeDatabase: "true"

# ===== Swagger =====
Swagger__Enabled: "true"
Swagger__Title: "OrderMonitor API"
Swagger__Version: "v1"

# ===== Logging =====
Logging__LogLevel__Default: "Information"
Logging__LogLevel__Microsoft.AspNetCore: "Warning"
Logging__LogLevel__OrderMonitor: "Debug"

# ===== Application =====
AllowedHosts: "*"
ASPNETCORE_ENVIRONMENT: "Production"
ASPNETCORE_URLS: "http://+:8080"
44 changes: 44 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
version: '3.8'

services:
ordermonitor-api:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Development
volumes:
- ./OrderMonitor_ENV.yml:/app/OrderMonitor_ENV.yml:ro
- ./OrderMonitor_ENV.development.yml:/app/OrderMonitor_ENV.development.yml:ro
depends_on:
sqlserver:
condition: service_healthy
networks:
- ordermonitor

sqlserver:
image: mcr.microsoft.com/mssql/server:2022-latest
environment:
- ACCEPT_EULA=Y
- MSSQL_SA_PASSWORD=YourStr0ngP@ssword!
ports:
- "1433:1433"
volumes:
- sqlserver-data:/var/opt/mssql
healthcheck:
test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "YourStr0ngP@ssword!" -Q "SELECT 1" -C || exit 1
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
networks:
- ordermonitor

volumes:
sqlserver-data:

networks:
ordermonitor:
driver: bridge
16 changes: 15 additions & 1 deletion src/OrderMonitor.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using OrderMonitor.Core.Interfaces;
using OrderMonitor.Infrastructure;
using OrderMonitor.Infrastructure.Configuration;
using Serilog;

// Configure Serilog early for startup logging
Expand All @@ -12,6 +14,14 @@

var builder = WebApplication.CreateBuilder(args);

// Add YAML configuration sources (before builder.Build)
// Override precedence: appsettings.json → YML file → environment variables
var environment = builder.Environment.EnvironmentName;
builder.Configuration
.AddYamlFile("OrderMonitor_ENV.yml", optional: true)
.AddYamlFile($"OrderMonitor_ENV.{environment.ToLowerInvariant()}.yml", optional: true)
.AddEnvironmentVariables();

// Configure Serilog from appsettings
builder.Host.UseSerilog((context, services, configuration) => configuration
.ReadFrom.Configuration(context.Configuration)
Expand All @@ -24,14 +34,18 @@
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// Add infrastructure services (database, repositories)
// Add infrastructure services (database, repositories, config validation)
builder.Services.AddInfrastructure(builder.Configuration);

// Add health checks
builder.Services.AddHealthChecks();

var app = builder.Build();

// Validate configuration at startup
var validator = app.Services.GetService<IConfigurationValidator>();
validator?.Validate();

// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
Expand Down
38 changes: 0 additions & 38 deletions src/OrderMonitor.Api/appsettings.Development.json

This file was deleted.

29 changes: 1 addition & 28 deletions src/OrderMonitor.Api/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
{
"ConnectionStrings": {
"BackofficeDb": "Server=localhost;Database=Backoffice;Trusted_Connection=True;TrustServerCertificate=True;ApplicationIntent=ReadOnly;"
},

"StatusThresholds": {
"PrepStatuses": {
"MinStatusId": 3001,
Expand All @@ -16,33 +12,10 @@
}
},

"Scanner": {
"Enabled": true,
"IntervalMinutes": 15,
"BatchSize": 1000
},

"Alerts": {
"Enabled": true,
"Recipients": [
"ranganathan.e@syncoms.com"
],
"SubjectPrefix": "[Order Monitor]"
},

"SmtpSettings": {
"Host": "pod51017.outlook.com",
"Port": 587,
"Username": "backoffice@printerpix.com",
"FromEmail": "backoffice@printerpix.com",
"UseSsl": true
},

"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"OrderMonitor": "Debug"
"Microsoft.AspNetCore": "Warning"
}
},

Expand Down
44 changes: 44 additions & 0 deletions src/OrderMonitor.Core/Configuration/BusinessHoursSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace OrderMonitor.Core.Configuration;

/// <summary>
/// Business hours and holiday configuration settings.
/// </summary>
public class BusinessHoursSettings
{
public const string SectionName = "BusinessHours";

/// <summary>
/// IANA timezone identifier (e.g., "Europe/London").
/// </summary>
public string Timezone { get; set; } = "Europe/London";

/// <summary>
/// Business day start hour (24h format).
/// </summary>
public int StartHour { get; set; } = 0;

/// <summary>
/// Business day end hour (24h format). 0 means full 24-hour days.
/// </summary>
public int EndHour { get; set; } = 0;

/// <summary>
/// Comma-separated list of holiday dates in yyyy-MM-dd format.
/// </summary>
public string Holidays { get; set; } = string.Empty;

/// <summary>
/// Parses the Holidays string into a list of DateTime values.
/// </summary>
public IEnumerable<DateTime> GetHolidayDates()
{
if (string.IsNullOrWhiteSpace(Holidays))
return Enumerable.Empty<DateTime>();

return Holidays
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Select(s => DateTime.TryParse(s, out var date) ? date : (DateTime?)null)
.Where(d => d.HasValue)
.Select(d => d!.Value.Date);
}
}
Loading