| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- // Zoho Projects Tasks API Module
- // EU data center base: https://projectsapi.zoho.eu
- var API_BASE = 'https://projectsapi.zoho.eu';
- function makeRequest(token, method, path, params) {
- var portalId = shadowman.config.value('portal_id');
- if (!portalId) {
- return null; // Let the caller handle missing portal ID
- }
- // Use Zoho Projects API domain. EU data center: projectsapi.zoho.eu
- // Note: auth may return api_domain as www.zohoapis.eu (generic), but Projects
- // requires its own domain.
- var apiBase = 'https://projectsapi.zoho.eu';
- // Replace placeholders in path
- var url = path.replace('{PORTALID}', portalId);
- var fullUrl = apiBase + url;
- var headers = {
- 'Authorization': 'Zoho-oauthtoken ' + token,
- 'Content-Type': 'application/x-www-form-urlencoded'
- };
- var body = null;
- var contentType = 'application/x-www-form-urlencoded';
- if (method === 'GET' && params) {
- var qs = [];
- for (var key in params) {
- if (params.hasOwnProperty(key) && params[key] !== undefined) {
- qs.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key]));
- }
- }
- if (qs.length > 0) {
- fullUrl += '?' + qs.join('&');
- }
- } else if (method === 'POST' && params) {
- var parts = [];
- for (var key in params) {
- if (params.hasOwnProperty(key) && params[key] !== undefined) {
- parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key]));
- }
- }
- body = parts.join('&');
- }
- var resp = shadowman.http.request(fullUrl, {
- method: method,
- body: body,
- headers: headers,
- contentType: contentType
- });
- if (resp.status === 401) {
- // Token might have expired - signal to retry
- return { _tokenExpired: true };
- }
- if (resp.status >= 400) {
- shadowman.log.error('Zoho API error: ' + resp.status + ' ' + resp.body);
- var errBody;
- try {
- errBody = JSON.parse(resp.body);
- } catch (e) {
- errBody = { error: resp.body };
- }
- return { error: 'Zoho API error (' + resp.status + '): ' + (errBody.description || errBody.message || errBody.error || resp.body) };
- }
- try {
- return JSON.parse(resp.body);
- } catch (e) {
- return { error: 'Invalid JSON response: ' + resp.body };
- }
- }
- // Retry wrapper - auto-refreshes token on 401
- function apiCall(token, method, path, params) {
- var result = makeRequest(token, method, path, params);
- if (result && result._tokenExpired) {
- // Token expired, trigger a refresh via auth module
- var auth = require('./auth');
- var newToken = auth.getValidToken();
- if (!newToken) {
- return { error: 'Authentication expired and refresh failed. Re-run setup_auth.' };
- }
- return makeRequest(newToken, method, path, params);
- }
- return result;
- }
- function listProjects(token, index) {
- var portalId = shadowman.config.value('portal_id');
- if (!portalId) {
- return { error: 'Missing portal_id in plugin config.' };
- }
- return apiCall(token, 'GET', '/restapi/portal/{PORTALID}/projects/', {
- index: String(index)
- });
- }
- function listTasks(token, projectId, filters) {
- var portalId = shadowman.config.value('portal_id');
- if (!portalId) {
- return { error: 'Missing portal_id in plugin config.' };
- }
- var params = {};
- if (filters.status) params.status = filters.status;
- if (filters.priority) params.priority = filters.priority;
- if (filters.owner) params.owner = filters.owner;
- if (filters.index) params.index = String(filters.index);
- if (filters.milestone_id) params.milestone_id = filters.milestone_id;
- if (filters.tasklist_id) params.tasklist_id = filters.tasklist_id;
- return apiCall(token, 'GET', '/restapi/portal/{PORTALID}/projects/{PROJECTID}/tasks/'
- .replace('{PROJECTID}', projectId), params);
- }
- function getTask(token, projectId, taskId) {
- var portalId = shadowman.config.value('portal_id');
- if (!portalId) {
- return { error: 'Missing portal_id in plugin config.' };
- }
- return apiCall(token, 'GET',
- '/restapi/portal/{PORTALID}/projects/{PROJECTID}/tasks/{TASKID}/'
- .replace('{PROJECTID}', projectId)
- .replace('{TASKID}', taskId), {});
- }
- function createTask(token, projectId, args) {
- var portalId = shadowman.config.value('portal_id');
- if (!portalId) {
- return { error: 'Missing portal_id in plugin config.' };
- }
- var params = { name: args.name };
- if (args.description) params.description = args.description;
- if (args.person_responsible) params.person_responsible = String(args.person_responsible);
- if (args.priority) params.priority = args.priority;
- if (args.start_date) params.start_date = args.start_date;
- if (args.end_date) params.end_date = args.end_date;
- if (args.tasklist_id) params.tasklist_id = String(args.tasklist_id);
- if (args.milestone_id) params.milestone_id = String(args.milestone_id);
- if (args.percent_complete) params.percent_complete = String(args.percent_complete);
- return apiCall(token, 'POST',
- '/restapi/portal/{PORTALID}/projects/{PROJECTID}/tasks/'
- .replace('{PROJECTID}', projectId), params);
- }
- function updateTask(token, projectId, taskId, args) {
- var portalId = shadowman.config.value('portal_id');
- if (!portalId) {
- return { error: 'Missing portal_id in plugin config.' };
- }
- var params = {};
- if (args.name) params.name = args.name;
- if (args.description !== undefined) params.description = args.description;
- if (args.person_responsible) params.person_responsible = String(args.person_responsible);
- if (args.priority) params.priority = args.priority;
- if (args.start_date) params.start_date = args.start_date;
- if (args.end_date) params.end_date = args.end_date;
- if (args.percent_complete !== undefined) params.percent_complete = String(args.percent_complete);
- return apiCall(token, 'POST',
- '/restapi/portal/{PORTALID}/projects/{PROJECTID}/tasks/{TASKID}/'
- .replace('{PROJECTID}', projectId)
- .replace('{TASKID}', taskId), params);
- }
- function deleteTask(token, projectId, taskId) {
- var portalId = shadowman.config.value('portal_id');
- if (!portalId) {
- return { error: 'Missing portal_id in plugin config.' };
- }
- return apiCall(token, 'DELETE',
- '/restapi/portal/{PORTALID}/projects/{PROJECTID}/tasks/{TASKID}/'
- .replace('{PROJECTID}', projectId)
- .replace('{TASKID}', taskId), {});
- }
- function getMyTasks(token, filters) {
- var portalId = shadowman.config.value('portal_id');
- if (!portalId) {
- return { error: 'Missing portal_id in plugin config.' };
- }
- var params = {};
- if (filters.status) params.status = filters.status;
- if (filters.priority) params.priority = filters.priority;
- if (filters.index) params.index = String(filters.index);
- return apiCall(token, 'GET', '/restapi/portal/{PORTALID}/mytasks/', params);
- }
- function searchTasks(token, args) {
- // Zoho Projects doesn't have a dedicated search endpoint, so we search across all projects
- var portalId = shadowman.config.value('portal_id');
- if (!portalId) {
- return { error: 'Missing portal_id in plugin config.' };
- }
- var query = args.query;
- var projectId = args.project_id;
- if (projectId) {
- // Search within a specific project
- var result = apiCall(token, 'GET',
- '/restapi/portal/{PORTALID}/projects/{PROJECTID}/tasks/'
- .replace('{PROJECTID}', projectId), {
- index: '1'
- });
- if (result.error) return result;
- var tasks = result.tasks || [];
- var matched = [];
- for (var i = 0; i < tasks.length; i++) {
- var t = tasks[i];
- if (t.name && t.name.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
- matched.push(t);
- } else if (t.description && t.description.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
- matched.push(t);
- }
- }
- return { tasks: matched, total: matched.length, query: query };
- }
- // Search across all projects - first get project list
- var projects = apiCall(token, 'GET', '/restapi/portal/{PORTALID}/projects/', {});
- if (projects.error) return projects;
- var allTasks = [];
- var projectList = projects.projects || [];
- for (var p = 0; p < projectList.length; p++) {
- var proj = projectList[p];
- var taskResult = apiCall(token, 'GET',
- '/restapi/portal/{PORTALID}/projects/{PROJECTID}/tasks/'
- .replace('{PROJECTID}', String(proj.id)), {
- index: '1'
- });
- if (taskResult.error) continue;
- var tasks = taskResult.tasks || [];
- for (var i = 0; i < tasks.length; i++) {
- var t = tasks[i];
- if (t.name && t.name.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
- t._project_name = proj.name;
- allTasks.push(t);
- } else if (t.description && t.description.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
- t._project_name = proj.name;
- allTasks.push(t);
- }
- }
- }
- return { tasks: allTasks, total: allTasks.length, query: query };
- }
- module.exports = {
- listProjects: listProjects,
- listTasks: listTasks,
- getTask: getTask,
- createTask: createTask,
- updateTask: updateTask,
- deleteTask: deleteTask,
- getMyTasks: getMyTasks,
- searchTasks: searchTasks
- };
|