Skip to content

Commit 37ddcd5

Browse files
perf: parallelize notification delivery loops (#765 #766) (#838)
Squash-merged via automation. Closes the linked issue(s).
1 parent 2224f1d commit 37ddcd5

3 files changed

Lines changed: 93 additions & 94 deletions

File tree

‎src/notifications/notifications.service.ts‎

Lines changed: 44 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -35,63 +35,50 @@ export class NotificationsService {
3535
{ user: transaction.seller, role: 'Seller' },
3636
];
3737

38-
for (const party of parties) {
39-
const { user, role } = party;
40-
const preferences = user.preferences;
41-
42-
const title = `Transaction ${transaction.status}`;
43-
const message = `Your transaction for property "${transaction.property.title}" has been updated to ${transaction.status}.`;
44-
45-
// 1. In-App Notification
46-
const canInApp = await this.userPreferencesService.shouldDeliverNotification(
47-
user.id,
48-
'TRANSACTION_UPDATE',
49-
'inApp',
50-
);
51-
if (canInApp) {
52-
await this.sendNotification(user.id, title, message, 'TRANSACTION_UPDATE', {
53-
transactionId: transaction.id,
54-
status: transaction.status,
55-
});
56-
}
57-
58-
// 2. Email Notification with template
59-
const canEmail = await this.userPreferencesService.shouldDeliverNotification(
60-
user.id,
61-
'TRANSACTION_UPDATE',
62-
'email',
63-
);
64-
if (canEmail) {
65-
await this.emailService.sendTransactionStatusEmail(user.email, transaction.status, {
66-
transactionId: transaction.id,
67-
propertyTitle: transaction.property.title,
68-
propertyAddress: `${transaction.property.address}, ${transaction.property.city}, ${transaction.property.state} ${transaction.property.zipCode}`,
69-
buyerName: transaction.buyer.firstName
70-
? `${transaction.buyer.firstName} ${transaction.buyer.lastName || ''}`
71-
: transaction.buyer.email,
72-
sellerName: transaction.seller.firstName
73-
? `${transaction.seller.firstName} ${transaction.seller.lastName || ''}`
74-
: transaction.seller.email,
75-
amount: `$${Number(transaction.amount || 0).toLocaleString()}`,
76-
completionDate:
77-
transaction.status === 'COMPLETED' ? new Date().toLocaleDateString() : undefined,
78-
blockchainTxHash: transaction.blockchainTxHash || undefined,
79-
cancellationReason: transaction.cancellationReason || undefined,
80-
cancelledDate:
81-
transaction.status === 'CANCELLED' ? new Date().toLocaleDateString() : undefined,
82-
});
83-
}
84-
85-
// 3. SMS Notification
86-
const canSms = await this.userPreferencesService.shouldDeliverNotification(
87-
user.id,
88-
'TRANSACTION_UPDATE',
89-
'sms',
90-
);
91-
if (canSms && user.phone) {
92-
await this.smsService.sendSms(user.phone, message);
93-
}
94-
}
38+
await Promise.all(
39+
parties.map(async ({ user, role }) => {
40+
const title = `Transaction ${transaction.status}`;
41+
const message = `Your transaction for property "${transaction.property.title}" has been updated to ${transaction.status}.`;
42+
43+
const [canInApp, canEmail, canSms] = await Promise.all([
44+
this.userPreferencesService.shouldDeliverNotification(user.id, 'TRANSACTION_UPDATE', 'inApp'),
45+
this.userPreferencesService.shouldDeliverNotification(user.id, 'TRANSACTION_UPDATE', 'email'),
46+
this.userPreferencesService.shouldDeliverNotification(user.id, 'TRANSACTION_UPDATE', 'sms'),
47+
]);
48+
49+
await Promise.all([
50+
canInApp
51+
? this.sendNotification(user.id, title, message, 'TRANSACTION_UPDATE', {
52+
transactionId: transaction.id,
53+
status: transaction.status,
54+
})
55+
: Promise.resolve(),
56+
canEmail
57+
? this.emailService.sendTransactionStatusEmail(user.email, transaction.status, {
58+
transactionId: transaction.id,
59+
propertyTitle: transaction.property.title,
60+
propertyAddress: `${transaction.property.address}, ${transaction.property.city}, ${transaction.property.state} ${transaction.property.zipCode}`,
61+
buyerName: transaction.buyer.firstName
62+
? `${transaction.buyer.firstName} ${transaction.buyer.lastName || ''}`
63+
: transaction.buyer.email,
64+
sellerName: transaction.seller.firstName
65+
? `${transaction.seller.firstName} ${transaction.seller.lastName || ''}`
66+
: transaction.seller.email,
67+
amount: `$${Number(transaction.amount || 0).toLocaleString()}`,
68+
completionDate:
69+
transaction.status === 'COMPLETED' ? new Date().toLocaleDateString() : undefined,
70+
blockchainTxHash: transaction.blockchainTxHash || undefined,
71+
cancellationReason: transaction.cancellationReason || undefined,
72+
cancelledDate:
73+
transaction.status === 'CANCELLED' ? new Date().toLocaleDateString() : undefined,
74+
})
75+
: Promise.resolve(),
76+
canSms && user.phone
77+
? this.smsService.sendSms(user.phone, message)
78+
: Promise.resolve(),
79+
]);
80+
}),
81+
);
9582
}
9683

9784
async sendNotification(

‎src/properties/property-expiry.service.ts‎

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -75,23 +75,24 @@ export class PropertyExpiryService {
7575
},
7676
});
7777

78-
for (const property of properties) {
79-
const title = `Property Listing Expiring Soon`;
80-
const message = `Your property "${property.title}" is scheduled to expire in 7 days. Consider renewing it to keep it active.`;
81-
82-
// Send notification to property owner
83-
await this.notificationsService.sendNotification(
84-
property.ownerId,
85-
title,
86-
message,
87-
'PROPERTY_EXPIRY_WARNING',
88-
{
89-
propertyId: property.id,
90-
propertyTitle: property.title,
91-
expiryDate: property.expiryDate,
92-
},
93-
);
94-
}
78+
await Promise.all(
79+
properties.map((property) => {
80+
const title = `Property Listing Expiring Soon`;
81+
const message = `Your property "${property.title}" is scheduled to expire in 7 days. Consider renewing it to keep it active.`;
82+
83+
return this.notificationsService.sendNotification(
84+
property.ownerId,
85+
title,
86+
message,
87+
'PROPERTY_EXPIRY_WARNING',
88+
{
89+
propertyId: property.id,
90+
propertyTitle: property.title,
91+
expiryDate: property.expiryDate,
92+
},
93+
);
94+
}),
95+
);
9596
}
9697

9798
/**
@@ -120,23 +121,24 @@ export class PropertyExpiryService {
120121
},
121122
});
122123

123-
for (const property of properties) {
124-
const title = `Property Listing Expired`;
125-
const message = `Your property "${property.title}" has expired due to reaching its expiry date. You can renew it to make it active again.`;
126-
127-
// Send notification to property owner
128-
await this.notificationsService.sendNotification(
129-
property.ownerId,
130-
title,
131-
message,
132-
'PROPERTY_EXPIRED',
133-
{
134-
propertyId: property.id,
135-
propertyTitle: property.title,
136-
expiryDate: property.expiryDate,
137-
},
138-
);
139-
}
124+
await Promise.all(
125+
properties.map((property) => {
126+
const title = `Property Listing Expired`;
127+
const message = `Your property "${property.title}" has expired due to reaching its expiry date. You can renew it to make it active again.`;
128+
129+
return this.notificationsService.sendNotification(
130+
property.ownerId,
131+
title,
132+
message,
133+
'PROPERTY_EXPIRED',
134+
{
135+
propertyId: property.id,
136+
propertyTitle: property.title,
137+
expiryDate: property.expiryDate,
138+
},
139+
);
140+
}),
141+
);
140142
}
141143

142144
/**

‎src/transactions/transaction-documents.service.ts‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,11 @@ export class TransactionDocumentsService {
8080
const tx = await this.ensureTransactionExists(transactionId);
8181
this.assertAccess(tx, userId, userRole);
8282

83-
const doc = await this.findOne(transactionId, documentId, userId, userRole);
83+
const doc = await this.prisma.document.findFirst({
84+
where: { id: documentId, transactionId },
85+
include: { versions: { orderBy: { versionNumber: 'asc' } } },
86+
});
87+
if (!doc) throw new NotFoundException('Document not found for this transaction');
8488

8589
const nextVersion = (doc.versions?.length ?? 0) + 1;
8690

@@ -111,7 +115,10 @@ export class TransactionDocumentsService {
111115
const tx = await this.ensureTransactionExists(transactionId);
112116
this.assertAccess(tx, userId, userRole);
113117

114-
await this.findOne(transactionId, documentId, userId, userRole);
118+
const doc = await this.prisma.document.findFirst({
119+
where: { id: documentId, transactionId },
120+
});
121+
if (!doc) throw new NotFoundException('Document not found for this transaction');
115122
return this.prisma.documentVersion.findMany({
116123
where: { documentId },
117124
orderBy: { versionNumber: 'asc' },
@@ -126,7 +133,10 @@ export class TransactionDocumentsService {
126133
const tx = await this.ensureTransactionExists(transactionId);
127134
this.assertAccess(tx, userId, userRole);
128135

129-
await this.findOne(transactionId, documentId, userId, userRole);
136+
const doc = await this.prisma.document.findFirst({
137+
where: { id: documentId, transactionId },
138+
});
139+
if (!doc) throw new NotFoundException('Document not found for this transaction');
130140
return this.prisma.document.delete({ where: { id: documentId } });
131141
}
132142

0 commit comments

Comments
 (0)