Files
bodyshop/server/integrations/partsManagement/endpoints/partsManagementDeprovisioning.js

188 lines
5.6 KiB
JavaScript

const admin = require("firebase-admin");
const client = require("../../../graphql-client/graphql-client").client;
const {
DELETE_SHOP,
DELETE_VENDORS_BY_SHOP,
GET_BODYSHOP,
GET_ASSOCIATED_USERS,
DELETE_ASSOCIATIONS_BY_SHOP,
GET_USER_ASSOCIATIONS_COUNT,
DELETE_USER,
GET_VENDORS,
GET_JOBS_BY_SHOP,
DELETE_JOBLINES_BY_JOB_IDS,
DELETE_JOBS_BY_IDS,
DELETE_AUDIT_TRAIL_BY_SHOP
} = require("../partsManagement.queries");
/**
* Deletes a Firebase user by UID.
* @param uid
* @returns {Promise<void>}
*/
const deleteFirebaseUser = async (uid) => {
return admin.auth().deleteUser(uid);
};
/**
* Deletes all vendors associated with a shop.
* @param shopId
* @returns {Promise<void>}
*/
const deleteVendorsByShop = async (shopId) => {
await client.request(DELETE_VENDORS_BY_SHOP, { shopId });
};
/**
* Deletes a bodyshop from the database.
* @param shopId
* @returns {Promise<void>}
*/
const deleteBodyshop = async (shopId) => {
await client.request(DELETE_SHOP, { id: shopId });
};
/**
* Fetch job ids for a given shop
* @param shopId
* @returns {Promise<string[]>}
*/
const getJobIdsForShop = async (shopId) => {
const resp = await client.request(GET_JOBS_BY_SHOP, { shopId });
return resp.jobs.map((j) => j.id);
};
/**
* Delete joblines for the given job ids
* @param jobIds {string[]}
* @returns {Promise<number>} affected rows
*/
const deleteJoblinesForJobs = async (jobIds) => {
if (!jobIds.length) return 0;
const resp = await client.request(DELETE_JOBLINES_BY_JOB_IDS, { jobIds });
return resp.delete_joblines.affected_rows;
};
/**
* Delete jobs for the given job ids
* @param jobIds {string[]}
* @returns {Promise<number>} affected rows
*/
const deleteJobsByIds = async (jobIds) => {
if (!jobIds.length) return 0;
const resp = await client.request(DELETE_JOBS_BY_IDS, { jobIds });
return resp.delete_jobs.affected_rows;
};
/**
* Handles deprovisioning a shop for parts management.
* @param req
* @param res
* @returns {Promise<*>}
*/
const partsManagementDeprovisioning = async (req, res) => {
const { logger } = req;
const p = req.body;
if (process.env.NODE_ENV === "production") {
return res.status(403).json({ error: "Deprovisioning not allowed in production environment." });
}
try {
if (!p.shopId) {
throw { status: 400, message: "shopId is required." };
}
// Fetch bodyshop and check external_shop_id
const shopResp = await client.request(GET_BODYSHOP, { id: p.shopId });
const shop = shopResp.bodyshops_by_pk;
if (!shop) {
throw { status: 404, message: `Bodyshop with id ${p.shopId} not found.` };
}
if (!shop.external_shop_id) {
throw { status: 400, message: "Cannot delete bodyshop without external_shop_id." };
}
logger.log("admin-delete-shop", "debug", null, null, {
shopId: p.shopId,
shopname: shop.shopname,
ioadmin: true
});
// Get vendors
const vendorsResp = await client.request(GET_VENDORS, { shopId: p.shopId });
const deletedVendors = vendorsResp.vendors.map((v) => v.name);
// Get associated users
const assocResp = await client.request(GET_ASSOCIATED_USERS, { shopId: p.shopId });
const associatedUsers = assocResp.associations.map((assoc) => ({
authId: assoc.user.authid,
email: assoc.user.email
}));
// Delete associations for the shop
const assocDeleteResp = await client.request(DELETE_ASSOCIATIONS_BY_SHOP, { shopId: p.shopId });
const associationsDeleted = assocDeleteResp.delete_associations.affected_rows;
// For each user, check if they have remaining associations; if not, delete user and Firebase account
const deletedUsers = [];
for (const user of associatedUsers) {
const countResp = await client.request(GET_USER_ASSOCIATIONS_COUNT, { userEmail: user.email });
const assocCount = countResp.associations_aggregate.aggregate.count;
if (assocCount === 0) {
await client.request(DELETE_USER, { email: user.email });
await deleteFirebaseUser(user.authId);
deletedUsers.push(user.email);
}
}
// Get all job ids for this shop, then delete joblines and jobs (joblines first)
const jobIds = await getJobIdsForShop(p.shopId);
const joblinesDeleted = await deleteJoblinesForJobs(jobIds);
const jobsDeleted = await deleteJobsByIds(jobIds);
// Delete any audit trail entries tied to this bodyshop to avoid FK violations
const auditResp = await client.request(DELETE_AUDIT_TRAIL_BY_SHOP, { shopId: p.shopId });
const auditDeleted = auditResp.delete_audit_trail.affected_rows;
// Delete vendors
await deleteVendorsByShop(p.shopId);
// Delete shop
await deleteBodyshop(p.shopId);
// Summary log
logger.log("admin-delete-shop-summary", "info", null, null, {
shopId: p.shopId,
shopname: shop.shopname,
associationsDeleted,
deletedUsers,
deletedVendors,
joblinesDeleted,
jobsDeleted,
auditDeleted
});
return res.status(200).json({
message: `Bodyshop ${p.shopId} and associated resources deleted successfully.`,
deletedShop: { id: p.shopId, name: shop.shopname },
deletedAssociationsCount: associationsDeleted,
deletedUsers: deletedUsers,
deletedVendors: deletedVendors,
deletedJoblinesCount: joblinesDeleted,
deletedJobsCount: jobsDeleted,
deletedAuditTrailCount: auditDeleted
});
} catch (err) {
logger.log("admin-delete-shop-error", "error", null, null, {
message: err.message,
detail: err.detail || err
});
return res.status(err.status || 500).json({ error: err.message || "Internal server error" });
}
};
module.exports = partsManagementDeprovisioning;