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
41 changes: 24 additions & 17 deletions src/configs/constant.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ const redis_keys = {
last_transffered_agent_: "last_transffered_agent_"
};

const embed_cache = {
keys: {
folder: (folderId) => `embed:folder_${folderId}`,
org: (orgId) => `embed:org_${orgId}`,
user: (userId, orgId) => `embed:user_${userId}:${orgId}`
}
};

const cost_types = {
bridge: "bridge",
folder: "folder",
Expand All @@ -78,7 +86,7 @@ const new_agent_service = {
grok: "grok-4-fast"
};

export { collectionNames, bridge_ids, redis_keys, cost_types, prebuilt_prompt_bridge_id, new_agent_service };
export { collectionNames, bridge_ids, redis_keys, cost_types, prebuilt_prompt_bridge_id, new_agent_service, embed_cache };

export const AI_OPERATION_CONFIG = {
optimize_prompt: {
Expand Down Expand Up @@ -206,29 +214,27 @@ export const AI_OPERATION_CONFIG = {
// ── Generic binding mode (new: itemTemplate + binding + itemAlias) ──
if (node.type === "ListView" && node.binding && node.itemTemplate) {
// binding may be a direct key ("rows") or a placeholder ("{{trips}}")
const bpMatch = typeof node.binding === "string"
? node.binding.match(/^\{\{([\w.]+)\}\}$/) : null;
const bpMatch = typeof node.binding === "string" ? node.binding.match(/^\{\{([\w.]+)\}\}$/) : null;
const bindingKey = bpMatch ? bpMatch[1] : node.binding;
const listData = getValue(context, bindingKey);
if (Array.isArray(listData)) {
const alias = node.itemAlias || "item";
const siblingScope = Object.fromEntries(
Object.entries(context).filter(([k]) => k !== bindingKey)
);
const siblingScope = Object.fromEntries(Object.entries(context).filter(([k]) => k !== bindingKey));
const resolvedChildren = listData.map((item) => {
const itemContext = { ...context, ...siblingScope, [alias]: item };
return resolve(node.itemTemplate, itemContext);
});
const rest = Object.fromEntries(Object.entries(node).filter(([k]) => !['itemTemplate','binding','itemAlias','idField'].includes(k)));
const rest = Object.fromEntries(
Object.entries(node).filter(([k]) => !["itemTemplate", "binding", "itemAlias", "idField"].includes(k))
);
return { ...rest, children: resolvedChildren };
}
}

// ── Legacy binding mode (children[0] as template, binding may be {{placeholder}}) ──
if (node.type === "ListView" && node.binding && !node.itemTemplate) {
// Unwrap "{{trips}}" → "trips", or use direct key "rows" as-is
const bpMatch = typeof node.binding === "string"
? node.binding.match(/^\{\{([\w.]+)\}\}$/) : null;
const bpMatch = typeof node.binding === "string" ? node.binding.match(/^\{\{([\w.]+)\}\}$/) : null;
const bindingKey = bpMatch ? bpMatch[1] : node.binding;
const listData = getValue(context, bindingKey);
if (Array.isArray(listData) && node.children?.length > 0) {
Expand All @@ -244,9 +250,10 @@ export const AI_OPERATION_CONFIG = {
}
}
const resolvedChildren = listData.map((item) => {
const localContext = (item && typeof item === "object" && !Array.isArray(item))
? { ...context, ...item, [localKey]: item }
: { ...context, [localKey]: item };
const localContext =
item && typeof item === "object" && !Array.isArray(item)
? { ...context, ...item, [localKey]: item }
: { ...context, [localKey]: item };
return resolve(itemTemplate, localContext);
});
return { ...node, children: resolvedChildren };
Expand All @@ -272,11 +279,11 @@ export const AI_OPERATION_CONFIG = {
return {
success: true,
message: "Rich UI template generated successfully",
result: ui,
ui,
variables,
template_format: originalRawUi,
json_schema: originalRawUi ? buildSchemaFromTemplateFormat(originalRawUi, {}, variables ?? {}) : null,
result: ui,
ui,
variables,
template_format: originalRawUi,
json_schema: originalRawUi ? buildSchemaFromTemplateFormat(originalRawUi, {}, variables ?? {}) : null
};
}
},
Expand Down
7 changes: 4 additions & 3 deletions src/controllers/embed.controller.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import ConfigurationServices from "../db_services/configuration.service.js";
import folderService from "../db_services/folder.service.js";
import FolderModel from "../mongoModel/GtwyEmbed.model.js";
import configurationModel from "../mongoModel/Configuration.model.js";
import { createProxyToken, getOrganizationById, updateOrganizationData } from "../services/proxy.service.js";
import { generateIdentifier } from "../services/utils/utility.service.js";
import { cleanupCache } from "../services/utils/redis.utils.js";
import { deleteInCache, findInCache } from "../cache_service/index.js";
import { cost_types, redis_keys } from "../configs/constant.js";
import { cost_types, redis_keys, embed_cache } from "../configs/constant.js";
import { generateAuthToken } from "../services/utils/utility.service.js";
import jwt from "jsonwebtoken";
import responseTypeService from "../db_services/responseType.service.js";
Expand Down Expand Up @@ -40,8 +41,7 @@ const embedLogin = async (req, res) => {
}
};

// Run DB query and token creation in parallel since they don't depend on each other
const [folder] = await Promise.all([FolderModel.findOne({ _id: req.Embed.folder_id }).lean(), createProxyToken(embedDetails)]);
const [folder] = await Promise.all([folderService.getFolderData(req.Embed.folder_id), createProxyToken(embedDetails)]);

const config = folder?.config || {};
const apikey_object_id = folder?.apikey_object_id;
Expand Down Expand Up @@ -178,6 +178,7 @@ const updateEmbed = async (req, res, next) => {
if (folder_usage == 0) {
await deleteInCache(`${redis_keys.folderusedcost_}${folder_id}`);
}
await deleteInCache(embed_cache.keys.folder(folder_id));
res.locals = { data: { ...folder.toObject(), folder_id: folder._id } };
req.statusCode = 200;
return next();
Expand Down
18 changes: 18 additions & 0 deletions src/db_services/folder.service.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
import Folder from "../mongoModel/GtwyEmbed.model.js";
import { embed_cache } from "../configs/constant.js";
import { findInCache, storeInCache, deleteInCache } from "../cache_service/index.js";

async function getFolderData(folder_id) {
if (!folder_id) return null;

const cacheKeyFolder = embed_cache.keys.folder(folder_id);
const cachedFolder = await findInCache(cacheKeyFolder);

if (cachedFolder) {
try {
return JSON.parse(cachedFolder);
} catch {
await deleteInCache(cacheKeyFolder);
}
}

try {
const folder = await Folder.findById(folder_id).lean();
if (folder) {
await storeInCache(cacheKeyFolder, folder);
}
return folder;
} catch (error) {
console.error("Error fetching folder data:", error);
Expand Down
5 changes: 3 additions & 2 deletions src/middlewares/gtwyEmbedMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ const GtwyEmbeddecodeToken = async (req, res, next) => {
return res.status(401).json({ message: "unauthorized user, user id, folder id or org id not provided" });
}
if (decodedToken) {
// const orgTokenFromDb = await orgDbServices.find(decodedToken.org_id);
const orgTokenFromDb = await getOrganizationById(decodedToken?.org_id);
const orgToken = orgTokenFromDb?.meta?.gtwyAccessToken;
if (orgToken) {
const checkToken = jwt.verify(token, orgToken);
if (checkToken) {
if (checkToken.user_id) checkToken.user_id = encryptString(checkToken.user_id);
const { proxyResponse, name, email } = await createOrGetUser(checkToken, decodedToken, orgTokenFromDb);

const proxyUserData = await createOrGetUser(checkToken, decodedToken, orgTokenFromDb);
const { proxyResponse, name, email } = proxyUserData;
req.Embed = {
...checkToken,
email: email,
Expand Down
22 changes: 20 additions & 2 deletions src/services/proxy.service.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from "axios";
import { findInCache, storeInCache } from "../cache_service/index.js";
import { findInCache, storeInCache, deleteInCache } from "../cache_service/index.js";
import { objectToQueryParams } from "./utils/utility.service.js";
// import { findInCache, storeInCache } from './cache.js';
import { embed_cache } from "../configs/constant.js";

export async function getUserOrgMapping(userId, orgId) {
try {
Expand Down Expand Up @@ -39,6 +39,17 @@ export const switchOrganization = async (data, proxyToken) => {
};

export async function getOrganizationById(orgId) {
const cacheKeyOrg = embed_cache.keys.org(orgId);
const cachedOrg = await findInCache(cacheKeyOrg);

if (cachedOrg) {
try {
return JSON.parse(cachedOrg);
} catch {
await deleteInCache(cacheKeyOrg);
}
}

try {
const response = await axios.get(`https://routes.msg91.com/api/${process.env.PUBLIC_REFERENCEID}/getCompanies?id=${orgId}`, {
// TODO not provided by proxy
Expand All @@ -50,6 +61,9 @@ export async function getOrganizationById(orgId) {
});

const data = response?.data?.data?.data?.[0];
if (data) {
await storeInCache(cacheKeyOrg, data);
}
return data; // data.org kardena if giving undefined.
} catch (error) {
console.error("Error fetching data:", error.message);
Expand Down Expand Up @@ -88,6 +102,10 @@ export async function updateOrganizationData(orgId, orgDetails) {
// You can include credentials if required (e.g., 'withCredentials': true)
});

if (orgId) {
await deleteInCache(embed_cache.keys.org(orgId));
}

const data = response?.data;
return data;
} catch (error) {
Expand Down
16 changes: 15 additions & 1 deletion src/utils/proxy.utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import axios from "axios";
import { generateIdentifier } from "../services/utils/utility.service.js";
import { createOrFindUserAndCompany } from "../services/proxy.service.js";
import { findInCache, storeInCache, deleteInCache } from "../cache_service/index.js";
import { embed_cache } from "../configs/constant.js";

async function getallOrgs() {
try {
Expand All @@ -17,6 +19,16 @@ async function getallOrgs() {
}

const createOrGetUser = async (checkToken, decodedToken, orgTokenFromDb) => {
const cacheKeyUser = embed_cache.keys.user(decodedToken.user_id, decodedToken.org_id);
const cachedUser = await findInCache(cacheKeyUser);

if (cachedUser) {
try {
return JSON.parse(cachedUser);
} catch {
await deleteInCache(cacheKeyUser);
}
}
const userDetails = {
name: generateIdentifier(14, "emb", false),
email: `${decodedToken.org_id}${checkToken.user_id}@gtwy.ai`,
Expand All @@ -36,7 +48,9 @@ const createOrGetUser = async (checkToken, decodedToken, orgTokenFromDb) => {
role_id: process.env.PROXY_USER_ROLE_ID
};
const proxyResponse = await createOrFindUserAndCompany(proxyObject); // proxy api call
return { proxyResponse, name: userDetails.name, email: userDetails.email };
const result = { proxyResponse, name: userDetails.name, email: userDetails.email };
await storeInCache(cacheKeyUser, result);
return result;
};

export { getallOrgs, createOrGetUser };