@taazkareem/clickup-mcp-server 0.8.5 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -17
- package/README.md +33 -38
- package/build/enhanced_server.js +262 -0
- package/build/index.js +9 -3
- package/build/license.js +172 -0
- package/build/middleware/auth.js +211 -0
- package/build/routes/auth.js +306 -0
- package/build/schemas/member.js +13 -0
- package/build/server.js +14 -0
- package/build/server.log +15 -0
- package/build/services/auth/oauth2.js +236 -0
- package/build/services/auth/session.js +337 -0
- package/build/services/clickup/adapter.js +281 -0
- package/build/services/clickup/factory.js +339 -0
- package/build/services/clickup/task/task-attachments.js +20 -12
- package/build/services/clickup/task/task-comments.js +19 -9
- package/build/services/clickup/task/task-custom-fields.js +23 -13
- package/build/services/clickup/task/task-search.js +79 -71
- package/build/services/clickup/task/task-service.js +88 -9
- package/build/services/clickup/task/task-tags.js +25 -13
- package/build/sse_server.js +4 -4
- package/build/tools/documents.js +11 -4
- package/build/tools/health.js +23 -0
- package/build/utils/schema-compatibility.js +222 -0
- package/build/utils/universal-schema-compatibility.js +171 -0
- package/build/virtual-sdk/generator.js +53 -0
- package/build/virtual-sdk/registry.js +45 -0
- package/package.json +2 -2
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* ClickUp Service Adapter
|
|
6
|
+
*
|
|
7
|
+
* Provides backward compatibility for existing ClickUp services
|
|
8
|
+
* while supporting both API key and OAuth2 authentication.
|
|
9
|
+
* This adapter wraps the existing services without modifying their constructors.
|
|
10
|
+
*/
|
|
11
|
+
import { WorkspaceService } from './workspace.js';
|
|
12
|
+
import { TaskService } from './task/task-service.js';
|
|
13
|
+
import { ListService } from './list.js';
|
|
14
|
+
import { FolderService } from './folder.js';
|
|
15
|
+
import { TimeService } from './time.js';
|
|
16
|
+
import { TagService } from './tag.js';
|
|
17
|
+
import { BulkService } from './bulk.js';
|
|
18
|
+
import { DocumentService } from './document.js';
|
|
19
|
+
import { Logger } from '../../logger.js';
|
|
20
|
+
import config from '../../config.js';
|
|
21
|
+
/**
|
|
22
|
+
* ClickUp service adapter that provides authentication-aware service creation
|
|
23
|
+
*/
|
|
24
|
+
export class ClickUpServiceAdapter {
|
|
25
|
+
constructor() {
|
|
26
|
+
this.logger = new Logger('ClickUpServiceAdapter');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get authentication parameters from context
|
|
30
|
+
* @private
|
|
31
|
+
*/
|
|
32
|
+
getAuthParams(context) {
|
|
33
|
+
if (context.authMethod === 'oauth2') {
|
|
34
|
+
if (!context.accessToken) {
|
|
35
|
+
throw new Error('OAuth2 access token is required but not provided');
|
|
36
|
+
}
|
|
37
|
+
if (!context.teamId) {
|
|
38
|
+
throw new Error('Team ID is required for OAuth2 authentication');
|
|
39
|
+
}
|
|
40
|
+
// For OAuth2, we'll use the access token as the "API key"
|
|
41
|
+
// and modify the service after creation
|
|
42
|
+
return {
|
|
43
|
+
apiKey: `Bearer ${context.accessToken}`,
|
|
44
|
+
teamId: context.teamId
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// API key authentication
|
|
49
|
+
const apiKey = context.apiKey || config.clickupApiKey;
|
|
50
|
+
const teamId = context.teamId || config.clickupTeamId;
|
|
51
|
+
if (!apiKey) {
|
|
52
|
+
throw new Error('API key is required but not provided');
|
|
53
|
+
}
|
|
54
|
+
if (!teamId) {
|
|
55
|
+
throw new Error('Team ID is required but not provided');
|
|
56
|
+
}
|
|
57
|
+
return { apiKey, teamId };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Create AuthConfig from context
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
createAuthConfig(context) {
|
|
65
|
+
if (context.authMethod === 'oauth2') {
|
|
66
|
+
if (!context.accessToken) {
|
|
67
|
+
throw new Error('Access token is required for OAuth2 authentication');
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
type: 'oauth2',
|
|
71
|
+
accessToken: context.accessToken,
|
|
72
|
+
teamId: context.teamId || config.clickupTeamId
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const apiKey = context.apiKey || config.clickupApiKey;
|
|
77
|
+
const teamId = context.teamId || config.clickupTeamId;
|
|
78
|
+
if (!apiKey) {
|
|
79
|
+
throw new Error('API key is required for API key authentication');
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
type: 'api_key',
|
|
83
|
+
apiKey,
|
|
84
|
+
teamId
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Create WorkspaceService instance
|
|
90
|
+
*/
|
|
91
|
+
createWorkspaceService(context) {
|
|
92
|
+
try {
|
|
93
|
+
const { apiKey, teamId } = this.getAuthParams(context);
|
|
94
|
+
const service = WorkspaceService.withApiKey(apiKey, teamId);
|
|
95
|
+
this.logger.debug('WorkspaceService created', {
|
|
96
|
+
authMethod: context.authMethod,
|
|
97
|
+
teamId
|
|
98
|
+
});
|
|
99
|
+
return service;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
this.logger.error('Failed to create WorkspaceService', {
|
|
103
|
+
error: error.message,
|
|
104
|
+
context
|
|
105
|
+
});
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Create TaskService instance
|
|
111
|
+
*/
|
|
112
|
+
createTaskService(context) {
|
|
113
|
+
try {
|
|
114
|
+
const { apiKey, teamId } = this.getAuthParams(context);
|
|
115
|
+
const service = TaskService.withApiKey(apiKey, teamId);
|
|
116
|
+
this.logger.debug('TaskService created', {
|
|
117
|
+
authMethod: context.authMethod,
|
|
118
|
+
teamId
|
|
119
|
+
});
|
|
120
|
+
return service;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
this.logger.error('Failed to create TaskService', {
|
|
124
|
+
error: error.message,
|
|
125
|
+
context
|
|
126
|
+
});
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Create ListService instance
|
|
132
|
+
*/
|
|
133
|
+
createListService(context) {
|
|
134
|
+
try {
|
|
135
|
+
const { apiKey, teamId } = this.getAuthParams(context);
|
|
136
|
+
const service = new ListService(apiKey, teamId);
|
|
137
|
+
this.logger.debug('ListService created', {
|
|
138
|
+
authMethod: context.authMethod,
|
|
139
|
+
teamId
|
|
140
|
+
});
|
|
141
|
+
return service;
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
this.logger.error('Failed to create ListService', {
|
|
145
|
+
error: error.message,
|
|
146
|
+
context
|
|
147
|
+
});
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Create FolderService instance
|
|
153
|
+
*/
|
|
154
|
+
createFolderService(context) {
|
|
155
|
+
try {
|
|
156
|
+
const { apiKey, teamId } = this.getAuthParams(context);
|
|
157
|
+
const service = new FolderService(apiKey, teamId);
|
|
158
|
+
this.logger.debug('FolderService created', {
|
|
159
|
+
authMethod: context.authMethod,
|
|
160
|
+
teamId
|
|
161
|
+
});
|
|
162
|
+
return service;
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
this.logger.error('Failed to create FolderService', {
|
|
166
|
+
error: error.message,
|
|
167
|
+
context
|
|
168
|
+
});
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Create TimeService instance
|
|
174
|
+
*/
|
|
175
|
+
createTimeService(context) {
|
|
176
|
+
try {
|
|
177
|
+
const authConfig = this.createAuthConfig(context);
|
|
178
|
+
const apiKey = authConfig.type === 'api_key' ? authConfig.apiKey : authConfig.accessToken;
|
|
179
|
+
const service = new TimeService(apiKey, authConfig.teamId);
|
|
180
|
+
this.logger.debug('TimeService created', {
|
|
181
|
+
authMethod: context.authMethod,
|
|
182
|
+
teamId: authConfig.teamId
|
|
183
|
+
});
|
|
184
|
+
return service;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
this.logger.error('Failed to create TimeService', {
|
|
188
|
+
error: error.message,
|
|
189
|
+
context
|
|
190
|
+
});
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Create TagService instance
|
|
196
|
+
*/
|
|
197
|
+
createTagService(context) {
|
|
198
|
+
try {
|
|
199
|
+
const authConfig = this.createAuthConfig(context);
|
|
200
|
+
const apiKey = authConfig.type === 'api_key' ? authConfig.apiKey : authConfig.accessToken;
|
|
201
|
+
const service = new TagService(apiKey, authConfig.teamId);
|
|
202
|
+
this.logger.debug('TagService created', {
|
|
203
|
+
authMethod: context.authMethod,
|
|
204
|
+
teamId: authConfig.teamId
|
|
205
|
+
});
|
|
206
|
+
return service;
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
this.logger.error('Failed to create TagService', {
|
|
210
|
+
error: error.message,
|
|
211
|
+
context
|
|
212
|
+
});
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Create BulkService instance
|
|
218
|
+
*/
|
|
219
|
+
createBulkService(context) {
|
|
220
|
+
try {
|
|
221
|
+
// BulkService needs a TaskService instance
|
|
222
|
+
const taskService = this.createTaskService(context);
|
|
223
|
+
const service = new BulkService(taskService);
|
|
224
|
+
this.logger.debug('BulkService created', {
|
|
225
|
+
authMethod: context.authMethod,
|
|
226
|
+
teamId: context.teamId
|
|
227
|
+
});
|
|
228
|
+
return service;
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
this.logger.error('Failed to create BulkService', {
|
|
232
|
+
error: error.message,
|
|
233
|
+
context
|
|
234
|
+
});
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Create DocumentService instance
|
|
240
|
+
*/
|
|
241
|
+
createDocumentService(context) {
|
|
242
|
+
try {
|
|
243
|
+
const { apiKey, teamId } = this.getAuthParams(context);
|
|
244
|
+
const service = new DocumentService(apiKey, teamId);
|
|
245
|
+
this.logger.debug('DocumentService created', {
|
|
246
|
+
authMethod: context.authMethod,
|
|
247
|
+
teamId
|
|
248
|
+
});
|
|
249
|
+
return service;
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
this.logger.error('Failed to create DocumentService', {
|
|
253
|
+
error: error.message,
|
|
254
|
+
context
|
|
255
|
+
});
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Create service context from API key authentication (backward compatibility)
|
|
261
|
+
*/
|
|
262
|
+
static createApiKeyContext(apiKey, teamId) {
|
|
263
|
+
return {
|
|
264
|
+
authMethod: 'api_key',
|
|
265
|
+
apiKey: apiKey || config.clickupApiKey,
|
|
266
|
+
teamId: teamId || config.clickupTeamId
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Create service context from OAuth2 authentication
|
|
271
|
+
*/
|
|
272
|
+
static createOAuth2Context(accessToken, teamId, userId, clickupUserId) {
|
|
273
|
+
return {
|
|
274
|
+
authMethod: 'oauth2',
|
|
275
|
+
accessToken,
|
|
276
|
+
teamId,
|
|
277
|
+
userId,
|
|
278
|
+
clickupUserId
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* ClickUp Service Factory
|
|
6
|
+
*
|
|
7
|
+
* Creates ClickUp service instances with appropriate authentication
|
|
8
|
+
* based on the request context (OAuth2 or API key).
|
|
9
|
+
*/
|
|
10
|
+
import { WorkspaceService } from './workspace.js';
|
|
11
|
+
import { TaskService } from './task/task-service.js';
|
|
12
|
+
import { ListService } from './list.js';
|
|
13
|
+
import { FolderService } from './folder.js';
|
|
14
|
+
import { TimeService } from './time.js';
|
|
15
|
+
import { TagService } from './tag.js';
|
|
16
|
+
import { BulkService } from './bulk.js';
|
|
17
|
+
import { DocumentService } from './document.js';
|
|
18
|
+
import { Logger } from '../../logger.js';
|
|
19
|
+
import config from '../../config.js';
|
|
20
|
+
/**
|
|
21
|
+
* Factory class for creating ClickUp services with appropriate authentication
|
|
22
|
+
*/
|
|
23
|
+
export class ClickUpServiceFactory {
|
|
24
|
+
constructor() {
|
|
25
|
+
this.logger = new Logger('ClickUpServiceFactory');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create authentication configuration from service context
|
|
29
|
+
* @private
|
|
30
|
+
*/
|
|
31
|
+
createAuthConfig(context) {
|
|
32
|
+
if (context.authMethod === 'oauth2') {
|
|
33
|
+
if (!context.accessToken) {
|
|
34
|
+
throw new Error('OAuth2 access token is required but not provided');
|
|
35
|
+
}
|
|
36
|
+
if (!context.teamId) {
|
|
37
|
+
throw new Error('Team ID is required for OAuth2 authentication');
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
type: 'oauth2',
|
|
41
|
+
accessToken: context.accessToken,
|
|
42
|
+
teamId: context.teamId
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// API key authentication
|
|
47
|
+
const apiKey = context.apiKey || config.clickupApiKey;
|
|
48
|
+
const teamId = context.teamId || config.clickupTeamId;
|
|
49
|
+
if (!apiKey) {
|
|
50
|
+
throw new Error('API key is required but not provided');
|
|
51
|
+
}
|
|
52
|
+
if (!teamId) {
|
|
53
|
+
throw new Error('Team ID is required but not provided');
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
type: 'api_key',
|
|
57
|
+
apiKey,
|
|
58
|
+
teamId
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create WorkspaceService instance
|
|
64
|
+
*/
|
|
65
|
+
createWorkspaceService(context) {
|
|
66
|
+
try {
|
|
67
|
+
const authConfig = this.createAuthConfig(context);
|
|
68
|
+
const service = new WorkspaceService(authConfig);
|
|
69
|
+
this.logger.debug('WorkspaceService created', {
|
|
70
|
+
authMethod: context.authMethod,
|
|
71
|
+
teamId: authConfig.teamId
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
success: true,
|
|
75
|
+
service
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
this.logger.error('Failed to create WorkspaceService', {
|
|
80
|
+
error: error.message,
|
|
81
|
+
context
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
error: {
|
|
86
|
+
message: error.message,
|
|
87
|
+
code: 'SERVICE_CREATION_FAILED'
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Create TaskService instance
|
|
94
|
+
*/
|
|
95
|
+
createTaskService(context) {
|
|
96
|
+
try {
|
|
97
|
+
const authConfig = this.createAuthConfig(context);
|
|
98
|
+
const apiKey = authConfig.type === 'api_key' ? authConfig.apiKey : authConfig.accessToken;
|
|
99
|
+
// Create workspace service first
|
|
100
|
+
const workspaceService = new WorkspaceService(authConfig);
|
|
101
|
+
const service = new TaskService(apiKey, authConfig.teamId, undefined, workspaceService);
|
|
102
|
+
this.logger.debug('TaskService created', {
|
|
103
|
+
authMethod: context.authMethod,
|
|
104
|
+
teamId: authConfig.teamId
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
success: true,
|
|
108
|
+
service
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
this.logger.error('Failed to create TaskService', {
|
|
113
|
+
error: error.message,
|
|
114
|
+
context
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
success: false,
|
|
118
|
+
error: {
|
|
119
|
+
message: error.message,
|
|
120
|
+
code: 'SERVICE_CREATION_FAILED'
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Create ListService instance
|
|
127
|
+
*/
|
|
128
|
+
createListService(context) {
|
|
129
|
+
try {
|
|
130
|
+
const authConfig = this.createAuthConfig(context);
|
|
131
|
+
const apiKey = authConfig.type === 'api_key' ? authConfig.apiKey : authConfig.accessToken;
|
|
132
|
+
const service = new ListService(apiKey, authConfig.teamId);
|
|
133
|
+
this.logger.debug('ListService created', {
|
|
134
|
+
authMethod: context.authMethod,
|
|
135
|
+
teamId: authConfig.teamId
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
success: true,
|
|
139
|
+
service
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
this.logger.error('Failed to create ListService', {
|
|
144
|
+
error: error.message,
|
|
145
|
+
context
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
success: false,
|
|
149
|
+
error: {
|
|
150
|
+
message: error.message,
|
|
151
|
+
code: 'SERVICE_CREATION_FAILED'
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Create FolderService instance
|
|
158
|
+
*/
|
|
159
|
+
createFolderService(context) {
|
|
160
|
+
try {
|
|
161
|
+
const authConfig = this.createAuthConfig(context);
|
|
162
|
+
const apiKey = authConfig.type === 'api_key' ? authConfig.apiKey : authConfig.accessToken;
|
|
163
|
+
const service = new FolderService(apiKey, authConfig.teamId);
|
|
164
|
+
this.logger.debug('FolderService created', {
|
|
165
|
+
authMethod: context.authMethod,
|
|
166
|
+
teamId: authConfig.teamId
|
|
167
|
+
});
|
|
168
|
+
return {
|
|
169
|
+
success: true,
|
|
170
|
+
service
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
this.logger.error('Failed to create FolderService', {
|
|
175
|
+
error: error.message,
|
|
176
|
+
context
|
|
177
|
+
});
|
|
178
|
+
return {
|
|
179
|
+
success: false,
|
|
180
|
+
error: {
|
|
181
|
+
message: error.message,
|
|
182
|
+
code: 'SERVICE_CREATION_FAILED'
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Create TimeService instance
|
|
189
|
+
*/
|
|
190
|
+
createTimeService(context) {
|
|
191
|
+
try {
|
|
192
|
+
const authConfig = this.createAuthConfig(context);
|
|
193
|
+
const apiKey = authConfig.type === 'api_key' ? authConfig.apiKey : authConfig.accessToken;
|
|
194
|
+
const service = new TimeService(apiKey, authConfig.teamId);
|
|
195
|
+
this.logger.debug('TimeService created', {
|
|
196
|
+
authMethod: context.authMethod,
|
|
197
|
+
teamId: authConfig.teamId
|
|
198
|
+
});
|
|
199
|
+
return {
|
|
200
|
+
success: true,
|
|
201
|
+
service
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
this.logger.error('Failed to create TimeService', {
|
|
206
|
+
error: error.message,
|
|
207
|
+
context
|
|
208
|
+
});
|
|
209
|
+
return {
|
|
210
|
+
success: false,
|
|
211
|
+
error: {
|
|
212
|
+
message: error.message,
|
|
213
|
+
code: 'SERVICE_CREATION_FAILED'
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Create TagService instance
|
|
220
|
+
*/
|
|
221
|
+
createTagService(context) {
|
|
222
|
+
try {
|
|
223
|
+
const authConfig = this.createAuthConfig(context);
|
|
224
|
+
const apiKey = authConfig.type === 'api_key' ? authConfig.apiKey : authConfig.accessToken;
|
|
225
|
+
const service = new TagService(apiKey, authConfig.teamId);
|
|
226
|
+
this.logger.debug('TagService created', {
|
|
227
|
+
authMethod: context.authMethod,
|
|
228
|
+
teamId: authConfig.teamId
|
|
229
|
+
});
|
|
230
|
+
return {
|
|
231
|
+
success: true,
|
|
232
|
+
service
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
this.logger.error('Failed to create TagService', {
|
|
237
|
+
error: error.message,
|
|
238
|
+
context
|
|
239
|
+
});
|
|
240
|
+
return {
|
|
241
|
+
success: false,
|
|
242
|
+
error: {
|
|
243
|
+
message: error.message,
|
|
244
|
+
code: 'SERVICE_CREATION_FAILED'
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Create BulkService instance
|
|
251
|
+
*/
|
|
252
|
+
createBulkService(context) {
|
|
253
|
+
try {
|
|
254
|
+
// BulkService needs a TaskService instance
|
|
255
|
+
const taskServiceResult = this.createTaskService(context);
|
|
256
|
+
if (!taskServiceResult.success) {
|
|
257
|
+
return {
|
|
258
|
+
success: false,
|
|
259
|
+
error: taskServiceResult.error
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
const service = new BulkService(taskServiceResult.service);
|
|
263
|
+
this.logger.debug('BulkService created', {
|
|
264
|
+
authMethod: context.authMethod,
|
|
265
|
+
teamId: context.teamId
|
|
266
|
+
});
|
|
267
|
+
return {
|
|
268
|
+
success: true,
|
|
269
|
+
service
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
this.logger.error('Failed to create BulkService', {
|
|
274
|
+
error: error.message,
|
|
275
|
+
context
|
|
276
|
+
});
|
|
277
|
+
return {
|
|
278
|
+
success: false,
|
|
279
|
+
error: {
|
|
280
|
+
message: error.message,
|
|
281
|
+
code: 'SERVICE_CREATION_FAILED'
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Create DocumentService instance
|
|
288
|
+
*/
|
|
289
|
+
createDocumentService(context) {
|
|
290
|
+
try {
|
|
291
|
+
const authConfig = this.createAuthConfig(context);
|
|
292
|
+
const apiKey = authConfig.type === 'api_key' ? authConfig.apiKey : authConfig.accessToken;
|
|
293
|
+
const service = new DocumentService(apiKey, authConfig.teamId);
|
|
294
|
+
this.logger.debug('DocumentService created', {
|
|
295
|
+
authMethod: context.authMethod,
|
|
296
|
+
teamId: authConfig.teamId
|
|
297
|
+
});
|
|
298
|
+
return {
|
|
299
|
+
success: true,
|
|
300
|
+
service
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
this.logger.error('Failed to create DocumentService', {
|
|
305
|
+
error: error.message,
|
|
306
|
+
context
|
|
307
|
+
});
|
|
308
|
+
return {
|
|
309
|
+
success: false,
|
|
310
|
+
error: {
|
|
311
|
+
message: error.message,
|
|
312
|
+
code: 'SERVICE_CREATION_FAILED'
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Create service context from API key authentication (backward compatibility)
|
|
319
|
+
*/
|
|
320
|
+
static createApiKeyContext(apiKey, teamId) {
|
|
321
|
+
return {
|
|
322
|
+
authMethod: 'api_key',
|
|
323
|
+
apiKey: apiKey || config.clickupApiKey,
|
|
324
|
+
teamId: teamId || config.clickupTeamId
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Create service context from OAuth2 authentication
|
|
329
|
+
*/
|
|
330
|
+
static createOAuth2Context(accessToken, teamId, userId, clickupUserId) {
|
|
331
|
+
return {
|
|
332
|
+
authMethod: 'oauth2',
|
|
333
|
+
accessToken,
|
|
334
|
+
teamId,
|
|
335
|
+
userId,
|
|
336
|
+
clickupUserId
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
}
|
|
@@ -8,12 +8,20 @@
|
|
|
8
8
|
* - Uploading file attachments from base64/buffer data
|
|
9
9
|
* - Uploading file attachments from a URL (web URLs like http/https)
|
|
10
10
|
* - Uploading file attachments from local file paths (absolute paths)
|
|
11
|
+
*
|
|
12
|
+
* REFACTORED: Now uses composition instead of inheritance.
|
|
13
|
+
* Only depends on TaskServiceCore for base functionality.
|
|
11
14
|
*/
|
|
12
|
-
import { TaskServiceSearch } from './task-search.js';
|
|
13
15
|
/**
|
|
14
16
|
* Attachment functionality for the TaskService
|
|
17
|
+
*
|
|
18
|
+
* This service handles all file attachment operations for ClickUp tasks.
|
|
19
|
+
* It uses composition to access core functionality instead of inheritance.
|
|
15
20
|
*/
|
|
16
|
-
export class TaskServiceAttachments
|
|
21
|
+
export class TaskServiceAttachments {
|
|
22
|
+
constructor(core) {
|
|
23
|
+
this.core = core;
|
|
24
|
+
}
|
|
17
25
|
/**
|
|
18
26
|
* Upload a file attachment to a ClickUp task
|
|
19
27
|
* @param taskId The ID of the task to attach the file to
|
|
@@ -22,9 +30,9 @@ export class TaskServiceAttachments extends TaskServiceSearch {
|
|
|
22
30
|
* @returns Promise resolving to the attachment response from ClickUp
|
|
23
31
|
*/
|
|
24
32
|
async uploadTaskAttachment(taskId, fileData, fileName) {
|
|
25
|
-
this.logOperation('uploadTaskAttachment', { taskId, fileName, fileSize: fileData.length });
|
|
33
|
+
this.core.logOperation('uploadTaskAttachment', { taskId, fileName, fileSize: fileData.length });
|
|
26
34
|
try {
|
|
27
|
-
return await this.makeRequest(async () => {
|
|
35
|
+
return await this.core.makeRequest(async () => {
|
|
28
36
|
// Create FormData for multipart/form-data upload
|
|
29
37
|
const FormData = (await import('form-data')).default;
|
|
30
38
|
const formData = new FormData();
|
|
@@ -34,17 +42,17 @@ export class TaskServiceAttachments extends TaskServiceSearch {
|
|
|
34
42
|
contentType: 'application/octet-stream' // Let ClickUp determine the content type
|
|
35
43
|
});
|
|
36
44
|
// Use the raw axios client for this request since we need to handle FormData
|
|
37
|
-
const response = await this.client.post(`/task/${taskId}/attachment`, formData, {
|
|
45
|
+
const response = await this.core.client.post(`/task/${taskId}/attachment`, formData, {
|
|
38
46
|
headers: {
|
|
39
47
|
...formData.getHeaders(),
|
|
40
|
-
'Authorization': this.apiKey
|
|
48
|
+
'Authorization': this.core.apiKey
|
|
41
49
|
}
|
|
42
50
|
});
|
|
43
51
|
return response.data;
|
|
44
52
|
});
|
|
45
53
|
}
|
|
46
54
|
catch (error) {
|
|
47
|
-
throw this.handleError(error, `Failed to upload attachment to task ${taskId}`);
|
|
55
|
+
throw this.core.handleError(error, `Failed to upload attachment to task ${taskId}`);
|
|
48
56
|
}
|
|
49
57
|
}
|
|
50
58
|
/**
|
|
@@ -56,9 +64,9 @@ export class TaskServiceAttachments extends TaskServiceSearch {
|
|
|
56
64
|
* @returns Promise resolving to the attachment response from ClickUp
|
|
57
65
|
*/
|
|
58
66
|
async uploadTaskAttachmentFromUrl(taskId, fileUrl, fileName, authHeader) {
|
|
59
|
-
this.logOperation('uploadTaskAttachmentFromUrl', { taskId, fileUrl, fileName });
|
|
67
|
+
this.core.logOperation('uploadTaskAttachmentFromUrl', { taskId, fileUrl, fileName });
|
|
60
68
|
try {
|
|
61
|
-
return await this.makeRequest(async () => {
|
|
69
|
+
return await this.core.makeRequest(async () => {
|
|
62
70
|
// Import required modules
|
|
63
71
|
const axios = (await import('axios')).default;
|
|
64
72
|
const FormData = (await import('form-data')).default;
|
|
@@ -81,17 +89,17 @@ export class TaskServiceAttachments extends TaskServiceSearch {
|
|
|
81
89
|
contentType: 'application/octet-stream'
|
|
82
90
|
});
|
|
83
91
|
// Upload the file to ClickUp
|
|
84
|
-
const uploadResponse = await this.client.post(`/task/${taskId}/attachment`, formData, {
|
|
92
|
+
const uploadResponse = await this.core.client.post(`/task/${taskId}/attachment`, formData, {
|
|
85
93
|
headers: {
|
|
86
94
|
...formData.getHeaders(),
|
|
87
|
-
'Authorization': this.apiKey
|
|
95
|
+
'Authorization': this.core.apiKey
|
|
88
96
|
}
|
|
89
97
|
});
|
|
90
98
|
return uploadResponse.data;
|
|
91
99
|
});
|
|
92
100
|
}
|
|
93
101
|
catch (error) {
|
|
94
|
-
throw this.handleError(error, `Failed to upload attachment from URL to task ${taskId}`);
|
|
102
|
+
throw this.core.handleError(error, `Failed to upload attachment from URL to task ${taskId}`);
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
105
|
}
|