The app rewrite#11
Open
IEvangelist wants to merge 17 commits into
Open
Conversation
- Deleted knockoutjs package - Removed repositories.config file - Eliminated README.md file containing setup instructions
- Introduced validation.md for end-to-end validation process after editing the AppHost. - Added SKILL.md for playwright-cli, detailing browser automation commands and usage. - Created request-mocking.md to document network request interception and mocking capabilities. - Added running-code.md for executing custom Playwright code for advanced scenarios. - Documented session-management.md for managing multiple isolated browser sessions. - Introduced storage-state.md for managing cookies, localStorage, and sessionStorage. - Added test-generation.md for generating Playwright test code from user interactions. - Created tracing.md for capturing execution traces for debugging and analysis. - Added video-recording.md for capturing browser sessions as video for documentation and verification.
Scaffold the modern app beside the legacy MVC5 source: - File-based Aspire AppHost (AppHost.cs) modeling SQL Server + musicdb - AspireMusic.Data: EF Core model (Genre, Artist, Album, CartItem, Order, OrderDetail, AppUser), DbContext, design-time factory, initial migration - AspireMusic.MigrationService: one-shot worker that migrates and idempotently seeds the classic catalog (15 genres, 303 artists, 456 albums) from the legacy SampleData.cs, then exits - AspireMusic.ServiceDefaults: OTel, health checks, service discovery Validated end-to-end via 'aspire start': SQL healthy, worker finished, catalog seeded. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AspireMusic.Api exposes a read-only catalog over the EF Core model:
- Route groups MapCategoriesApi / MapProductsApi (composed via MapCatalogApi):
GET /api/categories, /api/categories/{id}, /api/categories/{id}/products,
/api/products (category + search filters, paged), /api/products/{id}
- DTOs (CategoryDto, ProductDto, PagedResult<T>), ProblemDetails on errors,
CancellationToken plumbing, AsNoTracking reads
- OpenAPI document + Scalar API reference UI
- Aspire-injected SQL via AddSqlServerDbContext<MusicStoreDbContext>("musicdb")
AppHost wires the api with a dynamic http endpoint (avoids fixed-port clashes on
shared hosts), WithReference(musicdb) and WaitForCompletion(migrations) so it only
serves after schema + seed are in place.
Validated via 'aspire start': health/alive, OpenAPI, Scalar, and all catalog
endpoints return 200 (15 categories, paged/searchable products, 404 ProblemDetails).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AspireMusic.Web is an Astro (output: server, @astrojs/node) + Tailwind v4
storefront that browses the catalog entirely via server-side rendering:
- Server-only API client (src/lib/api.ts) reads API_URL at request time and
fetches the catalog; the browser never calls the API (no service discovery
in the browser). Pages set prerender = false.
- Pages: home (hero + featured + genres), all-products with search + paging,
category product list (paged), product detail. Shared Layout with genre nav,
ProductCard and Pagination components.
AppHost wires the web via the JavaScript hosting integration:
AddViteApp("web", ...) .WithEnvironment("API_URL", api http endpoint)
.WithReference(api).WaitFor(api).PublishAsPackageScript("start")
.WithExternalHttpEndpoints()
The API gains WithHttpHealthCheck("/health") so WaitFor(api) is meaningful.
Astro dev binds Aspire's injected PORT (host + strictPort) for the proxy.
Validated via 'aspire start': installer + web start, all SSR pages return 200,
home shows featured albums/genres, product detail renders live API data.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
API:
- CartEndpoints already mapped GET/POST/PUT/DELETE under /api/cart/{cartId};
add CheckoutEndpoints with POST /api/cart/{cartId}/checkout and
GET /api/orders/{orderId}. Totals are computed server-side from Album.Price
inside an EF execution-strategy transaction; the cart is cleared atomically.
- Wire MapCartApi + MapCheckoutApi into the /api group in Program.cs.
- CartItem now has a unique (CartId, AlbumId) index; migration CartItemUniqueIndex.
Web (Astro SSR BFF):
- Anonymous cart identity via httpOnly cartId cookie set in middleware.ts.
- Server-only cart/checkout/order client methods in lib/api.ts; cart/order types.
- Progressive-enhancement pages (no client JS): /cart (view/update/remove),
/checkout (form + summary), /orders/[id] (receipt). Product detail "Add to
cart" posts a form; Layout header shows a server-rendered cart badge.
Validated via aspire start: migration applied, add/update/checkout/order and
cart-clear verified against both the API and the Astro storefront end-to-end.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add end-to-end authentication built on a Keycloak realm import, with the API
validating bearer tokens and the Astro web app acting as an OIDC BFF so tokens
never reach the browser.
API
- Add Aspire.Keycloak.Authentication; AddKeycloakJwtBearer with audience
musicstore.api, dev-only RequireHttpsMetadata=false and a backchannel cert
bypass for Keycloak's self-signed dev cert.
- Split cart logic into shared CartOperations; anonymous cookie-cart routes
(CartEndpoints) stay open, while checkout/orders and a /me cart move to
authenticated MeEndpoints keyed by the token sub claim. Owner is always the
validated subject, never a route parameter.
- Orders and order receipts are owner-scoped; add cart-merge endpoint.
Web (Astro SSR BFF)
- openid-client auth-code + PKCE flow: lib/auth.ts (lazy discovery with retry,
refresh-token rotation, RP-initiated logout, safe returnTo) and
/auth/{login,callback,logout} routes. Tokens live in the server session.
- Middleware hydrates the signed-in user from the session and refreshes
expiring access tokens; identity-aware api client routes to /me when
authenticated and to the cookie cart when anonymous.
- On login, merge the anonymous cookie cart into the user cart (best-effort,
never blocks login) and rotate the cart cookie. Gate checkout/orders behind
login; add orders history page and header sign in/out + username.
AppHost
- Add Keycloak (fixed https port 8088, realm import, no data volume); inject
the https authority into the API and OIDC_*/NODE_TLS_REJECT_UNAUTHORIZED into
the web for dev trust of the self-signed cert.
Validated via aspire start: anonymous browse+cart, Keycloak login, cart merge,
gated checkout placing an order, order history, and logout.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
API:
- AdminEndpoints.cs: album CRUD plus genre/artist pick lists under
/api/admin/*, secured with RequireAuthorization("admin").
- Validation returns ProblemDetails: required/160-char title, 1024-char
art URL, non-negative price, existing genre/artist, and a unique
{artist, title} guard (self-excluded on update).
- DELETE returns 409 Conflict when the album is referenced by an order
instead of surfacing an EF FK failure as a 500.
- Program.cs: flatten Keycloak realm_access.roles into ClaimTypes.Role
via OnTokenValidated and add the "admin" authorization policy.
- CatalogDtos.cs: NamedRefDto and ProductWriteRequest.
Web (Astro BFF):
- auth.ts: extract realm roles from the access token into AuthSession.
- middleware.ts / env.d.ts: expose user roles on locals.
- api.ts: bearer-authed admin client functions; delete surfaces 409.
- pages/admin (index/new/[id]): list, create, edit, delete with
server-side gating to the administrator role (login redirect when
anonymous, 403 when authenticated without the role) and input
validation.
- Layout.astro: conditional Admin nav link for administrators.
Validated end-to-end via Aspire: authz matrix 401/403/200, create/
update/delete, duplicate 400, 409 on ordered album, and the BFF flow
(admin reaches /admin and creates a product; customer gets 403).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Document the modern .NET 10 / Aspire app first (architecture diagram, resource + project tables, API surface, prerequisites, `aspire start`, demo users, auth model, and migration notes/gaps) while preserving the legacy MVC reference content below. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Differentiate genres visually across the Astro storefront: - Add src/lib/genres.ts: a per-genre color system mapping each known genre to a hand-picked hue + emoji icon, with a deterministic hash fallback for admin-created genres. Uses inline HSL styles (not dynamic Tailwind classes, which JIT would purge). - ProductCard: genre-colored gradient placeholder + icon (replacing the gray note glyph) and a color-coded genre pill. - Home: convert flat "Shop by genre" tiles into colored gradient tiles with genre icons; round cards and richer hover states. - Category page: genre-colored header banner with icon and album count. - Product detail: genre-colored gradient placeholder + colored pill. - Layout: sticky translucent header, gradient logo badge, and genre color dots on the category nav pills. Validated with npm run build (clean), astro check (0 errors), and visual review of home, category, and product pages via aspire start. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Genre theming (genres.ts): - Per-genre saturation "character" + secondary gradient hue so each genre reads as a distinct identity (steely metal, neon electronic, muted classical/country, brass jazz, etc.) - Luminance-targeted accent tokens keep WCAG AA (4.5:1) at any saturation; verified across all genres + 360-hue fallback sweep - blues icon -> music-4 Storefront: - ProductCard: always-visible quick-add (+) button with stretched-link overlay; background fetch, check-flash, in-place header badge bump; single-line title (ellipsis + tooltip) to avoid orphaned wraps - Category hero: "All genres" back button restyled as a clear pill chip; strengthened scrim for AA white-on-gradient - AlbumRow track-number contrast bump (zinc-400 -> zinc-500) - Cart/checkout genre theming + icons API/AppHost: - CartItemDto carries GenreId/GenreName; LoadCartAsync projection - Scalar API reference surfaced as a dashboard resource URL on the api Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Keycloak host port was pinned (originally 8088 to dodge a now-gone
earlier-attempt instance, then 8080). Per the Aspire Keycloak docs a stable
port is only a browser-cookie convenience, not a requirement, so let Aspire
own the port and resolve the browser-facing authority from the resource
endpoint instead. Both the API (JWT issuer validation) and the web BFF
(OIDC login) consume the same ReferenceExpression, so they always agree on
the URL without a literal port.
- AppHost.cs: AddKeycloak("keycloak") with no port; keycloakAuthority is now
ReferenceExpression.Create from keycloak.GetEndpoint("http").
- README.md: replace the pinned https://localhost:8080 references with the
dashboard-assigned URL guidance.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the hand-built WithUrl(ReferenceExpression $"{apiEndpoint}/scalar")
with the idiomatic WithUrlForEndpoint("http", ...) API. It ties the link to
the api http endpoint and resolves the relative "/scalar" path against the
endpoint's runtime host:port, matching the documented Aspire Scalar pattern.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Group Browse all, Cart, theme toggle and auth into a right-aligned cluster so they line up; collapse the signed-out Sign in pill to a compact lucide log-in icon button; move the genre-rail scroll chevrons from a 6px to a 16px inset so they align with the action cluster and genre chips. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Classical and Country used low saturation (50/46), so forcing their warm hues to the AA luminance target produced muddy gray-olive accents and hero gradients in light theme. Raise their saturation (78/76) so the dark-derived tokens read as rich gilded gold and moss-green instead of dirty olive, while staying differentiated from Jazz's brass. Luminance targeting is unchanged, so WCAG AA still holds (verified). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the signed-in nav block with a user-menu dropdown (log-out icon, avatar+chevron trigger, panel with Orders/Admin). Simplify the Cart link to a round icon button with a corner count badge. Extract the quick-add handler into a shared is:inline script in Layout and add always-visible quick-add buttons to category list rows (AlbumRow), removing the now-redundant local script from ProductCard. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the native number input and Update button with a minus/qty/plus pill stepper. Wrap the cart in a data-cart-region and add a progressive-enhancement script that intercepts stepper/remove submits, fetches the POST, and swaps in the fresh server-rendered region plus header badge (server stays authoritative for totals). Decrementing at qty 1 uses intent=remove so quantity 0 is never sent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.