Files
SeConectai_Vendas/src/modules/alerts/master-alert-templates.service.ts
T
tiagoandClaude Sonnet 5 f9738e54da Etapa 8 — Personalização whitelabel e ajustes finais
Adiciona upload/remoção de logo e cor primária por tenant (módulo
tenant-profile, armazenamento local atrás de uma interface StorageService
trocável por object storage depois) e um endpoint público de tema para a
tela de login carregar antes da autenticação.

Revisão de segurança: Helmet e CORS explícito (extraídos para
security.util.ts, reutilizado pelos testes e2e que verificam os
cabeçalhos), Swagger em /docs cobrindo todos os módulos via o plugin
oficial, paginação em alert_templates. Corrige um achado real: o reset de
senha logava o token em texto puro desde a Etapa 2 — agora envia por
e-mail de verdade.

Build de produção do Docker validado (imagem sem devDependencies, app e
worker rodando contra Postgres/Redis reais) e README consolidado com o
processo de deploy. Fecha o backlog com um teste e2e do fluxo completo:
master cria tenant → login → customer/produto → venda com parcelas →
parcela paga → dashboard financeiro reflete o pagamento.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 23:38:37 -03:00

106 lines
3.5 KiB
TypeScript

import { Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { writeAuditLog } from '../../common/audit-log.util';
import { PrismaMasterService } from '../../config/prisma-master.service';
import { JwtPayload } from '../auth/types/jwt-payload.type';
import { CreateMasterAlertTemplateDto } from './dto/create-master-alert-template.dto';
import { ListAlertTemplatesQueryDto } from './dto/list-alert-templates.query.dto';
import { UpdateMasterAlertTemplateDto } from './dto/update-master-alert-template.dto';
@Injectable()
export class MasterAlertTemplatesService {
constructor(private readonly prismaMaster: PrismaMasterService) {}
async create(dto: CreateMasterAlertTemplateDto, actor: JwtPayload, ip?: string) {
return this.prismaMaster.$transaction(async (tx) => {
const template = await tx.alert_templates.create({ data: { ...dto, tenant_id: null } });
await writeAuditLog(tx, {
usuario_tipo: 'master',
usuario_id: actor.sub,
acao: 'criar_alert_template_master',
entidade: 'alert_templates',
entidade_id: template.id,
detalhes: dto as unknown as Prisma.InputJsonValue,
ip_origem: ip,
});
return template;
});
}
async findAll(query: ListAlertTemplatesQueryDto) {
const page = query.page ?? 1;
const limit = query.limit ?? 20;
const where: Prisma.alert_templatesWhereInput = {
tenant_id: null,
...(query.tipo ? { tipo: query.tipo } : {}),
...(query.canal ? { canal: query.canal } : {}),
...(query.ativo === undefined ? {} : { ativo: query.ativo }),
};
const [data, total] = await this.prismaMaster.$transaction([
this.prismaMaster.alert_templates.findMany({
where,
orderBy: { criado_em: 'desc' },
skip: (page - 1) * limit,
take: limit,
}),
this.prismaMaster.alert_templates.count({ where }),
]);
return { data, meta: { total, page, limit } };
}
async findOne(id: string) {
const template = await this.prismaMaster.alert_templates.findFirst({
where: { id, tenant_id: null },
});
if (!template) {
throw new NotFoundException('Template não encontrado');
}
return template;
}
async update(id: string, dto: UpdateMasterAlertTemplateDto, actor: JwtPayload, ip?: string) {
await this.findOne(id);
return this.prismaMaster.$transaction(async (tx) => {
const atualizado = await tx.alert_templates.update({ where: { id }, data: dto });
await writeAuditLog(tx, {
usuario_tipo: 'master',
usuario_id: actor.sub,
acao: 'atualizar_alert_template_master',
entidade: 'alert_templates',
entidade_id: id,
detalhes: dto as unknown as Prisma.InputJsonValue,
ip_origem: ip,
});
return atualizado;
});
}
/** Soft delete — `alert_logs.template_id` referencia o template sem CASCADE; exclusão real quebraria o histórico. */
async remove(id: string, actor: JwtPayload, ip?: string) {
await this.findOne(id);
return this.prismaMaster.$transaction(async (tx) => {
const desativado = await tx.alert_templates.update({ where: { id }, data: { ativo: false } });
await writeAuditLog(tx, {
usuario_tipo: 'master',
usuario_id: actor.sub,
acao: 'desativar_alert_template_master',
entidade: 'alert_templates',
entidade_id: id,
ip_origem: ip,
});
return desativado;
});
}
}