AgentSkillsCN

Create Endpoint

创建端点

SKILL.md

🔌 Skill: Criar Endpoint

Quando Usar

Quando precisar adicionar um novo endpoint na API NestJS.

Passos

1. Criar DTO (se necessário)

typescript
// src/modules/[module]/dto/create-[resource].dto.ts
import { IsString, IsEmail, IsOptional, MinLength } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

export class CreateResourceDto {
  @ApiProperty({ 
    description: 'Nome do recurso',
    example: 'Meu Recurso' 
  })
  @IsString()
  @MinLength(2)
  name: string;

  @ApiPropertyOptional({ 
    description: 'Descrição opcional' 
  })
  @IsOptional()
  @IsString()
  description?: string;
}

2. Adicionar no Service

typescript
// src/modules/[module]/[module].service.ts
import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common';
import { PrismaService } from '@/prisma/prisma.service';
import { CreateResourceDto } from './dto/create-resource.dto';

@Injectable()
export class ResourceService {
  constructor(private readonly prisma: PrismaService) {}

  async create(tenantId: string, userId: string, dto: CreateResourceDto) {
    return this.prisma.resource.create({
      data: {
        ...dto,
        tenantId,
        createdById: userId,
      },
    });
  }

  async findAll(tenantId: string, query: PaginationDto) {
    const { page = 1, limit = 20, search } = query;

    const where = {
      tenantId, // SEMPRE filtrar por tenant
      ...(search && {
        name: { contains: search, mode: 'insensitive' as const },
      }),
    };

    const [data, total] = await Promise.all([
      this.prisma.resource.findMany({
        where,
        skip: (page - 1) * limit,
        take: limit,
        orderBy: { createdAt: 'desc' },
      }),
      this.prisma.resource.count({ where }),
    ]);

    return {
      data,
      meta: { total, page, limit, totalPages: Math.ceil(total / limit) },
    };
  }

  async findOne(id: string, tenantId: string) {
    const resource = await this.prisma.resource.findFirst({
      where: { id, tenantId },
    });

    if (!resource) {
      throw new NotFoundException('Recurso não encontrado');
    }

    return resource;
  }

  async update(id: string, tenantId: string, dto: UpdateResourceDto) {
    // Verificar existência e tenant
    await this.findOne(id, tenantId);

    return this.prisma.resource.update({
      where: { id },
      data: dto,
    });
  }

  async remove(id: string, tenantId: string) {
    await this.findOne(id, tenantId);

    return this.prisma.resource.delete({
      where: { id },
    });
  }
}

3. Adicionar no Controller

typescript
// src/modules/[module]/[module].controller.ts
import {
  Controller,
  Get,
  Post,
  Put,
  Delete,
  Body,
  Param,
  Query,
  UseGuards,
} from '@nestjs/common';
import {
  ApiTags,
  ApiOperation,
  ApiBearerAuth,
  ApiResponse,
} from '@nestjs/swagger';
import { JwtAuthGuard } from '@/common/guards/jwt-auth.guard';
import { PermissionsGuard } from '@/common/guards/permissions.guard';
import { RequirePermission } from '@/common/decorators/permissions.decorator';
import { CurrentUser } from '@/common/decorators/current-user.decorator';
import { ResourceService } from './resource.service';
import { CreateResourceDto } from './dto/create-resource.dto';

@ApiTags('Resources')
@ApiBearerAuth()
@Controller('resources')
@UseGuards(JwtAuthGuard, PermissionsGuard)
export class ResourceController {
  constructor(private readonly resourceService: ResourceService) {}

  @Post()
  @ApiOperation({ summary: 'Criar recurso' })
  @ApiResponse({ status: 201, description: 'Recurso criado' })
  @RequirePermission('resource', 'create')
  async create(
    @CurrentUser() user: User,
    @Body() dto: CreateResourceDto,
  ) {
    return this.resourceService.create(user.tenantId, user.id, dto);
  }

  @Get()
  @ApiOperation({ summary: 'Listar recursos' })
  @RequirePermission('resource', 'read', 'all')
  async findAll(
    @CurrentUser() user: User,
    @Query() query: PaginationDto,
  ) {
    return this.resourceService.findAll(user.tenantId, query);
  }

  @Get(':id')
  @ApiOperation({ summary: 'Buscar recurso por ID' })
  @RequirePermission('resource', 'read')
  async findOne(
    @CurrentUser() user: User,
    @Param('id') id: string,
  ) {
    return this.resourceService.findOne(id, user.tenantId);
  }

  @Put(':id')
  @ApiOperation({ summary: 'Atualizar recurso' })
  @RequirePermission('resource', 'update')
  async update(
    @CurrentUser() user: User,
    @Param('id') id: string,
    @Body() dto: UpdateResourceDto,
  ) {
    return this.resourceService.update(id, user.tenantId, dto);
  }

  @Delete(':id')
  @ApiOperation({ summary: 'Remover recurso' })
  @RequirePermission('resource', 'delete')
  async remove(
    @CurrentUser() user: User,
    @Param('id') id: string,
  ) {
    return this.resourceService.remove(id, user.tenantId);
  }
}

4. Registrar no Module

typescript
// src/modules/[module]/[module].module.ts
import { Module } from '@nestjs/common';
import { ResourceController } from './resource.controller';
import { ResourceService } from './resource.service';

@Module({
  controllers: [ResourceController],
  providers: [ResourceService],
  exports: [ResourceService],
})
export class ResourceModule {}

5. Adicionar no AppModule

typescript
// src/app.module.ts
import { ResourceModule } from './modules/resource/resource.module';

@Module({
  imports: [
    // ...
    ResourceModule,
  ],
})
export class AppModule {}

Checklist

  • DTO com validação class-validator
  • Service com lógica de negócio
  • Controller com decorators Swagger
  • Guards: JwtAuthGuard + PermissionsGuard
  • Decorators: @RequirePermission, @CurrentUser
  • Filtro por tenantId em todas as queries
  • Tratamento de erros (NotFoundException, etc)
  • Module registrado no AppModule