Added docker-compose. Updated startups for API and Web layer.
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -361,3 +361,6 @@ MigrationBackup/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# Environment
|
||||
.env
|
||||
@@ -2,13 +2,16 @@ using JSMR.Api.Startup;
|
||||
|
||||
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
ConfigurationManager configuration = builder.Configuration;
|
||||
IWebHostEnvironment environment = builder.Environment;
|
||||
|
||||
builder.Services
|
||||
.AddAppServices(builder)
|
||||
.AddAppServices(configuration)
|
||||
.AddAppJson()
|
||||
.AddAppOpenApi()
|
||||
.AddAppAuthentication()
|
||||
.AddAppCors(builder)
|
||||
.AddAppLogging(builder);
|
||||
.AddAppAuthentication(environment)
|
||||
.AddAppCors(configuration);
|
||||
//.AddAppLogging(builder);
|
||||
|
||||
builder.Host.UseAppSerilog();
|
||||
|
||||
|
||||
@@ -1,9 +1,31 @@
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace JSMR.Api.Startup;
|
||||
|
||||
public static class HostBuilderExtensions
|
||||
{
|
||||
public static IHostBuilder UseAppSerilog(this IHostBuilder host)
|
||||
=> host.UseSerilog();
|
||||
{
|
||||
return host.UseSerilog((context, services, loggerConfiguration) =>
|
||||
{
|
||||
IConfiguration configuration = context.Configuration;
|
||||
IHostEnvironment environment = context.HostingEnvironment;
|
||||
|
||||
loggerConfiguration
|
||||
.ReadFrom.Configuration(configuration)
|
||||
.ReadFrom.Services(services)
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
.Enrich.WithProperty("Service", "JSMR.Api")
|
||||
.Enrich.WithProperty("Environment", environment.EnvironmentName);
|
||||
|
||||
// Conditionally add Seq if configured correctly
|
||||
string? seqUrl = configuration["Seq:ServerUrl"];
|
||||
|
||||
if (Uri.TryCreate(seqUrl, UriKind.Absolute, out _))
|
||||
{
|
||||
loggerConfiguration.WriteTo.Seq(seqUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@ using JSMR.Infrastructure.DI;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Http.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
@@ -13,14 +11,14 @@ namespace JSMR.Api.Startup;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddAppServices(this IServiceCollection services, IHostApplicationBuilder builder)
|
||||
public static IServiceCollection AddAppServices(this IServiceCollection services, IConfigurationManager configuration)
|
||||
{
|
||||
services
|
||||
.AddMemoryCache()
|
||||
.AddApplication()
|
||||
.AddInfrastructure();
|
||||
|
||||
string connectionString = builder.Configuration.GetConnectionString("AppDb")
|
||||
string connectionString = configuration.GetConnectionString("AppDb")
|
||||
?? throw new InvalidOperationException("Missing ConnectionStrings:AppDb");
|
||||
|
||||
services.AddDbContextFactory<AppDbContext>(opt =>
|
||||
@@ -51,7 +49,7 @@ public static class ServiceCollectionExtensions
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddAppAuthentication(this IServiceCollection services)
|
||||
public static IServiceCollection AddAppAuthentication(this IServiceCollection services, IHostEnvironment environment)
|
||||
{
|
||||
services.AddAuthorization();
|
||||
|
||||
@@ -61,8 +59,15 @@ public static class ServiceCollectionExtensions
|
||||
{
|
||||
options.Cookie.Name = "vw_auth";
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.SameSite = SameSiteMode.None;
|
||||
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
|
||||
//options.Cookie.SameSite = SameSiteMode.None;
|
||||
//options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
|
||||
|
||||
options.Cookie.SameSite = SameSiteMode.Lax;
|
||||
|
||||
options.Cookie.SecurePolicy =
|
||||
environment.IsDevelopment()
|
||||
? CookieSecurePolicy.SameAsRequest
|
||||
: CookieSecurePolicy.Always;
|
||||
|
||||
options.Events = new CookieAuthenticationEvents
|
||||
{
|
||||
@@ -82,37 +87,44 @@ public static class ServiceCollectionExtensions
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddAppCors(this IServiceCollection services, IHostApplicationBuilder builder)
|
||||
public static IServiceCollection AddAppCors(this IServiceCollection services, IConfigurationManager configuration)
|
||||
{
|
||||
// Prefer config-based origins so you stop editing code for ports.
|
||||
// appsettings.Development.json:
|
||||
// "Cors": { "AllowedOrigins": [ "https://localhost:7112", ... ] }
|
||||
string[] origins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>() ?? [];
|
||||
string[] origins = configuration.GetSection("Cors:AllowedOrigins").Get<string[]>() ?? [];
|
||||
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("ui", policyBuilder =>
|
||||
{
|
||||
if (origins.Length == 0)
|
||||
{
|
||||
// In container/prod you often don't need CORS at all (same-origin),
|
||||
// but if it *is* needed and not configured, fail closed rather than crash.
|
||||
// Do not call WithOrigins().
|
||||
return;
|
||||
}
|
||||
|
||||
policyBuilder.WithOrigins(origins)
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials());
|
||||
.AllowCredentials();
|
||||
});
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddAppLogging(this IServiceCollection services, IHostApplicationBuilder builder)
|
||||
{
|
||||
var config = builder.Configuration;
|
||||
var env = builder.Environment;
|
||||
//public static IServiceCollection AddAppLogging(this IServiceCollection services, IHostApplicationBuilder builder)
|
||||
//{
|
||||
// var config = builder.Configuration;
|
||||
// var env = builder.Environment;
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.ReadFrom.Configuration(config)
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
.Enrich.WithProperty("Service", "JSMR.Api")
|
||||
.Enrich.WithProperty("Environment", env.EnvironmentName)
|
||||
.CreateLogger();
|
||||
// Log.Logger = new LoggerConfiguration()
|
||||
// .ReadFrom.Configuration(config)
|
||||
// .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
// .Enrich.WithProperty("Service", "JSMR.Api")
|
||||
// .Enrich.WithProperty("Environment", env.EnvironmentName)
|
||||
// .CreateLogger();
|
||||
|
||||
return services;
|
||||
}
|
||||
// return services;
|
||||
//}
|
||||
}
|
||||
@@ -15,12 +15,18 @@ public static class WebApplicationExtensions
|
||||
{
|
||||
public static WebApplication UseAppPipeline(this WebApplication app, IHostEnvironment env)
|
||||
{
|
||||
string[] origins = app.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>() ?? [];
|
||||
|
||||
if (origins.Length > 0)
|
||||
app.UseCors("ui");
|
||||
|
||||
if (env.IsDevelopment())
|
||||
app.MapOpenApi();
|
||||
|
||||
if (!env.IsDevelopment())
|
||||
{
|
||||
app.UseHttpsRedirection();
|
||||
}
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
@@ -25,11 +25,10 @@
|
||||
},
|
||||
"Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ],
|
||||
"WriteTo": [
|
||||
{ "Name": "Console" },
|
||||
{
|
||||
"Name": "Seq",
|
||||
"Args": { "serverUrl": "%SEQ_URL%" }
|
||||
}
|
||||
{ "Name": "Console" }
|
||||
]
|
||||
},
|
||||
"Seq": {
|
||||
"ServerUrl": ""
|
||||
}
|
||||
}
|
||||
|
||||
8
JSMR.UI.Blazor/Dockerfile
Normal file
8
JSMR.UI.Blazor/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN dotnet publish -c Release -o out
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY --from=build /app/out/wwwroot /usr/share/nginx/html
|
||||
COPY JSMR.UI.Blazor/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
@@ -10,9 +10,14 @@ var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
|
||||
string apiBase = builder.Configuration["ApiBaseUrl"] ?? builder.HostEnvironment.BaseAddress;
|
||||
//string apiBase = builder.Configuration["ApiBaseUrl"] ?? builder.HostEnvironment.BaseAddress;
|
||||
//Console.WriteLine(apiBase);
|
||||
|
||||
Console.WriteLine(apiBase);
|
||||
// If ApiBaseUrl is set (VS dev), use it. Otherwise (docker/prod), use same-origin.
|
||||
var apiBase = builder.Configuration["ApiBaseUrl"];
|
||||
|
||||
if (string.IsNullOrWhiteSpace(apiBase))
|
||||
apiBase = builder.HostEnvironment.BaseAddress;
|
||||
|
||||
// Old way
|
||||
//builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(apiBase) });
|
||||
|
||||
32
JSMR.UI.Blazor/nginx.conf
Normal file
32
JSMR.UI.Blazor/nginx.conf
Normal file
@@ -0,0 +1,32 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Blazor WASM: serve static files, and fallback to index.html for client-side routes
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Proxy API calls to the api service (docker-compose DNS name: api)
|
||||
location /api/ {
|
||||
proxy_pass http://api:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Proxy auth endpoints too (yours are /auth/login, /auth/logout)
|
||||
location /auth/ {
|
||||
proxy_pass http://api:8080;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
3
JSMR.UI.Blazor/wwwroot/appsettings.Development.json
Normal file
3
JSMR.UI.Blazor/wwwroot/appsettings.Development.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"ApiBaseUrl": "https://localhost:7277"
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"ApiBaseUrl": "https://localhost:7277"
|
||||
"ApiBaseUrl": ""
|
||||
}
|
||||
5
JSMR.sln
5
JSMR.sln
@@ -17,6 +17,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JSMR.UI.Blazor", "JSMR.UI.B
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JSMR.Worker", "JSMR.Worker\JSMR.Worker.csproj", "{964BD375-FAE3-4044-A09B-5C43919C9B52}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F3C045AD-3861-4079-85F0-EDEEE83765B7}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
docker-compose.yml = docker-compose.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
||||
27
docker-compose.yml
Normal file
27
docker-compose.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
services:
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: JSMR.Api/Dockerfile
|
||||
ports:
|
||||
- "5000:8080"
|
||||
environment:
|
||||
ASPNETCORE_ENVIRONMENT: ${ASPNETCORE_ENVIRONMENT:-Production}
|
||||
ConnectionStrings__AppDb: ${APPDB_CONN}
|
||||
Seq__ServerUrl: ${SEQ_URL:-}
|
||||
networks:
|
||||
- app-net
|
||||
|
||||
web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: JSMR.UI.Blazor/Dockerfile
|
||||
ports:
|
||||
- "5001:80"
|
||||
depends_on:
|
||||
- api
|
||||
networks:
|
||||
- app-net
|
||||
|
||||
networks:
|
||||
app-net: {}
|
||||
Reference in New Issue
Block a user