Matchu
197c623426
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!!
252 lines
6.2 KiB
JavaScript
252 lines
6.2 KiB
JavaScript
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());
|