@openstax/ts-utils 1.31.0 → 1.31.1

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.
@@ -38,7 +38,7 @@ export type ApiClientResponse<Ro> = Ro extends any ? {
38
38
  } : never;
39
39
  export type ExpandRoute<T> = T extends ((...args: infer A) => infer R) & {
40
40
  renderUrl: (...args: infer Ar) => Promise<string>;
41
- } ? (...args: A) => R & {
41
+ } ? ((...args: A) => R) & {
42
42
  renderUrl: (...args: Ar) => Promise<string>;
43
43
  } : never;
44
44
  export type MapRoutesToClient<Ru> = [Ru] extends [AnyRoute<Ru>] ? {
@@ -14,5 +14,16 @@ export interface FileServerAdapter {
14
14
  putFileContent: (source: FileValue, content: string) => Promise<FileValue>;
15
15
  getSignedViewerUrl: (source: FileValue) => Promise<string>;
16
16
  getFileContent: (source: FileValue) => Promise<Buffer>;
17
+ getSignedFileUploadConfig: () => Promise<{
18
+ url: string;
19
+ payload: {
20
+ [key: string]: string;
21
+ };
22
+ }>;
23
+ copyFileTo: (source: FileValue, destinationPath: string) => Promise<FileValue>;
24
+ copyFileToDirectory: (source: FileValue, destinationDirectory: string) => Promise<FileValue>;
25
+ isTemporaryUpload: (source: FileValue) => boolean;
26
+ getFileChecksum: (source: FileValue) => Promise<string>;
27
+ filesEqual: (sourceA: FileValue, sourceB: FileValue) => Promise<boolean>;
17
28
  }
18
29
  export declare const isFileOrFolder: (thing: any) => thing is FileValue | FolderValue;
@@ -5,18 +5,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.localFileServer = void 0;
7
7
  /* cspell:ignore originalname */
8
+ const crypto_1 = __importDefault(require("crypto"));
8
9
  const fs_1 = __importDefault(require("fs"));
9
10
  const https_1 = __importDefault(require("https"));
10
11
  const path_1 = __importDefault(require("path"));
11
12
  const cors_1 = __importDefault(require("cors"));
12
13
  const express_1 = __importDefault(require("express"));
13
14
  const multer_1 = __importDefault(require("multer"));
15
+ const uuid_1 = require("uuid");
14
16
  const assertions_1 = require("../../assertions");
15
17
  const config_1 = require("../../config");
16
18
  const guards_1 = require("../../guards");
17
19
  const helpers_1 = require("../../misc/helpers");
18
20
  /* istanbul ignore next */
19
- const startServer = (0, helpers_1.once)((port, uploadDir) => {
21
+ const startServer = (0, helpers_1.memoize)((port, uploadDir) => {
20
22
  // TODO - re-evaluate the `preservePath` behavior to match whatever s3 does
21
23
  const upload = (0, multer_1.default)({ dest: uploadDir, preservePath: true });
22
24
  const fileServerApp = (0, express_1.default)();
@@ -72,10 +74,55 @@ const localFileServer = (initializer) => (configProvider) => {
72
74
  await fs_1.default.promises.writeFile(filePath, content);
73
75
  return source;
74
76
  };
77
+ const getSignedFileUploadConfig = async () => {
78
+ const prefix = 'uploads/' + (0, uuid_1.v4)();
79
+ return {
80
+ url: `https://${await host}:${await port}/`,
81
+ payload: {
82
+ key: prefix + '/${filename}',
83
+ }
84
+ };
85
+ };
86
+ const copyFileTo = async (source, destinationPath) => {
87
+ const sourcePath = path_1.default.join(await fileDir, source.path);
88
+ const destPath = path_1.default.join(await fileDir, destinationPath);
89
+ const destDirectory = path_1.default.dirname(destPath);
90
+ await fs_1.default.promises.mkdir(destDirectory, { recursive: true });
91
+ await fs_1.default.promises.copyFile(sourcePath, destPath);
92
+ return {
93
+ ...source,
94
+ path: destinationPath
95
+ };
96
+ };
97
+ const copyFileToDirectory = async (source, destination) => {
98
+ const destinationPath = path_1.default.join(destination, source.label);
99
+ return copyFileTo(source, destinationPath);
100
+ };
101
+ const isTemporaryUpload = (source) => {
102
+ return source.path.indexOf('uploads/') === 0;
103
+ };
104
+ const getFileChecksum = async (source) => {
105
+ const filePath = path_1.default.join(await fileDir, source.path);
106
+ const fileContent = await fs_1.default.promises.readFile(filePath);
107
+ return crypto_1.default.createHash('md5').update(fileContent).digest('hex');
108
+ };
109
+ const filesEqual = async (sourceA, sourceB) => {
110
+ const [aSum, bSum] = await Promise.all([
111
+ getFileChecksum(sourceA),
112
+ getFileChecksum(sourceB)
113
+ ]);
114
+ return aSum === bSum;
115
+ };
75
116
  return {
76
117
  getSignedViewerUrl,
77
118
  getFileContent,
78
119
  putFileContent,
120
+ getSignedFileUploadConfig,
121
+ copyFileTo,
122
+ copyFileToDirectory,
123
+ isTemporaryUpload,
124
+ getFileChecksum,
125
+ filesEqual,
79
126
  };
80
127
  };
81
128
  exports.localFileServer = localFileServer;
@@ -1,9 +1,15 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.s3FileServer = void 0;
4
7
  /* cspell:ignore presigner */
5
8
  const client_s3_1 = require("@aws-sdk/client-s3");
9
+ const s3_presigned_post_1 = require("@aws-sdk/s3-presigned-post");
6
10
  const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
11
+ const path_1 = __importDefault(require("path"));
12
+ const uuid_1 = require("uuid");
7
13
  const __1 = require("../..");
8
14
  const assertions_1 = require("../../assertions");
9
15
  const config_1 = require("../../config");
@@ -44,10 +50,74 @@ const s3FileServer = (initializer) => (configProvider) => {
44
50
  await (await s3Service()).send(command);
45
51
  return source;
46
52
  };
53
+ /*
54
+ * https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_s3_presigned_post.html
55
+ * https://docs.aws.amazon.com/AmazonS3/latest/userguide/HTTPPOSTExamples.html
56
+ * https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
57
+ */
58
+ const getSignedFileUploadConfig = async () => {
59
+ const prefix = 'uploads/' + (0, uuid_1.v4)();
60
+ const bucket = (await bucketName());
61
+ const Conditions = [
62
+ { acl: 'private' },
63
+ { bucket },
64
+ ['starts-with', '$key', prefix]
65
+ ];
66
+ const defaultFields = {
67
+ acl: 'private',
68
+ };
69
+ const { url, fields } = await (0, s3_presigned_post_1.createPresignedPost)(await s3Service(), {
70
+ Bucket: bucket,
71
+ Key: prefix + '/${filename}',
72
+ Conditions,
73
+ Fields: defaultFields,
74
+ Expires: 3600, // 1 hour
75
+ });
76
+ return {
77
+ url, payload: fields
78
+ };
79
+ };
80
+ const copyFileTo = async (source, destinationPath) => {
81
+ const bucket = (await bucketName());
82
+ const destinationPathWithoutLeadingSlash = destinationPath.replace(/^\//, '');
83
+ const command = new client_s3_1.CopyObjectCommand({
84
+ Bucket: bucket,
85
+ Key: destinationPathWithoutLeadingSlash,
86
+ CopySource: path_1.default.join(bucket, source.path),
87
+ });
88
+ await (await s3Service()).send(command);
89
+ return {
90
+ ...source,
91
+ path: destinationPathWithoutLeadingSlash
92
+ };
93
+ };
94
+ const copyFileToDirectory = async (source, destination) => {
95
+ const destinationPath = path_1.default.join(destination, source.label);
96
+ return copyFileTo(source, destinationPath);
97
+ };
98
+ const isTemporaryUpload = (source) => {
99
+ return source.path.indexOf('uploads/') === 0;
100
+ };
101
+ const getFileChecksum = async (source) => {
102
+ const bucket = (await bucketName());
103
+ const command = new client_s3_1.HeadObjectCommand({ Bucket: bucket, Key: source.path });
104
+ const response = await (await s3Service()).send(command);
105
+ return (0, assertions_1.assertDefined)(response.ETag);
106
+ };
107
+ const filesEqual = async (sourceA, sourceB) => {
108
+ const [aSum, bSum] = await Promise.all([getFileChecksum(sourceA), getFileChecksum(sourceB)]);
109
+ return aSum === bSum;
110
+ };
47
111
  return {
48
112
  getFileContent,
49
113
  putFileContent,
50
114
  getSignedViewerUrl,
115
+ getSignedFileUploadConfig,
116
+ copyFileTo,
117
+ copyFileToDirectory,
118
+ isTemporaryUpload,
119
+ getFileChecksum,
120
+ filesEqual,
51
121
  };
52
122
  };
53
123
  exports.s3FileServer = s3FileServer;
@@ -32,7 +32,6 @@ const openSearchService = (initializer = {}) => (configProvider) => {
32
32
  sniffOnConnectionFault: true,
33
33
  sniffOnStart: true,
34
34
  resurrectStrategy: 'ping',
35
- agent: { keepAlive: false },
36
35
  node: await (0, config_1.resolveConfigValue)(config.node),
37
36
  }));
38
37
  return (indexConfig) => {