From 409aeb12d3ab45332311026fb7dc32fae84ebeb2 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Fri, 12 Jan 2024 10:45:48 +0100 Subject: [PATCH] :monocle_face: Add updateUserEmail script --- packages/scripts/destroyUser.ts | 79 +------------------- packages/scripts/helpers/destroyUser.ts | 98 +++++++++++++++++++++++++ packages/scripts/inspectUser.ts | 76 ++----------------- packages/scripts/package.json | 3 +- packages/scripts/updateUserEmail.ts | 58 +++++++++++++++ 5 files changed, 170 insertions(+), 144 deletions(-) create mode 100644 packages/scripts/helpers/destroyUser.ts create mode 100644 packages/scripts/updateUserEmail.ts diff --git a/packages/scripts/destroyUser.ts b/packages/scripts/destroyUser.ts index 92910db05..49ed3494d 100644 --- a/packages/scripts/destroyUser.ts +++ b/packages/scripts/destroyUser.ts @@ -1,80 +1,9 @@ -import { PrismaClient } from '@typebot.io/prisma' -import * as p from '@clack/prompts' +import { destroyUser } from './helpers/destroyUser' import { promptAndSetEnvironment } from './utils' -const destroyUser = async () => { +const runDestroyUser = async () => { await promptAndSetEnvironment('production') - - const prisma = new PrismaClient({ - log: [{ emit: 'event', level: 'query' }, 'info', 'warn', 'error'], - }) - - prisma.$on('query', (e) => { - console.log(e.query) - console.log(e.params) - console.log(e.duration, 'ms') - }) - - const email = (await p.text({ - message: 'User email?', - })) as string - - if (!email || typeof email !== 'string') { - console.log('No email provided') - return - } - - const workspaces = await prisma.workspace.findMany({ - where: { - members: { every: { user: { email } } }, - }, - include: { - members: { select: { user: { select: { email: true } }, role: true } }, - typebots: { - select: { - results: { - select: { id: true }, - }, - }, - }, - }, - }) - - console.log(`Found ${workspaces.length} workspaces`) - - const proceed = await p.confirm({ message: 'Proceed?' }) - if (!proceed || typeof proceed !== 'boolean') { - console.log('Aborting') - return - } - - for (const workspace of workspaces) { - const hasResults = workspace.typebots.some((t) => t.results.length > 0) - if (hasResults) { - console.log( - `Workspace ${workspace.name} has results. Deleting results first...`, - workspace.typebots.filter((t) => t.results.length > 0) - ) - console.log(JSON.stringify({ members: workspace.members }, null, 2)) - const proceed = await p.confirm({ message: 'Proceed?' }) - if (!proceed || typeof proceed !== 'boolean') { - console.log('Aborting') - return - } - } - for (const typebot of workspace.typebots.filter( - (t) => t.results.length > 0 - )) { - for (const result of typebot.results) { - await prisma.result.deleteMany({ where: { id: result.id } }) - } - } - await prisma.workspace.delete({ where: { id: workspace.id } }) - } - - const user = await prisma.user.delete({ where: { email } }) - - console.log(`Deleted user ${JSON.stringify(user, null, 2)}`) + return destroyUser() } -destroyUser() +runDestroyUser() diff --git a/packages/scripts/helpers/destroyUser.ts b/packages/scripts/helpers/destroyUser.ts new file mode 100644 index 000000000..7665ebe57 --- /dev/null +++ b/packages/scripts/helpers/destroyUser.ts @@ -0,0 +1,98 @@ +import { isCancel, text, confirm } from '@clack/prompts' +import { Plan, PrismaClient } from '@typebot.io/prisma' +import { writeFileSync } from 'fs' + +export const destroyUser = async (userEmail?: string) => { + const prisma = new PrismaClient() + + const email = + userEmail ?? + (await text({ + message: 'User email?', + })) + + if (!email || isCancel(email)) { + console.log('No email provided') + return + } + + const workspaces = await prisma.workspace.findMany({ + where: { + members: { every: { user: { email } } }, + }, + include: { + members: { select: { user: { select: { email: true } }, role: true } }, + typebots: { + select: { + results: { + select: { id: true }, + }, + }, + }, + }, + }) + + console.log(`Found ${workspaces.length} workspaces`) + + if (workspaces.some((w) => w.plan !== Plan.FREE)) { + console.log( + `Some workspaces have a plan other than FREE. Something is wrong. Logging and exiting...` + ) + writeFileSync( + 'logs/workspaces-issue.json', + JSON.stringify(workspaces, null, 2) + ) + return + } + + if ( + workspaces.some((w) => + w.members.some((m) => m.user.email && m.user.email !== email) + ) + ) { + console.log( + `Some workspaces have other members. Something is wrong. Logging and exiting...` + ) + writeFileSync( + 'logs/workspaces-issue.json', + JSON.stringify(workspaces, null, 2) + ) + return + } + + console.log('All workspaces have a FREE plan') + + const proceed = await confirm({ message: 'Proceed?' }) + if (!proceed || typeof proceed !== 'boolean') { + console.log('Aborting') + return + } + + for (const workspace of workspaces) { + const hasResults = workspace.typebots.some((t) => t.results.length > 0) + if (hasResults) { + console.log( + `Workspace ${workspace.name} has results. Deleting results first...`, + workspace.typebots.filter((t) => t.results.length > 0) + ) + console.log(JSON.stringify({ members: workspace.members }, null, 2)) + const proceed = await confirm({ message: 'Proceed?' }) + if (!proceed || typeof proceed !== 'boolean') { + console.log('Aborting') + return + } + } + for (const typebot of workspace.typebots.filter( + (t) => t.results.length > 0 + )) { + for (const result of typebot.results) { + await prisma.result.deleteMany({ where: { id: result.id } }) + } + } + await prisma.workspace.delete({ where: { id: workspace.id } }) + } + + const user = await prisma.user.delete({ where: { email } }) + + console.log(`Deleted user ${JSON.stringify(user, null, 2)}`) +} diff --git a/packages/scripts/inspectUser.ts b/packages/scripts/inspectUser.ts index f085158ed..afac00657 100644 --- a/packages/scripts/inspectUser.ts +++ b/packages/scripts/inspectUser.ts @@ -1,24 +1,20 @@ import { PrismaClient } from '@typebot.io/prisma' import { promptAndSetEnvironment } from './utils' -import prompts from 'prompts' -import { isEmpty } from '@typebot.io/lib' +import { isCancel, text } from '@clack/prompts' const inspectUser = async () => { await promptAndSetEnvironment('production') - const response = await prompts({ - type: 'text', - name: 'email', + const email = await text({ message: 'User email', }) - if (isEmpty(response.email)) process.exit() - const prisma = new PrismaClient({ - log: [{ emit: 'event', level: 'query' }, 'info', 'warn', 'error'], - }) + if (!email || isCancel(email)) process.exit() + + const prisma = new PrismaClient() const user = await prisma.user.findFirst({ where: { - email: response.email, + email, }, select: { name: true, @@ -37,7 +33,7 @@ const inspectUser = async () => { plan: true, members: { where: { - user: { email: { not: response.email } }, + user: { email: { not: email } }, }, }, additionalStorageIndex: true, @@ -67,63 +63,7 @@ const inspectUser = async () => { }, }) - if (!user) { - console.log('User not found') - process.exit() - } - - console.log('Name:', user.name) - console.log('Last activity:', user.lastActivityAt.toLocaleDateString()) - console.log('Company:', user.company) - console.log('Onboarding categories:', user.onboardingCategories) - console.log('Total workspaces:', user.workspaces.length) - console.log('Workspaces:') - - for (const workspace of user.workspaces) { - console.log(' - ID:', workspace.workspace.id) - console.log(' Name:', workspace.workspace.name) - console.log(' Plan:', workspace.workspace.plan) - console.log(' Members:', workspace.workspace.members.length + 1) - console.log( - ' Additional storage:', - workspace.workspace.additionalStorageIndex - ) - console.log(' Typebots:', workspace.workspace.typebots.length) - - for (const typebot of workspace.workspace.typebots) { - console.log(' - Name:', typebot.name) - console.log(' Created:', typebot.createdAt.toLocaleDateString()) - console.log( - ' Last updated:', - typebot.updatedAt.toLocaleDateString() - ) - console.log(' Risk level:', typebot.riskLevel) - console.log( - ' Public ID:', - typebot.publishedTypebot?.typebot.publicId - ) - console.log( - ' URL:', - `https://app.typebot.io/typebots/${typebot.id}/edit` - ) - - if (!typebot.publishedTypebot) continue - - const totalTraffic = await prisma.result.count({ - where: { - typebotId: typebot.id, - isArchived: false, - }, - select: { - _all: true, - hasStarted: true, - }, - }) - - console.log(' Total traffic:', totalTraffic._all) - console.log(' Started:', totalTraffic.hasStarted) - } - } + console.log(JSON.stringify(user, null, 2)) } inspectUser() diff --git a/packages/scripts/package.json b/packages/scripts/package.json index 07264fe9b..ea1be2f58 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -26,7 +26,8 @@ "inspectTypebot": "tsx inspectTypebot.ts", "inspectWorkspace": "tsx inspectWorkspace.ts", "getCoupon": "tsx getCoupon.ts", - "exportResults": "tsx exportResults.ts" + "exportResults": "tsx exportResults.ts", + "updateUserEmail": "tsx updateUserEmail.ts" }, "devDependencies": { "@typebot.io/emails": "workspace:*", diff --git a/packages/scripts/updateUserEmail.ts b/packages/scripts/updateUserEmail.ts new file mode 100644 index 000000000..a2289595f --- /dev/null +++ b/packages/scripts/updateUserEmail.ts @@ -0,0 +1,58 @@ +import { PrismaClient } from '@typebot.io/prisma' +import { promptAndSetEnvironment } from './utils' +import { text, isCancel, confirm } from '@clack/prompts' +import { destroyUser } from './helpers/destroyUser' + +const updateUserEmail = async () => { + await promptAndSetEnvironment('production') + + const prisma = new PrismaClient() + + const currentUserEmail = await text({ + message: 'Current email?', + }) + + const newEmail = await text({ + message: 'New email?', + }) + + if ( + !currentUserEmail || + !newEmail || + isCancel(currentUserEmail) || + isCancel(newEmail) + ) + throw new Error('Invalid emails') + + const existingUserWithNewEmail = await prisma.user.findUnique({ + where: { + email: newEmail, + }, + }) + + if (existingUserWithNewEmail) { + console.log(`User with email ${newEmail} already exists`) + console.log(JSON.stringify(existingUserWithNewEmail, null, 2)) + + const isDestroying = await confirm({ + message: 'Would you like to destroy it and update the current user?', + }) + + if (!isDestroying || isCancel(isDestroying)) return + + await destroyUser(newEmail) + } + + const user = await prisma.user.update({ + where: { + email: currentUserEmail, + }, + data: { + email: newEmail, + }, + }) + + console.log(JSON.stringify(user, null, 2)) +} + +updateUserEmail()