1 Commits
Author SHA1 Message Date
Mahir-Mohamed 00cf7aed76 feat: improve product catalog backend configuration 2026-09-08 14:23:58 +05:30
21 changed files with 116 additions and 86 deletions
@@ -14,8 +14,8 @@ export class AttributeGroupRepository {
through: { attributes: ['display_order'] }
}
],
order: [
['name', 'ASC']
order: options.order || [
['created_at', 'DESC']
]
});
}
@@ -28,8 +28,8 @@ export class AttributeSetRepository {
]
}
],
order: [
['name', 'ASC']
order: options.order || [
['created_at', 'DESC']
]
});
}
@@ -4,6 +4,7 @@ import { applyTenantScope } from '../../../utils/helpers/common.helper.js';
export class AttributeRepository {
async findAll(options = {}, context = {}) {
const queryOptions = {
order: [['created_at', 'DESC']],
...options,
where: applyTenantScope(options.where || {}, context)
};
@@ -54,7 +54,7 @@ export class AttributeService {
}
// Sorting
let order = [['display_order', 'ASC']];
let order = [['created_at', 'DESC']];
if (query.sortBy) {
const direction = query.sortDir?.toUpperCase() === 'DESC' ? 'DESC' : 'ASC';
if (query.sortBy === 'name') order = [['name', direction]];
@@ -15,6 +15,7 @@ export class RoleRepository {
};
return await models.Role.findAll({
order: [['created_at', 'DESC']],
include: [
{
model: models.PermissionNode,
@@ -8,6 +8,7 @@ export class UserRepository {
? { tenant_id: context.tenantId }
: {};
return await models.User.findAll({
order: [['created_at', 'DESC']],
attributes: { exclude: ['password_hash'] },
include: [
{
@@ -4,6 +4,7 @@ import { applyTenantScope } from '../../../utils/helpers/common.helper.js';
export class BrandRepository {
async findAll(options = {}, context = {}) {
const queryOptions = {
order: [['created_at', 'DESC']],
...options,
where: applyTenantScope(options.where || {}, context)
};
@@ -4,6 +4,7 @@ import { applyTenantScope } from '../../../utils/helpers/common.helper.js';
export class UnitRepository {
async findAll(options = {}, context = {}) {
const queryOptions = {
order: [['created_at', 'DESC']],
...options,
where: applyTenantScope(options.where || {}, context)
};
@@ -18,6 +18,7 @@ export class CatalogController {
: (raw.category_id || ''),
attributeSetId: raw.attribute_set_id || (raw.attributeSet ? raw.attributeSet.id : null) || '',
workflowCode: raw.workflow_code || 'standard',
productType: raw.completeness_rules?.productType || raw.completenessRules?.productType || raw.productType || raw.product_type || null,
completenessRules: raw.completeness_rules || {},
allowedBrands: raw.allowedBrands || [],
allowedUnits: raw.allowedUnits || [],
@@ -57,8 +57,8 @@ export class CatalogRepository {
]
}
],
order: [
['name', 'ASC']
order: options.order || [
['created_at', 'DESC']
],
...queryOptions
});
@@ -243,11 +243,15 @@ export class CatalogService {
const completenessRules = data.completenessRules || data.completeness_rules || {};
completenessRules.allowedBrands = data.allowedBrands || data.allowed_brands || [];
completenessRules.allowedUnits = data.allowedUnits || data.allowed_units || [];
const incomingProductType = data.productType || data.product_type || data.type;
if (incomingProductType) {
completenessRules.productType = incomingProductType;
}
let totalWeight = 0;
let hasRules = false;
for (const [key, val] of Object.entries(completenessRules)) {
if (key === 'allowedBrands' || key === 'allowedUnits') continue;
if (key === 'allowedBrands' || key === 'allowedUnits' || key === 'productType') continue;
const weight = Number(val);
if (isNaN(weight)) {
throw new Error(`Completeness rule weight for "${key}" must be a number`);
@@ -515,11 +519,17 @@ export class CatalogService {
if (data.hasOwnProperty('allowedUnits') || data.hasOwnProperty('allowed_units')) {
completenessRules.allowedUnits = data.allowedUnits || data.allowed_units || [];
}
if (data.hasOwnProperty('productType') || data.hasOwnProperty('product_type') || data.hasOwnProperty('type')) {
const pType = data.productType || data.product_type || data.type;
if (pType) {
completenessRules.productType = pType;
}
}
let totalWeight = 0;
let hasRules = false;
for (const [key, val] of Object.entries(completenessRules)) {
if (key === 'allowedBrands' || key === 'allowedUnits') continue;
if (key === 'allowedBrands' || key === 'allowedUnits' || key === 'productType') continue;
const weight = Number(val);
if (isNaN(weight)) {
throw new Error(`Completeness rule weight for "${key}" must be a number`);
@@ -748,6 +758,7 @@ export class CatalogService {
if (!family) throw new Error('Product Family not found');
let groups = [];
let attributeSetObj = null;
if (family.attribute_set_id) {
const setRecord = await models.AttributeSet.findByPk(family.attribute_set_id, {
include: [
@@ -770,8 +781,13 @@ export class CatalogService {
}
]
});
if (setRecord && setRecord.groups) {
groups = setRecord.groups;
if (setRecord) {
const setObj = setRecord.toJSON
? setRecord.toJSON()
: JSON.parse(JSON.stringify(setRecord));
attributeSetObj = setObj;
groups = setObj.groups || [];
}
}
@@ -826,7 +842,9 @@ export class CatalogService {
name: family.name,
description: family.description,
category: family.category,
attributeSet: family.attributeSet,
attributeSet: attributeSetObj || family.attributeSet || null,
attribute_set_id: family.attribute_set_id || null,
attributeSetId: family.attribute_set_id || null,
groups,
attributes: family.attributes || [],
variantAxes: family.variantAxes || [],
@@ -837,7 +855,8 @@ export class CatalogService {
workflow: workflow,
allowedBrands: completenessRules.allowedBrands || [],
allowedUnits: completenessRules.allowedUnits || [],
completenessRules: completenessRules
completenessRules: completenessRules,
productType: completenessRules.productType || null
};
}
@@ -48,7 +48,17 @@ export const createValidation = [
.isString(),
body('categoryId')
.optional({ nullable: true })
.isString()
.isString(),
body('productType')
.optional({ nullable: true })
.isIn(['simple', 'variant'])
.withMessage('Product type must be either simple or variant'),
body('product_type')
.optional({ nullable: true })
.isIn(['simple', 'variant']),
body('type')
.optional({ nullable: true })
.isIn(['simple', 'variant'])
];
export const updateValidation = [
@@ -103,7 +113,17 @@ export const updateValidation = [
.isString(),
body('categoryId')
.optional({ nullable: true })
.isString()
.isString(),
body('productType')
.optional({ nullable: true })
.isIn(['simple', 'variant'])
.withMessage('Product type must be either simple or variant'),
body('product_type')
.optional({ nullable: true })
.isIn(['simple', 'variant']),
body('type')
.optional({ nullable: true })
.isIn(['simple', 'variant'])
];
export const deleteValidation = [
@@ -4,7 +4,11 @@ import { applyTenantScope } from '../../../utils/helpers/common.helper.js';
export class ChannelTypeRepository {
async findAll(options = {}, context = {}) {
const where = applyTenantScope(options.where || {}, context);
return await models.ChannelType.findAll({ ...options, where });
return await models.ChannelType.findAll({
order: [['created_at', 'DESC']],
...options,
where
});
}
async findById(id, options = {}, context = {}) {
@@ -4,7 +4,11 @@ import { applyTenantScope } from '../../../utils/helpers/common.helper.js';
export class ChannelRepository {
async findAll(options = {}, context = {}) {
const where = applyTenantScope(options.where || {}, context);
return await models.Channel.findAll({ ...options, where });
return await models.Channel.findAll({
order: [['created_at', 'DESC']],
...options,
where
});
}
async findById(id, options = {}, context = {}) {
@@ -79,6 +79,21 @@ export const processOutboxEvents = async () => {
}
};
let isProcessing = false;
export const guardedProcessOutboxEvents = async () => {
if (isProcessing) {
return;
}
isProcessing = true;
try {
await processOutboxEvents();
} finally {
isProcessing = false;
}
};
export const startOutboxWorker = (intervalMs = 10000) => {
setInterval(processOutboxEvents, intervalMs);
setInterval(guardedProcessOutboxEvents, intervalMs);
};
@@ -14,7 +14,11 @@ export class AssetFamilyRepository {
async findAll(options = {}, context = {}) {
const where = applyTenantScope(options.where || {}, context);
const queryOptions = { ...options, where };
const queryOptions = {
order: [['created_at', 'DESC']],
...options,
where
};
try {
return await models.AssetFamily.findAll(queryOptions);
} catch (err) {
@@ -4,7 +4,11 @@ import { applyTenantScope } from '../../../utils/helpers/common.helper.js';
export class AssetTypeRepository {
async findAll(options = {}, context = {}) {
const where = applyTenantScope(options.where || {}, context);
return await models.AssetType.findAll({ ...options, where });
return await models.AssetType.findAll({
order: [['created_at', 'DESC']],
...options,
where
});
}
async findById(id, options = {}, context = {}) {
@@ -4,6 +4,7 @@ import { applyTenantScope } from '../../../utils/helpers/common.helper.js';
export class ProductRepository {
async findAll(options = {}, context = {}) {
const queryOptions = {
order: [['created_at', 'DESC']],
...options,
where: applyTenantScope(options.where || {}, context)
};
@@ -62,79 +63,16 @@ export class ProductRepository {
as: 'family',
required: false,
include: [
{
model: models.Attribute,
as: 'attributes',
through: { attributes: [] },
required: false,
include: [
{
model: models.AttributeOption,
as: 'optionsList',
attributes: ['id', 'code', 'label', 'sort_order', 'status'],
required: false
}
]
},
{
model: models.Attribute,
as: 'variantAxes',
through: { attributes: [] },
required: false,
include: [
{
model: models.AttributeOption,
as: 'optionsList',
attributes: ['id', 'code', 'label', 'sort_order', 'status'],
required: false
}
]
},
{
model: models.AssetFamily,
as: 'assetRequirements',
through: { attributes: [] },
required: false
},
{
model: models.FamilyChannel,
as: 'channels',
required: false
},
{
model: models.Categorie,
as: 'category',
attributes: ['id', 'name', 'code'],
required: false
},
{
model: models.AttributeSet,
as: 'attributeSet',
required: false,
include: [
{
model: models.AttributeGroup,
as: 'groups',
through: { attributes: [] },
required: false,
include: [
{
model: models.Attribute,
as: 'attributes',
through: { attributes: [] },
required: false,
include: [
{
model: models.AttributeOption,
as: 'optionsList',
attributes: ['id', 'code', 'label', 'sort_order', 'status'],
required: false
}
]
}
]
}
]
attributes: ['id', 'name']
}
]
},
@@ -44,7 +44,7 @@ export class VariantRepository {
async findAll(options = {}, context = {}) {
const where = applyTenantScope(options.where || {}, context);
return await models.Variant.findAll({
order: [['sku', 'ASC']],
order: options.order || [['created_at', 'DESC']],
include: options.include || getDefaultVariantIncludes(),
...options,
where
@@ -4,7 +4,11 @@ import { applyTenantScope } from '../../utils/helpers/common.helper.js';
export class WorkflowRepository {
async findAll(options = {}, context = {}) {
const where = applyTenantScope(options.where || {}, context);
return await models.WorkflowRegistry.findAll({ ...options, where });
return await models.WorkflowRegistry.findAll({
order: [['created_at', 'DESC']],
...options,
where
});
}
async findById(id, options = {}, context = {}) {
+12
View File
@@ -20,6 +20,12 @@ module.exports = {
port: process.env.DB_PORT || 5432,
dialect: process.env.DB_DIALECT || "postgres",
logging: false,
pool: {
max: 15,
min: 2,
acquire: 30000,
idle: 10000,
},
},
development: {
@@ -30,6 +36,12 @@ module.exports = {
port: process.env.DB_PORT || 5432,
dialect: process.env.DB_DIALECT || "postgres",
logging: false,
pool: {
max: 15,
min: 2,
acquire: 30000,
idle: 10000,
},
},
test: {