This commit is contained in:
Flatlogic Bot 2026-06-30 02:58:29 +00:00
parent 750d472c37
commit fd155575d4
5 changed files with 1284 additions and 320 deletions

View File

@ -0,0 +1,75 @@
'use strict';
const businessColumns = {
street_address: { type: 'TEXT' },
address_line_2: { type: 'TEXT' },
city: { type: 'TEXT' },
state: { type: 'TEXT' },
postal_code: { type: 'TEXT' },
country: { type: 'TEXT' },
website_url: { type: 'TEXT' },
business_phone: { type: 'TEXT' },
business_email: { type: 'TEXT' },
timezone: { type: 'TEXT' },
};
function normalizeColumnDefinition(Sequelize, definition) {
const normalized = { ...definition };
if (definition.type === 'TEXT') {
normalized.type = Sequelize.DataTypes.TEXT;
}
return normalized;
}
async function addColumnsIfMissing(queryInterface, Sequelize, transaction, tableName, columns) {
const table = await queryInterface.describeTable(tableName);
for (const [columnName, definition] of Object.entries(columns)) {
if (!table[columnName]) {
await queryInterface.addColumn(
tableName,
columnName,
normalizeColumnDefinition(Sequelize, definition),
{ transaction },
);
}
}
}
async function removeColumnsIfPresent(queryInterface, transaction, tableName, columns) {
const table = await queryInterface.describeTable(tableName);
for (const columnName of Object.keys(columns).reverse()) {
if (table[columnName]) {
await queryInterface.removeColumn(tableName, columnName, { transaction });
}
}
}
module.exports = {
async up(queryInterface, Sequelize) {
const transaction = await queryInterface.sequelize.transaction();
try {
await addColumnsIfMissing(queryInterface, Sequelize, transaction, 'businesses', businessColumns);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
},
async down(queryInterface) {
const transaction = await queryInterface.sequelize.transaction();
try {
await removeColumnsIfPresent(queryInterface, transaction, 'businesses', businessColumns);
await transaction.commit();
} catch (error) {
await transaction.rollback();
throw error;
}
},
};

View File

@ -15,6 +15,46 @@ name: {
},
street_address: {
type: DataTypes.TEXT,
},
address_line_2: {
type: DataTypes.TEXT,
},
city: {
type: DataTypes.TEXT,
},
state: {
type: DataTypes.TEXT,
},
postal_code: {
type: DataTypes.TEXT,
},
country: {
type: DataTypes.TEXT,
},
website_url: {
type: DataTypes.TEXT,
},
business_phone: {
type: DataTypes.TEXT,
},
business_email: {
type: DataTypes.TEXT,
},
timezone: {
type: DataTypes.TEXT,
},
google_review_link: {
type: DataTypes.TEXT,

View File

@ -108,6 +108,21 @@ function normalizeOptionalUrl(value, message) {
return normalized;
}
function normalizeOptionalWebsiteUrl(value) {
const normalized = normalizeString(value);
if (!normalized) {
return '';
}
const url = /^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(normalized)
? normalized
: `https://${normalized}`;
validateUrl(url, 'Website must be a valid URL.');
return url;
}
function normalizeOptionalDate(value, message) {
const normalized = normalizeString(value);
@ -136,11 +151,11 @@ function normalizeDefaultReviewPlatform(value, fallback = 'google') {
return DEFAULT_REVIEW_PLATFORMS.has(fallback) ? fallback : 'google';
}
function normalizeOptionalEmail(value) {
function normalizeOptionalEmail(value, message = 'Reply-to email must be a valid email address.') {
const normalized = normalizeString(value).toLowerCase();
if (normalized && !EMAIL_PATTERN.test(normalized)) {
const error = new Error('Reply-to email must be a valid email address.');
const error = new Error(message);
error.code = 400;
throw error;
}
@ -507,6 +522,19 @@ router.put('/growth-tools/business', wrapAsync(async (req, res) => {
business = await db.businesses.create({
name: businessName,
business_type: normalizeBusinessType(body.businessType || body.business_type, 'hybrid'),
street_address: normalizeString(body.streetAddress ?? body.street_address),
address_line_2: normalizeString(body.addressLine2 ?? body.address_line_2),
city: normalizeString(body.city),
state: normalizeString(body.state),
postal_code: normalizeString(body.postalCode ?? body.postal_code),
country: normalizeString(body.country),
website_url: normalizeOptionalWebsiteUrl(body.websiteUrl ?? body.website_url),
business_phone: normalizeString(body.businessPhone ?? body.business_phone),
business_email: normalizeOptionalEmail(
body.businessEmail ?? body.business_email,
'Business email must be a valid email address.',
),
timezone: normalizeString(body.timezone),
automation_mode: 'set_and_forget',
is_active: parseBoolean(body.isActive ?? body.is_active, true),
createdById: currentUser.id,
@ -557,6 +585,19 @@ router.put('/growth-tools/business', wrapAsync(async (req, res) => {
const updatePayload = {
name: businessName || business.name,
business_type: businessType,
street_address: normalizeString(body.streetAddress ?? body.street_address ?? business.street_address),
address_line_2: normalizeString(body.addressLine2 ?? body.address_line_2 ?? business.address_line_2),
city: normalizeString(body.city ?? business.city),
state: normalizeString(body.state ?? business.state),
postal_code: normalizeString(body.postalCode ?? body.postal_code ?? business.postal_code),
country: normalizeString(body.country ?? business.country),
website_url: normalizeOptionalWebsiteUrl(body.websiteUrl ?? body.website_url ?? business.website_url),
business_phone: normalizeString(body.businessPhone ?? body.business_phone ?? business.business_phone),
business_email: normalizeOptionalEmail(
body.businessEmail ?? body.business_email ?? business.business_email,
'Business email must be a valid email address.',
),
timezone: normalizeString(body.timezone ?? business.timezone),
automation_mode: normalizeString(body.automationMode || body.automation_mode) || 'set_and_forget',
ownerId,
review_destination: reviewDestination,

View File

@ -1013,6 +1013,16 @@ function serializeBusiness(req, business) {
return {
id: business.id,
name: business.name,
street_address: business.street_address || '',
address_line_2: business.address_line_2 || '',
city: business.city || '',
state: business.state || '',
postal_code: business.postal_code || '',
country: business.country || '',
website_url: business.website_url || '',
business_phone: business.business_phone || '',
business_email: business.business_email || '',
timezone: business.timezone || '',
ownerId: business.ownerId || '',
is_active: business.is_active !== false,
stripe_account_reference: business.stripe_account_reference || '',

File diff suppressed because it is too large Load Diff