chore: initial setup with PostgreSQL, DragonflyDB, and Nginx routing configs

This commit is contained in:
Antigravity AI Agent
2026-05-13 05:12:32 +02:00
commit 19dbbae8b8
551 changed files with 308309 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.liberarSlotAgenda = liberarSlotAgenda;
exports.notificarConflito = notificarConflito;
exports.decrementarScore = decrementarScore;
exports.bloquearAgendamentosAutomaticos = bloquearAgendamentosAutomaticos;
exports.buscarScoreContato = buscarScoreContato;
/**
* Temporal Activities — operações com efeitos colaterais (banco, APIs, I/O).
* Activities são executadas pelo Worker e podem ser re-tentadas automaticamente.
*/
const prisma_1 = require("../database/prisma");
const logger_1 = require("../../config/logger");
// ─── Conflito de Agenda: liberar slot após timeout ────────────────────────────
async function liberarSlotAgenda(input) {
logger_1.logger.info(input, '[Temporal] Slot liberado por timeout — notificando solicitante');
// Aqui você envia mensagem via WhatsApp avisando o solicitante
// Implementação real acoplada ao WhatsAppConnectionManager via NATS
// Por ora, marca no banco como disponível
await prisma_1.prisma.protocol.updateMany({
where: {
contactId: input.solicitanteId,
status: 'WAITING_CLIENT',
},
data: { status: 'OPEN' },
});
}
async function notificarConflito(input) {
logger_1.logger.info(input, '[Temporal] Notificando pessoa B sobre solicitação de slot');
// Dispara mensagem via NATS → WhatsApp handler
}
// ─── Gestão de Reputação ──────────────────────────────────────────────────────
async function decrementarScore(input) {
const contact = await prisma_1.prisma.contact.findUnique({
where: { id: input.contactId },
});
if (!contact)
throw new Error(`Contato ${input.contactId} não encontrado`);
const novoScore = Math.max(0, contact.scoreReputacao - input.pontos);
const restrito = novoScore <= 20;
await prisma_1.prisma.contact.update({
where: { id: input.contactId },
data: {
scoreReputacao: novoScore,
flagRestricao: restrito,
},
});
logger_1.logger.info({ contactId: input.contactId, novoScore, restrito, motivo: input.motivo }, '[Temporal] Score de reputação atualizado');
return { novoScore, restrito };
}
async function bloquearAgendamentosAutomaticos(input) {
await prisma_1.prisma.contact.update({
where: { id: input.contactId },
data: { flagRestricao: true },
});
logger_1.logger.warn({ contactId: input.contactId }, '[Temporal] Contato marcado como RESTRITO — agendamentos automáticos bloqueados');
}
async function buscarScoreContato(contactId) {
const c = await prisma_1.prisma.contact.findUnique({
where: { id: contactId },
select: { scoreReputacao: true, flagRestricao: true },
});
return { score: c?.scoreReputacao ?? 100, restrito: c?.flagRestricao ?? false };
}
+18
View File
@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTemporalClient = getTemporalClient;
const client_1 = require("@temporalio/client");
const env_1 = require("../../config/env");
const logger_1 = require("../../config/logger");
let _client = null;
async function getTemporalClient() {
if (_client)
return _client;
const connection = await client_1.Connection.connect({ address: env_1.env.TEMPORAL_ADDRESS });
_client = new client_1.Client({
connection,
namespace: env_1.env.TEMPORAL_NAMESPACE,
});
logger_1.logger.info({ address: env_1.env.TEMPORAL_ADDRESS }, 'Temporal Client conectado');
return _client;
}
+57
View File
@@ -0,0 +1,57 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Worker do Temporal — roda como processo separado.
* Uso: npx tsx src/infra/temporal/temporalWorker.ts
*/
const worker_1 = require("@temporalio/worker");
const env_1 = require("../../config/env");
const logger_1 = require("../../config/logger");
const activities = __importStar(require("./activities"));
async function runWorker() {
const worker = await worker_1.Worker.create({
workflowsPath: require.resolve('./workflows'),
activities,
taskQueue: env_1.env.TEMPORAL_TASK_QUEUE,
namespace: env_1.env.TEMPORAL_NAMESPACE,
});
logger_1.logger.info({ taskQueue: env_1.env.TEMPORAL_TASK_QUEUE }, '🕰 Temporal Worker iniciado');
await worker.run();
}
runWorker().catch((err) => {
logger_1.logger.error(err, 'Falha fatal no Temporal Worker');
process.exit(1);
});
@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.confirmarSlotSignal = void 0;
exports.conflitoAgendaWorkflow = conflitoAgendaWorkflow;
/**
* Workflow: Conflito de Agenda (1 Hora)
*
* Regra de negócio:
* - Pessoa A solicita o slot que Pessoa B já tem PENDENTE.
* - Sistema marca o conflito como WAITING_B e aguarda 1 hora.
* - Se B confirmar dentro de 1h → slot permanece com B, A é notificada.
* - Se B não responder em 1h → slot é liberado e transferido para A.
*/
const workflow_1 = require("@temporalio/workflow");
const { liberarSlotAgenda, notificarConflito, } = (0, workflow_1.proxyActivities)({
startToCloseTimeout: '30 seconds',
retry: { maximumAttempts: 3 },
});
// Signal enviado quando B confirma o slot
exports.confirmarSlotSignal = (0, workflow_1.defineSignal)('confirmarSlot');
async function conflitoAgendaWorkflow(input) {
let bConfirmou = false;
// Registra o handler do signal de confirmação de B
(0, workflow_1.setHandler)(exports.confirmarSlotSignal, ({ confirmado }) => {
bConfirmou = confirmado;
workflow_1.log.info('Signal recebido de B', { confirmado });
});
// Notifica B sobre a disputa
await notificarConflito({
chatIdB: input.chatIdDetentor,
solicitanteNome: input.solicitanteId,
tituloSlot: input.tituloSlot,
});
workflow_1.log.info('Aguardando confirmação de B por 1 hora', { tituloSlot: input.tituloSlot });
// Aguarda signal de B OU timeout de 1 hora
const bRespondeu = await (0, workflow_1.condition)(() => bConfirmou !== false, '1 hour');
if (bRespondeu && bConfirmou) {
workflow_1.log.info('B confirmou o slot — slot mantido com B');
return { resultado: 'MANTIDO_B' };
}
// B não respondeu ou recusou → libera para A
workflow_1.log.info('B não confirmou em 1 hora — liberando slot para A');
await liberarSlotAgenda({
solicitanteId: input.solicitanteId,
tituloSlot: input.tituloSlot,
chatIdSolicitante: input.chatIdSolicitante,
});
return { resultado: 'LIBERADO_A' };
}
+10
View File
@@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.encerrarMonitoramentoSignal = exports.registrarInfracaoSignal = exports.reputacaoWorkflow = exports.confirmarSlotSignal = exports.conflitoAgendaWorkflow = void 0;
var conflitoAgendaWorkflow_1 = require("./conflitoAgendaWorkflow");
Object.defineProperty(exports, "conflitoAgendaWorkflow", { enumerable: true, get: function () { return conflitoAgendaWorkflow_1.conflitoAgendaWorkflow; } });
Object.defineProperty(exports, "confirmarSlotSignal", { enumerable: true, get: function () { return conflitoAgendaWorkflow_1.confirmarSlotSignal; } });
var reputacaoWorkflow_1 = require("./reputacaoWorkflow");
Object.defineProperty(exports, "reputacaoWorkflow", { enumerable: true, get: function () { return reputacaoWorkflow_1.reputacaoWorkflow; } });
Object.defineProperty(exports, "registrarInfracaoSignal", { enumerable: true, get: function () { return reputacaoWorkflow_1.registrarInfracaoSignal; } });
Object.defineProperty(exports, "encerrarMonitoramentoSignal", { enumerable: true, get: function () { return reputacaoWorkflow_1.encerrarMonitoramentoSignal; } });
@@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.encerrarMonitoramentoSignal = exports.registrarInfracaoSignal = void 0;
exports.reputacaoWorkflow = reputacaoWorkflow;
/**
* Workflow: Gestão de Reputação de Contato
*
* Regra de negócio:
* - Cada falta/infração reduz o score_reputacao do contato.
* - Score ≤ 20 → contato entra em flag_restricao = true.
* - Agendamentos automáticos são bloqueados para contatos RESTRITOS.
* - Score é recuperável manualmente pelo agente (fora deste workflow).
*/
const workflow_1 = require("@temporalio/workflow");
const { decrementarScore, bloquearAgendamentosAutomaticos, buscarScoreContato, } = (0, workflow_1.proxyActivities)({
startToCloseTimeout: '30 seconds',
retry: { maximumAttempts: 3 },
});
// Signal para registrar uma nova infração
exports.registrarInfracaoSignal = (0, workflow_1.defineSignal)('registrarInfracao');
// Signal para encerrar o monitoramento (contato reabilitado manualmente)
exports.encerrarMonitoramentoSignal = (0, workflow_1.defineSignal)('encerrarMonitoramento');
async function reputacaoWorkflow(input) {
let encerrado = false;
const infracoesPendentes = [];
(0, workflow_1.setHandler)(exports.registrarInfracaoSignal, (payload) => {
workflow_1.log.info('Infração registrada via signal', payload);
infracoesPendentes.push(payload);
});
(0, workflow_1.setHandler)(exports.encerrarMonitoramentoSignal, () => {
encerrado = true;
});
// Loop durável — o Temporal mantém o estado mesmo após reinícios
while (!encerrado) {
// Aguarda próxima infração ou encerramento
await (0, workflow_1.condition)(() => infracoesPendentes.length > 0 || encerrado);
if (encerrado)
break;
const infracao = infracoesPendentes.shift();
const { novoScore, restrito } = await decrementarScore({
contactId: input.contactId,
motivo: infracao.motivo,
pontos: infracao.pontos,
});
workflow_1.log.info('Score atualizado', { novoScore, restrito, contactId: input.contactId });
if (restrito) {
await bloquearAgendamentosAutomaticos({ contactId: input.contactId });
workflow_1.log.warn('Contato RESTRITO — agendamentos automáticos bloqueados', {
contactId: input.contactId,
scoreAtual: novoScore,
});
// Continua monitorando (restrição é revertida manualmente pelo agente)
}
}
workflow_1.log.info('Monitoramento de reputação encerrado', { contactId: input.contactId });
}