This commit is contained in:
2026-08-28 17:31:02 +02:00
commit 2b30e8bd39
694 changed files with 49243 additions and 0 deletions
@@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { ShopSettingsModule } from '../shopSettings/ShopSettingsModule';
import { SimplexModule } from '../simplex/SimplexModule';
import { NotificationService } from './services/NotificationService';
@Module({
imports: [SimplexModule, ShopSettingsModule],
providers: [NotificationService],
exports: [NotificationService]
})
export class NotificationsModule {}
@@ -0,0 +1,74 @@
import { Logger } from '@nestjs/common';
import type { ShopSettingsService } from '../../shopSettings/services/ShopSettingsService';
import type { SimplexChatClient } from '../../simplex/services/SimplexChatClient';
import { NotificationService } from './NotificationService';
describe('NotificationService', () => {
let service: NotificationService;
let shopSettingsService: {
findSettings: jest.Mock;
};
let simplexChatClient: {
sendText: jest.Mock;
};
let warnSpy: jest.SpiedFunction<typeof Logger.prototype.warn>;
beforeEach(() => {
warnSpy = jest.spyOn(Logger.prototype, 'warn').mockImplementation(() => undefined);
shopSettingsService = {
findSettings: jest.fn().mockResolvedValue({
notificationsEnabled: true,
simplexNotificationContactId: 42,
notifyOnNewOrder: true,
notifyOnOrderMessage: false
})
};
simplexChatClient = {
sendText: jest.fn().mockResolvedValue(undefined)
};
service = new NotificationService(
shopSettingsService as unknown as ShopSettingsService,
simplexChatClient as unknown as SimplexChatClient
);
});
afterEach(() => {
warnSpy.mockRestore();
});
it('does nothing when notifications are disabled', async () => {
shopSettingsService.findSettings.mockResolvedValue({
notificationsEnabled: false,
simplexNotificationContactId: 42,
notifyOnNewOrder: true,
notifyOnOrderMessage: true
});
await service.sendNotification('order-1234-abcd', 'newOrder');
expect(simplexChatClient.sendText).not.toHaveBeenCalled();
});
it('does nothing when the notification type is disabled', async () => {
await service.sendNotification('order-1234-abcd', 'newBuyerMessage');
expect(simplexChatClient.sendText).not.toHaveBeenCalled();
});
it('sends a new order notification when enabled', async () => {
await service.sendNotification('order-1234-abcd', 'newOrder');
expect(simplexChatClient.sendText).toHaveBeenCalledWith(42, 'New order #orde — open CMS Orders.');
});
it('logs a warning when sending fails without throwing', async () => {
simplexChatClient.sendText.mockRejectedValue(new Error('simplex down'));
await expect(service.sendNotification('order-1234-abcd', 'newOrder')).resolves.toBeUndefined();
expect(warnSpy).toHaveBeenCalledWith('Failed to send newOrder notification: simplex down');
});
});
@@ -0,0 +1,42 @@
import { Injectable, Logger } from '@nestjs/common';
import { getErrorMessage } from '../../../utils/getErrorMessage';
import { formatShortOrderId } from '../../../utils/order/formatShortOrderId';
import { ShopSettingsService } from '../../shopSettings/services/ShopSettingsService';
import { SimplexChatClient } from '../../simplex/services/SimplexChatClient';
@Injectable()
export class NotificationService {
private readonly logger = new Logger(NotificationService.name);
constructor(
private readonly shopSettingsService: ShopSettingsService,
private readonly simplexChatClient: SimplexChatClient
) {}
async sendNotification(orderId: string, type: 'newOrder' | 'newBuyerMessage'): Promise<void> {
try {
const settings = await this.shopSettingsService.findSettings();
if (!settings.notificationsEnabled || settings.simplexNotificationContactId === null) {
return;
}
const enabled = type === 'newOrder' ? settings.notifyOnNewOrder : settings.notifyOnOrderMessage;
if (!enabled) {
return;
}
const shortId = formatShortOrderId(orderId);
const message =
type === 'newOrder'
? `New order ${shortId} — open CMS Orders.`
: `New message on order ${shortId}.`;
await this.simplexChatClient.sendText(settings.simplexNotificationContactId, message);
} catch (error) {
this.logger.warn(`Failed to send ${type} notification: ${getErrorMessage(error)}`);
}
}
}