From 179db38fd16e8dff377f44ffd6f03d4a643a2f8e Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 2 Mar 2023 11:11:42 +0000 Subject: [PATCH 1/4] feat: show email queue jobs in bull-board --- packages/backend/src/helpers/create-bull-board-handler.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/backend/src/helpers/create-bull-board-handler.ts b/packages/backend/src/helpers/create-bull-board-handler.ts index fde42c85..a1a7f3c1 100644 --- a/packages/backend/src/helpers/create-bull-board-handler.ts +++ b/packages/backend/src/helpers/create-bull-board-handler.ts @@ -4,6 +4,7 @@ import { BullMQAdapter } from '@bull-board/api/bullMQAdapter'; import flowQueue from '../queues/flow'; import triggerQueue from '../queues/trigger'; import actionQueue from '../queues/action'; +import emailQueue from '../queues/email'; import appConfig from '../config/app'; const serverAdapter = new ExpressAdapter(); @@ -21,6 +22,7 @@ const createBullBoardHandler = async (serverAdapter: ExpressAdapter) => { new BullMQAdapter(flowQueue), new BullMQAdapter(triggerQueue), new BullMQAdapter(actionQueue), + new BullMQAdapter(emailQueue), ], serverAdapter: serverAdapter, }); From b51d9bb17b40942625bfa81a80e59bae07a08b28 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 2 Mar 2023 15:25:24 +0000 Subject: [PATCH 2/4] feat: add delete user queue --- .../src/helpers/create-bull-board-handler.ts | 2 ++ packages/backend/src/queues/delete-user.ee.ts | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 packages/backend/src/queues/delete-user.ee.ts diff --git a/packages/backend/src/helpers/create-bull-board-handler.ts b/packages/backend/src/helpers/create-bull-board-handler.ts index a1a7f3c1..28a39330 100644 --- a/packages/backend/src/helpers/create-bull-board-handler.ts +++ b/packages/backend/src/helpers/create-bull-board-handler.ts @@ -5,6 +5,7 @@ import flowQueue from '../queues/flow'; import triggerQueue from '../queues/trigger'; import actionQueue from '../queues/action'; import emailQueue from '../queues/email'; +import deleteUserQueue from '../queues/delete-user.ee'; import appConfig from '../config/app'; const serverAdapter = new ExpressAdapter(); @@ -23,6 +24,7 @@ const createBullBoardHandler = async (serverAdapter: ExpressAdapter) => { new BullMQAdapter(triggerQueue), new BullMQAdapter(actionQueue), new BullMQAdapter(emailQueue), + new BullMQAdapter(deleteUserQueue), ], serverAdapter: serverAdapter, }); diff --git a/packages/backend/src/queues/delete-user.ee.ts b/packages/backend/src/queues/delete-user.ee.ts new file mode 100644 index 00000000..d67a59b3 --- /dev/null +++ b/packages/backend/src/queues/delete-user.ee.ts @@ -0,0 +1,25 @@ +import process from 'process'; +import { Queue } from 'bullmq'; +import redisConfig from '../config/redis'; +import logger from '../helpers/logger'; + +const CONNECTION_REFUSED = 'ECONNREFUSED'; + +const redisConnection = { + connection: redisConfig, +}; + +const deleteUserQueue = new Queue('delete-user', redisConnection); + +process.on('SIGTERM', async () => { + await deleteUserQueue.close(); +}); + +deleteUserQueue.on('error', (err) => { + if ((err as any).code === CONNECTION_REFUSED) { + logger.error('Make sure you have installed Redis and it is running.', err); + process.exit(); + } +}); + +export default deleteUserQueue; From e06b646f49e4b1a7feb6af8b3d0f862e0fd78f88 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 2 Mar 2023 15:26:06 +0000 Subject: [PATCH 3/4] feat: add deleteUser mutation --- .../backend/src/graphql/mutation-resolvers.ts | 2 ++ .../src/graphql/mutations/delete-user.ee.ts | 31 +++++++++++++++++++ packages/backend/src/graphql/schema.graphql | 5 +++ 3 files changed, 38 insertions(+) create mode 100644 packages/backend/src/graphql/mutations/delete-user.ee.ts diff --git a/packages/backend/src/graphql/mutation-resolvers.ts b/packages/backend/src/graphql/mutation-resolvers.ts index 5d8b8615..c19411c1 100644 --- a/packages/backend/src/graphql/mutation-resolvers.ts +++ b/packages/backend/src/graphql/mutation-resolvers.ts @@ -13,6 +13,7 @@ import createStep from './mutations/create-step'; import updateStep from './mutations/update-step'; import deleteStep from './mutations/delete-step'; import createUser from './mutations/create-user.ee'; +import deleteUser from './mutations/delete-user.ee'; import updateUser from './mutations/update-user'; import forgotPassword from './mutations/forgot-password.ee'; import resetPassword from './mutations/reset-password.ee'; @@ -34,6 +35,7 @@ const mutationResolvers = { updateStep, deleteStep, createUser, + deleteUser, updateUser, forgotPassword, resetPassword, diff --git a/packages/backend/src/graphql/mutations/delete-user.ee.ts b/packages/backend/src/graphql/mutations/delete-user.ee.ts new file mode 100644 index 00000000..b1f83097 --- /dev/null +++ b/packages/backend/src/graphql/mutations/delete-user.ee.ts @@ -0,0 +1,31 @@ +import User from '../../models/user'; +import deleteUserQueue from '../../queues/delete-user.ee'; +import { Duration } from 'luxon'; + +type Params = { + input: { + id: string; + }; +}; + +const deleteUser = async (_parent: unknown, params: Params) => { + const { id } = params.input; + await User + .query() + .findById(id) + .delete() + .throwIfNotFound(); + + const jobName = `Delete user - ${id}`; + const jobPayload = { id }; + const millisecondsFor30Days = Duration.fromObject({ days: 30 }).toMillis(); + const jobOptions = { + delay: millisecondsFor30Days + }; + + await deleteUserQueue.add(jobName, jobPayload, jobOptions); + + return true; +}; + +export default deleteUser; diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index 1e09a90e..d40af41f 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -54,6 +54,7 @@ type Mutation { updateStep(input: UpdateStepInput): Step deleteStep(input: DeleteStepInput): Step createUser(input: CreateUserInput): User + deleteUser(input: DeleteUserInput): Boolean updateUser(input: UpdateUserInput): User forgotPassword(input: ForgotPasswordInput): Boolean resetPassword(input: ResetPasswordInput): Boolean @@ -339,6 +340,10 @@ input CreateUserInput { password: String! } +input DeleteUserInput { + id: String +} + input UpdateUserInput { email: String password: String From 64f7560b3b3d1dca63f75facbc65e88ee0c5db27 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 2 Mar 2023 15:26:20 +0000 Subject: [PATCH 4/4] feat: add delete user worker --- packages/backend/src/worker.ts | 1 + .../backend/src/workers/delete-user.ee.ts | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 packages/backend/src/workers/delete-user.ee.ts diff --git a/packages/backend/src/worker.ts b/packages/backend/src/worker.ts index 8a5cb447..9e0d5646 100644 --- a/packages/backend/src/worker.ts +++ b/packages/backend/src/worker.ts @@ -4,6 +4,7 @@ import './workers/flow'; import './workers/trigger'; import './workers/action'; import './workers/email'; +import './workers/delete-user.ee'; import telemetry from './helpers/telemetry'; telemetry.setServiceType('worker'); diff --git a/packages/backend/src/workers/delete-user.ee.ts b/packages/backend/src/workers/delete-user.ee.ts new file mode 100644 index 00000000..887149a2 --- /dev/null +++ b/packages/backend/src/workers/delete-user.ee.ts @@ -0,0 +1,44 @@ +import { Worker } from 'bullmq'; +import redisConfig from '../config/redis'; +import logger from '../helpers/logger'; +import User from '../models/user'; +import Execution from '../models/execution'; +import ExecutionStep from '../models/execution-step'; + +export const worker = new Worker( + 'delete-user', + async (job) => { + const { id } = job.data; + + const user = await User.query().findById(id).throwIfNotFound(); + + const executionIds = ( + await user.$relatedQuery('executions').select('executions.id') + ).map((execution: Execution) => execution.id); + + await ExecutionStep.query().hardDelete().whereIn('execution_id', executionIds); + await user.$relatedQuery('executions').hardDelete(); + await user.$relatedQuery('steps').hardDelete(); + await user.$relatedQuery('flows').hardDelete(); + await user.$relatedQuery('connections').hardDelete(); + + await user.$query().hardDelete(); + }, + { connection: redisConfig } +); + +worker.on('completed', (job) => { + logger.info( + `JOB ID: ${job.id} - The user with the ID of '${job.data.id}' has been deleted!` + ); +}); + +worker.on('failed', (job, err) => { + logger.info( + `JOB ID: ${job.id} - The user with the ID of '${job.data.id}' has failed to be deleted! ${err.message}` + ); +}); + +process.on('SIGTERM', async () => { + await worker.close(); +});