| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- // Zoho Projects v3 API Module
- // EU data center: https://projects.zoho.eu
- // Collection endpoints: no trailing slash. Resource endpoints: trailing slash optional.
- var API_BASE = 'https://projects.zoho.eu';
- function makeRequest(token, method, path, jsonBody, queryParams) {
- var portalId = shadowman.config.value('portal_id');
- if (!portalId) {
- return { error: 'Missing portal_id in plugin config. Run resolve_portal first.' };
- }
- var url = API_BASE + path.replace('{PORTALID}', portalId);
- if (queryParams) {
- var qs = [];
- for (var key in queryParams) {
- if (queryParams.hasOwnProperty(key) && queryParams[key] !== undefined) {
- qs.push(encodeURIComponent(key) + '=' + encodeURIComponent(queryParams[key]));
- }
- }
- if (qs.length > 0) {
- url += '?' + qs.join('&');
- }
- }
- var hasBody = (jsonBody !== null && jsonBody !== undefined);
- var headers = {
- 'Authorization': 'Zoho-oauthtoken ' + token
- };
- if (hasBody) {
- headers['Content-Type'] = 'application/json';
- }
- var resp = shadowman.http.request(url, {
- method: method,
- body: hasBody ? JSON.stringify(jsonBody) : null,
- headers: headers,
- contentType: hasBody ? 'application/json' : null
- });
- if (resp.status === 401) {
- return { _tokenExpired: true };
- }
- if (resp.status >= 400) {
- var raw = resp.body;
- var msg;
- if (typeof raw === 'string') {
- try {
- var p = JSON.parse(raw);
- msg = p.error && p.error.message ? p.error.message : raw;
- } catch (e) {
- msg = raw;
- }
- } else if (raw && typeof raw === 'object') {
- msg = (raw.error && raw.error.message) || raw.message || raw.description || JSON.stringify(raw);
- } else {
- msg = String(raw);
- }
- return { error: 'Zoho API error (' + resp.status + '): ' + msg };
- }
- if (resp.body && typeof resp.body === 'object') {
- return resp.body;
- }
- if (!resp.body || (typeof resp.body === 'string' && resp.body.trim() === '')) {
- return { success: true };
- }
- try {
- return JSON.parse(resp.body);
- } catch (e) {
- return { error: 'Invalid JSON response: ' + resp.body };
- }
- }
- function apiCall(token, method, path, jsonBody, queryParams) {
- var result = makeRequest(token, method, path, jsonBody, queryParams);
- if (result && result._tokenExpired) {
- 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, jsonBody, queryParams);
- }
- return result;
- }
- // --- Projects (v1 API — v3 returns 400) ---
- function listProjects(token, index) {
- var portalId = shadowman.config.value('portal_id');
- if (!portalId) {
- return { error: 'Missing portal_id in plugin config. Run resolve_portal first.' };
- }
- var params = {};
- if (index) params.index = String(index);
- return apiCall(token, 'GET',
- '/api/v3/portal/{PORTALID}/projects'
- .replace('{PORTALID}', portalId), null, params);
- }
- // --- Tasks (v3) ---
- function listTasks(token, projectId, filters) {
- 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',
- '/api/v3/portal/{PORTALID}/projects/{PROJECTID}/tasks'
- .replace('{PROJECTID}', projectId), null, params);
- }
- function getTask(token, projectId, taskId) {
- return apiCall(token, 'GET',
- '/api/v3/portal/{PORTALID}/projects/{PROJECTID}/tasks/{TASKID}'
- .replace('{PROJECTID}', projectId)
- .replace('{TASKID}', taskId), null, {});
- }
- function createTask(token, projectId, args) {
- var body = { name: args.name };
- if (args.description) body.description = args.description;
- if (args.person_responsible) body.person_responsible = String(args.person_responsible);
- if (args.priority) body.priority = args.priority;
- if (args.start_date) body.start_date = args.start_date;
- if (args.end_date) body.end_date = args.end_date;
- if (args.tasklist_id) body.tasklist_id = String(args.tasklist_id);
- if (args.milestone_id) body.milestone_id = String(args.milestone_id);
- if (args.percent_complete) body.completion_percentage = String(args.percent_complete);
- return apiCall(token, 'POST',
- '/api/v3/portal/{PORTALID}/projects/{PROJECTID}/tasks'
- .replace('{PROJECTID}', projectId), body);
- }
- function updateTask(token, projectId, taskId, args) {
- var body = {};
- if (args.name) body.name = args.name;
- if (args.description !== undefined) body.description = args.description;
- if (args.person_responsible) body.person_responsible = String(args.person_responsible);
- if (args.priority) body.priority = args.priority;
- if (args.start_date) body.start_date = args.start_date;
- if (args.end_date) body.end_date = args.end_date;
- if (args.percent_complete !== undefined) body.completion_percentage = String(args.percent_complete);
- return apiCall(token, 'PATCH',
- '/api/v3/portal/{PORTALID}/projects/{PROJECTID}/tasks/{TASKID}'
- .replace('{PROJECTID}', projectId)
- .replace('{TASKID}', taskId), body);
- }
- function deleteTask(token, projectId, taskId) {
- return apiCall(token, 'DELETE',
- '/api/v3/portal/{PORTALID}/projects/{PROJECTID}/tasks/{TASKID}'
- .replace('{PROJECTID}', projectId)
- .replace('{TASKID}', taskId), null, {});
- }
- // --- My Tasks ---
- function getMyTasks(token, filters) {
- 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', '/api/v3/portal/{PORTALID}/mytasks', null, params);
- }
- // --- Search ---
- function searchTasks(token, args) {
- 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) {
- var result = apiCall(token, 'GET',
- '/api/v3/portal/{PORTALID}/projects/{PROJECTID}/tasks'
- .replace('{PROJECTID}', projectId), null, { index: '1' });
- if (result.error) return result;
- return filterTasks(result.tasks || [], query);
- }
- var projects = listProjects(token, 1);
- 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',
- '/api/v3/portal/{PORTALID}/projects/{PROJECTID}/tasks'
- .replace('{PROJECTID}', String(proj.id)), null, { index: '1' });
- if (taskResult.error) continue;
- var tasks = taskResult.tasks || [];
- for (var j = 0; j < tasks.length; j++) {
- tasks[j]._project_name = proj.name;
- }
- allTasks = allTasks.concat(tasks);
- }
- return filterTasks(allTasks, query);
- }
- function filterTasks(tasks, query) {
- var matched = [];
- var q = query.toLowerCase();
- for (var i = 0; i < tasks.length; i++) {
- var t = tasks[i];
- if ((t.name && t.name.toLowerCase().indexOf(q) !== -1) ||
- (t.description && t.description.toLowerCase().indexOf(q) !== -1)) {
- matched.push(t);
- }
- }
- return { tasks: matched, total: matched.length, query: query };
- }
- // --- Comments ---
- function addComment(token, projectId, taskId, content) {
- return apiCall(token, 'POST',
- '/api/v3/portal/{PORTALID}/projects/{PROJECTID}/tasks/{TASKID}/comments'
- .replace('{PROJECTID}', projectId)
- .replace('{TASKID}', taskId), { content: content });
- }
- function updateComment(token, projectId, taskId, commentId, content) {
- return apiCall(token, 'PUT',
- '/api/v3/portal/{PORTALID}/projects/{PROJECTID}/tasks/{TASKID}/comments/{COMMENTID}'
- .replace('{PROJECTID}', projectId)
- .replace('{TASKID}', taskId)
- .replace('{COMMENTID}', commentId), { content: content });
- }
- function deleteComment(token, projectId, taskId, commentId) {
- return apiCall(token, 'DELETE',
- '/api/v3/portal/{PORTALID}/projects/{PROJECTID}/tasks/{TASKID}/comments/{COMMENTID}'
- .replace('{PROJECTID}', projectId)
- .replace('{TASKID}', taskId)
- .replace('{COMMENTID}', commentId), null, {});
- }
- // --- Timesheet ---
- function logTime(token, projectId, args) {
- var logDate = args.date || '2026-04-15';
- if (logDate.indexOf('-') === 2) {
- var parts = logDate.split('-');
- logDate = parts[2] + '-' + parts[0] + '-' + parts[1];
- }
- var totalHours = Number(args.hours) + (Number(args.minutes || 0) / 60);
- totalHours = Math.round(totalHours * 100) / 100;
- var body = {
- log_name: args.notes || 'Time logged',
- date: logDate,
- bill_status: 'Billable',
- hours: String(totalHours),
- notes: args.notes || '',
- module: { id: String(args.task_id), type: 'task' },
- for_timer: false,
- frompage: 'taskdetails'
- };
- return apiCall(token, 'POST',
- '/api/v3/portal/{PORTALID}/projects/{PROJECTID}/log'
- .replace('{PROJECTID}', projectId), body);
- }
- function getProject(token, projectId) {
- var portalId = shadowman.config.value('portal_id');
- if (!portalId) return { error: 'Missing portal_id' };
- return apiCall(token, 'GET',
- '/api/v3/portal/{PORTALID}/projects/{PROJECTID}'
- .replace('{PORTALID}', portalId)
- .replace('{PROJECTID}', projectId), null, {});
- }
- function listTimesheets(token, projectId, filters) {
- var portalId = shadowman.config.value('portal_id');
- if (!portalId) return { error: 'Missing portal_id in plugin config.' };
- var params = {
- view_type: 'customdate'
- };
- if (filters.from_date) {
- var parts = filters.from_date.split('-');
- if (parts.length === 3) params.start_date = parts[2] + '-' + parts[0] + '-' + parts[1];
- else params.start_date = filters.from_date;
- } else {
- var d = new Date();
- d.setDate(d.getDate() - 30);
- params.start_date = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
- }
-
- if (filters.to_date) {
- var parts = filters.to_date.split('-');
- if (parts.length === 3) params.end_date = parts[2] + '-' + parts[0] + '-' + parts[1];
- else params.end_date = filters.to_date;
- } else {
- var today = new Date();
- params.end_date = today.getFullYear() + '-' + String(today.getMonth() + 1).padStart(2, '0') + '-' + String(today.getDate()).padStart(2, '0');
- }
-
- if (filters.index) params.page = String(filters.index);
- if (filters.task_id) {
- params.module = JSON.stringify({ id: String(filters.task_id), type: 'task' });
- } else {
- params.module = JSON.stringify({ type: 'task' });
- }
- var url = '/api/v3/portal/{PORTALID}/timelogs'.replace('{PORTALID}', portalId);
- if (projectId) {
- // Ha van projekt ID, a projekt szintű végpontot hívjuk
- url = '/api/v3/portal/{PORTALID}/projects/{PROJECTID}/timelogs'
- .replace('{PORTALID}', portalId)
- .replace('{PROJECTID}', projectId);
- }
- return apiCall(token, 'GET', url, null, params);
- }
- function deleteTimesheet(token, projectId, logId) {
- return apiCall(token, 'DELETE',
- '/api/v3/portal/{PORTALID}/projects/{PROJECTID}/logs/{LOGID}'
- .replace('{PORTALID}', shadowman.config.value('portal_id'))
- .replace('{PROJECTID}', projectId)
- .replace('{LOGID}', String(logId)), { module: 'task', from_page: 'timesheetdetails' }, {});
- }
- module.exports = {
- listProjects: listProjects,
- listTasks: listTasks,
- getTask: getTask,
- createTask: createTask,
- updateTask: updateTask,
- deleteTask: deleteTask,
- getMyTasks: getMyTasks,
- searchTasks: searchTasks,
- addComment: addComment,
- updateComment: updateComment,
- deleteComment: deleteComment,
- logTime: logTime,
- listTimesheets: listTimesheets,
- deleteTimesheet: deleteTimesheet
- };
|