Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat : handlebars context var token replacement #823

Closed
wants to merge 6 commits into from
Closed
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
10 changes: 3 additions & 7 deletions api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions api/package.json
Original file line number Diff line number Diff line change
@@ -70,6 +70,7 @@
"dotenv": "^16.3.1",
"ejs": "^3.1.9",
"express-session": "^1.17.3",
"handlebars": "^4.7.8",
"module-alias": "^2.2.3",
"mongoose": "^8.0.0",
"mongoose-lean-defaults": "^2.2.1",
6 changes: 3 additions & 3 deletions api/src/chat/services/block.service.spec.ts
Original file line number Diff line number Diff line change
@@ -559,7 +559,7 @@ describe('BlockService', () => {

it('should process text replacements with ontext vars', () => {
const result = blockService.processText(
'{context.user.first_name} {context.user.last_name}, email : {context.vars.email}',
'{{context.user.first_name}} {{context.user.last_name}}, email : {{context.vars.email}}',
contextEmailVarInstance,
subscriberContext,
settings,
@@ -569,7 +569,7 @@ describe('BlockService', () => {

it('should process text replacements with context vars', () => {
const result = blockService.processText(
'{context.user.first_name} {context.user.last_name}, phone : {context.vars.phone}',
'{{context.user.first_name}} {{context.user.last_name}}, phone : {{context.vars.phone}}',
contextEmailVarInstance,
subscriberContext,
settings,
@@ -579,7 +579,7 @@ describe('BlockService', () => {

it('should process text replacements with settings contact infos', () => {
const result = blockService.processText(
'Trying the settings : the name of company is <<{contact.company_name}>>',
'Trying the settings : the name of company is <<{{contact.company_name}}>>',
contextBlankInstance,
subscriberContext,
settings,
97 changes: 56 additions & 41 deletions api/src/chat/services/block.service.ts
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@

import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import Handlebars from 'handlebars';

import { AttachmentService } from '@/attachment/services/attachment.service';
import EventWrapper from '@/channel/lib/EventWrapper';
@@ -389,57 +390,71 @@ export class BlockService extends BaseService<
subscriberContext: SubscriberContext,
settings: Settings,
): string {
const vars = { ...(subscriberContext?.vars || {}), ...context.vars };
// Replace context tokens with their values
Object.keys(vars).forEach((key) => {
if (typeof vars[key] === 'string' && vars[key].indexOf(':') !== -1) {
const tmp = vars[key].split(':');
vars[key] = tmp[1];
const mergedVars = { ...(subscriberContext?.vars || {}), ...context.vars };

// Process each var:
// - If a string contains a colon, take the substring after the colon.
// - If the value is not a string, JSON.stringify it.
const processedVars: { [key: string]: string } = {};
Object.keys(mergedVars).forEach((key) => {
let value = mergedVars[key];
if (typeof value === 'string' && value.indexOf(':') !== -1) {
const parts = value.split(':');
value = parts[1];
}
text = text.replace(
'{context.vars.' + key + '}',
typeof vars[key] === 'string' ? vars[key] : JSON.stringify(vars[key]),
);
if (typeof value !== 'string') {
value = JSON.stringify(value);
}
processedVars[key] = value;
});

// Replace context tokens about user location
// Process user_location if present
const processedUserLocation: any = {};
if (context.user_location) {
processedUserLocation.lat = context.user_location.lat.toString();
processedUserLocation.lon = context.user_location.lon.toString();

if (context.user_location.address) {
const userAddress = context.user_location.address;
Object.keys(userAddress).forEach((key) => {
text = text.replace(
'{context.user_location.address.' + key + '}',
typeof userAddress[key] === 'string'
? userAddress[key]
: JSON.stringify(userAddress[key]),
);
const processedAddress: { [key: string]: string } = {};
Object.keys(context.user_location.address).forEach((key) => {
let value = context.user_location.address![key];
if (typeof value !== 'string') {
value = JSON.stringify(value);
}
processedAddress[key] = value;
});
processedUserLocation.address = processedAddress;
}
text = text.replace(
'{context.user_location.lat}',
context.user_location.lat.toString(),
);
text = text.replace(
'{context.user_location.lon}',
context.user_location.lon.toString(),
);
}

// Replace tokens for user infos
Object.keys(context.user).forEach((key) => {
const userAttr = (context.user as any)[key];
text = text.replace(
'{context.user.' + key + '}',
typeof userAttr === 'string' ? userAttr : JSON.stringify(userAttr),
);
});

// Replace contact infos tokens with their values
Object.keys(settings.contact).forEach((key) => {
text = text.replace('{contact.' + key + '}', settings.contact[key]);
});
// Process user info tokens
const processedUser: { [key: string]: string } = {};
if (context.user) {
Object.keys(context.user).forEach((key) => {
let value = context.user![key];
if (typeof value !== 'string') {
value = JSON.stringify(value);
}
processedUser[key] = value;
});
}

return text;
// Process contact tokens from settings (assumed to be strings)
const processedContact = { ...settings.contact };

// Build the template context for Handlebars to match our token paths
const templateContext = {
context: {
vars: processedVars,
user_location: processedUserLocation,
user: processedUser,
},
contact: processedContact,
};

// Compile and run the Handlebars template
const compiledTemplate = Handlebars.compile(text);
return compiledTemplate(templateContext);
}

/**
Loading