From 04b4761aee52561abb278c0bc8f41995abadc2e5 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sat, 5 Jul 2025 10:54:06 +0300 Subject: [PATCH] add delete account cron --- backend/package.json | 1 + backend/src/core/core.module.ts | 4 +- backend/src/module/cron/cron.module.ts | 9 +++ backend/src/module/cron/cron.service.ts | 42 +++++++++++++ backend/src/module/libs/mail/mail.service.ts | 8 +++ .../templates/account-deletion.template.tsx | 60 +++++++++++++++++++ backend/yarn.lock | 25 ++++++++ 7 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 backend/src/module/cron/cron.module.ts create mode 100644 backend/src/module/cron/cron.service.ts create mode 100644 backend/src/module/libs/mail/templates/account-deletion.template.tsx diff --git a/backend/package.json b/backend/package.json index 2fb2469..b073df9 100644 --- a/backend/package.json +++ b/backend/package.json @@ -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", diff --git a/backend/src/core/core.module.ts b/backend/src/core/core.module.ts index c7a7996..9040bb1 100644 --- a/backend/src/core/core.module.ts +++ b/backend/src/core/core.module.ts @@ -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, diff --git a/backend/src/module/cron/cron.module.ts b/backend/src/module/cron/cron.module.ts new file mode 100644 index 0000000..42f541c --- /dev/null +++ b/backend/src/module/cron/cron.module.ts @@ -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 {} diff --git a/backend/src/module/cron/cron.service.ts b/backend/src/module/cron/cron.service.ts new file mode 100644 index 0000000..c586bf7 --- /dev/null +++ b/backend/src/module/cron/cron.service.ts @@ -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, + }, + }, + }) + } +} diff --git a/backend/src/module/libs/mail/mail.service.ts b/backend/src/module/libs/mail/mail.service.ts index b0744e4..1eda845 100644 --- a/backend/src/module/libs/mail/mail.service.ts +++ b/backend/src/module/libs/mail/mail.service.ts @@ -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('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, diff --git a/backend/src/module/libs/mail/templates/account-deletion.template.tsx b/backend/src/module/libs/mail/templates/account-deletion.template.tsx new file mode 100644 index 0000000..14cdfe2 --- /dev/null +++ b/backend/src/module/libs/mail/templates/account-deletion.template.tsx @@ -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 ( + + + Аккаунт удалён + + +
+ + Ваш аккаунт был полностью удалён + + + Ваш аккаунт был полностью стерт из базы данных TeaStream. Все ваши данные и информация были удалены безвозвратно. + +
+ +
+ + Вы больше не будете получать уведомления в Telegram и на почту. + + + Если вы захотите вернуться на платформу, вы можете зарегистрироваться по следующей ссылке: + + + Зарегистрироваться на Teastream + +
+ +
+ + Спасибо, что были с нами! Мы всегда будем рады видеть вас на платформе. + +
+ +
+ + ) +} diff --git a/backend/yarn.lock b/backend/yarn.lock index 35fbdc7..e803f0d 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -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"