changes for image
This commit is contained in:
@@ -1,36 +1,35 @@
|
|||||||
# Phase 1: Die Build-Phase
|
# 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
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
# Kopiere zuerst die Projekt- und Solution-Dateien.
|
# Kopiere zuerst die Projekt- und Solution-Dateien, um den Docker-Cache optimal zu nutzen.
|
||||||
# Docker ist schlau: Wenn sich diese Dateien nicht <20>ndern, verwendet es den Cache und der 'restore'-Schritt wird <20>bersprungen.
|
|
||||||
COPY ["ShopSolution.sln", "."]
|
COPY ["ShopSolution.sln", "."]
|
||||||
COPY ["Webshop.Api/Webshop.Api.csproj", "Webshop.Api/"]
|
COPY ["Webshop.Api/Webshop.Api.csproj", "Webshop.Api/"]
|
||||||
COPY ["Webshop.Application/Webshop.Application.csproj", "Webshop.Application/"]
|
COPY ["Webshop.Application/Webshop.Application.csproj", "Webshop.Application/"]
|
||||||
COPY ["Webshop.Domain/Webshop.Domain.csproj", "Webshop.Domain/"]
|
COPY ["Webshop.Domain/Webshop.Domain.csproj", "Webshop.Domain/"]
|
||||||
COPY ["Webshop.Infrastructure/Webshop.Infrastructure.csproj", "Webshop.Infrastructure/"]
|
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"
|
RUN dotnet restore "ShopSolution.sln"
|
||||||
|
|
||||||
# Kopiere den gesamten restlichen Quellcode.
|
# Kopiere den gesamten restlichen Quellcode.
|
||||||
COPY . .
|
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"
|
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
|
RUN dotnet publish "Webshop.Api.csproj" -c Release -o /app/publish --no-restore
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
# Phase 2: Die finale Runtime-Phase
|
# 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
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Kopiere nur die publizierte Anwendung aus der Build-Phase.
|
# 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 .
|
COPY --from=build /app/publish .
|
||||||
|
|
||||||
# Definiere den Befehl, der ausgef<65>hrt wird, wenn der Container startet.
|
# 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 ---
|
// --- 1. Dependency Injection Konfiguration ---
|
||||||
|
|
||||||
// Datenbank-Kontext
|
// Datenbank-Kontext mit Split-Query-Verhalten f<>r Performance-Optimierung
|
||||||
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
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
|
// ASP.NET Core Identity mit ApplicationUser
|
||||||
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
|
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>()
|
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||||
.AddDefaultTokenProviders();
|
.AddDefaultTokenProviders();
|
||||||
@@ -75,7 +77,15 @@ builder.Services.AddAuthentication(options =>
|
|||||||
});
|
});
|
||||||
builder.Services.AddAuthorization();
|
builder.Services.AddAuthorization();
|
||||||
|
|
||||||
|
// Infrastruktur & Externe Dienste
|
||||||
builder.Services.AddHttpContextAccessor();
|
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
|
// Repositories
|
||||||
builder.Services.AddScoped<IProductRepository, ProductRepository>();
|
builder.Services.AddScoped<IProductRepository, ProductRepository>();
|
||||||
@@ -109,16 +119,6 @@ builder.Services.AddScoped<IOrderService, OrderService>();
|
|||||||
builder.Services.AddScoped<IAddressService, AddressService>();
|
builder.Services.AddScoped<IAddressService, AddressService>();
|
||||||
builder.Services.AddScoped<ICheckoutService, CheckoutService>();
|
builder.Services.AddScoped<ICheckoutService, CheckoutService>();
|
||||||
builder.Services.AddScoped<IReviewService, ReviewService>();
|
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
|
// Controller und API-Infrastruktur
|
||||||
builder.Services.AddControllers()
|
builder.Services.AddControllers()
|
||||||
@@ -131,54 +131,29 @@ builder.Services.AddEndpointsApiExplorer();
|
|||||||
// Swagger / OpenAPI Konfiguration
|
// Swagger / OpenAPI Konfiguration
|
||||||
builder.Services.AddSwaggerGen(c =>
|
builder.Services.AddSwaggerGen(c =>
|
||||||
{
|
{
|
||||||
// --- 1. Gruppierung der Endpunkte (visuelle Tags) ---
|
|
||||||
c.TagActionsBy(api =>
|
c.TagActionsBy(api =>
|
||||||
{
|
{
|
||||||
var controllerName = api.ActionDescriptor.RouteValues["controller"];
|
var controllerName = api.ActionDescriptor.RouteValues["controller"];
|
||||||
if (controllerName == null) return new[] { "Default" };
|
if (controllerName == null) return new[] { "Default" };
|
||||||
|
|
||||||
// Bereinige den Controller-Namen f<>r einen sauberen Tag
|
|
||||||
var tag = controllerName.Replace("Admin", "").Replace("Controller", "");
|
var tag = controllerName.Replace("Admin", "").Replace("Controller", "");
|
||||||
|
|
||||||
|
if (api.RelativePath.StartsWith("api/v1/auth")) return new[] { "Auth" };
|
||||||
if (api.RelativePath.StartsWith("api/v1/auth"))
|
if (api.RelativePath.StartsWith("api/v1/admin")) return new[] { $"Admin - {tag}" };
|
||||||
{
|
if (api.RelativePath.StartsWith("api/v1/customer")) return new[] { $"Customer - {tag}" };
|
||||||
return new[] { "Auth" };
|
if (api.RelativePath.StartsWith("api/v1/public")) return new[] { $"Public - {tag}" };
|
||||||
}
|
|
||||||
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 };
|
return new[] { tag };
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- NEU: Sortierung der Gruppen in der UI ---
|
|
||||||
c.OrderActionsBy(apiDesc =>
|
c.OrderActionsBy(apiDesc =>
|
||||||
{
|
{
|
||||||
var relativePath = apiDesc.RelativePath ?? "";
|
var relativePath = apiDesc.RelativePath ?? "";
|
||||||
|
|
||||||
// Sortierung
|
|
||||||
if (relativePath.StartsWith("api/v1/auth")) return "1";
|
if (relativePath.StartsWith("api/v1/auth")) return "1";
|
||||||
if (relativePath.StartsWith("api/v1/admin")) return "2";
|
if (relativePath.StartsWith("api/v1/public")) return "2";
|
||||||
if (relativePath.StartsWith("api/v1/customer")) return "3";
|
if (relativePath.StartsWith("api/v1/customer")) return "3";
|
||||||
if (relativePath.StartsWith("api/v1/public")) return "4";
|
if (relativePath.StartsWith("api/v1/admin")) return "4";
|
||||||
|
return "5";
|
||||||
|
|
||||||
|
|
||||||
return "5"; // Fallback
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// --- 2. JWT Security Konfiguration ---
|
|
||||||
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||||
{
|
{
|
||||||
Description = "JWT Authorization header. Example: \"Authorization: Bearer {token}\"",
|
Description = "JWT Authorization header. Example: \"Authorization: Bearer {token}\"",
|
||||||
@@ -190,38 +165,24 @@ builder.Services.AddSwaggerGen(c =>
|
|||||||
|
|
||||||
c.AddSecurityRequirement(new OpenApiSecurityRequirement
|
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.SchemaFilter<AddExampleSchemaFilter>();
|
||||||
c.OperationFilter<AuthorizeOperationFilter>();
|
c.OperationFilter<AuthorizeOperationFilter>();
|
||||||
|
|
||||||
// Endpunktspezifische Beispiele
|
|
||||||
c.OperationFilter<LoginExampleOperationFilter>();
|
c.OperationFilter<LoginExampleOperationFilter>();
|
||||||
c.OperationFilter<PaymentMethodExampleOperationFilter>();
|
c.OperationFilter<PaymentMethodExampleOperationFilter>();
|
||||||
c.OperationFilter<SupplierExampleOperationFilter>();
|
c.OperationFilter<SupplierExampleOperationFilter>();
|
||||||
c.OperationFilter<ShippingMethodExampleOperationFilter>();
|
c.OperationFilter<ShippingMethodExampleOperationFilter>();
|
||||||
c.OperationFilter<AdminCategorieExampleOperationFilter>();
|
|
||||||
c.OperationFilter<AdminProductExampleOperationFilter>();
|
c.OperationFilter<AdminProductExampleOperationFilter>();
|
||||||
c.OperationFilter<CustomerAddressExampleOperationFilter>();
|
c.OperationFilter<CustomerAddressExampleOperationFilter>();
|
||||||
c.OperationFilter<CustomerOrderExampleOperationFilter>();
|
c.OperationFilter<CustomerOrderExampleOperationFilter>();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
// --- 2. HTTP Request Pipeline Konfiguration ---
|
// --- 2. HTTP Request Pipeline Konfiguration ---
|
||||||
|
|
||||||
var app = builder.Build();
|
|
||||||
|
|
||||||
// F<>hre Datenbank-Migrationen und Initialisierungslogik beim Start aus
|
// F<>hre Datenbank-Migrationen und Initialisierungslogik beim Start aus
|
||||||
using (var scope = app.Services.CreateScope())
|
using (var scope = app.Services.CreateScope())
|
||||||
{
|
{
|
||||||
@@ -231,7 +192,6 @@ using (var scope = app.Services.CreateScope())
|
|||||||
var context = services.GetRequiredService<ApplicationDbContext>();
|
var context = services.GetRequiredService<ApplicationDbContext>();
|
||||||
context.Database.Migrate();
|
context.Database.Migrate();
|
||||||
|
|
||||||
// TEMPOR<4F>RER INITIALER ADMIN- UND KUNDEN-SETUP
|
|
||||||
// HINWEIS: Dieser Block sollte in der Produktion entfernt oder deaktiviert werden.
|
// HINWEIS: Dieser Block sollte in der Produktion entfernt oder deaktiviert werden.
|
||||||
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
|
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
|
||||||
var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
|
var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
|
||||||
@@ -248,58 +208,58 @@ using (var scope = app.Services.CreateScope())
|
|||||||
if (adminUser == null)
|
if (adminUser == null)
|
||||||
{
|
{
|
||||||
adminUser = new ApplicationUser { UserName = "admin@yourwebshop.com", Email = "admin@yourwebshop.com", EmailConfirmed = true, CreatedDate = DateTimeOffset.UtcNow, LastActive = DateTimeOffset.UtcNow };
|
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!");
|
var createAdminResult = await userManager.CreateAsync(adminUser, "SecureAdminPass123!");
|
||||||
if (createAdmin.Succeeded)
|
if (createAdminResult.Succeeded)
|
||||||
{
|
{
|
||||||
await userManager.AddToRoleAsync(adminUser, "Admin");
|
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");
|
var customerUser = await userManager.FindByEmailAsync("customer@yourwebshop.com");
|
||||||
if (customerUser == null)
|
if (customerUser == null)
|
||||||
{
|
{
|
||||||
customerUser = new ApplicationUser { UserName = "customer@yourwebshop.com", Email = "customer@yourwebshop.com", EmailConfirmed = true, CreatedDate = DateTimeOffset.UtcNow, LastActive = DateTimeOffset.UtcNow };
|
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!");
|
var createCustomerResult = await userManager.CreateAsync(customerUser, "SecureCustomerPass12-3!");
|
||||||
if (createCustomer.Succeeded)
|
if (createCustomerResult.Succeeded)
|
||||||
{
|
{
|
||||||
await userManager.AddToRoleAsync(customerUser, "Customer");
|
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))
|
if (!await context.Customers.AnyAsync(c => c.AspNetUserId == customerUser.Id))
|
||||||
{
|
{
|
||||||
var customerProfile = new Customer { AspNetUserId = customerUser.Id, FirstName = "Test", LastName = "Kunde" };
|
var customerProfile = new Customer { AspNetUserId = customerUser.Id, FirstName = "Test", LastName = "Kunde" };
|
||||||
context.Customers.Add(customerProfile);
|
context.Customers.Add(customerProfile);
|
||||||
await context.SaveChangesAsync();
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var logger = services.GetRequiredService<ILogger<Program>>();
|
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
|
// Middleware f<>r Reverse-Proxies
|
||||||
var forwardedHeadersOptions = new ForwardedHeadersOptions
|
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
||||||
{
|
{
|
||||||
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
|
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();
|
app.UseStaticFiles();
|
||||||
|
|
||||||
// Swagger/SwaggerUI f<>r API-Dokumentation aktivieren
|
// Swagger/SwaggerUI f<>r API-Dokumentation aktivieren
|
||||||
//if (app.Environment.IsDevelopment())
|
if (app.Environment.IsDevelopment())
|
||||||
//{
|
{
|
||||||
app.UseSwagger();
|
app.UseSwagger();
|
||||||
app.UseSwaggerUI();
|
app.UseSwaggerUI();
|
||||||
//}
|
}
|
||||||
|
|
||||||
// app.UseHttpsRedirection(); // Auskommentiert f<>r Docker HTTP-Entwicklung
|
// app.UseHttpsRedirection(); // Auskommentiert f<>r Docker HTTP-Entwicklung
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user