@remit/backend 0.0.137 → 0.0.138
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
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
type CalendarInstance,
|
|
14
14
|
type CalendarWindow,
|
|
15
15
|
deleteCalendarObject,
|
|
16
|
+
followingUidOf,
|
|
16
17
|
listBusySpans,
|
|
17
18
|
listCalendarInstances,
|
|
18
19
|
listObjectInstances,
|
|
@@ -445,7 +446,10 @@ export const updateCalendarEventFor = async (
|
|
|
445
446
|
{
|
|
446
447
|
scope: request.scope,
|
|
447
448
|
recurrenceId: request.recurrenceId,
|
|
448
|
-
followingUid:
|
|
449
|
+
followingUid: followingUidOf(
|
|
450
|
+
object.icalUid,
|
|
451
|
+
`${deps.newId()}@reader.remit`,
|
|
452
|
+
),
|
|
449
453
|
},
|
|
450
454
|
patch,
|
|
451
455
|
);
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { after, before, describe, test } from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
acceptCalendarSuggestion,
|
|
5
|
+
reopenCalendarSuggestion,
|
|
6
|
+
} from "@remit/calendar-service";
|
|
3
7
|
import type {
|
|
4
8
|
CalendarSuggestionItem,
|
|
5
9
|
CreateFilterInput,
|
|
6
10
|
FilterItem,
|
|
7
11
|
ICalendarSuggestionRepository,
|
|
12
|
+
ICalendarUnitOfWork,
|
|
8
13
|
MessageData,
|
|
9
14
|
PutCalendarSuggestionInput,
|
|
10
15
|
SettleCalendarSuggestionInput,
|
|
11
16
|
} from "@remit/data-ports";
|
|
12
17
|
import {
|
|
18
|
+
CalendarAnswerOvertaken,
|
|
13
19
|
CalendarInviteAnswer,
|
|
14
20
|
CalendarInviteMethod,
|
|
15
21
|
CalendarSuggestionSource,
|
|
16
22
|
CalendarSuggestionState,
|
|
17
23
|
FilterState,
|
|
24
|
+
RecurrenceScope,
|
|
18
25
|
} from "@remit/domain-enums";
|
|
19
26
|
import type { APIGatewayProxyEvent } from "aws-lambda";
|
|
20
27
|
import type { Context } from "openapi-backend";
|
|
@@ -24,6 +31,11 @@ import {
|
|
|
24
31
|
type RemitClient,
|
|
25
32
|
setClient,
|
|
26
33
|
} from "../service/data-client.js";
|
|
34
|
+
import { calendarDepsOf } from "./calendar.js";
|
|
35
|
+
import {
|
|
36
|
+
calendarEventDepsOf,
|
|
37
|
+
updateCalendarEventFor,
|
|
38
|
+
} from "./calendar-event.js";
|
|
27
39
|
import { createCalendarSqliteClient } from "./calendar-sqlite-fixture.js";
|
|
28
40
|
import {
|
|
29
41
|
assertSettleable,
|
|
@@ -135,7 +147,10 @@ const muteRule = (sender: string): FilterItem =>
|
|
|
135
147
|
|
|
136
148
|
describe("toCalendarSuggestionResponse", () => {
|
|
137
149
|
test("keeps the raw invitation bytes on the server", async () => {
|
|
138
|
-
const response = toCalendarSuggestionResponse(
|
|
150
|
+
const response = toCalendarSuggestionResponse(
|
|
151
|
+
suggestion(),
|
|
152
|
+
CalendarAnswerOvertaken.None,
|
|
153
|
+
);
|
|
139
154
|
|
|
140
155
|
assert.equal("icalData" in response, false);
|
|
141
156
|
assert.equal(response.summary, "Quarterly review");
|
|
@@ -312,6 +327,8 @@ const dismissSuggestion =
|
|
|
312
327
|
CalendarSuggestionActionOperations.CalendarSuggestionActionOperations_dismissCalendarSuggestion as Handler;
|
|
313
328
|
const listMessageSuggestions =
|
|
314
329
|
MessageCalendarSuggestionOperations.MessageCalendarSuggestionOperations_listMessageCalendarSuggestions as Handler;
|
|
330
|
+
const reopenSuggestion =
|
|
331
|
+
CalendarSuggestionActionOperations.CalendarSuggestionActionOperations_reopenCalendarSuggestion as Handler;
|
|
315
332
|
|
|
316
333
|
interface Card {
|
|
317
334
|
suggestionId: string;
|
|
@@ -764,3 +781,382 @@ describe("POST /calendar-suggestions/{suggestionId}/accept with corrections", ()
|
|
|
764
781
|
);
|
|
765
782
|
});
|
|
766
783
|
});
|
|
784
|
+
|
|
785
|
+
describe("POST /calendar-suggestions/{suggestionId}/reopen", () => {
|
|
786
|
+
const answeredInto = async (
|
|
787
|
+
accountConfigId: string,
|
|
788
|
+
messageId: string,
|
|
789
|
+
answer: (typeof CalendarInviteAnswer)[keyof typeof CalendarInviteAnswer],
|
|
790
|
+
): Promise<{ calendarId: string; card: CalendarSuggestionItem }> => {
|
|
791
|
+
const calendar = await client.calendarCollection.create({
|
|
792
|
+
accountConfigId,
|
|
793
|
+
urlSegment: "default",
|
|
794
|
+
displayName: "Calendar",
|
|
795
|
+
});
|
|
796
|
+
const accepted = await acceptCalendarSuggestion(client.calendarUnitOfWork, {
|
|
797
|
+
accountConfigId,
|
|
798
|
+
calendarId: calendar.calendarId,
|
|
799
|
+
suggestion: await putSuggestion(accountConfigId, messageId),
|
|
800
|
+
attendee: "user@example.test",
|
|
801
|
+
answer,
|
|
802
|
+
});
|
|
803
|
+
assert.ok(accepted.ok);
|
|
804
|
+
return { calendarId: calendar.calendarId, card: accepted.value.suggestion };
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
for (const answer of [
|
|
808
|
+
CalendarInviteAnswer.Accepted,
|
|
809
|
+
CalendarInviteAnswer.Tentative,
|
|
810
|
+
]) {
|
|
811
|
+
test(`takes a ${answer} answer back and removes the event it wrote`, async () => {
|
|
812
|
+
const { accountConfigId, event } = anAccount();
|
|
813
|
+
const { calendarId, card } = await answeredInto(
|
|
814
|
+
accountConfigId,
|
|
815
|
+
`msg-reopen-${answer}`,
|
|
816
|
+
answer,
|
|
817
|
+
);
|
|
818
|
+
assert.equal(
|
|
819
|
+
(await client.calendarObject.listByCalendar(calendarId)).length,
|
|
820
|
+
1,
|
|
821
|
+
);
|
|
822
|
+
|
|
823
|
+
const reopened = (await reopenSuggestion(
|
|
824
|
+
contextOf({ params: { suggestionId: card.suggestionId } }),
|
|
825
|
+
event,
|
|
826
|
+
)) as unknown as CalendarSuggestionItem;
|
|
827
|
+
|
|
828
|
+
assert.equal(reopened.state, CalendarSuggestionState.Pending);
|
|
829
|
+
assert.equal(reopened.acceptedCalendarObjectId, "");
|
|
830
|
+
assert.deepEqual(
|
|
831
|
+
await client.calendarObject.listByCalendar(calendarId),
|
|
832
|
+
[],
|
|
833
|
+
);
|
|
834
|
+
const stored = await client.calendarSuggestion.get(
|
|
835
|
+
accountConfigId,
|
|
836
|
+
card.suggestionId,
|
|
837
|
+
);
|
|
838
|
+
assert.equal(stored.state, CalendarSuggestionState.Pending);
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
for (const state of [
|
|
843
|
+
CalendarSuggestionState.Declined,
|
|
844
|
+
CalendarSuggestionState.Dismissed,
|
|
845
|
+
]) {
|
|
846
|
+
test(`returns a ${state} card to Pending`, async () => {
|
|
847
|
+
const { accountConfigId, event } = anAccount();
|
|
848
|
+
const card = await putSuggestion(accountConfigId, `msg-reopen-${state}`);
|
|
849
|
+
await client.calendarSuggestion.settle(
|
|
850
|
+
accountConfigId,
|
|
851
|
+
card.suggestionId,
|
|
852
|
+
{ state, acceptedCalendarObjectId: "" },
|
|
853
|
+
);
|
|
854
|
+
|
|
855
|
+
const reopened = (await reopenSuggestion(
|
|
856
|
+
contextOf({ params: { suggestionId: card.suggestionId } }),
|
|
857
|
+
event,
|
|
858
|
+
)) as unknown as Card;
|
|
859
|
+
|
|
860
|
+
assert.equal(reopened.state, CalendarSuggestionState.Pending);
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
test("refuses a superseded card and leaves it as it is", async () => {
|
|
865
|
+
const { accountConfigId, event } = anAccount();
|
|
866
|
+
const card = await putSuggestion(accountConfigId, "msg-reopen-superseded");
|
|
867
|
+
await client.calendarSuggestion.supersedeIfPending(
|
|
868
|
+
accountConfigId,
|
|
869
|
+
card.suggestionId,
|
|
870
|
+
"msg-reopen-newer",
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
await assert.rejects(
|
|
874
|
+
() =>
|
|
875
|
+
reopenSuggestion(
|
|
876
|
+
contextOf({ params: { suggestionId: card.suggestionId } }),
|
|
877
|
+
event,
|
|
878
|
+
),
|
|
879
|
+
(error: unknown) => (error as { statusCode?: number }).statusCode === 400,
|
|
880
|
+
);
|
|
881
|
+
|
|
882
|
+
const untouched = await client.calendarSuggestion.get(
|
|
883
|
+
accountConfigId,
|
|
884
|
+
card.suggestionId,
|
|
885
|
+
);
|
|
886
|
+
assert.equal(untouched.state, CalendarSuggestionState.Superseded);
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
test("refuses a cancellation, whose answer it cannot undo", async () => {
|
|
890
|
+
const { accountConfigId, event } = anAccount();
|
|
891
|
+
const card = await putSuggestion(
|
|
892
|
+
accountConfigId,
|
|
893
|
+
"msg-reopen-cancel",
|
|
894
|
+
CalendarInviteMethod.Cancel,
|
|
895
|
+
);
|
|
896
|
+
await client.calendarSuggestion.settle(accountConfigId, card.suggestionId, {
|
|
897
|
+
state: CalendarSuggestionState.Dismissed,
|
|
898
|
+
acceptedCalendarObjectId: "",
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
await assert.rejects(
|
|
902
|
+
() =>
|
|
903
|
+
reopenSuggestion(
|
|
904
|
+
contextOf({ params: { suggestionId: card.suggestionId } }),
|
|
905
|
+
event,
|
|
906
|
+
),
|
|
907
|
+
(error: unknown) => (error as { statusCode?: number }).statusCode === 400,
|
|
908
|
+
);
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
test("answers not-found for a card on another account", async () => {
|
|
912
|
+
const stranger = anAccount();
|
|
913
|
+
const { card } = await answeredInto(
|
|
914
|
+
stranger.accountConfigId,
|
|
915
|
+
"msg-reopen-stranger",
|
|
916
|
+
CalendarInviteAnswer.Accepted,
|
|
917
|
+
);
|
|
918
|
+
const { event } = anAccount();
|
|
919
|
+
|
|
920
|
+
await assert.rejects(
|
|
921
|
+
() =>
|
|
922
|
+
reopenSuggestion(
|
|
923
|
+
contextOf({ params: { suggestionId: card.suggestionId } }),
|
|
924
|
+
event,
|
|
925
|
+
),
|
|
926
|
+
(error: unknown) => (error as { statusCode?: number }).statusCode === 404,
|
|
927
|
+
);
|
|
928
|
+
const untouched = await client.calendarSuggestion.get(
|
|
929
|
+
stranger.accountConfigId,
|
|
930
|
+
card.suggestionId,
|
|
931
|
+
);
|
|
932
|
+
assert.equal(untouched.state, CalendarSuggestionState.Accepted);
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
describe("POST /calendar-suggestions/{suggestionId}/reopen, against what else holds the event", () => {
|
|
937
|
+
const recurring = (sequence: number): string =>
|
|
938
|
+
INVITATION.replace(
|
|
939
|
+
"SUMMARY:Quarterly review",
|
|
940
|
+
`SEQUENCE:${sequence}\r\nRRULE:FREQ=WEEKLY;COUNT=4\r\nSUMMARY:Quarterly review`,
|
|
941
|
+
);
|
|
942
|
+
|
|
943
|
+
const putInvite = (
|
|
944
|
+
accountConfigId: string,
|
|
945
|
+
messageId: string,
|
|
946
|
+
sequence: number,
|
|
947
|
+
): Promise<CalendarSuggestionItem> =>
|
|
948
|
+
client.calendarSuggestion.put({
|
|
949
|
+
accountConfigId,
|
|
950
|
+
messageId,
|
|
951
|
+
bodyPartId: "part-1",
|
|
952
|
+
icalUid: "invite@example.test",
|
|
953
|
+
sequence,
|
|
954
|
+
method: CalendarInviteMethod.Request,
|
|
955
|
+
source: CalendarSuggestionSource.IcalendarPart,
|
|
956
|
+
summary: "Quarterly review",
|
|
957
|
+
dtStart: "2026-09-01T10:00:00+02:00",
|
|
958
|
+
dtEnd: "2026-09-01T11:00:00+02:00",
|
|
959
|
+
allDay: false,
|
|
960
|
+
location: "Room 4",
|
|
961
|
+
organizer: "organizer@example.test",
|
|
962
|
+
zoneCertainty: "Explicit",
|
|
963
|
+
icalData: recurring(sequence),
|
|
964
|
+
} as PutCalendarSuggestionInput);
|
|
965
|
+
|
|
966
|
+
const acceptInto = async (
|
|
967
|
+
accountConfigId: string,
|
|
968
|
+
calendarId: string,
|
|
969
|
+
suggestion: CalendarSuggestionItem,
|
|
970
|
+
): Promise<CalendarSuggestionItem> => {
|
|
971
|
+
const accepted = await acceptCalendarSuggestion(client.calendarUnitOfWork, {
|
|
972
|
+
accountConfigId,
|
|
973
|
+
calendarId,
|
|
974
|
+
suggestion,
|
|
975
|
+
attendee: "user@example.test",
|
|
976
|
+
answer: CalendarInviteAnswer.Accepted,
|
|
977
|
+
});
|
|
978
|
+
assert.ok(accepted.ok);
|
|
979
|
+
return accepted.value.suggestion;
|
|
980
|
+
};
|
|
981
|
+
|
|
982
|
+
const calendarOf = (accountConfigId: string) =>
|
|
983
|
+
client.calendarCollection.create({
|
|
984
|
+
accountConfigId,
|
|
985
|
+
urlSegment: "default",
|
|
986
|
+
displayName: "Calendar",
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
test("refuses a request's answer once its cancellation was answered into the event, and says so on the card", async () => {
|
|
990
|
+
const { accountConfigId, event } = anAccount();
|
|
991
|
+
const calendar = await calendarOf(accountConfigId);
|
|
992
|
+
const first = await acceptInto(
|
|
993
|
+
accountConfigId,
|
|
994
|
+
calendar.calendarId,
|
|
995
|
+
await putInvite(accountConfigId, "msg-overtaken-0", 0),
|
|
996
|
+
);
|
|
997
|
+
await acceptInto(
|
|
998
|
+
accountConfigId,
|
|
999
|
+
calendar.calendarId,
|
|
1000
|
+
await putSuggestion(
|
|
1001
|
+
accountConfigId,
|
|
1002
|
+
"msg-overtaken-cancel",
|
|
1003
|
+
CalendarInviteMethod.Cancel,
|
|
1004
|
+
),
|
|
1005
|
+
);
|
|
1006
|
+
|
|
1007
|
+
await assert.rejects(
|
|
1008
|
+
() =>
|
|
1009
|
+
reopenSuggestion(
|
|
1010
|
+
contextOf({ params: { suggestionId: first.suggestionId } }),
|
|
1011
|
+
event,
|
|
1012
|
+
),
|
|
1013
|
+
(error: unknown) => (error as { statusCode?: number }).statusCode === 400,
|
|
1014
|
+
);
|
|
1015
|
+
|
|
1016
|
+
assert.equal(
|
|
1017
|
+
(await client.calendarObject.listByCalendar(calendar.calendarId)).length,
|
|
1018
|
+
1,
|
|
1019
|
+
);
|
|
1020
|
+
const listed = (await listMessageSuggestions(
|
|
1021
|
+
contextOf({ params: { messageId: "msg-overtaken-0" } }),
|
|
1022
|
+
event,
|
|
1023
|
+
)) as unknown as { items: { answerOvertakenBy: string }[] };
|
|
1024
|
+
assert.deepEqual(
|
|
1025
|
+
listed.items.map((item) => item.answerOvertakenBy),
|
|
1026
|
+
[CalendarAnswerOvertaken.Cancellation],
|
|
1027
|
+
);
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
test("takes back both halves of a this-and-following split, and a re-add writes one event", async () => {
|
|
1031
|
+
const { accountConfigId, event } = anAccount();
|
|
1032
|
+
const calendar = await calendarOf(accountConfigId);
|
|
1033
|
+
const card = await acceptInto(
|
|
1034
|
+
accountConfigId,
|
|
1035
|
+
calendar.calendarId,
|
|
1036
|
+
await putInvite(accountConfigId, "msg-split", 0),
|
|
1037
|
+
);
|
|
1038
|
+
const split = await updateCalendarEventFor(
|
|
1039
|
+
calendarEventDepsOf(calendarDepsOf(client)),
|
|
1040
|
+
accountConfigId,
|
|
1041
|
+
{
|
|
1042
|
+
calendarId: calendar.calendarId,
|
|
1043
|
+
calendarObjectId: card.acceptedCalendarObjectId,
|
|
1044
|
+
scope: RecurrenceScope.Following,
|
|
1045
|
+
recurrenceId: "2026-09-15T08:00:00Z",
|
|
1046
|
+
ifMatch: undefined,
|
|
1047
|
+
},
|
|
1048
|
+
{ summary: "Quarterly review (moved)" },
|
|
1049
|
+
);
|
|
1050
|
+
assert.ok(split.ok, JSON.stringify(split));
|
|
1051
|
+
assert.equal(
|
|
1052
|
+
(await client.calendarObject.listByCalendar(calendar.calendarId)).length,
|
|
1053
|
+
2,
|
|
1054
|
+
);
|
|
1055
|
+
|
|
1056
|
+
const reopened = (await reopenSuggestion(
|
|
1057
|
+
contextOf({ params: { suggestionId: card.suggestionId } }),
|
|
1058
|
+
event,
|
|
1059
|
+
)) as unknown as CalendarSuggestionItem;
|
|
1060
|
+
|
|
1061
|
+
assert.equal(reopened.state, CalendarSuggestionState.Pending);
|
|
1062
|
+
assert.deepEqual(
|
|
1063
|
+
await client.calendarObject.listByCalendar(calendar.calendarId),
|
|
1064
|
+
[],
|
|
1065
|
+
);
|
|
1066
|
+
await acceptInto(accountConfigId, calendar.calendarId, {
|
|
1067
|
+
...card,
|
|
1068
|
+
...reopened,
|
|
1069
|
+
});
|
|
1070
|
+
assert.equal(
|
|
1071
|
+
(await client.calendarObject.listByCalendar(calendar.calendarId)).length,
|
|
1072
|
+
1,
|
|
1073
|
+
);
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1076
|
+
test("keeps the sender rule a mute wrote", async () => {
|
|
1077
|
+
const { accountConfigId, event } = anAccount();
|
|
1078
|
+
const card = await putSuggestion(accountConfigId, "msg-mute-reopen");
|
|
1079
|
+
await seedMessageFrom("msg-mute-reopen", "organizer@example.test");
|
|
1080
|
+
await dismissSuggestion(
|
|
1081
|
+
contextOf({
|
|
1082
|
+
params: { suggestionId: card.suggestionId },
|
|
1083
|
+
requestBody: { muteSender: true },
|
|
1084
|
+
}),
|
|
1085
|
+
event,
|
|
1086
|
+
);
|
|
1087
|
+
|
|
1088
|
+
const reopened = (await reopenSuggestion(
|
|
1089
|
+
contextOf({ params: { suggestionId: card.suggestionId } }),
|
|
1090
|
+
event,
|
|
1091
|
+
)) as unknown as Card;
|
|
1092
|
+
|
|
1093
|
+
assert.equal(reopened.state, CalendarSuggestionState.Pending);
|
|
1094
|
+
const rules = await client.filter.listByAccountAndState(
|
|
1095
|
+
accountConfigId,
|
|
1096
|
+
FilterState.Active,
|
|
1097
|
+
);
|
|
1098
|
+
assert.deepEqual(
|
|
1099
|
+
rules.map((rule) => rule.literalClauses),
|
|
1100
|
+
[[{ field: "From", value: "organizer@example.test" }]],
|
|
1101
|
+
);
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
test("a delete that fails part-way leaves the event and the answer as they were", async () => {
|
|
1105
|
+
const { accountConfigId } = anAccount();
|
|
1106
|
+
const calendar = await calendarOf(accountConfigId);
|
|
1107
|
+
const card = await acceptInto(
|
|
1108
|
+
accountConfigId,
|
|
1109
|
+
calendar.calendarId,
|
|
1110
|
+
await putInvite(accountConfigId, "msg-rollback", 0),
|
|
1111
|
+
);
|
|
1112
|
+
const failing: ICalendarUnitOfWork = {
|
|
1113
|
+
transaction: (fn) =>
|
|
1114
|
+
client.calendarUnitOfWork.transaction((repos) =>
|
|
1115
|
+
fn({
|
|
1116
|
+
...repos,
|
|
1117
|
+
calendarCollection: Object.assign(
|
|
1118
|
+
Object.create(repos.calendarCollection),
|
|
1119
|
+
{
|
|
1120
|
+
bumpSyncSequence: async () => {
|
|
1121
|
+
throw new Error("disk full");
|
|
1122
|
+
},
|
|
1123
|
+
},
|
|
1124
|
+
),
|
|
1125
|
+
}),
|
|
1126
|
+
),
|
|
1127
|
+
};
|
|
1128
|
+
|
|
1129
|
+
await assert.rejects(
|
|
1130
|
+
() =>
|
|
1131
|
+
reopenCalendarSuggestion(failing, {
|
|
1132
|
+
accountConfigId,
|
|
1133
|
+
suggestion: card,
|
|
1134
|
+
}),
|
|
1135
|
+
/disk full/,
|
|
1136
|
+
);
|
|
1137
|
+
|
|
1138
|
+
const object = await client.calendarObject.find(
|
|
1139
|
+
calendar.calendarId,
|
|
1140
|
+
card.acceptedCalendarObjectId,
|
|
1141
|
+
);
|
|
1142
|
+
assert.ok(object, "the event is still on the calendar");
|
|
1143
|
+
assert.ok(
|
|
1144
|
+
(
|
|
1145
|
+
await client.calendarEventIndex.listForObject(
|
|
1146
|
+
calendar.calendarId,
|
|
1147
|
+
card.acceptedCalendarObjectId,
|
|
1148
|
+
)
|
|
1149
|
+
).length > 0,
|
|
1150
|
+
"and so are its occurrences",
|
|
1151
|
+
);
|
|
1152
|
+
const untouched = await client.calendarSuggestion.get(
|
|
1153
|
+
accountConfigId,
|
|
1154
|
+
card.suggestionId,
|
|
1155
|
+
);
|
|
1156
|
+
assert.equal(untouched.state, CalendarSuggestionState.Accepted);
|
|
1157
|
+
assert.equal(
|
|
1158
|
+
untouched.acceptedCalendarObjectId,
|
|
1159
|
+
card.acceptedCalendarObjectId,
|
|
1160
|
+
);
|
|
1161
|
+
});
|
|
1162
|
+
});
|
|
@@ -5,7 +5,9 @@ import type {
|
|
|
5
5
|
} from "@remit/api-openapi-types";
|
|
6
6
|
import {
|
|
7
7
|
acceptCalendarSuggestion,
|
|
8
|
+
answersOvertakenBy,
|
|
8
9
|
correctCalendarSuggestion,
|
|
10
|
+
reopenCalendarSuggestion,
|
|
9
11
|
} from "@remit/calendar-service";
|
|
10
12
|
import {
|
|
11
13
|
type CalendarSuggestionItem,
|
|
@@ -44,28 +46,54 @@ import { pickEventUpdate } from "./calendar-event.js";
|
|
|
44
46
|
*/
|
|
45
47
|
export const toCalendarSuggestionResponse = (
|
|
46
48
|
item: CalendarSuggestionItem,
|
|
49
|
+
answerOvertakenBy: CalendarSuggestionResponse["answerOvertakenBy"],
|
|
47
50
|
): CalendarSuggestionResponse => {
|
|
48
51
|
const { icalData: _icalData, ...response } = item;
|
|
49
|
-
return { ...response, supersededByThreadId: "" };
|
|
52
|
+
return { ...response, supersededByThreadId: "", answerOvertakenBy };
|
|
50
53
|
};
|
|
51
54
|
|
|
52
|
-
export const toCalendarSuggestionResponses = (
|
|
53
|
-
|
|
55
|
+
export const toCalendarSuggestionResponses = async (
|
|
56
|
+
deps: {
|
|
57
|
+
threadMessage: Pick<IThreadMessageRepository, "findByMessageId">;
|
|
58
|
+
calendarSuggestion: Pick<
|
|
59
|
+
ICalendarSuggestionRepository,
|
|
60
|
+
"listByAcceptedCalendarObjects"
|
|
61
|
+
>;
|
|
62
|
+
},
|
|
54
63
|
accountConfigId: string,
|
|
55
64
|
items: CalendarSuggestionItem[],
|
|
56
65
|
): Promise<CalendarSuggestionResponse[]> =>
|
|
57
66
|
Promise.all(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
67
|
+
(
|
|
68
|
+
await answersOvertakenBy(deps.calendarSuggestion, accountConfigId, items)
|
|
69
|
+
).map(async ({ suggestion, answerOvertakenBy }) => {
|
|
70
|
+
const response = toCalendarSuggestionResponse(
|
|
71
|
+
suggestion,
|
|
72
|
+
answerOvertakenBy,
|
|
73
|
+
);
|
|
74
|
+
if (suggestion.supersededByMessageId === "") return response;
|
|
75
|
+
const newer = await deps.threadMessage.findByMessageId(
|
|
62
76
|
accountConfigId,
|
|
63
|
-
|
|
77
|
+
suggestion.supersededByMessageId,
|
|
64
78
|
);
|
|
65
79
|
return { ...response, supersededByThreadId: newer?.threadId ?? "" };
|
|
66
80
|
}),
|
|
67
81
|
);
|
|
68
82
|
|
|
83
|
+
const respondOne = async (
|
|
84
|
+
client: RemitClient,
|
|
85
|
+
accountConfigId: string,
|
|
86
|
+
item: CalendarSuggestionItem,
|
|
87
|
+
): Promise<CalendarSuggestionResponse> => {
|
|
88
|
+
const [response] = await toCalendarSuggestionResponses(
|
|
89
|
+
client,
|
|
90
|
+
accountConfigId,
|
|
91
|
+
[item],
|
|
92
|
+
);
|
|
93
|
+
if (!response) throw new Error("unreachable: one item in, one response out");
|
|
94
|
+
return response;
|
|
95
|
+
};
|
|
96
|
+
|
|
69
97
|
/**
|
|
70
98
|
* The states a person can move a pending card into. `Superseded` is the
|
|
71
99
|
* producer's alone — a revision retires a card, a person never does — and the
|
|
@@ -196,7 +224,7 @@ export const CalendarSuggestionOperations: Record<
|
|
|
196
224
|
|
|
197
225
|
return {
|
|
198
226
|
items: await toCalendarSuggestionResponses(
|
|
199
|
-
client
|
|
227
|
+
client,
|
|
200
228
|
accountConfigId,
|
|
201
229
|
page.items,
|
|
202
230
|
),
|
|
@@ -225,7 +253,7 @@ export const MessageCalendarSuggestionOperations: Record<
|
|
|
225
253
|
|
|
226
254
|
return {
|
|
227
255
|
items: await toCalendarSuggestionResponses(
|
|
228
|
-
client
|
|
256
|
+
client,
|
|
229
257
|
accountConfigId,
|
|
230
258
|
items,
|
|
231
259
|
),
|
|
@@ -312,7 +340,7 @@ export const CalendarSuggestionActionOperations: Record<
|
|
|
312
340
|
throw new BadRequestError(accepted.error.message);
|
|
313
341
|
}
|
|
314
342
|
|
|
315
|
-
return
|
|
343
|
+
return respondOne(client, accountConfigId, accepted.value.suggestion);
|
|
316
344
|
},
|
|
317
345
|
|
|
318
346
|
CalendarSuggestionActionOperations_declineCalendarSuggestion: async (
|
|
@@ -333,7 +361,7 @@ export const CalendarSuggestionActionOperations: Record<
|
|
|
333
361
|
CalendarSuggestionState.Declined,
|
|
334
362
|
);
|
|
335
363
|
|
|
336
|
-
return
|
|
364
|
+
return respondOne(client, accountConfigId, declined);
|
|
337
365
|
},
|
|
338
366
|
|
|
339
367
|
CalendarSuggestionActionOperations_dismissCalendarSuggestion: async (
|
|
@@ -360,6 +388,32 @@ export const CalendarSuggestionActionOperations: Record<
|
|
|
360
388
|
await muteSender(client, accountConfigId, dismissed.messageId);
|
|
361
389
|
}
|
|
362
390
|
|
|
363
|
-
return
|
|
391
|
+
return respondOne(client, accountConfigId, dismissed);
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
CalendarSuggestionActionOperations_reopenCalendarSuggestion: async (
|
|
395
|
+
context,
|
|
396
|
+
...args: unknown[]
|
|
397
|
+
) => {
|
|
398
|
+
const event = args[0] as APIGatewayProxyEvent;
|
|
399
|
+
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
400
|
+
const { suggestionId } = context.request.params as {
|
|
401
|
+
suggestionId: string;
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const client = await getClient();
|
|
405
|
+
const suggestion = await client.calendarSuggestion.get(
|
|
406
|
+
accountConfigId,
|
|
407
|
+
suggestionId,
|
|
408
|
+
);
|
|
409
|
+
const reopened = await reopenCalendarSuggestion(client.calendarUnitOfWork, {
|
|
410
|
+
accountConfigId,
|
|
411
|
+
suggestion,
|
|
412
|
+
});
|
|
413
|
+
if (!reopened.ok) {
|
|
414
|
+
throw new BadRequestError(reopened.error.message);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return respondOne(client, accountConfigId, reopened.value);
|
|
364
418
|
},
|
|
365
419
|
};
|
package/src/types.ts
CHANGED
|
@@ -88,7 +88,8 @@ export type OperationIds =
|
|
|
88
88
|
| "MessageCalendarSuggestionOperations_listMessageCalendarSuggestions"
|
|
89
89
|
| "CalendarSuggestionActionOperations_acceptCalendarSuggestion"
|
|
90
90
|
| "CalendarSuggestionActionOperations_declineCalendarSuggestion"
|
|
91
|
-
| "CalendarSuggestionActionOperations_dismissCalendarSuggestion"
|
|
91
|
+
| "CalendarSuggestionActionOperations_dismissCalendarSuggestion"
|
|
92
|
+
| "CalendarSuggestionActionOperations_reopenCalendarSuggestion";
|
|
92
93
|
|
|
93
94
|
export type MeOperationIds = MatchPrefix<"MeOperations_", OperationIds>;
|
|
94
95
|
|