Skip to content

Latest commit

 

History

History
147 lines (105 loc) · 7.81 KB

File metadata and controls

147 lines (105 loc) · 7.81 KB

AGENTS.md

Architecture

.NET Aspire distributed app (net10.0) for recipe/menu management. Menu.AppHost orchestrates all services:

  • MenuApi – Minimal API (Auth0 JWT-secured). Endpoints defined via MapGroup extensions in MenuApi/Recipes/.
  • MenuDB – EF Core MenuDbContext + entity definitions (MenuDB/Data/) + migrations (MenuDB/Migrations/).
  • Menu.MigrationService – BackgroundService that applies EF migrations on startup, then exits. The API (MenuApi) waits for this to complete before starting (WaitForCompletion).
  • RedisAddRedis("cache") resource for caching.
  • Menu.ServiceDefaults / Menu.ApiServiceDefaults – Shared Aspire service defaults (OpenTelemetry, health checks, Swagger).
  • ui/menu-website – Vue 3 + Quasar + Vite frontend (pnpm). Connected to the API via Aspire's AddJavaScriptApp.

Layered Model Pattern

Three distinct model layers — never mix them:

Layer Namespace / Location Purpose
EF Entities MenuDB/Data/ (e.g. RecipeEntity) Database rows; configured in MenuDbContext.OnModelCreating
DB Models MenuApi/DBModel/ (e.g. DBModel.Recipe) Intermediate records using Vogen value objects; returned by repositories
ViewModels MenuApi/ViewModel/ (e.g. ViewModel.Recipe, NewRecipe, FullRecipe) API request/response DTOs

Mapping between layers uses Riok.Mapperly (source-generated, zero-reflection) in MenuApi/MappingProfiles/ViewModelMapper.cs. When adding properties, update the [MapProperty] attributes there.

Vogen Value Objects

Primitive types are wrapped with Vogen (MenuApi/ValueObjects/). Example: RecipeId, RecipeName, IngredientAmount. Assembly-wide defaults in VogenDefaults.cs enable EF Core value converters and Swagger mapping. When creating a new value object:

[ValueObject<int>]
public readonly partial struct MyNewId { }

Repositories must use .Value to unwrap and TypeName.From(x) to wrap.

Key Commands

# Run the full stack (API + SQL container + migrations + UI)
dotnet run --project Menu.AppHost

# EF migrations (always from solution root)
dotnet ef migrations add <Name> --project MenuDB --startup-project MenuApi
dotnet ef migrations remove --project MenuDB --startup-project MenuApi

# Unit tests
dotnet test MenuApi.Tests

# Integration tests (requires Docker for SQL Server container + Auth0 secrets)
dotnet test MenuApi.Integration.Tests

Testing Conventions

  • Unit tests (MenuApi.Tests): xUnit + AutoFixture + FakeItEasy + AwesomeAssertions. Custom ValueObjectSpecimenBuilder in CustomGenerator.cs auto-constructs Vogen types via reflection; use [CustomAutoData] (from CustomAutoDataAttribute.cs) on test methods to wire it up.
  • Integration tests (MenuApi.Integration.Tests): Aspire Testing spins up the full AppHost with a containerised SQL Server. All test classes must use [Collection("API Host Collection")] for sequential execution against a shared host. ShortStringAutoDataAttribute limits string length to fit varchar(50) columns and empties collection properties.
  • Assertions use AwesomeAssertions (.Should()) — not FluentAssertions.

Code Style

  • TreatWarningsAsErrors is enabled in Debug and Release for all projects.
  • StyleCop is configured via MenuApi/stylecop.json.
  • ConfigureAwait(false) is used on all async calls in service/repository layers.
  • Program.cs exposes a public partial class Program for integration test WebApplicationFactory compatibility.

Frontend (ui/menu-website)

Vue 3 SPA using Quasar component library, Vite bundler, and pnpm package manager.

Tech Stack

  • Vue 3 (Composition API) + TypeScript + Pinia (stores) + Vue Router
  • Quasar v2 – UI components, SCSS variables in src/css/quasar.variables.scss
  • TanStack Vue Query – server-state management with query invalidation (src/services/recipe-service.ts)
  • openapi-fetch + openapi-typescript – type-safe API client generated from the backend's OpenAPI spec
  • Auth0 (@auth0/auth0-vue) – authentication; configured in src/boot/auth0.ts
  • Storybook 10 – component stories co-located with components (e.g. *.stories.ts)
  • Vitest (unit, jsdom) + Playwright (e2e in e2e/)

API Client Pattern

Types and HTTP calls are generated from the backend OpenAPI spec — never hand-write API types:

# Regenerate after any API endpoint change (run from ui/menu-website)
pnpm generate-openapi

This reads open-api/menu-api.json (generated by the .NET build) and outputs src/generated/open-api/menu-api.ts. The generated types are consumed by src/services/recipe-api.ts which creates a typed openapi-fetch client with Auth0 bearer-token middleware (auth logic extracted into src/services/auth.ts).

Service Layer Convention

Two-layer pattern for API interaction:

Layer File Purpose
API layer src/services/recipe-api.ts Low-level openapi-fetch calls; exports typed functions (postRecipe, getRecipes, etc.)
Service layer src/services/recipe-service.ts Composable wrapping API calls in TanStack Query hooks (useRecipes, useCreateRecipe); handles cache invalidation

Pages/components consume the service layer, never the API layer directly.

Routing

Routes are split into src/router/public.routes.ts (unauthenticated) and src/router/authenticated.routes.ts (protected by Auth0 authGuard). Both groups use MainLayout.vue as the parent layout.

Component Structure

  • src/components/generic/form/ – reusable form fields (text-field, select-field) with co-located Storybook stories
  • src/components/generic/header/ – reusable header buttons (header-button) with co-located Storybook stories
  • src/components/recipe/ – recipe-specific components (new-recipe-form) and fields/ subfolder for recipe field components
  • src/components/buttons/ – auth and navigation buttons (LoginButton, LogoutButton, ProfileButton, NewRecipeHeaderButton, RecipeListButton)
  • src/pages/ – route-level page components

Key Commands (from ui/menu-website/)

pnpm install              # Install dependencies
pnpm dev                  # Dev server (standalone, port 5173)
pnpm aspire               # Dev server started by Aspire (port 5173)
pnpm build                # Type-check + production build
pnpm test                 # Vitest unit tests
pnpm test:e2e             # Playwright end-to-end tests
pnpm lint                 # ESLint
pnpm lint-fix             # ESLint with auto-fix
pnpm format               # Prettier
pnpm generate-openapi     # Regenerate API types from OpenAPI spec
pnpm storybook            # Storybook dev server (port 6006)

Style & Linting

  • ESLint flat config (eslint.config.ts): Vue recommended + TypeScript type-checked + Vitest + Playwright + Storybook + TanStack Query plugins
  • @typescript-eslint/consistent-type-imports enforced — use import type for type-only imports
  • Prettier for formatting (config in .prettierrc.json), ESLint skips formatting rules via @vue/eslint-config-prettier
  • Path alias: @/src/ (configured in tsconfig.app.json and vite.config.ts)
  • .npmrc sets shamefully-hoist=true (required by Quasar)

Adding a New API Endpoint

  1. Add ViewModel DTOs in MenuApi/ViewModel/.
  2. Add DB model records in MenuApi/DBModel/ (if new data shapes are needed).
  3. Add/update Mapperly mappings in MenuApi/MappingProfiles/ViewModelMapper.cs.
  4. Add repository method (interface in MenuApi/Repositories/I*Repository.cs, impl in *Repository.cs).
  5. Add service method (interface + impl in MenuApi/Services/).
  6. Add the endpoint in the relevant MenuApi/Recipes/*Api.cs file using the MapGroup pattern.
  7. Register new DI services in MenuApi/Program.cs.