@remit/calendar-service 0.0.12 → 0.0.13
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/package.json +2 -1
- package/src/accept.test.ts +44 -0
- package/src/accept.ts +27 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/calendar-service",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "iCalendar (RFC 5545) parsing, projection and recurrence expansion over the calendar store",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"test:run": "node $NODE_TEST_FLAGS --import tsx --experimental-test-coverage --test-coverage-include='src/**' --test-coverage-exclude='src/**/*.test.ts' --test-coverage-exclude='src/memory-store.ts' --test-coverage-lines=90 --test 'src/**/*.test.ts'"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"@remit/api-openapi-types": "*",
|
|
23
24
|
"@remit/data-ports": "*",
|
|
24
25
|
"@remit/domain-enums": "*",
|
|
25
26
|
"ical.js": "^2.2.1"
|
package/src/accept.test.ts
CHANGED
|
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import { describe, it } from "node:test";
|
|
3
3
|
import type { CalendarSuggestionItem } from "@remit/data-ports";
|
|
4
4
|
import {
|
|
5
|
+
CalendarInviteAnswer,
|
|
5
6
|
CalendarSuggestionSource,
|
|
6
7
|
CalendarSuggestionState,
|
|
7
8
|
} from "@remit/domain-enums";
|
|
@@ -99,6 +100,7 @@ describe("buildAcceptedCalendar", () => {
|
|
|
99
100
|
const built = await buildAcceptedCalendar(
|
|
100
101
|
suggestionOf(invitation({ sequence: 3 })),
|
|
101
102
|
ATTENDEE,
|
|
103
|
+
CalendarInviteAnswer.Accepted,
|
|
102
104
|
);
|
|
103
105
|
|
|
104
106
|
assert.ok(built.ok);
|
|
@@ -110,6 +112,7 @@ describe("buildAcceptedCalendar", () => {
|
|
|
110
112
|
const built = await buildAcceptedCalendar(
|
|
111
113
|
suggestionOf(invitation({})),
|
|
112
114
|
ATTENDEE,
|
|
115
|
+
CalendarInviteAnswer.Accepted,
|
|
113
116
|
);
|
|
114
117
|
|
|
115
118
|
assert.ok(built.ok);
|
|
@@ -122,6 +125,7 @@ describe("buildAcceptedCalendar", () => {
|
|
|
122
125
|
const built = await buildAcceptedCalendar(
|
|
123
126
|
suggestionOf(invitation({ attendees: [] })),
|
|
124
127
|
ATTENDEE,
|
|
128
|
+
CalendarInviteAnswer.Accepted,
|
|
125
129
|
);
|
|
126
130
|
|
|
127
131
|
assert.ok(built.ok);
|
|
@@ -137,6 +141,7 @@ describe("buildAcceptedCalendar", () => {
|
|
|
137
141
|
const built = await buildAcceptedCalendar(
|
|
138
142
|
suggestionOf(invitation({})),
|
|
139
143
|
ATTENDEE,
|
|
144
|
+
CalendarInviteAnswer.Accepted,
|
|
140
145
|
);
|
|
141
146
|
|
|
142
147
|
assert.ok(built.ok);
|
|
@@ -147,6 +152,7 @@ describe("buildAcceptedCalendar", () => {
|
|
|
147
152
|
const built = await buildAcceptedCalendar(
|
|
148
153
|
suggestionOf(invitation({})),
|
|
149
154
|
ATTENDEE,
|
|
155
|
+
CalendarInviteAnswer.Accepted,
|
|
150
156
|
);
|
|
151
157
|
|
|
152
158
|
assert.ok(built.ok);
|
|
@@ -157,6 +163,7 @@ describe("buildAcceptedCalendar", () => {
|
|
|
157
163
|
const built = await buildAcceptedCalendar(
|
|
158
164
|
suggestionOf(invitation({ method: "CANCEL" }), { method: "Cancel" }),
|
|
159
165
|
ATTENDEE,
|
|
166
|
+
CalendarInviteAnswer.Accepted,
|
|
160
167
|
);
|
|
161
168
|
|
|
162
169
|
assert.ok(built.ok);
|
|
@@ -167,6 +174,7 @@ describe("buildAcceptedCalendar", () => {
|
|
|
167
174
|
const built = await buildAcceptedCalendar(
|
|
168
175
|
suggestionOf("", { source: CalendarSuggestionSource.TextHeuristic }),
|
|
169
176
|
ATTENDEE,
|
|
177
|
+
CalendarInviteAnswer.Accepted,
|
|
170
178
|
);
|
|
171
179
|
|
|
172
180
|
assert.equal(built.ok, false);
|
|
@@ -183,6 +191,7 @@ describe("acceptCalendarSuggestion", () => {
|
|
|
183
191
|
calendarId,
|
|
184
192
|
suggestion,
|
|
185
193
|
attendee: ATTENDEE,
|
|
194
|
+
answer: CalendarInviteAnswer.Accepted,
|
|
186
195
|
});
|
|
187
196
|
|
|
188
197
|
assert.ok(accepted.ok);
|
|
@@ -198,6 +207,32 @@ describe("acceptCalendarSuggestion", () => {
|
|
|
198
207
|
assert.equal(store.objects.size, 1);
|
|
199
208
|
});
|
|
200
209
|
|
|
210
|
+
it("a maybe writes the resource as tentative and settles the card Tentative", async () => {
|
|
211
|
+
const { store, calendarId } = await provisioned();
|
|
212
|
+
const suggestion = await recorded(store, invitation({}), "message-1");
|
|
213
|
+
|
|
214
|
+
const answered = await acceptCalendarSuggestion(store, {
|
|
215
|
+
accountConfigId: ACCOUNT_CONFIG_ID,
|
|
216
|
+
calendarId,
|
|
217
|
+
suggestion,
|
|
218
|
+
attendee: ATTENDEE,
|
|
219
|
+
answer: CalendarInviteAnswer.Tentative,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
assert.ok(answered.ok);
|
|
223
|
+
assert.equal(
|
|
224
|
+
answered.value.suggestion.state,
|
|
225
|
+
CalendarSuggestionState.Tentative,
|
|
226
|
+
);
|
|
227
|
+
assert.equal(
|
|
228
|
+
answered.value.suggestion.acceptedCalendarObjectId,
|
|
229
|
+
answered.value.object?.calendarObjectId,
|
|
230
|
+
);
|
|
231
|
+
assert.match(answered.value.object?.icalData ?? "", /PARTSTAT=TENTATIVE/);
|
|
232
|
+
assert.doesNotMatch(answered.value.object?.icalData ?? "", /ACCEPTED/);
|
|
233
|
+
assert.equal(store.objects.size, 1);
|
|
234
|
+
});
|
|
235
|
+
|
|
201
236
|
it("expands the accepted event into the occurrence index", async () => {
|
|
202
237
|
const { store, calendarId } = await provisioned();
|
|
203
238
|
const suggestion = await recorded(store, invitation({}), "message-1");
|
|
@@ -207,6 +242,7 @@ describe("acceptCalendarSuggestion", () => {
|
|
|
207
242
|
calendarId,
|
|
208
243
|
suggestion,
|
|
209
244
|
attendee: ATTENDEE,
|
|
245
|
+
answer: CalendarInviteAnswer.Accepted,
|
|
210
246
|
});
|
|
211
247
|
|
|
212
248
|
assert.ok(accepted.ok);
|
|
@@ -226,12 +262,14 @@ describe("acceptCalendarSuggestion", () => {
|
|
|
226
262
|
calendarId,
|
|
227
263
|
suggestion,
|
|
228
264
|
attendee: ATTENDEE,
|
|
265
|
+
answer: CalendarInviteAnswer.Accepted,
|
|
229
266
|
});
|
|
230
267
|
const second = await acceptCalendarSuggestion(store, {
|
|
231
268
|
accountConfigId: ACCOUNT_CONFIG_ID,
|
|
232
269
|
calendarId,
|
|
233
270
|
suggestion,
|
|
234
271
|
attendee: ATTENDEE,
|
|
272
|
+
answer: CalendarInviteAnswer.Accepted,
|
|
235
273
|
});
|
|
236
274
|
|
|
237
275
|
assert.ok(first.ok);
|
|
@@ -255,6 +293,7 @@ describe("acceptCalendarSuggestion", () => {
|
|
|
255
293
|
calendarId,
|
|
256
294
|
suggestion: first,
|
|
257
295
|
attendee: ATTENDEE,
|
|
296
|
+
answer: CalendarInviteAnswer.Accepted,
|
|
258
297
|
});
|
|
259
298
|
|
|
260
299
|
const revision = await recorded(
|
|
@@ -267,6 +306,7 @@ describe("acceptCalendarSuggestion", () => {
|
|
|
267
306
|
calendarId,
|
|
268
307
|
suggestion: revision,
|
|
269
308
|
attendee: ATTENDEE,
|
|
309
|
+
answer: CalendarInviteAnswer.Accepted,
|
|
270
310
|
});
|
|
271
311
|
|
|
272
312
|
assert.ok(accepted.ok);
|
|
@@ -292,6 +332,7 @@ describe("acceptCalendarSuggestion", () => {
|
|
|
292
332
|
calendarId,
|
|
293
333
|
suggestion: cancel,
|
|
294
334
|
attendee: ATTENDEE,
|
|
335
|
+
answer: CalendarInviteAnswer.Accepted,
|
|
295
336
|
});
|
|
296
337
|
|
|
297
338
|
assert.ok(accepted.ok);
|
|
@@ -319,6 +360,7 @@ describe("acceptCalendarSuggestion", () => {
|
|
|
319
360
|
calendarId,
|
|
320
361
|
suggestion: request,
|
|
321
362
|
attendee: ATTENDEE,
|
|
363
|
+
answer: CalendarInviteAnswer.Accepted,
|
|
322
364
|
});
|
|
323
365
|
|
|
324
366
|
const cancel = await recorded(
|
|
@@ -334,6 +376,7 @@ describe("acceptCalendarSuggestion", () => {
|
|
|
334
376
|
calendarId,
|
|
335
377
|
suggestion: cancel,
|
|
336
378
|
attendee: ATTENDEE,
|
|
379
|
+
answer: CalendarInviteAnswer.Accepted,
|
|
337
380
|
});
|
|
338
381
|
|
|
339
382
|
assert.ok(accepted.ok);
|
|
@@ -349,6 +392,7 @@ describe("acceptCalendarSuggestion", () => {
|
|
|
349
392
|
calendarId,
|
|
350
393
|
suggestion: suggestionOf("BEGIN:VCALENDAR"),
|
|
351
394
|
attendee: ATTENDEE,
|
|
395
|
+
answer: CalendarInviteAnswer.Accepted,
|
|
352
396
|
});
|
|
353
397
|
|
|
354
398
|
assert.equal(accepted.ok, false);
|
package/src/accept.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { CalendarInviteAnswer as CalendarInviteAnswerValue } from "@remit/api-openapi-types";
|
|
1
2
|
import type {
|
|
2
3
|
CalendarObjectItem,
|
|
3
4
|
CalendarSuggestionItem,
|
|
4
5
|
ICalendarUnitOfWork,
|
|
5
6
|
} from "@remit/data-ports";
|
|
6
7
|
import {
|
|
8
|
+
CalendarInviteAnswer,
|
|
7
9
|
CalendarInviteMethod,
|
|
8
10
|
CalendarSuggestionState,
|
|
9
11
|
} from "@remit/domain-enums";
|
|
@@ -16,15 +18,32 @@ import { mailAddressOf } from "./suggest.js";
|
|
|
16
18
|
const eventsOf = (component: ICAL.Component): ICAL.Component[] =>
|
|
17
19
|
component.getAllSubcomponents("vevent");
|
|
18
20
|
|
|
21
|
+
const partstatOf: Record<CalendarInviteAnswerValue, string> = {
|
|
22
|
+
[CalendarInviteAnswer.Accepted]: "ACCEPTED",
|
|
23
|
+
[CalendarInviteAnswer.Tentative]: "TENTATIVE",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const stateOf: Record<
|
|
27
|
+
CalendarInviteAnswerValue,
|
|
28
|
+
CalendarSuggestionItem["state"]
|
|
29
|
+
> = {
|
|
30
|
+
[CalendarInviteAnswer.Accepted]: CalendarSuggestionState.Accepted,
|
|
31
|
+
[CalendarInviteAnswer.Tentative]: CalendarSuggestionState.Tentative,
|
|
32
|
+
};
|
|
33
|
+
|
|
19
34
|
/**
|
|
20
|
-
* Marks one address
|
|
35
|
+
* Marks one address with its answer, on every VEVENT of the resource — the
|
|
21
36
|
* master and each override alike, since an attendee's answer is to the series.
|
|
22
37
|
*
|
|
23
38
|
* An ATTENDEE line already naming the user has its PARTSTAT rewritten rather
|
|
24
39
|
* than a second one appended: two ATTENDEE lines for one person is a resource
|
|
25
40
|
* every other client reads as two people.
|
|
26
41
|
*/
|
|
27
|
-
const
|
|
42
|
+
const markAnswered = (
|
|
43
|
+
component: ICAL.Component,
|
|
44
|
+
attendee: string,
|
|
45
|
+
answer: CalendarInviteAnswerValue,
|
|
46
|
+
): void => {
|
|
28
47
|
const wanted = attendee.toLowerCase();
|
|
29
48
|
for (const event of eventsOf(component)) {
|
|
30
49
|
const existing = event
|
|
@@ -36,7 +55,7 @@ const markAccepted = (component: ICAL.Component, attendee: string): void => {
|
|
|
36
55
|
);
|
|
37
56
|
const property =
|
|
38
57
|
existing ?? new ICAL.Property("attendee", event as ICAL.Component);
|
|
39
|
-
property.setParameter("partstat",
|
|
58
|
+
property.setParameter("partstat", partstatOf[answer]);
|
|
40
59
|
if (!existing) {
|
|
41
60
|
property.setValue(`mailto:${attendee}`);
|
|
42
61
|
event.addProperty(property);
|
|
@@ -69,13 +88,14 @@ const markCancelled = (component: ICAL.Component): void => {
|
|
|
69
88
|
export const buildAcceptedCalendar = async (
|
|
70
89
|
suggestion: Pick<CalendarSuggestionItem, "icalData" | "method">,
|
|
71
90
|
attendee: string,
|
|
91
|
+
answer: CalendarInviteAnswerValue,
|
|
72
92
|
): Promise<CalendarResult<string>> => {
|
|
73
93
|
const parsed = await parseCalendar(suggestion.icalData);
|
|
74
94
|
if (!parsed.ok) return parsed;
|
|
75
95
|
|
|
76
96
|
const { component } = parsed.value;
|
|
77
97
|
component.removeAllProperties("method");
|
|
78
|
-
|
|
98
|
+
markAnswered(component, attendee, answer);
|
|
79
99
|
if (suggestion.method === CalendarInviteMethod.Cancel) {
|
|
80
100
|
markCancelled(component);
|
|
81
101
|
}
|
|
@@ -89,6 +109,7 @@ export interface AcceptCalendarSuggestionInput {
|
|
|
89
109
|
suggestion: CalendarSuggestionItem;
|
|
90
110
|
/** Mail address of the person accepting — the account the message arrived on. */
|
|
91
111
|
attendee: string;
|
|
112
|
+
answer: CalendarInviteAnswerValue;
|
|
92
113
|
}
|
|
93
114
|
|
|
94
115
|
/**
|
|
@@ -142,6 +163,7 @@ export const acceptCalendarSuggestion = async (
|
|
|
142
163
|
const icalData = await buildAcceptedCalendar(
|
|
143
164
|
input.suggestion,
|
|
144
165
|
input.attendee,
|
|
166
|
+
input.answer,
|
|
145
167
|
);
|
|
146
168
|
if (!icalData.ok) return icalData;
|
|
147
169
|
|
|
@@ -190,7 +212,7 @@ export const acceptCalendarSuggestion = async (
|
|
|
190
212
|
input.accountConfigId,
|
|
191
213
|
input.suggestion.suggestionId,
|
|
192
214
|
{
|
|
193
|
-
state:
|
|
215
|
+
state: stateOf[input.answer],
|
|
194
216
|
acceptedCalendarObjectId: written.value.calendarObjectId,
|
|
195
217
|
},
|
|
196
218
|
);
|