@remit/backend 0.0.135 → 0.0.137
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
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
assertSettleable,
|
|
30
30
|
CalendarSuggestionActionOperations,
|
|
31
31
|
CalendarSuggestionOperations,
|
|
32
|
+
MessageCalendarSuggestionOperations,
|
|
32
33
|
type MuteSenderDeps,
|
|
33
34
|
muteSender,
|
|
34
35
|
settleSuggestion,
|
|
@@ -58,6 +59,7 @@ const suggestion = (
|
|
|
58
59
|
zoneCertainty: "Explicit",
|
|
59
60
|
icalData: "BEGIN:VCALENDAR\r\nEND:VCALENDAR\r\n",
|
|
60
61
|
acceptedCalendarObjectId: "",
|
|
62
|
+
supersededByMessageId: "",
|
|
61
63
|
createdAt: 1,
|
|
62
64
|
updatedAt: 1,
|
|
63
65
|
...overrides,
|
|
@@ -308,12 +310,38 @@ const acceptSuggestion =
|
|
|
308
310
|
CalendarSuggestionActionOperations.CalendarSuggestionActionOperations_acceptCalendarSuggestion as Handler;
|
|
309
311
|
const dismissSuggestion =
|
|
310
312
|
CalendarSuggestionActionOperations.CalendarSuggestionActionOperations_dismissCalendarSuggestion as Handler;
|
|
313
|
+
const listMessageSuggestions =
|
|
314
|
+
MessageCalendarSuggestionOperations.MessageCalendarSuggestionOperations_listMessageCalendarSuggestions as Handler;
|
|
311
315
|
|
|
312
316
|
interface Card {
|
|
313
317
|
suggestionId: string;
|
|
314
318
|
state: string;
|
|
319
|
+
supersededByMessageId: string;
|
|
320
|
+
supersededByThreadId: string;
|
|
315
321
|
}
|
|
316
322
|
|
|
323
|
+
const fileInThread = async (
|
|
324
|
+
accountConfigId: string,
|
|
325
|
+
messageId: string,
|
|
326
|
+
threadId: string,
|
|
327
|
+
): Promise<void> => {
|
|
328
|
+
await client.threadMessage.create({
|
|
329
|
+
accountConfigId,
|
|
330
|
+
threadId,
|
|
331
|
+
messageId,
|
|
332
|
+
mailboxId: "mbx-inbox",
|
|
333
|
+
uid: 1,
|
|
334
|
+
referenceOrder: 0,
|
|
335
|
+
internalDate: 1,
|
|
336
|
+
sentDate: 1,
|
|
337
|
+
subject: "Invitation: Quarterly review",
|
|
338
|
+
isRead: false,
|
|
339
|
+
isDeleted: false,
|
|
340
|
+
hasAttachment: false,
|
|
341
|
+
hasStars: false,
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
|
|
317
345
|
let client: RemitClient;
|
|
318
346
|
let cleanup: () => void;
|
|
319
347
|
let mintedSubs = 0;
|
|
@@ -473,6 +501,74 @@ describe("GET /calendar-suggestions", () => {
|
|
|
473
501
|
});
|
|
474
502
|
});
|
|
475
503
|
|
|
504
|
+
describe("GET /messages/{messageId}/calendar-suggestions", () => {
|
|
505
|
+
test("names the conversation of the revision that retired a card", async () => {
|
|
506
|
+
const { accountConfigId, event } = anAccount();
|
|
507
|
+
const older = await putSuggestion(accountConfigId, "msg-revision-0");
|
|
508
|
+
await client.calendarSuggestion.supersedeIfPending(
|
|
509
|
+
accountConfigId,
|
|
510
|
+
older.suggestionId,
|
|
511
|
+
"msg-revision-1",
|
|
512
|
+
);
|
|
513
|
+
await fileInThread(accountConfigId, "msg-revision-1", "thread-revision-1");
|
|
514
|
+
|
|
515
|
+
const listed = (await listMessageSuggestions(
|
|
516
|
+
contextOf({ params: { messageId: "msg-revision-0" } }),
|
|
517
|
+
event,
|
|
518
|
+
)) as unknown as { items: Card[] };
|
|
519
|
+
|
|
520
|
+
assert.deepEqual(
|
|
521
|
+
listed.items.map((card) => [
|
|
522
|
+
card.state,
|
|
523
|
+
card.supersededByMessageId,
|
|
524
|
+
card.supersededByThreadId,
|
|
525
|
+
]),
|
|
526
|
+
[
|
|
527
|
+
[
|
|
528
|
+
CalendarSuggestionState.Superseded,
|
|
529
|
+
"msg-revision-1",
|
|
530
|
+
"thread-revision-1",
|
|
531
|
+
],
|
|
532
|
+
],
|
|
533
|
+
);
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
test("names no conversation when the newer message sits in none", async () => {
|
|
537
|
+
const { accountConfigId, event } = anAccount();
|
|
538
|
+
const older = await putSuggestion(accountConfigId, "msg-orphan-0");
|
|
539
|
+
await client.calendarSuggestion.supersedeIfPending(
|
|
540
|
+
accountConfigId,
|
|
541
|
+
older.suggestionId,
|
|
542
|
+
"msg-orphan-1",
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
const listed = (await listMessageSuggestions(
|
|
546
|
+
contextOf({ params: { messageId: "msg-orphan-0" } }),
|
|
547
|
+
event,
|
|
548
|
+
)) as unknown as { items: Card[] };
|
|
549
|
+
|
|
550
|
+
assert.equal(listed.items[0]?.supersededByThreadId, "");
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
test("names no conversation for a card nothing retired", async () => {
|
|
554
|
+
const { accountConfigId, event } = anAccount();
|
|
555
|
+
await putSuggestion(accountConfigId, "msg-current");
|
|
556
|
+
|
|
557
|
+
const listed = (await listMessageSuggestions(
|
|
558
|
+
contextOf({ params: { messageId: "msg-current" } }),
|
|
559
|
+
event,
|
|
560
|
+
)) as unknown as { items: Card[] };
|
|
561
|
+
|
|
562
|
+
assert.deepEqual(
|
|
563
|
+
listed.items.map((card) => [
|
|
564
|
+
card.supersededByMessageId,
|
|
565
|
+
card.supersededByThreadId,
|
|
566
|
+
]),
|
|
567
|
+
[["", ""]],
|
|
568
|
+
);
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
|
|
476
572
|
describe("POST /calendar-suggestions/{suggestionId}/accept", () => {
|
|
477
573
|
test("answers not-found for a calendar on another account, before writing anything", async () => {
|
|
478
574
|
const stranger = anAccount();
|
|
@@ -597,3 +693,74 @@ describe("POST /calendar-suggestions/{suggestionId}/dismiss", () => {
|
|
|
597
693
|
);
|
|
598
694
|
});
|
|
599
695
|
});
|
|
696
|
+
|
|
697
|
+
describe("POST /calendar-suggestions/{suggestionId}/accept with corrections", () => {
|
|
698
|
+
test("answers not-found for a calendar on another account, before writing anything", async () => {
|
|
699
|
+
const stranger = anAccount();
|
|
700
|
+
const strangersCalendar = await client.calendarCollection.create({
|
|
701
|
+
accountConfigId: stranger.accountConfigId,
|
|
702
|
+
urlSegment: "default",
|
|
703
|
+
displayName: "Calendar",
|
|
704
|
+
});
|
|
705
|
+
const { accountConfigId, event } = anAccount();
|
|
706
|
+
const card = await putSuggestion(accountConfigId, "msg-edited-cross");
|
|
707
|
+
|
|
708
|
+
await assert.rejects(
|
|
709
|
+
() =>
|
|
710
|
+
acceptSuggestion(
|
|
711
|
+
contextOf({
|
|
712
|
+
params: { suggestionId: card.suggestionId },
|
|
713
|
+
requestBody: {
|
|
714
|
+
calendarId: strangersCalendar.calendarId,
|
|
715
|
+
event: { summary: "Quarterly review, moved" },
|
|
716
|
+
},
|
|
717
|
+
}),
|
|
718
|
+
event,
|
|
719
|
+
),
|
|
720
|
+
(error: unknown) => (error as { statusCode?: number }).statusCode === 404,
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
assert.deepEqual(
|
|
724
|
+
await client.calendarObject.listByCalendar(strangersCalendar.calendarId),
|
|
725
|
+
[],
|
|
726
|
+
);
|
|
727
|
+
const untouched = await client.calendarSuggestion.get(
|
|
728
|
+
accountConfigId,
|
|
729
|
+
card.suggestionId,
|
|
730
|
+
);
|
|
731
|
+
assert.equal(untouched.state, CalendarSuggestionState.Pending);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
test("refuses a card the reader already declined", async () => {
|
|
735
|
+
const { accountConfigId, event } = anAccount();
|
|
736
|
+
const calendar = await client.calendarCollection.create({
|
|
737
|
+
accountConfigId,
|
|
738
|
+
urlSegment: "default",
|
|
739
|
+
displayName: "Calendar",
|
|
740
|
+
});
|
|
741
|
+
const card = await putSuggestion(accountConfigId, "msg-edited-declined");
|
|
742
|
+
await client.calendarSuggestion.settle(accountConfigId, card.suggestionId, {
|
|
743
|
+
state: CalendarSuggestionState.Declined,
|
|
744
|
+
acceptedCalendarObjectId: "",
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
await assert.rejects(
|
|
748
|
+
() =>
|
|
749
|
+
acceptSuggestion(
|
|
750
|
+
contextOf({
|
|
751
|
+
params: { suggestionId: card.suggestionId },
|
|
752
|
+
requestBody: {
|
|
753
|
+
calendarId: calendar.calendarId,
|
|
754
|
+
event: { summary: "Quarterly review, moved" },
|
|
755
|
+
},
|
|
756
|
+
}),
|
|
757
|
+
event,
|
|
758
|
+
),
|
|
759
|
+
(error: unknown) => (error as { statusCode?: number }).statusCode === 400,
|
|
760
|
+
);
|
|
761
|
+
assert.deepEqual(
|
|
762
|
+
await client.calendarObject.listByCalendar(calendar.calendarId),
|
|
763
|
+
[],
|
|
764
|
+
);
|
|
765
|
+
});
|
|
766
|
+
});
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
CalendarInviteAnswer as CalendarInviteAnswerValue,
|
|
3
3
|
CalendarSuggestionResponse,
|
|
4
|
+
UpdateCalendarEventInput,
|
|
4
5
|
} from "@remit/api-openapi-types";
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
acceptCalendarSuggestion,
|
|
8
|
+
correctCalendarSuggestion,
|
|
9
|
+
} from "@remit/calendar-service";
|
|
6
10
|
import {
|
|
7
11
|
type CalendarSuggestionItem,
|
|
8
12
|
type ICalendarSuggestionRepository,
|
|
9
13
|
type IEnvelopeRepository,
|
|
10
14
|
type IFilterRepository,
|
|
15
|
+
type IThreadMessageRepository,
|
|
11
16
|
isSenderMuted,
|
|
12
17
|
} from "@remit/data-ports";
|
|
13
18
|
import { BadRequestError } from "@remit/data-ports/errors";
|
|
@@ -30,6 +35,7 @@ import type {
|
|
|
30
35
|
MessageCalendarSuggestionOperationIds,
|
|
31
36
|
OperationHandler,
|
|
32
37
|
} from "../types.js";
|
|
38
|
+
import { pickEventUpdate } from "./calendar-event.js";
|
|
33
39
|
|
|
34
40
|
/**
|
|
35
41
|
* The raw invitation bytes stay on the server. A client renders the projected
|
|
@@ -40,9 +46,26 @@ export const toCalendarSuggestionResponse = (
|
|
|
40
46
|
item: CalendarSuggestionItem,
|
|
41
47
|
): CalendarSuggestionResponse => {
|
|
42
48
|
const { icalData: _icalData, ...response } = item;
|
|
43
|
-
return response;
|
|
49
|
+
return { ...response, supersededByThreadId: "" };
|
|
44
50
|
};
|
|
45
51
|
|
|
52
|
+
export const toCalendarSuggestionResponses = (
|
|
53
|
+
threads: Pick<IThreadMessageRepository, "findByMessageId">,
|
|
54
|
+
accountConfigId: string,
|
|
55
|
+
items: CalendarSuggestionItem[],
|
|
56
|
+
): Promise<CalendarSuggestionResponse[]> =>
|
|
57
|
+
Promise.all(
|
|
58
|
+
items.map(async (item) => {
|
|
59
|
+
const response = toCalendarSuggestionResponse(item);
|
|
60
|
+
if (item.supersededByMessageId === "") return response;
|
|
61
|
+
const newer = await threads.findByMessageId(
|
|
62
|
+
accountConfigId,
|
|
63
|
+
item.supersededByMessageId,
|
|
64
|
+
);
|
|
65
|
+
return { ...response, supersededByThreadId: newer?.threadId ?? "" };
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
|
|
46
69
|
/**
|
|
47
70
|
* The states a person can move a pending card into. `Superseded` is the
|
|
48
71
|
* producer's alone — a revision retires a card, a person never does — and the
|
|
@@ -172,7 +195,11 @@ export const CalendarSuggestionOperations: Record<
|
|
|
172
195
|
);
|
|
173
196
|
|
|
174
197
|
return {
|
|
175
|
-
items:
|
|
198
|
+
items: await toCalendarSuggestionResponses(
|
|
199
|
+
client.threadMessage,
|
|
200
|
+
accountConfigId,
|
|
201
|
+
page.items,
|
|
202
|
+
),
|
|
176
203
|
continuationToken: page.continuationToken,
|
|
177
204
|
};
|
|
178
205
|
},
|
|
@@ -197,7 +224,11 @@ export const MessageCalendarSuggestionOperations: Record<
|
|
|
197
224
|
);
|
|
198
225
|
|
|
199
226
|
return {
|
|
200
|
-
items:
|
|
227
|
+
items: await toCalendarSuggestionResponses(
|
|
228
|
+
client.threadMessage,
|
|
229
|
+
accountConfigId,
|
|
230
|
+
items,
|
|
231
|
+
),
|
|
201
232
|
continuationToken: undefined,
|
|
202
233
|
};
|
|
203
234
|
},
|
|
@@ -255,10 +286,25 @@ export const CalendarSuggestionActionOperations: Record<
|
|
|
255
286
|
);
|
|
256
287
|
}
|
|
257
288
|
|
|
289
|
+
const { event: corrections } = context.request.requestBody as {
|
|
290
|
+
event?: UpdateCalendarEventInput;
|
|
291
|
+
};
|
|
292
|
+
const corrected =
|
|
293
|
+
corrections === undefined
|
|
294
|
+
? { ok: true as const, value: suggestion }
|
|
295
|
+
: await correctCalendarSuggestion(
|
|
296
|
+
suggestion,
|
|
297
|
+
pickEventUpdate(corrections),
|
|
298
|
+
collection.timezone,
|
|
299
|
+
);
|
|
300
|
+
if (!corrected.ok) {
|
|
301
|
+
throw new BadRequestError(corrected.error.message);
|
|
302
|
+
}
|
|
303
|
+
|
|
258
304
|
const accepted = await acceptCalendarSuggestion(client.calendarUnitOfWork, {
|
|
259
305
|
accountConfigId,
|
|
260
306
|
calendarId,
|
|
261
|
-
suggestion,
|
|
307
|
+
suggestion: corrected.value,
|
|
262
308
|
attendee: await accountEmailForMessage(client, suggestion.messageId),
|
|
263
309
|
answer,
|
|
264
310
|
});
|