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

263 lines
7.1 KiB
JavaScript

const admin = require("firebase-admin");
const client = require("../../../graphql-client/graphql-client").client;
const DefaultNewShop = require("../defaultNewShop.json");
const {
CHECK_EXTERNAL_SHOP_ID,
CREATE_SHOP,
DELETE_VENDORS_BY_SHOP,
DELETE_SHOP,
CREATE_USER
} = require("../partsManagement.queries");
/**
* Checks if the required fields are present in the payload.
* @param payload
* @param fields
*/
const requireFields = (payload, fields) => {
for (const field of fields) {
if (!payload[field]) {
throw { status: 400, message: `${field} is required.` };
}
}
};
/**
* Ensures that the provided email is not already registered in Firebase.
* @param email
* @returns {Promise<void>}
*/
const ensureEmailNotRegistered = async (email) => {
try {
await admin.auth().getUserByEmail(email);
throw { status: 400, message: "userEmail is already registered in Firebase." };
} catch (err) {
if (err.code !== "auth/user-not-found") {
throw { status: 500, message: "Error validating userEmail uniqueness", detail: err };
}
}
};
/**
* Creates a new Firebase user with the given email and optional password.
* @param email
* @param password
* @returns {Promise<UserRecord>}
*/
const createFirebaseUser = async (email, password = null) => {
const userData = { email };
if (password) userData.password = password;
return admin.auth().createUser(userData);
};
/**
* Deletes a Firebase user by UID.
* @param uid
* @returns {Promise<void>}
*/
const deleteFirebaseUser = async (uid) => {
return admin.auth().deleteUser(uid);
};
/**
* Generates a password reset link for the given email.
* @param email
* @returns {Promise<string>}
*/
const generateResetLink = async (email) => {
return admin.auth().generatePasswordResetLink(email);
};
/**
* Ensures that the provided external shop ID is unique.
* @param externalId
* @returns {Promise<void>}
*/
const ensureExternalIdUnique = async (externalId) => {
const resp = await client.request(CHECK_EXTERNAL_SHOP_ID, { key: externalId });
if (resp.bodyshops.length) {
throw { status: 400, message: `external_shop_id '${externalId}' is already in use.` };
}
};
/**
* Inserts a new bodyshop into the database.
* @param input
* @returns {Promise<*>}
*/
const insertBodyshop = async (input) => {
const resp = await client.request(CREATE_SHOP, { bs: input });
return resp.insert_bodyshops_one.id;
};
/**
* 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 });
};
/**
* Inserts a new user association into the database.
* @param uid
* @param email
* @param shopId
* @returns {Promise<*>}
*/
const insertUserAssociation = async (uid, email, shopId) => {
const vars = {
u: {
email,
authid: uid,
validemail: true,
associations: {
data: [{ shopid: shopId, authlevel: 80, active: true }]
}
}
};
const resp = await client.request(CREATE_USER, vars);
return resp.insert_users_one;
};
/**
* Handles provisioning a new shop for parts management.
* @param req
* @param res
* @returns {Promise<*>}
*/
const partsManagementProvisioning = async (req, res) => {
const { logger } = req;
const p = { ...req.body, userEmail: req.body.userEmail?.toLowerCase() };
try {
await ensureEmailNotRegistered(p.userEmail);
requireFields(p, [
"external_shop_id",
"shopname",
"address1",
"city",
"state",
"zip_post",
"country",
"email",
"phone",
"userEmail"
]);
await ensureExternalIdUnique(p.external_shop_id);
logger.log("admin-create-shop-user", "debug", p.userEmail, null, {
request: req.body,
ioadmin: true
});
const shopInput = {
shopname: p.shopname,
address1: p.address1,
address2: p.address2 || null,
city: p.city,
state: p.state,
zip_post: p.zip_post,
country: p.country,
email: p.email,
external_shop_id: p.external_shop_id,
timezone: p.timezone || DefaultNewShop.timezone,
phone: p.phone,
logo_img_path: {
src: p.logoUrl,
width: "",
height: "",
headerMargin: DefaultNewShop.logo_img_path.headerMargin
},
features: {
allAccess: false,
partsManagementOnly: true
},
md_ro_statuses: DefaultNewShop.md_ro_statuses,
md_order_statuses: DefaultNewShop.md_order_statuses,
md_responsibility_centers: DefaultNewShop.md_responsibility_centers,
md_referral_sources: DefaultNewShop.md_referral_sources,
md_messaging_presets: DefaultNewShop.md_messaging_presets,
md_rbac: DefaultNewShop.md_rbac,
md_classes: DefaultNewShop.md_classes,
md_ins_cos: DefaultNewShop.md_ins_cos,
md_categories: DefaultNewShop.md_categories,
md_labor_rates: DefaultNewShop.md_labor_rates,
md_payment_types: DefaultNewShop.md_payment_types,
md_hour_split: DefaultNewShop.md_hour_split,
md_ccc_rates: DefaultNewShop.md_ccc_rates,
appt_alt_transport: DefaultNewShop.appt_alt_transport,
md_jobline_presets: DefaultNewShop.md_jobline_presets,
vendors: {
data: p.vendors.map((v) => ({
name: v.name,
street1: v.street1 || null,
street2: v.street2 || null,
city: v.city || null,
state: v.state || null,
zip: v.zip || null,
country: v.country || null,
email: v.email || null,
discount: v.discount ?? 0,
due_date: v.due_date ?? null,
cost_center: v.cost_center || null,
favorite: v.favorite ?? [],
phone: v.phone || null,
active: v.active ?? true,
dmsid: v.dmsid || null
}))
}
};
const newShopId = await insertBodyshop(shopInput);
const userRecord = await createFirebaseUser(p.userEmail, p.userPassword);
let resetLink = null;
if (!p.userPassword) resetLink = await generateResetLink(p.userEmail);
const createdUser = await insertUserAssociation(userRecord.uid, p.userEmail, newShopId);
return res.status(200).json({
shop: { id: newShopId, shopname: p.shopname },
user: {
id: createdUser.id,
email: createdUser.email,
resetLink: resetLink || undefined
}
});
} catch (err) {
logger.log("admin-create-shop-user-error", "error", p.userEmail, null, {
message: err.message,
detail: err.detail || err
});
if (err.userRecord) {
await deleteFirebaseUser(err.userRecord.uid).catch(() => {
/* empty */
});
}
if (err.newShopId) {
await deleteVendorsByShop(err.newShopId).catch(() => {
/* empty */
});
await deleteBodyshop(err.newShopId).catch(() => {
/* empty */
});
}
return res.status(err.status || 500).json({ error: err.message || "Internal server error" });
}
};
module.exports = partsManagementProvisioning;