notification
This commit is contained in:
1 parent
382cafa152
commit
60391ee123
26 files changed
+1077
-14
No files matched your search
+25
@@ -0,0 +1,25 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaAdapter } from '../../../../../../common/prisma-adapter/prisma.adapter.js';
|
||||
import type { NotificationDeviceToken } from '../../../../../../generated/prisma/client.js';
|
||||
import type { IDeviceTokenRepository } from '../../../../domain/repositories/device-token.repository.interface.js';
|
||||
|
||||
@Injectable()
|
||||
export class DeviceTokenPrismaRepository implements IDeviceTokenRepository {
|
||||
constructor(private readonly prisma: PrismaAdapter) {}
|
||||
|
||||
async upsert(userId: string, token: string, platform: string): Promise<void> {
|
||||
await this.prisma.notificationDeviceToken.upsert({
|
||||
where: { userId_token: { userId, token } },
|
||||
create: { userId, token, platform },
|
||||
update: { platform },
|
||||
});
|
||||
}
|
||||
|
||||
async remove(userId: string, token: string): Promise<void> {
|
||||
await this.prisma.notificationDeviceToken.deleteMany({ where: { userId, token } });
|
||||
}
|
||||
|
||||
findByUserId(userId: string): Promise<NotificationDeviceToken[]> {
|
||||
return this.prisma.notificationDeviceToken.findMany({ where: { userId } });
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaAdapter } from '../../../../../../common/prisma-adapter/prisma.adapter.js';
|
||||
import type { NotificationMessage } from '../../../../../../generated/prisma/client.js';
|
||||
import type { INotificationMessageRepository } from '../../../../domain/repositories/notification-message.repository.interface.js';
|
||||
import type { NotificationChannelValue } from '../../../../domain/value-objects/notification-channel.vo.js';
|
||||
import type { NotificationStatusValue } from '../../../../domain/value-objects/notification-status.vo.js';
|
||||
|
||||
@Injectable()
|
||||
export class NotificationMessagePrismaRepository implements INotificationMessageRepository {
|
||||
constructor(private readonly prisma: PrismaAdapter) {}
|
||||
|
||||
create(params: {
|
||||
userId: string;
|
||||
channel: NotificationChannelValue;
|
||||
subject?: string;
|
||||
content: string;
|
||||
}): Promise<NotificationMessage> {
|
||||
return this.prisma.notificationMessage.create({ data: params });
|
||||
}
|
||||
|
||||
async markStatus(id: string, status: NotificationStatusValue, error?: string): Promise<void> {
|
||||
await this.prisma.notificationMessage.update({
|
||||
where: { id },
|
||||
data: { status, error, sentAt: status === 'SENT' ? new Date() : undefined },
|
||||
});
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaAdapter } from '../../../../../../common/prisma-adapter/prisma.adapter.js';
|
||||
import type { NotificationPreference } from '../../../../../../generated/prisma/client.js';
|
||||
import type { INotificationPreferenceRepository } from '../../../../domain/repositories/notification-preference.repository.interface.js';
|
||||
import type { NotificationChannelValue } from '../../../../domain/value-objects/notification-channel.vo.js';
|
||||
|
||||
@Injectable()
|
||||
export class NotificationPreferencePrismaRepository implements INotificationPreferenceRepository {
|
||||
constructor(private readonly prisma: PrismaAdapter) {}
|
||||
|
||||
findByUserId(userId: string): Promise<NotificationPreference[]> {
|
||||
return this.prisma.notificationPreference.findMany({ where: { userId } });
|
||||
}
|
||||
|
||||
async set(userId: string, channel: NotificationChannelValue, enabled: boolean): Promise<void> {
|
||||
await this.prisma.notificationPreference.upsert({
|
||||
where: { userId_channel: { userId, channel } },
|
||||
create: { userId, channel, enabled },
|
||||
update: { enabled },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import EmailSender from '../../../../lib/email-sender.js';
|
||||
import { PrismaAdapter } from '../../../../common/prisma-adapter/prisma.adapter.js';
|
||||
import type { NotificationChannelSender } from '../../domain/channel-sender.interface.js';
|
||||
import type { NotificationChannelValue } from '../../domain/value-objects/notification-channel.vo.js';
|
||||
|
||||
@Injectable()
|
||||
export class EmailChannelSender implements NotificationChannelSender {
|
||||
readonly channel: NotificationChannelValue = 'EMAIL';
|
||||
|
||||
constructor(private readonly prisma: PrismaAdapter) {}
|
||||
|
||||
async send(params: { userId: string; subject?: string; content: string }): Promise<void> {
|
||||
const user = await this.prisma.user.findUniqueOrThrow({
|
||||
where: { id: params.userId },
|
||||
select: { email: true },
|
||||
});
|
||||
|
||||
await EmailSender.getTransporter().sendMail({
|
||||
to: user.email,
|
||||
subject: params.subject ?? 'Уведомление',
|
||||
text: params.content,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import type { NotificationChannelSender } from '../../domain/channel-sender.interface.js';
|
||||
import type { NotificationChannelValue } from '../../domain/value-objects/notification-channel.vo.js';
|
||||
import {
|
||||
DEVICE_TOKEN_REPOSITORY,
|
||||
type IDeviceTokenRepository,
|
||||
} from '../../domain/repositories/device-token.repository.interface.js';
|
||||
|
||||
/**
|
||||
* Провайдер push-доставки (FCM/APNs) пока не подключён — отправка логируется.
|
||||
* Токены уже читаются из БД, так что при подключении провайдера
|
||||
* меняется только тело send(), остальной пайплайн трогать не нужно.
|
||||
*/
|
||||
@Injectable()
|
||||
export class PushChannelSender implements NotificationChannelSender {
|
||||
private readonly logger = new Logger(PushChannelSender.name);
|
||||
readonly channel: NotificationChannelValue = 'PUSH';
|
||||
|
||||
constructor(@Inject(DEVICE_TOKEN_REPOSITORY) private readonly deviceTokens: IDeviceTokenRepository) {}
|
||||
|
||||
async send(params: { userId: string; content: string }): Promise<void> {
|
||||
const tokens = await this.deviceTokens.findByUserId(params.userId);
|
||||
if (tokens.length === 0) {
|
||||
throw new Error(`No device tokens registered for user ${params.userId}`);
|
||||
}
|
||||
|
||||
this.logger.warn(
|
||||
`Push provider is not configured yet, skipping actual delivery to ${tokens.length} device(s): ${params.content}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user