Skip to content

Commit 6e9617e

Browse files
authored
fix: duplicate chats being created for eCurrency (#525)
1 parent ebb6a54 commit 6e9617e

File tree

1 file changed

+73
-14
lines changed

1 file changed

+73
-14
lines changed

platforms/eCurrency-api/src/services/GroupService.ts

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,17 @@ export class GroupService {
1818
return null;
1919
}
2020

21-
// Use a more efficient query to find groups with exactly these members
2221
const sortedMemberIds = memberIds.sort();
2322

24-
// For 2-member groups (DMs), use a more efficient query
23+
// For 2-member groups (DMs), use a precise query that ensures exact match
2524
if (sortedMemberIds.length === 2) {
26-
// Use a subquery to find groups where both members are present
25+
// Find groups that are private and have exactly these 2 members
2726
const groups = await this.groupRepository
2827
.createQueryBuilder("group")
2928
.leftJoinAndSelect("group.members", "members")
3029
.where("group.isPrivate = :isPrivate", { isPrivate: true })
3130
.andWhere((qb) => {
31+
// Subquery to find groups where both members are present
3232
const subQuery = qb.subQuery()
3333
.select("gm.group_id")
3434
.from("group_members", "gm")
@@ -42,7 +42,7 @@ export class GroupService {
4242
})
4343
.getMany();
4444

45-
// Filter groups that have exactly the same 2 members
45+
// Filter groups that have exactly the same 2 members (no more, no less)
4646
for (const group of groups) {
4747
if (group.members && group.members.length === 2) {
4848
const groupMemberIds = group.members.map((m: User) => m.id).sort();
@@ -55,15 +55,15 @@ export class GroupService {
5555
}
5656
}
5757

58-
// Fallback to general search for other group sizes
59-
const groups = await this.groupRepository
58+
// Fallback: get all private groups and filter in memory
59+
const allPrivateGroups = await this.groupRepository
6060
.createQueryBuilder("group")
6161
.leftJoinAndSelect("group.members", "members")
6262
.where("group.isPrivate = :isPrivate", { isPrivate: true })
6363
.getMany();
6464

6565
// Filter groups that have exactly the same members (order doesn't matter)
66-
for (const group of groups) {
66+
for (const group of allPrivateGroups) {
6767
if (!group.members || group.members.length !== sortedMemberIds.length) {
6868
continue;
6969
}
@@ -99,16 +99,75 @@ export class GroupService {
9999
bannerUrl?: string,
100100
originalMatchParticipants?: string[],
101101
): Promise<Group> {
102-
// For eCurrency Chat groups, check if a DM already exists between these users
103-
// This prevents duplicate chat creation in race conditions
102+
// For eCurrency Chat groups, use a transaction to prevent race conditions
104103
if (isPrivate && (name.startsWith("eCurrency Chat") || name.includes("eCurrency Chat")) && memberIds.length === 2) {
105-
const existingDM = await this.findGroupByMembers(memberIds);
106-
if (existingDM) {
107-
console.log(`⚠️ DM already exists between users ${memberIds.join(", ")}, returning existing DM: ${existingDM.id}`);
108-
return existingDM;
109-
}
104+
return await AppDataSource.transaction(async (transactionalEntityManager) => {
105+
// Check again within transaction to prevent race conditions
106+
const sortedMemberIds = memberIds.sort();
107+
const existingGroups = await transactionalEntityManager
108+
.createQueryBuilder(Group, "group")
109+
.leftJoinAndSelect("group.members", "members")
110+
.where("group.isPrivate = :isPrivate", { isPrivate: true })
111+
.andWhere((qb) => {
112+
const subQuery = qb.subQuery()
113+
.select("gm.group_id")
114+
.from("group_members", "gm")
115+
.where("gm.user_id IN (:...memberIds)", {
116+
memberIds: sortedMemberIds
117+
})
118+
.groupBy("gm.group_id")
119+
.having("COUNT(DISTINCT gm.user_id) = :memberCount", { memberCount: 2 })
120+
.getQuery();
121+
return "group.id IN " + subQuery;
122+
})
123+
.getMany();
124+
125+
// Check if any group has exactly these 2 members
126+
for (const group of existingGroups) {
127+
if (group.members && group.members.length === 2) {
128+
const groupMemberIds = group.members.map((m: User) => m.id).sort();
129+
if (groupMemberIds.length === sortedMemberIds.length &&
130+
groupMemberIds.every((id: string, index: number) => id === sortedMemberIds[index])) {
131+
console.log(`⚠️ DM already exists between users ${memberIds.join(", ")}, returning existing DM: ${group.id}`);
132+
return group;
133+
}
134+
}
135+
}
136+
137+
// No existing group found, create new one
138+
const members = await transactionalEntityManager.findBy(User, {
139+
id: In(memberIds),
140+
});
141+
if (members.length !== memberIds.length) {
142+
throw new Error("One or more members not found");
143+
}
144+
145+
const admins = await transactionalEntityManager.findBy(User, {
146+
id: In(adminIds),
147+
});
148+
if (admins.length !== adminIds.length) {
149+
throw new Error("One or more admins not found");
150+
}
151+
152+
const group = transactionalEntityManager.create(Group, {
153+
name,
154+
description,
155+
owner,
156+
charter,
157+
members,
158+
admins,
159+
participants: members,
160+
isPrivate,
161+
visibility,
162+
avatarUrl,
163+
bannerUrl,
164+
originalMatchParticipants: originalMatchParticipants || [],
165+
});
166+
return await transactionalEntityManager.save(Group, group);
167+
});
110168
}
111169

170+
// For non-DM groups, proceed normally
112171
const members = await this.userRepository.findBy({
113172
id: In(memberIds),
114173
});

0 commit comments

Comments
 (0)