---
name: backend-controller
description: >
  Generates the **integration-strata** API controller (generic CRUD under
  /api/v1/integration/, Swagger group "integration") with NavRoute,
  RequirePermission, auto-mapped DTO → Command conversions, and module-scoped
  permission classes. Consumes NuGet packages (SmartStack.Core, SmartStack.Api,
  MediatR). The companion **screen-driven strata** lives in
  /api/screens/... and is generated by `scaffold-screen-controller`.
phase: development/backend
cli: cli/scaffold-controller
allowed-tools: [Read, Glob, Grep, Bash]  # Bash: CLI invocation
---

# Integration Controller — API Layer (generic strata)

Generates the **integration-strata** REST controller for an entity. This is the
controller machine-to-machine consumers talk to: imports/exports, ETL, BI,
third-party integrations, batch jobs, Postman/Swagger exploration, dev tooling.

> **Two strata, one business layer.** SmartStack splits the public surface in two:
>
> | Strata | Generator | Route | Swagger group | Consumer |
> |--------|-----------|-------|---------------|----------|
> | **Integration** | `scaffold-controller` *(this skill)* | `/api/v1/integration/{plural}` | `integration` | Machine-to-machine (ETL, BI, batch, scripts) |
> | **Screens** | `scaffold-screen-controller` *(Phase 2b — fan-out per section)* | `/api/screens/{plural}/{action}` | `screens` | The generated React app — one endpoint per screen, payload shaped from the pagespec |
>
> Both strata call the **same Business layer** (`I{Entity}Service`). Business
> rules never duplicate — only the DTO shape + URL contract differ.

## When to Use

- Phase 2a (API integration) of `/ba-develop`
- When adding a generic CRUD endpoint over an entity for non-UI consumers
- Custom actions / state transitions (archive, validate, …) coming from BA UCs
  are still emitted here — the screens stratum calls them via the same Business
  method, never reimplements them

## Controller Pattern (output)

```csharp
/// <summary>
/// Integration controller — exposes generic CRUD over the entity for
/// machine-to-machine consumers. The screen-driven API lives under
/// /api/screens/... and is generated by scaffold-screen-controller.
/// </summary>
[ApiController]
[ApiExplorerSettings(GroupName = "integration")]
[Route("api/v1/integration/[controller]")]
[NavRoute("hrm.employees")]
[Authorize]
[Produces("application/json")]
public class EmployeesController : ControllerBase
{
    private readonly IEmployeeService _service;
    public EmployeesController(IEmployeeService service) => _service = service;

    [HttpGet]
    [RequirePermission(HrmPermissions.Employees.Read)]
    public async Task<ActionResult<PaginatedResult<EmployeeListDto>>> GetAll(
        [FromQuery] int page = 1, [FromQuery] int pageSize = 20,
        [FromQuery] string? search = null, CancellationToken ct = default)
    {
        return Ok(await _service.GetAllAsync(new GetEmployeesQuery(page, pageSize, search), ct));
    }

    [HttpPost]
    [RequirePermission(HrmPermissions.Employees.Create)]
    public async Task<ActionResult<Guid>> Create([FromBody] CreateEmployeeDto dto, CancellationToken ct = default)
    {
        var command = new CreateEmployeeCommand(dto.FirstName, dto.LastName);
        var id = await _service.CreateAsync(command, ct);
        return CreatedAtAction(nameof(GetById), new { id }, id);
    }

    [HttpPut("{id:guid}")]
    [RequirePermission(HrmPermissions.Employees.Update)]
    public async Task<ActionResult> Update(Guid id, [FromBody] UpdateEmployeeDto dto, CancellationToken ct = default)
    {
        var command = new UpdateEmployeeCommand(id, dto.FirstName, dto.LastName);
        await _service.UpdateAsync(command, ct);
        return NoContent();
    }
}
```

## ⚠ BLOCKING — Roslyn Convention Rules

**Any generated controller violating these rules MUST fail audit and be rewritten.**

- **SS001**: Must inherit `ControllerBase` + have `[ApiController]`
- **SS002**: Single service injection (no `DbContext`, `IRepository<T>`, `ILogger` as primary deps — use the service layer)
- **SS003**: `[NavRoute]` present, format `module.section` (min 2 segments)
- **SS004**: `[RequirePermission]` on EACH public endpoint (no "forgot" exceptions)
- **SS005**: Return DTOs only — NEVER domain entities (`Employee`, `Order` etc.). Use `EmployeeListDto`, `OrderDetailDto`.
- **SS006**: `CancellationToken ct` as the LAST parameter of every endpoint
- **SS007**: `NavRoute` unique project-wide — grep before generating to ensure no collision
- **SS008**: Return type is **always** `ActionResult<T>` for responses with a body, `ActionResult` for 204 NoContent. Never raw `IActionResult`.
- **SS009**: No business logic in the controller — call `_service.XxxAsync(...)` and return
- **SS010**: `[FromBody]` / `[FromQuery]` explicit on every DTO parameter (OpenAPI clarity)
- **SS011**: `Create` endpoint returns `CreatedAtAction(nameof(GetById), new { id }, id)` — not `Ok(id)`
- **SS012**: DTO → Command mapping is **fully materialised** in generated code — no `/* map dto fields */` placeholders

## ⚠ BLOCKING — Clean Architecture layers

- Controller (Api layer) imports ONLY from: `Application` (services, DTOs, commands), `Domain` (enums, value types), `SmartStack.Core.Navigation`, `SmartStack.Api.Authorization`. Never from `Infrastructure`.
- Service (`Application` layer) imports from `Domain` + abstractions. Never from `Api` or `Infrastructure` directly — infrastructure implementations are injected via DI.
- Violation → CS error or layer leak flagged by audit.

## Module-scoped Permissions Class Pattern

The CLI emits one Permissions file per section, placed under a module-scoped
namespace so two modules sharing section names never collide:

```csharp
// src/{Ns}.Api/Permissions/Hrm/HrmPermissions.Employees.cs
namespace {Ns}.Api.Permissions.Hrm;

public static class HrmPermissions
{
    public static class Employees
    {
        public const string Access = "hrm.employees.access";
        public const string Read = "hrm.employees.read";
        public const string Create = "hrm.employees.create";
        public const string Update = "hrm.employees.update";
        public const string Delete = "hrm.employees.delete";
    }
}
```

## After generation — MANDATORY

Once the CLI has finished writing the controller + permissions class, invoke
the audit skill. Catches SS001–SS012 violations, missing permissions entries,
DTO-vs-entity confusion, and cross-layer imports:

```
@.claude/skills/development/audit/SKILL.md
Audit the controller + permissions class I just generated at
src/{AppCode}.Api/Controllers/... and the service contract.
```

The audit MUST be green before declaring the generation "done".

## Invocation

```bash
npx tsx skills/development/backend/controller/cli/scaffold-controller/index.ts \
  --spec '{"name":"Employee","module":"hrm","appCode":"MyApp","section":"employees","navRoute":"hrm.employees","fields":[{"name":"firstName","type":"string","required":true},{"name":"lastName","type":"string","required":true}],"projectPath":"/path"}'
```

**Important**: the `fields` array is required — it drives the DTO → Command mapping
generation. Without it, the CLI has no way to know which fields to pass through.
