feat(notifications): add saved search email alert templates
Add the two missing Handlebars templates (saved_search_alert and saved_search_digest) that are referenced by the real-time event handler and daily digest cron but were never defined, causing a runtime crash. Includes corresponding unit tests. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -39,10 +39,52 @@ describe('TemplateService', () => {
|
||||
expect(service.hasTemplate('unknown.template')).toBe(false);
|
||||
});
|
||||
|
||||
it('getTemplateKeys returns all 9 template keys', () => {
|
||||
it('render returns rendered subject and body for saved_search_alert template', () => {
|
||||
const result = service.render('saved_search_alert', {
|
||||
userName: 'Nguyễn Văn A',
|
||||
searchName: 'Chung cư Quận 7',
|
||||
listingTitle: 'Căn hộ 2PN tầng cao view sông',
|
||||
listingPrice: '3.500.000.000',
|
||||
listingDistrict: 'Quận 7',
|
||||
listingCity: 'Hồ Chí Minh',
|
||||
listingUrl: '/listings/abc123',
|
||||
});
|
||||
|
||||
expect(result.subject).toBe('Tin mới phù hợp tìm kiếm "Chung cư Quận 7"');
|
||||
expect(result.body).toContain('Nguyễn Văn A');
|
||||
expect(result.body).toContain('Chung cư Quận 7');
|
||||
expect(result.body).toContain('Căn hộ 2PN tầng cao view sông');
|
||||
expect(result.body).toContain('3.500.000.000 VNĐ');
|
||||
expect(result.body).toContain('Quận 7');
|
||||
expect(result.body).toContain('/listings/abc123');
|
||||
});
|
||||
|
||||
it('render returns rendered subject and body for saved_search_digest template', () => {
|
||||
const result = service.render('saved_search_digest', {
|
||||
userName: 'Trần Thị B',
|
||||
searchName: 'Villa Thủ Đức',
|
||||
matchCount: 3,
|
||||
listings: [
|
||||
{ title: 'Villa 1', price: '5.000.000.000', district: 'Thủ Đức', city: 'Hồ Chí Minh', url: '/listings/1' },
|
||||
{ title: 'Villa 2', price: '7.000.000.000', district: 'Thủ Đức', city: 'Hồ Chí Minh', url: '/listings/2' },
|
||||
],
|
||||
});
|
||||
|
||||
expect(result.subject).toBe('3 bất động sản mới khớp tìm kiếm "Villa Thủ Đức"');
|
||||
expect(result.body).toContain('Trần Thị B');
|
||||
expect(result.body).toContain('Villa Thủ Đức');
|
||||
expect(result.body).toContain('3');
|
||||
expect(result.body).toContain('Villa 1');
|
||||
expect(result.body).toContain('5.000.000.000 VNĐ');
|
||||
expect(result.body).toContain('Villa 2');
|
||||
expect(result.body).toContain('/listings/1');
|
||||
expect(result.body).toContain('/listings/2');
|
||||
});
|
||||
|
||||
it('getTemplateKeys returns all 11 template keys', () => {
|
||||
const keys = service.getTemplateKeys();
|
||||
|
||||
expect(keys).toHaveLength(9);
|
||||
expect(keys).toHaveLength(11);
|
||||
expect(keys).toContain('user.registered');
|
||||
expect(keys).toContain('agent.verified');
|
||||
expect(keys).toContain('listing.approved');
|
||||
@@ -52,5 +94,7 @@ describe('TemplateService', () => {
|
||||
expect(keys).toContain('password.reset');
|
||||
expect(keys).toContain('payment.confirmed');
|
||||
expect(keys).toContain('subscription.expiring');
|
||||
expect(keys).toContain('saved_search_alert');
|
||||
expect(keys).toContain('saved_search_digest');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -75,6 +75,43 @@ const TEMPLATES: Record<string, TemplateDefinition> = {
|
||||
body: `<h1>Gói đăng ký đã bị huỷ</h1>
|
||||
<p>Gói <strong>{{planTier}}</strong> của bạn đã bị huỷ.</p>
|
||||
<p>Bạn có thể đăng ký lại bất cứ lúc nào để tiếp tục sử dụng đầy đủ tính năng.</p>
|
||||
<p>Trân trọng,<br/>Đội ngũ GoodGo</p>`,
|
||||
},
|
||||
'saved_search_alert': {
|
||||
subject: 'Tin mới phù hợp tìm kiếm "{{searchName}}"',
|
||||
body: `<h1>Xin chào {{userName}}!</h1>
|
||||
<p>Có tin đăng mới phù hợp với tìm kiếm đã lưu <strong>"{{searchName}}"</strong> của bạn:</p>
|
||||
<table style="width:100%;border-collapse:collapse;margin:16px 0;">
|
||||
<tr style="background:#f8f9fa;">
|
||||
<td style="padding:12px;border:1px solid #dee2e6;"><strong>{{listingTitle}}</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding:12px;border:1px solid #dee2e6;">
|
||||
Giá: <strong>{{listingPrice}} VNĐ</strong><br/>
|
||||
Khu vực: {{listingDistrict}}, {{listingCity}}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p><a href="{{listingUrl}}" style="display:inline-block;padding:10px 20px;background:#2563eb;color:#fff;text-decoration:none;border-radius:6px;">Xem chi tiết</a></p>
|
||||
<p style="color:#6b7280;font-size:14px;">Bạn có thể tắt thông báo cho tìm kiếm này trong phần <a href="/saved-searches">Tìm kiếm đã lưu</a>.</p>
|
||||
<p>Trân trọng,<br/>Đội ngũ GoodGo</p>`,
|
||||
},
|
||||
'saved_search_digest': {
|
||||
subject: '{{matchCount}} bất động sản mới khớp tìm kiếm "{{searchName}}"',
|
||||
body: `<h1>Xin chào {{userName}}!</h1>
|
||||
<p>Có <strong>{{matchCount}}</strong> bất động sản mới phù hợp với tìm kiếm đã lưu <strong>"{{searchName}}"</strong> của bạn:</p>
|
||||
<table style="width:100%;border-collapse:collapse;margin:16px 0;">
|
||||
{{#each listings}}
|
||||
<tr style="{{#if @first}}background:#f8f9fa;{{/if}}">
|
||||
<td style="padding:12px;border:1px solid #dee2e6;">
|
||||
<strong><a href="{{url}}" style="color:#2563eb;text-decoration:none;">{{title}}</a></strong><br/>
|
||||
Giá: <strong>{{price}} VNĐ</strong> · {{district}}, {{city}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</table>
|
||||
<p><a href="/saved-searches" style="display:inline-block;padding:10px 20px;background:#2563eb;color:#fff;text-decoration:none;border-radius:6px;">Xem tất cả kết quả</a></p>
|
||||
<p style="color:#6b7280;font-size:14px;">Bạn có thể tắt thông báo cho tìm kiếm này trong phần <a href="/saved-searches">Tìm kiếm đã lưu</a>.</p>
|
||||
<p>Trân trọng,<br/>Đội ngũ GoodGo</p>`,
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user