add cron operation
This commit is contained in:
parent
bfe3d9573c
commit
d1c5ac7e19
@ -1,9 +1,10 @@
|
|||||||
|
import { NotificationService } from '@/src/module/notification/notification.service'
|
||||||
import { Module } from '@nestjs/common'
|
import { Module } from '@nestjs/common'
|
||||||
import { ScheduleModule } from '@nestjs/schedule'
|
import { ScheduleModule } from '@nestjs/schedule'
|
||||||
import { CronService } from './cron.service'
|
import { CronService } from './cron.service'
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [ScheduleModule.forRoot()],
|
imports: [ScheduleModule.forRoot()],
|
||||||
providers: [CronService],
|
providers: [CronService, NotificationService],
|
||||||
})
|
})
|
||||||
export class CronModule {}
|
export class CronModule {}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { PrismaService } from '@/src/core/prisma/prisma.service'
|
|||||||
import { MailService } from '@/src/module/libs/mail/mail.service'
|
import { MailService } from '@/src/module/libs/mail/mail.service'
|
||||||
import { StorageService } from '@/src/module/libs/storage/storage.service'
|
import { StorageService } from '@/src/module/libs/storage/storage.service'
|
||||||
import { TelegramService } from '@/src/module/libs/telegram/telegram.service'
|
import { TelegramService } from '@/src/module/libs/telegram/telegram.service'
|
||||||
|
import { NotificationService } from '@/src/module/notification/notification.service'
|
||||||
import { Injectable } from '@nestjs/common'
|
import { Injectable } from '@nestjs/common'
|
||||||
import { Cron, CronExpression } from '@nestjs/schedule'
|
import { Cron, CronExpression } from '@nestjs/schedule'
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ export class CronService {
|
|||||||
private readonly mailService: MailService,
|
private readonly mailService: MailService,
|
||||||
private readonly storageService: StorageService,
|
private readonly storageService: StorageService,
|
||||||
private readonly telegramService: TelegramService,
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 { SessionInfo } from '@/src/shared/types/session-metadata.types'
|
||||||
import { Token } from '@prisma/generated'
|
import { Token } from '@prisma/generated'
|
||||||
import { ProcessEnv } from '@/src/shared/types/env'
|
import { ProcessEnv } from '@/src/shared/types/env'
|
||||||
import VerificationTemplate from './templates/verification.template'
|
|
||||||
import { MailerService } from '@nestjs-modules/mailer'
|
import { MailerService } from '@nestjs-modules/mailer'
|
||||||
import { Injectable } from '@nestjs/common'
|
import { Injectable } from '@nestjs/common'
|
||||||
import { ConfigService } from '@nestjs/config'
|
import { ConfigService } from '@nestjs/config'
|
||||||
import { render } from '@react-email/components'
|
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()
|
@Injectable()
|
||||||
export class MailService {
|
export class MailService {
|
||||||
@ -44,6 +46,19 @@ export class MailService {
|
|||||||
void this.sendMail(email, 'Удаление аккаунта', html)
|
void this.sendMail(email, 'Удаление аккаунта', html)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async sendEnableTwoFactor(email: string) {
|
||||||
|
const domain = this.configService.getOrThrow<string>('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) {
|
private sendMail(email: string, subject: string, html: string) {
|
||||||
return this.mailerService.sendMail({
|
return this.mailerService.sendMail({
|
||||||
to: email,
|
to: email,
|
||||||
|
|||||||
@ -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 (
|
||||||
|
<Html>
|
||||||
|
<Head />
|
||||||
|
<Preview>Обеспечьте свою безопасность</Preview>
|
||||||
|
<Tailwind>
|
||||||
|
<Body className="max-w-2xl mx-auto p-6 bg-slate-50">
|
||||||
|
<Section className="text-center mb-8">
|
||||||
|
<Heading className="text-3xl text-black font-bold">
|
||||||
|
Защитите свой аккаунт с двухфакторной аутентификацией
|
||||||
|
</Heading>
|
||||||
|
<Text className="text-black text-base mt-2">
|
||||||
|
Включите двухфакторную аутентификацию, чтобы повысить безопасность вашего аккаунта.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section className="bg-white rounded-lg shadow-md p-6 text-center mb-6">
|
||||||
|
<Heading className="text-2xl text-black font-semibold">
|
||||||
|
Почему это важно?
|
||||||
|
</Heading>
|
||||||
|
<Text className="text-base text-black mt-2">
|
||||||
|
Двухфакторная аутентификация добавляет дополнительный уровень защиты, требуя код, который известен только вам.
|
||||||
|
</Text>
|
||||||
|
<Link
|
||||||
|
href={settingsLink}
|
||||||
|
className="inline-flex justify-center items-center rounded-md text-sm font-medium text-white bg-[#18B9AE] px-5 py-2 rounded-full"
|
||||||
|
>
|
||||||
|
Перейти в настройки аккаунта
|
||||||
|
</Link>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section className="text-center mt-8">
|
||||||
|
<Text className="text-gray-600">
|
||||||
|
Если у вас возникли вопросы, обращайтесь в службу поддержки по адресу
|
||||||
|
{' '}
|
||||||
|
<Link
|
||||||
|
href="mailto:help@teastream.ru"
|
||||||
|
className="text-[#18b9ae] underline"
|
||||||
|
>
|
||||||
|
help@teastream.ru
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</Body>
|
||||||
|
</Tailwind>
|
||||||
|
</Html>
|
||||||
|
)
|
||||||
|
}
|
||||||
6
backend/src/module/libs/mail/templates/index.ts
Normal file
6
backend/src/module/libs/mail/templates/index.ts
Normal file
@ -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'
|
||||||
@ -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 (
|
||||||
|
<Html>
|
||||||
|
<Head />
|
||||||
|
<Preview>Ваш канал верифицирован</Preview>
|
||||||
|
<Tailwind>
|
||||||
|
<Body className="max-w-2xl mx-auto p-6 bg-slate-50">
|
||||||
|
<Section className="text-center mb-8">
|
||||||
|
<Heading className="text-3xl text-black font-bold">
|
||||||
|
Поздравляем! Ваш канал верифицирован
|
||||||
|
</Heading>
|
||||||
|
<Text className="text-black text-base mt-2">
|
||||||
|
Мы рады сообщить, что ваш канал теперь верифицирован, и вы получили официальный значок.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section className="bg-white rounded-lg shadow-md p-6 text-center mb-6">
|
||||||
|
<Heading className="text-2xl text-black font-semibold">
|
||||||
|
Что это значит?
|
||||||
|
</Heading>
|
||||||
|
<Text className="text-base text-black mt-2">
|
||||||
|
Значок верификации подтверждает подлинность вашего канала и улучшает доверие зрителей.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<Section className="text-center mt-8">
|
||||||
|
<Text className="text-gray-600">
|
||||||
|
Если у вас есть вопросы, напишите нам на
|
||||||
|
{' '}
|
||||||
|
<Link
|
||||||
|
href="mailto:help@teastream.ru"
|
||||||
|
className="text-[#18b9ae] underline"
|
||||||
|
>
|
||||||
|
help@teastream.ru
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</Body>
|
||||||
|
</Tailwind>
|
||||||
|
</Html>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -62,6 +62,7 @@ export const MESSAGES = {
|
|||||||
`<b>📡 На канале ${channel.displayName} началась трансляция!</b>\n\n`
|
`<b>📡 На канале ${channel.displayName} началась трансляция!</b>\n\n`
|
||||||
+ `Смотрите здесь: <a href="https://teastream.ru/${channel.name}">Перейти к трансляции</a>`,
|
+ `Смотрите здесь: <a href="https://teastream.ru/${channel.name}">Перейти к трансляции</a>`,
|
||||||
newFollowing: (follower: User, followersCount: number) =>
|
newFollowing: (follower: User, followersCount: number) =>
|
||||||
|
|
||||||
`<b>У вас новый подписчик!</b>\n\nЭто пользователь <a href="https://teastream.ru/${follower.name}">${follower.displayName}</a>\n\nИтоговое количество подписчиков на вашем канале: ${followersCount}`,
|
`<b>У вас новый подписчик!</b>\n\nЭто пользователь <a href="https://teastream.ru/${follower.name}">${follower.displayName}</a>\n\nИтоговое количество подписчиков на вашем канале: ${followersCount}`,
|
||||||
enableTwoFactor:
|
enableTwoFactor:
|
||||||
`🔐 Обеспечьте свою безопасность!\n\n`
|
`🔐 Обеспечьте свою безопасность!\n\n`
|
||||||
|
|||||||
@ -144,6 +144,14 @@ export class TelegramService extends Telegraf {
|
|||||||
await this.telegram.sendMessage(chatId, MESSAGES.newFollowing(follower, user.followings.length), { parse_mode: 'HTML' })
|
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) {
|
private async connectTelegram(userId: string, chatId: string) {
|
||||||
return this.prismaService.user.update({
|
return this.prismaService.user.update({
|
||||||
where: { id: userId },
|
where: { id: userId },
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user