m365-agent-cli 1.2.0
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 +22 -0
- package/README.md +916 -0
- package/package.json +50 -0
- package/src/cli.ts +100 -0
- package/src/commands/auto-reply.ts +182 -0
- package/src/commands/calendar.ts +576 -0
- package/src/commands/counter.ts +87 -0
- package/src/commands/create-event.ts +544 -0
- package/src/commands/delegates.ts +286 -0
- package/src/commands/delete-event.ts +321 -0
- package/src/commands/drafts.ts +502 -0
- package/src/commands/files.ts +532 -0
- package/src/commands/find.ts +195 -0
- package/src/commands/findtime.ts +270 -0
- package/src/commands/folders.ts +177 -0
- package/src/commands/forward-event.ts +49 -0
- package/src/commands/graph-calendar.ts +217 -0
- package/src/commands/login.ts +195 -0
- package/src/commands/mail.ts +950 -0
- package/src/commands/oof.ts +263 -0
- package/src/commands/outlook-categories.ts +173 -0
- package/src/commands/outlook-graph.ts +880 -0
- package/src/commands/planner.ts +1678 -0
- package/src/commands/respond.ts +291 -0
- package/src/commands/rooms.ts +210 -0
- package/src/commands/rules.ts +511 -0
- package/src/commands/schedule.ts +109 -0
- package/src/commands/send.ts +204 -0
- package/src/commands/serve.ts +14 -0
- package/src/commands/sharepoint.ts +179 -0
- package/src/commands/site-pages.ts +163 -0
- package/src/commands/subscribe.ts +103 -0
- package/src/commands/subscriptions.ts +29 -0
- package/src/commands/suggest.ts +155 -0
- package/src/commands/todo.ts +2092 -0
- package/src/commands/update-event.ts +608 -0
- package/src/commands/update.ts +88 -0
- package/src/commands/verify-token.ts +62 -0
- package/src/commands/whoami.ts +74 -0
- package/src/index.ts +190 -0
- package/src/lib/atomic-write.ts +20 -0
- package/src/lib/attach-link-spec.test.ts +24 -0
- package/src/lib/attach-link-spec.ts +70 -0
- package/src/lib/attachments.ts +79 -0
- package/src/lib/auth.ts +192 -0
- package/src/lib/calendar-range.test.ts +41 -0
- package/src/lib/calendar-range.ts +103 -0
- package/src/lib/dates.test.ts +74 -0
- package/src/lib/dates.ts +137 -0
- package/src/lib/delegate-client.test.ts +74 -0
- package/src/lib/delegate-client.ts +322 -0
- package/src/lib/ews-client.ts +3418 -0
- package/src/lib/git-commit.ts +4 -0
- package/src/lib/glitchtip-eligibility.ts +220 -0
- package/src/lib/glitchtip.ts +253 -0
- package/src/lib/global-env.ts +3 -0
- package/src/lib/graph-auth.ts +223 -0
- package/src/lib/graph-calendar-client.test.ts +118 -0
- package/src/lib/graph-calendar-client.ts +112 -0
- package/src/lib/graph-client.test.ts +107 -0
- package/src/lib/graph-client.ts +1058 -0
- package/src/lib/graph-constants.ts +12 -0
- package/src/lib/graph-directory.ts +116 -0
- package/src/lib/graph-event.ts +134 -0
- package/src/lib/graph-schedule.ts +173 -0
- package/src/lib/graph-subscriptions.ts +94 -0
- package/src/lib/graph-user-path.ts +13 -0
- package/src/lib/jwt-utils.ts +34 -0
- package/src/lib/markdown.test.ts +21 -0
- package/src/lib/markdown.ts +174 -0
- package/src/lib/mime-type.ts +106 -0
- package/src/lib/oof-client.test.ts +59 -0
- package/src/lib/oof-client.ts +122 -0
- package/src/lib/outlook-graph-client.test.ts +146 -0
- package/src/lib/outlook-graph-client.ts +649 -0
- package/src/lib/outlook-master-categories.ts +145 -0
- package/src/lib/package-info.ts +59 -0
- package/src/lib/places-client.ts +144 -0
- package/src/lib/planner-client.ts +1226 -0
- package/src/lib/rules-client.ts +178 -0
- package/src/lib/sharepoint-client.ts +101 -0
- package/src/lib/site-pages-client.ts +73 -0
- package/src/lib/todo-client.test.ts +298 -0
- package/src/lib/todo-client.ts +1309 -0
- package/src/lib/url-validation.ts +40 -0
- package/src/lib/utils.ts +45 -0
- package/src/lib/webhook-server.ts +51 -0
- package/src/test/auth.test.ts +104 -0
- package/src/test/cli.integration.test.ts +1083 -0
- package/src/test/ews-client.test.ts +268 -0
- package/src/test/mocks/index.ts +375 -0
- package/src/test/mocks/responses.ts +861 -0
|
@@ -0,0 +1,1309 @@
|
|
|
1
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
callGraph,
|
|
5
|
+
callGraphAbsolute,
|
|
6
|
+
fetchAllPages,
|
|
7
|
+
fetchGraphRaw,
|
|
8
|
+
GraphApiError,
|
|
9
|
+
type GraphResponse,
|
|
10
|
+
graphError,
|
|
11
|
+
graphResult
|
|
12
|
+
} from './graph-client.js';
|
|
13
|
+
import { graphUserPath } from './graph-user-path.js';
|
|
14
|
+
|
|
15
|
+
function todoRoot(user?: string): string {
|
|
16
|
+
return graphUserPath(user, 'todo');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type TodoImportance = 'low' | 'normal' | 'high';
|
|
20
|
+
export type TodoStatus = 'notStarted' | 'inProgress' | 'completed' | 'waitingOnOthers' | 'deferred';
|
|
21
|
+
|
|
22
|
+
/** Graph [linkedResource](https://learn.microsoft.com/en-us/graph/api/resources/linkedresource); use `displayName` (alias: `description`). */
|
|
23
|
+
export interface TodoLinkedResource {
|
|
24
|
+
id?: string;
|
|
25
|
+
webUrl?: string;
|
|
26
|
+
/** Graph `displayName` (title of the link). */
|
|
27
|
+
displayName?: string;
|
|
28
|
+
/** Legacy alias for `displayName` when creating/updating. */
|
|
29
|
+
description?: string;
|
|
30
|
+
applicationName?: string;
|
|
31
|
+
externalId?: string;
|
|
32
|
+
iconUrl?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Shape payload for Graph `linkedResources` on todoTask (PATCH/POST). */
|
|
36
|
+
export function linkedResourceToGraphPayload(lr: TodoLinkedResource): Record<string, unknown> {
|
|
37
|
+
const displayName = lr.displayName ?? lr.description;
|
|
38
|
+
const out: Record<string, unknown> = {};
|
|
39
|
+
if (displayName !== undefined && displayName !== '') out.displayName = displayName;
|
|
40
|
+
if (lr.webUrl !== undefined) out.webUrl = lr.webUrl;
|
|
41
|
+
if (lr.applicationName !== undefined) out.applicationName = lr.applicationName;
|
|
42
|
+
if (lr.externalId !== undefined) out.externalId = lr.externalId;
|
|
43
|
+
if (lr.id !== undefined) out.id = lr.id;
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface TodoChecklistItem {
|
|
48
|
+
id: string;
|
|
49
|
+
displayName: string;
|
|
50
|
+
isChecked: boolean;
|
|
51
|
+
createdDateTime?: string;
|
|
52
|
+
/** Set when `isChecked` is true (Graph). */
|
|
53
|
+
checkedDateTime?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface TodoTask {
|
|
57
|
+
id: string;
|
|
58
|
+
title: string;
|
|
59
|
+
body?: { content: string; contentType: string };
|
|
60
|
+
isReminderOn?: boolean;
|
|
61
|
+
reminderDateTime?: { dateTime: string; timeZone: string };
|
|
62
|
+
dueDateTime?: { dateTime: string; timeZone: string };
|
|
63
|
+
startDateTime?: { dateTime: string; timeZone: string };
|
|
64
|
+
importance?: TodoImportance;
|
|
65
|
+
status?: TodoStatus;
|
|
66
|
+
/** Outlook-style category labels (strings). */
|
|
67
|
+
categories?: string[];
|
|
68
|
+
linkedResources?: TodoLinkedResource[];
|
|
69
|
+
checklistItems?: TodoChecklistItem[];
|
|
70
|
+
/** Graph `patternedRecurrence` resource (opaque JSON). */
|
|
71
|
+
recurrence?: Record<string, unknown>;
|
|
72
|
+
hasAttachments?: boolean;
|
|
73
|
+
createdDateTime?: string;
|
|
74
|
+
lastModifiedDateTime?: string;
|
|
75
|
+
completedDateTime?: { dateTime: string; timeZone: string };
|
|
76
|
+
bodyLastModifiedDateTime?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Small file attachment on a To Do task (Graph `taskFileAttachment`). */
|
|
80
|
+
export interface TodoAttachment {
|
|
81
|
+
id: string;
|
|
82
|
+
name?: string;
|
|
83
|
+
contentType?: string;
|
|
84
|
+
size?: number;
|
|
85
|
+
lastModifiedDateTime?: string;
|
|
86
|
+
'@odata.type'?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface TodoList {
|
|
90
|
+
id: string;
|
|
91
|
+
displayName: string;
|
|
92
|
+
isOwner?: boolean;
|
|
93
|
+
isShared?: boolean;
|
|
94
|
+
parentSectionId?: string;
|
|
95
|
+
wellknownListName?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function createTodoList(
|
|
99
|
+
token: string,
|
|
100
|
+
displayName: string,
|
|
101
|
+
user?: string
|
|
102
|
+
): Promise<GraphResponse<TodoList>> {
|
|
103
|
+
let result: GraphResponse<TodoList>;
|
|
104
|
+
try {
|
|
105
|
+
result = await callGraph<TodoList>(token, `${todoRoot(user)}/lists`, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
body: JSON.stringify({ displayName })
|
|
108
|
+
});
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if (err instanceof GraphApiError) {
|
|
111
|
+
return graphError(err.message, err.code, err.status);
|
|
112
|
+
}
|
|
113
|
+
return graphError(err instanceof Error ? err.message : 'Failed to create list');
|
|
114
|
+
}
|
|
115
|
+
if (!result.ok || !result.data) {
|
|
116
|
+
return graphError(result.error?.message || 'Failed to create list', result.error?.code, result.error?.status);
|
|
117
|
+
}
|
|
118
|
+
return graphResult(result.data);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function updateTodoList(
|
|
122
|
+
token: string,
|
|
123
|
+
listId: string,
|
|
124
|
+
displayName: string,
|
|
125
|
+
user?: string
|
|
126
|
+
): Promise<GraphResponse<TodoList>> {
|
|
127
|
+
let result: GraphResponse<TodoList>;
|
|
128
|
+
try {
|
|
129
|
+
result = await callGraph<TodoList>(token, `${todoRoot(user)}/lists/${encodeURIComponent(listId)}`, {
|
|
130
|
+
method: 'PATCH',
|
|
131
|
+
body: JSON.stringify({ displayName })
|
|
132
|
+
});
|
|
133
|
+
} catch (err) {
|
|
134
|
+
if (err instanceof GraphApiError) {
|
|
135
|
+
return graphError(err.message, err.code, err.status);
|
|
136
|
+
}
|
|
137
|
+
return graphError(err instanceof Error ? err.message : 'Failed to update list');
|
|
138
|
+
}
|
|
139
|
+
if (!result.ok || !result.data) {
|
|
140
|
+
return graphError(result.error?.message || 'Failed to update list', result.error?.code, result.error?.status);
|
|
141
|
+
}
|
|
142
|
+
return graphResult(result.data);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function deleteTodoList(token: string, listId: string, user?: string): Promise<GraphResponse<void>> {
|
|
146
|
+
try {
|
|
147
|
+
return await callGraph<void>(
|
|
148
|
+
token,
|
|
149
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}`,
|
|
150
|
+
{ method: 'DELETE' },
|
|
151
|
+
false
|
|
152
|
+
);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
if (err instanceof GraphApiError) {
|
|
155
|
+
return graphError(err.message, err.code, err.status);
|
|
156
|
+
}
|
|
157
|
+
return graphError(err instanceof Error ? err.message : 'Failed to delete list');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function getTodoLists(token: string, user?: string): Promise<GraphResponse<TodoList[]>> {
|
|
162
|
+
let result: GraphResponse<{ value: TodoList[] }>;
|
|
163
|
+
try {
|
|
164
|
+
result = await callGraph<{ value: TodoList[] }>(token, `${todoRoot(user)}/lists`);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
if (err instanceof GraphApiError) {
|
|
167
|
+
return graphError(err.message, err.code, err.status);
|
|
168
|
+
}
|
|
169
|
+
return graphError(err instanceof Error ? err.message : 'Failed to get todo lists');
|
|
170
|
+
}
|
|
171
|
+
if (!result.ok || !result.data) {
|
|
172
|
+
return graphError(result.error?.message || 'Failed to get todo lists', result.error?.code, result.error?.status);
|
|
173
|
+
}
|
|
174
|
+
return graphResult(result.data.value);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function getTodoList(token: string, listId: string, user?: string): Promise<GraphResponse<TodoList>> {
|
|
178
|
+
try {
|
|
179
|
+
return await callGraph<TodoList>(token, `${todoRoot(user)}/lists/${encodeURIComponent(listId)}`);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (err instanceof GraphApiError) {
|
|
182
|
+
return graphError(err.message, err.code, err.status);
|
|
183
|
+
}
|
|
184
|
+
return graphError(err instanceof Error ? err.message : 'Failed to get todo list');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface TodoTasksQueryOptions {
|
|
189
|
+
filter?: string;
|
|
190
|
+
orderby?: string;
|
|
191
|
+
select?: string;
|
|
192
|
+
/** When set, only one page is returned (no automatic paging). */
|
|
193
|
+
top?: number;
|
|
194
|
+
skip?: number;
|
|
195
|
+
/** OData `$expand` (e.g. `attachments`). */
|
|
196
|
+
expand?: string;
|
|
197
|
+
/** Set `$count=true` (may require `ConsistencyLevel: eventual` on some tenants). */
|
|
198
|
+
count?: boolean;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function tasksListPath(listId: string, user: string | undefined, query?: string | TodoTasksQueryOptions): string {
|
|
202
|
+
const params = new URLSearchParams();
|
|
203
|
+
if (query === undefined) {
|
|
204
|
+
// no query params
|
|
205
|
+
} else if (typeof query === 'string') {
|
|
206
|
+
if (query) params.set('$filter', query);
|
|
207
|
+
} else {
|
|
208
|
+
if (query.filter) params.set('$filter', query.filter);
|
|
209
|
+
if (query.orderby) params.set('$orderby', query.orderby);
|
|
210
|
+
if (query.select) params.set('$select', query.select);
|
|
211
|
+
if (query.top !== undefined) params.set('$top', String(query.top));
|
|
212
|
+
if (query.skip !== undefined) params.set('$skip', String(query.skip));
|
|
213
|
+
if (query.expand) params.set('$expand', query.expand);
|
|
214
|
+
if (query.count) params.set('$count', 'true');
|
|
215
|
+
}
|
|
216
|
+
const qs = params.toString() ? `?${params.toString()}` : '';
|
|
217
|
+
return `${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks${qs}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function getTasks(
|
|
221
|
+
token: string,
|
|
222
|
+
listId: string,
|
|
223
|
+
filterOrQuery?: string | TodoTasksQueryOptions,
|
|
224
|
+
user?: string
|
|
225
|
+
): Promise<GraphResponse<TodoTask[]>> {
|
|
226
|
+
const path = tasksListPath(listId, user, filterOrQuery);
|
|
227
|
+
const singlePage =
|
|
228
|
+
filterOrQuery !== undefined &&
|
|
229
|
+
typeof filterOrQuery === 'object' &&
|
|
230
|
+
(filterOrQuery.top !== undefined || filterOrQuery.skip !== undefined || filterOrQuery.count === true);
|
|
231
|
+
|
|
232
|
+
if (singlePage) {
|
|
233
|
+
let result: GraphResponse<{ value: TodoTask[] }>;
|
|
234
|
+
try {
|
|
235
|
+
result = await callGraph<{ value: TodoTask[] }>(token, path);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
if (err instanceof GraphApiError) {
|
|
238
|
+
return graphError(err.message, err.code, err.status);
|
|
239
|
+
}
|
|
240
|
+
return graphError(err instanceof Error ? err.message : 'Failed to get tasks');
|
|
241
|
+
}
|
|
242
|
+
if (!result.ok || !result.data) {
|
|
243
|
+
return graphError(result.error?.message || 'Failed to get tasks', result.error?.code, result.error?.status);
|
|
244
|
+
}
|
|
245
|
+
return graphResult(result.data.value);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return fetchAllPages<TodoTask>(token, path, 'Failed to get tasks');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export async function getTask(
|
|
252
|
+
token: string,
|
|
253
|
+
listId: string,
|
|
254
|
+
taskId: string,
|
|
255
|
+
user?: string,
|
|
256
|
+
options?: { select?: string }
|
|
257
|
+
): Promise<GraphResponse<TodoTask>> {
|
|
258
|
+
try {
|
|
259
|
+
const qs = options?.select ? `?$select=${encodeURIComponent(options.select)}` : '';
|
|
260
|
+
return await callGraph<TodoTask>(
|
|
261
|
+
token,
|
|
262
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}${qs}`
|
|
263
|
+
);
|
|
264
|
+
} catch (err) {
|
|
265
|
+
if (err instanceof GraphApiError) {
|
|
266
|
+
return graphError(err.message, err.code, err.status);
|
|
267
|
+
}
|
|
268
|
+
return graphError(err instanceof Error ? err.message : 'Failed to get task');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export interface CreateTaskOptions {
|
|
273
|
+
title: string;
|
|
274
|
+
body?: string;
|
|
275
|
+
bodyContentType?: string;
|
|
276
|
+
dueDateTime?: string;
|
|
277
|
+
startDateTime?: string;
|
|
278
|
+
timeZone?: string;
|
|
279
|
+
dueTimeZone?: string;
|
|
280
|
+
startTimeZone?: string;
|
|
281
|
+
reminderTimeZone?: string;
|
|
282
|
+
importance?: TodoImportance;
|
|
283
|
+
status?: TodoStatus;
|
|
284
|
+
isReminderOn?: boolean;
|
|
285
|
+
reminderDateTime?: string;
|
|
286
|
+
linkedResources?: TodoLinkedResource[];
|
|
287
|
+
categories?: string[];
|
|
288
|
+
/** Graph `patternedRecurrence` (see Microsoft Graph docs). */
|
|
289
|
+
recurrence?: Record<string, unknown>;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export async function createTask(
|
|
293
|
+
token: string,
|
|
294
|
+
listId: string,
|
|
295
|
+
options: CreateTaskOptions,
|
|
296
|
+
user?: string
|
|
297
|
+
): Promise<GraphResponse<TodoTask>> {
|
|
298
|
+
const payload: Record<string, unknown> = { title: options.title };
|
|
299
|
+
if (options.body) payload.body = { content: options.body, contentType: options.bodyContentType || 'text' };
|
|
300
|
+
if (options.dueDateTime) {
|
|
301
|
+
payload.dueDateTime = {
|
|
302
|
+
dateTime: options.dueDateTime,
|
|
303
|
+
timeZone: options.dueTimeZone ?? options.timeZone ?? 'UTC'
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
if (options.startDateTime) {
|
|
307
|
+
payload.startDateTime = {
|
|
308
|
+
dateTime: options.startDateTime,
|
|
309
|
+
timeZone: options.startTimeZone ?? options.timeZone ?? 'UTC'
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (options.importance) payload.importance = options.importance;
|
|
313
|
+
if (options.status) payload.status = options.status;
|
|
314
|
+
if (options.isReminderOn !== undefined) payload.isReminderOn = options.isReminderOn;
|
|
315
|
+
if (options.reminderDateTime) {
|
|
316
|
+
payload.reminderDateTime = {
|
|
317
|
+
dateTime: options.reminderDateTime,
|
|
318
|
+
timeZone: options.reminderTimeZone ?? options.timeZone ?? 'UTC'
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
if (options.linkedResources?.length) {
|
|
322
|
+
payload.linkedResources = options.linkedResources.map((lr) => linkedResourceToGraphPayload(lr));
|
|
323
|
+
}
|
|
324
|
+
if (options.categories?.length) payload.categories = options.categories;
|
|
325
|
+
if (options.recurrence !== undefined) payload.recurrence = options.recurrence;
|
|
326
|
+
let result: GraphResponse<TodoTask>;
|
|
327
|
+
try {
|
|
328
|
+
result = await callGraph<TodoTask>(token, `${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks`, {
|
|
329
|
+
method: 'POST',
|
|
330
|
+
body: JSON.stringify(payload)
|
|
331
|
+
});
|
|
332
|
+
} catch (err) {
|
|
333
|
+
if (err instanceof GraphApiError) {
|
|
334
|
+
return graphError(err.message, err.code, err.status);
|
|
335
|
+
}
|
|
336
|
+
return graphError(err instanceof Error ? err.message : 'Failed to create task');
|
|
337
|
+
}
|
|
338
|
+
if (!result.ok || !result.data) {
|
|
339
|
+
return graphError(result.error?.message || 'Failed to create task', result.error?.code, result.error?.status);
|
|
340
|
+
}
|
|
341
|
+
return graphResult(result.data);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export interface UpdateTaskOptions {
|
|
345
|
+
title?: string;
|
|
346
|
+
body?: string;
|
|
347
|
+
bodyContentType?: string;
|
|
348
|
+
dueDateTime?: string | null;
|
|
349
|
+
startDateTime?: string | null;
|
|
350
|
+
timeZone?: string;
|
|
351
|
+
dueTimeZone?: string;
|
|
352
|
+
startTimeZone?: string;
|
|
353
|
+
reminderTimeZone?: string;
|
|
354
|
+
importance?: TodoImportance;
|
|
355
|
+
status?: TodoStatus;
|
|
356
|
+
isReminderOn?: boolean;
|
|
357
|
+
reminderDateTime?: string | null;
|
|
358
|
+
completedDateTime?: string | null;
|
|
359
|
+
linkedResources?: TodoLinkedResource[];
|
|
360
|
+
/** Replace categories when set (including empty array). */
|
|
361
|
+
categories?: string[];
|
|
362
|
+
/** When true, PATCH with categories: []. Ignored if categories is set. */
|
|
363
|
+
clearCategories?: boolean;
|
|
364
|
+
/** Set or clear recurrence; `null` removes recurrence from the task. */
|
|
365
|
+
recurrence?: Record<string, unknown> | null;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export async function updateTask(
|
|
369
|
+
token: string,
|
|
370
|
+
listId: string,
|
|
371
|
+
taskId: string,
|
|
372
|
+
options: UpdateTaskOptions,
|
|
373
|
+
user?: string
|
|
374
|
+
): Promise<GraphResponse<TodoTask>> {
|
|
375
|
+
const payload: Record<string, unknown> = {};
|
|
376
|
+
if (options.title !== undefined) payload.title = options.title;
|
|
377
|
+
if (options.body !== undefined)
|
|
378
|
+
payload.body = { content: options.body, contentType: options.bodyContentType || 'text' };
|
|
379
|
+
if (options.dueDateTime !== undefined) {
|
|
380
|
+
payload.dueDateTime =
|
|
381
|
+
options.dueDateTime === null
|
|
382
|
+
? null
|
|
383
|
+
: { dateTime: options.dueDateTime, timeZone: options.dueTimeZone ?? options.timeZone ?? 'UTC' };
|
|
384
|
+
}
|
|
385
|
+
if (options.startDateTime !== undefined) {
|
|
386
|
+
payload.startDateTime =
|
|
387
|
+
options.startDateTime === null
|
|
388
|
+
? null
|
|
389
|
+
: { dateTime: options.startDateTime, timeZone: options.startTimeZone ?? options.timeZone ?? 'UTC' };
|
|
390
|
+
}
|
|
391
|
+
if (options.importance !== undefined) payload.importance = options.importance;
|
|
392
|
+
if (options.status !== undefined) payload.status = options.status;
|
|
393
|
+
if (options.isReminderOn !== undefined) payload.isReminderOn = options.isReminderOn;
|
|
394
|
+
if (options.reminderDateTime !== undefined) {
|
|
395
|
+
payload.reminderDateTime =
|
|
396
|
+
options.reminderDateTime === null
|
|
397
|
+
? null
|
|
398
|
+
: { dateTime: options.reminderDateTime, timeZone: options.reminderTimeZone ?? options.timeZone ?? 'UTC' };
|
|
399
|
+
}
|
|
400
|
+
if (options.completedDateTime !== undefined) {
|
|
401
|
+
payload.completedDateTime =
|
|
402
|
+
options.completedDateTime === null
|
|
403
|
+
? null
|
|
404
|
+
: { dateTime: options.completedDateTime, timeZone: options.timeZone || 'UTC' };
|
|
405
|
+
}
|
|
406
|
+
if (options.linkedResources !== undefined) {
|
|
407
|
+
payload.linkedResources = options.linkedResources.map((lr) => linkedResourceToGraphPayload(lr));
|
|
408
|
+
}
|
|
409
|
+
if (options.categories !== undefined) payload.categories = options.categories;
|
|
410
|
+
else if (options.clearCategories) payload.categories = [];
|
|
411
|
+
if (options.recurrence !== undefined) payload.recurrence = options.recurrence;
|
|
412
|
+
let result: GraphResponse<TodoTask>;
|
|
413
|
+
try {
|
|
414
|
+
result = await callGraph<TodoTask>(
|
|
415
|
+
token,
|
|
416
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}`,
|
|
417
|
+
{ method: 'PATCH', body: JSON.stringify(payload) }
|
|
418
|
+
);
|
|
419
|
+
} catch (err) {
|
|
420
|
+
if (err instanceof GraphApiError) {
|
|
421
|
+
return graphError(err.message, err.code, err.status);
|
|
422
|
+
}
|
|
423
|
+
return graphError(err instanceof Error ? err.message : 'Failed to update task');
|
|
424
|
+
}
|
|
425
|
+
if (!result.ok || !result.data) {
|
|
426
|
+
return graphError(result.error?.message || 'Failed to update task', result.error?.code, result.error?.status);
|
|
427
|
+
}
|
|
428
|
+
return graphResult(result.data);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export async function deleteTask(
|
|
432
|
+
token: string,
|
|
433
|
+
listId: string,
|
|
434
|
+
taskId: string,
|
|
435
|
+
user?: string
|
|
436
|
+
): Promise<GraphResponse<void>> {
|
|
437
|
+
try {
|
|
438
|
+
return await callGraph<void>(
|
|
439
|
+
token,
|
|
440
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}`,
|
|
441
|
+
{ method: 'DELETE' },
|
|
442
|
+
false
|
|
443
|
+
);
|
|
444
|
+
} catch (err) {
|
|
445
|
+
if (err instanceof GraphApiError) {
|
|
446
|
+
return graphError(err.message, err.code, err.status);
|
|
447
|
+
}
|
|
448
|
+
return graphError(err instanceof Error ? err.message : 'Failed to delete task');
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export async function addChecklistItem(
|
|
453
|
+
token: string,
|
|
454
|
+
listId: string,
|
|
455
|
+
taskId: string,
|
|
456
|
+
displayName: string,
|
|
457
|
+
user?: string
|
|
458
|
+
): Promise<GraphResponse<TodoChecklistItem>> {
|
|
459
|
+
let result: GraphResponse<TodoChecklistItem>;
|
|
460
|
+
try {
|
|
461
|
+
result = await callGraph<TodoChecklistItem>(
|
|
462
|
+
token,
|
|
463
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}/checklistItems`,
|
|
464
|
+
{ method: 'POST', body: JSON.stringify({ displayName }) }
|
|
465
|
+
);
|
|
466
|
+
} catch (err) {
|
|
467
|
+
if (err instanceof GraphApiError) {
|
|
468
|
+
return graphError(err.message, err.code, err.status);
|
|
469
|
+
}
|
|
470
|
+
return graphError(err instanceof Error ? err.message : 'Failed to add checklist item');
|
|
471
|
+
}
|
|
472
|
+
if (!result.ok || !result.data) {
|
|
473
|
+
return graphError(
|
|
474
|
+
result.error?.message || 'Failed to add checklist item',
|
|
475
|
+
result.error?.code,
|
|
476
|
+
result.error?.status
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
return graphResult(result.data);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export async function deleteChecklistItem(
|
|
483
|
+
token: string,
|
|
484
|
+
listId: string,
|
|
485
|
+
taskId: string,
|
|
486
|
+
checklistItemId: string,
|
|
487
|
+
user?: string
|
|
488
|
+
): Promise<GraphResponse<void>> {
|
|
489
|
+
try {
|
|
490
|
+
return await callGraph<void>(
|
|
491
|
+
token,
|
|
492
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}/checklistItems/${encodeURIComponent(checklistItemId)}`,
|
|
493
|
+
{ method: 'DELETE' },
|
|
494
|
+
false
|
|
495
|
+
);
|
|
496
|
+
} catch (err) {
|
|
497
|
+
if (err instanceof GraphApiError) {
|
|
498
|
+
return graphError(err.message, err.code, err.status);
|
|
499
|
+
}
|
|
500
|
+
return graphError(err instanceof Error ? err.message : 'Failed to delete checklist item');
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export async function updateChecklistItem(
|
|
505
|
+
token: string,
|
|
506
|
+
listId: string,
|
|
507
|
+
taskId: string,
|
|
508
|
+
checklistItemId: string,
|
|
509
|
+
patch: { displayName?: string; isChecked?: boolean },
|
|
510
|
+
user?: string
|
|
511
|
+
): Promise<GraphResponse<TodoChecklistItem>> {
|
|
512
|
+
let result: GraphResponse<TodoChecklistItem>;
|
|
513
|
+
try {
|
|
514
|
+
result = await callGraph<TodoChecklistItem>(
|
|
515
|
+
token,
|
|
516
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}/checklistItems/${encodeURIComponent(checklistItemId)}`,
|
|
517
|
+
{ method: 'PATCH', body: JSON.stringify(patch) }
|
|
518
|
+
);
|
|
519
|
+
} catch (err) {
|
|
520
|
+
if (err instanceof GraphApiError) {
|
|
521
|
+
return graphError(err.message, err.code, err.status);
|
|
522
|
+
}
|
|
523
|
+
return graphError(err instanceof Error ? err.message : 'Failed to update checklist item');
|
|
524
|
+
}
|
|
525
|
+
if (!result.ok || !result.data) {
|
|
526
|
+
return graphError(
|
|
527
|
+
result.error?.message || 'Failed to update checklist item',
|
|
528
|
+
result.error?.code,
|
|
529
|
+
result.error?.status
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
return graphResult(result.data);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
export async function listAttachments(
|
|
536
|
+
token: string,
|
|
537
|
+
listId: string,
|
|
538
|
+
taskId: string,
|
|
539
|
+
user?: string
|
|
540
|
+
): Promise<GraphResponse<TodoAttachment[]>> {
|
|
541
|
+
return fetchAllPages<TodoAttachment>(
|
|
542
|
+
token,
|
|
543
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}/attachments`,
|
|
544
|
+
'Failed to list attachments'
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export async function createTaskFileAttachment(
|
|
549
|
+
token: string,
|
|
550
|
+
listId: string,
|
|
551
|
+
taskId: string,
|
|
552
|
+
name: string,
|
|
553
|
+
contentBytesBase64: string,
|
|
554
|
+
contentType: string,
|
|
555
|
+
user?: string
|
|
556
|
+
): Promise<GraphResponse<TodoAttachment>> {
|
|
557
|
+
const body = {
|
|
558
|
+
'@odata.type': '#microsoft.graph.taskFileAttachment',
|
|
559
|
+
name,
|
|
560
|
+
contentBytes: contentBytesBase64,
|
|
561
|
+
contentType
|
|
562
|
+
};
|
|
563
|
+
let result: GraphResponse<TodoAttachment>;
|
|
564
|
+
try {
|
|
565
|
+
result = await callGraph<TodoAttachment>(
|
|
566
|
+
token,
|
|
567
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}/attachments`,
|
|
568
|
+
{ method: 'POST', body: JSON.stringify(body) }
|
|
569
|
+
);
|
|
570
|
+
} catch (err) {
|
|
571
|
+
if (err instanceof GraphApiError) {
|
|
572
|
+
return graphError(err.message, err.code, err.status);
|
|
573
|
+
}
|
|
574
|
+
return graphError(err instanceof Error ? err.message : 'Failed to add attachment');
|
|
575
|
+
}
|
|
576
|
+
if (!result.ok || !result.data) {
|
|
577
|
+
return graphError(result.error?.message || 'Failed to add attachment', result.error?.code, result.error?.status);
|
|
578
|
+
}
|
|
579
|
+
return graphResult(result.data);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export async function deleteAttachment(
|
|
583
|
+
token: string,
|
|
584
|
+
listId: string,
|
|
585
|
+
taskId: string,
|
|
586
|
+
attachmentId: string,
|
|
587
|
+
user?: string
|
|
588
|
+
): Promise<GraphResponse<void>> {
|
|
589
|
+
try {
|
|
590
|
+
return await callGraph<void>(
|
|
591
|
+
token,
|
|
592
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}/attachments/${encodeURIComponent(attachmentId)}`,
|
|
593
|
+
{ method: 'DELETE' },
|
|
594
|
+
false
|
|
595
|
+
);
|
|
596
|
+
} catch (err) {
|
|
597
|
+
if (err instanceof GraphApiError) {
|
|
598
|
+
return graphError(err.message, err.code, err.status);
|
|
599
|
+
}
|
|
600
|
+
return graphError(err instanceof Error ? err.message : 'Failed to delete attachment');
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export async function getTaskAttachment(
|
|
605
|
+
token: string,
|
|
606
|
+
listId: string,
|
|
607
|
+
taskId: string,
|
|
608
|
+
attachmentId: string,
|
|
609
|
+
user?: string
|
|
610
|
+
): Promise<GraphResponse<TodoAttachment>> {
|
|
611
|
+
try {
|
|
612
|
+
const result = await callGraph<TodoAttachment>(
|
|
613
|
+
token,
|
|
614
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}/attachments/${encodeURIComponent(attachmentId)}`
|
|
615
|
+
);
|
|
616
|
+
if (!result.ok || !result.data) {
|
|
617
|
+
return graphError(result.error?.message || 'Failed to get attachment', result.error?.code, result.error?.status);
|
|
618
|
+
}
|
|
619
|
+
return graphResult(result.data);
|
|
620
|
+
} catch (err) {
|
|
621
|
+
if (err instanceof GraphApiError) return graphError(err.message, err.code, err.status);
|
|
622
|
+
return graphError(err instanceof Error ? err.message : 'Failed to get attachment');
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/** Link attachment (URL reference), not file bytes. */
|
|
627
|
+
export async function createTaskReferenceAttachment(
|
|
628
|
+
token: string,
|
|
629
|
+
listId: string,
|
|
630
|
+
taskId: string,
|
|
631
|
+
name: string,
|
|
632
|
+
url: string,
|
|
633
|
+
user?: string
|
|
634
|
+
): Promise<GraphResponse<TodoAttachment>> {
|
|
635
|
+
const body = {
|
|
636
|
+
'@odata.type': '#microsoft.graph.taskReferenceAttachment',
|
|
637
|
+
name,
|
|
638
|
+
url
|
|
639
|
+
};
|
|
640
|
+
let result: GraphResponse<TodoAttachment>;
|
|
641
|
+
try {
|
|
642
|
+
result = await callGraph<TodoAttachment>(
|
|
643
|
+
token,
|
|
644
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}/attachments`,
|
|
645
|
+
{ method: 'POST', body: JSON.stringify(body) }
|
|
646
|
+
);
|
|
647
|
+
} catch (err) {
|
|
648
|
+
if (err instanceof GraphApiError) {
|
|
649
|
+
return graphError(err.message, err.code, err.status);
|
|
650
|
+
}
|
|
651
|
+
return graphError(err instanceof Error ? err.message : 'Failed to add reference attachment');
|
|
652
|
+
}
|
|
653
|
+
if (!result.ok || !result.data) {
|
|
654
|
+
return graphError(
|
|
655
|
+
result.error?.message || 'Failed to add reference attachment',
|
|
656
|
+
result.error?.code,
|
|
657
|
+
result.error?.status
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
return graphResult(result.data);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
export async function addLinkedResource(
|
|
664
|
+
token: string,
|
|
665
|
+
listId: string,
|
|
666
|
+
taskId: string,
|
|
667
|
+
resource: TodoLinkedResource,
|
|
668
|
+
user?: string
|
|
669
|
+
): Promise<GraphResponse<TodoTask>> {
|
|
670
|
+
const tr = await getTask(token, listId, taskId, user);
|
|
671
|
+
if (!tr.ok || !tr.data) {
|
|
672
|
+
return graphError(tr.error?.message || 'Failed to get task', tr.error?.code, tr.error?.status);
|
|
673
|
+
}
|
|
674
|
+
const existing = (tr.data.linkedResources || []) as TodoLinkedResource[];
|
|
675
|
+
const merged = [...existing, resource];
|
|
676
|
+
return updateTask(token, listId, taskId, { linkedResources: merged }, user);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export async function removeLinkedResourceByWebUrl(
|
|
680
|
+
token: string,
|
|
681
|
+
listId: string,
|
|
682
|
+
taskId: string,
|
|
683
|
+
webUrl: string,
|
|
684
|
+
user?: string
|
|
685
|
+
): Promise<GraphResponse<TodoTask>> {
|
|
686
|
+
const tr = await getTask(token, listId, taskId, user);
|
|
687
|
+
if (!tr.ok || !tr.data) {
|
|
688
|
+
return graphError(tr.error?.message || 'Failed to get task', tr.error?.code, tr.error?.status);
|
|
689
|
+
}
|
|
690
|
+
const merged = (tr.data.linkedResources || []).filter((r) => r.webUrl !== webUrl);
|
|
691
|
+
return updateTask(token, listId, taskId, { linkedResources: merged as TodoLinkedResource[] }, user);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function linkedResourcesCollectionPath(
|
|
695
|
+
listId: string,
|
|
696
|
+
taskId: string,
|
|
697
|
+
user: string | undefined,
|
|
698
|
+
linkedResourceId?: string
|
|
699
|
+
): string {
|
|
700
|
+
const b = `${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}/linkedResources`;
|
|
701
|
+
return linkedResourceId ? `${b}/${encodeURIComponent(linkedResourceId)}` : b;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/** List linked resources via the task navigation (same data as `linkedResources` on todoTask; supports paging). */
|
|
705
|
+
export async function listTaskLinkedResources(
|
|
706
|
+
token: string,
|
|
707
|
+
listId: string,
|
|
708
|
+
taskId: string,
|
|
709
|
+
user?: string
|
|
710
|
+
): Promise<GraphResponse<TodoLinkedResource[]>> {
|
|
711
|
+
return fetchAllPages<TodoLinkedResource>(
|
|
712
|
+
token,
|
|
713
|
+
linkedResourcesCollectionPath(listId, taskId, user),
|
|
714
|
+
'Failed to list linked resources'
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export async function createTaskLinkedResource(
|
|
719
|
+
token: string,
|
|
720
|
+
listId: string,
|
|
721
|
+
taskId: string,
|
|
722
|
+
resource: TodoLinkedResource,
|
|
723
|
+
user?: string
|
|
724
|
+
): Promise<GraphResponse<TodoLinkedResource>> {
|
|
725
|
+
let result: GraphResponse<TodoLinkedResource>;
|
|
726
|
+
try {
|
|
727
|
+
result = await callGraph<TodoLinkedResource>(token, linkedResourcesCollectionPath(listId, taskId, user), {
|
|
728
|
+
method: 'POST',
|
|
729
|
+
body: JSON.stringify(linkedResourceToGraphPayload(resource))
|
|
730
|
+
});
|
|
731
|
+
} catch (err) {
|
|
732
|
+
if (err instanceof GraphApiError) {
|
|
733
|
+
return graphError(err.message, err.code, err.status);
|
|
734
|
+
}
|
|
735
|
+
return graphError(err instanceof Error ? err.message : 'Failed to create linked resource');
|
|
736
|
+
}
|
|
737
|
+
if (!result.ok || !result.data) {
|
|
738
|
+
return graphError(
|
|
739
|
+
result.error?.message || 'Failed to create linked resource',
|
|
740
|
+
result.error?.code,
|
|
741
|
+
result.error?.status
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
return graphResult(result.data);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
export async function getTaskLinkedResource(
|
|
748
|
+
token: string,
|
|
749
|
+
listId: string,
|
|
750
|
+
taskId: string,
|
|
751
|
+
linkedResourceId: string,
|
|
752
|
+
user?: string
|
|
753
|
+
): Promise<GraphResponse<TodoLinkedResource>> {
|
|
754
|
+
try {
|
|
755
|
+
const result = await callGraph<TodoLinkedResource>(
|
|
756
|
+
token,
|
|
757
|
+
linkedResourcesCollectionPath(listId, taskId, user, linkedResourceId)
|
|
758
|
+
);
|
|
759
|
+
if (!result.ok || !result.data) {
|
|
760
|
+
return graphError(
|
|
761
|
+
result.error?.message || 'Failed to get linked resource',
|
|
762
|
+
result.error?.code,
|
|
763
|
+
result.error?.status
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
return graphResult(result.data);
|
|
767
|
+
} catch (err) {
|
|
768
|
+
if (err instanceof GraphApiError) return graphError(err.message, err.code, err.status);
|
|
769
|
+
return graphError(err instanceof Error ? err.message : 'Failed to get linked resource');
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
export async function updateTaskLinkedResource(
|
|
774
|
+
token: string,
|
|
775
|
+
listId: string,
|
|
776
|
+
taskId: string,
|
|
777
|
+
linkedResourceId: string,
|
|
778
|
+
patch: Partial<Pick<TodoLinkedResource, 'webUrl' | 'displayName' | 'description' | 'applicationName' | 'externalId'>>,
|
|
779
|
+
user?: string
|
|
780
|
+
): Promise<GraphResponse<TodoLinkedResource>> {
|
|
781
|
+
const body: Record<string, unknown> = {};
|
|
782
|
+
if (patch.webUrl !== undefined) body.webUrl = patch.webUrl;
|
|
783
|
+
if (patch.applicationName !== undefined) body.applicationName = patch.applicationName;
|
|
784
|
+
if (patch.externalId !== undefined) body.externalId = patch.externalId;
|
|
785
|
+
const displayName = patch.displayName ?? patch.description;
|
|
786
|
+
if (displayName !== undefined) body.displayName = displayName;
|
|
787
|
+
body['@odata.type'] = '#microsoft.graph.linkedResource';
|
|
788
|
+
let result: GraphResponse<TodoLinkedResource>;
|
|
789
|
+
try {
|
|
790
|
+
result = await callGraph<TodoLinkedResource>(
|
|
791
|
+
token,
|
|
792
|
+
linkedResourcesCollectionPath(listId, taskId, user, linkedResourceId),
|
|
793
|
+
{ method: 'PATCH', body: JSON.stringify(body) }
|
|
794
|
+
);
|
|
795
|
+
} catch (err) {
|
|
796
|
+
if (err instanceof GraphApiError) {
|
|
797
|
+
return graphError(err.message, err.code, err.status);
|
|
798
|
+
}
|
|
799
|
+
return graphError(err instanceof Error ? err.message : 'Failed to update linked resource');
|
|
800
|
+
}
|
|
801
|
+
if (!result.ok || !result.data) {
|
|
802
|
+
return graphError(
|
|
803
|
+
result.error?.message || 'Failed to update linked resource',
|
|
804
|
+
result.error?.code,
|
|
805
|
+
result.error?.status
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
return graphResult(result.data);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
export async function deleteTaskLinkedResource(
|
|
812
|
+
token: string,
|
|
813
|
+
listId: string,
|
|
814
|
+
taskId: string,
|
|
815
|
+
linkedResourceId: string,
|
|
816
|
+
user?: string
|
|
817
|
+
): Promise<GraphResponse<void>> {
|
|
818
|
+
try {
|
|
819
|
+
return await callGraph<void>(
|
|
820
|
+
token,
|
|
821
|
+
linkedResourcesCollectionPath(listId, taskId, user, linkedResourceId),
|
|
822
|
+
{ method: 'DELETE' },
|
|
823
|
+
false
|
|
824
|
+
);
|
|
825
|
+
} catch (err) {
|
|
826
|
+
if (err instanceof GraphApiError) {
|
|
827
|
+
return graphError(err.message, err.code, err.status);
|
|
828
|
+
}
|
|
829
|
+
return graphError(err instanceof Error ? err.message : 'Failed to delete linked resource');
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
export async function listTaskChecklistItems(
|
|
834
|
+
token: string,
|
|
835
|
+
listId: string,
|
|
836
|
+
taskId: string,
|
|
837
|
+
user?: string
|
|
838
|
+
): Promise<GraphResponse<TodoChecklistItem[]>> {
|
|
839
|
+
return fetchAllPages<TodoChecklistItem>(
|
|
840
|
+
token,
|
|
841
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}/checklistItems`,
|
|
842
|
+
'Failed to list checklist items'
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/** `GET .../tasks/{taskId}/checklistItems/{checklistItemId}` (see Graph checklistItem). */
|
|
847
|
+
export async function getChecklistItem(
|
|
848
|
+
token: string,
|
|
849
|
+
listId: string,
|
|
850
|
+
taskId: string,
|
|
851
|
+
checklistItemId: string,
|
|
852
|
+
user?: string
|
|
853
|
+
): Promise<GraphResponse<TodoChecklistItem>> {
|
|
854
|
+
try {
|
|
855
|
+
const result = await callGraph<TodoChecklistItem>(
|
|
856
|
+
token,
|
|
857
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}/checklistItems/${encodeURIComponent(checklistItemId)}`
|
|
858
|
+
);
|
|
859
|
+
if (!result.ok || !result.data) {
|
|
860
|
+
return graphError(
|
|
861
|
+
result.error?.message || 'Failed to get checklist item',
|
|
862
|
+
result.error?.code,
|
|
863
|
+
result.error?.status
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
return graphResult(result.data);
|
|
867
|
+
} catch (err) {
|
|
868
|
+
if (err instanceof GraphApiError) return graphError(err.message, err.code, err.status);
|
|
869
|
+
return graphError(err instanceof Error ? err.message : 'Failed to get checklist item');
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Raw file bytes for a task file attachment (`GET .../attachments/{id}/$value`).
|
|
875
|
+
* Reference attachments do not support this; use metadata from {@link getTaskAttachment} instead.
|
|
876
|
+
*/
|
|
877
|
+
export async function getTaskAttachmentContent(
|
|
878
|
+
token: string,
|
|
879
|
+
listId: string,
|
|
880
|
+
taskId: string,
|
|
881
|
+
attachmentId: string,
|
|
882
|
+
user?: string
|
|
883
|
+
): Promise<GraphResponse<Uint8Array>> {
|
|
884
|
+
const path = `${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}/attachments/${encodeURIComponent(attachmentId)}/$value`;
|
|
885
|
+
try {
|
|
886
|
+
const res = await fetchGraphRaw(token, path);
|
|
887
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
888
|
+
if (!res.ok) {
|
|
889
|
+
try {
|
|
890
|
+
const text = new TextDecoder().decode(buf);
|
|
891
|
+
const json = JSON.parse(text) as { error?: { code?: string; message?: string } };
|
|
892
|
+
return graphError(json.error?.message || `HTTP ${res.status}`, json.error?.code, res.status);
|
|
893
|
+
} catch {
|
|
894
|
+
return graphError(`Failed to download attachment: HTTP ${res.status}`, undefined, res.status);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return graphResult(buf);
|
|
898
|
+
} catch (err) {
|
|
899
|
+
if (err instanceof GraphApiError) return graphError(err.message, err.code, err.status);
|
|
900
|
+
return graphError(err instanceof Error ? err.message : 'Failed to download attachment');
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function listListExtensionsPath(listId: string, user: string | undefined, extensionName?: string): string {
|
|
905
|
+
const base = `${todoRoot(user)}/lists/${encodeURIComponent(listId)}/extensions`;
|
|
906
|
+
return extensionName ? `${base}/${encodeURIComponent(extensionName)}` : base;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
export async function listTodoListOpenExtensions(
|
|
910
|
+
token: string,
|
|
911
|
+
listId: string,
|
|
912
|
+
user?: string
|
|
913
|
+
): Promise<GraphResponse<Array<Record<string, unknown>>>> {
|
|
914
|
+
try {
|
|
915
|
+
const result = await callGraph<{ value: Array<Record<string, unknown>> }>(
|
|
916
|
+
token,
|
|
917
|
+
listListExtensionsPath(listId, user)
|
|
918
|
+
);
|
|
919
|
+
if (!result.ok || !result.data) {
|
|
920
|
+
return graphError(
|
|
921
|
+
result.error?.message || 'Failed to list list extensions',
|
|
922
|
+
result.error?.code,
|
|
923
|
+
result.error?.status
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
return graphResult(result.data.value || []);
|
|
927
|
+
} catch (err) {
|
|
928
|
+
if (err instanceof GraphApiError) return graphError(err.message, err.code, err.status);
|
|
929
|
+
return graphError(err instanceof Error ? err.message : 'Failed to list list extensions');
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
export async function getTodoListOpenExtension(
|
|
934
|
+
token: string,
|
|
935
|
+
listId: string,
|
|
936
|
+
extensionName: string,
|
|
937
|
+
user?: string
|
|
938
|
+
): Promise<GraphResponse<Record<string, unknown>>> {
|
|
939
|
+
try {
|
|
940
|
+
const result = await callGraph<Record<string, unknown>>(token, listListExtensionsPath(listId, user, extensionName));
|
|
941
|
+
if (!result.ok || !result.data) {
|
|
942
|
+
return graphError(
|
|
943
|
+
result.error?.message || 'Failed to get list extension',
|
|
944
|
+
result.error?.code,
|
|
945
|
+
result.error?.status
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
return graphResult(result.data);
|
|
949
|
+
} catch (err) {
|
|
950
|
+
if (err instanceof GraphApiError) return graphError(err.message, err.code, err.status);
|
|
951
|
+
return graphError(err instanceof Error ? err.message : 'Failed to get list extension');
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
export async function setTodoListOpenExtension(
|
|
956
|
+
token: string,
|
|
957
|
+
listId: string,
|
|
958
|
+
extensionName: string,
|
|
959
|
+
extensionData: Record<string, unknown>,
|
|
960
|
+
user?: string
|
|
961
|
+
): Promise<GraphResponse<Record<string, unknown>>> {
|
|
962
|
+
const body = {
|
|
963
|
+
'@odata.type': 'microsoft.graph.openTypeExtension',
|
|
964
|
+
extensionName,
|
|
965
|
+
...extensionData
|
|
966
|
+
};
|
|
967
|
+
let result: GraphResponse<Record<string, unknown>>;
|
|
968
|
+
try {
|
|
969
|
+
result = await callGraph<Record<string, unknown>>(token, listListExtensionsPath(listId, user), {
|
|
970
|
+
method: 'POST',
|
|
971
|
+
body: JSON.stringify(body)
|
|
972
|
+
});
|
|
973
|
+
} catch (err) {
|
|
974
|
+
if (err instanceof GraphApiError) {
|
|
975
|
+
return graphError(err.message, err.code, err.status);
|
|
976
|
+
}
|
|
977
|
+
return graphError(err instanceof Error ? err.message : 'Failed to set list extension');
|
|
978
|
+
}
|
|
979
|
+
if (!result.ok || !result.data) {
|
|
980
|
+
return graphError(
|
|
981
|
+
result.error?.message || 'Failed to set list extension',
|
|
982
|
+
result.error?.code,
|
|
983
|
+
result.error?.status
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
return graphResult(result.data);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
export async function updateTodoListOpenExtension(
|
|
990
|
+
token: string,
|
|
991
|
+
listId: string,
|
|
992
|
+
extensionName: string,
|
|
993
|
+
patch: Record<string, unknown>,
|
|
994
|
+
user?: string
|
|
995
|
+
): Promise<GraphResponse<void>> {
|
|
996
|
+
try {
|
|
997
|
+
const result = await callGraph<void>(
|
|
998
|
+
token,
|
|
999
|
+
listListExtensionsPath(listId, user, extensionName),
|
|
1000
|
+
{
|
|
1001
|
+
method: 'PATCH',
|
|
1002
|
+
body: JSON.stringify(patch)
|
|
1003
|
+
},
|
|
1004
|
+
false
|
|
1005
|
+
);
|
|
1006
|
+
if (!result.ok) {
|
|
1007
|
+
return graphError(
|
|
1008
|
+
result.error?.message || 'Failed to update list extension',
|
|
1009
|
+
result.error?.code,
|
|
1010
|
+
result.error?.status
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
return graphResult(undefined as undefined);
|
|
1014
|
+
} catch (err) {
|
|
1015
|
+
if (err instanceof GraphApiError) {
|
|
1016
|
+
return graphError(err.message, err.code, err.status);
|
|
1017
|
+
}
|
|
1018
|
+
return graphError(err instanceof Error ? err.message : 'Failed to update list extension');
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
export async function deleteTodoListOpenExtension(
|
|
1023
|
+
token: string,
|
|
1024
|
+
listId: string,
|
|
1025
|
+
extensionName: string,
|
|
1026
|
+
user?: string
|
|
1027
|
+
): Promise<GraphResponse<void>> {
|
|
1028
|
+
try {
|
|
1029
|
+
return await callGraph<void>(
|
|
1030
|
+
token,
|
|
1031
|
+
listListExtensionsPath(listId, user, extensionName),
|
|
1032
|
+
{ method: 'DELETE' },
|
|
1033
|
+
false
|
|
1034
|
+
);
|
|
1035
|
+
} catch (err) {
|
|
1036
|
+
if (err instanceof GraphApiError) {
|
|
1037
|
+
return graphError(err.message, err.code, err.status);
|
|
1038
|
+
}
|
|
1039
|
+
return graphError(err instanceof Error ? err.message : 'Failed to delete list extension');
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
export interface TodoTaskDeltaPage {
|
|
1044
|
+
value?: TodoTask[];
|
|
1045
|
+
'@odata.nextLink'?: string;
|
|
1046
|
+
'@odata.deltaLink'?: string;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
export async function getTodoTasksDeltaPage(
|
|
1050
|
+
token: string,
|
|
1051
|
+
listId: string,
|
|
1052
|
+
fullUrl?: string,
|
|
1053
|
+
user?: string
|
|
1054
|
+
): Promise<GraphResponse<TodoTaskDeltaPage>> {
|
|
1055
|
+
try {
|
|
1056
|
+
if (fullUrl) {
|
|
1057
|
+
return await callGraphAbsolute<TodoTaskDeltaPage>(token, fullUrl);
|
|
1058
|
+
}
|
|
1059
|
+
return await callGraph<TodoTaskDeltaPage>(
|
|
1060
|
+
token,
|
|
1061
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/delta`
|
|
1062
|
+
);
|
|
1063
|
+
} catch (err) {
|
|
1064
|
+
if (err instanceof GraphApiError) return graphError(err.message, err.code, err.status);
|
|
1065
|
+
return graphError(err instanceof Error ? err.message : 'Failed to get todo delta');
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
export interface UploadSessionResult {
|
|
1070
|
+
uploadUrl: string;
|
|
1071
|
+
expirationDateTime?: string;
|
|
1072
|
+
nextExpectedRanges?: string[];
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
async function createTaskAttachmentUploadSession(
|
|
1076
|
+
token: string,
|
|
1077
|
+
listId: string,
|
|
1078
|
+
taskId: string,
|
|
1079
|
+
attachmentName: string,
|
|
1080
|
+
size: number,
|
|
1081
|
+
user?: string
|
|
1082
|
+
): Promise<GraphResponse<UploadSessionResult>> {
|
|
1083
|
+
try {
|
|
1084
|
+
const result = await callGraph<UploadSessionResult>(
|
|
1085
|
+
token,
|
|
1086
|
+
`${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}/attachments/createUploadSession`,
|
|
1087
|
+
{
|
|
1088
|
+
method: 'POST',
|
|
1089
|
+
body: JSON.stringify({
|
|
1090
|
+
attachmentInfo: {
|
|
1091
|
+
attachmentType: 'file',
|
|
1092
|
+
name: attachmentName,
|
|
1093
|
+
size
|
|
1094
|
+
}
|
|
1095
|
+
})
|
|
1096
|
+
}
|
|
1097
|
+
);
|
|
1098
|
+
if (!result.ok || !result.data) {
|
|
1099
|
+
return graphError(
|
|
1100
|
+
result.error?.message || 'Failed to create upload session',
|
|
1101
|
+
result.error?.code,
|
|
1102
|
+
result.error?.status
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
return graphResult(result.data);
|
|
1106
|
+
} catch (err) {
|
|
1107
|
+
if (err instanceof GraphApiError) return graphError(err.message, err.code, err.status);
|
|
1108
|
+
return graphError(err instanceof Error ? err.message : 'Failed to create upload session');
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Upload file bytes via session (no Bearer on PUT; Graph upload URL is pre-authorized).
|
|
1114
|
+
* Returns the final attachment object from the last chunk response when JSON.
|
|
1115
|
+
*/
|
|
1116
|
+
async function uploadFileViaTodoAttachmentSession(
|
|
1117
|
+
uploadUrl: string,
|
|
1118
|
+
filePath: string,
|
|
1119
|
+
chunkSize = 4 * 1024 * 1024
|
|
1120
|
+
): Promise<GraphResponse<TodoAttachment>> {
|
|
1121
|
+
const buf = await readFile(filePath);
|
|
1122
|
+
const total = buf.byteLength;
|
|
1123
|
+
let start = 0;
|
|
1124
|
+
let lastJson: TodoAttachment | undefined;
|
|
1125
|
+
while (start < total) {
|
|
1126
|
+
const end = Math.min(start + chunkSize, total);
|
|
1127
|
+
const slice = buf.subarray(start, end);
|
|
1128
|
+
const contentRange = `bytes ${start}-${end - 1}/${total}`;
|
|
1129
|
+
let response: Response;
|
|
1130
|
+
try {
|
|
1131
|
+
// codeql[js/file-access-to-http]: chunked upload of a user-selected attachment file to Graph (pre-authorized uploadUrl).
|
|
1132
|
+
response = await fetch(uploadUrl, {
|
|
1133
|
+
method: 'PUT',
|
|
1134
|
+
headers: {
|
|
1135
|
+
'Content-Length': String(slice.byteLength),
|
|
1136
|
+
'Content-Range': contentRange
|
|
1137
|
+
},
|
|
1138
|
+
body: slice
|
|
1139
|
+
});
|
|
1140
|
+
} catch (err) {
|
|
1141
|
+
return graphError(err instanceof Error ? err.message : 'Upload chunk failed');
|
|
1142
|
+
}
|
|
1143
|
+
const text = await response.text();
|
|
1144
|
+
if (!response.ok) {
|
|
1145
|
+
return graphError(text || `Upload failed: HTTP ${response.status}`, undefined, response.status);
|
|
1146
|
+
}
|
|
1147
|
+
if (text) {
|
|
1148
|
+
try {
|
|
1149
|
+
const parsed = JSON.parse(text) as TodoAttachment & { value?: unknown };
|
|
1150
|
+
if (parsed.id) lastJson = parsed;
|
|
1151
|
+
} catch {
|
|
1152
|
+
// non-JSON success body
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
start = end;
|
|
1156
|
+
}
|
|
1157
|
+
if (lastJson) return graphResult(lastJson);
|
|
1158
|
+
return graphError('Upload completed but attachment body was not returned', 'UPLOAD_PARSE', 500);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
export async function uploadLargeFileAttachment(
|
|
1162
|
+
token: string,
|
|
1163
|
+
listId: string,
|
|
1164
|
+
taskId: string,
|
|
1165
|
+
filePath: string,
|
|
1166
|
+
attachmentName?: string,
|
|
1167
|
+
user?: string
|
|
1168
|
+
): Promise<GraphResponse<TodoAttachment>> {
|
|
1169
|
+
const name = attachmentName?.trim() || basename(filePath);
|
|
1170
|
+
const st = await stat(filePath);
|
|
1171
|
+
if (!st.isFile()) return graphError(`Not a file: ${filePath}`, 'NOT_FILE', 400);
|
|
1172
|
+
const session = await createTaskAttachmentUploadSession(token, listId, taskId, name, st.size, user);
|
|
1173
|
+
if (!session.ok || !session.data?.uploadUrl) {
|
|
1174
|
+
return graphError(session.error?.message || 'No upload session', session.error?.code, session.error?.status);
|
|
1175
|
+
}
|
|
1176
|
+
return uploadFileViaTodoAttachmentSession(session.data.uploadUrl, filePath);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function extensionsPath(listId: string, taskId: string, user: string | undefined, extensionName?: string): string {
|
|
1180
|
+
const base = `${todoRoot(user)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}/extensions`;
|
|
1181
|
+
return extensionName ? `${base}/${encodeURIComponent(extensionName)}` : base;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
export async function listTaskOpenExtensions(
|
|
1185
|
+
token: string,
|
|
1186
|
+
listId: string,
|
|
1187
|
+
taskId: string,
|
|
1188
|
+
user?: string
|
|
1189
|
+
): Promise<GraphResponse<Array<Record<string, unknown>>>> {
|
|
1190
|
+
try {
|
|
1191
|
+
const result = await callGraph<{ value: Array<Record<string, unknown>> }>(
|
|
1192
|
+
token,
|
|
1193
|
+
extensionsPath(listId, taskId, user)
|
|
1194
|
+
);
|
|
1195
|
+
if (!result.ok || !result.data) {
|
|
1196
|
+
return graphError(result.error?.message || 'Failed to list extensions', result.error?.code, result.error?.status);
|
|
1197
|
+
}
|
|
1198
|
+
return graphResult(result.data.value || []);
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
if (err instanceof GraphApiError) return graphError(err.message, err.code, err.status);
|
|
1201
|
+
return graphError(err instanceof Error ? err.message : 'Failed to list extensions');
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
export async function getTaskOpenExtension(
|
|
1206
|
+
token: string,
|
|
1207
|
+
listId: string,
|
|
1208
|
+
taskId: string,
|
|
1209
|
+
extensionName: string,
|
|
1210
|
+
user?: string
|
|
1211
|
+
): Promise<GraphResponse<Record<string, unknown>>> {
|
|
1212
|
+
try {
|
|
1213
|
+
const result = await callGraph<Record<string, unknown>>(token, extensionsPath(listId, taskId, user, extensionName));
|
|
1214
|
+
if (!result.ok || !result.data) {
|
|
1215
|
+
return graphError(result.error?.message || 'Failed to get extension', result.error?.code, result.error?.status);
|
|
1216
|
+
}
|
|
1217
|
+
return graphResult(result.data);
|
|
1218
|
+
} catch (err) {
|
|
1219
|
+
if (err instanceof GraphApiError) return graphError(err.message, err.code, err.status);
|
|
1220
|
+
return graphError(err instanceof Error ? err.message : 'Failed to get extension');
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
export async function setTaskOpenExtension(
|
|
1225
|
+
token: string,
|
|
1226
|
+
listId: string,
|
|
1227
|
+
taskId: string,
|
|
1228
|
+
extensionName: string,
|
|
1229
|
+
extensionData: Record<string, unknown>,
|
|
1230
|
+
user?: string
|
|
1231
|
+
): Promise<GraphResponse<Record<string, unknown>>> {
|
|
1232
|
+
const body = {
|
|
1233
|
+
'@odata.type': 'microsoft.graph.openTypeExtension',
|
|
1234
|
+
extensionName,
|
|
1235
|
+
...extensionData
|
|
1236
|
+
};
|
|
1237
|
+
let result: GraphResponse<Record<string, unknown>>;
|
|
1238
|
+
try {
|
|
1239
|
+
result = await callGraph<Record<string, unknown>>(token, extensionsPath(listId, taskId, user), {
|
|
1240
|
+
method: 'POST',
|
|
1241
|
+
body: JSON.stringify(body)
|
|
1242
|
+
});
|
|
1243
|
+
} catch (err) {
|
|
1244
|
+
if (err instanceof GraphApiError) {
|
|
1245
|
+
return graphError(err.message, err.code, err.status);
|
|
1246
|
+
}
|
|
1247
|
+
return graphError(err instanceof Error ? err.message : 'Failed to set extension');
|
|
1248
|
+
}
|
|
1249
|
+
if (!result.ok || !result.data) {
|
|
1250
|
+
return graphError(result.error?.message || 'Failed to set extension', result.error?.code, result.error?.status);
|
|
1251
|
+
}
|
|
1252
|
+
return graphResult(result.data);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
export async function updateTaskOpenExtension(
|
|
1256
|
+
token: string,
|
|
1257
|
+
listId: string,
|
|
1258
|
+
taskId: string,
|
|
1259
|
+
extensionName: string,
|
|
1260
|
+
patch: Record<string, unknown>,
|
|
1261
|
+
user?: string
|
|
1262
|
+
): Promise<GraphResponse<void>> {
|
|
1263
|
+
try {
|
|
1264
|
+
const result = await callGraph<void>(
|
|
1265
|
+
token,
|
|
1266
|
+
extensionsPath(listId, taskId, user, extensionName),
|
|
1267
|
+
{
|
|
1268
|
+
method: 'PATCH',
|
|
1269
|
+
body: JSON.stringify(patch)
|
|
1270
|
+
},
|
|
1271
|
+
false
|
|
1272
|
+
);
|
|
1273
|
+
if (!result.ok) {
|
|
1274
|
+
return graphError(
|
|
1275
|
+
result.error?.message || 'Failed to update extension',
|
|
1276
|
+
result.error?.code,
|
|
1277
|
+
result.error?.status
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
return graphResult(undefined as undefined);
|
|
1281
|
+
} catch (err) {
|
|
1282
|
+
if (err instanceof GraphApiError) {
|
|
1283
|
+
return graphError(err.message, err.code, err.status);
|
|
1284
|
+
}
|
|
1285
|
+
return graphError(err instanceof Error ? err.message : 'Failed to update extension');
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
export async function deleteTaskOpenExtension(
|
|
1290
|
+
token: string,
|
|
1291
|
+
listId: string,
|
|
1292
|
+
taskId: string,
|
|
1293
|
+
extensionName: string,
|
|
1294
|
+
user?: string
|
|
1295
|
+
): Promise<GraphResponse<void>> {
|
|
1296
|
+
try {
|
|
1297
|
+
return await callGraph<void>(
|
|
1298
|
+
token,
|
|
1299
|
+
extensionsPath(listId, taskId, user, extensionName),
|
|
1300
|
+
{ method: 'DELETE' },
|
|
1301
|
+
false
|
|
1302
|
+
);
|
|
1303
|
+
} catch (err) {
|
|
1304
|
+
if (err instanceof GraphApiError) {
|
|
1305
|
+
return graphError(err.message, err.code, err.status);
|
|
1306
|
+
}
|
|
1307
|
+
return graphError(err instanceof Error ? err.message : 'Failed to delete extension');
|
|
1308
|
+
}
|
|
1309
|
+
}
|