Skip to content

The app rewrite#11

Open
IEvangelist wants to merge 17 commits into
jongalloway:mainfrom
IEvangelist:dapine/rewrite-app-modernization
Open

The app rewrite#11
IEvangelist wants to merge 17 commits into
jongalloway:mainfrom
IEvangelist:dapine/rewrite-app-modernization

Conversation

@IEvangelist

Copy link
Copy Markdown

No description provided.

IEvangelist and others added 17 commits June 3, 2026 10:41
- 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant