Source

decorators.ts

import { Metadata } from "@decaf-ts/decoration";
import { Delete, Get, Patch, Post, Put } from "@nestjs/common";
import { ApiExcludeEndpoint } from "@nestjs/swagger";
import { apply } from "@decaf-ts/reflection";
import { CrudOperations, OperationKeys } from "@decaf-ts/db-decorators";
import { ModelConstructor } from "@decaf-ts/decorator-validation";

export type HttpVerb = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";

function isOperationBlocked(
  ModelConstructor: ModelConstructor<any>,
  op: CrudOperations
): boolean {
  const { handler, args } = (Metadata.get(
    ModelConstructor as any,
    OperationKeys.REFLECT + OperationKeys.BLOCK
  ) || {}) as {
    handler: (
      operations: CrudOperations[],
      operation: CrudOperations
    ) => boolean;
    args: any[];
  };

  // @ts-ignore
  return !handler ? false : (handler(...args, op) ?? false);
}

/**
 * Decorador de rota condicional:
 * - Se bloqueado: aplica somente `@ApiExcludeEndpoint()` → Swagger oculta; e como NÃO aplica @Get/@Post/etc, Nest NÃO registra rota.
 * - Se permitido: aplica o decorador HTTP correspondente.
 */
export function ApiOperationFromModel(
  ModelConstructor: ModelConstructor<any>,
  verb: HttpVerb,
  path?: string
): MethodDecorator {
  const httpToCrud: Record<
    HttpVerb,
    [CrudOperations, (path?: string) => MethodDecorator]
  > = {
    GET: [OperationKeys.READ, Get],
    POST: [OperationKeys.CREATE, Post],
    PUT: [OperationKeys.UPDATE, Put],
    PATCH: [OperationKeys.UPDATE, Patch],
    DELETE: [OperationKeys.DELETE, Delete],
  };

  const [crudOp, decorator] = httpToCrud[verb];
  return isOperationBlocked(ModelConstructor, crudOp)
    ? apply(ApiExcludeEndpoint())
    : apply(decorator(path));
}