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>
106 lines
3.5 KiB
TypeScript
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;
|
|
});
|
|
}
|
|
}
|