# additionalApis Workflow

## When it applies

The operation you need is a custom verb — `activate`, `approve`, `cancel`, `publish`,
`change-status`, `inherit-permissions`, `check-unique-<field>`, `*-me` variants, and similar — that
does not fit the CRUD vocabulary cleanly. Even though you could technically do the work through
`updateById`, a dedicated endpoint gives the action a clear name, a scoped permission, and room for
domain-specific inputs and outputs.

The solution is **not** to hand-write a controller/resolver/handler. It is to declare the endpoint
in the module's `additionalApis` block, regenerate with `catalyst load`, and refine the scaffold the
CLI produces.

## The three files the CLI scaffolds

For each `additionalApis` entry the CLI emits exactly three files. No DTO or GraphQL schema fragment
is produced specifically for the custom endpoint — it reuses the module-wide types.

| File                                                              | Purpose                          |
| ----------------------------------------------------------------- | -------------------------------- |
| `@api/<bc>/<mod>/controllers/<bc>-<verb>-<mod>.controller.ts`     | REST controller                  |
| `@api/<bc>/<mod>/resolvers/<bc>-<verb>-<mod>.resolver.ts`         | GraphQL resolver                 |
| `@app/<bc>/<mod>/application/<verb>/<bc>-<verb>-<mod>.handler.ts` | Application-layer handler (stub) |

Template source lives at `aurora-catalyst-cli/src/templates/codegen/backend/additional-@api/` and
`.../additional-@app/`.

## The defaults the CLI chooses (which you will usually override)

The CLI has to guess at five things because the YAML only declares the path, resolver type, and HTTP
method. The guesses are conservative but frequently wrong for the real operation.

| Dimension       | CLI default (mutation)                                | CLI default (query)                   |
| --------------- | ----------------------------------------------------- | ------------------------------------- |
| Payload type    | `<Bc>Update<Mod>ByIdInput` / `<Bc>Update<Mod>ByIdDto` | `QueryStatement` (no payload)         |
| Auth permission | `<bc>.<mod>.update`                                   | `<bc>.<mod>.get`                      |
| Return type     | `Promise<boolean>`                                    | `Promise<<Bc><Mod>[]>`                |
| Handler body    | `return true;`                                        | `return [];`                          |
| Constructor     | empty with `// TODO: inject services`                 | empty with `// TODO: inject services` |

Tenancy and auditing branches the CLI also emits conditionally:

- `hasTenant: true` → the handler, resolver, and controller receive `account: IamAccountResponse` as
  the first argument; the resolver/controller carry `@TenantPolicy()` (mutation) or
  `@TenantConstraint()` (query).
- `hasAuditing: true` and mutation → the resolver/controller add
  `@Auditing() auditing?: AuditingMeta` and forward it as `repositoryOptions: { auditing }` into the
  handler meta.
- The module has i18n properties → `@ContentLanguage()` is added and forwarded as
  `meta: { contentLanguage }`.

## Anchor example: `iam/role/inherit-permissions-role`

This is the canonical scaffolded `additionalApis` on disk today. The YAML declares:

```yaml
# cliter/iam/role.aurora.yaml
additionalApis:
  - path: iam/role/inherit-permissions-role
    resolverType: mutation
    httpMethod: post
```

The CLI emits three files with the shape shown below.

### Resolver scaffold

```typescript
// @api/iam/role/resolvers/iam-inherit-permissions-role-role.resolver.ts
@Resolver()
@Auth('iam.role.update')
export class IamInheritPermissionsRoleRoleResolver {
  constructor(private readonly handler: IamInheritPermissionsRoleRoleHandler) {}

  @Mutation('iamInheritPermissionsRoleRole')
  async main(
    @Args('payload') payload: IamUpdateRoleByIdInput,
    @Args('constraint') constraint?: QueryStatement,
    @Timezone() timezone?: string,
    @Auditing() auditing?: AuditingMeta,
  ): Promise<boolean> {
    return await this.handler.main(payload, constraint, {
      timezone,
      repositoryOptions: { auditing },
    });
  }
}
```

### Controller scaffold

```typescript
// @api/iam/role/controllers/iam-inherit-permissions-role-role.controller.ts
@ApiTags('[iam] role')
@Controller('iam/role/inherit-permissions-role')
@Auth('iam.role.update')
export class IamInheritPermissionsRoleRoleController {
  constructor(private readonly handler: IamInheritPermissionsRoleRoleHandler) {}

  @Post()
  @ApiOperation({ summary: 'inherit-permissions-role role' })
  @ApiCreatedResponse({
    description: 'The operation has been successfully executed.',
    type: Boolean,
  })
  async main(
    @Body() payload: IamUpdateRoleByIdDto,
    @Body('constraint') constraint?: QueryStatement,
    @Timezone() timezone?: string,
    @Auditing() auditing?: AuditingMeta,
  ) {
    return await this.handler.main(payload, constraint, {
      timezone,
      repositoryOptions: { auditing },
    });
  }
}
```

### Handler scaffold

```typescript
// @app/iam/role/application/inherit-permissions-role/iam-inherit-permissions-role-role.handler.ts
@Injectable()
export class IamInheritPermissionsRoleRoleHandler {
  constructor() {
    // TODO: inject services needed for this operation
  }

  async main(
    payload: IamUpdateRoleByIdInput | IamUpdateRoleByIdDto,
    constraint?: QueryStatement,
    handlerMeta?: HandlerMeta,
  ): Promise<boolean> {
    // TODO: implement business logic
    return true;
  }
}
```

Everything above is generated output. Your job is to refine it.

## The five refinement phases (in order)

### Phase 1 — Payload type

The CLI picks `<Bc>Update<Mod>ByIdInput` / `<Bc>Update<Mod>ByIdDto` for mutations because those
types always exist for any module. They are almost never the right shape for a custom action. For
`inherit-permissions-role`, the real action probably needs a payload like
`{ targetRoleId: string; sourceRoleId: string }` — nothing to do with `UpdateRoleByIdInput`.

Options:

1. **Reuse an existing type** that already captures the shape you need.
2. **Define a new input/DTO** specifically for the action. This usually means:
   - Add a type definition to the module's GraphQL schema (`@api/<bc>/<mod>/graphql/*.graphql`).
   - Add a matching DTO class under `@api/<bc>/<mod>/dto/` with the appropriate class-validator
     decorators.
   - Update the resolver's `@Args('payload')` type.
   - Update the controller's `@Body()` type.
   - Update the handler signature to accept the new type (or a union with it).
3. **Use primitives** for a single-parameter action. For example, `check-unique-email` might accept
   just `email: string` via `@Args('email')`.

### Phase 2 — Auth permission

The CLI sets `@Auth('<bc>.<mod>.update')` for mutations and `@Auth('<bc>.<mod>.get')` for queries.
If the operation has a distinct authorization profile, register a more specific permission:

- Pick a verb-scoped identifier: `<bc>.<mod>.<action>` — e.g., `iam.role.inheritPermissions`.
- Declare it wherever the module's permissions are seeded.
- Replace `@Auth(...)` on both the resolver and the controller.

### Phase 3 — Return type

The CLI returns `boolean` for mutations and `Entity[]` for queries. Decide what the operation should
actually return:

- **Boolean status** — keep as-is if the client only needs to know success/failure.
- **Updated entity** — return `<Bc><Mod>` (singular). Change the resolver's return type, the
  controller's `@ApiCreatedResponse({ type: ... })`, and the handler's `Promise<...>`.
- **Custom result shape** — define a return type (GraphQL + DTO) and update all three files.

### Phase 4 — Service injection

Replace the empty `constructor()` with the real dependencies. Most custom operations reuse the CRUD
services already generated for the module: `findByIdService`, `updateByIdService`,
`deleteByIdService`. Sometimes a handler of a different module is needed (e.g., the
`check-password-me` action injects the password-verification service from `o-auth`).

Also inject `EventEmitter2` if the operation emits events via `@EmitEvent`.

### Phase 5 — Business logic in `main()`

Replace the `// TODO: implement business logic` stub with the real body. Typical patterns:

- Fetch → validate → mutate → return: same skeleton as an update handler, but with the verb's own
  rules in the middle. The find + NotFoundException pattern from
  [`update-handler.md`](update-handler.md) applies.
- Multi-service orchestration: the handler calls multiple services in sequence. Keep each service
  thin; the orchestration lives here.
- Domain event emission: add `@EmitEvent('<bc>.<mod>.<past-tense>')` above `main()` once the
  operation changes state and other modules should react. Reuse the CRUD event topics is an
  anti-pattern — the topic name is the semantic contract.

## Illustrative refinement (synthesized)

Continuing with `inherit-permissions-role`, a realistic completion looks like:

```typescript
// Refined handler
@Injectable()
export class IamInheritPermissionsRoleRoleHandler {
  constructor(
    private readonly eventEmitter: EventEmitter2,
    private readonly findByIdService: IamFindRoleByIdService,
    private readonly updateByIdService: IamUpdateRoleByIdService,
    private readonly findPermissionsByRoleService: IamFindPermissionsByRoleService,
  ) {}

  @EmitEvent('iam.role.permissionsInherited')
  async main(
    payload: IamInheritPermissionsPayload, // new type, not IamUpdateRoleByIdInput
    constraint?: QueryStatement,
    handlerMeta?: HandlerMeta,
  ): Promise<IamRole> {
    const sourceRole = await this.findByIdService.main(
      payload.sourceRoleId,
      constraint,
      handlerMeta,
    );
    if (!sourceRole) {
      throw new NotFoundException(`Source IamRole with id: ${payload.sourceRoleId}, not found`);
    }

    const targetRole = await this.findByIdService.main(
      payload.targetRoleId,
      constraint,
      handlerMeta,
    );
    if (!targetRole) {
      throw new NotFoundException(`Target IamRole with id: ${payload.targetRoleId}, not found`);
    }

    const sourcePermissions = await this.findPermissionsByRoleService.main(
      payload.sourceRoleId,
      constraint,
      handlerMeta,
    );

    await this.updateByIdService.main(
      { id: payload.targetRoleId, permissions: sourcePermissions },
      constraint,
      handlerMeta,
    );

    return await this.findByIdService.main(payload.targetRoleId, constraint, handlerMeta);
  }
}
```

This is illustrative — no real implementation lives in `iam/role` today. Update the example here
when one lands.

## Mutation vs query — what differs

The refinement phases are identical in structure, but the defaults and the resolver decorator
differ.

| Aspect                  | Mutation                                    | Query                            |
| ----------------------- | ------------------------------------------- | -------------------------------- |
| Resolver decorator      | `@Mutation('<verb><Bc><Mod>')`              | `@Query('<verb><Bc><Mod>')`      |
| Controller HTTP         | Whatever `httpMethod:` says, typically POST | Usually POST with body (not GET) |
| Default payload         | `<Bc>Update<Mod>ByIdInput` / `Dto`          | `QueryStatement`                 |
| Default return          | `Promise<boolean>`                          | `Promise<<Bc><Mod>[]>`           |
| `@Auth` default         | `<bc>.<mod>.update`                         | `<bc>.<mod>.get`                 |
| Tenant decorator        | `@TenantPolicy()`                           | `@TenantConstraint()`            |
| `@Auditing()` parameter | Present (resolver + controller)             | Absent                           |
| Controller `@HttpCode`  | Absent (defaults to 201 Created)            | `@HttpCode(200)`                 |
| Swagger response        | `@ApiCreatedResponse`                       | `@ApiOkResponse`                 |
| Handler default body    | `return true;`                              | `return [];`                     |

## Archetype-specific anti-patterns

- **Hand-writing the three files instead of declaring in `additionalApis` + regenerating.** The CLI
  produces the scaffolds with the right imports, decorators, and wiring. Starting from scratch
  misses those defaults.
- **Leaving the TODO stubs in place in production.** A handler that returns `true` without
  implementing anything passes tests silently and masks the missing logic. If the action is declared
  in YAML but not yet implemented, remove the resolver / controller wiring so the endpoint is not
  exposed — or implement the stub properly.
- **Reusing the CRUD event topic.** If the custom action changes state, emit a verb-specific event
  (e.g., `iam.role.permissionsInherited`) rather than `iam.role.updated`. Listeners depend on the
  verb.
- **Mixing business logic into the service.** Custom operations tempt the developer to fill the
  empty service with orchestration. The split stays the same: handler orchestrates; service
  persists.
- **Forgetting to update all three files when changing the payload type.** The resolver, the
  controller, and the handler must agree. Changing only the handler produces TS errors or, worse,
  runtime unmarshalling errors.

## Reference back to Step 0

If you arrived here and the three scaffold files do not exist yet, you skipped the regenerate step.
Return to Step 0 of `SKILL.md`: declare `additionalApis` in the YAML, run
`catalyst load back module --name=<bc>/<mod> --force`, then come back and refine the output.
