delete-user.js script
I already had a script for this lying around, and adapted it a tiny bit to the repository! Part of me thought about building it in as a support tool. I might've if: - this CLI didn't already exist - we already had tighter permissioning, this is pretty high stakes!!
This commit is contained in:
parent
2f36f8a0e8
commit
197c623426
3 changed files with 256 additions and 0 deletions
|
@ -66,6 +66,7 @@
|
|||
"setup-mysql-dev": "yarn mysql-dev < scripts/setup-mysql-dev-constants.sql && yarn mysql-dev < scripts/setup-mysql-dev-schema.sql",
|
||||
"build-cached-data": "ts-node --compiler=typescript-cached-transpile --transpile-only -r dotenv/config scripts/build-cached-data.js",
|
||||
"cache-asset-manifests": "ts-node --compiler=typescript-cached-transpile --transpile-only -r dotenv/config scripts/cache-asset-manifests.js",
|
||||
"delete-user": "ts-node --compiler=typescript-cached-transpile --transpile-only -r dotenv/config scripts/delete-user.js",
|
||||
"export-users-to-auth0": "ts-node --compiler=typescript-cached-transpile --transpile-only -r dotenv/config scripts/export-users-to-auth0.js"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
|
252
scripts/delete-user.js
Normal file
252
scripts/delete-user.js
Normal file
|
@ -0,0 +1,252 @@
|
|||
const fsp = require("fs").promises;
|
||||
const path = require("path");
|
||||
|
||||
const argv = require("yargs").argv;
|
||||
const inquirer = require("inquirer");
|
||||
|
||||
const connectToDb = require("../src/server/db");
|
||||
|
||||
async function findUser(db, usernameOrEmail) {
|
||||
const [
|
||||
rows,
|
||||
_,
|
||||
] = await db.execute(
|
||||
"SELECT * FROM openneo_id.users WHERE name = ? OR email = ? LIMIT 1",
|
||||
[usernameOrEmail, usernameOrEmail]
|
||||
);
|
||||
if (rows.length === 0) {
|
||||
throw new Error("user not found");
|
||||
}
|
||||
|
||||
const user = rows[0];
|
||||
console.log(`Name: ${user.name}`);
|
||||
console.log(`Email: ${user.email}`);
|
||||
console.log(`Sign in count: ${user.sign_in_count}`);
|
||||
console.log(`Last sign in: ${user.last_sign_in_at}`);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const [usernameOrEmail] = argv._;
|
||||
|
||||
const { user, password } = await inquirer.prompt([
|
||||
{ name: "user", message: "MySQL admin user:" },
|
||||
{ name: "password", type: "password" },
|
||||
]);
|
||||
const db = await connectToDb({ user, password });
|
||||
|
||||
console.log("Loading ID user...");
|
||||
const idUser = await findUser(db, usernameOrEmail);
|
||||
console.log("Loading Impress user...");
|
||||
const impressUser = await findImpressUser(db, idUser.id);
|
||||
|
||||
console.log("Loading other user data... (1)");
|
||||
const [
|
||||
closetHangers,
|
||||
closetLists,
|
||||
contributions,
|
||||
neopetsConnections,
|
||||
outfits,
|
||||
] = await Promise.all([
|
||||
findAllForUser(db, impressUser.id, "closet_hangers"),
|
||||
findAllForUser(db, impressUser.id, "closet_lists"),
|
||||
findAllForUser(db, impressUser.id, "contributions"),
|
||||
findAllForUser(db, impressUser.id, "neopets_connections"),
|
||||
findAllForUser(db, impressUser.id, "outfits"),
|
||||
]);
|
||||
|
||||
console.log("Loading other user data... (2)");
|
||||
const itemOutfitRelationships = await findAllForOutfits(
|
||||
db,
|
||||
outfits.map((o) => o.id),
|
||||
"item_outfit_relationships"
|
||||
);
|
||||
|
||||
const userDataToExport = {
|
||||
idUser,
|
||||
impressUser,
|
||||
closetHangers,
|
||||
closetLists,
|
||||
contributions,
|
||||
neopetsConnections,
|
||||
outfits,
|
||||
itemOutfitRelationships,
|
||||
};
|
||||
|
||||
const userDataToExportAsJson = JSON.stringify(userDataToExport, null, 4);
|
||||
const userDataFilePath = path.join(
|
||||
__dirname,
|
||||
"exported-user-data",
|
||||
`${idUser.name}-${Date.now()}.json`
|
||||
);
|
||||
await fsp.writeFile(userDataFilePath, userDataToExportAsJson, "utf8");
|
||||
console.log(`Wrote to ${userDataFilePath}.`);
|
||||
|
||||
const { shouldDelete } = await inquirer.prompt([
|
||||
{
|
||||
type: "confirm",
|
||||
default: false,
|
||||
name: "shouldDelete",
|
||||
message: "Delete this user?",
|
||||
},
|
||||
]);
|
||||
|
||||
if (!shouldDelete) {
|
||||
console.log("Okay, we won't delete this user. Goodbye!");
|
||||
return;
|
||||
}
|
||||
|
||||
const { shouldDeleteConfirm } = await inquirer.prompt([
|
||||
{
|
||||
type: "confirm",
|
||||
default: false,
|
||||
name: "shouldDeleteConfirm",
|
||||
message: "Are you sure?",
|
||||
},
|
||||
]);
|
||||
|
||||
if (!shouldDeleteConfirm) {
|
||||
console.log("Okay, we won't delete this user. Goodbye!");
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
deleteAllForUser(db, impressUser.id, "closet_hangers"),
|
||||
deleteAllForUser(db, impressUser.id, "closet_lists"),
|
||||
deleteAllForUser(db, impressUser.id, "contributions"),
|
||||
deleteAllForUser(db, impressUser.id, "neopets_connections"),
|
||||
deleteAllForUser(db, impressUser.id, "outfits"),
|
||||
deleteAllForOutfits(
|
||||
db,
|
||||
outfits.map((o) => o.id),
|
||||
"item_outfit_relationships"
|
||||
),
|
||||
]);
|
||||
|
||||
await deleteImpressUser(db, idUser.id);
|
||||
await deleteUser(db, idUser.id);
|
||||
}
|
||||
|
||||
async function deleteUser(db, id) {
|
||||
const [
|
||||
results,
|
||||
_,
|
||||
] = await db.execute("DELETE FROM openneo_id.users WHERE id = ? LIMIT 1", [
|
||||
id,
|
||||
]);
|
||||
if (results.affectedRows === 0) {
|
||||
throw new Error("failed to delete impress user");
|
||||
}
|
||||
|
||||
console.log(` - Deleted user.`);
|
||||
}
|
||||
|
||||
async function findImpressUser(db, remoteId) {
|
||||
const [
|
||||
rows,
|
||||
_,
|
||||
] = await db.execute(
|
||||
"SELECT * FROM openneo_impress.users WHERE remote_id = ? LIMIT 1",
|
||||
[remoteId]
|
||||
);
|
||||
if (rows.length === 0) {
|
||||
throw new Error("impress user not found");
|
||||
}
|
||||
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
async function deleteImpressUser(db, remoteId) {
|
||||
const [
|
||||
results,
|
||||
_,
|
||||
] = await db.execute(
|
||||
"DELETE FROM openneo_impress.users WHERE remote_id = ? LIMIT 1",
|
||||
[remoteId]
|
||||
);
|
||||
if (results.affectedRows === 0) {
|
||||
throw new Error("failed to delete user");
|
||||
}
|
||||
|
||||
console.log(` - Deleted impress user.`);
|
||||
}
|
||||
|
||||
async function findAllForUser(db, impressUserId, table) {
|
||||
const [
|
||||
rows,
|
||||
_,
|
||||
] = await db.execute(
|
||||
`SELECT * FROM openneo_impress.${table} WHERE user_id = ?`,
|
||||
[impressUserId]
|
||||
);
|
||||
|
||||
console.log(` - Found ${rows.length} ${table}`);
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function deleteAllForUser(db, impressUserId, table) {
|
||||
const [
|
||||
results,
|
||||
_,
|
||||
] = await db.execute(
|
||||
`DELETE FROM openneo_impress.${table} WHERE user_id = ?`,
|
||||
[impressUserId]
|
||||
);
|
||||
|
||||
console.log(` - Deleted ${results.affectedRows} ${table}`);
|
||||
}
|
||||
|
||||
async function findAllForOutfits(db, outfitIds, table) {
|
||||
if (outfitIds.length === 0) {
|
||||
console.log(` - Skipped searching for ${table}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
// mysql2 doesn't seem to have a way to interpolate an array into a prepared
|
||||
// statement, so we create placeholder "?"s in the query to fill in! This
|
||||
// keeps us safe from injections, while still being lightweight.
|
||||
const placeholders = outfitIds.map(() => "?").join(", ");
|
||||
|
||||
const [
|
||||
rows,
|
||||
_,
|
||||
] = await db.execute(
|
||||
`SELECT * FROM openneo_impress.${table} WHERE outfit_id IN (${placeholders})`,
|
||||
[...outfitIds]
|
||||
);
|
||||
|
||||
console.log(` - Found ${rows.length} ${table}`);
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function deleteAllForOutfits(db, outfitIds, table) {
|
||||
if (outfitIds.length === 0) {
|
||||
console.log(` - Skipped deleting ${table}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
// mysql2 doesn't seem to have a way to interpolate an array into a prepared
|
||||
// statement, so we create placeholder "?"s in the query to fill in! This
|
||||
// keeps us safe from injections, while still being lightweight.
|
||||
const placeholders = outfitIds.map(() => "?").join(", ");
|
||||
|
||||
const [
|
||||
results,
|
||||
_,
|
||||
] = await db.execute(
|
||||
`DELETE FROM openneo_impress.${table} WHERE outfit_id IN (${placeholders})`,
|
||||
[...outfitIds]
|
||||
);
|
||||
|
||||
console.log(` - Deleted ${results.affectedRows} ${table}`);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.then(() => process.exit());
|
3
scripts/exported-user-data/.gitignore
vendored
Normal file
3
scripts/exported-user-data/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# This is for `delete-user.js` output! Don't publish anything in here!
|
||||
*
|
||||
!.gitignore
|
Loading…
Reference in a new issue