fix(channels): resolve type and channel references safely

This commit is contained in:
Inamul-hasan-tec
2026-08-31 18:13:08 +05:30
parent 8ade613d5f
commit 64cffdd7ba
2 changed files with 31 additions and 7 deletions
@@ -5,6 +5,19 @@ import { AuditService } from '../../../shared/services/audit.service.js';
import { ApiError } from '../../../utils/helpers/ApiError.utils.js';
import { generateUniqueCode } from '../../../utils/helpers/code.utils.js';
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
async function resolveChannelTypeId(channelType) {
if (!channelType) return null;
if (UUID_PATTERN.test(channelType)) return channelType;
const type = await models.ChannelType.findOne({ where: { code: channelType } });
if (!type) {
throw new ApiError(400, `Unknown channel type: ${channelType}`);
}
return type.id;
}
export class ChannelService {
async getAll(query = {}, context = {}) {
// Add business logic filtering, pagination, etc.
@@ -22,7 +35,7 @@ export class ChannelService {
async create(data, userContext = {}) {
const payload = { ...data };
if (payload.channelType) {
payload.type_id = payload.channelType;
payload.type_id = await resolveChannelTypeId(payload.channelType);
delete payload.channelType;
}
if (payload.allowPublishing !== undefined) {
@@ -52,7 +65,7 @@ export class ChannelService {
async update(id, data, userContext = {}) {
const payload = { ...data };
if (payload.channelType) {
payload.type_id = payload.channelType;
payload.type_id = await resolveChannelTypeId(payload.channelType);
delete payload.channelType;
}
if (payload.allowPublishing !== undefined) {
@@ -12,6 +12,8 @@ import { executeGenericWebhook } from '../channels/syndication/genericWebhookCon
import { models } from '../../shared/database/models.js';
import { normalizeShopDomain, testShopifyConnection } from '../channels/syndication/shopifyConnector.service.js';
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const RESERVED_FIELDS = new Set([
'id', 'tenant_id', 'created_at', 'updated_at', 'deleted_at', 'name',
'description', 'channel', 'integrationType', 'integration_type', 'environment',
@@ -77,10 +79,15 @@ function baseRecord(payload, context) {
};
}
async function assertTenantChannel(channelId, context) {
if (!channelId) return;
const channel = await models.Channel.findOne({ where: { id: channelId, tenant_id: context.tenantId } });
async function resolveTenantChannel(channelReference, context) {
if (!channelReference) return null;
const channel = await models.Channel.findOne({
where: UUID_PATTERN.test(channelReference)
? { id: channelReference, tenant_id: context.tenantId }
: { code: channelReference, tenant_id: context.tenantId }
});
if (!channel) throw new ApiError(400, 'Selected Channel does not belong to this tenant');
return channel;
}
function normalizeGenericRestPayload(payload) {
@@ -113,7 +120,8 @@ export class IntegrationService {
async create(payload, context) {
if (!payload.name) throw new ApiError(400, 'Integration name is required');
await assertTenantChannel(payload.channel, context);
const channel = await resolveTenantChannel(payload.channel, context);
payload = { ...payload, channel: channel?.id || null };
if ((payload.integrationType || payload.integration_type) === 'shopify') {
payload = { ...payload, storeUrl: `https://${normalizeShopDomain(payload.storeUrl)}`, apiVersion: '2026-07' };
if (!payload.clientId || !payload.clientSecret) throw new ApiError(400, 'Shopify Client ID and Client Secret are required');
@@ -132,7 +140,10 @@ export class IntegrationService {
async update(id, payload, context) {
const record = await repository.findById(id, context);
if (!record) throw new ApiError(404, 'Integration not found');
if (payload.channel !== undefined) await assertTenantChannel(payload.channel, context);
if (payload.channel !== undefined) {
const channel = await resolveTenantChannel(payload.channel, context);
payload = { ...payload, channel: channel?.id || null };
}
if ((payload.integrationType || record.integration_type) === 'shopify') {
const storeUrl = payload.storeUrl ?? record.config?.storeUrl;
payload = { ...payload, storeUrl: `https://${normalizeShopDomain(storeUrl)}`, apiVersion: '2026-07' };