-
Notifications
You must be signed in to change notification settings - Fork 1
/
submissionService.ts
215 lines (207 loc) · 6.18 KB
/
submissionService.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
import { API } from "aws-amplify";
import {
Attachment,
ReactQueryApiError,
Action,
AttachmentKey,
Authority,
} from "shared-types";
import { buildActionUrl, SubmissionServiceEndpoint } from "@/utils";
import { OneMacUser } from "@/api";
import { useMutation, UseMutationOptions } from "@tanstack/react-query";
import { seaToolFriendlyTimestamp } from "shared-utils";
export type SubmissionServiceParameters<T> = {
data: T;
endpoint: SubmissionServiceEndpoint;
user: OneMacUser | undefined;
authority: Authority;
};
type SubmissionServiceResponse = {
body: {
message: string;
};
};
type PreSignedURL = {
url: string;
key: string;
bucket: string;
};
export type UploadRecipe = PreSignedURL & {
data: File;
title: AttachmentKey;
name: string;
};
type AttachmentKeyValue = { attachmentKey: AttachmentKey; file: File };
/** Pass in an array of UploadRecipes and get a back-end compatible object
* to store attachment data */
export const buildAttachmentObject = (recipes?: UploadRecipe[]) => {
if (!recipes) return null;
return recipes
.map(
(r) =>
({
key: r.key,
filename: r.name,
title: r.title,
bucket: r.bucket,
uploadDate: Date.now(),
} as Attachment),
)
.flat();
};
/** Builds the payload for submission based on which variant a developer has
* configured the {@link submit} function with */
export const buildSubmissionPayload = <T extends Record<string, unknown>>(
data: T,
user: OneMacUser | undefined,
endpoint: SubmissionServiceEndpoint,
authority: Authority,
attachments?: UploadRecipe[],
) => {
const userDetails = {
submitterEmail: user?.user?.email ?? "N/A",
submitterName:
user?.user?.given_name && user?.user?.family_name
? `${user.user.given_name} ${user.user.family_name}`
: "N/A",
};
const baseProperties = {
authority: authority,
origin: "OneMAC",
};
switch (endpoint) {
case "/appk":
return {
...data,
...userDetails,
...baseProperties,
authority: Authority["1915c"],
proposedEffectiveDate: seaToolFriendlyTimestamp(
data.proposedEffectiveDate as Date,
),
attachments: attachments ? buildAttachmentObject(attachments) : null,
};
case buildActionUrl(Action.REMOVE_APPK_CHILD):
return {
...data,
...baseProperties,
...userDetails,
authority: Authority["1915c"],
};
case "/submit":
return {
...data,
...baseProperties,
...userDetails,
proposedEffectiveDate: seaToolFriendlyTimestamp(
data.proposedEffectiveDate as Date,
),
attachments: attachments ? buildAttachmentObject(attachments) : null,
state: (data.id as string).split("-")[0],
};
case buildActionUrl(Action.ISSUE_RAI):
case buildActionUrl(Action.RESPOND_TO_RAI):
case buildActionUrl(Action.ENABLE_RAI_WITHDRAW):
case buildActionUrl(Action.DISABLE_RAI_WITHDRAW):
case buildActionUrl(Action.WITHDRAW_RAI):
case buildActionUrl(Action.WITHDRAW_PACKAGE):
case buildActionUrl(Action.TEMP_EXTENSION):
case buildActionUrl(Action.UPDATE_ID):
default:
return {
...baseProperties,
...userDetails,
...data,
attachments: attachments ? buildAttachmentObject(attachments) : null,
};
}
};
export const buildAttachmentKeyValueArr = (
attachments: Record<string, File[]>,
): AttachmentKeyValue[] =>
Object.entries(attachments)
.filter(([, val]) => val !== undefined && (val as File[]).length)
.map(([key, value]) => {
return (value as File[]).map((file) => ({
attachmentKey: key as AttachmentKey,
file: file,
}));
})
.flat();
export const urlsToRecipes = (
urls: PreSignedURL[],
attachments: AttachmentKeyValue[],
): UploadRecipe[] =>
urls.map((obj, idx) => ({
...obj, // Spreading the presigned url
data: attachments[idx].file, // The attachment file object
// Add your attachments object key and file label value to the attachmentTitleMap
// for this transform to work. Else the title will just be the object key.
title: attachments[idx].attachmentKey,
name: attachments[idx].file.name,
}));
/** A useful interface for submitting form data to our submission service */
export const submit = async <T extends Record<string, unknown>>({
data,
endpoint,
user,
authority,
}: SubmissionServiceParameters<T>): Promise<SubmissionServiceResponse> => {
if (data?.attachments) {
// Drop nulls and non arrays
const attachments = buildAttachmentKeyValueArr(
data.attachments as Record<string, File[]>,
);
// Generate a presigned url for each attachment
const preSignedURLs: PreSignedURL[] = await Promise.all(
attachments.map((attachment) =>
API.post("os", "/getUploadUrl", {
body: {
fileName: attachment.file.name,
},
}),
),
);
// For each attachment, add name, title, and a presigned url... and push to uploadRecipes
const uploadRecipes: UploadRecipe[] = urlsToRecipes(
preSignedURLs,
attachments,
);
// Upload attachments
await Promise.all(
uploadRecipes.map(async ({ url, data }) => {
await fetch(url, {
body: data,
method: "PUT",
});
}),
);
// Submit form data
return await API.post("os", endpoint, {
body: buildSubmissionPayload(
data,
user,
endpoint,
authority,
uploadRecipes,
),
});
} else {
// Submit form data
return await API.post("os", endpoint, {
body: buildSubmissionPayload(data, user, endpoint, authority),
});
}
};
/** A useful interface for using react-query with our submission service. If you
* are using react-hook-form's `form.handleSubmit()` pattern, bypass this and just
* use {@link submit}. */
export const useSubmissionService = <T extends Record<string, unknown>>(
config: SubmissionServiceParameters<T>,
options?: UseMutationOptions<SubmissionServiceResponse, ReactQueryApiError>,
) =>
useMutation<SubmissionServiceResponse, ReactQueryApiError>(
["submit"],
() => submit(config),
options,
);