changes for image
This commit is contained in:
@@ -1,36 +1,35 @@
|
||||
# Phase 1: Die Build-Phase
|
||||
# Wir verwenden das offizielle .NET 8 SDK-Image, das alle Werkzeuge zum Bauen enth<74>lt.
|
||||
# Verwendet das .NET 8 SDK-Image, das alle Werkzeuge zum Bauen enth<74>lt.
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
# Kopiere zuerst die Projekt- und Solution-Dateien.
|
||||
# Docker ist schlau: Wenn sich diese Dateien nicht <20>ndern, verwendet es den Cache und der 'restore'-Schritt wird <20>bersprungen.
|
||||
# Kopiere zuerst die Projekt- und Solution-Dateien, um den Docker-Cache optimal zu nutzen.
|
||||
COPY ["ShopSolution.sln", "."]
|
||||
COPY ["Webshop.Api/Webshop.Api.csproj", "Webshop.Api/"]
|
||||
COPY ["Webshop.Application/Webshop.Application.csproj", "Webshop.Application/"]
|
||||
COPY ["Webshop.Domain/Webshop.Domain.csproj", "Webshop.Domain/"]
|
||||
COPY ["Webshop.Infrastructure/Webshop.Infrastructure.csproj", "Webshop.Infrastructure/"]
|
||||
|
||||
# Lade alle NuGet-Abh<62>ngigkeiten f<EFBFBD>r die gesamte Solution herunter.
|
||||
# Lade alle NuGet-Abh<62>ngigkeiten herunter.
|
||||
RUN dotnet restore "ShopSolution.sln"
|
||||
|
||||
# Kopiere den gesamten restlichen Quellcode.
|
||||
COPY . .
|
||||
|
||||
# Wechsle in das Verzeichnis des API-Projekts.
|
||||
# Publiziert die Anwendung in einer Release-Konfiguration.
|
||||
# `dotnet publish` kopiert automatisch alle notwendigen Dateien, einschlie<69>lich `wwwroot`.
|
||||
WORKDIR "/src/Webshop.Api"
|
||||
# Baue und publiziere die Anwendung in einer Release-Konfiguration.
|
||||
# Das Ergebnis wird in den Ordner /app/publish verschoben.
|
||||
RUN dotnet publish "Webshop.Api.csproj" -c Release -o /app/publish --no-restore
|
||||
|
||||
# ---
|
||||
|
||||
# Phase 2: Die finale Runtime-Phase
|
||||
# Wir verwenden das deutlich kleinere ASP.NET Core Runtime-Image, da wir den Code nicht mehr kompilieren m<>ssen.
|
||||
# Verwendet das kleinere ASP.NET Core Runtime-Image f<>r eine geringere Gr<47><72>e.
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
|
||||
WORKDIR /app
|
||||
|
||||
# Kopiere nur die publizierte Anwendung aus der Build-Phase.
|
||||
# Der `publish`-Ordner enth<74>lt die DLLs, Abh<62>ngigkeiten und den `wwwroot`-Ordner.
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
# Definiere den Befehl, der ausgef<65>hrt wird, wenn der Container startet.
|
||||
|
||||
@@ -29,15 +29,17 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// --- 1. Dependency Injection Konfiguration ---
|
||||
|
||||
// Datenbank-Kontext
|
||||
// Datenbank-Kontext mit Split-Query-Verhalten f<>r Performance-Optimierung
|
||||
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))
|
||||
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"),
|
||||
o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery))
|
||||
);
|
||||
|
||||
// ASP.NET Core Identity mit ApplicationUser
|
||||
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
|
||||
{
|
||||
options.SignIn.RequireConfirmedEmail = true;
|
||||
// In Produktion auf 'true' setzen, f<>r Entwicklung ist 'false' einfacher.
|
||||
options.SignIn.RequireConfirmedEmail = false;
|
||||
})
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||
.AddDefaultTokenProviders();
|
||||
@@ -75,7 +77,15 @@ builder.Services.AddAuthentication(options =>
|
||||
});
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
// Infrastruktur & Externe Dienste
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddScoped<IFileStorageService, LocalFileStorageService>();
|
||||
builder.Services.AddHttpClient<ResendClient>();
|
||||
builder.Services.Configure<ResendClientOptions>(options =>
|
||||
{
|
||||
options.ApiToken = builder.Configuration["Resend:ApiToken"]!;
|
||||
});
|
||||
builder.Services.AddTransient<IResend, ResendClient>();
|
||||
|
||||
// Repositories
|
||||
builder.Services.AddScoped<IProductRepository, ProductRepository>();
|
||||
@@ -109,16 +119,6 @@ builder.Services.AddScoped<IOrderService, OrderService>();
|
||||
builder.Services.AddScoped<IAddressService, AddressService>();
|
||||
builder.Services.AddScoped<ICheckoutService, CheckoutService>();
|
||||
builder.Services.AddScoped<IReviewService, ReviewService>();
|
||||
builder.Services.AddScoped<IFileStorageService, LocalFileStorageService>();
|
||||
|
||||
|
||||
// Externe Dienste (Resend)
|
||||
builder.Services.AddHttpClient<ResendClient>();
|
||||
builder.Services.Configure<ResendClientOptions>(options =>
|
||||
{
|
||||
options.ApiToken = builder.Configuration["Resend:ApiToken"]!;
|
||||
});
|
||||
builder.Services.AddTransient<IResend, ResendClient>();
|
||||
|
||||
// Controller und API-Infrastruktur
|
||||
builder.Services.AddControllers()
|
||||
@@ -131,54 +131,29 @@ builder.Services.AddEndpointsApiExplorer();
|
||||
// Swagger / OpenAPI Konfiguration
|
||||
builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
// --- 1. Gruppierung der Endpunkte (visuelle Tags) ---
|
||||
c.TagActionsBy(api =>
|
||||
{
|
||||
var controllerName = api.ActionDescriptor.RouteValues["controller"];
|
||||
if (controllerName == null) return new[] { "Default" };
|
||||
|
||||
// Bereinige den Controller-Namen f<>r einen sauberen Tag
|
||||
var tag = controllerName.Replace("Admin", "").Replace("Controller", "");
|
||||
|
||||
|
||||
if (api.RelativePath.StartsWith("api/v1/auth"))
|
||||
{
|
||||
return new[] { "Auth" };
|
||||
}
|
||||
if (api.RelativePath.StartsWith("api/v1/admin"))
|
||||
{
|
||||
return new[] { $"Admin - {tag}" };
|
||||
}
|
||||
if (api.RelativePath.StartsWith("api/v1/customer"))
|
||||
{
|
||||
return new[] { $"Customer - {tag}" };
|
||||
}
|
||||
if (api.RelativePath.StartsWith("api/v1/public"))
|
||||
{
|
||||
return new[] { $"Public - {tag}" };
|
||||
}
|
||||
|
||||
if (api.RelativePath.StartsWith("api/v1/auth")) return new[] { "Auth" };
|
||||
if (api.RelativePath.StartsWith("api/v1/admin")) return new[] { $"Admin - {tag}" };
|
||||
if (api.RelativePath.StartsWith("api/v1/customer")) return new[] { $"Customer - {tag}" };
|
||||
if (api.RelativePath.StartsWith("api/v1/public")) return new[] { $"Public - {tag}" };
|
||||
return new[] { tag };
|
||||
});
|
||||
|
||||
// --- NEU: Sortierung der Gruppen in der UI ---
|
||||
c.OrderActionsBy(apiDesc =>
|
||||
{
|
||||
var relativePath = apiDesc.RelativePath ?? "";
|
||||
|
||||
// Sortierung
|
||||
if (relativePath.StartsWith("api/v1/auth")) return "1";
|
||||
if (relativePath.StartsWith("api/v1/admin")) return "2";
|
||||
if (relativePath.StartsWith("api/v1/auth")) return "1";
|
||||
if (relativePath.StartsWith("api/v1/public")) return "2";
|
||||
if (relativePath.StartsWith("api/v1/customer")) return "3";
|
||||
if (relativePath.StartsWith("api/v1/public")) return "4";
|
||||
|
||||
|
||||
|
||||
return "5"; // Fallback
|
||||
if (relativePath.StartsWith("api/v1/admin")) return "4";
|
||||
return "5";
|
||||
});
|
||||
|
||||
|
||||
// --- 2. JWT Security Konfiguration ---
|
||||
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||
{
|
||||
Description = "JWT Authorization header. Example: \"Authorization: Bearer {token}\"",
|
||||
@@ -190,38 +165,24 @@ builder.Services.AddSwaggerGen(c =>
|
||||
|
||||
c.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
},
|
||||
new string[] {}
|
||||
}
|
||||
{ new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, new string[] {} }
|
||||
});
|
||||
|
||||
c.SchemaFilter<AddExampleSchemaFilter>();
|
||||
c.OperationFilter<AuthorizeOperationFilter>();
|
||||
|
||||
// Endpunktspezifische Beispiele
|
||||
c.OperationFilter<LoginExampleOperationFilter>();
|
||||
c.OperationFilter<PaymentMethodExampleOperationFilter>();
|
||||
c.OperationFilter<SupplierExampleOperationFilter>();
|
||||
c.OperationFilter<ShippingMethodExampleOperationFilter>();
|
||||
c.OperationFilter<AdminCategorieExampleOperationFilter>();
|
||||
c.OperationFilter<AdminProductExampleOperationFilter>();
|
||||
c.OperationFilter<CustomerAddressExampleOperationFilter>();
|
||||
c.OperationFilter<CustomerOrderExampleOperationFilter>();
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// --- 2. HTTP Request Pipeline Konfiguration ---
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// F<>hre Datenbank-Migrationen und Initialisierungslogik beim Start aus
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
@@ -231,7 +192,6 @@ using (var scope = app.Services.CreateScope())
|
||||
var context = services.GetRequiredService<ApplicationDbContext>();
|
||||
context.Database.Migrate();
|
||||
|
||||
// TEMPOR<4F>RER INITIALER ADMIN- UND KUNDEN-SETUP
|
||||
// HINWEIS: Dieser Block sollte in der Produktion entfernt oder deaktiviert werden.
|
||||
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
|
||||
var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
|
||||
@@ -248,58 +208,58 @@ using (var scope = app.Services.CreateScope())
|
||||
if (adminUser == null)
|
||||
{
|
||||
adminUser = new ApplicationUser { UserName = "admin@yourwebshop.com", Email = "admin@yourwebshop.com", EmailConfirmed = true, CreatedDate = DateTimeOffset.UtcNow, LastActive = DateTimeOffset.UtcNow };
|
||||
var createAdmin = await userManager.CreateAsync(adminUser, "SecureAdminPass123!");
|
||||
if (createAdmin.Succeeded)
|
||||
var createAdminResult = await userManager.CreateAsync(adminUser, "SecureAdminPass123!");
|
||||
if (createAdminResult.Succeeded)
|
||||
{
|
||||
await userManager.AddToRoleAsync(adminUser, "Admin");
|
||||
Console.WriteLine("Admin user created.");
|
||||
Console.WriteLine("INFO: Admin user created successfully.");
|
||||
}
|
||||
else { Console.WriteLine($"Error creating admin user: {string.Join(", ", createAdmin.Errors.Select(e => e.Description))}"); }
|
||||
else { Console.WriteLine($"ERROR: Could not create admin user. Errors: {string.Join(", ", createAdminResult.Errors.Select(e => e.Description))}"); }
|
||||
}
|
||||
|
||||
var customerUser = await userManager.FindByEmailAsync("customer@yourwebshop.com");
|
||||
if (customerUser == null)
|
||||
{
|
||||
customerUser = new ApplicationUser { UserName = "customer@yourwebshop.com", Email = "customer@yourwebshop.com", EmailConfirmed = true, CreatedDate = DateTimeOffset.UtcNow, LastActive = DateTimeOffset.UtcNow };
|
||||
var createCustomer = await userManager.CreateAsync(customerUser, "SecureCustomerPass123!");
|
||||
if (createCustomer.Succeeded)
|
||||
var createCustomerResult = await userManager.CreateAsync(customerUser, "SecureCustomerPass12-3!");
|
||||
if (createCustomerResult.Succeeded)
|
||||
{
|
||||
await userManager.AddToRoleAsync(customerUser, "Customer");
|
||||
Console.WriteLine("Customer user created.");
|
||||
Console.WriteLine("INFO: Customer user created successfully.");
|
||||
|
||||
if (!await context.Customers.AnyAsync(c => c.AspNetUserId == customerUser.Id))
|
||||
{
|
||||
var customerProfile = new Customer { AspNetUserId = customerUser.Id, FirstName = "Test", LastName = "Kunde" };
|
||||
context.Customers.Add(customerProfile);
|
||||
await context.SaveChangesAsync();
|
||||
Console.WriteLine("Customer profile created for new customer user.");
|
||||
Console.WriteLine("INFO: Customer profile created for new customer user.");
|
||||
}
|
||||
}
|
||||
else { Console.WriteLine($"Error creating customer user: {string.Join(", ", createCustomer.Errors.Select(e => e.Description))}"); }
|
||||
else { Console.WriteLine($"ERROR: Could not create customer user. Errors: {string.Join(", ", createCustomerResult.Errors.Select(e => e.Description))}"); }
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var logger = services.GetRequiredService<ILogger<Program>>();
|
||||
logger.LogError(ex, "An error occurred during database migration or user initialization.");
|
||||
logger.LogError(ex, "FATAL: An error occurred during database migration or user initialization.");
|
||||
}
|
||||
}
|
||||
|
||||
// Middleware f<>r Reverse-Proxies
|
||||
var forwardedHeadersOptions = new ForwardedHeadersOptions
|
||||
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
||||
{
|
||||
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
|
||||
};
|
||||
});
|
||||
|
||||
// << Statische Dateien aus wwwroot bereitstellen (z.B. /uploads/xyz.jpg) >>
|
||||
// Statische Dateien aus wwwroot bereitstellen (z.B. /uploads/xyz.jpg)
|
||||
app.UseStaticFiles();
|
||||
|
||||
// Swagger/SwaggerUI f<>r API-Dokumentation aktivieren
|
||||
//if (app.Environment.IsDevelopment())
|
||||
//{
|
||||
app.UseSwagger();
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
//}
|
||||
}
|
||||
|
||||
// app.UseHttpsRedirection(); // Auskommentiert f<>r Docker HTTP-Entwicklung
|
||||
|
||||
|
||||
Reference in New Issue
Block a user