Initial commit: YesChef family shopping list and recipe app

Backend (.NET 10 minimal API):
- Vertical slice architecture with feature folders
- Postgres via EF Core with initial migration
- JWT auth with family invite code registration
- REST endpoints for stores, shopping lists, items, recipes
- SignalR hub for real-time list collaboration (per-list groups
  and lists-overview group for live list creation/archival/progress)
- Multi-stage Dockerfile

Frontend (SvelteKit + Svelte 5 runes, Tailwind v4):
- Mobile-first PWA with web manifest and service worker
- Bottom-nav layout, login/register, lists overview, list detail,
  stores management, recipes (list/create/detail with add-to-list)
- SignalR client with reference-counted connection
- Real-time updates on both lists overview and list detail pages

Infrastructure:
- docker-compose.yml with postgres, backend, frontend services
  and Traefik labels for path-based routing (/api, /hubs to backend)
- .env.example with required config

End-to-end tests (Playwright):
- test-e2e.mjs: single-user flow (auth, stores, lists, items, recipes)
- test-e2e-multiuser.mjs: two-user real-time sync coverage

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Josh Rogers
2026-05-06 19:32:39 -05:00
commit 48d30df07b
64 changed files with 5873 additions and 0 deletions
@@ -0,0 +1,51 @@
using Microsoft.EntityFrameworkCore;
using YesChef.Api.Data;
using YesChef.Api.Entities;
namespace YesChef.Api.Features.Stores;
public static class StoreEndpoints
{
public record CreateStoreRequest(string Name, int SortOrder = 0);
public record UpdateStoreRequest(string Name, int SortOrder);
public static RouteGroupBuilder MapStoreEndpoints(this RouteGroupBuilder group)
{
group.MapGet("/", async (YesChefDb db) =>
await db.Stores.OrderBy(s => s.SortOrder).ThenBy(s => s.Name).ToListAsync());
group.MapPost("/", async (CreateStoreRequest request, YesChefDb db) =>
{
var store = new Store { Name = request.Name, SortOrder = request.SortOrder };
db.Stores.Add(store);
await db.SaveChangesAsync();
return Results.Created($"/api/stores/{store.Id}", store);
});
group.MapPut("/{id:int}", async (int id, UpdateStoreRequest request, YesChefDb db) =>
{
var store = await db.Stores.FindAsync(id);
if (store is null) return Results.NotFound();
store.Name = request.Name;
store.SortOrder = request.SortOrder;
await db.SaveChangesAsync();
return Results.Ok(store);
});
group.MapDelete("/{id:int}", async (int id, YesChefDb db) =>
{
var hasLists = await db.ShoppingLists.AnyAsync(l => l.StoreId == id);
if (hasLists) return Results.BadRequest(new { error = "Store has shopping lists. Remove them first." });
var store = await db.Stores.FindAsync(id);
if (store is null) return Results.NotFound();
db.Stores.Remove(store);
await db.SaveChangesAsync();
return Results.NoContent();
});
return group;
}
}