rl-core-api
v0.14.4
Published
Core NestJS: autenticação com 2FA, RBAC, auditoria, notificações e listagens com filtro dinâmico
Maintainers
Readme
rl-core-api
Base NestJS para não recomeçar todo projeto pelo login.
Instala e o sistema já sobe com autenticação (2FA obrigatório por TOTP), RBAC, trilha de auditoria, notificações em WebSocket e listagens paginadas com filtro dinâmico. Você escreve só o domínio.
npm install rl-core-apiUso
// src/app.module.ts
import { Module } from "@nestjs/common";
import { RlCoreModule } from "rl-core-api";
import { ContasModule } from "@/features/contas/contas.module";
import { Conta } from "@/features/contas/infra/schema/conta.schema";
@Module({
imports: [RlCoreModule.forRoot({ entities: [Conta] }), ContasModule],
})
export class AppModule {}// src/main.ts
import { bootstrapCore } from "rl-core-api";
import { AppModule } from "@/app.module";
void bootstrapCore(AppModule, { title: "Controle Pessoal" });// src/data-source.ts — a CLI do TypeORM usa este arquivo
import "dotenv/config";
import { coreEntities, coreMigrations, createCoreDataSource } from "rl-core-api";
import { CreateContas1790000000000 } from "@/migrations/1790000000000-CreateContas";
import { Conta } from "@/features/contas/infra/schema/conta.schema";
export const AppDataSource = createCoreDataSource({
entities: [...coreEntities, Conta],
migrations: [...coreMigrations, CreateContas1790000000000],
});npm run migration:run cria as tabelas do core e as suas, na ordem certa. As
migrations do core vêm no pacote — você nunca copia nem reescreve nenhuma.
O que vem pronto
| | |
|---|---|
| Auth | login, refresh por cookie httpOnly, 2FA obrigatório (TOTP + fallback por e-mail + códigos de backup), primeiro acesso, reset de senha, bloqueio por tentativas, expiração de senha |
| RBAC | resources, actions, scopes, permissions, roles e groups, com guard por resource:action:scope |
| Auditoria | trilha de alterações de dados, log de requisições e log de erros, mantidos indefinidamente (sem retenção automática) |
| Notificações | WebSocket com handshake por cookie ou Bearer, escopo resolvido na leitura |
| Listagens | paginação, ordenação e filtro dinâmico com catálogo por listagem ($AND/$OR aninhados, 17 operadores) |
| Fila | processamento em segundo plano com progresso em tempo real (BullMQ + Redis), opcional por ambiente |
| Infra | config validada com Zod, mailer, agendador, rate-limit, CSRF, filtro global de exceções, logger Winston |
Configuração
O pacote não traz .env — ele declara o que precisa e quebra no boot se
faltar. Cada projeto tem os valores dele (banco próprio, segredo JWT próprio).
Comece pelo .env.example do repositório.
Fila de processamento (opcional)
Sem REDIS_HOST a aplicação sobe sem fila e sem erro — é o padrão.
Declarado, o Redis passa a ser obrigatório: a subida falha se ele não responder,
em vez de a falha aparecer no primeiro job.
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=redis
REDIS_PREFIX=controlx # um Redis para vários projetos: o prefixo os separaConcorrência, tentativas, backoff e retenção não são variáveis de ambiente —
são constantes em queue.constants.ts. São decisão de arquitetura da fila, e um
número desses ajustado no .env de produção sem ninguém revisar é como se
descobre, tarde, por que os jobs empilharam.
O processador é sempre do projeto — o core não conhece o que a feature faz:
@Processor(JOBS_QUEUE, { concurrency: JOB_CONCURRENCY, autorun: false })
export class FunkosImportProcessor extends JobWorkerHost<Payload, Summary> {
constructor(
jobEvents: JobEventsService,
private readonly imports: FunkosImportService,
) {
super(jobEvents);
}
async handle(job: JobContext<Payload>): Promise<Summary> {
return this.imports.run(job.payload, (done, total) => job.report(done, total));
}
}Registre a classe como provider do módulo da feature, atrás do
isRedisConfigured() — sem Redis não há conexão para o worker abrir:
@Module({ providers: isRedisConfigured() ? [FunkosImportProcessor] : [] })
export class FunkosModule {}Na rota, jobs.enqueue(name, payload, { ownerId }) devolve o jobId na hora. O
progresso vai para a sala do dono pela mesma conexão de socket das
notificações (job:progress, job:completed, job:failed), e GET /jobs/:id
responde a quem perdeu o socket.
Quem opera vê tudo pela tela de Filas (QueuesScreen, no rl-core-front):
contagem por estado, o motivo cru da falha, reprocessar e remover — com
queues:read:any e queues:manage:any.
Identidade do sistema
O core não tem nome próprio. APP_NAME é o do projeto, e dele saem o título do
Swagger, o remetente dos emails e o emissor exibido no app autenticador:
| Variável | Para quê | Sem declarar |
|---|---|---|
| APP_NAME | Nome do sistema | Core App |
| APP_LOGO_URL | Logo no cabeçalho dos emails — URL absoluta e pública, porque o cliente de email busca a imagem do servidor dele | só o nome, em texto |
| APP_BRAND_COLOR | Cor dos emails — botões, código 2FA e o nome no cabeçalho. Não é a cor da interface, que vem do tailwind-preset. Escreva entre aspas: APP_BRAND_COLOR="#16A34A", senão o dotenv lê o # como comentário e o valor chega vazio | #007bff |
| TOTP_ISSUER | Emissor no app autenticador | segue o APP_NAME |
| EMAIL_FROM_NAME | Nome do remetente | segue o APP_NAME |
Quem já declarava TOTP_ISSUER ou EMAIL_FROM_NAME continua igual: eles só
caem no APP_NAME quando estão ausentes.
Para acrescentar variáveis suas, estenda o schema:
import { envSchema } from "rl-core-api";
export const appEnvSchema = envSchema.extend({
FUNKO_API_KEY: z.string().min(1),
});Peer dependencies
@nestjs/common, @nestjs/core, @nestjs/platform-express, @nestjs/swagger,
@nestjs/typeorm, class-transformer, class-validator, reflect-metadata,
rxjs, typeorm e zod são peers: precisam ser uma instância só, ou a
injeção de dependência e os decorators param de se enxergar. O npm 7+ instala
sozinho.
Pacotes irmãos
| | |
|---|---|
| rl-core-front | as telas em Next.js |
| rl_core_app | o app Flutter, consumido por tag do git |
Changelog
As três versões andam juntas e saem de uma tag só — o que mudou em cada uma está em CHANGELOG.md.
Licença
MIT © Rodrigo Liberti
