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());