Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,15 @@ export class WebhookController {
if (tableName === "users") {
docRef = collection.doc(data.ename);
} else if (tableName === "chats") {
// Check if this is a DM with DM ID in description (already processed)
// This prevents duplicate chat creation when webhooks arrive from eCurrency
const description = data.description || mappedData.description;
if (description && typeof description === 'string' && description.startsWith('DM ID:')) {
console.log("⏭️ Skipping DM creation - this DM has DM ID in description (already processed)");
adapter.addToLockedIds(globalId);
return; // Exit early, don't create new chat
}

// Check for existing DM (2 participants, no name) before creating
const participants = mappedData.participants || [];
const isDM = participants.length === 2 && !mappedData.name;
Expand Down
15 changes: 8 additions & 7 deletions platforms/eCurrency-api/src/services/LedgerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,21 +130,22 @@ export class LedgerService {

// Send transaction notifications for all transfer types
// (USER-to-USER, USER-to-GROUP, GROUP-to-USER, GROUP-to-GROUP)
try {
// Fire-and-forget: don't block the transfer response
setImmediate(() => {
const notificationService = new TransactionNotificationService();
await notificationService.sendTransactionNotifications(
notificationService.sendTransactionNotifications(
amount,
currency,
fromAccountId,
fromAccountType,
toAccountId,
toAccountType,
description
);
} catch (error) {
// Don't fail the transfer if notification fails
console.error("Error sending transaction notifications:", error);
}
).catch(error => {
// Don't fail the transfer if notification fails
console.error("Error sending transaction notifications:", error);
});
});

return { debit, credit };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,12 @@ export class PostgresSubscriber implements EntitySubscriberInterface {
*/
private async sendGroupWebhook(data: any): Promise<void> {
try {
// Skip DMs with DM ID in description (already processed, prevents duplicate webhooks)
if (data.description && typeof data.description === 'string' && data.description.startsWith('DM ID:')) {
console.log(`🔍 Skipping DM webhook - has DM ID in description: ${data.description}`);
return;
}

let globalId = await this.adapter.mappingDb.getGlobalId(data.id);
globalId = globalId ?? "";

Expand Down
118 changes: 74 additions & 44 deletions platforms/pictique-api/src/controllers/WebhookController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,13 @@ export class WebhookController {
}
}
} else if (mapping.tableName === "chats") {
// Check if this is a DM with DM ID in description (already processed)
// This prevents duplicate chat creation when webhooks arrive from eCurrency
if (local.data.description && typeof local.data.description === 'string' && local.data.description.startsWith('DM ID:')) {
console.log("⏭️ Skipping DM creation - this DM has DM ID in description (already processed)");
return res.status(200).send("Skipped DM ID group");
}

let participants: User[] = [];
if (
local.data.participants &&
Expand Down Expand Up @@ -265,30 +272,20 @@ export class WebhookController {
this.adapter.addToLockedIds(localId);
await this.chatService.chatRepository.save(chat);
} else {
// Check for existing DM (2 participants, no name) before creating
const participantIds = participants.map((p) => p.id);
const isDM = participantIds.length === 2 && !local.data.name;

let chat;
if (isDM) {
const existingChat = await this.chatService.findChatByParticipants(participantIds);
let chat: Chat | null = null;

// First, check if a mapping already exists for this globalId
// This prevents duplicates when webhooks arrive simultaneously from multiple platforms
const existingLocalId = await this.adapter.mappingDb.getLocalId(globalId);
if (existingLocalId) {
const existingChat = await this.chatService.findById(existingLocalId);
if (existingChat) {
// Use existing chat and store mapping
console.log(`⚠️ Chat mapping already exists for globalId ${globalId}, using existing chat: ${existingChat.id}`);
chat = existingChat;
chat.name = local.data.name as string;
chat.participants = participants;
chat.admins = admins;
await this.chatService.chatRepository.save(chat);
this.adapter.addToLockedIds(chat.id);
await this.adapter.mappingDb.storeMapping({
localId: chat.id,
globalId: req.body.id,
});
} else {
// Create new chat
chat = await this.chatService.createChat(
local.data.name as string,
participantIds
);
chat.admins = admins;
chat.ename = local.data.ename as string;
await this.chatService.chatRepository.save(chat);
this.adapter.addToLockedIds(chat.id);
await this.adapter.mappingDb.storeMapping({
Expand All @@ -297,17 +294,15 @@ export class WebhookController {
});
}
} else {
// Group chat - check for eCurrency Chat duplicates by name
const chatName = local.data.name as string;
if (chatName && (chatName.startsWith("eCurrency Chat") || chatName.includes("eCurrency Chat"))) {
// Check if chat with this name already exists
const existingChat = await this.chatService.chatRepository.findOne({
where: { name: chatName },
relations: ["participants", "admins"]
});

// Check for existing DM (2 participants, no name) before creating
const participantIds = participants.map((p) => p.id);
const isDM = participantIds.length === 2 && !local.data.name;

let chat;
if (isDM) {
const existingChat = await this.chatService.findChatByParticipants(participantIds);
if (existingChat) {
console.log(`⚠️ eCurrency Chat with name "${chatName}" already exists, using existing: ${existingChat.id}`);
// Use existing chat and store mapping
chat = existingChat;
chat.admins = admins;
await this.chatService.chatRepository.save(chat);
Expand All @@ -319,7 +314,7 @@ export class WebhookController {
} else {
// Create new chat
chat = await this.chatService.createChat(
chatName,
local.data.name as string,
participantIds
);
chat.admins = admins;
Expand All @@ -331,18 +326,53 @@ export class WebhookController {
});
}
} else {
// Regular group chat - always create new
chat = await this.chatService.createChat(
chatName,
participantIds
);
chat.admins = admins;
await this.chatService.chatRepository.save(chat);
this.adapter.addToLockedIds(chat.id);
await this.adapter.mappingDb.storeMapping({
localId: chat.id,
globalId: req.body.id,
});
// Group chat - check for eCurrency Chat duplicates by name
const chatName = local.data.name as string;
if (chatName && (chatName.startsWith("eCurrency Chat") || chatName.includes("eCurrency Chat"))) {
// Check if chat with this name already exists
const existingChat = await this.chatService.chatRepository.findOne({
where: { name: chatName },
relations: ["participants", "admins"]
});

if (existingChat) {
console.log(`⚠️ eCurrency Chat with name "${chatName}" already exists, using existing: ${existingChat.id}`);
chat = existingChat;
chat.admins = admins;
await this.chatService.chatRepository.save(chat);
this.adapter.addToLockedIds(chat.id);
await this.adapter.mappingDb.storeMapping({
localId: chat.id,
globalId: req.body.id,
});
} else {
// Create new chat
chat = await this.chatService.createChat(
chatName,
participantIds
);
chat.admins = admins;
await this.chatService.chatRepository.save(chat);
this.adapter.addToLockedIds(chat.id);
await this.adapter.mappingDb.storeMapping({
localId: chat.id,
globalId: req.body.id,
});
}
} else {
// Regular group chat - always create new
chat = await this.chatService.createChat(
chatName,
participantIds
);
chat.admins = admins;
await this.chatService.chatRepository.save(chat);
this.adapter.addToLockedIds(chat.id);
await this.adapter.mappingDb.storeMapping({
localId: chat.id,
globalId: req.body.id,
});
}
}
}
}
Expand Down