feat: complete mealplanner app (backend + frontend + deployment)

.NET 8 backend with Zitadel JWT auth, TheMealDB integration,
weekly meal plan generation, shopping list aggregation.
Vue 3 + Tailwind 4 frontend with dark emerald theme,
manual OIDC PKCE auth, all views implemented.
Multi-stage Dockerfile with nginx reverse proxy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 19:10:10 +00:00
parent 660bcd1953
commit f58782774b
51 changed files with 4061 additions and 0 deletions

70
backend/Program.cs Normal file
View File

@@ -0,0 +1,70 @@
using Microsoft.EntityFrameworkCore;
using MealPlanner.Data;
using MealPlanner.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.AddJsonOptions(o =>
{
o.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles;
o.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter());
});
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
// Zitadel JWT Bearer Auth
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = builder.Configuration["Zitadel:Issuer"] ?? "https://auth.kuns.dev";
options.Audience = builder.Configuration["Zitadel:ClientId"] ?? "";
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = false, // Zitadel uses client_id as audience in some configs
ValidateLifetime = true,
NameClaimType = "sub",
};
});
builder.Services.AddAuthorization();
builder.Services.AddHttpClient<TheMealDbClient>();
builder.Services.AddScoped<RecipeService>();
builder.Services.AddScoped<MealPlanService>();
builder.Services.AddScoped<ShoppingListService>();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var allowedOrigin = builder.Configuration["AllowedOrigin"] ?? "https://essen.kuns.dev";
builder.Services.AddCors(options =>
options.AddDefaultPolicy(policy =>
policy.WithOrigins(allowedOrigin, "http://localhost:5173")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()));
builder.Services.AddHealthChecks();
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
db.Database.Migrate();
}
app.UseCors();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapHealthChecks("/health");
app.UseStaticFiles();
app.Run();