Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 35 additions & 16 deletions src/api/routes/business.router.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { RouterBroker } from '@api/abstract/abstract.router';
import { NumberDto } from '@api/dto/chat.dto';
import { businessController } from '@api/server.module';
import { createMetaErrorResponse } from '@utils/errorResponse';
import { catalogSchema, collectionsSchema } from '@validate/validate.schema';
import { RequestHandler, Router } from 'express';

Expand All @@ -11,25 +12,43 @@ export class BusinessRouter extends RouterBroker {
super();
this.router
.post(this.routerPath('getCatalog'), ...guards, async (req, res) => {
const response = await this.dataValidate<NumberDto>({
request: req,
schema: catalogSchema,
ClassRef: NumberDto,
execute: (instance, data) => businessController.fetchCatalog(instance, data),
});

return res.status(HttpStatus.OK).json(response);
try {
const response = await this.dataValidate<NumberDto>({
request: req,
schema: catalogSchema,
ClassRef: NumberDto,
execute: (instance, data) => businessController.fetchCatalog(instance, data),
});

return res.status(HttpStatus.OK).json(response);
} catch (error) {
// Log error for debugging
console.error('Business catalog error:', error);

// Use utility function to create standardized error response
const errorResponse = createMetaErrorResponse(error, 'business_catalog');
return res.status(errorResponse.status).json(errorResponse);
}
})

.post(this.routerPath('getCollections'), ...guards, async (req, res) => {
const response = await this.dataValidate<NumberDto>({
request: req,
schema: collectionsSchema,
ClassRef: NumberDto,
execute: (instance, data) => businessController.fetchCollections(instance, data),
});

return res.status(HttpStatus.OK).json(response);
try {
const response = await this.dataValidate<NumberDto>({
request: req,
schema: collectionsSchema,
ClassRef: NumberDto,
execute: (instance, data) => businessController.fetchCollections(instance, data),
});

return res.status(HttpStatus.OK).json(response);
} catch (error) {
// Log error for debugging
console.error('Business collections error:', error);

// Use utility function to create standardized error response
const errorResponse = createMetaErrorResponse(error, 'business_collections');
return res.status(errorResponse.status).json(errorResponse);
}
});
}

Expand Down
47 changes: 33 additions & 14 deletions src/api/routes/template.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { InstanceDto } from '@api/dto/instance.dto';
import { TemplateDto } from '@api/dto/template.dto';
import { templateController } from '@api/server.module';
import { ConfigService } from '@config/env.config';
import { createMetaErrorResponse } from '@utils/errorResponse';
import { instanceSchema, templateSchema } from '@validate/validate.schema';
import { RequestHandler, Router } from 'express';

Expand All @@ -16,24 +17,42 @@ export class TemplateRouter extends RouterBroker {
super();
this.router
.post(this.routerPath('create'), ...guards, async (req, res) => {
const response = await this.dataValidate<TemplateDto>({
request: req,
schema: templateSchema,
ClassRef: TemplateDto,
execute: (instance, data) => templateController.createTemplate(instance, data),
});
try {
const response = await this.dataValidate<TemplateDto>({
request: req,
schema: templateSchema,
ClassRef: TemplateDto,
execute: (instance, data) => templateController.createTemplate(instance, data),
});

res.status(HttpStatus.CREATED).json(response);
res.status(HttpStatus.CREATED).json(response);
} catch (error) {
// Log error for debugging
console.error('Template creation error:', error);

// Use utility function to create standardized error response
const errorResponse = createMetaErrorResponse(error, 'template_creation');
res.status(errorResponse.status).json(errorResponse);
}
})
.get(this.routerPath('find'), ...guards, async (req, res) => {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => templateController.findTemplate(instance),
});
try {
const response = await this.dataValidate<InstanceDto>({
request: req,
schema: instanceSchema,
ClassRef: InstanceDto,
execute: (instance) => templateController.findTemplate(instance),
});

res.status(HttpStatus.OK).json(response);
} catch (error) {
// Log error for debugging
console.error('Template find error:', error);

res.status(HttpStatus.OK).json(response);
// Use utility function to create standardized error response
const errorResponse = createMetaErrorResponse(error, 'template_find');
res.status(errorResponse.status).json(errorResponse);
}
});
}

Expand Down
26 changes: 22 additions & 4 deletions src/api/services/template.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ export class TemplateService {
const response = await this.requestTemplate(postData, 'POST');

if (!response || response.error) {
// If there's an error from WhatsApp API, throw it with the real error data
if (response && response.error) {
// Create an error object that includes the template field for Meta errors
const metaError = new Error(response.error.message || 'WhatsApp API Error');
(metaError as any).template = response.error;
throw metaError;
}
throw new Error('Error to create template');
}

Expand All @@ -75,8 +82,9 @@ export class TemplateService {

return template;
} catch (error) {
this.logger.error(error);
throw new Error('Error to create template');
this.logger.error('Error in create template: ' + error);
// Propagate the real error instead of "engolindo" it
throw error;
}
}

Expand All @@ -86,6 +94,7 @@ export class TemplateService {
const version = this.configService.get<WaBusiness>('WA_BUSINESS').VERSION;
urlServer = `${urlServer}/${version}/${this.businessId}/message_templates`;
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${this.token}` };

if (method === 'GET') {
const result = await axios.get(urlServer, { headers });
return result.data;
Expand All @@ -94,8 +103,17 @@ export class TemplateService {
return result.data;
}
} catch (e) {
this.logger.error(e.response.data);
return e.response.data.error;
this.logger.error(
'WhatsApp API request error: ' + (e.response?.data ? JSON.stringify(e.response?.data) : e.message),
);

// Return the complete error response from WhatsApp API
if (e.response?.data) {
return e.response.data;
}

// If no response data, throw connection error
throw new Error(`Connection error: ${e.message}`);
}
}
}
47 changes: 47 additions & 0 deletions src/utils/errorResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { HttpStatus } from '@api/routes/index.router';

export interface MetaErrorResponse {
status: number;
error: string;
message: string;
details: {
whatsapp_error: string;
whatsapp_code: string | number;
error_user_title: string;
error_user_msg: string;
error_type: string;
error_subcode: number | null;
fbtrace_id: string | null;
context: string;
type: string;
};
timestamp: string;
}

/**
* Creates standardized error response for Meta/WhatsApp API errors
*/
export function createMetaErrorResponse(error: any, context: string): MetaErrorResponse {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The error response always uses BAD_REQUEST status, which may not fit all error scenarios.

Allow the status code to be set dynamically based on the error type, so responses can accurately reflect server or authorization errors.

Suggested implementation:

/**
 * Creates standardized error response for Meta/WhatsApp API errors
 * @param error - The error object from Meta/WhatsApp API
 * @param context - Context string for the error
 * @param status - Optional HTTP status code (defaults to BAD_REQUEST)
 * @param errorString - Optional error string (defaults to 'Bad Request')
 */
export function createMetaErrorResponse(
  error: any,
  context: string,
  status: number = HttpStatus.BAD_REQUEST,
  errorString: string = 'Bad Request'
): MetaErrorResponse {
  // Extract Meta/WhatsApp specific error fields
  const metaError = error.template || error;
  const errorUserTitle = metaError.error_user_title || metaError.message || 'Unknown error';
  const errorUserMsg = metaError.error_user_msg || metaError.message || 'Unknown error';

  return {
    status: status,
    error: errorString,
    message: errorUserTitle,
    details: {
      whatsapp_error: errorUserMsg,
      whatsapp_code: metaError.code || 'UNKNOWN_ERROR',

You will need to update all usages of createMetaErrorResponse to optionally pass the status and errorString parameters where appropriate, e.g.:

createMetaErrorResponse(error, context, HttpStatus.UNAUTHORIZED, 'Unauthorized')

// Extract Meta/WhatsApp specific error fields
const metaError = error.template || error;
const errorUserTitle = metaError.error_user_title || metaError.message || 'Unknown error';
const errorUserMsg = metaError.error_user_msg || metaError.message || 'Unknown error';

return {
status: HttpStatus.BAD_REQUEST,
error: 'Bad Request',
message: errorUserTitle,
details: {
whatsapp_error: errorUserMsg,
whatsapp_code: metaError.code || 'UNKNOWN_ERROR',
error_user_title: errorUserTitle,
error_user_msg: errorUserMsg,
error_type: metaError.type || 'UNKNOWN',
error_subcode: metaError.error_subcode || null,
fbtrace_id: metaError.fbtrace_id || null,
context,
type: 'whatsapp_api_error',
},
timestamp: new Date().toISOString(),
};
}