Skip to content

Commit a1393b6

Browse files
committed
fix(OnWhatsappCache): Prevent unique constraint errors and optimize DB writes
Refactors the cache-saving logic to prevent `Unique constraint failed` errors. This issue occurs when an item's `remoteJid` is not yet included in the `jidOptions` of the existing record. The database query now uses an `OR` condition to find a matching record by either `jidOptions` (using `contains`) or by the `remoteJid` itself in a single query. Additionally, this commit introduces several performance optimizations: 1. **Skip Unnecessary Updates**: The function now performs a deep comparison between the new payload and the `existingRecord`. An `update` operation is only executed if the data has actually changed, reducing unnecessary database writes. 2. **Parallel Processing**: The sequential `for...of` loop has been replaced with `Promise.allSettled`. This allows all items in the `data` array to be processed concurrently, significantly speeding up execution for batch inputs. 3. **Data Consistency**: The JIDs in `jidOptions` are now sorted alphabetically before being joined into a string. This ensures that the change-detection logic is accurate, regardless of the order in which JIDs were discovered. 4. **Refactor**: Simplified JID unification logic using a `Set` and introduced a `normalizeJid` helper function for cleaner code. TODO: Investigate the root cause of why `remoteJid` is sometimes not present in `jidOptions` upon initial discovery.
1 parent c555048 commit a1393b6

File tree

1 file changed

+79
-45
lines changed

1 file changed

+79
-45
lines changed

src/utils/onWhatsappCache.ts

Lines changed: 79 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -65,78 +65,112 @@ interface ISaveOnWhatsappCacheParams {
6565
lid?: 'lid' | undefined;
6666
}
6767

68+
function normalizeJid(jid: string | null | undefined): string | null {
69+
if (!jid) return null;
70+
return jid.startsWith('+') ? jid.slice(1) : jid;
71+
}
72+
6873
export async function saveOnWhatsappCache(data: ISaveOnWhatsappCacheParams[]) {
69-
if (configService.get<Database>('DATABASE').SAVE_DATA.IS_ON_WHATSAPP) {
70-
for (const item of data) {
71-
const remoteJid = item.remoteJid.startsWith('+') ? item.remoteJid.slice(1) : item.remoteJid;
74+
if (!configService.get<Database>('DATABASE').SAVE_DATA.IS_ON_WHATSAPP) {
75+
return;
76+
}
7277

73-
// TODO: Buscar registro existente PRIMEIRO para preservar dados
74-
const allJids = [remoteJid];
78+
// Processa todos os itens em paralelo para melhor performance
79+
const processingPromises = data.map(async (item) => {
80+
try {
81+
const remoteJid = normalizeJid(item.remoteJid);
82+
if (!remoteJid) {
83+
logger.warn('[saveOnWhatsappCache] Item skipped, missing remoteJid.');
84+
return;
85+
}
7586

76-
const altJid =
77-
item.remoteJidAlt && item.remoteJidAlt.includes('@lid')
78-
? item.remoteJidAlt.startsWith('+')
79-
? item.remoteJidAlt.slice(1)
80-
: item.remoteJidAlt
81-
: null;
87+
const altJidNormalized = normalizeJid(item.remoteJidAlt);
88+
const lidAltJid = (altJidNormalized && altJidNormalized.includes('@lid')) ? altJidNormalized : null;
8289

83-
if (altJid) {
84-
allJids.push(altJid);
90+
const baseJids = [remoteJid]; // Garante que o remoteJid esteja na lista inicial
91+
if (lidAltJid) {
92+
baseJids.push(lidAltJid);
8593
}
8694

87-
const expandedJids = allJids.flatMap((jid) => getAvailableNumbers(jid));
95+
const expandedJids = baseJids.flatMap((jid) => getAvailableNumbers(jid));
8896

97+
// 1. Busca entrada por jidOptions e também remoteJid
98+
// Às vezes acontece do remoteJid atual NÃO ESTAR no jidOptions ainda, ocasionando o erro:
99+
// 'Unique constraint failed on the fields: (`remoteJid`)'
100+
// Isso acontece principalmente em grupos que possuem o número do criador no ID (ex.: '[email protected]')
89101
const existingRecord = await prismaRepository.isOnWhatsapp.findFirst({
90102
where: {
91-
OR: expandedJids.map((jid) => ({ jidOptions: { contains: jid } })),
103+
OR: [
104+
...expandedJids.map((jid) => ({ jidOptions: { contains: jid } })),
105+
{ remoteJid: remoteJid }, // TODO: Descobrir o motivo que causa o remoteJid não estar (às vezes) incluso na lista de jidOptions
106+
],
92107
},
93108
});
94109

95-
logger.verbose(`Register exists: ${existingRecord ? existingRecord.remoteJid : 'não not found'}`);
96-
97-
const finalJidOptions = [...expandedJids];
110+
logger.verbose(`[saveOnWhatsappCache] Register exists for [${expandedJids.join(",")}]? => ${existingRecord ? existingRecord.remoteJid : 'Not found'}`);
111+
112+
// 2. Unifica todos os JIDs usando um Set para garantir valores únicos
113+
const finalJidOptions = new Set(expandedJids);
98114

99-
if (existingRecord?.jidOptions) {
100-
const existingJids = existingRecord.jidOptions.split(',');
101-
// TODO: Adicionar JIDs existentes que não estão na lista atual
102-
existingJids.forEach((jid) => {
103-
if (!finalJidOptions.includes(jid)) {
104-
finalJidOptions.push(jid);
105-
}
106-
});
115+
if (lidAltJid) {
116+
finalJidOptions.add(lidAltJid);
107117
}
108-
109-
// TODO: Se tiver remoteJidAlt com @lid novo, adicionar
110-
if (altJid && !finalJidOptions.includes(altJid)) {
111-
finalJidOptions.push(altJid);
118+
119+
if (existingRecord?.jidOptions) {
120+
existingRecord.jidOptions.split(',').forEach(jid => finalJidOptions.add(jid));
112121
}
113122

114-
const uniqueNumbers = Array.from(new Set(finalJidOptions));
123+
// 3. Prepara o payload final
124+
// Ordena os JIDs para garantir consistência na string final
125+
const sortedJidOptions = [...finalJidOptions].sort();
126+
const newJidOptionsString = sortedJidOptions.join(',');
127+
const newLid = item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null;
115128

116-
logger.verbose(
117-
`Saving: remoteJid=${remoteJid}, jidOptions=${uniqueNumbers.join(',')}, lid=${item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null}`,
118-
);
129+
const dataPayload = {
130+
remoteJid: remoteJid,
131+
jidOptions: newJidOptionsString,
132+
lid: newLid,
133+
};
119134

135+
// 4. Decide entre Criar ou Atualizar
120136
if (existingRecord) {
137+
// Compara a string de JIDs ordenada existente com a nova
138+
const existingJidOptionsString = existingRecord.jidOptions
139+
? existingRecord.jidOptions.split(',').sort().join(',')
140+
: '';
141+
142+
const isDataSame = existingRecord.remoteJid === dataPayload.remoteJid &&
143+
existingJidOptionsString === dataPayload.jidOptions &&
144+
existingRecord.lid === dataPayload.lid;
145+
146+
if (isDataSame) {
147+
logger.verbose(`[saveOnWhatsappCache] Data for ${remoteJid} is already up-to-date. Skipping update.`);
148+
return; // Pula para o próximo item
149+
}
150+
151+
// Os dados são diferentes, então atualiza
152+
logger.verbose(`[saveOnWhatsappCache] Register exists, updating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}, lid=${dataPayload.lid}`);
121153
await prismaRepository.isOnWhatsapp.update({
122154
where: { id: existingRecord.id },
123-
data: {
124-
remoteJid: remoteJid,
125-
jidOptions: uniqueNumbers.join(','),
126-
lid: item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null,
127-
},
155+
data: dataPayload,
128156
});
157+
129158
} else {
159+
// Cria nova entrada
160+
logger.verbose(`[saveOnWhatsappCache] Register does not exist, creating: remoteJid=${remoteJid}, jidOptions=${dataPayload.jidOptions}, lid=${dataPayload.lid}`);
130161
await prismaRepository.isOnWhatsapp.create({
131-
data: {
132-
remoteJid: remoteJid,
133-
jidOptions: uniqueNumbers.join(','),
134-
lid: item.lid === 'lid' || item.remoteJid?.includes('@lid') ? 'lid' : null,
135-
},
162+
data: dataPayload,
136163
});
137164
}
165+
166+
} catch (e) {
167+
// Loga o erro mas não para a execução dos outros promises
168+
logger.error(`[saveOnWhatsappCache] Error processing item for ${item.remoteJid}: `, e);
138169
}
139-
}
170+
});
171+
172+
// Espera todas as operações paralelas terminarem
173+
await Promise.allSettled(processingPromises);
140174
}
141175

142176
export async function getOnWhatsappCache(remoteJids: string[]) {

0 commit comments

Comments
 (0)