crm data mapped to frontend and created api for zoho project ansd tasks

This commit is contained in:
yashwin-foxy 2025-09-12 18:51:17 +05:30
parent 7445a6a156
commit b137f8cc1a
5 changed files with 246 additions and 22 deletions

View File

@ -45,4 +45,72 @@ async function getResources(req, res) {
} }
} }
module.exports = { getData, getServices, getResources }; async function getPortals(req, res) {
try {
const { provider } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('Portals are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.id);
const portals = await integrationService.getPortals(provider);
res.json(success('Zoho portals retrieved successfully', portals));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getAllProjects(req, res) {
try {
const { provider, page, limit, filters } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('All projects are only available for Zoho provider', 'INVALID_PROVIDER'));
}
const integrationService = new IntegrationService(req.user.id);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const projects = await integrationService.getAllProjects(provider, params);
res.json(success('All Zoho projects retrieved successfully', projects));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
async function getAllProjectTasks(req, res) {
try {
const { provider, page, limit, filters, portal_id } = req.query;
if (provider !== 'zoho') {
return res.status(400).json(failure('All project tasks are only available for Zoho provider', 'INVALID_PROVIDER'));
}
if (!portal_id) {
return res.status(400).json(failure('portal_id is required in query parameters', 'MISSING_PORTAL_ID'));
}
const integrationService = new IntegrationService(req.user.id);
const params = { page, limit };
if (filters) {
try {
params.filters = JSON.parse(filters);
} catch (e) {
return res.status(400).json(failure('Invalid filters format', 'INVALID_FILTERS'));
}
}
const tasks = await integrationService.getAllProjectTasks(provider, portal_id, params);
res.json(success('All Zoho project tasks retrieved successfully', tasks));
} catch (error) {
res.status(400).json(failure(error.message, 'INTEGRATION_ERROR'));
}
}
module.exports = { getData, getServices, getResources, getPortals, getAllProjects, getAllProjectTasks };

View File

@ -1,14 +1,15 @@
const express = require('express'); const express = require('express');
const Joi = require('joi'); const Joi = require('joi');
const { getData, getServices, getResources } = require('../controllers/integrationController'); const { getData, getServices, getResources, getPortals, getAllProjects, getAllProjectTasks } = require('../controllers/integrationController');
const auth = require('../middlewares/auth'); const auth = require('../middlewares/auth');
const ZohoHandler = require('../../integrations/zoho/handler'); const ZohoHandler = require('../../integrations/zoho/handler');
const router = express.Router(); const router = express.Router();
function validate(schema) { function validate(schema, source = 'query') {
return (req, res, next) => { return (req, res, next) => {
const { error, value } = schema.validate(req.query, { abortEarly: false, stripUnknown: true }); const data = source === 'body' ? req.body : req.query;
const { error, value } = schema.validate(data, { abortEarly: false, stripUnknown: true });
if (error) { if (error) {
return res.status(400).json({ return res.status(400).json({
status: 'error', status: 'error',
@ -18,7 +19,11 @@ function validate(schema) {
timestamp: new Date().toISOString() timestamp: new Date().toISOString()
}); });
} }
req.query = value; if (source === 'body') {
req.body = value;
} else {
req.query = value;
}
next(); next();
}; };
} }
@ -50,6 +55,34 @@ const resourcesSchema = Joi.object({
router.get('/resources', auth, validate(resourcesSchema), getResources); router.get('/resources', auth, validate(resourcesSchema), getResources);
// Get Zoho portals
const portalsSchema = Joi.object({
provider: Joi.string().valid('zoho').required()
});
router.get('/portals', auth, validate(portalsSchema), getPortals);
// Get all Zoho projects (across all portals)
const allProjectsSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
router.get('/all-projects', auth, validate(allProjectsSchema), getAllProjects);
// Get all project tasks for a specific portal
const allProjectTasksSchema = Joi.object({
provider: Joi.string().valid('zoho').required(),
portal_id: Joi.string().required(),
page: Joi.number().min(1).default(1),
limit: Joi.number().min(1).max(100).default(20),
filters: Joi.string().optional()
});
router.get('/all-project-tasks', auth, validate(allProjectTasksSchema), getAllProjectTasks);
// Webhook endpoints (no auth required - uses signature verification) // Webhook endpoints (no auth required - uses signature verification)
const zohoHandler = new ZohoHandler(); const zohoHandler = new ZohoHandler();
router.post('/webhooks/zoho/crm', zohoHandler.handleCrmWebhook.bind(zohoHandler)); router.post('/webhooks/zoho/crm', zohoHandler.handleCrmWebhook.bind(zohoHandler));

View File

@ -6,7 +6,11 @@ const ZohoMapper = require('./mapper');
class ZohoClient { class ZohoClient {
constructor(userId) { constructor(userId) {
this.userId = userId; this.userId = userId;
this.baseUrl = 'https://www.zohoapis.com'; this.baseUrls = {
crm: 'https://www.zohoapis.com',
people: 'https://www.zohoapis.com',
projects: 'https://projectsapi.zoho.com'
};
} }
async getTokens() { async getTokens() {
@ -21,10 +25,11 @@ class ZohoClient {
}; };
} }
async makeRequest(endpoint, options = {}) { async makeRequest(endpoint, options = {}, service = 'crm') {
const { accessToken } = await this.getTokens(); const { accessToken } = await this.getTokens();
console.log('i am in make request with token',accessToken) console.log('i am in make request with token',accessToken)
const url = `${this.baseUrl}${endpoint}`; const baseUrl = this.baseUrls[service] || this.baseUrls.crm;
const url = `${baseUrl}${endpoint}`;
const config = { const config = {
...options, ...options,
@ -101,23 +106,68 @@ class ZohoClient {
// Zoho People methods // Zoho People methods
async getEmployees(params = {}) { async getEmployees(params = {}) {
const response = await this.makeRequest('/people/api/v1/employees', { params }); const response = await this.makeRequest('/people/api/v1/employees', { params }, 'people');
return ZohoMapper.mapApiResponse(response, 'employees'); return ZohoMapper.mapApiResponse(response, 'employees');
} }
// Zoho Projects methods // Zoho Projects methods
async getProjects(params = {}) { async getPortals() {
const response = await this.makeRequest('/projects/v1/projects', { params }); const response = await this.makeRequest('/api/v3/portals', {}, 'projects');
return ZohoMapper.mapApiResponse(response, 'portals');
}
async getProjects(portalId, params = {}) {
const response = await this.makeRequest(`/api/v3/portal/${portalId}/projects`, { params }, 'projects');
return ZohoMapper.mapApiResponse(response, 'projects'); return ZohoMapper.mapApiResponse(response, 'projects');
} }
async getAllProjects(params = {}) {
// First get all portals
const portalsResponse = await this.getPortals();
const portals = portalsResponse.data;
// Get projects for each portal
const allProjects = [];
for (const portal of portals) {
try {
const projectsResponse = await this.getProjects(portal.id, params);
const projects = projectsResponse.data;
console.log('all projects', projects);
// Add portal information to each project
const projectsWithPortal = projects.map(project => ({
...project,
portal: {
id: portal.id,
name: portal.portal_name,
org_name: portal.org_name,
portal_url: portal.portal_url
}
}));
allProjects.push(...projectsWithPortal);
} catch (error) {
console.error(`Error fetching projects for portal ${portal.id}:`, error.message);
// Continue with other portals even if one fails
}
}
return {
data: allProjects,
info: {
count: allProjects.length,
moreRecords: false,
page: 1
}
};
}
async getProjectTasks(projectId, params = {}) { async getProjectTasks(projectId, params = {}) {
const response = await this.makeRequest(`/projects/v1/projects/${projectId}/tasks`, { params }); const response = await this.makeRequest(`/projects/v1/projects/${projectId}/tasks`, { params }, 'projects');
return ZohoMapper.mapApiResponse(response, 'tasks'); return ZohoMapper.mapApiResponse(response, 'tasks');
} }
async getAllProjectTasks(params = {}) { async getAllProjectTasks(portalId, params = {}) {
const response = await this.makeRequest('/projects/v1/tasks', { params }); const response = await this.makeRequest(`/api/v3/portal/${portalId}/all-tasklists`, { params }, 'projects');
return ZohoMapper.mapApiResponse(response, 'tasks'); return ZohoMapper.mapApiResponse(response, 'tasks');
} }

View File

@ -135,16 +135,56 @@ class ZohoMapper {
// Map Zoho API response to standardized format // Map Zoho API response to standardized format
static mapApiResponse(zohoResponse, recordType) { static mapApiResponse(zohoResponse, recordType) {
const records = zohoResponse.data || []; let records = [];
// const mappedRecords = this.mapRecords(records, recordType); let pageInfo = {};
// Handle different response structures based on record type
switch (recordType) {
case 'projects':
// Projects response is directly an array
records = zohoResponse || [];
pageInfo = {
count: records.length,
moreRecords: false,
page: 1
};
break;
case 'tasks':
// Tasks response has tasklists array and page_info
records = zohoResponse.tasklists || [];
pageInfo = {
count: zohoResponse.page_info?.per_page || records.length,
moreRecords: zohoResponse.page_info?.has_next_page || false,
page: zohoResponse.page_info?.page || 1,
pageCount: zohoResponse.page_info?.page_count || 1
};
break;
case 'portals':
// Portals response is directly an array
records = zohoResponse || [];
pageInfo = {
count: records.length,
moreRecords: false,
page: 1
};
break;
default:
// Default CRM response structure (leads, contacts, deals, employees)
records = zohoResponse.data || [];
pageInfo = {
count: zohoResponse.info?.count || records.length,
moreRecords: zohoResponse.info?.more_records || false,
page: zohoResponse.info?.page || 1
};
break;
}
return { return {
data: records, data: records,
info: { info: pageInfo
count: zohoResponse.info?.count || mappedRecords.length,
moreRecords: zohoResponse.info?.more_records || false,
page: zohoResponse.info?.page || 1
}
}; };
} }
} }

View File

@ -68,6 +68,39 @@ class IntegrationService {
} }
return client.getAvailableResources(service); return client.getAvailableResources(service);
} }
async getPortals(provider) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('Portals are only available for Zoho provider');
}
return await client.getPortals();
}
async getAllProjects(provider, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('All projects are only available for Zoho provider');
}
return await client.getAllProjects(params);
}
async getAllProjectTasks(provider, portalId, params = {}) {
const client = this.clients[provider];
if (!client) {
throw new Error(`Provider ${provider} not supported`);
}
if (provider !== 'zoho') {
throw new Error('All project tasks are only available for Zoho provider');
}
return await client.getAllProjectTasks(portalId, params);
}
} }
module.exports = IntegrationService; module.exports = IntegrationService;