@bash-app/bash-common 10.7.0 → 10.9.0
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/README.md +8 -8
- package/package.json +45 -45
- package/prisma/schema.prisma +982 -983
- package/src/definitions.ts +443 -443
- package/src/extendedSchemas.ts +244 -244
- package/src/index.ts +10 -10
- package/src/utils/addressUtils.ts +173 -173
- package/src/utils/awsS3Utils.ts +100 -100
- package/src/utils/dateTimeUtils.ts +186 -186
- package/src/utils/paymentUtils.ts +56 -56
- package/src/utils/qrCodeUtils.ts +23 -23
- package/src/utils/recurrenceUtils.ts +175 -175
- package/src/utils/sortUtils.ts +28 -28
- package/src/utils/ticketListUtils.ts +71 -71
- package/tsconfig.json +19 -19
|
@@ -1,173 +1,173 @@
|
|
|
1
|
-
|
|
2
|
-
const ADDRESS_DELIM = '|';
|
|
3
|
-
|
|
4
|
-
const googleMapsApiKey = process.env.REACT_APP_GOOGLE_MAP_API_KEY as string;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export interface IAddress {
|
|
8
|
-
place: string;
|
|
9
|
-
street: string;
|
|
10
|
-
city: string;
|
|
11
|
-
state: string;
|
|
12
|
-
zipCode: string;
|
|
13
|
-
country: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function addressHasEnoughDataForGeolocation(address: IAddress): boolean {
|
|
17
|
-
return !!((address.place && address.city && address.state) || (address.street && address.city && address.state));
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function addressValuesToDatabaseAddressString(addressValues: IAddress): string {
|
|
21
|
-
const { place = '', street, city, state, zipCode, country } = addressValues;
|
|
22
|
-
return [place, street, city, state, zipCode, country].join(ADDRESS_DELIM);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function databaseAddressStringToAddressValues(addressString: string | undefined | null): IAddress {
|
|
26
|
-
if (addressString) {
|
|
27
|
-
const addressArray = addressString.split(ADDRESS_DELIM);
|
|
28
|
-
return {
|
|
29
|
-
place: addressArray[0],
|
|
30
|
-
street: addressArray[1],
|
|
31
|
-
city: addressArray[2],
|
|
32
|
-
state: addressArray[3],
|
|
33
|
-
zipCode: addressArray[4],
|
|
34
|
-
country: 'USA'
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return { place: '', street: '', city: '', state: '', zipCode: '', country: '' };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
export function databaseAddressStringToOneLineString(addressString: string | undefined | null): string {
|
|
42
|
-
if (addressString) {
|
|
43
|
-
const address = databaseAddressStringToAddressValues(addressString);
|
|
44
|
-
let addressArr = address.place ? [address.place] : [];
|
|
45
|
-
addressArr = [...addressArr, address.street, address.city, address.state];
|
|
46
|
-
const addressStr = addressArr.filter((str) => !!str).join(', ');
|
|
47
|
-
return `${addressStr} ${address.zipCode}`;
|
|
48
|
-
}
|
|
49
|
-
return '';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function databaseAddressStringToDisplayString(addressString: string | undefined | null): string {
|
|
53
|
-
if (addressString) {
|
|
54
|
-
const oneLineString = databaseAddressStringToOneLineString(addressString);
|
|
55
|
-
const formatted = oneLineString.replace(/([A-Z])(?=[A-Z][a-z])/g, "$1 ") // Add space between a single uppercase letter and a capitalized word
|
|
56
|
-
.replace(/(\d)(?=[A-Z])/g, "$1 ") // Add space between numbers and letters
|
|
57
|
-
.replace(/,/g, ", "); // Add space after commas
|
|
58
|
-
return formatted;
|
|
59
|
-
}
|
|
60
|
-
return '';
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
export async function getAddressFromCoordinates( lat: number, lng: number ): Promise<IAddress> {
|
|
65
|
-
const apiUrl = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lng}&key=${googleMapsApiKey}&loading=async`;
|
|
66
|
-
try {
|
|
67
|
-
const response = await fetch(apiUrl);
|
|
68
|
-
const data = await response.json();
|
|
69
|
-
if (data.results.length > 0) {
|
|
70
|
-
const addressComponents = data.results[0].address_components;
|
|
71
|
-
|
|
72
|
-
let street = "";
|
|
73
|
-
let city = "";
|
|
74
|
-
let state = "";
|
|
75
|
-
let zipCode = "";
|
|
76
|
-
|
|
77
|
-
addressComponents.forEach((component: any) => {
|
|
78
|
-
if (component.types.includes("route")) {
|
|
79
|
-
street = component.long_name;
|
|
80
|
-
} else if (component.types.includes("locality")) {
|
|
81
|
-
city = component.long_name;
|
|
82
|
-
} else if (component.types.includes("administrative_area_level_1")) {
|
|
83
|
-
state = component.short_name;
|
|
84
|
-
} else if (component.types.includes("postal_code")) {
|
|
85
|
-
zipCode = component.long_name;
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
return { place: '', street, city, state, zipCode, country: 'USA' };
|
|
90
|
-
} else {
|
|
91
|
-
throw new Error("No address found");
|
|
92
|
-
}
|
|
93
|
-
} catch (error) {
|
|
94
|
-
console.error(error);
|
|
95
|
-
return {
|
|
96
|
-
place: '',
|
|
97
|
-
street: "",
|
|
98
|
-
city: "",
|
|
99
|
-
state: "",
|
|
100
|
-
zipCode: "",
|
|
101
|
-
country: 'USA'
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function extractAddressComponents(place: any): IAddress {
|
|
107
|
-
const addressComponents = place.address_components;
|
|
108
|
-
let streetNumber = '';
|
|
109
|
-
let streetName = '';
|
|
110
|
-
let city = '';
|
|
111
|
-
let state = '';
|
|
112
|
-
let zipCode = '';
|
|
113
|
-
let country = 'USA';
|
|
114
|
-
|
|
115
|
-
addressComponents.forEach((component: any) => {
|
|
116
|
-
const types = component.types;
|
|
117
|
-
if (types.includes('street_number')) {
|
|
118
|
-
streetNumber = component.long_name;
|
|
119
|
-
}
|
|
120
|
-
if (types.includes('route')) {
|
|
121
|
-
streetName = component.long_name;
|
|
122
|
-
}
|
|
123
|
-
if (types.includes('locality')) {
|
|
124
|
-
city = component.long_name;
|
|
125
|
-
}
|
|
126
|
-
if (types.includes('administrative_area_level_1')) {
|
|
127
|
-
state = component.short_name;
|
|
128
|
-
}
|
|
129
|
-
if (types.includes('postal_code')) {
|
|
130
|
-
zipCode = component.long_name;
|
|
131
|
-
}
|
|
132
|
-
if (types.includes('country')) {
|
|
133
|
-
country = component.long_name;
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const street = `${streetNumber} ${streetName}`.trim();
|
|
138
|
-
|
|
139
|
-
return {
|
|
140
|
-
place: place.name || '',
|
|
141
|
-
street,
|
|
142
|
-
city,
|
|
143
|
-
state,
|
|
144
|
-
zipCode,
|
|
145
|
-
country
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
export async function getGeoCoordinatesFromAddress(address: IAddress): Promise<{lat: number, lng: number} | undefined> {
|
|
151
|
-
const addressStr = `${address.street}, ${address.city}, ${address.state} ${address.zipCode}, ${address.country}`;
|
|
152
|
-
|
|
153
|
-
const response = await fetch(
|
|
154
|
-
`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(addressStr)}&key=${googleMapsApiKey}`
|
|
155
|
-
);
|
|
156
|
-
if (!response.ok) {
|
|
157
|
-
console.error('Geocode response was not ok for address: ' + addressStr);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const data = await response.json();
|
|
161
|
-
|
|
162
|
-
if (data.status === 'OK') {
|
|
163
|
-
if (data.results?.length) {
|
|
164
|
-
return data.results[0].geometry.location;
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
console.error('Geocode results were empty with address: ' + addressStr);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
console.error(`Geocode was not successful with address: ${addressStr}\nfor the following reason: ${data.status}`);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
1
|
+
|
|
2
|
+
const ADDRESS_DELIM = '|';
|
|
3
|
+
|
|
4
|
+
const googleMapsApiKey = process.env.REACT_APP_GOOGLE_MAP_API_KEY as string;
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export interface IAddress {
|
|
8
|
+
place: string;
|
|
9
|
+
street: string;
|
|
10
|
+
city: string;
|
|
11
|
+
state: string;
|
|
12
|
+
zipCode: string;
|
|
13
|
+
country: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function addressHasEnoughDataForGeolocation(address: IAddress): boolean {
|
|
17
|
+
return !!((address.place && address.city && address.state) || (address.street && address.city && address.state));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function addressValuesToDatabaseAddressString(addressValues: IAddress): string {
|
|
21
|
+
const { place = '', street, city, state, zipCode, country } = addressValues;
|
|
22
|
+
return [place, street, city, state, zipCode, country].join(ADDRESS_DELIM);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function databaseAddressStringToAddressValues(addressString: string | undefined | null): IAddress {
|
|
26
|
+
if (addressString) {
|
|
27
|
+
const addressArray = addressString.split(ADDRESS_DELIM);
|
|
28
|
+
return {
|
|
29
|
+
place: addressArray[0],
|
|
30
|
+
street: addressArray[1],
|
|
31
|
+
city: addressArray[2],
|
|
32
|
+
state: addressArray[3],
|
|
33
|
+
zipCode: addressArray[4],
|
|
34
|
+
country: 'USA'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return { place: '', street: '', city: '', state: '', zipCode: '', country: '' };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export function databaseAddressStringToOneLineString(addressString: string | undefined | null): string {
|
|
42
|
+
if (addressString) {
|
|
43
|
+
const address = databaseAddressStringToAddressValues(addressString);
|
|
44
|
+
let addressArr = address.place ? [address.place] : [];
|
|
45
|
+
addressArr = [...addressArr, address.street, address.city, address.state];
|
|
46
|
+
const addressStr = addressArr.filter((str) => !!str).join(', ');
|
|
47
|
+
return `${addressStr} ${address.zipCode}`;
|
|
48
|
+
}
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function databaseAddressStringToDisplayString(addressString: string | undefined | null): string {
|
|
53
|
+
if (addressString) {
|
|
54
|
+
const oneLineString = databaseAddressStringToOneLineString(addressString);
|
|
55
|
+
const formatted = oneLineString.replace(/([A-Z])(?=[A-Z][a-z])/g, "$1 ") // Add space between a single uppercase letter and a capitalized word
|
|
56
|
+
.replace(/(\d)(?=[A-Z])/g, "$1 ") // Add space between numbers and letters
|
|
57
|
+
.replace(/,/g, ", "); // Add space after commas
|
|
58
|
+
return formatted;
|
|
59
|
+
}
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
export async function getAddressFromCoordinates( lat: number, lng: number ): Promise<IAddress> {
|
|
65
|
+
const apiUrl = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lng}&key=${googleMapsApiKey}&loading=async`;
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(apiUrl);
|
|
68
|
+
const data = await response.json();
|
|
69
|
+
if (data.results.length > 0) {
|
|
70
|
+
const addressComponents = data.results[0].address_components;
|
|
71
|
+
|
|
72
|
+
let street = "";
|
|
73
|
+
let city = "";
|
|
74
|
+
let state = "";
|
|
75
|
+
let zipCode = "";
|
|
76
|
+
|
|
77
|
+
addressComponents.forEach((component: any) => {
|
|
78
|
+
if (component.types.includes("route")) {
|
|
79
|
+
street = component.long_name;
|
|
80
|
+
} else if (component.types.includes("locality")) {
|
|
81
|
+
city = component.long_name;
|
|
82
|
+
} else if (component.types.includes("administrative_area_level_1")) {
|
|
83
|
+
state = component.short_name;
|
|
84
|
+
} else if (component.types.includes("postal_code")) {
|
|
85
|
+
zipCode = component.long_name;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return { place: '', street, city, state, zipCode, country: 'USA' };
|
|
90
|
+
} else {
|
|
91
|
+
throw new Error("No address found");
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error(error);
|
|
95
|
+
return {
|
|
96
|
+
place: '',
|
|
97
|
+
street: "",
|
|
98
|
+
city: "",
|
|
99
|
+
state: "",
|
|
100
|
+
zipCode: "",
|
|
101
|
+
country: 'USA'
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function extractAddressComponents(place: any): IAddress {
|
|
107
|
+
const addressComponents = place.address_components;
|
|
108
|
+
let streetNumber = '';
|
|
109
|
+
let streetName = '';
|
|
110
|
+
let city = '';
|
|
111
|
+
let state = '';
|
|
112
|
+
let zipCode = '';
|
|
113
|
+
let country = 'USA';
|
|
114
|
+
|
|
115
|
+
addressComponents.forEach((component: any) => {
|
|
116
|
+
const types = component.types;
|
|
117
|
+
if (types.includes('street_number')) {
|
|
118
|
+
streetNumber = component.long_name;
|
|
119
|
+
}
|
|
120
|
+
if (types.includes('route')) {
|
|
121
|
+
streetName = component.long_name;
|
|
122
|
+
}
|
|
123
|
+
if (types.includes('locality')) {
|
|
124
|
+
city = component.long_name;
|
|
125
|
+
}
|
|
126
|
+
if (types.includes('administrative_area_level_1')) {
|
|
127
|
+
state = component.short_name;
|
|
128
|
+
}
|
|
129
|
+
if (types.includes('postal_code')) {
|
|
130
|
+
zipCode = component.long_name;
|
|
131
|
+
}
|
|
132
|
+
if (types.includes('country')) {
|
|
133
|
+
country = component.long_name;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const street = `${streetNumber} ${streetName}`.trim();
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
place: place.name || '',
|
|
141
|
+
street,
|
|
142
|
+
city,
|
|
143
|
+
state,
|
|
144
|
+
zipCode,
|
|
145
|
+
country
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
export async function getGeoCoordinatesFromAddress(address: IAddress): Promise<{lat: number, lng: number} | undefined> {
|
|
151
|
+
const addressStr = `${address.street}, ${address.city}, ${address.state} ${address.zipCode}, ${address.country}`;
|
|
152
|
+
|
|
153
|
+
const response = await fetch(
|
|
154
|
+
`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(addressStr)}&key=${googleMapsApiKey}`
|
|
155
|
+
);
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
console.error('Geocode response was not ok for address: ' + addressStr);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const data = await response.json();
|
|
161
|
+
|
|
162
|
+
if (data.status === 'OK') {
|
|
163
|
+
if (data.results?.length) {
|
|
164
|
+
return data.results[0].geometry.location;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
console.error('Geocode results were empty with address: ' + addressStr);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
console.error(`Geocode was not successful with address: ${addressStr}\nfor the following reason: ${data.status}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
package/src/utils/awsS3Utils.ts
CHANGED
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
import {DeleteObjectCommand, PutObjectCommand, S3Client} from "@aws-sdk/client-s3";
|
|
2
|
-
import {getSignedUrl} from "@aws-sdk/s3-request-presigner";
|
|
3
|
-
import {DeleteObjectCommandOutput} from "@aws-sdk/client-s3/dist-types/commands";
|
|
4
|
-
|
|
5
|
-
const BASE_64_IMAGE_REGEX = /^data:image\/\w+;base64,/;
|
|
6
|
-
|
|
7
|
-
export class AwsS3Utils {
|
|
8
|
-
static async uploadBase64String(base64Str: string, assetKey: string): Promise<string | undefined> {
|
|
9
|
-
const bufferData = Buffer.from(base64Str.replace(BASE_64_IMAGE_REGEX, ""), 'base64');
|
|
10
|
-
const blobData = new Blob([bufferData]);
|
|
11
|
-
return AwsS3Utils.uploadFile(blobData, assetKey, 'image/jpeg');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
static async uploadFile(file: Blob, assetKey: string, mimetype: string): Promise<string | undefined> {
|
|
15
|
-
const preSignedUrl = await AwsS3Utils.createPreSignedUrl(assetKey, mimetype);
|
|
16
|
-
const assetUrl = AwsS3Utils.createUrlFromAssetKey(assetKey);
|
|
17
|
-
const uploadSuccessful = await AwsS3Utils.uploadFileViaPreSignedUrl(file, preSignedUrl, mimetype);
|
|
18
|
-
if (uploadSuccessful) {
|
|
19
|
-
return assetUrl;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
static async uploadFileViaPreSignedUrl(file: Blob,
|
|
24
|
-
preSignedUrl: string | undefined,
|
|
25
|
-
mimetype: string): Promise<boolean> {
|
|
26
|
-
try {
|
|
27
|
-
if (!file) {
|
|
28
|
-
throw new Error('The file data buffer was undefined; no data to upload.');
|
|
29
|
-
}
|
|
30
|
-
if (!preSignedUrl) {
|
|
31
|
-
throw new Error('The pre-signed url for uploading the file was undefined');
|
|
32
|
-
}
|
|
33
|
-
console.log(`Uploading with url: ${preSignedUrl}`);
|
|
34
|
-
const res = await fetch(preSignedUrl, {
|
|
35
|
-
method: 'PUT',
|
|
36
|
-
headers: {
|
|
37
|
-
'Content-Type': mimetype
|
|
38
|
-
},
|
|
39
|
-
body: file
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
if (!res.ok) {
|
|
43
|
-
throw new Error(`Response was not ok when uploading media file.\nStatus, text: ${res.status}, ${res.statusText}`);
|
|
44
|
-
}
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
catch (e) {
|
|
48
|
-
console.error(`Error when uploading file to: ${preSignedUrl}\n${e}`);
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
static createUrlFromAssetKey(assetKey: string): string {
|
|
54
|
-
if (!assetKey) {
|
|
55
|
-
throw new Error(`Cannot create a url with an assetKey that is undefined`);
|
|
56
|
-
}
|
|
57
|
-
return `https://${process.env.CLOUDFRONT_ASSETS_URL}/${assetKey}`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
static async createPreSignedUrl(key: string | undefined, mimetype: string | undefined): Promise<string> {
|
|
61
|
-
if (!key || !mimetype) {
|
|
62
|
-
throw new Error(`Cannot create a pre-signed url without a key or a mimetype`);
|
|
63
|
-
}
|
|
64
|
-
const client = new S3Client({
|
|
65
|
-
credentials: {
|
|
66
|
-
accessKeyId: String(process.env.AWS_S3_ACCESS_KEY_ID),
|
|
67
|
-
secretAccessKey: String(process.env.AWS_S3_SECRET_ACCESS_KEY),
|
|
68
|
-
},
|
|
69
|
-
region: String(process.env.AWS_S3_REGION),
|
|
70
|
-
});
|
|
71
|
-
const command = new PutObjectCommand({
|
|
72
|
-
Bucket: String(process.env.AWS_S3_ASSETS_BUCKET_NAME),
|
|
73
|
-
Key: key,
|
|
74
|
-
ContentType: mimetype
|
|
75
|
-
});
|
|
76
|
-
return getSignedUrl(client, command, {
|
|
77
|
-
expiresIn: 3600,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
static async deleteFile(key: string): Promise<DeleteObjectCommandOutput | undefined> {
|
|
82
|
-
try {
|
|
83
|
-
const command = new DeleteObjectCommand({
|
|
84
|
-
Bucket: String(process.env.AWS_S3_BUCKET_NAME),
|
|
85
|
-
Key: key,
|
|
86
|
-
});
|
|
87
|
-
const client = new S3Client({
|
|
88
|
-
credentials: {
|
|
89
|
-
accessKeyId: String(process.env.AWS_S3_ACCESS_KEY_ID),
|
|
90
|
-
secretAccessKey: String(process.env.AWS_S3_SECRET_ACCESS_KEY),
|
|
91
|
-
},
|
|
92
|
-
region: String(process.env.AWS_S3_REGION),
|
|
93
|
-
});
|
|
94
|
-
return client.send(command);
|
|
95
|
-
}
|
|
96
|
-
catch (e: any) {
|
|
97
|
-
console.error(e);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
1
|
+
import {DeleteObjectCommand, PutObjectCommand, S3Client} from "@aws-sdk/client-s3";
|
|
2
|
+
import {getSignedUrl} from "@aws-sdk/s3-request-presigner";
|
|
3
|
+
import {DeleteObjectCommandOutput} from "@aws-sdk/client-s3/dist-types/commands";
|
|
4
|
+
|
|
5
|
+
const BASE_64_IMAGE_REGEX = /^data:image\/\w+;base64,/;
|
|
6
|
+
|
|
7
|
+
export class AwsS3Utils {
|
|
8
|
+
static async uploadBase64String(base64Str: string, assetKey: string): Promise<string | undefined> {
|
|
9
|
+
const bufferData = Buffer.from(base64Str.replace(BASE_64_IMAGE_REGEX, ""), 'base64');
|
|
10
|
+
const blobData = new Blob([bufferData]);
|
|
11
|
+
return AwsS3Utils.uploadFile(blobData, assetKey, 'image/jpeg');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static async uploadFile(file: Blob, assetKey: string, mimetype: string): Promise<string | undefined> {
|
|
15
|
+
const preSignedUrl = await AwsS3Utils.createPreSignedUrl(assetKey, mimetype);
|
|
16
|
+
const assetUrl = AwsS3Utils.createUrlFromAssetKey(assetKey);
|
|
17
|
+
const uploadSuccessful = await AwsS3Utils.uploadFileViaPreSignedUrl(file, preSignedUrl, mimetype);
|
|
18
|
+
if (uploadSuccessful) {
|
|
19
|
+
return assetUrl;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static async uploadFileViaPreSignedUrl(file: Blob,
|
|
24
|
+
preSignedUrl: string | undefined,
|
|
25
|
+
mimetype: string): Promise<boolean> {
|
|
26
|
+
try {
|
|
27
|
+
if (!file) {
|
|
28
|
+
throw new Error('The file data buffer was undefined; no data to upload.');
|
|
29
|
+
}
|
|
30
|
+
if (!preSignedUrl) {
|
|
31
|
+
throw new Error('The pre-signed url for uploading the file was undefined');
|
|
32
|
+
}
|
|
33
|
+
console.log(`Uploading with url: ${preSignedUrl}`);
|
|
34
|
+
const res = await fetch(preSignedUrl, {
|
|
35
|
+
method: 'PUT',
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': mimetype
|
|
38
|
+
},
|
|
39
|
+
body: file
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
throw new Error(`Response was not ok when uploading media file.\nStatus, text: ${res.status}, ${res.statusText}`);
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
console.error(`Error when uploading file to: ${preSignedUrl}\n${e}`);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
static createUrlFromAssetKey(assetKey: string): string {
|
|
54
|
+
if (!assetKey) {
|
|
55
|
+
throw new Error(`Cannot create a url with an assetKey that is undefined`);
|
|
56
|
+
}
|
|
57
|
+
return `https://${process.env.CLOUDFRONT_ASSETS_URL}/${assetKey}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static async createPreSignedUrl(key: string | undefined, mimetype: string | undefined): Promise<string> {
|
|
61
|
+
if (!key || !mimetype) {
|
|
62
|
+
throw new Error(`Cannot create a pre-signed url without a key or a mimetype`);
|
|
63
|
+
}
|
|
64
|
+
const client = new S3Client({
|
|
65
|
+
credentials: {
|
|
66
|
+
accessKeyId: String(process.env.AWS_S3_ACCESS_KEY_ID),
|
|
67
|
+
secretAccessKey: String(process.env.AWS_S3_SECRET_ACCESS_KEY),
|
|
68
|
+
},
|
|
69
|
+
region: String(process.env.AWS_S3_REGION),
|
|
70
|
+
});
|
|
71
|
+
const command = new PutObjectCommand({
|
|
72
|
+
Bucket: String(process.env.AWS_S3_ASSETS_BUCKET_NAME),
|
|
73
|
+
Key: key,
|
|
74
|
+
ContentType: mimetype
|
|
75
|
+
});
|
|
76
|
+
return getSignedUrl(client, command, {
|
|
77
|
+
expiresIn: 3600,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static async deleteFile(key: string): Promise<DeleteObjectCommandOutput | undefined> {
|
|
82
|
+
try {
|
|
83
|
+
const command = new DeleteObjectCommand({
|
|
84
|
+
Bucket: String(process.env.AWS_S3_BUCKET_NAME),
|
|
85
|
+
Key: key,
|
|
86
|
+
});
|
|
87
|
+
const client = new S3Client({
|
|
88
|
+
credentials: {
|
|
89
|
+
accessKeyId: String(process.env.AWS_S3_ACCESS_KEY_ID),
|
|
90
|
+
secretAccessKey: String(process.env.AWS_S3_SECRET_ACCESS_KEY),
|
|
91
|
+
},
|
|
92
|
+
region: String(process.env.AWS_S3_REGION),
|
|
93
|
+
});
|
|
94
|
+
return client.send(command);
|
|
95
|
+
}
|
|
96
|
+
catch (e: any) {
|
|
97
|
+
console.error(e);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|