Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions .github/skills/add-api-endpoint/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
name: add-api-endpoint
description: Step-by-step guide for adding a new Minimal API endpoint to MenuApi following the layered model pattern (EF Entity → DBModel → ViewModel, Mapperly, repository, service, MapGroup registration).
user-invokable: true
context: inline
---

# Adding a New API Endpoint

## Purpose

Use this skill when adding any new HTTP endpoint to `MenuApi`. It enforces the layered model pattern and prevents common mistakes such as layer-mixing, missing Mapperly attributes, or unregistered DI services.

## Repository facts

- Backend solution root: `backend/`
- API project: `backend/MenuApi/`
- DB project: `backend/MenuDB/`

## The three model layers

Never mix these layers. Each has a distinct location and purpose:

| Layer | Location | Purpose |
|---|---|---|
| EF Entities | `backend/MenuDB/Data/` | Database rows (e.g. `RecipeEntity`) |
| DB Models | `backend/MenuApi/DBModel/` | Intermediate records with Vogen value objects (e.g. `DBModel.Recipe`) |
| ViewModels | `backend/MenuApi/ViewModel/` | API request/response DTOs (e.g. `ViewModel.Recipe`, `NewRecipe`) |

## Steps

### 1. Add ViewModel DTOs

Create or update files in `backend/MenuApi/ViewModel/`. Each DTO is a C# `class` (not a `record`) to support DataAnnotations such as `[Required]`. Use Vogen value objects for all typed properties (see `add-value-object` skill); never use raw `int`, `string`, or `Guid` for domain identifiers or constrained values.

### 2. Add DB model records (if needed)

If the endpoint requires a new data shape that differs from existing DB models, create the corresponding `record` in `backend/MenuApi/DBModel/`.

### 3. Update Mapperly mappings

Open `backend/MenuApi/MappingProfiles/ViewModelMapper.cs` and add or update `[MapProperty]` attributes for any renamed or new properties. Mapperly is source-generated and zero-reflection; missing attributes cause compile errors that `TreatWarningsAsErrors` will surface immediately.

### 4. Add the repository method

- Declare the method signature on the interface: `backend/MenuApi/Repositories/I<Name>Repository.cs`
- Implement it in: `backend/MenuApi/Repositories/<Name>Repository.cs`
- Use `.Value` to unwrap Vogen types when writing EF queries.
- Use `TypeName.From(x)` to wrap raw values returned from EF into Vogen types.
- Add `ConfigureAwait(false)` on every `await` call.

### 5. Add the service method

- Declare the method on the interface: `backend/MenuApi/Services/I<Name>Service.cs`
- Implement it in: `backend/MenuApi/Services/<Name>Service.cs`
- Services call repositories and apply business rules. Add `ConfigureAwait(false)` on every `await` call.

### 6. Register the endpoint

Add the endpoint in the relevant `backend/MenuApi/Recipes/*Api.cs` file using the `MapGroup` pattern. Use `.Produces<T>(statusCode)` and `.ProducesProblem(statusCode)` to document response types — the existing endpoints do not use `.WithName()` or `.WithOpenApi()`. Example:

```csharp
group.MapGet("/{id}", GetByIdAsync)
.Produces<FullRecipe>(StatusCodes.Status200OK)
.ProducesProblem(StatusCodes.Status404NotFound);
```
Comment thread
dgee2 marked this conversation as resolved.

### 7. Register DI services

Add any new repository and service registrations to `backend/MenuApi/Program.cs`:

```csharp
builder.Services.AddScoped<IRecipeRepository, RecipeRepository>();
builder.Services.AddScoped<IRecipeService, RecipeService>();
```

## Validation

After implementing, run from `backend/`:

```bash
dotnet restore MenuApi.sln
dotnet build MenuApi.sln --configuration Release --no-restore
dotnet test --project MenuApi.Tests/MenuApi.Tests.csproj --configuration Release --no-build
```

Then regenerate the OpenAPI spec and validate the frontend (see `openapi-sync` skill):

```bash
cd ../ui/menu-website
pnpm run generate-openapi
pnpm run lint
pnpm run build
```
79 changes: 79 additions & 0 deletions .github/skills/add-value-object/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
name: add-value-object
description: How to create a new Vogen value object in MenuApi — covering the attribute, assembly-wide defaults, EF Core value converter registration, Swagger mapping, and repository wrapping conventions.
user-invokable: true
context: inline
---

# Adding a New Vogen Value Object

## Purpose

Use this skill when wrapping a primitive type in a Vogen value object. All domain identifiers and constrained values in `MenuApi` must be Vogen types — never raw `int`, `string`, or `Guid`.

## Repository facts

- Value objects live in: `backend/MenuApi/ValueObjects/`
- Assembly-wide Vogen defaults: `backend/MenuApi/VogenDefaults.cs` (note: at the project root, not inside the `ValueObjects/` folder)
- The assembly-level attribute in `VogenDefaults.cs` enables EF Core value converters and Swagger schema mapping automatically for all value objects in the assembly.

## Steps

### 1. Create the value object file

Add a new file in `backend/MenuApi/ValueObjects/`, e.g. `RecipeId.cs`:

```csharp
[ValueObject<int>]
public readonly partial struct RecipeId { }
```

For a string-backed value object:

```csharp
[ValueObject<string>]
public readonly partial struct RecipeName { }
```

Supported backing types include `int`, `long`, `string`, `Guid`, `decimal`, and any other primitive that Vogen supports.

### 2. No additional EF or Swagger registration needed

`VogenDefaults.cs` contains an assembly-level attribute that instructs Vogen to generate EF Core value converters and Swagger schema mappings for every value object in the assembly. No manual registration is required.

### 3. Use the value object in DB models and ViewModels

- **DB models** (`backend/MenuApi/DBModel/`): use the Vogen type directly as the property type.
- **ViewModels** (`backend/MenuApi/ViewModel/`): use the Vogen type for request and response DTOs.
- **EF entities** (`backend/MenuDB/Data/`): store the raw primitive; EF will use the generated value converter automatically.

### 4. Wrap and unwrap in repositories

In repository implementations, convert between the raw EF primitive and the Vogen type:

- **Wrap** (raw → Vogen): `RecipeId.From(entity.Id)`
- **Unwrap** (Vogen → raw): `recipeId.Value`

```csharp
// Wrapping when reading from EF
var recipeId = RecipeId.From(entity.Id);

// Unwrapping when writing an EF query predicate
var entity = await _context.Recipes
.FirstOrDefaultAsync(r => r.Id == recipeId.Value)
.ConfigureAwait(false);
```

### 5. Update Mapperly if needed

If the new value object appears in a mapping profile, open `backend/MenuApi/MappingProfiles/ViewModelMapper.cs` and add the appropriate `[MapProperty]` attribute. Mapperly handles Vogen conversions automatically when both sides use the same Vogen type, but explicit attributes are needed for renamed properties.

## Validation

```bash
cd backend
dotnet restore MenuApi.sln
dotnet build MenuApi.sln --configuration Release --no-restore
```

`TreatWarningsAsErrors` is enabled, so any Mapperly mapping gap or Vogen configuration error will surface as a build failure.
68 changes: 68 additions & 0 deletions .github/skills/code-review-checklist/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
name: code-review-checklist
description: Structured checklist for reviewing pull requests in the Menu repository — layer separation, Vogen coverage, Mapperly attributes, async conventions, and StyleCop compliance.
user-invokable: true
context: inline
---

# Code Review Checklist

## Purpose

Use this skill when reviewing a pull request against the Menu repository. Work through each section in order and flag any item that fails.

## 1. Layer separation

The three model layers must never be mixed:

- [ ] EF entities (`backend/MenuDB/Data/`) are not referenced directly from `MenuApi` controllers or services — only from repository implementations.
- [ ] DB models (`backend/MenuApi/DBModel/`) are not returned from API endpoints — only from repository methods.
- [ ] ViewModels (`backend/MenuApi/ViewModel/`) are not passed into repository methods — only into and out of service methods.
- [ ] No raw EF `DbContext` usage outside of repository implementations.

## 2. Vogen value objects

- [ ] New domain identifiers and constrained values use Vogen types — not raw `int`, `string`, or `Guid`.
- [ ] Repository code uses `.Value` to unwrap and `TypeName.From(x)` to wrap.
- [ ] New value objects are placed in `backend/MenuApi/ValueObjects/`.

## 3. Mapperly mappings

- [ ] Any new or renamed property on a ViewModel or DB model has a corresponding `[MapProperty]` attribute update in `backend/MenuApi/MappingProfiles/ViewModelMapper.cs`.
- [ ] The solution builds without warnings (build with `--configuration Release` to confirm, since `TreatWarningsAsErrors` is enabled).

## 4. Async conventions

- [ ] Every `await` call in service and repository implementations has `ConfigureAwait(false)`.
- [ ] No blocking calls (`.Result`, `.Wait()`) on `Task` in service or repository code.

## 5. Dependency injection

- [ ] New repositories and services are registered in `backend/MenuApi/Program.cs`.
- [ ] Interfaces are used for DI registrations (e.g. `IRecipeService`, not `RecipeService`).

## 6. API endpoints

- [ ] Each new endpoint is documented with `.Produces<T>(statusCode)`, `.ProducesProblem(statusCode)`, and/or `.ProducesValidationProblem()` — do not use `.WithName(...)` or `.WithOpenApi()`.
- [ ] The generated frontend types (`src/generated/open-api/menu-api.ts`) are updated in the same commit as the endpoint change (run `pnpm generate-openapi` in `ui/menu-website/`). Note: `open-api/menu-api.json` is gitignored and should not be committed.

## 7. StyleCop and code style

- [ ] No StyleCop suppressions added without a justifying comment.
- [ ] `using` directives are ordered per StyleCop rules.
- [ ] `public partial class Program` remains in `Program.cs` for integration test `WebApplicationFactory` compatibility.

## 8. Tests

- [ ] New service or repository logic has corresponding unit tests in `MenuApi.Tests`.
- [ ] Unit tests use `[CustomAutoData]` (not bare `[AutoData]`) to handle Vogen specimen construction.
- [ ] Assertions use AwesomeAssertions (`.Should()`) — not xUnit raw assertions or FluentAssertions.
- [ ] New integration tests carry `[Collection("API Host Collection")]`.
- [ ] Integration test string parameters use `[StringLength(N, MinimumLength = 1)]` to avoid `varchar` overflow and `[NoAutoProperties]` to prevent constraint violations from auto-populated collections.

## 9. Frontend (if applicable)

- [ ] No hand-written types in `src/generated/` — types are always generated by `pnpm generate-openapi`.
- [ ] New components have a co-located Storybook story (`*.stories.ts`).
- [ ] Pages and components use the service layer — never the API layer (`recipe-api.ts`) directly.
- [ ] `import type` is used for type-only imports.
65 changes: 65 additions & 0 deletions .github/skills/ef-migration/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
name: ef-migration
description: Canonical commands and safety rules for EF Core migration management in the Menu repository — adding, removing, and applying migrations correctly.
user-invokable: true
context: inline
---

# EF Core Migration Management

## Purpose

Use this skill when adding, removing, or reasoning about EF Core migrations in the Menu repository. The project layout requires specific flags that differ from the EF CLI defaults.

## Repository facts

- Migrations project: `backend/MenuDB` (contains `MenuDbContext` and the `Migrations/` folder)
- Startup project: `backend/MenuApi` (required for EF tool configuration)
- Always run EF CLI commands from the **backend solution root** (`backend/`), not from a project subfolder.

## Commands

### Add a migration

```bash
cd backend
dotnet ef migrations add <MigrationName> --project MenuDB --startup-project MenuApi
```

`<MigrationName>` should be PascalCase and describe the schema change, e.g. `AddRecipeIngredientsTable`.

### Remove the most recent migration (before it is applied)

```bash
cd backend
dotnet ef migrations remove --project MenuDB --startup-project MenuApi
```

Only remove a migration that has **not** been applied to any environment. If the migration is already in the database, revert it first with a new migration.

### List applied and pending migrations

```bash
cd backend
dotnet ef migrations list --project MenuDB --startup-project MenuApi
```

## How migrations are applied at runtime

`Menu.MigrationService` is a `BackgroundService` that applies all pending EF migrations on startup and then exits. `MenuApi` is configured with `WaitForCompletion` on `Menu.MigrationService`, so the API never starts before migrations complete.

**Do not run `dotnet ef database update` against a shared or production database.** Let `Menu.MigrationService` manage migration application at runtime.

## EF entity configuration

Entity mappings live in `backend/MenuDB/Data/`. Column constraints (max length, required, etc.) are configured inside `MenuDbContext.OnModelCreating`. After adding a migration, always inspect the generated migration file to confirm the SQL it will execute matches your intent before committing.

## Validation

After adding a migration, build the solution to confirm there are no EF model snapshot conflicts:

```bash
cd backend
dotnet restore MenuApi.sln
dotnet build MenuApi.sln --configuration Release --no-restore
```
Loading
Loading