diff --git a/src/features/attributes/attributeGroups/attributeGroup.repository.js b/src/features/attributes/attributeGroups/attributeGroup.repository.js index 0b99816..c4076fd 100644 --- a/src/features/attributes/attributeGroups/attributeGroup.repository.js +++ b/src/features/attributes/attributeGroups/attributeGroup.repository.js @@ -14,8 +14,8 @@ export class AttributeGroupRepository { through: { attributes: ['display_order'] } } ], - order: [ - ['name', 'ASC'] + order: options.order || [ + ['created_at', 'DESC'] ] }); } diff --git a/src/features/attributes/attributeSets/attributeSet.repository.js b/src/features/attributes/attributeSets/attributeSet.repository.js index c01b006..fe89973 100644 --- a/src/features/attributes/attributeSets/attributeSet.repository.js +++ b/src/features/attributes/attributeSets/attributeSet.repository.js @@ -28,8 +28,8 @@ export class AttributeSetRepository { ] } ], - order: [ - ['name', 'ASC'] + order: options.order || [ + ['created_at', 'DESC'] ] }); } diff --git a/src/features/attributes/attributes/attribute.repository.js b/src/features/attributes/attributes/attribute.repository.js index 5be82c9..fec72b8 100644 --- a/src/features/attributes/attributes/attribute.repository.js +++ b/src/features/attributes/attributes/attribute.repository.js @@ -4,6 +4,7 @@ import { applyTenantScope, applyTenantWriteScope } from '../../../utils/helpers/ export class AttributeRepository { async findAll(options = {}, context = {}) { const queryOptions = { + order: [['created_at', 'DESC']], ...options, where: applyTenantScope(options.where || {}, context) }; diff --git a/src/features/attributes/attributes/attribute.service.js b/src/features/attributes/attributes/attribute.service.js index b2d42c3..a0ea841 100644 --- a/src/features/attributes/attributes/attribute.service.js +++ b/src/features/attributes/attributes/attribute.service.js @@ -55,7 +55,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]]; diff --git a/src/features/authentication/access/role.repository.js b/src/features/authentication/access/role.repository.js index 6a6afc3..158d129 100644 --- a/src/features/authentication/access/role.repository.js +++ b/src/features/authentication/access/role.repository.js @@ -15,6 +15,7 @@ export class RoleRepository { }; return await models.Role.findAll({ + order: [['created_at', 'DESC']], include: [ { model: models.PermissionNode, diff --git a/src/features/authentication/users/user.repository.js b/src/features/authentication/users/user.repository.js index 6f9be64..a7d6077 100644 --- a/src/features/authentication/users/user.repository.js +++ b/src/features/authentication/users/user.repository.js @@ -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: [ { diff --git a/src/features/brands/brands/brand.repository.js b/src/features/brands/brands/brand.repository.js index b40482a..832f13c 100644 --- a/src/features/brands/brands/brand.repository.js +++ b/src/features/brands/brands/brand.repository.js @@ -4,6 +4,7 @@ import { applyTenantScope, applyTenantWriteScope } from '../../../utils/helpers/ export class BrandRepository { async findAll(options = {}, context = {}) { const queryOptions = { + order: [['created_at', 'DESC']], ...options, where: applyTenantScope(options.where || {}, context) }; diff --git a/src/features/brands/units/unit.repository.js b/src/features/brands/units/unit.repository.js index f7df6e9..1b25b69 100644 --- a/src/features/brands/units/unit.repository.js +++ b/src/features/brands/units/unit.repository.js @@ -4,6 +4,7 @@ import { applyTenantScope, applyTenantWriteScope } from '../../../utils/helpers/ export class UnitRepository { async findAll(options = {}, context = {}) { const queryOptions = { + order: [['created_at', 'DESC']], ...options, where: applyTenantScope(options.where || {}, context) }; diff --git a/src/features/catalogs/catalogs/catalog.controller.js b/src/features/catalogs/catalogs/catalog.controller.js index 81f7b6f..250c216 100644 --- a/src/features/catalogs/catalogs/catalog.controller.js +++ b/src/features/catalogs/catalogs/catalog.controller.js @@ -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 || [], diff --git a/src/features/catalogs/catalogs/catalog.repository.js b/src/features/catalogs/catalogs/catalog.repository.js index 95d84a4..8c44a7b 100644 --- a/src/features/catalogs/catalogs/catalog.repository.js +++ b/src/features/catalogs/catalogs/catalog.repository.js @@ -57,8 +57,8 @@ export class CatalogRepository { ] } ], - order: [ - ['name', 'ASC'] + order: options.order || [ + ['created_at', 'DESC'] ], ...queryOptions }); diff --git a/src/features/catalogs/catalogs/catalog.service.js b/src/features/catalogs/catalogs/catalog.service.js index 852db55..091abf1 100644 --- a/src/features/catalogs/catalogs/catalog.service.js +++ b/src/features/catalogs/catalogs/catalog.service.js @@ -249,11 +249,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`); @@ -522,11 +526,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`); @@ -756,6 +766,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: [ @@ -778,8 +789,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 || []; } } @@ -834,7 +850,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 || [], @@ -845,7 +863,8 @@ export class CatalogService { workflow: workflow, allowedBrands: completenessRules.allowedBrands || [], allowedUnits: completenessRules.allowedUnits || [], - completenessRules: completenessRules + completenessRules: completenessRules, + productType: completenessRules.productType || null }; } diff --git a/src/features/catalogs/catalogs/catalog.validation.js b/src/features/catalogs/catalogs/catalog.validation.js index cfc0788..b3ad843 100644 --- a/src/features/catalogs/catalogs/catalog.validation.js +++ b/src/features/catalogs/catalogs/catalog.validation.js @@ -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 = [ diff --git a/src/features/channels/channelTypes/channelType.repository.js b/src/features/channels/channelTypes/channelType.repository.js index f073029..05b732b 100644 --- a/src/features/channels/channelTypes/channelType.repository.js +++ b/src/features/channels/channelTypes/channelType.repository.js @@ -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 = {}) { diff --git a/src/features/channels/channels/channel.repository.js b/src/features/channels/channels/channel.repository.js index b8c6c6d..d7402e0 100644 --- a/src/features/channels/channels/channel.repository.js +++ b/src/features/channels/channels/channel.repository.js @@ -4,7 +4,11 @@ import { applyTenantScope, applyTenantWriteScope } from '../../../utils/helpers/ 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 = {}) { diff --git a/src/features/integrations/workers/outbox.worker.js b/src/features/integrations/workers/outbox.worker.js index 40389fb..53ca1fc 100644 --- a/src/features/integrations/workers/outbox.worker.js +++ b/src/features/integrations/workers/outbox.worker.js @@ -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); }; diff --git a/src/features/media/assetFamilies/assetFamily.repository.js b/src/features/media/assetFamilies/assetFamily.repository.js index 849848d..6c8219b 100644 --- a/src/features/media/assetFamilies/assetFamily.repository.js +++ b/src/features/media/assetFamilies/assetFamily.repository.js @@ -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) { diff --git a/src/features/media/assetTypes/assetType.repository.js b/src/features/media/assetTypes/assetType.repository.js index 87836e0..fd27b7f 100644 --- a/src/features/media/assetTypes/assetType.repository.js +++ b/src/features/media/assetTypes/assetType.repository.js @@ -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 = {}) { diff --git a/src/features/products/products/product.repository.js b/src/features/products/products/product.repository.js index 8862e93..a867f54 100644 --- a/src/features/products/products/product.repository.js +++ b/src/features/products/products/product.repository.js @@ -10,6 +10,7 @@ const applyProductTenantScope = (where = {}, context = {}) => { export class ProductRepository { async findAll(options = {}, context = {}) { const queryOptions = { + order: [['created_at', 'DESC']], ...options, where: applyProductTenantScope(options.where || {}, context) }; @@ -68,79 +69,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'] } ] }, diff --git a/src/features/variants/variants/variant.repository.js b/src/features/variants/variants/variant.repository.js index dcd9384..15c0726 100644 --- a/src/features/variants/variants/variant.repository.js +++ b/src/features/variants/variants/variant.repository.js @@ -46,7 +46,7 @@ export class VariantRepository { // there is no global baseline variant that tenant workspaces should inherit. const where = applyTenantWriteScope(options.where || {}, context); return await models.Variant.findAll({ - order: [['sku', 'ASC']], + order: options.order || [['created_at', 'DESC']], include: options.include || getDefaultVariantIncludes(), ...options, where diff --git a/src/features/workflows/workflow.repository.js b/src/features/workflows/workflow.repository.js index 799c24c..dc52dbf 100644 --- a/src/features/workflows/workflow.repository.js +++ b/src/features/workflows/workflow.repository.js @@ -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 = {}) { diff --git a/src/shared/config/database.config.cjs b/src/shared/config/database.config.cjs index 2a5266f..0dc7fac 100644 --- a/src/shared/config/database.config.cjs +++ b/src/shared/config/database.config.cjs @@ -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: {