diff --git a/backend/src/module/cron/cron.module.ts b/backend/src/module/cron/cron.module.ts index 42f541c..2642f95 100644 --- a/backend/src/module/cron/cron.module.ts +++ b/backend/src/module/cron/cron.module.ts @@ -1,9 +1,10 @@ +import { NotificationService } from '@/src/module/notification/notification.service' import { Module } from '@nestjs/common' import { ScheduleModule } from '@nestjs/schedule' import { CronService } from './cron.service' @Module({ imports: [ScheduleModule.forRoot()], - providers: [CronService], + providers: [CronService, NotificationService], }) export class CronModule {} diff --git a/backend/src/module/cron/cron.service.ts b/backend/src/module/cron/cron.service.ts index 7bd548e..c6d2afc 100644 --- a/backend/src/module/cron/cron.service.ts +++ b/backend/src/module/cron/cron.service.ts @@ -2,6 +2,7 @@ import { PrismaService } from '@/src/core/prisma/prisma.service' import { MailService } from '@/src/module/libs/mail/mail.service' import { StorageService } from '@/src/module/libs/storage/storage.service' import { TelegramService } from '@/src/module/libs/telegram/telegram.service' +import { NotificationService } from '@/src/module/notification/notification.service' import { Injectable } from '@nestjs/common' import { Cron, CronExpression } from '@nestjs/schedule' @@ -12,6 +13,7 @@ export class CronService { private readonly mailService: MailService, private readonly storageService: StorageService, private readonly telegramService: TelegramService, + private readonly notificationService: NotificationService, ) { } @@ -53,4 +55,72 @@ export class CronService { }, }) } + + @Cron('0 0 */4 * *') + public async notifyUsersEnableTwoFactor() { + const users = await this.prismaService.user.findMany({ + where: { isTotpEnabled: false }, + include: { notificationSettings: true }, + }) + + for (const user of users) { + if (!user) { + continue + } + + await this.mailService.sendEnableTwoFactor(user.email) + + if (user.notificationSettings?.siteNotifications) { + await this.notificationService.createEnableTwoFactor(user.id) + } + + if (user.notificationSettings?.telegramNotifications && user.telegramId) { + await this.telegramService.sendEnableTwoFactor(user.telegramId) + } + } + } + + @Cron(CronExpression.EVERY_DAY_AT_1AM) + public async verifyChannels() { + const users = await this.prismaService.user.findMany({ + include: { notificationSettings: true }, + }) + + for (const user of users) { + const followersCount = await this.prismaService.follow.count({ + where: { followingId: user.id }, + }) + + if (followersCount > 10 && !user.isVerified) { + await this.prismaService.user.update({ + where: { id: user.id }, + data: { isVerified: true }, + }) + + await this.mailService.sendVerifyChannel(user.email) + + if (user.notificationSettings?.siteNotifications) { + await this.notificationService.createVerifyChannel(user.id) + } + + if (user.notificationSettings?.telegramNotifications && user.telegramId) { + await this.telegramService.sendVerifyChannel(user.telegramId) + } + } + } + } + + @Cron(CronExpression.EVERY_DAY_AT_1AM) + public async deleteOldNotifications() { + const sevenDaysAgo = new Date() + sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7) + + await this.prismaService.notification.deleteMany({ + where: { + createdAt: { + lte: sevenDaysAgo, + }, + }, + }) + } } diff --git a/backend/src/module/libs/mail/mail.service.ts b/backend/src/module/libs/mail/mail.service.ts index 1f47a79..18bd40d 100644 --- a/backend/src/module/libs/mail/mail.service.ts +++ b/backend/src/module/libs/mail/mail.service.ts @@ -1,14 +1,16 @@ -import { AccountDeletionTemplate } from '@/src/module/libs/mail/templates/account-deletion.template' -import { DeactivateTemplate } from '@/src/module/libs/mail/templates/deactivate.template' -import PasswordRecoveryTemplate from '@/src/module/libs/mail/templates/password-recovery.template' import { SessionInfo } from '@/src/shared/types/session-metadata.types' import { Token } from '@prisma/generated' import { ProcessEnv } from '@/src/shared/types/env' -import VerificationTemplate from './templates/verification.template' import { MailerService } from '@nestjs-modules/mailer' import { Injectable } from '@nestjs/common' import { ConfigService } from '@nestjs/config' import { render } from '@react-email/components' +import { EnableTwoFactorTemplate } from './templates' +import { VerifyChannelTemplate } from './templates' +import { VerificationTemplate } from './templates' +import { PasswordRecoveryTemplate } from './templates' +import { DeactivateTemplate } from './templates' +import { AccountDeletionTemplate } from './templates' @Injectable() export class MailService { @@ -44,6 +46,19 @@ export class MailService { void this.sendMail(email, 'Удаление аккаунта', html) } + public async sendEnableTwoFactor(email: string) { + const domain = this.configService.getOrThrow('ALLOWED_ORIGIN') + const html = await render(EnableTwoFactorTemplate({ domain })) + + void this.sendMail(email, 'Обеспечьте свою безопасность', html) + } + + public async sendVerifyChannel(email: string) { + const html = await render(VerifyChannelTemplate()) + + void this.sendMail(email, 'Ваш канал верифицирован', html) + } + private sendMail(email: string, subject: string, html: string) { return this.mailerService.sendMail({ to: email, diff --git a/backend/src/module/libs/mail/templates/enable-two-factor.template.tsx b/backend/src/module/libs/mail/templates/enable-two-factor.template.tsx new file mode 100644 index 0000000..1433240 --- /dev/null +++ b/backend/src/module/libs/mail/templates/enable-two-factor.template.tsx @@ -0,0 +1,68 @@ +import { + Body, + Head, + Heading, + Html, + Link, + Preview, + Section, + Tailwind, + Text, +} from '@react-email/components' +import * as React from 'react' + +interface EnableTwoFactorTemplateProps { + domain: string +} + +export function EnableTwoFactorTemplate({ domain }: EnableTwoFactorTemplateProps) { + const settingsLink = `${domain}/dashboard/settings` + + return ( + + + Обеспечьте свою безопасность + + +
+ + Защитите свой аккаунт с двухфакторной аутентификацией + + + Включите двухфакторную аутентификацию, чтобы повысить безопасность вашего аккаунта. + +
+ +
+ + Почему это важно? + + + Двухфакторная аутентификация добавляет дополнительный уровень защиты, требуя код, который известен только вам. + + + Перейти в настройки аккаунта + +
+ +
+ + Если у вас возникли вопросы, обращайтесь в службу поддержки по адресу + {' '} + + help@teastream.ru + + . + +
+ +
+ + ) +} diff --git a/backend/src/module/libs/mail/templates/index.ts b/backend/src/module/libs/mail/templates/index.ts new file mode 100644 index 0000000..58a81f5 --- /dev/null +++ b/backend/src/module/libs/mail/templates/index.ts @@ -0,0 +1,6 @@ +export { default as VerificationTemplate } from './verification.template' +export { AccountDeletionTemplate } from './account-deletion.template' +export { DeactivateTemplate } from './deactivate.template' +export { EnableTwoFactorTemplate } from './enable-two-factor.template' +export { default as PasswordRecoveryTemplate } from './password-recovery.template' +export { VerifyChannelTemplate } from './verify-channel.template' diff --git a/backend/src/module/libs/mail/templates/verify-channel.template.tsx b/backend/src/module/libs/mail/templates/verify-channel.template.tsx new file mode 100644 index 0000000..27b4311 --- /dev/null +++ b/backend/src/module/libs/mail/templates/verify-channel.template.tsx @@ -0,0 +1,56 @@ +import { + Body, + Head, + Heading, + Html, + Link, + Preview, + Section, + Tailwind, + Text, +} from '@react-email/components' +import * as React from 'react' + +export function VerifyChannelTemplate() { + return ( + + + Ваш канал верифицирован + + +
+ + Поздравляем! Ваш канал верифицирован + + + Мы рады сообщить, что ваш канал теперь верифицирован, и вы получили официальный значок. + +
+ +
+ + Что это значит? + + + Значок верификации подтверждает подлинность вашего канала и улучшает доверие зрителей. + +
+ +
+ + Если у вас есть вопросы, напишите нам на + {' '} + + help@teastream.ru + + . + +
+ +
+ + ) +} diff --git a/backend/src/module/libs/telegram/telegram.message.ts b/backend/src/module/libs/telegram/telegram.message.ts index 1d290d9..082267d 100644 --- a/backend/src/module/libs/telegram/telegram.message.ts +++ b/backend/src/module/libs/telegram/telegram.message.ts @@ -62,6 +62,7 @@ export const MESSAGES = { `📡 На канале ${channel.displayName} началась трансляция!\n\n` + `Смотрите здесь: Перейти к трансляции`, newFollowing: (follower: User, followersCount: number) => + `У вас новый подписчик!\n\nЭто пользователь ${follower.displayName}\n\nИтоговое количество подписчиков на вашем канале: ${followersCount}`, enableTwoFactor: `🔐 Обеспечьте свою безопасность!\n\n` diff --git a/backend/src/module/libs/telegram/telegram.service.ts b/backend/src/module/libs/telegram/telegram.service.ts index 4bc311e..f9f775d 100644 --- a/backend/src/module/libs/telegram/telegram.service.ts +++ b/backend/src/module/libs/telegram/telegram.service.ts @@ -144,6 +144,14 @@ export class TelegramService extends Telegraf { await this.telegram.sendMessage(chatId, MESSAGES.newFollowing(follower, user.followings.length), { parse_mode: 'HTML' }) } + public async sendEnableTwoFactor(chatId: string) { + return this.telegram.sendMessage(chatId, MESSAGES.enableTwoFactor, { parse_mode: 'HTML' }) + } + + public async sendVerifyChannel(chatId: string) { + return this.telegram.sendMessage(chatId, MESSAGES.verifyChannel, { parse_mode: 'HTML' }) + } + private async connectTelegram(userId: string, chatId: string) { return this.prismaService.user.update({ where: { id: userId },