Files
stats/admin/src/commands/delete-user.ts

221 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { db } from '@openpanel/db';
import chalk from 'chalk';
import fuzzy from 'fuzzy';
import inquirer from 'inquirer';
import autocomplete from 'inquirer-autocomplete-prompt';
// Register autocomplete prompt
inquirer.registerPrompt('autocomplete', autocomplete);
interface UserSearchItem {
id: string;
email: string;
firstName: string | null;
lastName: string | null;
displayText: string;
}
export async function deleteUser() {
console.log(chalk.red('\n🗑 Delete User\n'));
console.log(
chalk.yellow(
'⚠️ WARNING: This will permanently delete the user and remove them from all organizations!\n'
)
);
console.log('Loading users...\n');
const users = await db.user.findMany({
include: {
membership: {
include: {
organization: true,
},
},
accounts: true,
},
orderBy: {
email: 'asc',
},
});
if (users.length === 0) {
console.log(chalk.red('No users found.'));
return;
}
const searchItems: UserSearchItem[] = users.map((user) => {
const fullName =
user.firstName || user.lastName
? `${user.firstName || ''} ${user.lastName || ''}`.trim()
: '';
const orgCount = user.membership.length;
return {
id: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
displayText: `${user.email} ${fullName ? chalk.gray(`(${fullName})`) : ''} ${chalk.cyan(`- ${orgCount} orgs`)}`,
};
});
const searchFunction = (_answers: unknown, input = '') => {
const fuzzyResult = fuzzy.filter(input, searchItems, {
extract: (item: UserSearchItem) =>
`${item.email} ${item.firstName || ''} ${item.lastName || ''}`,
});
return fuzzyResult.map((result: fuzzy.FilterResult<UserSearchItem>) => ({
name: result.original.displayText,
value: result.original,
}));
};
const { selectedUser } = (await inquirer.prompt([
{
type: 'autocomplete',
name: 'selectedUser',
message: 'Search for a user to delete:',
source: searchFunction,
pageSize: 15,
},
])) as { selectedUser: UserSearchItem };
// Fetch full user details
const user = await db.user.findUnique({
where: {
id: selectedUser.id,
},
include: {
membership: {
include: {
organization: true,
},
},
accounts: true,
createdOrganizations: true,
},
});
if (!user) {
console.log(chalk.red('User not found.'));
return;
}
// Display what will be deleted
console.log(chalk.red('\n⚠ YOU ARE ABOUT TO DELETE:\n'));
console.log(` ${chalk.bold('User:')} ${user.email}`);
if (user.firstName || user.lastName) {
console.log(
` ${chalk.gray('Name:')} ${user.firstName || ''} ${user.lastName || ''}`
);
}
console.log(` ${chalk.gray('ID:')} ${user.id}`);
console.log(
` ${chalk.gray('Member of:')} ${user.membership.length} organizations`
);
console.log(` ${chalk.gray('Auth accounts:')} ${user.accounts.length}`);
if (user.createdOrganizations.length > 0) {
console.log(
chalk.red(
`\n ⚠️ This user CREATED ${user.createdOrganizations.length} organization(s):`
)
);
for (const org of user.createdOrganizations) {
console.log(` - ${org.name} ${chalk.gray(`(${org.id})`)}`);
}
console.log(
chalk.yellow(
' Note: These organizations will NOT be deleted, only the user reference.'
)
);
}
if (user.membership.length > 0) {
console.log(
chalk.red('\n Organizations where user will be removed from:')
);
for (const member of user.membership) {
console.log(
` - ${member.organization.name} ${chalk.gray(`(${member.role})`)}`
);
}
}
console.log(
chalk.red(
'\n⚠ This will delete the user account, all sessions, and remove them from all organizations!'
)
);
// First confirmation
const { confirmFirst } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirmFirst',
message: chalk.red(
`Are you ABSOLUTELY SURE you want to delete user "${user.email}"?`
),
default: false,
},
]);
if (!confirmFirst) {
console.log(chalk.yellow('\nDeletion cancelled.'));
return;
}
// Second confirmation - type email
const { confirmEmail } = await inquirer.prompt([
{
type: 'input',
name: 'confirmEmail',
message: `Type the user email "${user.email}" to confirm deletion:`,
},
]);
if (confirmEmail !== user.email) {
console.log(chalk.red('\n❌ Email does not match. Deletion cancelled.'));
return;
}
// Final confirmation
const { confirmFinal } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirmFinal',
message: chalk.red(
'FINAL WARNING: This action CANNOT be undone. Delete now?'
),
default: false,
},
]);
if (!confirmFinal) {
console.log(chalk.yellow('\nDeletion cancelled.'));
return;
}
console.log(chalk.red('\n🗑 Deleting user...\n'));
try {
// Delete the user (cascade will handle related records like sessions, accounts, memberships)
await db.user.delete({
where: {
id: user.id,
},
});
console.log(chalk.green('\n✅ User deleted successfully!'));
console.log(
chalk.gray(
`Deleted: ${user.email} (removed from ${user.membership.length} organizations)`
)
);
} catch (error) {
console.error(chalk.red('\n❌ Error deleting user:'), error);
throw error;
}
}