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>
This commit is contained in:
2026-08-14 23:38:37 -03:00
co-authored by Claude Sonnet 5
parent d6d7d5fa61
commit f9738e54da
48 changed files with 1470 additions and 64 deletions
@@ -29,16 +29,28 @@ export class MasterAlertTemplatesService {
});
}
findAll(query: ListAlertTemplatesQueryDto) {
return this.prismaMaster.alert_templates.findMany({
where: {
tenant_id: null,
...(query.tipo ? { tipo: query.tipo } : {}),
...(query.canal ? { canal: query.canal } : {}),
...(query.ativo === undefined ? {} : { ativo: query.ativo }),
},
orderBy: { criado_em: 'desc' },
});
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) {