add delete account cron

This commit is contained in:
Sergey Krylov 2025-07-05 10:54:06 +03:00
parent 706387b9ad
commit 04b4761aee
7 changed files with 148 additions and 1 deletions

View File

@ -31,6 +31,7 @@
"@nestjs/graphql": "^13.1.0",
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^11.0.1",
"@nestjs/schedule": "^6.0.0",
"@prisma/client": "^6.10.1",
"@react-email/components": "^0.1.1",
"@react-email/html": "^0.0.11",

View File

@ -1,10 +1,11 @@
import { getGraphQLConfig } from '@/src/core/config/graphql.config'
import { AccountModule } from '@/src/module/auth/account/account.module'
import { DeactivateModule } from '@/src/module/auth/deactivate/deactivate.module';
import { DeactivateModule } from '@/src/module/auth/deactivate/deactivate.module'
import { PasswordRecoveryModule } from '@/src/module/auth/password-recovery/password-recovery.module'
import { SessionModule } from '@/src/module/auth/session/session.module'
import { TotpModule } from '@/src/module/auth/totp/totp.module'
import { VerificationModule } from '@/src/module/auth/verification/verification.module'
import { CronModule } from '@/src/module/cron/cron.module'
import { MailModule } from '@/src/module/libs/mail/mail.module'
import { IS_DEV } from '@/src/shared/util/is-dev.util'
import { ApolloDriver } from '@nestjs/apollo'
@ -29,6 +30,7 @@ import { RedisModule } from './redis/redis.module'
PrismaModule,
RedisModule,
MailModule,
CronModule,
AccountModule,
SessionModule,
VerificationModule,

View File

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common'
import { ScheduleModule } from '@nestjs/schedule'
import { CronService } from './cron.service'
@Module({
imports: [ScheduleModule.forRoot()],
providers: [CronService],
})
export class CronModule {}

View File

@ -0,0 +1,42 @@
import { PrismaService } from '@/src/core/prisma/prisma.service'
import { MailService } from '@/src/module/libs/mail/mail.service'
import { Injectable } from '@nestjs/common'
import { Cron, CronExpression } from '@nestjs/schedule'
@Injectable()
export class CronService {
constructor(
private readonly prismaService: PrismaService,
private readonly mailService: MailService,
) {
}
@Cron(CronExpression.EVERY_DAY_AT_NOON)
private async deleteDeactivatedAccounts() {
const sevenDayAgo = new Date()
sevenDayAgo.setDate(sevenDayAgo.getDay() - 7)
const deactivatedAccounts = await this.prismaService.user.findMany({
where: {
isDeactivated: true,
deactivatedAt: {
lte: sevenDayAgo,
},
},
})
for (const user of deactivatedAccounts) {
console.log('Deactivate user', user.name, user.email)
await this.mailService.sendAccountDeletion(user.email)
}
await this.prismaService.user.deleteMany({
where: {
isDeactivated: true,
deactivatedAt: {
lte: sevenDayAgo,
},
},
})
}
}

View File

@ -1,3 +1,4 @@
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'
@ -35,6 +36,13 @@ export class MailService {
void this.sendMail(email, 'Деактивация аккаунта', html)
}
public async sendAccountDeletion(email: string) {
const domain = this.configService.getOrThrow<string>('ALLOWED_ORIGIN')
const html = await render(AccountDeletionTemplate({ domain }))
void this.sendMail(email, 'Удаление аккаунта', html)
}
private sendMail(email: string, subject: string, html: string) {
return this.mailerService.sendMail({
to: email,

View File

@ -0,0 +1,60 @@
import {
Body,
Head,
Heading,
Html,
Link,
Preview,
Section,
Tailwind,
Text,
} from '@react-email/components'
import * as React from 'react'
interface AccountDeletionTemplateProps {
domain: string
}
export function AccountDeletionTemplate({ domain }: AccountDeletionTemplateProps) {
const registerLink = `${domain}/account/create`
return (
<Html>
<Head />
<Preview>Аккаунт удалён</Preview>
<Tailwind>
<Body className="max-w-2xl mx-auto p-6 bg-slate-50">
<Section className="text-center">
<Heading className="text-3xl text-black font-bold">
Ваш аккаунт был полностью удалён
</Heading>
<Text className="text-base text-black mt-2">
Ваш аккаунт был полностью стерт из базы данных TeaStream. Все ваши данные и информация были удалены безвозвратно.
</Text>
</Section>
<Section className="bg-white text-black text-center rounded-lg shadow-md p-6 mb-4">
<Text>
Вы больше не будете получать уведомления в Telegram и на почту.
</Text>
<Text>
Если вы захотите вернуться на платформу, вы можете зарегистрироваться по следующей ссылке:
</Text>
<Link
href={registerLink}
className="inline-flex justify-center items-center rounded-md mt-2 text-sm font-medium text-white bg-[#18B9AE] px-5 py-2 rounded-full"
>
Зарегистрироваться на Teastream
</Link>
</Section>
<Section className="text-center text-black">
<Text>
Спасибо, что были с нами! Мы всегда будем рады видеть вас на платформе.
</Text>
</Section>
</Body>
</Tailwind>
</Html>
)
}

View File

@ -1408,6 +1408,13 @@
path-to-regexp "8.2.0"
tslib "2.8.1"
"@nestjs/schedule@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@nestjs/schedule/-/schedule-6.0.0.tgz#c8069d1f19742e418a617af76311ab853cb48f0a"
integrity sha512-aQySMw6tw2nhitELXd3EiRacQRgzUKD9mFcUZVOJ7jPLqIBvXOyvRWLsK9SdurGA+jjziAlMef7iB5ZEFFoQpw==
dependencies:
cron "4.3.0"
"@nestjs/schematics@^11.0.0", "@nestjs/schematics@^11.0.1":
version "11.0.5"
resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-11.0.5.tgz#cee2fb26f3273fb3874398aad3006517e6b802f9"
@ -2096,6 +2103,11 @@
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
"@types/luxon@~3.6.0":
version "3.6.2"
resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.6.2.tgz#be6536931801f437eafcb9c0f6d6781f72308041"
integrity sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw==
"@types/methods@^1.1.4":
version "1.1.4"
resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547"
@ -3479,6 +3491,14 @@ create-require@^1.1.0:
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
cron@4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/cron/-/cron-4.3.0.tgz#c5a62872f74f72294cf1cadef34c72ad8d8f50b5"
integrity sha512-ciiYNLfSlF9MrDqnbMdRWFiA6oizSF7kA1osPP9lRzNu0Uu+AWog1UKy7SkckiDY2irrNjeO6qLyKnXC8oxmrw==
dependencies:
"@types/luxon" "~3.6.0"
luxon "~3.6.0"
cross-inspect@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cross-inspect/-/cross-inspect-1.0.1.tgz#15f6f65e4ca963cf4cc1a2b5fef18f6ca328712b"
@ -5861,6 +5881,11 @@ lru-cache@^7.10.1, lru-cache@^7.14.1:
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89"
integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==
luxon@~3.6.0:
version "3.6.1"
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.6.1.tgz#d283ffc4c0076cb0db7885ec6da1c49ba97e47b0"
integrity sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==
magic-string@0.30.17:
version "0.30.17"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453"