@dbos-inc/aws-s3-workflows 3.6.3-preview → 3.6.7-preview
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/dist/src/s3_utils.test.d.ts +0 -23
- package/dist/src/s3_utils.test.d.ts.map +1 -1
- package/dist/src/s3_utils.test.js +308 -351
- package/dist/src/s3_utils.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/s3_utils.test.ts +350 -350
- package/dist/knexfile.d.ts +0 -4
- package/dist/knexfile.d.ts.map +0 -1
- package/dist/knexfile.js +0 -16
- package/dist/knexfile.js.map +0 -1
- package/knexfile.ts +0 -17
package/src/s3_utils.test.ts
CHANGED
|
@@ -1,350 +1,350 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from './s3_utils';
|
|
8
|
-
import { DBOS } from '@dbos-inc/dbos-sdk';
|
|
9
|
-
|
|
10
|
-
import { S3Client, DeleteObjectCommand, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
11
|
-
import { createPresignedPost, PresignedPost } from '@aws-sdk/s3-presigned-post';
|
|
12
|
-
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
13
|
-
|
|
14
|
-
import FormData from 'form-data';
|
|
15
|
-
import axios, { AxiosResponse } from 'axios';
|
|
16
|
-
import { Readable } from 'stream';
|
|
17
|
-
import * as fs from 'fs';
|
|
18
|
-
import { randomUUID } from 'node:crypto';
|
|
19
|
-
|
|
20
|
-
enum FileStatus {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface FileDetails {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface UserFile extends FileDetails, FileRecord {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
class TestUserFileTable {
|
|
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
|
-
const s3bucket = process.env['S3_BUCKET'];
|
|
135
|
-
const s3region = process.env['AWS_REGION'];
|
|
136
|
-
const s3accessKey = process.env['AWS_ACCESS_KEY_ID'];
|
|
137
|
-
const s3accessSecret = process.env['AWS_SECRET_ACCESS_KEY'];
|
|
138
|
-
|
|
139
|
-
let s3client: S3Client | undefined = undefined;
|
|
140
|
-
|
|
141
|
-
interface Opts {
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const s3callback: S3WorkflowCallbacks<UserFile, Opts> = {
|
|
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
|
-
export const uploadWF = registerS3UploadWorkflow(s3callback, { className: 'UserFile', name: 'uploadWF' });
|
|
200
|
-
export const uploadPWF = registerS3PresignedUploadWorkflow(s3callback, { className: 'UserFile', name: 'uploadPWF' });
|
|
201
|
-
export const deleteWF = registerS3DeleteWorkflow(s3callback, { className: 'UserFile', name: 'deleteWF' });
|
|
202
|
-
|
|
203
|
-
describe('ses-tests', () => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
});
|
|
1
|
+
// import {
|
|
2
|
+
// FileRecord,
|
|
3
|
+
// S3WorkflowCallbacks,
|
|
4
|
+
// registerS3UploadWorkflow,
|
|
5
|
+
// registerS3PresignedUploadWorkflow,
|
|
6
|
+
// registerS3DeleteWorkflow,
|
|
7
|
+
// } from './s3_utils';
|
|
8
|
+
// import { DBOS } from '@dbos-inc/dbos-sdk';
|
|
9
|
+
|
|
10
|
+
// import { S3Client, DeleteObjectCommand, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
11
|
+
// import { createPresignedPost, PresignedPost } from '@aws-sdk/s3-presigned-post';
|
|
12
|
+
// import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
13
|
+
|
|
14
|
+
// import FormData from 'form-data';
|
|
15
|
+
// import axios, { AxiosResponse } from 'axios';
|
|
16
|
+
// import { Readable } from 'stream';
|
|
17
|
+
// import * as fs from 'fs';
|
|
18
|
+
// import { randomUUID } from 'node:crypto';
|
|
19
|
+
|
|
20
|
+
// enum FileStatus {
|
|
21
|
+
// PENDING = 'Pending',
|
|
22
|
+
// RECEIVED = 'Received',
|
|
23
|
+
// ACTIVE = 'Active',
|
|
24
|
+
// }
|
|
25
|
+
|
|
26
|
+
// interface FileDetails {
|
|
27
|
+
// user_id: string;
|
|
28
|
+
// file_type: string;
|
|
29
|
+
// file_name: string;
|
|
30
|
+
// file_id?: string;
|
|
31
|
+
// file_status?: string;
|
|
32
|
+
// file_time?: number;
|
|
33
|
+
// }
|
|
34
|
+
|
|
35
|
+
// interface UserFile extends FileDetails, FileRecord {
|
|
36
|
+
// user_id: string;
|
|
37
|
+
// file_type: string;
|
|
38
|
+
// file_name: string;
|
|
39
|
+
// file_id: string;
|
|
40
|
+
// file_status: string;
|
|
41
|
+
// file_time: number;
|
|
42
|
+
// }
|
|
43
|
+
|
|
44
|
+
// class TestUserFileTable {
|
|
45
|
+
// //////////
|
|
46
|
+
// //// Database table
|
|
47
|
+
// //////////
|
|
48
|
+
|
|
49
|
+
// // Pick a file ID
|
|
50
|
+
// @DBOS.step()
|
|
51
|
+
// static chooseFileRecord(details: FileDetails): Promise<UserFile> {
|
|
52
|
+
// const rec: UserFile = {
|
|
53
|
+
// user_id: details.user_id,
|
|
54
|
+
// file_status: FileStatus.PENDING,
|
|
55
|
+
// file_type: details.file_type,
|
|
56
|
+
// file_id: randomUUID(),
|
|
57
|
+
// file_name: details.file_name,
|
|
58
|
+
// file_time: new Date().getTime(),
|
|
59
|
+
// key: '',
|
|
60
|
+
// };
|
|
61
|
+
// rec.key = TestUserFileTable.createS3Key(rec);
|
|
62
|
+
// return Promise.resolve(rec);
|
|
63
|
+
// }
|
|
64
|
+
|
|
65
|
+
// static createS3Key(rec: UserFile) {
|
|
66
|
+
// const key = `${rec.file_type}/${rec.user_id}/${rec.file_id}/${rec.file_time}`;
|
|
67
|
+
// return key;
|
|
68
|
+
// }
|
|
69
|
+
|
|
70
|
+
// static toFileDetails(rec: UserFile) {
|
|
71
|
+
// return {
|
|
72
|
+
// user_id: rec.user_id,
|
|
73
|
+
// file_type: rec.file_type,
|
|
74
|
+
// file_name: rec.file_name,
|
|
75
|
+
// file_id: rec.file_id,
|
|
76
|
+
// file_status: rec.file_status,
|
|
77
|
+
// file_time: rec.file_time,
|
|
78
|
+
// };
|
|
79
|
+
// }
|
|
80
|
+
|
|
81
|
+
// // File table DML operations
|
|
82
|
+
// // Whole record is known
|
|
83
|
+
// @DBOS.transaction()
|
|
84
|
+
// static async insertFileRecord(rec: UserFile) {
|
|
85
|
+
// await DBOS.knexClient<FileDetails>('user_files').insert(TestUserFileTable.toFileDetails(rec));
|
|
86
|
+
// }
|
|
87
|
+
// @DBOS.transaction()
|
|
88
|
+
// static async updateFileRecord(rec: UserFile) {
|
|
89
|
+
// await DBOS.knexClient<FileDetails>('user_files')
|
|
90
|
+
// .update(TestUserFileTable.toFileDetails(rec))
|
|
91
|
+
// .where({ file_id: rec.file_id });
|
|
92
|
+
// }
|
|
93
|
+
// // Delete when part of record is known
|
|
94
|
+
// @DBOS.transaction()
|
|
95
|
+
// static async deleteFileRecordById(file_id: string) {
|
|
96
|
+
// await DBOS.knexClient<FileDetails>('user_files').delete().where({ file_id });
|
|
97
|
+
// }
|
|
98
|
+
|
|
99
|
+
// // Queries
|
|
100
|
+
// @DBOS.transaction({ readOnly: true })
|
|
101
|
+
// static async lookUpByFields(fields: FileDetails) {
|
|
102
|
+
// const rv = await DBOS.knexClient<FileDetails>('user_files')
|
|
103
|
+
// .select()
|
|
104
|
+
// .where({ ...fields, file_status: FileStatus.ACTIVE })
|
|
105
|
+
// .orderBy('file_time', 'desc')
|
|
106
|
+
// .first();
|
|
107
|
+
// return rv ? [rv] : [];
|
|
108
|
+
// }
|
|
109
|
+
// @DBOS.transaction({ readOnly: true })
|
|
110
|
+
// static async lookUpByName(user_id: string, file_type: string, file_name: string) {
|
|
111
|
+
// const rv = await DBOS.knexClient<FileDetails>('user_files')
|
|
112
|
+
// .select()
|
|
113
|
+
// .where({ user_id, file_type, file_name, file_status: FileStatus.ACTIVE })
|
|
114
|
+
// .orderBy('file_time', 'desc')
|
|
115
|
+
// .first();
|
|
116
|
+
// return rv ? [rv] : [];
|
|
117
|
+
// }
|
|
118
|
+
// @DBOS.transaction({ readOnly: true })
|
|
119
|
+
// static async lookUpByType(user_id: string, file_type: string) {
|
|
120
|
+
// const rv = await DBOS.knexClient<FileDetails>('user_files')
|
|
121
|
+
// .select()
|
|
122
|
+
// .where({ user_id, file_type, file_status: FileStatus.ACTIVE });
|
|
123
|
+
// return rv;
|
|
124
|
+
// }
|
|
125
|
+
// @DBOS.transaction({ readOnly: true })
|
|
126
|
+
// static async lookUpByUser(user_id: string) {
|
|
127
|
+
// const rv = await DBOS.knexClient<FileDetails>('user_files')
|
|
128
|
+
// .select()
|
|
129
|
+
// .where({ user_id, file_status: FileStatus.ACTIVE });
|
|
130
|
+
// return rv;
|
|
131
|
+
// }
|
|
132
|
+
// }
|
|
133
|
+
|
|
134
|
+
// const s3bucket = process.env['S3_BUCKET'];
|
|
135
|
+
// const s3region = process.env['AWS_REGION'];
|
|
136
|
+
// const s3accessKey = process.env['AWS_ACCESS_KEY_ID'];
|
|
137
|
+
// const s3accessSecret = process.env['AWS_SECRET_ACCESS_KEY'];
|
|
138
|
+
|
|
139
|
+
// let s3client: S3Client | undefined = undefined;
|
|
140
|
+
|
|
141
|
+
// interface Opts {
|
|
142
|
+
// contentType?: string;
|
|
143
|
+
// }
|
|
144
|
+
|
|
145
|
+
// const s3callback: S3WorkflowCallbacks<UserFile, Opts> = {
|
|
146
|
+
// // Database operations (these should be transactions)
|
|
147
|
+
// newActiveFile: async (rec: UserFile) => {
|
|
148
|
+
// rec.file_status = FileStatus.ACTIVE;
|
|
149
|
+
// return await TestUserFileTable.insertFileRecord(rec);
|
|
150
|
+
// },
|
|
151
|
+
// newPendingFile: async (rec: UserFile) => {
|
|
152
|
+
// rec.file_status = FileStatus.PENDING;
|
|
153
|
+
// return await TestUserFileTable.insertFileRecord(rec);
|
|
154
|
+
// },
|
|
155
|
+
// fileActivated: async (rec: UserFile) => {
|
|
156
|
+
// rec.file_status = FileStatus.ACTIVE;
|
|
157
|
+
// return await TestUserFileTable.updateFileRecord(rec);
|
|
158
|
+
// },
|
|
159
|
+
// fileDeleted: async (rec: UserFile) => {
|
|
160
|
+
// return await TestUserFileTable.deleteFileRecordById(rec.file_id);
|
|
161
|
+
// },
|
|
162
|
+
|
|
163
|
+
// // S3 interaction options, these will be run as steps
|
|
164
|
+
// putS3Contents: async (rec: UserFile, content: string, options?: Opts) => {
|
|
165
|
+
// return await s3client?.send(
|
|
166
|
+
// new PutObjectCommand({
|
|
167
|
+
// Bucket: s3bucket,
|
|
168
|
+
// Key: rec.key,
|
|
169
|
+
// ContentType: options?.contentType ?? 'text/plain',
|
|
170
|
+
// Body: content,
|
|
171
|
+
// }),
|
|
172
|
+
// );
|
|
173
|
+
// },
|
|
174
|
+
// createPresignedPost: async (rec: UserFile, timeout?: number, opts?: Opts) => {
|
|
175
|
+
// const postPresigned = await createPresignedPost(s3client!, {
|
|
176
|
+
// Conditions: [
|
|
177
|
+
// ['content-length-range', 1, 10000000], // 10MB
|
|
178
|
+
// ],
|
|
179
|
+
// Bucket: s3bucket!,
|
|
180
|
+
// Key: rec.key,
|
|
181
|
+
// Expires: timeout || 60,
|
|
182
|
+
// Fields: {
|
|
183
|
+
// 'Content-Type': opts?.contentType || '*',
|
|
184
|
+
// },
|
|
185
|
+
// });
|
|
186
|
+
// return { url: postPresigned.url, fields: postPresigned.fields };
|
|
187
|
+
// },
|
|
188
|
+
// validateS3Upload: undefined,
|
|
189
|
+
// deleteS3Object: async (rec: UserFile) => {
|
|
190
|
+
// return await s3client?.send(
|
|
191
|
+
// new DeleteObjectCommand({
|
|
192
|
+
// Bucket: s3bucket,
|
|
193
|
+
// Key: rec.key,
|
|
194
|
+
// }),
|
|
195
|
+
// );
|
|
196
|
+
// },
|
|
197
|
+
// };
|
|
198
|
+
|
|
199
|
+
// export const uploadWF = registerS3UploadWorkflow(s3callback, { className: 'UserFile', name: 'uploadWF' });
|
|
200
|
+
// export const uploadPWF = registerS3PresignedUploadWorkflow(s3callback, { className: 'UserFile', name: 'uploadPWF' });
|
|
201
|
+
// export const deleteWF = registerS3DeleteWorkflow(s3callback, { className: 'UserFile', name: 'deleteWF' });
|
|
202
|
+
|
|
203
|
+
// describe('ses-tests', () => {
|
|
204
|
+
// let s3IsAvailable = true;
|
|
205
|
+
|
|
206
|
+
// beforeAll(async () => {
|
|
207
|
+
// // Check if S3 is available and update app config, skip the test if it's not
|
|
208
|
+
// if (!s3region || !s3bucket || !s3accessKey || !s3accessSecret) {
|
|
209
|
+
// s3IsAvailable = false;
|
|
210
|
+
// console.log(
|
|
211
|
+
// 'S3 Test is not configured. To run, set AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and S3_BUCKET',
|
|
212
|
+
// );
|
|
213
|
+
// } else {
|
|
214
|
+
// s3client = new S3Client({
|
|
215
|
+
// region: s3region,
|
|
216
|
+
// credentials: {
|
|
217
|
+
// accessKeyId: s3accessKey,
|
|
218
|
+
// secretAccessKey: s3accessSecret,
|
|
219
|
+
// },
|
|
220
|
+
// });
|
|
221
|
+
|
|
222
|
+
// await DBOS.launch();
|
|
223
|
+
// }
|
|
224
|
+
// });
|
|
225
|
+
|
|
226
|
+
// afterAll(async () => {
|
|
227
|
+
// if (s3IsAvailable) {
|
|
228
|
+
// await DBOS.shutdown();
|
|
229
|
+
// }
|
|
230
|
+
// }, 10000);
|
|
231
|
+
|
|
232
|
+
// test('s3-simple-wfs', async () => {
|
|
233
|
+
// if (!s3IsAvailable) {
|
|
234
|
+
// console.log('S3 unavailable, skipping S3 tests');
|
|
235
|
+
// return;
|
|
236
|
+
// }
|
|
237
|
+
|
|
238
|
+
// const userid = randomUUID();
|
|
239
|
+
|
|
240
|
+
// // The simple workflows that will be performed are to:
|
|
241
|
+
// // Put file contents into DBOS (w/ table index)
|
|
242
|
+
// const myFile: FileDetails = { user_id: userid, file_type: 'text', file_name: 'mytextfile.txt' };
|
|
243
|
+
// const myFileRec = await TestUserFileTable.chooseFileRecord(myFile);
|
|
244
|
+
// await uploadWF(myFileRec, 'This is my file');
|
|
245
|
+
|
|
246
|
+
// // Get the file contents out of DBOS (using the table index)
|
|
247
|
+
// const mytxt = await getS3KeyContents(myFileRec.key);
|
|
248
|
+
// expect(mytxt).toBe('This is my file');
|
|
249
|
+
|
|
250
|
+
// // Delete the file contents out of DBOS (using the table index)
|
|
251
|
+
// const dfhandle = await deleteWF(myFileRec);
|
|
252
|
+
// expect(dfhandle).toBeDefined();
|
|
253
|
+
// }, 10000);
|
|
254
|
+
|
|
255
|
+
// test('s3-complex-wfs', async () => {
|
|
256
|
+
// if (!s3IsAvailable) {
|
|
257
|
+
// console.log('S3 unavailable, skipping S3 tests');
|
|
258
|
+
// return;
|
|
259
|
+
// }
|
|
260
|
+
|
|
261
|
+
// // The complex workflows that will be performed are to:
|
|
262
|
+
// // Put the file contents into DBOS with a presigned post
|
|
263
|
+
// const userid = randomUUID();
|
|
264
|
+
|
|
265
|
+
// // The simple workflows that will be performed are to:
|
|
266
|
+
// // Put file contents into DBOS (w/ table index)
|
|
267
|
+
// const myFile: FileDetails = { user_id: userid, file_type: 'text', file_name: 'mytextfile.txt' };
|
|
268
|
+
// const myFileRec = await TestUserFileTable.chooseFileRecord(myFile);
|
|
269
|
+
// const wfhandle = await DBOS.startWorkflow(uploadPWF)(myFileRec, 60, { contentType: 'text/plain' });
|
|
270
|
+
// // Get the presigned post
|
|
271
|
+
// const ppost = await DBOS.getEvent<PresignedPost>(wfhandle.workflowID, 'uploadkey');
|
|
272
|
+
// // Upload to the URL
|
|
273
|
+
// try {
|
|
274
|
+
// const res = await uploadToS3(ppost!, './src/s3_utils.test.ts');
|
|
275
|
+
// expect(res.status.toString()[0]).toBe('2');
|
|
276
|
+
// } catch (e) {
|
|
277
|
+
// // You do NOT want to accidentally serialize an AxiosError - they don't!
|
|
278
|
+
// console.log('Caught something awful!', e);
|
|
279
|
+
// expect(e).toBeUndefined();
|
|
280
|
+
// }
|
|
281
|
+
// // Notify WF
|
|
282
|
+
// await DBOS.send<boolean>(wfhandle.workflowID, true, 'uploadfinish');
|
|
283
|
+
|
|
284
|
+
// // Wait for WF complete
|
|
285
|
+
// const _myFileRecord = await wfhandle.getResult();
|
|
286
|
+
|
|
287
|
+
// // Get the file out of DBOS (using a signed URL)
|
|
288
|
+
// const myurl = await getS3KeyUrl(myFileRec.key, 60);
|
|
289
|
+
// expect(myurl).not.toBeNull();
|
|
290
|
+
// // Get the file contents out of S3
|
|
291
|
+
// await downloadFromS3(myurl, './deleteme.xxx');
|
|
292
|
+
// expect(fs.existsSync('./deleteme.xxx')).toBeTruthy();
|
|
293
|
+
// fs.rmSync('./deleteme.xxx');
|
|
294
|
+
|
|
295
|
+
// // Delete the file contents out of DBOS (using the table index)
|
|
296
|
+
// const dfhandle = await deleteWF(myFileRec);
|
|
297
|
+
// expect(dfhandle).toBeDefined();
|
|
298
|
+
// }, 10000);
|
|
299
|
+
|
|
300
|
+
// async function getS3KeyContents(key: string) {
|
|
301
|
+
// return (
|
|
302
|
+
// await s3client!.send(
|
|
303
|
+
// new GetObjectCommand({
|
|
304
|
+
// Bucket: s3bucket!,
|
|
305
|
+
// Key: key,
|
|
306
|
+
// }),
|
|
307
|
+
// )
|
|
308
|
+
// ).Body?.transformToString();
|
|
309
|
+
// }
|
|
310
|
+
|
|
311
|
+
// async function getS3KeyUrl(key: string, expirationSecs: number) {
|
|
312
|
+
// const getObjectCommand = new GetObjectCommand({
|
|
313
|
+
// Bucket: s3bucket!,
|
|
314
|
+
// Key: key,
|
|
315
|
+
// });
|
|
316
|
+
|
|
317
|
+
// const presignedUrl = await getSignedUrl(s3client!, getObjectCommand, { expiresIn: expirationSecs });
|
|
318
|
+
// return presignedUrl;
|
|
319
|
+
// }
|
|
320
|
+
|
|
321
|
+
// async function uploadToS3(presignedPostData: PresignedPost, filePath: string) {
|
|
322
|
+
// const formData = new FormData();
|
|
323
|
+
|
|
324
|
+
// // Append all the fields from the presigned post data
|
|
325
|
+
// Object.keys(presignedPostData.fields).forEach((key) => {
|
|
326
|
+
// formData.append(key, presignedPostData.fields[key]);
|
|
327
|
+
// });
|
|
328
|
+
|
|
329
|
+
// // Append the file you want to upload
|
|
330
|
+
// const fileStream = fs.createReadStream(filePath);
|
|
331
|
+
// formData.append('file', fileStream);
|
|
332
|
+
|
|
333
|
+
// return await axios.post(presignedPostData.url, formData);
|
|
334
|
+
// }
|
|
335
|
+
|
|
336
|
+
// async function downloadFromS3(presignedGetUrl: string, outputPath: string) {
|
|
337
|
+
// const response: AxiosResponse<Readable> = await axios.get(presignedGetUrl, {
|
|
338
|
+
// responseType: 'stream', // Important to handle large files
|
|
339
|
+
// });
|
|
340
|
+
|
|
341
|
+
// // Use a write stream to save the file to the desired path
|
|
342
|
+
// const writer = fs.createWriteStream(outputPath);
|
|
343
|
+
// response.data.pipe(writer);
|
|
344
|
+
|
|
345
|
+
// return new Promise<void>((resolve, reject) => {
|
|
346
|
+
// writer.on('finish', resolve);
|
|
347
|
+
// writer.on('error', reject);
|
|
348
|
+
// });
|
|
349
|
+
// }
|
|
350
|
+
// });
|