From b137f8cc1acc1236c946d3c003d6d33c7cdfae97 Mon Sep 17 00:00:00 2001 From: yashwin-foxy Date: Fri, 12 Sep 2025 18:51:17 +0530 Subject: [PATCH] crm data mapped to frontend and created api for zoho project ansd tasks --- src/api/controllers/integrationController.js | 70 ++++++++++++++++++- src/api/routes/integrationRoutes.js | 41 +++++++++-- src/integrations/zoho/client.js | 68 +++++++++++++++--- src/integrations/zoho/mapper.js | 56 ++++++++++++--- .../integration/integrationService.js | 33 +++++++++ 5 files changed, 246 insertions(+), 22 deletions(-) diff --git a/src/api/controllers/integrationController.js b/src/api/controllers/integrationController.js index ea2af8c..0cfbcd5 100644 --- a/src/api/controllers/integrationController.js +++ b/src/api/controllers/integrationController.js @@ -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 }; diff --git a/src/api/routes/integrationRoutes.js b/src/api/routes/integrationRoutes.js index fd1d409..52e3f9c 100644 --- a/src/api/routes/integrationRoutes.js +++ b/src/api/routes/integrationRoutes.js @@ -1,14 +1,15 @@ const express = require('express'); 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 ZohoHandler = require('../../integrations/zoho/handler'); const router = express.Router(); -function validate(schema) { +function validate(schema, source = 'query') { 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) { return res.status(400).json({ status: 'error', @@ -18,7 +19,11 @@ function validate(schema) { timestamp: new Date().toISOString() }); } - req.query = value; + if (source === 'body') { + req.body = value; + } else { + req.query = value; + } next(); }; } @@ -50,6 +55,34 @@ const resourcesSchema = Joi.object({ 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) const zohoHandler = new ZohoHandler(); router.post('/webhooks/zoho/crm', zohoHandler.handleCrmWebhook.bind(zohoHandler)); diff --git a/src/integrations/zoho/client.js b/src/integrations/zoho/client.js index 4e7baba..5160d03 100644 --- a/src/integrations/zoho/client.js +++ b/src/integrations/zoho/client.js @@ -6,7 +6,11 @@ const ZohoMapper = require('./mapper'); class ZohoClient { constructor(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() { @@ -21,10 +25,11 @@ class ZohoClient { }; } - async makeRequest(endpoint, options = {}) { + async makeRequest(endpoint, options = {}, service = 'crm') { const { accessToken } = await this.getTokens(); 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 = { ...options, @@ -101,23 +106,68 @@ class ZohoClient { // Zoho People methods 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'); } // Zoho Projects methods - async getProjects(params = {}) { - const response = await this.makeRequest('/projects/v1/projects', { params }); + async getPortals() { + 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'); } + 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 = {}) { - 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'); } - async getAllProjectTasks(params = {}) { - const response = await this.makeRequest('/projects/v1/tasks', { params }); + async getAllProjectTasks(portalId, params = {}) { + const response = await this.makeRequest(`/api/v3/portal/${portalId}/all-tasklists`, { params }, 'projects'); return ZohoMapper.mapApiResponse(response, 'tasks'); } diff --git a/src/integrations/zoho/mapper.js b/src/integrations/zoho/mapper.js index 72599a2..95124eb 100644 --- a/src/integrations/zoho/mapper.js +++ b/src/integrations/zoho/mapper.js @@ -135,16 +135,56 @@ class ZohoMapper { // Map Zoho API response to standardized format static mapApiResponse(zohoResponse, recordType) { - const records = zohoResponse.data || []; - // const mappedRecords = this.mapRecords(records, recordType); - + let records = []; + 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 { data: records, - info: { - count: zohoResponse.info?.count || mappedRecords.length, - moreRecords: zohoResponse.info?.more_records || false, - page: zohoResponse.info?.page || 1 - } + info: pageInfo }; } } diff --git a/src/services/integration/integrationService.js b/src/services/integration/integrationService.js index a8480fa..61a0f2e 100644 --- a/src/services/integration/integrationService.js +++ b/src/services/integration/integrationService.js @@ -68,6 +68,39 @@ class IntegrationService { } 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;